Compare commits
No commits in common. "main" and "v1.2.0" have entirely different histories.
@ -1,39 +0,0 @@
|
|||||||
name: Gitea Build Check
|
|
||||||
run-name: ${{ gitea.actor }} is testing the build
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
pull_request:
|
|
||||||
branches: [ "main" ]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
Build:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
|
|
||||||
|
|
||||||
- name: Setup Go environment
|
|
||||||
uses: https://github.com/actions/setup-go@v5
|
|
||||||
with:
|
|
||||||
# The Go version to download (if necessary) and use. Supports semver spec and ranges.
|
|
||||||
go-version: 1.22.0 # optional
|
|
||||||
# Path to the go.mod file.
|
|
||||||
go-version-file: ./go.mod # optional
|
|
||||||
# Set this option to true if you want the action to always check for the latest available version that satisfies the version spec
|
|
||||||
check-latest: true # optional
|
|
||||||
# Used to specify whether caching is needed. Set to true, if you'd like to enable caching.
|
|
||||||
cache: true # optional
|
|
||||||
|
|
||||||
- name: Execute Go Test files with coverage report
|
|
||||||
run: TESTCONTAINERS_RYUK_DISABLED=true go test -v ./... -json -coverprofile="coverage.out" | tee "test-report.out"
|
|
||||||
|
|
||||||
- uses: sonarsource/sonarqube-scan-action@master
|
|
||||||
env:
|
|
||||||
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
|
||||||
SONAR_HOST_URL: ${{ vars.SONAR_HOST_URL }}
|
|
||||||
with:
|
|
||||||
args: >
|
|
||||||
-Dsonar.projectKey=Anthrove---OtterSpace-SDK
|
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -192,4 +192,3 @@ $RECYCLE.BIN/
|
|||||||
|
|
||||||
|
|
||||||
.env
|
.env
|
||||||
main.go
|
|
||||||
|
30
README.md
30
README.md
@ -1,18 +1,3 @@
|
|||||||
![Build Check Runner](https://git.dragse.it/anthrove/otter-space-sdk/v2/actions/workflows/build_check.yaml/badge.svg)
|
|
||||||
[![Bugs](https://sonarqube.dragse.de/api/project_badges/measure?project=Anthrove---OtterSpace-SDK&metric=bugs&token=sqb_96012ffdd64ce721d7f9c82bfa77aa27a5c1fd38)](https://sonarqube.dragse.de/dashboard?id=Anthrove---OtterSpace-SDK)
|
|
||||||
[![Code Smells](https://sonarqube.dragse.de/api/project_badges/measure?project=Anthrove---OtterSpace-SDK&metric=code_smells&token=sqb_96012ffdd64ce721d7f9c82bfa77aa27a5c1fd38)](https://sonarqube.dragse.de/dashboard?id=Anthrove---OtterSpace-SDK)
|
|
||||||
[![Coverage](https://sonarqube.dragse.de/api/project_badges/measure?project=Anthrove---OtterSpace-SDK&metric=coverage&token=sqb_96012ffdd64ce721d7f9c82bfa77aa27a5c1fd38)](https://sonarqube.dragse.de/dashboard?id=Anthrove---OtterSpace-SDK)
|
|
||||||
|
|
||||||
[![Duplicated Lines (%)](https://sonarqube.dragse.de/api/project_badges/measure?project=Anthrove---OtterSpace-SDK&metric=duplicated_lines_density&token=sqb_96012ffdd64ce721d7f9c82bfa77aa27a5c1fd38)](https://sonarqube.dragse.de/dashboard?id=Anthrove---OtterSpace-SDK)
|
|
||||||
[![Lines of Code](https://sonarqube.dragse.de/api/project_badges/measure?project=Anthrove---OtterSpace-SDK&metric=ncloc&token=sqb_96012ffdd64ce721d7f9c82bfa77aa27a5c1fd38)](https://sonarqube.dragse.de/dashboard?id=Anthrove---OtterSpace-SDK)
|
|
||||||
[![Maintainability Rating](https://sonarqube.dragse.de/api/project_badges/measure?project=Anthrove---OtterSpace-SDK&metric=sqale_rating&token=sqb_96012ffdd64ce721d7f9c82bfa77aa27a5c1fd38)](https://sonarqube.dragse.de/dashboard?id=Anthrove---OtterSpace-SDK)
|
|
||||||
[![Quality Gate Status](https://sonarqube.dragse.de/api/project_badges/measure?project=Anthrove---OtterSpace-SDK&metric=alert_status&token=sqb_96012ffdd64ce721d7f9c82bfa77aa27a5c1fd38)](https://sonarqube.dragse.de/dashboard?id=Anthrove---OtterSpace-SDK)
|
|
||||||
|
|
||||||
[![Reliability Rating](https://sonarqube.dragse.de/api/project_badges/measure?project=Anthrove---OtterSpace-SDK&metric=reliability_rating&token=sqb_96012ffdd64ce721d7f9c82bfa77aa27a5c1fd38)](https://sonarqube.dragse.de/dashboard?id=Anthrove---OtterSpace-SDK)
|
|
||||||
[![Security Hotspots](https://sonarqube.dragse.de/api/project_badges/measure?project=Anthrove---OtterSpace-SDK&metric=security_hotspots&token=sqb_96012ffdd64ce721d7f9c82bfa77aa27a5c1fd38)](https://sonarqube.dragse.de/dashboard?id=Anthrove---OtterSpace-SDK)
|
|
||||||
[![Security Rating](https://sonarqube.dragse.de/api/project_badges/measure?project=Anthrove---OtterSpace-SDK&metric=security_rating&token=sqb_96012ffdd64ce721d7f9c82bfa77aa27a5c1fd38)](https://sonarqube.dragse.de/dashboard?id=Anthrove---OtterSpace-SDK)
|
|
||||||
[![Vulnerabilities](https://sonarqube.dragse.de/api/project_badges/measure?project=Anthrove---OtterSpace-SDK&metric=vulnerabilities&token=sqb_96012ffdd64ce721d7f9c82bfa77aa27a5c1fd38)](https://sonarqube.dragse.de/dashboard?id=Anthrove---OtterSpace-SDK)
|
|
||||||
|
|
||||||
# OtterSpace SDK
|
# OtterSpace SDK
|
||||||
|
|
||||||
The OtterSpace SDK is a Go package for interacting with the OtterSpace API. It provides methods for connecting to the API, adding and linking users, posts, and sources, and retrieving information about users and posts.
|
The OtterSpace SDK is a Go package for interacting with the OtterSpace API. It provides methods for connecting to the API, adding and linking users, posts, and sources, and retrieving information about users and posts.
|
||||||
@ -22,7 +7,7 @@ The OtterSpace SDK is a Go package for interacting with the OtterSpace API. It p
|
|||||||
To install the OtterSpace SDK, you can use `go get`:
|
To install the OtterSpace SDK, you can use `go get`:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
go get git.dragse.it/anthrove/otter-space-sdk/v2
|
go get git.dragse.it/anthrove/otter-space-sdk
|
||||||
````
|
````
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
@ -34,18 +19,13 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"git.dragse.it/anthrove/otter-space-sdk/pkg/models"
|
||||||
"git.dragse.it/anthrove/otter-space-sdk/v2/pkg/database"
|
"git.dragse.it/anthrove/otter-space-sdk/pkg/graph"
|
||||||
"git.dragse.it/anthrove/otter-space-sdk/v2/pkg/models"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var err error
|
client := graph.NewGraphConnection()
|
||||||
dbDebug := false
|
err := client.Connect(context.Background(), "your-endpoint", "your-username", "your-password")
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
pgClient := database.NewPostgresqlConnection(dbDebug)
|
|
||||||
err = pgClient.Connect(ctx, "your-endpoint", "your-username", "your-password", "anthrove", 5432, "disable", "Europe/Berlin")
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
return
|
return
|
||||||
|
69
go.mod
69
go.mod
@ -1,73 +1,10 @@
|
|||||||
module git.dragse.it/anthrove/otter-space-sdk/v2
|
module git.dragse.it/anthrove/otter-space-sdk
|
||||||
|
|
||||||
go 1.22.0
|
go 1.22.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/lib/pq v1.10.9
|
github.com/neo4j/neo4j-go-driver/v5 v5.17.0
|
||||||
github.com/matoous/go-nanoid/v2 v2.1.0
|
|
||||||
github.com/rubenv/sql-migrate v1.6.1
|
|
||||||
github.com/sirupsen/logrus v1.9.3
|
github.com/sirupsen/logrus v1.9.3
|
||||||
github.com/testcontainers/testcontainers-go v0.31.0
|
|
||||||
github.com/testcontainers/testcontainers-go/modules/postgres v0.31.0
|
|
||||||
gorm.io/driver/postgres v1.5.9
|
|
||||||
gorm.io/gorm v1.25.10
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
|
||||||
dario.cat/mergo v1.0.0 // indirect
|
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
|
|
||||||
github.com/Microsoft/go-winio v0.6.1 // indirect
|
|
||||||
github.com/Microsoft/hcsshim v0.11.4 // indirect
|
|
||||||
github.com/cenkalti/backoff/v4 v4.2.1 // indirect
|
|
||||||
github.com/containerd/containerd v1.7.15 // indirect
|
|
||||||
github.com/containerd/log v0.1.0 // indirect
|
|
||||||
github.com/cpuguy83/dockercfg v0.3.1 // indirect
|
|
||||||
github.com/distribution/reference v0.5.0 // indirect
|
|
||||||
github.com/docker/docker v25.0.5+incompatible // indirect
|
|
||||||
github.com/docker/go-connections v0.5.0 // indirect
|
|
||||||
github.com/docker/go-units v0.5.0 // indirect
|
|
||||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
|
||||||
github.com/go-gorp/gorp/v3 v3.1.0 // indirect
|
|
||||||
github.com/go-logr/logr v1.4.1 // indirect
|
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
|
||||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
|
||||||
github.com/golang/protobuf v1.5.4 // indirect
|
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
|
||||||
github.com/jackc/pgx/v5 v5.5.5 // indirect
|
|
||||||
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
|
||||||
github.com/klauspost/compress v1.16.0 // indirect
|
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
|
||||||
github.com/magiconair/properties v1.8.7 // indirect
|
|
||||||
github.com/moby/patternmatcher v0.6.0 // indirect
|
|
||||||
github.com/moby/sys/sequential v0.5.0 // indirect
|
|
||||||
github.com/moby/sys/user v0.1.0 // indirect
|
|
||||||
github.com/moby/term v0.5.0 // indirect
|
|
||||||
github.com/morikuni/aec v1.0.0 // indirect
|
|
||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
|
||||||
github.com/opencontainers/image-spec v1.1.0 // indirect
|
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
|
||||||
github.com/shirou/gopsutil/v3 v3.23.12 // indirect
|
|
||||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
|
||||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
|
||||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
|
||||||
github.com/yusufpapurcu/wmi v1.2.3 // indirect
|
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
|
||||||
go.opentelemetry.io/otel v1.24.0 // indirect
|
|
||||||
go.opentelemetry.io/otel/metric v1.24.0 // indirect
|
|
||||||
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
|
||||||
golang.org/x/crypto v0.22.0 // indirect
|
|
||||||
golang.org/x/mod v0.16.0 // indirect
|
|
||||||
golang.org/x/sync v0.7.0 // indirect
|
|
||||||
golang.org/x/sys v0.19.0 // indirect
|
|
||||||
golang.org/x/text v0.14.0 // indirect
|
|
||||||
golang.org/x/tools v0.13.0 // indirect
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d // indirect
|
|
||||||
google.golang.org/grpc v1.58.3 // indirect
|
|
||||||
google.golang.org/protobuf v1.33.0 // indirect
|
|
||||||
)
|
|
||||||
|
222
go.sum
222
go.sum
@ -1,229 +1,17 @@
|
|||||||
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
|
|
||||||
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
|
||||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
|
|
||||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
|
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
|
||||||
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
|
|
||||||
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
|
|
||||||
github.com/Microsoft/hcsshim v0.11.4 h1:68vKo2VN8DE9AdN4tnkWnmdhqdbpUFM8OF3Airm7fz8=
|
|
||||||
github.com/Microsoft/hcsshim v0.11.4/go.mod h1:smjE4dvqPX9Zldna+t5FG3rnoHhaB7QYxPRqGcpAD9w=
|
|
||||||
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
|
||||||
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
|
||||||
github.com/containerd/containerd v1.7.15 h1:afEHXdil9iAm03BmhjzKyXnnEBtjaLJefdU7DV0IFes=
|
|
||||||
github.com/containerd/containerd v1.7.15/go.mod h1:ISzRRTMF8EXNpJlTzyr2XMhN+j9K302C21/+cr3kUnY=
|
|
||||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
|
||||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
|
||||||
github.com/cpuguy83/dockercfg v0.3.1 h1:/FpZ+JaygUR/lZP2NlFI2DVfrOEMAIKP5wWEJdoYe9E=
|
|
||||||
github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
|
|
||||||
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
|
||||||
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
|
github.com/neo4j/neo4j-go-driver/v5 v5.17.0 h1:Bdqg1Y8Hd3uLYToXtBjysDYXTdMiP7zeUNUEwfbJkSo=
|
||||||
github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
github.com/neo4j/neo4j-go-driver/v5 v5.17.0/go.mod h1:Vff8OwT7QpLm7L2yYr85XNWe9Rbqlbeb9asNXJTHO4k=
|
||||||
github.com/docker/docker v25.0.5+incompatible h1:UmQydMduGkrD5nQde1mecF/YnSbTOaPeFIeP5C4W+DE=
|
|
||||||
github.com/docker/docker v25.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
|
||||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
|
||||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
|
||||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
|
||||||
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
|
|
||||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
|
||||||
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
|
||||||
github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs=
|
|
||||||
github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw=
|
|
||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
|
||||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
|
||||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
|
||||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
|
||||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
|
||||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
|
||||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
|
||||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
|
||||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
|
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
|
|
||||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
|
||||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
|
||||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
|
||||||
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
|
|
||||||
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
|
|
||||||
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
|
||||||
github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
|
||||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
|
||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
|
||||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
|
||||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
|
||||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
|
||||||
github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4=
|
|
||||||
github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
|
||||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
|
||||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
|
||||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
|
||||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
|
||||||
github.com/matoous/go-nanoid/v2 v2.1.0 h1:P64+dmq21hhWdtvZfEAofnvJULaRR1Yib0+PnU669bE=
|
|
||||||
github.com/matoous/go-nanoid/v2 v2.1.0/go.mod h1:KlbGNQ+FhrUNIHUxZdL63t7tl4LaPkZNpUULS8H4uVM=
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.19 h1:fhGleo2h1p8tVChob4I9HpmVFIAkKGpiukdrgQbWfGI=
|
|
||||||
github.com/mattn/go-sqlite3 v1.14.19/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
|
||||||
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
|
|
||||||
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
|
||||||
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
|
|
||||||
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
|
|
||||||
github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg=
|
|
||||||
github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU=
|
|
||||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
|
||||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
|
||||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
|
||||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
|
||||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
|
||||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
|
||||||
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
|
||||||
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
|
||||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
|
||||||
github.com/poy/onpar v1.1.2 h1:QaNrNiZx0+Nar5dLgTVp5mXkyoVFIbepjyEoGSnhbAY=
|
|
||||||
github.com/poy/onpar v1.1.2/go.mod h1:6X8FLNoxyr9kkmnlqpK6LSoiOtrO6MICtWwEuWkLjzg=
|
|
||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
|
||||||
github.com/rubenv/sql-migrate v1.6.1 h1:bo6/sjsan9HaXAsNxYP/jCEDUGibHp8JmOBw7NTGRos=
|
|
||||||
github.com/rubenv/sql-migrate v1.6.1/go.mod h1:tPzespupJS0jacLfhbwto/UjSX+8h2FdWB7ar+QlHa0=
|
|
||||||
github.com/shirou/gopsutil/v3 v3.23.12 h1:z90NtUkp3bMtmICZKpC4+WaknU1eXtp5vtbQ11DgpE4=
|
|
||||||
github.com/shirou/gopsutil/v3 v3.23.12/go.mod h1:1FrWgea594Jp7qmjHUUPlJDTPgcsb9mGnXDxavtikzM=
|
|
||||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
|
||||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
|
||||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
|
||||||
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
|
||||||
github.com/testcontainers/testcontainers-go v0.31.0 h1:W0VwIhcEVhRflwL9as3dhY6jXjVCA27AkmbnZ+UTh3U=
|
|
||||||
github.com/testcontainers/testcontainers-go v0.31.0/go.mod h1:D2lAoA0zUFiSY+eAflqK5mcUx/A5hrrORaEQrd0SefI=
|
|
||||||
github.com/testcontainers/testcontainers-go/modules/postgres v0.31.0 h1:isAwFS3KNKRbJMbWv+wolWqOFUECmjYZ+sIRZCIBc/E=
|
|
||||||
github.com/testcontainers/testcontainers-go/modules/postgres v0.31.0/go.mod h1:ZNYY8vumNCEG9YI59A9d6/YaMY49uwRhmeU563EzFGw=
|
|
||||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
|
||||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
|
||||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
|
||||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
|
|
||||||
github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
|
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
|
|
||||||
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
|
|
||||||
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0 h1:Mne5On7VWdx7omSrSSZvM4Kw7cS7NQkOOmLcgscI51U=
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.19.0/go.mod h1:IPtUMKL4O3tH5y+iXVyAXqpAwMuzC1IrxVS81rummfE=
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
|
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
|
|
||||||
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
|
|
||||||
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
|
|
||||||
go.opentelemetry.io/otel/sdk v1.19.0 h1:6USY6zH+L8uMH8L3t1enZPR3WFEmSTADlqldyHtJi3o=
|
|
||||||
go.opentelemetry.io/otel/sdk v1.19.0/go.mod h1:NedEbbS4w3C6zElbLdPJKOpJQOrGUJ+GfzpjUvI0v1A=
|
|
||||||
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
|
|
||||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
|
||||||
go.opentelemetry.io/proto/otlp v1.0.0 h1:T0TX0tmXU8a3CbNXzEKGeU5mIVOdf0oykP+u2lIVU/I=
|
|
||||||
go.opentelemetry.io/proto/otlp v1.0.0/go.mod h1:Sy6pihPLfYHkr3NkUbEhGHFhINUSI/v80hjKIs5JXpM=
|
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
|
||||||
golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30=
|
|
||||||
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
|
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
|
|
||||||
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
|
||||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
|
||||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
|
|
||||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
|
|
||||||
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
|
||||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
|
||||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
|
||||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
|
|
||||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g=
|
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98 h1:FmF5cCW94Ij59cfpoLiwTgodWmm60eEV0CjlsVg2fuw=
|
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20230711160842-782d3b101e98/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d h1:pgIUhmqwKOUlnKna4r6amKdUngdL8DrkpFeV8+VBElY=
|
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20230731190214-cbb8c96f2d6d/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM=
|
|
||||||
google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ=
|
|
||||||
google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0=
|
|
||||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
|
||||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
||||||
gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8=
|
|
||||||
gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
|
|
||||||
gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s=
|
|
||||||
gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
|
||||||
gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY=
|
|
||||||
gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
|
||||||
|
46
internal/logger.go
Normal file
46
internal/logger.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
neo4jLog "github.com/neo4j/neo4j-go-driver/v5/neo4j/log"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type graphLogger struct {
|
||||||
|
graphDebug bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGraphLogger(graphDebug bool) neo4jLog.Logger {
|
||||||
|
return &graphLogger{graphDebug: graphDebug}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n graphLogger) Error(name string, id string, err error) {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"name": name,
|
||||||
|
"id": id,
|
||||||
|
}).Errorf("graph: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n graphLogger) Warnf(name string, id string, msg string, args ...any) {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"name": name,
|
||||||
|
"id": id,
|
||||||
|
}).Warnf("graph: %v", fmt.Sprintf(msg, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n graphLogger) Infof(name string, id string, msg string, args ...any) {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"name": name,
|
||||||
|
"id": id,
|
||||||
|
}).Infof("graph: %v", fmt.Sprintf(msg, args...))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n graphLogger) Debugf(name string, id string, msg string, args ...any) {
|
||||||
|
if n.graphDebug {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"name": name,
|
||||||
|
"id": id,
|
||||||
|
}).Debugf("graph: %v", fmt.Sprintf(msg, args...))
|
||||||
|
}
|
||||||
|
}
|
116
internal/post.go
Normal file
116
internal/post.go
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"git.dragse.it/anthrove/otter-space-sdk/pkg/models"
|
||||||
|
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateAnthrovePostNode(ctx context.Context, driver neo4j.DriverWithContext, anthrovePost *models.AnthrovePost) error {
|
||||||
|
query := `
|
||||||
|
CREATE (newPostNode:AnthrovePost {post_id: $anthrove_post_id, rating: $anthrove_rating})
|
||||||
|
`
|
||||||
|
|
||||||
|
params := map[string]any{
|
||||||
|
"anthrove_post_id": anthrovePost.PostID,
|
||||||
|
"anthrove_rating": anthrovePost.Rating,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := neo4j.ExecuteQuery(ctx, driver, query, params, neo4j.EagerResultTransformer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"anthrove_post_id": anthrovePost.PostID,
|
||||||
|
"anthrove_post_rating": anthrovePost.Rating,
|
||||||
|
}).Trace("graph: created anthrove post")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckIfAnthrovePostNodeExistsByAnthroveID(ctx context.Context, driver neo4j.DriverWithContext, anthrovePost *models.AnthrovePost) (*models.AnthrovePost, bool, error) {
|
||||||
|
query := `
|
||||||
|
OPTIONAL MATCH (postNode:AnthrovePost {post_id: $anthrove_post_id})
|
||||||
|
RETURN postNode.post_id AS AnthrovePostID
|
||||||
|
`
|
||||||
|
|
||||||
|
params := map[string]any{
|
||||||
|
"anthrove_post_id": anthrovePost.PostID,
|
||||||
|
}
|
||||||
|
|
||||||
|
anthrovePost, exists, err := executeCheckQuery(ctx, driver, query, params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return anthrovePost, exists, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckIfAnthrovePostNodeExistsBySourceURl(ctx context.Context, driver neo4j.DriverWithContext, sourceUrl string) (*models.AnthrovePost, bool, error) {
|
||||||
|
query := `
|
||||||
|
OPTIONAL MATCH (postNode:AnthrovePost)<-[:REFERENCE {url: $source_url}]-()
|
||||||
|
RETURN postNode.post_id AS AnthrovePostID
|
||||||
|
`
|
||||||
|
|
||||||
|
params := map[string]any{
|
||||||
|
"source_url": sourceUrl,
|
||||||
|
}
|
||||||
|
anthrovePost, exists, err := executeCheckQuery(ctx, driver, query, params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return anthrovePost, exists, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckIfAnthrovePostNodeExistsBySourceID(ctx context.Context, driver neo4j.DriverWithContext, sourcePostID string) (*models.AnthrovePost, bool, error) {
|
||||||
|
query := `
|
||||||
|
OPTIONAL MATCH (postNode:AnthrovePost)<-[:REFERENCE {source_post_id: $source_post_id}]-()
|
||||||
|
RETURN postNode.post_id AS AnthrovePostID
|
||||||
|
`
|
||||||
|
|
||||||
|
params := map[string]any{
|
||||||
|
"source_post_id": sourcePostID,
|
||||||
|
}
|
||||||
|
|
||||||
|
anthrovePost, exists, err := executeCheckQuery(ctx, driver, query, params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return anthrovePost, exists, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func executeCheckQuery(ctx context.Context, driver neo4j.DriverWithContext, query string, params map[string]any) (*models.AnthrovePost, bool, error) {
|
||||||
|
|
||||||
|
var anthrovePost models.AnthrovePost
|
||||||
|
|
||||||
|
result, err := neo4j.ExecuteQuery(ctx, driver, query, params, neo4j.EagerResultTransformer)
|
||||||
|
if err != nil {
|
||||||
|
return &anthrovePost, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
record := result.Records
|
||||||
|
|
||||||
|
anthrovePostID, isNil, err := neo4j.GetRecordValue[string](record[0], "AnthrovePostID")
|
||||||
|
exists := !isNil
|
||||||
|
if err != nil {
|
||||||
|
return &anthrovePost, exists, err
|
||||||
|
}
|
||||||
|
|
||||||
|
anthrovePost.PostID = models.AnthrovePostID(anthrovePostID)
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"anthrove_post_id": anthrovePost.PostID,
|
||||||
|
"anthrove_post_exists": exists,
|
||||||
|
}).Trace("graph: checked if post exists")
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return nil, exists, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return &anthrovePost, exists, nil
|
||||||
|
}
|
@ -1,131 +0,0 @@
|
|||||||
package postgres
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
otterError "git.dragse.it/anthrove/otter-space-sdk/v2/pkg/error"
|
|
||||||
|
|
||||||
"git.dragse.it/anthrove/otter-space-sdk/v2/pkg/models"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
func CreatePost(ctx context.Context, db *gorm.DB, anthrovePost *models.Post) error {
|
|
||||||
|
|
||||||
if anthrovePost == nil {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: "anthrovePost is nil"}
|
|
||||||
}
|
|
||||||
|
|
||||||
result := db.WithContext(ctx).Create(&anthrovePost)
|
|
||||||
if result.Error != nil {
|
|
||||||
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
|
|
||||||
return &otterError.EntityAlreadyExists{}
|
|
||||||
}
|
|
||||||
return result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.RowsAffected == 0 {
|
|
||||||
return &otterError.NoDataWritten{}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"anthrove_post_id": anthrovePost.ID,
|
|
||||||
"anthrove_post_rating": anthrovePost.Rating,
|
|
||||||
}).Trace("database: created anthrove post")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreatePostInBatch(ctx context.Context, db *gorm.DB, anthrovePost []models.Post, batchSize int) error {
|
|
||||||
if anthrovePost == nil {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: "anthrovePost cannot be nil"}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(anthrovePost) == 0 {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: "anthrovePost cannot be empty"}
|
|
||||||
}
|
|
||||||
|
|
||||||
if batchSize == 0 {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: "batch size cannot be zero"}
|
|
||||||
}
|
|
||||||
|
|
||||||
result := db.WithContext(ctx).CreateInBatches(anthrovePost, batchSize)
|
|
||||||
if result.Error != nil {
|
|
||||||
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
|
|
||||||
return &otterError.EntityAlreadyExists{}
|
|
||||||
}
|
|
||||||
return result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.RowsAffected == 0 {
|
|
||||||
return &otterError.NoDataWritten{}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"tag_size": len(anthrovePost),
|
|
||||||
"batch_size": batchSize,
|
|
||||||
}).Trace("database: created tag node")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetPostByAnthroveID(ctx context.Context, db *gorm.DB, anthrovePostID models.AnthrovePostID) (*models.Post, error) {
|
|
||||||
|
|
||||||
if anthrovePostID == "" {
|
|
||||||
return nil, &otterError.EntityValidationFailed{Reason: "anthrovePostID is not set"}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(anthrovePostID) != 25 {
|
|
||||||
return nil, &otterError.EntityValidationFailed{Reason: "anthrovePostID needs to be 25 characters long"}
|
|
||||||
}
|
|
||||||
|
|
||||||
var post models.Post
|
|
||||||
result := db.WithContext(ctx).First(&post, "id = ?", anthrovePostID)
|
|
||||||
if result.Error != nil {
|
|
||||||
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
|
||||||
return nil, &otterError.NoDataFound{}
|
|
||||||
}
|
|
||||||
return nil, result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
return &post, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetPostBySourceURL(ctx context.Context, db *gorm.DB, sourceURL string) (*models.Post, error) {
|
|
||||||
|
|
||||||
if sourceURL == "" {
|
|
||||||
return nil, &otterError.EntityValidationFailed{Reason: "sourceURL is not set"}
|
|
||||||
}
|
|
||||||
|
|
||||||
var post models.Post
|
|
||||||
result := db.WithContext(ctx).Raw(`SELECT p.id AS id, p.rating as rating FROM "Post" AS p INNER JOIN "PostReference" AS pr ON p.id = pr.post_id AND pr.url = $1 LIMIT 1`, sourceURL).First(&post)
|
|
||||||
|
|
||||||
if result.Error != nil {
|
|
||||||
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
|
||||||
return nil, &otterError.NoDataFound{}
|
|
||||||
}
|
|
||||||
return nil, result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
return &post, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetPostBySourceID(ctx context.Context, db *gorm.DB, sourceID models.AnthroveSourceID) (*models.Post, error) {
|
|
||||||
|
|
||||||
if sourceID == "" {
|
|
||||||
return nil, &otterError.EntityValidationFailed{Reason: "sourceID is not set"}
|
|
||||||
}
|
|
||||||
|
|
||||||
var post models.Post
|
|
||||||
result := db.WithContext(ctx).Raw(`SELECT p.id AS id, p.rating as rating FROM "Post" AS p INNER JOIN "PostReference" AS pr ON p.id = pr.post_id AND pr.source_id = $1 LIMIT 1`, sourceID).First(&post)
|
|
||||||
|
|
||||||
if result.Error != nil {
|
|
||||||
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
|
||||||
return nil, &otterError.NoDataFound{}
|
|
||||||
}
|
|
||||||
return nil, result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
return &post, nil
|
|
||||||
}
|
|
@ -1,468 +0,0 @@
|
|||||||
package postgres
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
_ "github.com/lib/pq"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"git.dragse.it/anthrove/otter-space-sdk/v2/pkg/models"
|
|
||||||
"git.dragse.it/anthrove/otter-space-sdk/v2/test"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCreateAnthrovePostNode(t *testing.T) {
|
|
||||||
|
|
||||||
// Setup trow away container
|
|
||||||
ctx := context.Background()
|
|
||||||
container, gormDB, err := test.StartPostgresContainer(ctx)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Could not start PostgreSQL container: %v", err)
|
|
||||||
}
|
|
||||||
defer container.Terminate(ctx)
|
|
||||||
|
|
||||||
// Setup Tests
|
|
||||||
|
|
||||||
validPost := &models.Post{
|
|
||||||
BaseModel: models.BaseModel[models.AnthrovePostID]{
|
|
||||||
ID: models.AnthrovePostID(fmt.Sprintf("%025s", "1")),
|
|
||||||
},
|
|
||||||
Rating: "safe",
|
|
||||||
}
|
|
||||||
|
|
||||||
invalidPost := &models.Post{
|
|
||||||
Rating: "error",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test
|
|
||||||
type args struct {
|
|
||||||
ctx context.Context
|
|
||||||
db *gorm.DB
|
|
||||||
anthrovePost *models.Post
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Test 1: Valid AnthrovePostID and Rating",
|
|
||||||
args: args{
|
|
||||||
ctx: context.Background(),
|
|
||||||
db: gormDB,
|
|
||||||
anthrovePost: validPost,
|
|
||||||
},
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test 2: Invalid Rating",
|
|
||||||
args: args{
|
|
||||||
ctx: context.Background(),
|
|
||||||
db: gormDB,
|
|
||||||
anthrovePost: invalidPost,
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test 3: Nill",
|
|
||||||
args: args{
|
|
||||||
ctx: context.Background(),
|
|
||||||
db: gormDB,
|
|
||||||
anthrovePost: nil,
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
if err := CreatePost(tt.args.ctx, tt.args.db, tt.args.anthrovePost); (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("CreatePost() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetPostByAnthroveID(t *testing.T) {
|
|
||||||
// Setup trow away container
|
|
||||||
ctx := context.Background()
|
|
||||||
container, gormDB, err := test.StartPostgresContainer(ctx)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Could not start PostgreSQL container: %v", err)
|
|
||||||
}
|
|
||||||
defer container.Terminate(ctx)
|
|
||||||
|
|
||||||
// Setup Tests
|
|
||||||
|
|
||||||
post := &models.Post{
|
|
||||||
BaseModel: models.BaseModel[models.AnthrovePostID]{
|
|
||||||
ID: models.AnthrovePostID(fmt.Sprintf("%025s", "1")),
|
|
||||||
},
|
|
||||||
Rating: "safe",
|
|
||||||
}
|
|
||||||
|
|
||||||
err = CreatePost(ctx, gormDB, post)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Could not create post", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test
|
|
||||||
type args struct {
|
|
||||||
ctx context.Context
|
|
||||||
db *gorm.DB
|
|
||||||
anthrovePostID models.AnthrovePostID
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
want *models.Post
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Test 1: Valid anthrovePostID",
|
|
||||||
args: args{
|
|
||||||
ctx: ctx,
|
|
||||||
db: gormDB,
|
|
||||||
anthrovePostID: post.ID,
|
|
||||||
},
|
|
||||||
want: post,
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test 2: Invalid anthrovePostID",
|
|
||||||
args: args{
|
|
||||||
ctx: ctx,
|
|
||||||
db: gormDB,
|
|
||||||
anthrovePostID: "1234",
|
|
||||||
},
|
|
||||||
want: nil,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test 3: No anthrovePostID",
|
|
||||||
args: args{
|
|
||||||
ctx: ctx,
|
|
||||||
db: gormDB,
|
|
||||||
anthrovePostID: "",
|
|
||||||
},
|
|
||||||
want: nil,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
got, err := GetPostByAnthroveID(tt.args.ctx, tt.args.db, tt.args.anthrovePostID)
|
|
||||||
if (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("GetPostByAnthroveID() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !checkPost(got, tt.want) {
|
|
||||||
t.Errorf("GetPostByAnthroveID() got = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetPostBySourceURL(t *testing.T) {
|
|
||||||
// Setup trow away container
|
|
||||||
ctx := context.Background()
|
|
||||||
container, gormDB, err := test.StartPostgresContainer(ctx)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Could not start PostgreSQL container: %v", err)
|
|
||||||
}
|
|
||||||
defer container.Terminate(ctx)
|
|
||||||
|
|
||||||
// Setup Tests
|
|
||||||
post := &models.Post{
|
|
||||||
BaseModel: models.BaseModel[models.AnthrovePostID]{
|
|
||||||
ID: models.AnthrovePostID(fmt.Sprintf("%025s", "1")),
|
|
||||||
},
|
|
||||||
|
|
||||||
Rating: "safe",
|
|
||||||
}
|
|
||||||
|
|
||||||
err = CreatePost(ctx, gormDB, post)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Could not create post", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
source := models.Source{
|
|
||||||
BaseModel: models.BaseModel[models.AnthroveSourceID]{
|
|
||||||
ID: models.AnthroveSourceID(fmt.Sprintf("%025s", "1")),
|
|
||||||
},
|
|
||||||
DisplayName: "e621",
|
|
||||||
Domain: "e621.net",
|
|
||||||
Icon: "https://e621.net/icon.ico",
|
|
||||||
}
|
|
||||||
|
|
||||||
err = CreateSource(ctx, gormDB, &source)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Could not create source", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = CreateReferenceBetweenPostAndSource(ctx, gormDB, post.ID, models.AnthroveSourceDomain(source.Domain), "http://test.org", models.PostReferenceConfig{})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Could not create source reference", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test
|
|
||||||
type args struct {
|
|
||||||
ctx context.Context
|
|
||||||
db *gorm.DB
|
|
||||||
sourceURL string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
want *models.Post
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Test 1: Valid sourceURL",
|
|
||||||
args: args{
|
|
||||||
ctx: ctx,
|
|
||||||
db: gormDB,
|
|
||||||
sourceURL: "http://test.org",
|
|
||||||
},
|
|
||||||
want: post,
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test 2: Invalid sourceURL",
|
|
||||||
args: args{
|
|
||||||
ctx: ctx,
|
|
||||||
db: gormDB,
|
|
||||||
sourceURL: "1234",
|
|
||||||
},
|
|
||||||
want: nil,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test 3: No sourceURL",
|
|
||||||
args: args{
|
|
||||||
ctx: ctx,
|
|
||||||
db: gormDB,
|
|
||||||
sourceURL: "",
|
|
||||||
},
|
|
||||||
want: nil,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
got, err := GetPostBySourceURL(tt.args.ctx, tt.args.db, tt.args.sourceURL)
|
|
||||||
if (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("GetPostBySourceURL() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !checkPost(got, tt.want) {
|
|
||||||
t.Errorf("GetPostBySourceURL() got = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetPostBySourceID(t *testing.T) {
|
|
||||||
// Setup trow away container
|
|
||||||
ctx := context.Background()
|
|
||||||
container, gormDB, err := test.StartPostgresContainer(ctx)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Could not start PostgreSQL container: %v", err)
|
|
||||||
}
|
|
||||||
defer container.Terminate(ctx)
|
|
||||||
|
|
||||||
// Setup Tests
|
|
||||||
|
|
||||||
post := &models.Post{
|
|
||||||
BaseModel: models.BaseModel[models.AnthrovePostID]{
|
|
||||||
ID: models.AnthrovePostID(fmt.Sprintf("%025s", "1")),
|
|
||||||
},
|
|
||||||
Rating: "safe",
|
|
||||||
}
|
|
||||||
|
|
||||||
err = CreatePost(ctx, gormDB, post)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Could not create post", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
source := models.Source{
|
|
||||||
BaseModel: models.BaseModel[models.AnthroveSourceID]{
|
|
||||||
ID: models.AnthroveSourceID(fmt.Sprintf("%025s", "1")),
|
|
||||||
},
|
|
||||||
DisplayName: "e621",
|
|
||||||
Domain: "e621.net",
|
|
||||||
Icon: "https://e621.net/icon.ico",
|
|
||||||
}
|
|
||||||
|
|
||||||
err = CreateSource(ctx, gormDB, &source)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Could not create source", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = CreateReferenceBetweenPostAndSource(ctx, gormDB, post.ID, models.AnthroveSourceDomain(source.Domain), "http://test.otg", models.PostReferenceConfig{})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Could not create source reference", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test
|
|
||||||
type args struct {
|
|
||||||
ctx context.Context
|
|
||||||
db *gorm.DB
|
|
||||||
sourceID models.AnthroveSourceID
|
|
||||||
}
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
want *models.Post
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Test 1: Valid sourceID",
|
|
||||||
args: args{
|
|
||||||
ctx: ctx,
|
|
||||||
db: gormDB,
|
|
||||||
sourceID: source.ID,
|
|
||||||
},
|
|
||||||
want: post,
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test 2: Invalid sourceID",
|
|
||||||
args: args{
|
|
||||||
ctx: ctx,
|
|
||||||
db: gormDB,
|
|
||||||
sourceID: "1234",
|
|
||||||
},
|
|
||||||
want: nil,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test 3: No sourceID",
|
|
||||||
args: args{
|
|
||||||
ctx: ctx,
|
|
||||||
db: gormDB,
|
|
||||||
sourceID: "",
|
|
||||||
},
|
|
||||||
want: nil,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
got, err := GetPostBySourceID(tt.args.ctx, tt.args.db, tt.args.sourceID)
|
|
||||||
if (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("GetPostBySourceID() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !checkPost(got, tt.want) {
|
|
||||||
t.Errorf("GetPostBySourceID() got = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCreatePostInBatch(t *testing.T) {
|
|
||||||
// Setup trow away container
|
|
||||||
ctx := context.Background()
|
|
||||||
container, gormDB, err := test.StartPostgresContainer(ctx)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Could not start PostgreSQL container: %v", err)
|
|
||||||
}
|
|
||||||
defer container.Terminate(ctx)
|
|
||||||
|
|
||||||
// Setup Tests
|
|
||||||
|
|
||||||
validPosts := []models.Post{
|
|
||||||
{
|
|
||||||
Rating: models.SFW,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Rating: models.NSFW,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Rating: models.Questionable,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
emptyPost := []models.Post{}
|
|
||||||
|
|
||||||
// Test
|
|
||||||
type args struct {
|
|
||||||
ctx context.Context
|
|
||||||
db *gorm.DB
|
|
||||||
anthrovePost []models.Post
|
|
||||||
batchSize int
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Test 1: Valid Data",
|
|
||||||
args: args{
|
|
||||||
ctx: ctx,
|
|
||||||
db: gormDB,
|
|
||||||
anthrovePost: validPosts,
|
|
||||||
batchSize: len(validPosts),
|
|
||||||
},
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test 2: Emtpy Data",
|
|
||||||
args: args{
|
|
||||||
ctx: ctx,
|
|
||||||
db: gormDB,
|
|
||||||
anthrovePost: emptyPost,
|
|
||||||
batchSize: 0,
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test 3: Nil Data",
|
|
||||||
args: args{
|
|
||||||
ctx: ctx,
|
|
||||||
db: gormDB,
|
|
||||||
anthrovePost: nil,
|
|
||||||
batchSize: 0,
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test 4: batchSize 0",
|
|
||||||
args: args{
|
|
||||||
ctx: ctx,
|
|
||||||
db: gormDB,
|
|
||||||
anthrovePost: validPosts,
|
|
||||||
batchSize: 0,
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
if err := CreatePostInBatch(tt.args.ctx, tt.args.db, tt.args.anthrovePost, tt.args.batchSize); (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("CreatePostInBatch() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkPost(got *models.Post, want *models.Post) bool {
|
|
||||||
|
|
||||||
if got == nil && want == nil {
|
|
||||||
return true
|
|
||||||
} else if got == nil || want == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if got.ID != want.ID {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if got.Rating != want.Rating {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
@ -1,126 +0,0 @@
|
|||||||
package postgres
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
otterError "git.dragse.it/anthrove/otter-space-sdk/v2/pkg/error"
|
|
||||||
"git.dragse.it/anthrove/otter-space-sdk/v2/pkg/models"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
func CreateReferenceBetweenPostAndSource(ctx context.Context, db *gorm.DB, anthrovePostID models.AnthrovePostID, sourceDomain models.AnthroveSourceDomain, postURL models.AnthrovePostURL, config models.PostReferenceConfig) error {
|
|
||||||
if anthrovePostID == "" {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDIsEmpty}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(anthrovePostID) != 25 {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDToShort}
|
|
||||||
}
|
|
||||||
|
|
||||||
if sourceDomain == "" {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: "sourceDomain cannot be empty"}
|
|
||||||
}
|
|
||||||
|
|
||||||
result := db.WithContext(ctx).Exec(`INSERT INTO "PostReference" (post_id, source_id, url, full_file_url, preview_file_url, sample_file_url, source_post_id) SELECT $1, source.id, $2, $4, $5, $6, $7 FROM "Source" AS source WHERE domain = $3;`, anthrovePostID, postURL, sourceDomain, config.FullFileURL, config.PreviewFileURL, config.SampleFileURL, config.SourcePostID)
|
|
||||||
|
|
||||||
if result.Error != nil {
|
|
||||||
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
|
||||||
return &otterError.NoDataFound{}
|
|
||||||
}
|
|
||||||
if errors.Is(result.Error, gorm.ErrCheckConstraintViolated) {
|
|
||||||
return &otterError.EntityAlreadyExists{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.RowsAffected == 0 {
|
|
||||||
return &otterError.NoDataWritten{}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"anthrove_post_id": anthrovePostID,
|
|
||||||
"anthrove_source_domain": sourceDomain,
|
|
||||||
}).Trace("database: created anthrove post to source link")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateReferenceBetweenUserAndPost(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID, anthrovePostID models.AnthrovePostID) error {
|
|
||||||
|
|
||||||
if anthrovePostID == "" {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDIsEmpty}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(anthrovePostID) != 25 {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDToShort}
|
|
||||||
}
|
|
||||||
|
|
||||||
if anthroveUserID == "" {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: "anthroveUserID cannot be empty"}
|
|
||||||
}
|
|
||||||
|
|
||||||
userFavorite := models.UserFavorites{
|
|
||||||
UserID: string(anthroveUserID),
|
|
||||||
PostID: string(anthrovePostID),
|
|
||||||
}
|
|
||||||
|
|
||||||
result := db.WithContext(ctx).Create(&userFavorite)
|
|
||||||
if result.Error != nil {
|
|
||||||
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
|
|
||||||
return &otterError.EntityAlreadyExists{}
|
|
||||||
}
|
|
||||||
return result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.RowsAffected == 0 {
|
|
||||||
return &otterError.NoDataWritten{}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"anthrove_user_id": anthroveUserID,
|
|
||||||
"anthrove_post_id": anthrovePostID,
|
|
||||||
}).Trace("database: created user to post link")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func CheckReferenceBetweenUserAndPost(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID, anthrovePostID models.AnthrovePostID) (bool, error) {
|
|
||||||
var count int64
|
|
||||||
|
|
||||||
if anthrovePostID == "" {
|
|
||||||
return false, &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDIsEmpty}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(anthrovePostID) != 25 {
|
|
||||||
return false, &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDToShort}
|
|
||||||
}
|
|
||||||
|
|
||||||
if anthroveUserID == "" {
|
|
||||||
return false, &otterError.EntityValidationFailed{Reason: "anthroveUserID cannot be empty"}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(anthroveUserID) != 25 {
|
|
||||||
return false, &otterError.EntityValidationFailed{Reason: "anthroveUserID needs to be 25 characters long"}
|
|
||||||
}
|
|
||||||
|
|
||||||
result := db.WithContext(ctx).Model(&models.UserFavorites{}).Where("user_id = ? AND post_id = ?", string(anthroveUserID), string(anthrovePostID)).Count(&count)
|
|
||||||
if result.Error != nil {
|
|
||||||
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
|
||||||
return false, &otterError.NoDataFound{}
|
|
||||||
}
|
|
||||||
return false, result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
exists := count > 0
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"relationship_exists": exists,
|
|
||||||
"relationship_anthrove_user_id": anthroveUserID,
|
|
||||||
"relationship_anthrove_post_id": anthrovePostID,
|
|
||||||
}).Trace("database: checked user post relationship")
|
|
||||||
|
|
||||||
return exists, nil
|
|
||||||
}
|
|
@ -1,392 +0,0 @@
|
|||||||
package postgres
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"git.dragse.it/anthrove/otter-space-sdk/v2/pkg/models"
|
|
||||||
"git.dragse.it/anthrove/otter-space-sdk/v2/test"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCheckUserToPostLink(t *testing.T) {
|
|
||||||
|
|
||||||
// Setup trow away container
|
|
||||||
ctx := context.Background()
|
|
||||||
container, gormDB, err := test.StartPostgresContainer(ctx)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Could not start PostgreSQL container: %v", err)
|
|
||||||
}
|
|
||||||
defer container.Terminate(ctx)
|
|
||||||
|
|
||||||
// Setup Test
|
|
||||||
|
|
||||||
validUserID := models.AnthroveUserID(fmt.Sprintf("%025s", "User1"))
|
|
||||||
invalidUserID := models.AnthroveUserID("XXX")
|
|
||||||
|
|
||||||
validPostID := models.AnthrovePostID(fmt.Sprintf("%025s", "Post1"))
|
|
||||||
|
|
||||||
err = CreateUser(ctx, gormDB, validUserID)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
post := &models.Post{
|
|
||||||
BaseModel: models.BaseModel[models.AnthrovePostID]{
|
|
||||||
ID: validPostID,
|
|
||||||
},
|
|
||||||
Rating: "safe",
|
|
||||||
}
|
|
||||||
|
|
||||||
err = CreatePost(ctx, gormDB, post)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = CreateReferenceBetweenUserAndPost(ctx, gormDB, validUserID, post.ID)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test
|
|
||||||
type args struct {
|
|
||||||
ctx context.Context
|
|
||||||
db *gorm.DB
|
|
||||||
anthroveUserID models.AnthroveUserID
|
|
||||||
anthrovePostID models.AnthrovePostID
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
want bool
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Test 1: Valid AnthroveUserID and AnthrovePostID",
|
|
||||||
args: args{
|
|
||||||
ctx: ctx,
|
|
||||||
db: gormDB,
|
|
||||||
anthroveUserID: validUserID,
|
|
||||||
anthrovePostID: post.ID,
|
|
||||||
},
|
|
||||||
want: true,
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test 2: Valid AnthroveUserID and invalid AnthrovePostID",
|
|
||||||
args: args{
|
|
||||||
ctx: ctx,
|
|
||||||
db: gormDB,
|
|
||||||
anthroveUserID: validUserID,
|
|
||||||
anthrovePostID: "qadw",
|
|
||||||
},
|
|
||||||
want: false,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test 3: Valid AnthrovePostID and invalid AnthroveUserID",
|
|
||||||
args: args{
|
|
||||||
ctx: ctx,
|
|
||||||
db: gormDB,
|
|
||||||
anthroveUserID: invalidUserID,
|
|
||||||
anthrovePostID: post.ID,
|
|
||||||
},
|
|
||||||
want: false,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test 4: Invalid AnthrovePostID and invalid AnthroveUserID",
|
|
||||||
args: args{
|
|
||||||
ctx: ctx,
|
|
||||||
db: gormDB,
|
|
||||||
anthroveUserID: invalidUserID,
|
|
||||||
anthrovePostID: "123456",
|
|
||||||
},
|
|
||||||
want: false,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test 5: No AnthrovePostID given",
|
|
||||||
args: args{
|
|
||||||
ctx: ctx,
|
|
||||||
db: gormDB,
|
|
||||||
anthroveUserID: "",
|
|
||||||
anthrovePostID: "123456",
|
|
||||||
},
|
|
||||||
want: false,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test 6: No anthrovePostID given",
|
|
||||||
args: args{
|
|
||||||
ctx: ctx,
|
|
||||||
db: gormDB,
|
|
||||||
anthroveUserID: invalidUserID,
|
|
||||||
anthrovePostID: "",
|
|
||||||
},
|
|
||||||
want: false,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
got, err := CheckReferenceBetweenUserAndPost(tt.args.ctx, tt.args.db, tt.args.anthroveUserID, tt.args.anthrovePostID)
|
|
||||||
if (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("CheckIfUserHasPostAsFavorite() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if got != tt.want {
|
|
||||||
t.Errorf("CheckIfUserHasPostAsFavorite() got = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCheckUserToPostLinkWithNoData(t *testing.T) {
|
|
||||||
|
|
||||||
// Setup trow away container
|
|
||||||
ctx := context.Background()
|
|
||||||
container, gormDB, err := test.StartPostgresContainer(ctx)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Could not start PostgreSQL container: %v", err)
|
|
||||||
}
|
|
||||||
defer container.Terminate(ctx)
|
|
||||||
|
|
||||||
// Setup Test
|
|
||||||
|
|
||||||
validUserID := models.AnthroveUserID(fmt.Sprintf("%025s", "User1"))
|
|
||||||
invalidUserID := models.AnthroveUserID("XXX")
|
|
||||||
|
|
||||||
validPostID := models.AnthrovePostID(fmt.Sprintf("%025s", "Post1"))
|
|
||||||
|
|
||||||
err = CreateUser(ctx, gormDB, validUserID)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
post := &models.Post{
|
|
||||||
BaseModel: models.BaseModel[models.AnthrovePostID]{
|
|
||||||
ID: validPostID,
|
|
||||||
},
|
|
||||||
Rating: "safe",
|
|
||||||
}
|
|
||||||
|
|
||||||
err = CreatePost(ctx, gormDB, post)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = CreateReferenceBetweenUserAndPost(ctx, gormDB, validUserID, post.ID)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test
|
|
||||||
type args struct {
|
|
||||||
ctx context.Context
|
|
||||||
db *gorm.DB
|
|
||||||
anthroveUserID models.AnthroveUserID
|
|
||||||
anthrovePostID models.AnthrovePostID
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
want bool
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Test 1: Valid AnthroveUserID and AnthrovePostID",
|
|
||||||
args: args{
|
|
||||||
ctx: ctx,
|
|
||||||
db: gormDB,
|
|
||||||
anthroveUserID: validUserID,
|
|
||||||
anthrovePostID: post.ID,
|
|
||||||
},
|
|
||||||
want: true,
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test 2: Valid AnthroveUserID and invalid AnthrovePostID",
|
|
||||||
args: args{
|
|
||||||
ctx: ctx,
|
|
||||||
db: gormDB,
|
|
||||||
anthroveUserID: validUserID,
|
|
||||||
anthrovePostID: "qadw",
|
|
||||||
},
|
|
||||||
want: false,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test 3: Valid AnthrovePostID and invalid AnthroveUserID",
|
|
||||||
args: args{
|
|
||||||
ctx: ctx,
|
|
||||||
db: gormDB,
|
|
||||||
anthroveUserID: invalidUserID,
|
|
||||||
anthrovePostID: post.ID,
|
|
||||||
},
|
|
||||||
want: false,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test 4: Invalid AnthrovePostID and invalid AnthroveUserID",
|
|
||||||
args: args{
|
|
||||||
ctx: ctx,
|
|
||||||
db: gormDB,
|
|
||||||
anthroveUserID: invalidUserID,
|
|
||||||
anthrovePostID: "123456",
|
|
||||||
},
|
|
||||||
want: false,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test 5: No AnthrovePostID given",
|
|
||||||
args: args{
|
|
||||||
ctx: ctx,
|
|
||||||
db: gormDB,
|
|
||||||
anthroveUserID: "",
|
|
||||||
anthrovePostID: "123456",
|
|
||||||
},
|
|
||||||
want: false,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test 6: No anthrovePostID given",
|
|
||||||
args: args{
|
|
||||||
ctx: ctx,
|
|
||||||
db: gormDB,
|
|
||||||
anthroveUserID: invalidUserID,
|
|
||||||
anthrovePostID: "",
|
|
||||||
},
|
|
||||||
want: false,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
got, err := CheckReferenceBetweenUserAndPost(tt.args.ctx, tt.args.db, tt.args.anthroveUserID, tt.args.anthrovePostID)
|
|
||||||
if (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("CheckIfUserHasPostAsFavorite() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if got != tt.want {
|
|
||||||
t.Errorf("CheckIfUserHasPostAsFavorite() got = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEstablishUserToPostLink(t *testing.T) {
|
|
||||||
// Setup trow away container
|
|
||||||
ctx := context.Background()
|
|
||||||
container, gormDB, err := test.StartPostgresContainer(ctx)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Could not start PostgreSQL container: %v", err)
|
|
||||||
}
|
|
||||||
defer container.Terminate(ctx)
|
|
||||||
|
|
||||||
// Setup Test
|
|
||||||
|
|
||||||
validUserID := models.AnthroveUserID(fmt.Sprintf("%025s", "User1"))
|
|
||||||
invalidUserID := models.AnthroveUserID("XXX")
|
|
||||||
|
|
||||||
validPostID := models.AnthrovePostID(fmt.Sprintf("%025s", "Post1"))
|
|
||||||
|
|
||||||
err = CreateUser(ctx, gormDB, validUserID)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
post := &models.Post{
|
|
||||||
BaseModel: models.BaseModel[models.AnthrovePostID]{
|
|
||||||
ID: validPostID,
|
|
||||||
},
|
|
||||||
Rating: "safe",
|
|
||||||
}
|
|
||||||
|
|
||||||
err = CreatePost(ctx, gormDB, post)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test
|
|
||||||
type args struct {
|
|
||||||
ctx context.Context
|
|
||||||
db *gorm.DB
|
|
||||||
anthroveUserID models.AnthroveUserID
|
|
||||||
anthrovePostID models.AnthrovePostID
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Test 1: Valid AnthroveUserID and AnthrovePostID",
|
|
||||||
args: args{
|
|
||||||
ctx: ctx,
|
|
||||||
db: gormDB,
|
|
||||||
anthroveUserID: validUserID,
|
|
||||||
anthrovePostID: post.ID,
|
|
||||||
},
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test 2: Valid AnthroveUserID and invalid AnthrovePostID",
|
|
||||||
args: args{
|
|
||||||
ctx: ctx,
|
|
||||||
db: gormDB,
|
|
||||||
anthroveUserID: validUserID,
|
|
||||||
anthrovePostID: "123456",
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test 3: invalid AnthroveUserID and valid AnthrovePostID",
|
|
||||||
args: args{
|
|
||||||
ctx: ctx,
|
|
||||||
db: gormDB,
|
|
||||||
anthroveUserID: invalidUserID,
|
|
||||||
anthrovePostID: post.ID,
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test 4: Invalid AnthrovePostID and invalid AnthroveUserID",
|
|
||||||
args: args{
|
|
||||||
ctx: ctx,
|
|
||||||
db: gormDB,
|
|
||||||
anthroveUserID: invalidUserID,
|
|
||||||
anthrovePostID: "123456",
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test 5: AnthrovePostID is empty",
|
|
||||||
args: args{
|
|
||||||
ctx: ctx,
|
|
||||||
db: gormDB,
|
|
||||||
anthroveUserID: invalidUserID,
|
|
||||||
anthrovePostID: "",
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test 6: anthroveUserID is empty",
|
|
||||||
args: args{
|
|
||||||
ctx: ctx,
|
|
||||||
db: gormDB,
|
|
||||||
anthroveUserID: "",
|
|
||||||
anthrovePostID: validPostID,
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
if err := CreateReferenceBetweenUserAndPost(tt.args.ctx, tt.args.db, tt.args.anthroveUserID, tt.args.anthrovePostID); (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("CreateReferenceBetweenUserAndPost() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,85 +0,0 @@
|
|||||||
package postgres
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
otterError "git.dragse.it/anthrove/otter-space-sdk/v2/pkg/error"
|
|
||||||
"git.dragse.it/anthrove/otter-space-sdk/v2/pkg/models"
|
|
||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CreateSource creates a pgModels.Source
|
|
||||||
func CreateSource(ctx context.Context, db *gorm.DB, anthroveSource *models.Source) error {
|
|
||||||
|
|
||||||
if anthroveSource.Domain == "" {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: "Domain is required"}
|
|
||||||
}
|
|
||||||
|
|
||||||
result := db.WithContext(ctx).Where(models.Source{Domain: anthroveSource.Domain}).FirstOrCreate(anthroveSource)
|
|
||||||
|
|
||||||
if result.Error != nil {
|
|
||||||
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
|
|
||||||
return &otterError.EntityAlreadyExists{}
|
|
||||||
}
|
|
||||||
return result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.RowsAffected == 0 {
|
|
||||||
return &otterError.NoDataWritten{}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"node_source_url": anthroveSource.Domain,
|
|
||||||
"node_source_displayName": anthroveSource.DisplayName,
|
|
||||||
"node_source_icon": anthroveSource.Icon,
|
|
||||||
}).Trace("database: created source node")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAllSource returns a list of all pgModels.Source
|
|
||||||
func GetAllSource(ctx context.Context, db *gorm.DB) ([]models.Source, error) {
|
|
||||||
var sources []models.Source
|
|
||||||
|
|
||||||
result := db.WithContext(ctx).Find(&sources)
|
|
||||||
|
|
||||||
if result.Error != nil {
|
|
||||||
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
|
||||||
return nil, &otterError.NoDataFound{}
|
|
||||||
}
|
|
||||||
return nil, result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"tag_amount": result.RowsAffected,
|
|
||||||
}).Trace("database: get all source nodes")
|
|
||||||
|
|
||||||
return sources, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSourceByDomain returns the first source it finds based on the domain
|
|
||||||
func GetSourceByDomain(ctx context.Context, db *gorm.DB, sourceDomain models.AnthroveSourceDomain) (*models.Source, error) {
|
|
||||||
var sources models.Source
|
|
||||||
|
|
||||||
if sourceDomain == "" {
|
|
||||||
return nil, &otterError.EntityValidationFailed{Reason: "AnthroveSourceDomain is not set"}
|
|
||||||
}
|
|
||||||
|
|
||||||
result := db.WithContext(ctx).Where("domain = ?", sourceDomain).First(&sources)
|
|
||||||
|
|
||||||
if result.Error != nil {
|
|
||||||
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
|
||||||
return nil, &otterError.NoDataFound{}
|
|
||||||
}
|
|
||||||
return nil, result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"tag_amount": result.RowsAffected,
|
|
||||||
}).Trace("database: get all source nodes")
|
|
||||||
|
|
||||||
return &sources, nil
|
|
||||||
}
|
|
@ -1,270 +0,0 @@
|
|||||||
package postgres
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"git.dragse.it/anthrove/otter-space-sdk/v2/pkg/models"
|
|
||||||
"git.dragse.it/anthrove/otter-space-sdk/v2/test"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCreateSourceNode(t *testing.T) {
|
|
||||||
// Setup trow away container
|
|
||||||
ctx := context.Background()
|
|
||||||
container, gormDB, err := test.StartPostgresContainer(ctx)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Could not start PostgreSQL container: %v", err)
|
|
||||||
}
|
|
||||||
defer container.Terminate(ctx)
|
|
||||||
|
|
||||||
// Setup Test
|
|
||||||
|
|
||||||
validPostID := models.AnthroveSourceID(fmt.Sprintf("%025s", "Post1"))
|
|
||||||
|
|
||||||
validSource := &models.Source{
|
|
||||||
BaseModel: models.BaseModel[models.AnthroveSourceID]{ID: validPostID},
|
|
||||||
DisplayName: "e621",
|
|
||||||
Domain: "e621.net",
|
|
||||||
Icon: "icon.e621.net",
|
|
||||||
}
|
|
||||||
|
|
||||||
invalidSource := &models.Source{
|
|
||||||
BaseModel: models.BaseModel[models.AnthroveSourceID]{ID: validPostID},
|
|
||||||
Domain: "notfound.intern",
|
|
||||||
}
|
|
||||||
|
|
||||||
invalidSourceDomain := &models.Source{
|
|
||||||
BaseModel: models.BaseModel[models.AnthroveSourceID]{ID: validPostID},
|
|
||||||
Domain: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test
|
|
||||||
type args struct {
|
|
||||||
ctx context.Context
|
|
||||||
db *gorm.DB
|
|
||||||
anthroveSource *models.Source
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Test 1: Valid anthroveSource",
|
|
||||||
args: args{
|
|
||||||
ctx: ctx,
|
|
||||||
db: gormDB,
|
|
||||||
anthroveSource: validSource,
|
|
||||||
},
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test 2: inValid anthroveSource",
|
|
||||||
args: args{
|
|
||||||
ctx: ctx,
|
|
||||||
db: gormDB,
|
|
||||||
anthroveSource: invalidSourceDomain,
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test 3: unique anthroveSource",
|
|
||||||
args: args{
|
|
||||||
ctx: ctx,
|
|
||||||
db: gormDB,
|
|
||||||
anthroveSource: invalidSource,
|
|
||||||
},
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
if err := CreateSource(tt.args.ctx, tt.args.db, tt.args.anthroveSource); (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("CreateSource() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetAllSourceNodes(t *testing.T) {
|
|
||||||
// Setup trow away container
|
|
||||||
ctx := context.Background()
|
|
||||||
container, gormDB, err := test.StartPostgresContainer(ctx)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Could not start PostgreSQL container: %v", err)
|
|
||||||
}
|
|
||||||
defer container.Terminate(ctx)
|
|
||||||
|
|
||||||
// Setup Test
|
|
||||||
|
|
||||||
sources := []models.Source{
|
|
||||||
{
|
|
||||||
DisplayName: "e621",
|
|
||||||
Domain: "e621.net",
|
|
||||||
Icon: "icon.e621.net",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
DisplayName: "furaffinity",
|
|
||||||
Domain: "furaffinity.net",
|
|
||||||
Icon: "icon.furaffinity.net",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
DisplayName: "fenpaws",
|
|
||||||
Domain: "fenpa.ws",
|
|
||||||
Icon: "icon.fenpa.ws",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, source := range sources {
|
|
||||||
err = CreateSource(ctx, gormDB, &source)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test
|
|
||||||
type args struct {
|
|
||||||
ctx context.Context
|
|
||||||
db *gorm.DB
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
want []models.Source
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Test 1: Get all entries",
|
|
||||||
args: args{
|
|
||||||
ctx: ctx,
|
|
||||||
db: gormDB,
|
|
||||||
},
|
|
||||||
want: sources,
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
got, err := GetAllSource(tt.args.ctx, tt.args.db)
|
|
||||||
if (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("GetAllSource() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !checkSourcesNode(got, tt.want) {
|
|
||||||
t.Errorf("GetAllSource() got = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestGetSourceNodesByURL(t *testing.T) {
|
|
||||||
// Setup trow away container
|
|
||||||
ctx := context.Background()
|
|
||||||
container, gormDB, err := test.StartPostgresContainer(ctx)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Could not start PostgreSQL container: %v", err)
|
|
||||||
}
|
|
||||||
defer container.Terminate(ctx)
|
|
||||||
|
|
||||||
// Setup Test
|
|
||||||
|
|
||||||
source := &models.Source{
|
|
||||||
DisplayName: "e621",
|
|
||||||
Domain: "e621.net",
|
|
||||||
Icon: "icon.e621.net",
|
|
||||||
}
|
|
||||||
|
|
||||||
err = CreateSource(ctx, gormDB, source)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test
|
|
||||||
type args struct {
|
|
||||||
ctx context.Context
|
|
||||||
db *gorm.DB
|
|
||||||
domain models.AnthroveSourceDomain
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
want *models.Source
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Test 1: Valid URL",
|
|
||||||
args: args{
|
|
||||||
ctx: ctx,
|
|
||||||
db: gormDB,
|
|
||||||
domain: "e621.net",
|
|
||||||
},
|
|
||||||
want: source,
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test 2: Invalid URL",
|
|
||||||
args: args{
|
|
||||||
ctx: ctx,
|
|
||||||
db: gormDB,
|
|
||||||
domain: "eeeee.net",
|
|
||||||
},
|
|
||||||
want: nil,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test 2: No URL",
|
|
||||||
args: args{
|
|
||||||
ctx: ctx,
|
|
||||||
db: gormDB,
|
|
||||||
domain: "",
|
|
||||||
},
|
|
||||||
want: nil,
|
|
||||||
wantErr: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
got, err := GetSourceByDomain(tt.args.ctx, tt.args.db, tt.args.domain)
|
|
||||||
if (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("GetSourceByDomain() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !checkSourceNode(got, tt.want) {
|
|
||||||
t.Errorf("GetSourceByDomain() got = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkSourcesNode(got []models.Source, want []models.Source) bool {
|
|
||||||
for i, source := range want {
|
|
||||||
if source.DisplayName != got[i].DisplayName {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if source.Domain != got[i].Domain {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if source.Icon != got[i].Icon {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkSourceNode(got *models.Source, want *models.Source) bool {
|
|
||||||
|
|
||||||
if want == nil && got == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if got.Domain != want.Domain {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
|
|
||||||
}
|
|
@ -1,440 +0,0 @@
|
|||||||
package postgres
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"gorm.io/gorm/clause"
|
|
||||||
|
|
||||||
otterError "git.dragse.it/anthrove/otter-space-sdk/v2/pkg/error"
|
|
||||||
"git.dragse.it/anthrove/otter-space-sdk/v2/pkg/models"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
func CreateTag(ctx context.Context, db *gorm.DB, tagName models.AnthroveTagName, tagType models.TagType) error {
|
|
||||||
|
|
||||||
if tagName == "" {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: "tagName cannot be empty"}
|
|
||||||
}
|
|
||||||
|
|
||||||
if tagType == "" {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: "tagType cannot be empty"}
|
|
||||||
}
|
|
||||||
|
|
||||||
result := db.WithContext(ctx).Create(&models.Tag{Name: string(tagName), Type: tagType})
|
|
||||||
if result.Error != nil {
|
|
||||||
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
|
|
||||||
return &otterError.EntityAlreadyExists{}
|
|
||||||
}
|
|
||||||
return result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.RowsAffected == 0 {
|
|
||||||
return &otterError.NoDataWritten{}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"tag_name": tagName,
|
|
||||||
"tag_type": tagType,
|
|
||||||
}).Trace("database: created tag node")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateTagInBatchAndUpdate(ctx context.Context, db *gorm.DB, tags []models.Tag, batchSize int) error {
|
|
||||||
if len(tags) == 0 {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: "tags cannot be empty"}
|
|
||||||
}
|
|
||||||
|
|
||||||
if tags == nil {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: "tags cannot be nil"}
|
|
||||||
}
|
|
||||||
|
|
||||||
if batchSize == 0 {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: "batch size cannot be zero"}
|
|
||||||
}
|
|
||||||
|
|
||||||
result := db.WithContext(ctx).
|
|
||||||
Clauses(clause.OnConflict{
|
|
||||||
Columns: []clause.Column{{Name: "name"}},
|
|
||||||
DoUpdates: clause.AssignmentColumns([]string{"tag_type"}),
|
|
||||||
}).CreateInBatches(tags, batchSize)
|
|
||||||
if result.Error != nil {
|
|
||||||
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
|
|
||||||
return &otterError.EntityAlreadyExists{}
|
|
||||||
}
|
|
||||||
return result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.RowsAffected == 0 {
|
|
||||||
return &otterError.NoDataWritten{}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"tag_size": len(tags),
|
|
||||||
"batch_size": batchSize,
|
|
||||||
}).Trace("database: created tag node")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func DeleteTag(ctx context.Context, db *gorm.DB, tagName models.AnthroveTagName) error {
|
|
||||||
|
|
||||||
if tagName == "" {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: "tagName cannot be empty"}
|
|
||||||
}
|
|
||||||
|
|
||||||
result := db.WithContext(ctx).Delete(&models.Tag{Name: string(tagName)})
|
|
||||||
|
|
||||||
if result.Error != nil {
|
|
||||||
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
|
||||||
return &otterError.NoDataFound{}
|
|
||||||
}
|
|
||||||
return result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"tag_name": tagName,
|
|
||||||
}).Trace("database: deleted tag")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetAllTagByTagsType(ctx context.Context, db *gorm.DB, tagType models.TagType) ([]models.Tag, error) {
|
|
||||||
var tags []models.Tag
|
|
||||||
|
|
||||||
if tagType == "" {
|
|
||||||
return nil, &otterError.EntityValidationFailed{Reason: "tagType cannot be empty"}
|
|
||||||
}
|
|
||||||
|
|
||||||
result := db.WithContext(ctx).Model(&models.Tag{}).Where("tag_type = ?", tagType).Scan(&tags)
|
|
||||||
|
|
||||||
if result.Error != nil {
|
|
||||||
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
|
||||||
return nil, &otterError.NoDataFound{}
|
|
||||||
}
|
|
||||||
return nil, result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"tags_length": len(tags),
|
|
||||||
}).Trace("database: got tag")
|
|
||||||
|
|
||||||
return tags, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateTagAndReferenceToPost(ctx context.Context, db *gorm.DB, anthrovePostID models.AnthrovePostID, tag *models.Tag) error {
|
|
||||||
|
|
||||||
if anthrovePostID == "" {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: "anthrovePostID cannot be empty"}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(anthrovePostID) != 25 {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: "anthrovePostID needs to be 25 characters long"}
|
|
||||||
}
|
|
||||||
|
|
||||||
if tag == nil {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: "Tag is nil"}
|
|
||||||
}
|
|
||||||
|
|
||||||
pgPost := models.Post{
|
|
||||||
BaseModel: models.BaseModel[models.AnthrovePostID]{
|
|
||||||
ID: anthrovePostID,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
err := db.WithContext(ctx).Model(&pgPost).Association("Tags").Append(tag)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
return &otterError.NoDataFound{}
|
|
||||||
}
|
|
||||||
return errors.Join(err, &otterError.NoRelationCreated{})
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"anthrove_post_id": anthrovePostID,
|
|
||||||
"tag_name": tag.Name,
|
|
||||||
"tag_type": tag.Type,
|
|
||||||
}).Trace("database: created tag node")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetTags(ctx context.Context, db *gorm.DB) ([]models.Tag, error) {
|
|
||||||
var tags []models.Tag
|
|
||||||
|
|
||||||
result := db.WithContext(ctx).Find(&tags)
|
|
||||||
if result.Error != nil {
|
|
||||||
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
|
||||||
return nil, &otterError.NoDataFound{}
|
|
||||||
}
|
|
||||||
return nil, result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"tag_amount": len(tags),
|
|
||||||
}).Trace("database: got tags")
|
|
||||||
|
|
||||||
return tags, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateTagAlias(ctx context.Context, db *gorm.DB, tagAliasName models.AnthroveTagAliasName, tagID models.AnthroveTagID) error {
|
|
||||||
|
|
||||||
if tagAliasName == "" {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: "tagAliasName cannot be empty"}
|
|
||||||
}
|
|
||||||
if tagID == "" {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveTagIDEmpty}
|
|
||||||
}
|
|
||||||
|
|
||||||
result := db.WithContext(ctx).Clauses(clause.OnConflict{
|
|
||||||
Columns: []clause.Column{{Name: "name"}},
|
|
||||||
DoNothing: true,
|
|
||||||
}).Create(&models.TagAlias{
|
|
||||||
Name: string(tagAliasName),
|
|
||||||
TagID: string(tagID),
|
|
||||||
})
|
|
||||||
|
|
||||||
if result.Error != nil {
|
|
||||||
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
|
|
||||||
return &otterError.EntityAlreadyExists{}
|
|
||||||
}
|
|
||||||
return result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"tag_alias_name": tagAliasName,
|
|
||||||
"tag_alias_tag_id": tagID,
|
|
||||||
}).Trace("database: created tagAlias")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateTagAliasInBatch(ctx context.Context, db *gorm.DB, tagAliases []models.TagAlias, batchSize int) error {
|
|
||||||
if len(tagAliases) == 0 {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: "tagAliases cannot be empty"}
|
|
||||||
}
|
|
||||||
|
|
||||||
if tagAliases == nil {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: "tagAliases cannot be nil"}
|
|
||||||
}
|
|
||||||
|
|
||||||
if batchSize == 0 {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: "batch size cannot be zero"}
|
|
||||||
}
|
|
||||||
|
|
||||||
result := db.WithContext(ctx).Clauses(clause.OnConflict{
|
|
||||||
Columns: []clause.Column{{Name: "name"}},
|
|
||||||
DoNothing: true,
|
|
||||||
}).CreateInBatches(tagAliases, batchSize)
|
|
||||||
if result.Error != nil {
|
|
||||||
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
|
|
||||||
return &otterError.EntityAlreadyExists{}
|
|
||||||
}
|
|
||||||
return result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.RowsAffected == 0 {
|
|
||||||
return &otterError.NoDataWritten{}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"tag_size": len(tagAliases),
|
|
||||||
"batch_size": batchSize,
|
|
||||||
}).Trace("database: created tag node")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetAllTagAlias(ctx context.Context, db *gorm.DB) ([]models.TagAlias, error) {
|
|
||||||
var tagAliases []models.TagAlias
|
|
||||||
|
|
||||||
result := db.WithContext(ctx).Find(&tagAliases)
|
|
||||||
|
|
||||||
if result.Error != nil {
|
|
||||||
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
|
||||||
return nil, &otterError.NoDataFound{}
|
|
||||||
}
|
|
||||||
return nil, result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"tag_alias_length": len(tagAliases),
|
|
||||||
}).Trace("database: created tagAlias")
|
|
||||||
|
|
||||||
return tagAliases, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetAllTagAliasByTag(ctx context.Context, db *gorm.DB, tagID models.AnthroveTagID) ([]models.TagAlias, error) {
|
|
||||||
var tagAliases []models.TagAlias
|
|
||||||
|
|
||||||
if tagID == "" {
|
|
||||||
return nil, &otterError.EntityValidationFailed{Reason: otterError.AnthroveTagIDEmpty}
|
|
||||||
}
|
|
||||||
|
|
||||||
result := db.WithContext(ctx).Where("tag_id = ?", tagID).Find(&tagAliases)
|
|
||||||
|
|
||||||
if result.Error != nil {
|
|
||||||
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
|
||||||
return nil, &otterError.NoDataFound{}
|
|
||||||
}
|
|
||||||
return nil, result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"tag_alias_length": len(tagAliases),
|
|
||||||
"tag_alias_tag_id": tagID,
|
|
||||||
}).Trace("database: get specific tagAlias")
|
|
||||||
|
|
||||||
return tagAliases, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func DeleteTagAlias(ctx context.Context, db *gorm.DB, tagAliasName models.AnthroveTagAliasName) error {
|
|
||||||
|
|
||||||
if tagAliasName == "" {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: "tagAliasName cannot be empty"}
|
|
||||||
}
|
|
||||||
|
|
||||||
result := db.WithContext(ctx).Delete(&models.TagAlias{Name: string(tagAliasName)})
|
|
||||||
|
|
||||||
if result.Error != nil {
|
|
||||||
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
|
||||||
return &otterError.NoDataFound{}
|
|
||||||
}
|
|
||||||
return result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"tag_alias_name": tagAliasName,
|
|
||||||
}).Trace("database: deleted tagAlias")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateTagGroup(ctx context.Context, db *gorm.DB, tagGroupName models.AnthroveTagGroupName, tagID models.AnthroveTagID) error {
|
|
||||||
|
|
||||||
if tagGroupName == "" {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: "tagGroupName cannot be empty"}
|
|
||||||
}
|
|
||||||
if tagID == "" {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveTagIDEmpty}
|
|
||||||
}
|
|
||||||
|
|
||||||
result := db.WithContext(ctx).Create(&models.TagGroup{
|
|
||||||
Name: string(tagGroupName),
|
|
||||||
TagID: string(tagID),
|
|
||||||
})
|
|
||||||
|
|
||||||
if result.Error != nil {
|
|
||||||
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
|
|
||||||
return &otterError.EntityAlreadyExists{}
|
|
||||||
}
|
|
||||||
return result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"tag_group_name": tagGroupName,
|
|
||||||
"tag_group_tag_id": tagID,
|
|
||||||
}).Trace("database: created tagGroup")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateTagGroupInBatch(ctx context.Context, db *gorm.DB, tagGroups []models.TagGroup, batchSize int) error {
|
|
||||||
if len(tagGroups) == 0 {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: "tagAliases cannot be empty"}
|
|
||||||
}
|
|
||||||
|
|
||||||
if tagGroups == nil {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: "tagAliases cannot be nil"}
|
|
||||||
}
|
|
||||||
|
|
||||||
if batchSize == 0 {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: "batch size cannot be zero"}
|
|
||||||
}
|
|
||||||
|
|
||||||
result := db.WithContext(ctx).CreateInBatches(tagGroups, batchSize)
|
|
||||||
if result.Error != nil {
|
|
||||||
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
|
|
||||||
return &otterError.EntityAlreadyExists{}
|
|
||||||
}
|
|
||||||
return result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.RowsAffected == 0 {
|
|
||||||
return &otterError.NoDataWritten{}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"tag_size": len(tagGroups),
|
|
||||||
"batch_size": batchSize,
|
|
||||||
}).Trace("database: created tag node")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetAllTagGroup(ctx context.Context, db *gorm.DB) ([]models.TagGroup, error) {
|
|
||||||
var tagGroups []models.TagGroup
|
|
||||||
|
|
||||||
result := db.WithContext(ctx).Find(&tagGroups)
|
|
||||||
|
|
||||||
if result.Error != nil {
|
|
||||||
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
|
||||||
return nil, &otterError.NoDataFound{}
|
|
||||||
}
|
|
||||||
return nil, result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"tag_alias_length": len(tagGroups),
|
|
||||||
}).Trace("database: created tagGroup")
|
|
||||||
|
|
||||||
return tagGroups, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetAllTagGroupByTag(ctx context.Context, db *gorm.DB, tagID models.AnthroveTagID) ([]models.TagGroup, error) {
|
|
||||||
var tagGroups []models.TagGroup
|
|
||||||
|
|
||||||
if tagID == "" {
|
|
||||||
return nil, &otterError.EntityValidationFailed{Reason: otterError.AnthroveTagIDEmpty}
|
|
||||||
}
|
|
||||||
|
|
||||||
result := db.WithContext(ctx).Where("tag_id = ?", tagID).Find(&tagGroups)
|
|
||||||
|
|
||||||
if result.Error != nil {
|
|
||||||
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
|
||||||
return nil, &otterError.NoDataFound{}
|
|
||||||
}
|
|
||||||
return nil, result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"tag_alias_length": len(tagGroups),
|
|
||||||
"tag_alias_tag_id": tagID,
|
|
||||||
}).Trace("database: get specific tagGroup")
|
|
||||||
|
|
||||||
return tagGroups, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func DeleteTagGroup(ctx context.Context, db *gorm.DB, tagGroupName models.AnthroveTagGroupName) error {
|
|
||||||
|
|
||||||
if tagGroupName == "" {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: "tagGroupName cannot be empty"}
|
|
||||||
}
|
|
||||||
|
|
||||||
result := db.WithContext(ctx).Delete(&models.TagGroup{Name: string(tagGroupName)})
|
|
||||||
|
|
||||||
if result.Error != nil {
|
|
||||||
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
|
||||||
return &otterError.NoDataFound{}
|
|
||||||
}
|
|
||||||
return result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"tag_alias_name": tagGroupName,
|
|
||||||
}).Trace("database: deleted tagAlias")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,398 +0,0 @@
|
|||||||
package postgres
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
otterError "git.dragse.it/anthrove/otter-space-sdk/v2/pkg/error"
|
|
||||||
"git.dragse.it/anthrove/otter-space-sdk/v2/pkg/models"
|
|
||||||
gonanoid "github.com/matoous/go-nanoid/v2"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Workaround, should be changed later maybe, but its not that bad right now
|
|
||||||
type selectFrequencyTag struct {
|
|
||||||
tagName string `gorm:"tag_name"`
|
|
||||||
count int64 `gorm:"count"`
|
|
||||||
tagType models.TagType `gorm:"tag_type"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateUser(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID) error {
|
|
||||||
|
|
||||||
if anthroveUserID == "" {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDIsEmpty}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(anthroveUserID) != 25 {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDToShort}
|
|
||||||
}
|
|
||||||
|
|
||||||
user := models.User{
|
|
||||||
BaseModel: models.BaseModel[models.AnthroveUserID]{
|
|
||||||
ID: anthroveUserID,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
result := db.WithContext(ctx).FirstOrCreate(&user)
|
|
||||||
if result.Error != nil {
|
|
||||||
|
|
||||||
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
|
|
||||||
return &otterError.EntityAlreadyExists{}
|
|
||||||
}
|
|
||||||
return result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateUserWithRelationToSource(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID, sourceID models.AnthroveSourceID, accountId string, accountUsername string) error {
|
|
||||||
|
|
||||||
if anthroveUserID == "" {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDIsEmpty}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(anthroveUserID) != 25 {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDToShort}
|
|
||||||
}
|
|
||||||
|
|
||||||
if accountId == "" {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: "accountID cannot be empty"}
|
|
||||||
}
|
|
||||||
|
|
||||||
if accountUsername == "" {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: "accountUsername cannot be empty"}
|
|
||||||
}
|
|
||||||
|
|
||||||
validationCode, err := gonanoid.New(25)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
result := db.WithContext(ctx).Exec(`WITH userObj AS (
|
|
||||||
INSERT INTO "User" (id)
|
|
||||||
VALUES ($1)
|
|
||||||
ON CONFLICT (id) DO NOTHING
|
|
||||||
)
|
|
||||||
INSERT INTO "UserSource" (user_id, source_id, account_username, account_id, account_validate, account_validation_key)
|
|
||||||
SELECT $2, source.id, $3, $4, false, $5
|
|
||||||
FROM "Source" AS source
|
|
||||||
WHERE source.id = $6;`, anthroveUserID, anthroveUserID, accountUsername, accountId, validationCode, sourceID)
|
|
||||||
|
|
||||||
if result.Error != nil {
|
|
||||||
if errors.Is(result.Error, gorm.ErrDuplicatedKey) {
|
|
||||||
return &otterError.EntityAlreadyExists{}
|
|
||||||
}
|
|
||||||
return result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
if result.RowsAffected == 0 {
|
|
||||||
return &otterError.NoDataWritten{}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"anthrove_user_id": anthroveUserID,
|
|
||||||
"source_id": sourceID,
|
|
||||||
"account_username": accountUsername,
|
|
||||||
"account_id": accountId,
|
|
||||||
}).Info("database: created user-source relationship")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetUserFavoritesCount(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID) (int64, error) {
|
|
||||||
var count int64
|
|
||||||
|
|
||||||
if anthroveUserID == "" {
|
|
||||||
return 0, &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDIsEmpty}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(anthroveUserID) != 25 {
|
|
||||||
return 0, &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDToShort}
|
|
||||||
}
|
|
||||||
|
|
||||||
result := db.WithContext(ctx).Model(&models.UserFavorites{}).Where("user_id = ?", string(anthroveUserID)).Count(&count)
|
|
||||||
if result.Error != nil {
|
|
||||||
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
|
||||||
return 0, &otterError.NoDataFound{}
|
|
||||||
}
|
|
||||||
return 0, result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"anthrove_user_id": anthroveUserID,
|
|
||||||
"anthrove_user_fav_count": count,
|
|
||||||
}).Trace("database: got user favorite count")
|
|
||||||
|
|
||||||
return count, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetUserSourceLinks(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID) (map[string]models.UserSource, error) {
|
|
||||||
var userSources []models.UserSource
|
|
||||||
userSourceMap := make(map[string]models.UserSource)
|
|
||||||
|
|
||||||
if anthroveUserID == "" {
|
|
||||||
return nil, &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDIsEmpty}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(anthroveUserID) != 25 {
|
|
||||||
return nil, &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDToShort}
|
|
||||||
}
|
|
||||||
|
|
||||||
result := db.WithContext(ctx).Model(&models.UserSource{}).Where("user_id = ?", string(anthroveUserID)).Find(&userSources)
|
|
||||||
if result.Error != nil {
|
|
||||||
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
|
||||||
return nil, &otterError.NoDataFound{}
|
|
||||||
}
|
|
||||||
return nil, result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, userSource := range userSources {
|
|
||||||
var source models.Source
|
|
||||||
result = db.WithContext(ctx).Model(&models.Source{}).Where("id = ?", userSource.SourceID).First(&source)
|
|
||||||
if result.Error != nil {
|
|
||||||
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
|
||||||
return nil, &otterError.NoDataFound{}
|
|
||||||
}
|
|
||||||
return nil, result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
userSourceMap[source.DisplayName] = models.UserSource{
|
|
||||||
UserID: userSource.AccountID,
|
|
||||||
AccountUsername: userSource.AccountUsername,
|
|
||||||
Source: models.Source{
|
|
||||||
DisplayName: source.DisplayName,
|
|
||||||
Domain: source.Domain,
|
|
||||||
Icon: source.Icon,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"anthrove_user_id": anthroveUserID,
|
|
||||||
}).Trace("database: got user source link")
|
|
||||||
|
|
||||||
return userSourceMap, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetUserSourceBySourceID(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID, sourceID models.AnthroveSourceID) (*models.UserSource, error) {
|
|
||||||
var userSource models.UserSource
|
|
||||||
|
|
||||||
if anthroveUserID == "" {
|
|
||||||
return nil, &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDIsEmpty}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(anthroveUserID) != 25 {
|
|
||||||
return nil, &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDToShort}
|
|
||||||
}
|
|
||||||
|
|
||||||
if sourceID == "" {
|
|
||||||
return nil, &otterError.EntityValidationFailed{Reason: "sourceID cannot be empty"}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(sourceID) != 25 {
|
|
||||||
return nil, &otterError.EntityValidationFailed{Reason: "sourceID needs to be 25 characters long"}
|
|
||||||
}
|
|
||||||
|
|
||||||
result := db.WithContext(ctx).Model(&models.UserSource{}).InnerJoins("Source", db.Where("id = ?", sourceID)).Where("user_id = ?", string(anthroveUserID)).First(&userSource)
|
|
||||||
if result.Error != nil {
|
|
||||||
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
|
||||||
return nil, &otterError.NoDataFound{}
|
|
||||||
}
|
|
||||||
return nil, result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"anthrove_user_id": anthroveUserID,
|
|
||||||
"source_id": sourceID,
|
|
||||||
}).Trace("database: got specified user source link")
|
|
||||||
|
|
||||||
return &userSource, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetAllUsers(ctx context.Context, db *gorm.DB) ([]models.User, error) {
|
|
||||||
var users []models.User
|
|
||||||
|
|
||||||
result := db.WithContext(ctx).Model(&models.User{}).Find(&users)
|
|
||||||
if result.Error != nil {
|
|
||||||
if errors.Is(result.Error, gorm.ErrRecordNotFound) {
|
|
||||||
return nil, &otterError.NoDataFound{}
|
|
||||||
}
|
|
||||||
return nil, result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"anthrove_user_id_count": len(users),
|
|
||||||
}).Trace("database: got all anthrove user IDs")
|
|
||||||
|
|
||||||
return users, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: FIX THE TEST
|
|
||||||
func GetUserFavoriteWithPagination(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID, skip int, limit int) (*models.FavoriteList, error) {
|
|
||||||
var favoritePosts []models.Post
|
|
||||||
|
|
||||||
if anthroveUserID == "" {
|
|
||||||
return nil, &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDIsEmpty}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(anthroveUserID) != 25 {
|
|
||||||
return nil, &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDToShort}
|
|
||||||
}
|
|
||||||
|
|
||||||
db.WithContext(ctx).Joins("RIGHT JOIN \"UserFavorites\" AS of ON \"Post\".id = of.post_id AND of.user_id = ?", anthroveUserID).Preload("References").Offset(skip).Limit(limit).Find(&favoritePosts)
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"anthrove_user_id": anthroveUserID,
|
|
||||||
"anthrove_user_fav_count": len(favoritePosts),
|
|
||||||
}).Trace("database: got all anthrove user favorites")
|
|
||||||
|
|
||||||
return &models.FavoriteList{Posts: favoritePosts}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetUserTagWitRelationToFavedPosts(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID) ([]models.TagsWithFrequency, error) {
|
|
||||||
var queryUserFavorites []selectFrequencyTag
|
|
||||||
|
|
||||||
if anthroveUserID == "" {
|
|
||||||
return nil, &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDIsEmpty}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(anthroveUserID) != 25 {
|
|
||||||
return nil, &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDToShort}
|
|
||||||
}
|
|
||||||
|
|
||||||
rows, err := db.WithContext(ctx).Raw(
|
|
||||||
`WITH user_posts AS (
|
|
||||||
SELECT post_id FROM "UserFavorites" WHERE user_id = $1
|
|
||||||
)
|
|
||||||
SELECT post_tags.tag_name AS tag_name, count(*) AS count, (SELECT tag_type FROM "Tag" WHERE "Tag".name = post_tags.tag_name LIMIT 1) AS tag_type FROM post_tags, user_posts WHERE post_tags.post_id IN (user_posts.post_id) GROUP BY post_tags.tag_name ORDER BY tag_type DESC, tag_name DESC`, anthroveUserID).Rows()
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
||||||
return nil, &otterError.NoDataFound{}
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var userFavoritesFrequency = make([]models.TagsWithFrequency, 0)
|
|
||||||
defer rows.Close()
|
|
||||||
for rows.Next() {
|
|
||||||
var tagName string
|
|
||||||
var count int64
|
|
||||||
var tagType string
|
|
||||||
rows.Scan(&tagName, &count, &tagType)
|
|
||||||
userFavoritesFrequency = append(userFavoritesFrequency, models.TagsWithFrequency{
|
|
||||||
Frequency: count,
|
|
||||||
Tags: models.Tag{
|
|
||||||
Name: tagName,
|
|
||||||
Type: models.TagType(tagType),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"anthrove_user_id": anthroveUserID,
|
|
||||||
"tag_amount": len(queryUserFavorites),
|
|
||||||
}).Trace("database: got user tag node with relation to faved posts")
|
|
||||||
|
|
||||||
return userFavoritesFrequency, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func UpdateUserSourceScrapeTimeInterval(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID, sourceID models.AnthroveSourceID, scrapeTime models.AnthroveScrapeTimeInterval) error {
|
|
||||||
|
|
||||||
if anthroveUserID == "" {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDIsEmpty}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(anthroveUserID) != 25 {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDToShort}
|
|
||||||
}
|
|
||||||
|
|
||||||
if sourceID == "" {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveSourceIDEmpty}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(sourceID) != 25 {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveSourceIDToShort}
|
|
||||||
}
|
|
||||||
|
|
||||||
if scrapeTime == 0 {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: "ScrapeTimeInterval cannot be empty"}
|
|
||||||
}
|
|
||||||
|
|
||||||
userSource := &models.UserSource{
|
|
||||||
UserID: string(anthroveUserID),
|
|
||||||
}
|
|
||||||
|
|
||||||
result := db.WithContext(ctx).Model(&userSource).Update("scrape_time_interval", scrapeTime)
|
|
||||||
if result.Error != nil {
|
|
||||||
return result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func UpdateUserSourceLastScrapeTime(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID, sourceID models.AnthroveSourceID, lastScrapeTime models.AnthroveUserLastScrapeTime) error {
|
|
||||||
|
|
||||||
if anthroveUserID == "" {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDIsEmpty}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(anthroveUserID) != 25 {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDToShort}
|
|
||||||
}
|
|
||||||
|
|
||||||
if sourceID == "" {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveSourceIDEmpty}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(sourceID) != 25 {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveSourceIDToShort}
|
|
||||||
}
|
|
||||||
|
|
||||||
if time.Time.IsZero(time.Time(lastScrapeTime)) {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: "LastScrapeTime cannot be empty"}
|
|
||||||
}
|
|
||||||
|
|
||||||
userSource := &models.UserSource{
|
|
||||||
UserID: string(anthroveUserID),
|
|
||||||
}
|
|
||||||
|
|
||||||
result := db.WithContext(ctx).Model(&userSource).Update("last_scrape_time", time.Time(lastScrapeTime))
|
|
||||||
if result.Error != nil {
|
|
||||||
return result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func UpdateUserSourceValidation(ctx context.Context, db *gorm.DB, anthroveUserID models.AnthroveUserID, sourceID models.AnthroveSourceID, valid bool) error {
|
|
||||||
|
|
||||||
if anthroveUserID == "" {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDIsEmpty}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(anthroveUserID) != 25 {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveUserIDToShort}
|
|
||||||
}
|
|
||||||
|
|
||||||
if sourceID == "" {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveSourceIDEmpty}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(sourceID) != 25 {
|
|
||||||
return &otterError.EntityValidationFailed{Reason: otterError.AnthroveSourceIDToShort}
|
|
||||||
}
|
|
||||||
|
|
||||||
userSource := &models.UserSource{
|
|
||||||
UserID: string(anthroveUserID),
|
|
||||||
}
|
|
||||||
|
|
||||||
result := db.WithContext(ctx).Model(&userSource).Update("account_validate", valid)
|
|
||||||
if result.Error != nil {
|
|
||||||
return result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
100
internal/relationships.go
Normal file
100
internal/relationships.go
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.dragse.it/anthrove/otter-space-sdk/pkg/models"
|
||||||
|
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func EstablishAnthrovePostToSourceLink(ctx context.Context, driver neo4j.DriverWithContext, anthrovePostID models.AnthrovePostID, anthroveSourceDomain string, anthrovePostRelationship *models.AnthrovePostRelationship) error {
|
||||||
|
query := `
|
||||||
|
MATCH (sourceNode:Source {domain: $source_url})
|
||||||
|
MATCH (postNode:AnthrovePost {post_id: $anthrove_post_id})
|
||||||
|
MERGE (sourceNode)-[:REFERENCE {url: $source_post_url, source_post_id: $source_post_id}]->(postNode)
|
||||||
|
`
|
||||||
|
|
||||||
|
params := map[string]any{
|
||||||
|
"source_url": anthroveSourceDomain,
|
||||||
|
"anthrove_post_id": anthrovePostID,
|
||||||
|
"source_post_url": anthrovePostRelationship.Url,
|
||||||
|
"source_post_id": anthrovePostRelationship.PostID,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := neo4j.ExecuteQuery(ctx, driver, query, params, neo4j.EagerResultTransformer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"source_url": anthroveSourceDomain,
|
||||||
|
"anthrove_post_id": anthrovePostID,
|
||||||
|
"source_post_url": anthrovePostRelationship.Url,
|
||||||
|
"source_post_id": anthrovePostRelationship.PostID,
|
||||||
|
}).Trace("graph: creating anthrove post to source link")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func EstablishUserToPostLink(ctx context.Context, driver neo4j.DriverWithContext, anthroveUser *models.AnthroveUser, anthrovePost *models.AnthrovePost) error {
|
||||||
|
|
||||||
|
query := `
|
||||||
|
MATCH (user:User {user_id: $anthrove_user_id})
|
||||||
|
MATCH (anthrovePost:AnthrovePost {post_id: $anthrove_post_id})
|
||||||
|
MERGE (user)-[:FAV]->(anthrovePost)
|
||||||
|
`
|
||||||
|
|
||||||
|
params := map[string]any{
|
||||||
|
"anthrove_post_id": anthrovePost.PostID,
|
||||||
|
"anthrove_user_id": anthroveUser.UserID,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := neo4j.ExecuteQuery(ctx, driver, query, params, neo4j.EagerResultTransformer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"anthrove_post_id": anthrovePost.PostID,
|
||||||
|
"anthrove_user_id": anthroveUser.UserID,
|
||||||
|
}).Trace("graph: created user to post link")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckUserToPostLink(ctx context.Context, driver neo4j.DriverWithContext, anthroveUserID models.AnthroveUserID, sourcePostID string, sourceUrl string) (bool, error) {
|
||||||
|
query := `
|
||||||
|
OPTIONAL MATCH (:User {user_id: $anthrove_user_id})-[f:FAV]->(:AnthrovePost)<-[:REFERENCE{source_post_id: $source_post_id}]-(:Source{domain: $source_domain})
|
||||||
|
RETURN COUNT(f) > 0 AS hasRelationship
|
||||||
|
`
|
||||||
|
|
||||||
|
params := map[string]any{
|
||||||
|
"anthrove_user_id": anthroveUserID,
|
||||||
|
"source_post_id": sourcePostID,
|
||||||
|
"source_domain": sourceUrl,
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := neo4j.ExecuteQuery(ctx, driver, query, params, neo4j.EagerResultTransformer)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result.Records) == 0 {
|
||||||
|
return false, fmt.Errorf("no records found")
|
||||||
|
}
|
||||||
|
|
||||||
|
exists, _, err := neo4j.GetRecordValue[bool](result.Records[0], "hasRelationship")
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"relationship_exists": exists,
|
||||||
|
"relationship_anthrove_user_id": anthroveUserID,
|
||||||
|
"relationship_e621_post_id": "",
|
||||||
|
}).Trace("graph: checked user post relationship")
|
||||||
|
|
||||||
|
return exists, nil
|
||||||
|
}
|
35
internal/source.go
Normal file
35
internal/source.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.dragse.it/anthrove/otter-space-sdk/pkg/models"
|
||||||
|
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateSourceNode(ctx context.Context, driver neo4j.DriverWithContext, anthroveSource *models.AnthroveSource) error {
|
||||||
|
query := `
|
||||||
|
MERGE (sourceNode:Source {domain: $source_url})
|
||||||
|
ON CREATE SET sourceNode.domain = $source_url, sourceNode.display_name = $source_display_name, sourceNode.icon = $source_icon
|
||||||
|
`
|
||||||
|
params := map[string]any{
|
||||||
|
"source_url": anthroveSource.Domain,
|
||||||
|
"source_display_name": anthroveSource.DisplayName,
|
||||||
|
"source_icon": anthroveSource.Icon,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := neo4j.ExecuteQuery(ctx, driver, query, params, neo4j.EagerResultTransformer)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("graph: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"node_source_url": anthroveSource.Domain,
|
||||||
|
"node_source_displayName": anthroveSource.DisplayName,
|
||||||
|
"node_source_icon": anthroveSource.Icon,
|
||||||
|
}).Trace("graph: created source node")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
35
internal/tag.go
Normal file
35
internal/tag.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"git.dragse.it/anthrove/otter-space-sdk/pkg/models"
|
||||||
|
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateTagNodeWitRelation(ctx context.Context, driver neo4j.DriverWithContext, anthrovePostID models.AnthrovePostID, anthroveTag *models.AnthroveTag) error {
|
||||||
|
query := `
|
||||||
|
MATCH (anthrovePost:AnthrovePost {post_id: $anthrove_post_id})
|
||||||
|
MERGE (tagNode:Tag {name: $tag_name, type: $tag_type})
|
||||||
|
MERGE (anthrovePost)-[:HAS]->(tagNode)
|
||||||
|
`
|
||||||
|
params := map[string]interface{}{
|
||||||
|
"tag_name": anthroveTag.Name,
|
||||||
|
"tag_type": anthroveTag.Type,
|
||||||
|
"anthrove_post_id": anthrovePostID,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := neo4j.ExecuteQuery(ctx, driver, query, params, neo4j.EagerResultTransformer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"anthrove_post_id": anthrovePostID,
|
||||||
|
"tag_name": anthroveTag.Name,
|
||||||
|
"tag_type": anthroveTag.Type,
|
||||||
|
}).Trace("graph: created tag node")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
317
internal/user.go
Normal file
317
internal/user.go
Normal file
@ -0,0 +1,317 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"git.dragse.it/anthrove/otter-space-sdk/internal/utils"
|
||||||
|
"git.dragse.it/anthrove/otter-space-sdk/pkg/models"
|
||||||
|
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateUserNodeWithSourceRelation(ctx context.Context, driver neo4j.DriverWithContext, anthroveUserID models.AnthroveUserID, sourceDomain string, userID string, username string) error {
|
||||||
|
query := `
|
||||||
|
MATCH (userNode:User {user_id: $anthrove_user_id})
|
||||||
|
MATCH (sourceNode:Source {domain: $source_domain})
|
||||||
|
MERGE (userNode)-[r:HAS_ACCOUNT_AT {username: $source_user_name, user_id: $source_user_id}]->(sourceNode)
|
||||||
|
`
|
||||||
|
params := map[string]any{
|
||||||
|
"anthrove_user_id": anthroveUserID,
|
||||||
|
"source_user_id": userID,
|
||||||
|
"source_user_name": username,
|
||||||
|
"source_domain": sourceDomain,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := neo4j.ExecuteQuery(ctx, driver, query, params, neo4j.EagerResultTransformer)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var anthroveUserRelationship []models.AnthroveUserRelationship
|
||||||
|
|
||||||
|
anthroveUserRelationship = append(anthroveUserRelationship, models.AnthroveUserRelationship{
|
||||||
|
UserID: userID,
|
||||||
|
Username: username,
|
||||||
|
ScrapeTimeInterval: "",
|
||||||
|
Source: models.AnthroveSource{
|
||||||
|
DisplayName: "",
|
||||||
|
Domain: sourceDomain,
|
||||||
|
Icon: "",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"anthrove_user_id": anthroveUserID,
|
||||||
|
"source_user_id": userID,
|
||||||
|
"source_user_name": username,
|
||||||
|
"source_domain": sourceDomain,
|
||||||
|
}).Trace("graph: crated user with relationship")
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUserFavoritesCount(ctx context.Context, driver neo4j.DriverWithContext, anthroveUserID models.AnthroveUserID) (int64, error) {
|
||||||
|
var userFavoriteCount int64
|
||||||
|
|
||||||
|
query := `
|
||||||
|
MATCH (userNode:User {user_id: $anthrove_user_id})
|
||||||
|
MATCH (userNode)-[:FAV]->(favPost:AnthrovePost)
|
||||||
|
MATCH (sourceNode)-[:REFERENCE]->(favPost)
|
||||||
|
RETURN count( DISTINCT favPost) AS FavoritePostsCount
|
||||||
|
`
|
||||||
|
|
||||||
|
params := map[string]any{
|
||||||
|
"anthrove_user_id": anthroveUserID,
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := neo4j.ExecuteQuery(ctx, driver, query, params, neo4j.EagerResultTransformer)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result.Records) == 0 {
|
||||||
|
// no matches -> user does not exist, return count 0
|
||||||
|
return userFavoriteCount, err
|
||||||
|
}
|
||||||
|
|
||||||
|
record := result.Records[0]
|
||||||
|
|
||||||
|
userFavoriteCount, _, err = neo4j.GetRecordValue[int64](record, "FavoritePostsCount")
|
||||||
|
if err != nil {
|
||||||
|
return userFavoriteCount, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"anthrove_user_id": anthroveUserID,
|
||||||
|
"anthrove_user_fav_count": userFavoriteCount,
|
||||||
|
}).Trace("graph: got user favorite count")
|
||||||
|
|
||||||
|
return userFavoriteCount, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetUserSourceLink(ctx context.Context, driver neo4j.DriverWithContext, anthroveUserID models.AnthroveUserID) (map[string]models.AnthroveUserRelationship, error) {
|
||||||
|
|
||||||
|
userSource := make(map[string]models.AnthroveUserRelationship)
|
||||||
|
|
||||||
|
query := `
|
||||||
|
MATCH (user:User{user_id: $anthrove_user_id})-[r:HAS_ACCOUNT_AT]->(s:Source)
|
||||||
|
RETURN toString(r.user_id) AS sourceUserID, toString(r.username) AS sourceUsername, s as source;
|
||||||
|
`
|
||||||
|
params := map[string]any{
|
||||||
|
"anthrove_user_id": anthroveUserID,
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := neo4j.ExecuteQuery(ctx, driver, query, params, neo4j.EagerResultTransformer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result.Records) == 0 {
|
||||||
|
return nil, fmt.Errorf("user has no relations")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range result.Records {
|
||||||
|
record := result.Records[i]
|
||||||
|
source, _, err := neo4j.GetRecordValue[neo4j.Node](record, "source")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sourceUserID, _, err := neo4j.GetRecordValue[string](record, "sourceUserID")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sourceUsername, _, err := neo4j.GetRecordValue[string](record, "sourceUsername")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
displayName := source.Props["display_name"].(string)
|
||||||
|
domain := source.Props["domain"].(string)
|
||||||
|
icon := source.Props["icon"].(string)
|
||||||
|
|
||||||
|
anthroveSourceUser := models.AnthroveUserRelationship{
|
||||||
|
UserID: sourceUserID,
|
||||||
|
Username: sourceUsername,
|
||||||
|
Source: models.AnthroveSource{
|
||||||
|
DisplayName: displayName,
|
||||||
|
Domain: domain,
|
||||||
|
Icon: icon,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
userSource[displayName] = anthroveSourceUser
|
||||||
|
}
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"anthrove_user_id": anthroveUserID,
|
||||||
|
"anthrove_data": userSource,
|
||||||
|
}).Trace("graph: got user favorite count")
|
||||||
|
|
||||||
|
return userSource, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetSpecifiedUserSourceLink(ctx context.Context, driver neo4j.DriverWithContext, anthroveUserID models.AnthroveUserID, sourceDisplayName string) (map[string]models.AnthroveUserRelationship, error) {
|
||||||
|
|
||||||
|
userSource := make(map[string]models.AnthroveUserRelationship)
|
||||||
|
|
||||||
|
query := `
|
||||||
|
MATCH (user:User{user_id: $anthrove_user_id})-[r:HAS_ACCOUNT_AT]->(s:Source{display_name: $source_display_name})
|
||||||
|
RETURN toString(r.user_id) AS sourceUserID, toString(r.username) AS sourceUsername, s as source;
|
||||||
|
`
|
||||||
|
params := map[string]any{
|
||||||
|
"anthrove_user_id": anthroveUserID,
|
||||||
|
"source_display_name": sourceDisplayName,
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := neo4j.ExecuteQuery(ctx, driver, query, params, neo4j.EagerResultTransformer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result.Records) == 0 {
|
||||||
|
return nil, fmt.Errorf("user has no relations with the source %s", sourceDisplayName)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range result.Records {
|
||||||
|
record := result.Records[i]
|
||||||
|
source, _, err := neo4j.GetRecordValue[neo4j.Node](record, "source")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sourceUserID, _, err := neo4j.GetRecordValue[string](record, "sourceUserID")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sourceUsername, _, err := neo4j.GetRecordValue[string](record, "sourceUsername")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
displayName := source.Props["display_name"].(string)
|
||||||
|
domain := source.Props["domain"].(string)
|
||||||
|
icon := source.Props["icon"].(string)
|
||||||
|
|
||||||
|
anthroveSourceUser := models.AnthroveUserRelationship{
|
||||||
|
UserID: sourceUserID,
|
||||||
|
Username: sourceUsername,
|
||||||
|
Source: models.AnthroveSource{
|
||||||
|
DisplayName: displayName,
|
||||||
|
Domain: domain,
|
||||||
|
Icon: icon,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
userSource[displayName] = anthroveSourceUser
|
||||||
|
}
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"anthrove_user_id": anthroveUserID,
|
||||||
|
"anthrove_data": userSource,
|
||||||
|
}).Trace("graph: got user favorite count")
|
||||||
|
|
||||||
|
return userSource, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAnthroveUser(ctx context.Context, driver neo4j.DriverWithContext, anthroveUserID models.AnthroveUserID) (*models.AnthroveUser, error) {
|
||||||
|
var err error
|
||||||
|
var anthroveUser models.AnthroveUser
|
||||||
|
var userSources models.AnthroveSource
|
||||||
|
userRelationships := make([]models.AnthroveUserRelationship, 0)
|
||||||
|
|
||||||
|
query := `
|
||||||
|
MATCH (user:User{user_id: $anthrove_user_id})-[relation:HAS_ACCOUNT_AT]->(source:Source)
|
||||||
|
RETURN user as User, relation as Relation, source as Source;
|
||||||
|
`
|
||||||
|
params := map[string]any{
|
||||||
|
"anthrove_user_id": anthroveUserID,
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := neo4j.ExecuteQuery(ctx, driver, query, params, neo4j.EagerResultTransformer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result.Records) == 0 {
|
||||||
|
return nil, fmt.Errorf("user has no relations")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range result.Records {
|
||||||
|
record := result.Records[i]
|
||||||
|
|
||||||
|
user, _, err := neo4j.GetRecordValue[neo4j.Node](record, "User")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
relation, _, err := neo4j.GetRecordValue[neo4j.Relationship](record, "Relation")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
source, _, err := neo4j.GetRecordValue[neo4j.Node](record, "Source")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
userRelationships = append(userRelationships, models.AnthroveUserRelationship{
|
||||||
|
UserID: fmt.Sprintf("%v", utils.GetOrDefault(relation.Props, "user_id", "")),
|
||||||
|
Username: utils.GetOrDefault(relation.Props, "username", "").(string),
|
||||||
|
ScrapeTimeInterval: utils.GetOrDefault(relation.Props, "scrape_time_interval", "").(string),
|
||||||
|
})
|
||||||
|
|
||||||
|
userSources = models.AnthroveSource{
|
||||||
|
DisplayName: utils.GetOrDefault(source.Props, "display_name", "").(string),
|
||||||
|
Domain: utils.GetOrDefault(source.Props, "domain", "").(string),
|
||||||
|
Icon: utils.GetOrDefault(source.Props, "icon", "").(string),
|
||||||
|
}
|
||||||
|
|
||||||
|
anthroveUser.UserID = models.AnthroveUserID(utils.GetOrDefault(user.Props, "user_id", "").(string))
|
||||||
|
anthroveUser.Relationship = userRelationships
|
||||||
|
|
||||||
|
for j := range userRelationships {
|
||||||
|
anthroveUser.Relationship[j].Source = userSources
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"anthrove_user_id": anthroveUserID,
|
||||||
|
}).Trace("graph: got anthrove user")
|
||||||
|
|
||||||
|
return &anthroveUser, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAllAnthroveUserIDs(ctx context.Context, driver neo4j.DriverWithContext) ([]models.AnthroveUserID, error) {
|
||||||
|
var err error
|
||||||
|
var anthroveUsers []models.AnthroveUserID
|
||||||
|
|
||||||
|
query := `
|
||||||
|
MATCH (anthroveUser:User)
|
||||||
|
RETURN anthroveUser
|
||||||
|
`
|
||||||
|
result, err := neo4j.ExecuteQuery(ctx, driver, query, nil, neo4j.EagerResultTransformer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result.Records) == 0 {
|
||||||
|
log.Warnf("No users found, this should not be happening!")
|
||||||
|
return []models.AnthroveUserID{}, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range result.Records {
|
||||||
|
record := result.Records[i]
|
||||||
|
|
||||||
|
user, _, err := neo4j.GetRecordValue[neo4j.Node](record, "anthroveUser")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
anthroveUsers = append(anthroveUsers, models.AnthroveUserID(fmt.Sprintf(user.Props["user_id"].(string))))
|
||||||
|
}
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"anthrove_user_id_count": len(anthroveUsers),
|
||||||
|
}).Trace("graph: got al anthrove user IDs")
|
||||||
|
|
||||||
|
return anthroveUsers, nil
|
||||||
|
|
||||||
|
}
|
@ -1,60 +0,0 @@
|
|||||||
package utils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGetOrDefault(t *testing.T) {
|
|
||||||
type args struct {
|
|
||||||
data map[string]any
|
|
||||||
key string
|
|
||||||
defaultVal any
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
args args
|
|
||||||
want any
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Test 1: Nil map",
|
|
||||||
args: args{
|
|
||||||
data: nil,
|
|
||||||
key: "key1",
|
|
||||||
defaultVal: "default",
|
|
||||||
},
|
|
||||||
want: "default",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test 2: Existing key",
|
|
||||||
args: args{
|
|
||||||
data: map[string]interface{}{
|
|
||||||
"key1": "value1",
|
|
||||||
"key2": "value2",
|
|
||||||
},
|
|
||||||
key: "key1",
|
|
||||||
defaultVal: "default",
|
|
||||||
},
|
|
||||||
want: "value1",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test 3: Non-existing key",
|
|
||||||
args: args{
|
|
||||||
data: map[string]interface{}{
|
|
||||||
"key1": "value1",
|
|
||||||
"key2": "value2",
|
|
||||||
},
|
|
||||||
key: "key3",
|
|
||||||
defaultVal: "default",
|
|
||||||
},
|
|
||||||
want: "default",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
if got := GetOrDefault(tt.args.data, tt.args.key, tt.args.defaultVal); !reflect.DeepEqual(got, tt.want) {
|
|
||||||
t.Errorf("GetOrDefault() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
package database
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"git.dragse.it/anthrove/otter-space-sdk/v2/pkg/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
type OtterSpace interface {
|
|
||||||
// Connect establishes a connection to the database.
|
|
||||||
Connect(ctx context.Context, config models.DatabaseConfig) error
|
|
||||||
|
|
||||||
// Post contains all function that are needed to manage Posts
|
|
||||||
Post
|
|
||||||
|
|
||||||
// User contains all function that are needed to manage the AnthroveUser
|
|
||||||
User
|
|
||||||
|
|
||||||
// Source contains all function that are needed to manage the Source
|
|
||||||
Source
|
|
||||||
|
|
||||||
// Tag contains all functions that are used to manage Tag
|
|
||||||
Tag
|
|
||||||
|
|
||||||
// TagAlias contains all function that are needed to manage the TagAlias
|
|
||||||
TagAlias
|
|
||||||
|
|
||||||
// TagGroup contains all function that are needed to manage the TagGroup
|
|
||||||
TagGroup
|
|
||||||
}
|
|
@ -1,109 +0,0 @@
|
|||||||
-- +migrate Up
|
|
||||||
CREATE TYPE Rating AS ENUM (
|
|
||||||
'safe',
|
|
||||||
'questionable',
|
|
||||||
'explicit'
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TYPE TagType AS ENUM (
|
|
||||||
'general',
|
|
||||||
'species',
|
|
||||||
'character',
|
|
||||||
'artist',
|
|
||||||
'lore',
|
|
||||||
'meta',
|
|
||||||
'invalid',
|
|
||||||
'copyright'
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE "Post"
|
|
||||||
(
|
|
||||||
id CHAR(25) PRIMARY KEY,
|
|
||||||
rating Rating,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
deleted_at TIMESTAMP NULL NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE "Source"
|
|
||||||
(
|
|
||||||
id CHAR(25) PRIMARY KEY,
|
|
||||||
display_name TEXT NULL,
|
|
||||||
icon TEXT NULL,
|
|
||||||
domain TEXT NOT NULL UNIQUE,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
deleted_at TIMESTAMP NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE "Tag"
|
|
||||||
(
|
|
||||||
name TEXT PRIMARY KEY,
|
|
||||||
tag_type TagType,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
deleted_at TIMESTAMP NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE "User"
|
|
||||||
(
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
deleted_at TIMESTAMP NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE "PostReference"
|
|
||||||
(
|
|
||||||
post_id TEXT REFERENCES "Post" (id),
|
|
||||||
source_id TEXT REFERENCES "Source" (id),
|
|
||||||
url TEXT NOT NULL,
|
|
||||||
full_file_url TEXT,
|
|
||||||
preview_file_url TEXT,
|
|
||||||
sample_file_url TEXT,
|
|
||||||
source_post_id TEXT,
|
|
||||||
PRIMARY KEY (post_id, source_id, url)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE "TagAlias"
|
|
||||||
(
|
|
||||||
name TEXT PRIMARY KEY,
|
|
||||||
tag_id TEXT REFERENCES "Tag" (name),
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE "TagGroup"
|
|
||||||
(
|
|
||||||
name TEXT PRIMARY KEY,
|
|
||||||
tag_id TEXT REFERENCES "Tag" (name),
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE "UserFavorites"
|
|
||||||
(
|
|
||||||
user_id TEXT REFERENCES "User" (id),
|
|
||||||
post_id TEXT REFERENCES "Post" (id),
|
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
||||||
PRIMARY KEY (user_id, post_id)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE "UserSource"
|
|
||||||
(
|
|
||||||
user_id TEXT REFERENCES "User" (id),
|
|
||||||
source_id TEXT REFERENCES "Source" (id),
|
|
||||||
scrape_time_interval INT,
|
|
||||||
account_username TEXT,
|
|
||||||
account_id TEXT,
|
|
||||||
last_scrape_time TIMESTAMP,
|
|
||||||
account_validate BOOL DEFAULT FALSE,
|
|
||||||
account_validation_key CHAR(25),
|
|
||||||
PRIMARY KEY (user_id, source_id),
|
|
||||||
UNIQUE (source_id, account_username, account_id)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE "post_tags"
|
|
||||||
(
|
|
||||||
post_id TEXT REFERENCES "Post" (id),
|
|
||||||
tag_name TEXT REFERENCES "Tag" (name),
|
|
||||||
PRIMARY KEY (post_id, tag_name)
|
|
||||||
);
|
|
@ -1,31 +0,0 @@
|
|||||||
package database
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"git.dragse.it/anthrove/otter-space-sdk/v2/pkg/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Post interface {
|
|
||||||
|
|
||||||
// CreatePost adds a new post to the database.
|
|
||||||
CreatePost(ctx context.Context, anthrovePost *models.Post) error
|
|
||||||
|
|
||||||
// TODO: Everything
|
|
||||||
CreatePostInBatch(ctx context.Context, anthrovePost []models.Post, batchSize int) error
|
|
||||||
|
|
||||||
// GetPostByAnthroveID retrieves a post by its Anthrove ID.
|
|
||||||
GetPostByAnthroveID(ctx context.Context, anthrovePostID models.AnthrovePostID) (*models.Post, error)
|
|
||||||
|
|
||||||
// GetPostByURL retrieves a post by its source URL.
|
|
||||||
GetPostByURL(ctx context.Context, postURL string) (*models.Post, error)
|
|
||||||
|
|
||||||
// GetPostBySourceID retrieves a post by its source ID.
|
|
||||||
GetPostBySourceID(ctx context.Context, sourceID models.AnthroveSourceID) (*models.Post, error)
|
|
||||||
|
|
||||||
// CreatePostWithReferenceToTagAnd adds a tag with a relation to a post.
|
|
||||||
CreatePostWithReferenceToTagAnd(ctx context.Context, anthrovePostID models.AnthrovePostID, anthroveTag *models.Tag) error
|
|
||||||
|
|
||||||
// CreatePostReference links a post with a source.
|
|
||||||
CreatePostReference(ctx context.Context, anthrovePostID models.AnthrovePostID, sourceDomain models.AnthroveSourceDomain, postURL models.AnthrovePostURL, config models.PostReferenceConfig) error
|
|
||||||
}
|
|
@ -1,261 +0,0 @@
|
|||||||
package database
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"embed"
|
|
||||||
"fmt"
|
|
||||||
log2 "log"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.dragse.it/anthrove/otter-space-sdk/v2/internal/postgres"
|
|
||||||
"git.dragse.it/anthrove/otter-space-sdk/v2/pkg/models"
|
|
||||||
_ "github.com/lib/pq"
|
|
||||||
migrate "github.com/rubenv/sql-migrate"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
gormPostgres "gorm.io/driver/postgres"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
gormLogger "gorm.io/gorm/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed migrations/*.sql
|
|
||||||
var embedMigrations embed.FS
|
|
||||||
|
|
||||||
type postgresqlConnection struct {
|
|
||||||
db *gorm.DB
|
|
||||||
debug bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewPostgresqlConnection() OtterSpace {
|
|
||||||
return &postgresqlConnection{
|
|
||||||
db: nil,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgresqlConnection) Connect(_ context.Context, config models.DatabaseConfig) error {
|
|
||||||
var localSSL string
|
|
||||||
var logLevel gormLogger.LogLevel
|
|
||||||
|
|
||||||
if config.SSL {
|
|
||||||
localSSL = "require"
|
|
||||||
} else {
|
|
||||||
localSSL = "disable"
|
|
||||||
}
|
|
||||||
|
|
||||||
p.debug = config.Debug
|
|
||||||
|
|
||||||
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d sslmode=%s TimeZone=%s", config.Endpoint, config.Username, config.Password, config.Database, config.Port, localSSL, config.Timezone)
|
|
||||||
var err error
|
|
||||||
|
|
||||||
if p.debug {
|
|
||||||
logLevel = gormLogger.Info
|
|
||||||
} else {
|
|
||||||
logLevel = gormLogger.Silent
|
|
||||||
}
|
|
||||||
|
|
||||||
dbLogger := gormLogger.New(log2.New(os.Stdout, "\r\n", log2.LstdFlags), gormLogger.Config{
|
|
||||||
SlowThreshold: 200 * time.Millisecond,
|
|
||||||
LogLevel: logLevel,
|
|
||||||
IgnoreRecordNotFoundError: true,
|
|
||||||
Colorful: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
db, err := gorm.Open(gormPostgres.Open(dsn), &gorm.Config{
|
|
||||||
Logger: dbLogger,
|
|
||||||
})
|
|
||||||
p.db = db
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("OtterSpace: database connection established")
|
|
||||||
|
|
||||||
err = p.migrateDatabase(db)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("OtterSpace: migration compleate")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgresqlConnection) CreateUserWithRelationToSource(ctx context.Context, anthroveUserID models.AnthroveUserID, sourceID models.AnthroveSourceID, accountId string, accountUsername string) error {
|
|
||||||
return postgres.CreateUserWithRelationToSource(ctx, p.db, anthroveUserID, sourceID, accountId, accountUsername)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgresqlConnection) CreateSource(ctx context.Context, anthroveSource *models.Source) error {
|
|
||||||
return postgres.CreateSource(ctx, p.db, anthroveSource)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgresqlConnection) CreatePost(ctx context.Context, anthrovePost *models.Post) error {
|
|
||||||
return postgres.CreatePost(ctx, p.db, anthrovePost)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgresqlConnection) CreatePostInBatch(ctx context.Context, anthrovePost []models.Post, batchSize int) error {
|
|
||||||
return postgres.CreatePostInBatch(ctx, p.db, anthrovePost, batchSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgresqlConnection) CreatePostWithReferenceToTagAnd(ctx context.Context, anthrovePostID models.AnthrovePostID, anthroveTag *models.Tag) error {
|
|
||||||
return postgres.CreateTagAndReferenceToPost(ctx, p.db, anthrovePostID, anthroveTag)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgresqlConnection) CreatePostReference(ctx context.Context, anthrovePostID models.AnthrovePostID, sourceDomain models.AnthroveSourceDomain, postURL models.AnthrovePostURL, config models.PostReferenceConfig) error {
|
|
||||||
return postgres.CreateReferenceBetweenPostAndSource(ctx, p.db, anthrovePostID, sourceDomain, postURL, config)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgresqlConnection) CreateReferenceBetweenUserAndPost(ctx context.Context, anthroveUserID models.AnthroveUserID, anthrovePostID models.AnthrovePostID) error {
|
|
||||||
return postgres.CreateReferenceBetweenUserAndPost(ctx, p.db, anthroveUserID, anthrovePostID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgresqlConnection) CheckIfUserHasPostAsFavorite(ctx context.Context, anthroveUserID models.AnthroveUserID, anthrovePostID models.AnthrovePostID) (bool, error) {
|
|
||||||
return postgres.CheckReferenceBetweenUserAndPost(ctx, p.db, anthroveUserID, anthrovePostID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgresqlConnection) GetPostByAnthroveID(ctx context.Context, anthrovePostID models.AnthrovePostID) (*models.Post, error) {
|
|
||||||
return postgres.GetPostByAnthroveID(ctx, p.db, anthrovePostID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgresqlConnection) GetPostByURL(ctx context.Context, sourceUrl string) (*models.Post, error) {
|
|
||||||
return postgres.GetPostBySourceURL(ctx, p.db, sourceUrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgresqlConnection) GetPostBySourceID(ctx context.Context, sourceID models.AnthroveSourceID) (*models.Post, error) {
|
|
||||||
return postgres.GetPostBySourceID(ctx, p.db, sourceID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgresqlConnection) GetUserFavoritesCount(ctx context.Context, anthroveUserID models.AnthroveUserID) (int64, error) {
|
|
||||||
return postgres.GetUserFavoritesCount(ctx, p.db, anthroveUserID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgresqlConnection) GetAllUserSources(ctx context.Context, anthroveUserID models.AnthroveUserID) (map[string]models.UserSource, error) {
|
|
||||||
return postgres.GetUserSourceLinks(ctx, p.db, anthroveUserID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgresqlConnection) GetUserSourceBySourceID(ctx context.Context, anthroveUserID models.AnthroveUserID, sourceID models.AnthroveSourceID) (*models.UserSource, error) {
|
|
||||||
return postgres.GetUserSourceBySourceID(ctx, p.db, anthroveUserID, sourceID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgresqlConnection) GetAllUsers(ctx context.Context) ([]models.User, error) {
|
|
||||||
return postgres.GetAllUsers(ctx, p.db)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgresqlConnection) GetAllUserFavoritesWithPagination(ctx context.Context, anthroveUserID models.AnthroveUserID, skip int, limit int) (*models.FavoriteList, error) {
|
|
||||||
return postgres.GetUserFavoriteWithPagination(ctx, p.db, anthroveUserID, skip, limit)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgresqlConnection) GetAllTagsFromUser(ctx context.Context, anthroveUserID models.AnthroveUserID) ([]models.TagsWithFrequency, error) {
|
|
||||||
return postgres.GetUserTagWitRelationToFavedPosts(ctx, p.db, anthroveUserID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgresqlConnection) GetAllTags(ctx context.Context) ([]models.Tag, error) {
|
|
||||||
return postgres.GetTags(ctx, p.db)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgresqlConnection) GetAllSources(ctx context.Context) ([]models.Source, error) {
|
|
||||||
return postgres.GetAllSource(ctx, p.db)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgresqlConnection) GetSourceByDomain(ctx context.Context, sourceDomain models.AnthroveSourceDomain) (*models.Source, error) {
|
|
||||||
return postgres.GetSourceByDomain(ctx, p.db, sourceDomain)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NEW FUNCTIONS
|
|
||||||
|
|
||||||
func (p *postgresqlConnection) UpdateUserSourceScrapeTimeInterval(ctx context.Context, anthroveUserID models.AnthroveUserID, sourceID models.AnthroveSourceID, scrapeTime models.AnthroveScrapeTimeInterval) error {
|
|
||||||
return postgres.UpdateUserSourceScrapeTimeInterval(ctx, p.db, anthroveUserID, sourceID, scrapeTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgresqlConnection) UpdateUserSourceLastScrapeTime(ctx context.Context, anthroveUserID models.AnthroveUserID, sourceID models.AnthroveSourceID, lastScrapeTime models.AnthroveUserLastScrapeTime) error {
|
|
||||||
return postgres.UpdateUserSourceLastScrapeTime(ctx, p.db, anthroveUserID, sourceID, lastScrapeTime)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgresqlConnection) UpdateUserSourceValidation(ctx context.Context, anthroveUserID models.AnthroveUserID, sourceID models.AnthroveSourceID, valid bool) error {
|
|
||||||
return postgres.UpdateUserSourceValidation(ctx, p.db, anthroveUserID, sourceID, valid)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgresqlConnection) CreateTagAlias(ctx context.Context, tagAliasName models.AnthroveTagAliasName, tagID models.AnthroveTagID) error {
|
|
||||||
return postgres.CreateTagAlias(ctx, p.db, tagAliasName, tagID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgresqlConnection) GetAllTagAlias(ctx context.Context) ([]models.TagAlias, error) {
|
|
||||||
return postgres.GetAllTagAlias(ctx, p.db)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgresqlConnection) GetAllTagAliasByTag(ctx context.Context, tagID models.AnthroveTagID) ([]models.TagAlias, error) {
|
|
||||||
return postgres.GetAllTagAliasByTag(ctx, p.db, tagID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgresqlConnection) DeleteTagAlias(ctx context.Context, tagAliasName models.AnthroveTagAliasName) error {
|
|
||||||
return postgres.DeleteTagAlias(ctx, p.db, tagAliasName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgresqlConnection) CreateTagGroup(ctx context.Context, tagGroupName models.AnthroveTagGroupName, tagID models.AnthroveTagID) error {
|
|
||||||
return postgres.CreateTagGroup(ctx, p.db, tagGroupName, tagID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgresqlConnection) GetAllTagGroup(ctx context.Context) ([]models.TagGroup, error) {
|
|
||||||
return postgres.GetAllTagGroup(ctx, p.db)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgresqlConnection) GetAllTagGroupByTag(ctx context.Context, tagID models.AnthroveTagID) ([]models.TagGroup, error) {
|
|
||||||
return postgres.GetAllTagGroupByTag(ctx, p.db, tagID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgresqlConnection) DeleteTagGroup(ctx context.Context, tagGroupName models.AnthroveTagGroupName) error {
|
|
||||||
return postgres.DeleteTagGroup(ctx, p.db, tagGroupName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgresqlConnection) CreateTag(ctx context.Context, tagName models.AnthroveTagName, tagType models.TagType) error {
|
|
||||||
return postgres.CreateTag(ctx, p.db, tagName, tagType)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgresqlConnection) GetAllTagsByTagType(ctx context.Context, tagType models.TagType) ([]models.Tag, error) {
|
|
||||||
return postgres.GetAllTagByTagsType(ctx, p.db, tagType)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgresqlConnection) DeleteTag(ctx context.Context, tagName models.AnthroveTagName) error {
|
|
||||||
return postgres.DeleteTag(ctx, p.db, tagName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgresqlConnection) CreateTagInBatchAndUpdate(ctx context.Context, tags []models.Tag, batchSize int) error {
|
|
||||||
return postgres.CreateTagInBatchAndUpdate(ctx, p.db, tags, batchSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgresqlConnection) CreateTagAliasInBatch(ctx context.Context, tagAliases []models.TagAlias, batchSize int) error {
|
|
||||||
return postgres.CreateTagAliasInBatch(ctx, p.db, tagAliases, batchSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *postgresqlConnection) CreateTagGroupInBatch(ctx context.Context, tagGroups []models.TagGroup, batchSize int) error {
|
|
||||||
return postgres.CreateTagGroupInBatch(ctx, p.db, tagGroups, batchSize)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// HELPER
|
|
||||||
|
|
||||||
func (p *postgresqlConnection) migrateDatabase(dbPool *gorm.DB) error {
|
|
||||||
dialect := "postgres"
|
|
||||||
migrations := &migrate.EmbedFileSystemMigrationSource{FileSystem: embedMigrations, Root: "migrations"}
|
|
||||||
|
|
||||||
db, err := dbPool.DB()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("postgres migration: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := migrate.Exec(db, dialect, migrations, migrate.Up)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("postgres migration: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.debug {
|
|
||||||
if n != 0 {
|
|
||||||
log.Infof("postgres migration: applied %d migrations!", n)
|
|
||||||
|
|
||||||
} else {
|
|
||||||
log.Info("postgres migration: nothing to migrate")
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,19 +0,0 @@
|
|||||||
package database
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"git.dragse.it/anthrove/otter-space-sdk/v2/pkg/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Source interface {
|
|
||||||
|
|
||||||
// CreateSource adds a new source to the database.
|
|
||||||
CreateSource(ctx context.Context, anthroveSource *models.Source) error
|
|
||||||
|
|
||||||
// GetAllSources retrieves all sources.
|
|
||||||
GetAllSources(ctx context.Context) ([]models.Source, error)
|
|
||||||
|
|
||||||
// GetSourceByDomain retrieves a source by its URL.
|
|
||||||
GetSourceByDomain(ctx context.Context, sourceDomain models.AnthroveSourceDomain) (*models.Source, error)
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
package database
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"git.dragse.it/anthrove/otter-space-sdk/v2/pkg/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Tag interface {
|
|
||||||
CreateTag(ctx context.Context, tagName models.AnthroveTagName, tagType models.TagType) error
|
|
||||||
|
|
||||||
CreateTagInBatchAndUpdate(ctx context.Context, tags []models.Tag, batchSize int) error
|
|
||||||
|
|
||||||
// GetAllTags retrieves all tags.
|
|
||||||
GetAllTags(ctx context.Context) ([]models.Tag, error)
|
|
||||||
|
|
||||||
GetAllTagsByTagType(ctx context.Context, tagType models.TagType) ([]models.Tag, error)
|
|
||||||
|
|
||||||
DeleteTag(ctx context.Context, tagName models.AnthroveTagName) error
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package database
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"git.dragse.it/anthrove/otter-space-sdk/v2/pkg/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TagAlias interface {
|
|
||||||
CreateTagAlias(ctx context.Context, tagAliasName models.AnthroveTagAliasName, tagID models.AnthroveTagID) error
|
|
||||||
|
|
||||||
CreateTagAliasInBatch(ctx context.Context, tagsAliases []models.TagAlias, batchSize int) error
|
|
||||||
|
|
||||||
GetAllTagAlias(ctx context.Context) ([]models.TagAlias, error)
|
|
||||||
|
|
||||||
GetAllTagAliasByTag(ctx context.Context, tagID models.AnthroveTagID) ([]models.TagAlias, error)
|
|
||||||
|
|
||||||
DeleteTagAlias(ctx context.Context, tagAliasName models.AnthroveTagAliasName) error
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package database
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"git.dragse.it/anthrove/otter-space-sdk/v2/pkg/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TagGroup interface {
|
|
||||||
CreateTagGroup(ctx context.Context, tagGroupName models.AnthroveTagGroupName, tagID models.AnthroveTagID) error
|
|
||||||
|
|
||||||
CreateTagGroupInBatch(ctx context.Context, tagsGroups []models.TagGroup, batchSize int) error
|
|
||||||
|
|
||||||
GetAllTagGroup(ctx context.Context) ([]models.TagGroup, error)
|
|
||||||
|
|
||||||
GetAllTagGroupByTag(ctx context.Context, tagID models.AnthroveTagID) ([]models.TagGroup, error)
|
|
||||||
|
|
||||||
DeleteTagGroup(ctx context.Context, tagGroupName models.AnthroveTagGroupName) error
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
package database
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"git.dragse.it/anthrove/otter-space-sdk/v2/pkg/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
type User interface {
|
|
||||||
|
|
||||||
// CreateUserWithRelationToSource adds a user with a relation to a source.
|
|
||||||
CreateUserWithRelationToSource(ctx context.Context, anthroveUserID models.AnthroveUserID, sourceID models.AnthroveSourceID, accountId string, accountUsername string) error
|
|
||||||
|
|
||||||
// CreateReferenceBetweenUserAndPost links a user with a post.
|
|
||||||
CreateReferenceBetweenUserAndPost(ctx context.Context, anthroveUserID models.AnthroveUserID, anthrovePostID models.AnthrovePostID) error
|
|
||||||
|
|
||||||
UpdateUserSourceScrapeTimeInterval(ctx context.Context, anthroveUserID models.AnthroveUserID, sourceID models.AnthroveSourceID, scrapeTime models.AnthroveScrapeTimeInterval) error
|
|
||||||
|
|
||||||
UpdateUserSourceLastScrapeTime(ctx context.Context, anthroveUserID models.AnthroveUserID, sourceID models.AnthroveSourceID, lastScrapeTime models.AnthroveUserLastScrapeTime) error
|
|
||||||
|
|
||||||
UpdateUserSourceValidation(ctx context.Context, anthroveUserID models.AnthroveUserID, sourceID models.AnthroveSourceID, valid bool) error
|
|
||||||
|
|
||||||
GetAllUsers(ctx context.Context) ([]models.User, error)
|
|
||||||
|
|
||||||
// GetUserFavoritesCount retrieves the count of a user's favorites.
|
|
||||||
GetUserFavoritesCount(ctx context.Context, anthroveUserID models.AnthroveUserID) (int64, error)
|
|
||||||
|
|
||||||
// GetAllUserSources retrieves the source links of a user.
|
|
||||||
GetAllUserSources(ctx context.Context, anthroveUserID models.AnthroveUserID) (map[string]models.UserSource, error)
|
|
||||||
|
|
||||||
// GetUserSourceBySourceID retrieves a specified source link of a user.
|
|
||||||
GetUserSourceBySourceID(ctx context.Context, anthroveUserID models.AnthroveUserID, sourceID models.AnthroveSourceID) (*models.UserSource, error)
|
|
||||||
|
|
||||||
// GetAllUserFavoritesWithPagination retrieves a user's favorite posts with pagination.
|
|
||||||
GetAllUserFavoritesWithPagination(ctx context.Context, anthroveUserID models.AnthroveUserID, skip int, limit int) (*models.FavoriteList, error)
|
|
||||||
|
|
||||||
// GetAllTagsFromUser retrieves a user's tags through their favorite posts.
|
|
||||||
GetAllTagsFromUser(ctx context.Context, anthroveUserID models.AnthroveUserID) ([]models.TagsWithFrequency, error)
|
|
||||||
|
|
||||||
// CheckIfUserHasPostAsFavorite checks if a user-post link exists.
|
|
||||||
CheckIfUserHasPostAsFavorite(ctx context.Context, anthroveUserID models.AnthroveUserID, sourcePostID models.AnthrovePostID) (bool, error)
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
package error
|
|
||||||
|
|
||||||
type EntityAlreadyExists struct{}
|
|
||||||
|
|
||||||
func (e *EntityAlreadyExists) Error() string {
|
|
||||||
return "EntityAlreadyExists error"
|
|
||||||
}
|
|
||||||
|
|
||||||
type NoDataWritten struct{}
|
|
||||||
|
|
||||||
func (e *NoDataWritten) Error() string {
|
|
||||||
return "NoDataWritten error"
|
|
||||||
}
|
|
||||||
|
|
||||||
type NoDataFound struct{}
|
|
||||||
|
|
||||||
func (e *NoDataFound) Error() string {
|
|
||||||
return "NoDataFound error"
|
|
||||||
}
|
|
||||||
|
|
||||||
type NoRelationCreated struct{}
|
|
||||||
|
|
||||||
func (e *NoRelationCreated) Error() string {
|
|
||||||
return "relationship creation error"
|
|
||||||
}
|
|
@ -1,83 +0,0 @@
|
|||||||
package error
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func TestEntityAlreadyExists_Error(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Test : Valid error String",
|
|
||||||
want: "EntityAlreadyExists error",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
e := &EntityAlreadyExists{}
|
|
||||||
if got := e.Error(); got != tt.want {
|
|
||||||
t.Errorf("Error() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNoDataFound_Error(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Test : Valid error String",
|
|
||||||
want: "NoDataFound error",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
e := &NoDataFound{}
|
|
||||||
if got := e.Error(); got != tt.want {
|
|
||||||
t.Errorf("Error() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNoDataWritten_Error(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Test : Valid error String",
|
|
||||||
want: "NoDataWritten error",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
e := &NoDataWritten{}
|
|
||||||
if got := e.Error(); got != tt.want {
|
|
||||||
t.Errorf("Error() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNoRelationCreated_Error(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Test : Valid error String",
|
|
||||||
want: "relationship creation error",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
e := &NoRelationCreated{}
|
|
||||||
if got := e.Error(); got != tt.want {
|
|
||||||
t.Errorf("Error() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
package error
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
const (
|
|
||||||
AnthroveUserIDIsEmpty = "anthrovePostID cannot be empty"
|
|
||||||
AnthroveUserIDToShort = "anthrovePostID needs to be 25 characters long"
|
|
||||||
AnthroveSourceIDEmpty = "anthroveSourceID cannot be empty"
|
|
||||||
AnthroveSourceIDToShort = "anthroveSourceID needs to be 25 characters long"
|
|
||||||
AnthroveTagIDEmpty = "tagID cannot be empty"
|
|
||||||
)
|
|
||||||
|
|
||||||
type EntityValidationFailed struct {
|
|
||||||
Reason string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e EntityValidationFailed) Error() string {
|
|
||||||
return fmt.Sprintf("Entity validation failed: %s", e.Reason)
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
package error
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func TestEntityValidationFailed_Error(t *testing.T) {
|
|
||||||
type fields struct {
|
|
||||||
Reason string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
fields fields
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Test 1: Reason",
|
|
||||||
fields: fields{Reason: "TEST ERROR"},
|
|
||||||
want: "Entity validation failed: TEST ERROR",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
e := EntityValidationFailed{
|
|
||||||
Reason: tt.fields.Reason,
|
|
||||||
}
|
|
||||||
if got := e.Error(); got != tt.want {
|
|
||||||
t.Errorf("Error() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
100
pkg/graph/graph.go
Normal file
100
pkg/graph/graph.go
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
// Package graph provides a client for using the OtterSpace API.
|
||||||
|
//
|
||||||
|
// This package provides a client to interact with the OtterSpace API. It includes
|
||||||
|
// methods for all API endpoints, and convenience methods for common tasks.
|
||||||
|
//
|
||||||
|
// This is a simple usage example:
|
||||||
|
//
|
||||||
|
// package main
|
||||||
|
//
|
||||||
|
// import (
|
||||||
|
// "context"
|
||||||
|
// "fmt"
|
||||||
|
// "git.dragse.it/anthrove/otter-space-sdk/pkg/models"
|
||||||
|
// "git.dragse.it/anthrove/otter-space-sdk/pkg/graph"
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// func main() {
|
||||||
|
// client := graph.NewGraphConnection()
|
||||||
|
// err := client.Connect(context.Background(), "your-endpoint", "your-username", "your-password")
|
||||||
|
// if err != nil {
|
||||||
|
// fmt.Println(err)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// // further usage of the client...
|
||||||
|
// }
|
||||||
|
package graph
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"git.dragse.it/anthrove/otter-space-sdk/pkg/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// OtterSpace provides an interface for interacting with the OtterSpace API.
|
||||||
|
// It includes methods for connecting to the API, adding and linking users, posts, and sources,
|
||||||
|
// and retrieving information about users and posts.
|
||||||
|
type OtterSpace interface {
|
||||||
|
// Connect sets up a connection to the OtterSpace API endpoint using the provided username and password.
|
||||||
|
// It returns an error if the connection cannot be established.
|
||||||
|
Connect(ctx context.Context, endpoint string, username string, password string) error
|
||||||
|
|
||||||
|
// AddUserWithRelationToSource adds a new user to the OtterSpace graph and associates them with a source.
|
||||||
|
// It returns the newly created user and an error if the operation fails.
|
||||||
|
AddUserWithRelationToSource(ctx context.Context, anthroveUserID models.AnthroveUserID, sourceDomain string, userID string, username string) error
|
||||||
|
|
||||||
|
// AddSource adds a new source to the OtterSpace graph.
|
||||||
|
// It returns an error if the operation fails.
|
||||||
|
AddSource(ctx context.Context, anthroveSource *models.AnthroveSource) error
|
||||||
|
|
||||||
|
// AddPost adds a new post to the OtterSpace graph.
|
||||||
|
// It returns an error if the operation fails.
|
||||||
|
AddPost(ctx context.Context, anthrovePost *models.AnthrovePost) error
|
||||||
|
|
||||||
|
// AddTagWithRelationToPost adds a new tag to the OtterSpace graph and associates it with a post.
|
||||||
|
// It returns an error if the operation fails.
|
||||||
|
AddTagWithRelationToPost(ctx context.Context, anthrovePostID models.AnthrovePostID, anthroveTag *models.AnthroveTag) error
|
||||||
|
|
||||||
|
// LinkPostWithSource establishes a link between a post and a source in the OtterSpace graph.
|
||||||
|
// It returns an error if the operation fails.
|
||||||
|
LinkPostWithSource(ctx context.Context, anthrovePostID models.AnthrovePostID, anthroveSourceDomain string, anthrovePostRelationship *models.AnthrovePostRelationship) error
|
||||||
|
|
||||||
|
// LinkUserWithPost establishes a link between a user and a post in the OtterSpace graph.
|
||||||
|
// It returns an error if the operation fails.
|
||||||
|
LinkUserWithPost(ctx context.Context, anthroveUser *models.AnthroveUser, anthrovePost *models.AnthrovePost) error
|
||||||
|
|
||||||
|
// CheckUserPostLink checks if a link between a user and a post exists in the OtterSpace graph.
|
||||||
|
// It returns true if the link exists, false otherwise, and an error if the operation fails.
|
||||||
|
CheckUserPostLink(ctx context.Context, anthroveUserID models.AnthroveUserID, sourcePostID string, sourceUrl string) (bool, error)
|
||||||
|
|
||||||
|
// CheckPostNodeExistsByAnthroveID checks if a post node exists in the OtterSpace graph by its Anthrove ID.
|
||||||
|
// It returns the post if it exists, a boolean indicating whether the post was found, and an error if the operation fails.
|
||||||
|
CheckPostNodeExistsByAnthroveID(ctx context.Context, anthrovePost *models.AnthrovePost) (*models.AnthrovePost, bool, error)
|
||||||
|
|
||||||
|
// CheckPostNodeExistsBySourceURL checks if a post node exists in the OtterSpace graph by its source URL.
|
||||||
|
// It returns the post if it exists, a boolean indicating whether the post was found, and an error if the operation fails.
|
||||||
|
CheckPostNodeExistsBySourceURL(ctx context.Context, sourceUrl string) (*models.AnthrovePost, bool, error)
|
||||||
|
|
||||||
|
// CheckPostNodeExistsBySourceID checks if a post node exists in the OtterSpace graph by its source ID.
|
||||||
|
// It returns the post if it exists, a boolean indicating whether the post was found, and an error if the operation fails.
|
||||||
|
CheckPostNodeExistsBySourceID(ctx context.Context, sourcePostID string) (*models.AnthrovePost, bool, error)
|
||||||
|
|
||||||
|
// GetUserFavoriteCount retrieves the count of a user's favorite posts from the OtterSpace graph.
|
||||||
|
// It returns the count and an error if the operation fails.
|
||||||
|
GetUserFavoriteCount(ctx context.Context, anthroveUserID models.AnthroveUserID) (int64, error)
|
||||||
|
|
||||||
|
// GetUserSourceLinks retrieves the links between a user and sources in the OtterSpace graph.
|
||||||
|
// It returns a map of source domains to user-source relationships, and an error if the operation fails.
|
||||||
|
GetUserSourceLinks(ctx context.Context, anthroveUserID models.AnthroveUserID) (map[string]models.AnthroveUserRelationship, error)
|
||||||
|
|
||||||
|
// GetUserSourceLinks retrieves the links between a user and sources in the OtterSpace graph.
|
||||||
|
// It returns a map of source domains to user-source relationships, and an error if the operation fails.
|
||||||
|
GetSpecifiedUserSourceLink(ctx context.Context, anthroveUserID models.AnthroveUserID, sourceDisplayName string) (map[string]models.AnthroveUserRelationship, error)
|
||||||
|
|
||||||
|
// GetAnthroveUser retrieves a user from the OtterSpace graph by their ID.
|
||||||
|
// It returns the user and an error if the operation fails.
|
||||||
|
GetAnthroveUser(ctx context.Context, anthroveUserID models.AnthroveUserID) (*models.AnthroveUser, error)
|
||||||
|
|
||||||
|
// GetAllAnthroveUserIDs retrieves all user IDs from the OtterSpace graph.
|
||||||
|
// It returns a slice of user IDs and an error if the operation fails.
|
||||||
|
GetAllAnthroveUserIDs(ctx context.Context) ([]models.AnthroveUserID, error)
|
||||||
|
}
|
103
pkg/graph/impl.go
Normal file
103
pkg/graph/impl.go
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
package graph
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"git.dragse.it/anthrove/otter-space-sdk/internal"
|
||||||
|
"git.dragse.it/anthrove/otter-space-sdk/pkg/models"
|
||||||
|
"github.com/neo4j/neo4j-go-driver/v5/neo4j"
|
||||||
|
"github.com/neo4j/neo4j-go-driver/v5/neo4j/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
type graphConnection struct {
|
||||||
|
driver neo4j.DriverWithContext
|
||||||
|
graphDebug bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGraphConnection(graphDebug bool) OtterSpace {
|
||||||
|
return &graphConnection{
|
||||||
|
driver: nil,
|
||||||
|
graphDebug: graphDebug,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *graphConnection) Connect(ctx context.Context, endpoint string, username string, password string) error {
|
||||||
|
driver, err := neo4j.NewDriverWithContext(endpoint, neo4j.BasicAuth(username, password, ""),
|
||||||
|
logger(g.graphDebug))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = driver.VerifyAuthentication(ctx, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
g.driver = driver
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *graphConnection) AddUserWithRelationToSource(ctx context.Context, anthroveUserID models.AnthroveUserID, sourceDomain string, userID string, username string) error {
|
||||||
|
return internal.CreateUserNodeWithSourceRelation(ctx, g.driver, anthroveUserID, sourceDomain, userID, username)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *graphConnection) AddSource(ctx context.Context, anthroveSource *models.AnthroveSource) error {
|
||||||
|
return internal.CreateSourceNode(ctx, g.driver, anthroveSource)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *graphConnection) AddPost(ctx context.Context, anthrovePost *models.AnthrovePost) error {
|
||||||
|
return internal.CreateAnthrovePostNode(ctx, g.driver, anthrovePost)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *graphConnection) AddTagWithRelationToPost(ctx context.Context, anthrovePostID models.AnthrovePostID, anthroveTag *models.AnthroveTag) error {
|
||||||
|
return internal.CreateTagNodeWitRelation(ctx, g.driver, anthrovePostID, anthroveTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *graphConnection) LinkPostWithSource(ctx context.Context, anthrovePostID models.AnthrovePostID, anthroveSourceDomain string, anthrovePostRelationship *models.AnthrovePostRelationship) error {
|
||||||
|
return internal.EstablishAnthrovePostToSourceLink(ctx, g.driver, anthrovePostID, anthroveSourceDomain, anthrovePostRelationship)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *graphConnection) LinkUserWithPost(ctx context.Context, anthroveUser *models.AnthroveUser, anthrovePost *models.AnthrovePost) error {
|
||||||
|
return internal.EstablishUserToPostLink(ctx, g.driver, anthroveUser, anthrovePost)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *graphConnection) CheckUserPostLink(ctx context.Context, anthroveUserID models.AnthroveUserID, sourcePostID string, sourceUrl string) (bool, error) {
|
||||||
|
return internal.CheckUserToPostLink(ctx, g.driver, anthroveUserID, sourcePostID, sourceUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *graphConnection) CheckPostNodeExistsByAnthroveID(ctx context.Context, anthrovePost *models.AnthrovePost) (*models.AnthrovePost, bool, error) {
|
||||||
|
return internal.CheckIfAnthrovePostNodeExistsByAnthroveID(ctx, g.driver, anthrovePost)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *graphConnection) CheckPostNodeExistsBySourceURL(ctx context.Context, sourceUrl string) (*models.AnthrovePost, bool, error) {
|
||||||
|
return internal.CheckIfAnthrovePostNodeExistsBySourceURl(ctx, g.driver, sourceUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *graphConnection) CheckPostNodeExistsBySourceID(ctx context.Context, sourcePostID string) (*models.AnthrovePost, bool, error) {
|
||||||
|
return internal.CheckIfAnthrovePostNodeExistsBySourceID(ctx, g.driver, sourcePostID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *graphConnection) GetUserFavoriteCount(ctx context.Context, anthroveUserID models.AnthroveUserID) (int64, error) {
|
||||||
|
return internal.GetUserFavoritesCount(ctx, g.driver, anthroveUserID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *graphConnection) GetUserSourceLinks(ctx context.Context, anthroveUserID models.AnthroveUserID) (map[string]models.AnthroveUserRelationship, error) {
|
||||||
|
return internal.GetUserSourceLink(ctx, g.driver, anthroveUserID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *graphConnection) GetSpecifiedUserSourceLink(ctx context.Context, anthroveUserID models.AnthroveUserID, sourceDisplayName string) (map[string]models.AnthroveUserRelationship, error) {
|
||||||
|
return internal.GetSpecifiedUserSourceLink(ctx, g.driver, anthroveUserID, sourceDisplayName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *graphConnection) GetAnthroveUser(ctx context.Context, anthroveUserID models.AnthroveUserID) (*models.AnthroveUser, error) {
|
||||||
|
return internal.GetAnthroveUser(ctx, g.driver, anthroveUserID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *graphConnection) GetAllAnthroveUserIDs(ctx context.Context) ([]models.AnthroveUserID, error) {
|
||||||
|
return internal.GetAllAnthroveUserIDs(ctx, g.driver)
|
||||||
|
}
|
||||||
|
|
||||||
|
func logger(graphDebug bool) func(config *config.Config) {
|
||||||
|
return func(config *config.Config) {
|
||||||
|
config.Log = internal.NewGraphLogger(graphDebug)
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
type FavoriteList struct {
|
|
||||||
Posts []Post `json:"posts,omitempty"`
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
type DatabaseConfig struct {
|
|
||||||
Endpoint string `env:"DB_ENDPOINT,required"`
|
|
||||||
Username string `env:"DB_USERNAME,required"`
|
|
||||||
Password string `env:"DB_PASSWORD,required,unset"`
|
|
||||||
Database string `env:"DB_DATABASE,required"`
|
|
||||||
Port int `env:"DB_PORT,required" envDefault:"5432"`
|
|
||||||
SSL bool `env:"DB_SSL,required" envDefault:"true"`
|
|
||||||
Timezone string `env:"DB_TIMEZONE,required" envDefault:"Europe/Berlin"`
|
|
||||||
Debug bool `env:"DB_DEBUG" envDefault:"false"`
|
|
||||||
}
|
|
@ -1,41 +1,18 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type AnthroveUserID string
|
type AnthroveUserID string
|
||||||
type AnthrovePostID string
|
type AnthrovePostID string
|
||||||
type AnthroveSourceID string
|
type AnthroveRating string
|
||||||
type AnthroveSourceDomain string
|
|
||||||
type AnthrovePostURL string
|
|
||||||
type AnthroveTagGroupName string
|
|
||||||
type AnthroveTagAliasName string
|
|
||||||
type AnthroveTagID string
|
|
||||||
type AnthroveScrapeTimeInterval int
|
|
||||||
type AnthroveUserLastScrapeTime time.Time
|
|
||||||
type AnthroveTagName string
|
|
||||||
|
|
||||||
type Rating string
|
|
||||||
type TagType string
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
SFW Rating = "safe"
|
SFW AnthroveRating = "s"
|
||||||
NSFW Rating = "explicit"
|
NSFW AnthroveRating = "e"
|
||||||
Questionable Rating = "questionable"
|
Questionable AnthroveRating = "q"
|
||||||
Unknown Rating = "unknown"
|
Unknown AnthroveRating = "unknown"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
func (r *AnthroveRating) Convert(e621Rating string) {
|
||||||
General TagType = "general"
|
|
||||||
Species TagType = "species"
|
|
||||||
Character TagType = "character"
|
|
||||||
Artist TagType = "artist"
|
|
||||||
Lore TagType = "lore"
|
|
||||||
Meta TagType = "meta"
|
|
||||||
Invalid TagType = "invalid"
|
|
||||||
Copyright TagType = "copyright"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (r *Rating) Convert(e621Rating string) {
|
|
||||||
switch e621Rating {
|
switch e621Rating {
|
||||||
case "e":
|
case "e":
|
||||||
*r = NSFW
|
*r = NSFW
|
||||||
@ -46,4 +23,5 @@ func (r *Rating) Convert(e621Rating string) {
|
|||||||
default:
|
default:
|
||||||
*r = Unknown
|
*r = Unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,59 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestRating_Convert(t *testing.T) {
|
|
||||||
type args struct {
|
|
||||||
e621Rating string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
r *Rating
|
|
||||||
args args
|
|
||||||
want Rating
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Test 1: NSFW Rating",
|
|
||||||
r: new(Rating),
|
|
||||||
args: args{
|
|
||||||
e621Rating: "e",
|
|
||||||
},
|
|
||||||
want: NSFW,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test 2: Questionable Rating",
|
|
||||||
r: new(Rating),
|
|
||||||
args: args{
|
|
||||||
e621Rating: "q",
|
|
||||||
},
|
|
||||||
want: Questionable,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test 3: SFW Rating",
|
|
||||||
r: new(Rating),
|
|
||||||
args: args{
|
|
||||||
e621Rating: "s",
|
|
||||||
},
|
|
||||||
want: SFW,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test 4: Unknown Rating",
|
|
||||||
r: new(Rating),
|
|
||||||
args: args{
|
|
||||||
e621Rating: "x",
|
|
||||||
},
|
|
||||||
want: Unknown,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
tt.r.Convert(tt.args.e621Rating)
|
|
||||||
if !reflect.DeepEqual(*tt.r, tt.want) {
|
|
||||||
t.Errorf("Convert() = %v, want %v", *tt.r, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
gonanoid "github.com/matoous/go-nanoid/v2"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ID interface {
|
|
||||||
AnthroveUserID | AnthroveSourceID | AnthrovePostID
|
|
||||||
}
|
|
||||||
|
|
||||||
type BaseModel[T ID] struct {
|
|
||||||
ID T `json:"id" gorm:"primaryKey"`
|
|
||||||
CreatedAt time.Time `json:"-"`
|
|
||||||
UpdatedAt time.Time `json:"-"`
|
|
||||||
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (base *BaseModel[T]) BeforeCreate(db *gorm.DB) error {
|
|
||||||
var defaultVar T
|
|
||||||
|
|
||||||
if base.ID == defaultVar {
|
|
||||||
id, err := gonanoid.New(25)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
base.ID = T(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,56 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBaseModel_BeforeCreate(t *testing.T) {
|
|
||||||
type fields struct {
|
|
||||||
ID string
|
|
||||||
CreatedAt time.Time
|
|
||||||
UpdatedAt time.Time
|
|
||||||
DeletedAt gorm.DeletedAt
|
|
||||||
}
|
|
||||||
type args struct {
|
|
||||||
db *gorm.DB
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
fields fields
|
|
||||||
args args
|
|
||||||
wantErr bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Test 1: Prefilled ID",
|
|
||||||
fields: fields{
|
|
||||||
ID: "1",
|
|
||||||
},
|
|
||||||
args: args{db: nil},
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Test 1: Autogenerate ID",
|
|
||||||
fields: fields{
|
|
||||||
ID: "",
|
|
||||||
},
|
|
||||||
args: args{db: nil},
|
|
||||||
wantErr: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
base := &BaseModel[AnthrovePostID]{
|
|
||||||
ID: AnthrovePostID(tt.fields.ID),
|
|
||||||
CreatedAt: tt.fields.CreatedAt,
|
|
||||||
UpdatedAt: tt.fields.UpdatedAt,
|
|
||||||
DeletedAt: tt.fields.DeletedAt,
|
|
||||||
}
|
|
||||||
if err := base.BeforeCreate(tt.args.db); (err != nil) != tt.wantErr {
|
|
||||||
t.Errorf("BeforeCreate() error = %v, wantErr %v", err, tt.wantErr)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +1,6 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
// Post model
|
type AnthrovePost struct {
|
||||||
type Post struct {
|
PostID AnthrovePostID `json:"post_id"`
|
||||||
BaseModel[AnthrovePostID]
|
Rating AnthroveRating `json:"rating"`
|
||||||
Rating Rating `json:"rating" gorm:"type:enum('safe','questionable','explicit')"`
|
|
||||||
Tags []Tag `json:"-" gorm:"many2many:post_tags;"`
|
|
||||||
Favorites []UserFavorites `json:"-" gorm:"foreignKey:PostID"`
|
|
||||||
References []PostReference `json:"references" gorm:"foreignKey:PostID"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (Post) TableName() string {
|
|
||||||
return "Post"
|
|
||||||
}
|
}
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
type PostReference struct {
|
|
||||||
PostID string `json:"post_id" gorm:"primaryKey"`
|
|
||||||
SourceID string `json:"source_id" gorm:"primaryKey"`
|
|
||||||
URL string `json:"url" gorm:"primaryKey"`
|
|
||||||
PostReferenceConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
type PostReferenceConfig struct {
|
|
||||||
SourcePostID string `json:"source_post_id"`
|
|
||||||
FullFileURL string `json:"full_file_url"`
|
|
||||||
PreviewFileURL string `json:"preview_file_url"`
|
|
||||||
SampleFileURL string `json:"sample_file_url"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (PostReference) TableName() string {
|
|
||||||
return "PostReference"
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func TestPostReference_TableName(t *testing.T) {
|
|
||||||
type fields struct {
|
|
||||||
PostID string
|
|
||||||
SourceID string
|
|
||||||
URL string
|
|
||||||
SourcePostID string
|
|
||||||
FullFileURL string
|
|
||||||
PreviewFileURL string
|
|
||||||
SampleFileURL string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
fields fields
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Test 1: PostReference",
|
|
||||||
fields: fields{},
|
|
||||||
want: "PostReference",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
po := PostReference{
|
|
||||||
PostID: tt.fields.PostID,
|
|
||||||
SourceID: tt.fields.SourceID,
|
|
||||||
URL: tt.fields.URL,
|
|
||||||
PostReferenceConfig: PostReferenceConfig{
|
|
||||||
SourcePostID: tt.fields.SourcePostID,
|
|
||||||
FullFileURL: tt.fields.FullFileURL,
|
|
||||||
PreviewFileURL: tt.fields.PreviewFileURL,
|
|
||||||
SampleFileURL: tt.fields.SampleFileURL,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if got := po.TableName(); got != tt.want {
|
|
||||||
t.Errorf("TableName() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func TestPost_TableName(t *testing.T) {
|
|
||||||
type fields struct {
|
|
||||||
BaseModel BaseModel[AnthrovePostID]
|
|
||||||
Rating Rating
|
|
||||||
Tags []Tag
|
|
||||||
Favorites []UserFavorites
|
|
||||||
References []PostReference
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
fields fields
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Test 1: Is name Post",
|
|
||||||
fields: fields{},
|
|
||||||
want: "Post",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
po := Post{
|
|
||||||
BaseModel: tt.fields.BaseModel,
|
|
||||||
Rating: tt.fields.Rating,
|
|
||||||
Tags: tt.fields.Tags,
|
|
||||||
Favorites: tt.fields.Favorites,
|
|
||||||
References: tt.fields.References,
|
|
||||||
}
|
|
||||||
if got := po.TableName(); got != tt.want {
|
|
||||||
t.Errorf("TableName() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
12
pkg/models/relationships.go
Normal file
12
pkg/models/relationships.go
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
type AnthroveUserRelationship struct {
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
ScrapeTimeInterval string `json:"scrape_time_interval"`
|
||||||
|
Source AnthroveSource `json:"source"`
|
||||||
|
}
|
||||||
|
type AnthrovePostRelationship struct {
|
||||||
|
PostID string `json:"post_id"`
|
||||||
|
Url string `json:"url"`
|
||||||
|
}
|
@ -1,15 +1,7 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
// Source model
|
type AnthroveSource struct {
|
||||||
type Source struct {
|
DisplayName string `json:"display_name"`
|
||||||
BaseModel[AnthroveSourceID]
|
Domain string `json:"domain"`
|
||||||
DisplayName string `json:"display_name" `
|
Icon string `json:"icon"`
|
||||||
Domain string `json:"domain" gorm:"not null;unique"`
|
|
||||||
Icon string `json:"icon" gorm:"not null"`
|
|
||||||
UserSources []UserSource `json:"-" gorm:"foreignKey:SourceID"`
|
|
||||||
References []PostReference `json:"references" gorm:"foreignKey:SourceID"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (Source) TableName() string {
|
|
||||||
return "Source"
|
|
||||||
}
|
}
|
||||||
|
@ -1,40 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func TestSource_TableName(t *testing.T) {
|
|
||||||
type fields struct {
|
|
||||||
BaseModel BaseModel[AnthroveSourceID]
|
|
||||||
DisplayName string
|
|
||||||
Domain string
|
|
||||||
Icon string
|
|
||||||
UserSources []UserSource
|
|
||||||
References []PostReference
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
fields fields
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Test 1: Is name Source",
|
|
||||||
fields: fields{},
|
|
||||||
want: "Source",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
so := Source{
|
|
||||||
BaseModel: tt.fields.BaseModel,
|
|
||||||
DisplayName: tt.fields.DisplayName,
|
|
||||||
Domain: tt.fields.Domain,
|
|
||||||
Icon: tt.fields.Icon,
|
|
||||||
UserSources: tt.fields.UserSources,
|
|
||||||
References: tt.fields.References,
|
|
||||||
}
|
|
||||||
if got := so.TableName(); got != tt.want {
|
|
||||||
t.Errorf("TableName() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,39 +1,6 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
// Tag models
|
type AnthroveTag struct {
|
||||||
type Tag struct {
|
Name string `json:"name"`
|
||||||
Name string `json:"name" gorm:"primaryKey"`
|
Type string `json:"type"`
|
||||||
Type TagType `json:"type" gorm:"column:tag_type"`
|
|
||||||
Aliases []TagAlias `json:"aliases" gorm:"foreignKey:TagID"`
|
|
||||||
Groups []TagGroup `json:"groups" gorm:"foreignKey:TagID"`
|
|
||||||
Posts []Post `json:"posts" gorm:"many2many:post_tags;"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (Tag) TableName() string {
|
|
||||||
return "Tag"
|
|
||||||
}
|
|
||||||
|
|
||||||
// TagAlias model
|
|
||||||
type TagAlias struct {
|
|
||||||
Name string `json:"name" gorm:"primaryKey"`
|
|
||||||
TagID string `json:"tag_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (TagAlias) TableName() string {
|
|
||||||
return "TagAlias"
|
|
||||||
}
|
|
||||||
|
|
||||||
// TagGroup model
|
|
||||||
type TagGroup struct {
|
|
||||||
Name string `json:"name" gorm:"primaryKey"`
|
|
||||||
TagID string `json:"tag_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (TagGroup) TableName() string {
|
|
||||||
return "TagGroup"
|
|
||||||
}
|
|
||||||
|
|
||||||
type TagsWithFrequency struct {
|
|
||||||
Frequency int64 `json:"frequency"`
|
|
||||||
Tags Tag `json:"tags"`
|
|
||||||
}
|
}
|
||||||
|
@ -1,96 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func TestTagAlias_TableName(t *testing.T) {
|
|
||||||
type fields struct {
|
|
||||||
Name string
|
|
||||||
TagID string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
fields fields
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Test 1: Is Name TagAlias",
|
|
||||||
fields: fields{},
|
|
||||||
want: "TagAlias",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
ta := TagAlias{
|
|
||||||
Name: tt.fields.Name,
|
|
||||||
TagID: tt.fields.TagID,
|
|
||||||
}
|
|
||||||
if got := ta.TableName(); got != tt.want {
|
|
||||||
t.Errorf("TableName() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTagGroup_TableName(t *testing.T) {
|
|
||||||
type fields struct {
|
|
||||||
Name string
|
|
||||||
TagID string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
fields fields
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Test 1: Is name TagGroup",
|
|
||||||
fields: fields{},
|
|
||||||
want: "TagGroup",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
ta := TagGroup{
|
|
||||||
Name: tt.fields.Name,
|
|
||||||
TagID: tt.fields.TagID,
|
|
||||||
}
|
|
||||||
if got := ta.TableName(); got != tt.want {
|
|
||||||
t.Errorf("TableName() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTag_TableName(t *testing.T) {
|
|
||||||
type fields struct {
|
|
||||||
Name string
|
|
||||||
Type TagType
|
|
||||||
Aliases []TagAlias
|
|
||||||
Groups []TagGroup
|
|
||||||
Posts []Post
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
fields fields
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Test 1: Is name Tag",
|
|
||||||
fields: fields{},
|
|
||||||
want: "Tag",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
ta := Tag{
|
|
||||||
Name: tt.fields.Name,
|
|
||||||
Type: tt.fields.Type,
|
|
||||||
Aliases: tt.fields.Aliases,
|
|
||||||
Groups: tt.fields.Groups,
|
|
||||||
Posts: tt.fields.Posts,
|
|
||||||
}
|
|
||||||
if got := ta.TableName(); got != tt.want {
|
|
||||||
t.Errorf("TableName() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +1,6 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
// User model
|
type AnthroveUser struct {
|
||||||
type User struct {
|
UserID AnthroveUserID `json:"user_id"`
|
||||||
BaseModel[AnthroveUserID]
|
Relationship []AnthroveUserRelationship `json:"relationship"`
|
||||||
Favorites []UserFavorites `json:"-" gorm:"foreignKey:UserID"`
|
|
||||||
Sources []UserSource `json:"-" gorm:"foreignKey:UserID"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (User) TableName() string {
|
|
||||||
return "User"
|
|
||||||
}
|
}
|
||||||
|
@ -1,13 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type UserFavorites struct {
|
|
||||||
UserID string `json:"user_id" gorm:"primaryKey"`
|
|
||||||
PostID string `json:"post_id" gorm:"primaryKey"`
|
|
||||||
CreatedAt time.Time `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (UserFavorites) TableName() string {
|
|
||||||
return "UserFavorites"
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestUserFavorite_TableName(t *testing.T) {
|
|
||||||
type fields struct {
|
|
||||||
UserID string
|
|
||||||
PostID string
|
|
||||||
CreatedAt time.Time
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
fields fields
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Test 1: Is name UserFavorites",
|
|
||||||
fields: fields{},
|
|
||||||
want: "UserFavorites",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
us := UserFavorites{
|
|
||||||
UserID: tt.fields.UserID,
|
|
||||||
PostID: tt.fields.PostID,
|
|
||||||
CreatedAt: tt.fields.CreatedAt,
|
|
||||||
}
|
|
||||||
if got := us.TableName(); got != tt.want {
|
|
||||||
t.Errorf("TableName() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
type UserSource struct {
|
|
||||||
User User `json:"user" gorm:"foreignKey:ID;references:UserID"`
|
|
||||||
UserID string `json:"user_id" gorm:"primaryKey"`
|
|
||||||
Source Source `json:"source" gorm:"foreignKey:ID;references:SourceID"`
|
|
||||||
SourceID string `json:"source_id" gorm:"primaryKey"`
|
|
||||||
ScrapeTimeInterval string `json:"scrape_time_interval"`
|
|
||||||
AccountUsername string `json:"account_username"`
|
|
||||||
AccountID string `json:"account_id"`
|
|
||||||
LastScrapeTime time.Time `json:"last_scrape_time"`
|
|
||||||
AccountValidate bool `json:"account_validate"`
|
|
||||||
AccountValidationKey string `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (UserSource) TableName() string {
|
|
||||||
return "UserSource"
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func TestUserSource_TableName(t *testing.T) {
|
|
||||||
type fields struct {
|
|
||||||
User User
|
|
||||||
UserID string
|
|
||||||
Source Source
|
|
||||||
SourceID string
|
|
||||||
ScrapeTimeInterval string
|
|
||||||
AccountUsername string
|
|
||||||
AccountID string
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
fields fields
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Test 1: Is name UserSource",
|
|
||||||
fields: fields{},
|
|
||||||
want: "UserSource",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
us := UserSource{
|
|
||||||
User: tt.fields.User,
|
|
||||||
UserID: tt.fields.UserID,
|
|
||||||
Source: tt.fields.Source,
|
|
||||||
SourceID: tt.fields.SourceID,
|
|
||||||
ScrapeTimeInterval: tt.fields.ScrapeTimeInterval,
|
|
||||||
AccountUsername: tt.fields.AccountUsername,
|
|
||||||
AccountID: tt.fields.AccountID,
|
|
||||||
}
|
|
||||||
if got := us.TableName(); got != tt.want {
|
|
||||||
t.Errorf("TableName() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
package models
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func TestUser_TableName(t *testing.T) {
|
|
||||||
type fields struct {
|
|
||||||
BaseModel BaseModel[AnthroveUserID]
|
|
||||||
Favorites []UserFavorites
|
|
||||||
Sources []UserSource
|
|
||||||
}
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
fields fields
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "Test 1: Is name User",
|
|
||||||
fields: fields{},
|
|
||||||
want: "User",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
us := User{
|
|
||||||
BaseModel: tt.fields.BaseModel,
|
|
||||||
Favorites: tt.fields.Favorites,
|
|
||||||
Sources: tt.fields.Sources,
|
|
||||||
}
|
|
||||||
if got := us.TableName(); got != tt.want {
|
|
||||||
t.Errorf("TableName() = %v, want %v", got, tt.want)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
123
test/helper.go
123
test/helper.go
@ -1,123 +0,0 @@
|
|||||||
package test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"git.dragse.it/anthrove/otter-space-sdk/v2/pkg/models"
|
|
||||||
migrate "github.com/rubenv/sql-migrate"
|
|
||||||
postgrescontainer "github.com/testcontainers/testcontainers-go/modules/postgres"
|
|
||||||
"gorm.io/driver/postgres"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"gorm.io/gorm/logger"
|
|
||||||
|
|
||||||
"github.com/testcontainers/testcontainers-go"
|
|
||||||
"github.com/testcontainers/testcontainers-go/wait"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
databaseName = "anthrove"
|
|
||||||
databaseUser = "anthrove"
|
|
||||||
databasePassword = "anthrove"
|
|
||||||
migrationSource = "../../pkg/database/migrations/"
|
|
||||||
)
|
|
||||||
|
|
||||||
func StartPostgresContainer(ctx context.Context) (*postgrescontainer.PostgresContainer, *gorm.DB, error) {
|
|
||||||
|
|
||||||
pgContainer, err := postgrescontainer.RunContainer(ctx,
|
|
||||||
testcontainers.WithImage("postgres:alpine"),
|
|
||||||
postgrescontainer.WithDatabase(databaseName),
|
|
||||||
postgrescontainer.WithUsername(databaseUser),
|
|
||||||
postgrescontainer.WithPassword(databasePassword),
|
|
||||||
testcontainers.WithWaitStrategy(
|
|
||||||
wait.ForLog("database system is ready to accept connections").
|
|
||||||
WithOccurrence(2).WithStartupTimeout(60*time.Second)),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
connectionString, err := pgContainer.ConnectionString(ctx, "sslmode=disable")
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = migrateDatabase(connectionString)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
gormDB, err := getGormDB(connectionString)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
return pgContainer, gormDB, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func migrateDatabase(connectionString string) error {
|
|
||||||
db, err := sql.Open("postgres", connectionString)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
migrations := &migrate.FileMigrationSource{
|
|
||||||
Dir: migrationSource,
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = migrate.Exec(db, "postgres", migrations, migrate.Up)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func getGormDB(connectionString string) (*gorm.DB, error) {
|
|
||||||
return gorm.Open(postgres.Open(connectionString), &gorm.Config{
|
|
||||||
Logger: logger.Default.LogMode(logger.Info),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func DatabaseModesFromConnectionString(ctx context.Context, pgContainer *postgrescontainer.PostgresContainer) (*models.DatabaseConfig, error) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
connectionString, err := pgContainer.ConnectionString(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
connectionStringUrl, err := url.Parse(connectionString)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
split := strings.Split(connectionStringUrl.Host, ":")
|
|
||||||
host := split[0]
|
|
||||||
|
|
||||||
port, err := strconv.Atoi(split[1])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
database := strings.TrimPrefix(connectionStringUrl.Path, "/")
|
|
||||||
|
|
||||||
username := connectionStringUrl.User.Username()
|
|
||||||
password, _ := connectionStringUrl.User.Password()
|
|
||||||
|
|
||||||
return &models.DatabaseConfig{
|
|
||||||
Endpoint: host,
|
|
||||||
Username: username,
|
|
||||||
Password: password,
|
|
||||||
Database: database,
|
|
||||||
Port: port,
|
|
||||||
SSL: false,
|
|
||||||
Timezone: "Europe/Berlin",
|
|
||||||
Debug: true,
|
|
||||||
}, nil
|
|
||||||
}
|
|
Reference in New Issue
Block a user