First commit
This commit is contained in:
commit
1161a56ec9
25 changed files with 5288 additions and 0 deletions
5
.dockerignore
Normal file
5
.dockerignore
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
Dockerfile
|
||||||
|
gqlgen.yml
|
||||||
|
*.graphqls
|
||||||
|
deployments/
|
||||||
|
Makefile
|
0
.gitignore
vendored
Normal file
0
.gitignore
vendored
Normal file
12
Dockerfile
Normal file
12
Dockerfile
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
FROM golang:1.22.4
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN CGO_ENABLED=0 GOOS=linux go build -o /ozon-task cmd/server.go
|
||||||
|
|
||||||
|
CMD ["/ozon-task"]
|
16
Makefile
Normal file
16
Makefile
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
docker-postgres: docker
|
||||||
|
docker compose -f deployments/docker-compose/postgres/docker-compose.yml up
|
||||||
|
|
||||||
|
docker-standalone: docker
|
||||||
|
docker compose -f deployments/docker-compose/standalone/docker-compose.yml up
|
||||||
|
|
||||||
|
docker: go-generate Dockerfile
|
||||||
|
docker build --tag ozon-task .
|
||||||
|
|
||||||
|
go-generate:
|
||||||
|
go generate ./...
|
||||||
|
|
||||||
|
clean: docker-clean
|
||||||
|
|
||||||
|
docker-clean:
|
||||||
|
docker container rm postgres-ozon-task-1 postgres-postgres-1 && docker volume rm postgres_postgres-data
|
36
cmd/server.go
Normal file
36
cmd/server.go
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.obamna.ru/erius/ozon-task/graph"
|
||||||
|
"git.obamna.ru/erius/ozon-task/internal/storage"
|
||||||
|
"github.com/99designs/gqlgen/graphql/handler"
|
||||||
|
"github.com/99designs/gqlgen/graphql/playground"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultPort = "8080"
|
||||||
|
|
||||||
|
var port = os.Getenv("APP_PORT")
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if port == "" {
|
||||||
|
port = defaultPort
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := storage.InitStorage()
|
||||||
|
if err != nil {
|
||||||
|
log.Panic("could not connect to database")
|
||||||
|
}
|
||||||
|
|
||||||
|
res := graph.InitResolver(s)
|
||||||
|
srv := handler.NewDefaultServer(graph.NewExecutableSchema(graph.Config{Resolvers: res}))
|
||||||
|
|
||||||
|
http.Handle("/", playground.Handler("GraphQL playground", "/query"))
|
||||||
|
http.Handle("/query", srv)
|
||||||
|
|
||||||
|
log.Printf("connect to http://localhost:%s/ for GraphQL playground", port)
|
||||||
|
log.Fatal(http.ListenAndServe(":"+port, nil))
|
||||||
|
}
|
23
deployments/docker-compose/postgres/docker-compose.yml
Normal file
23
deployments/docker-compose/postgres/docker-compose.yml
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
services:
|
||||||
|
ozon-task:
|
||||||
|
image: ozon-task:latest
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
||||||
|
env_file: ozon-task.env
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
postgres:
|
||||||
|
image: postgres:latest
|
||||||
|
hostname: postgres
|
||||||
|
env_file: postgres.env
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
volumes:
|
||||||
|
- postgres-data:/var/lib/postgresql/data
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres-data:
|
11
deployments/docker-compose/postgres/ozon-task.env
Normal file
11
deployments/docker-compose/postgres/ozon-task.env
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
# Define storage type for the app
|
||||||
|
# inmemory (default) - store data in memory (non-persistent)
|
||||||
|
# postgres - store data in postgres database
|
||||||
|
APP_STORAGE=postgres
|
||||||
|
|
||||||
|
# Postgres configuration for APP_STORAGE=postgres
|
||||||
|
APP_POSTGRES_HOST=postgres
|
||||||
|
APP_POSTGRES_PORT=5432
|
||||||
|
APP_POSTGRES_USER=username
|
||||||
|
APP_POSTGRES_PASSWORD=password
|
||||||
|
APP_POSTGRES_DB=database
|
3
deployments/docker-compose/postgres/postgres.env
Normal file
3
deployments/docker-compose/postgres/postgres.env
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
POSTGRES_USER=username
|
||||||
|
POSTGRES_PASSWORD=password
|
||||||
|
POSTGRES_DB=database
|
6
deployments/docker-compose/standalone/docker-compose.yml
Normal file
6
deployments/docker-compose/standalone/docker-compose.yml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
services:
|
||||||
|
ozon-task:
|
||||||
|
image: ozon-task:latest
|
||||||
|
ports:
|
||||||
|
- 8080:8080
|
||||||
|
env_file: ozon-task.env
|
4
deployments/docker-compose/standalone/ozon-task.env
Normal file
4
deployments/docker-compose/standalone/ozon-task.env
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# Define storage type for the app
|
||||||
|
# inmemory (default) - store data in memory (non-persistent)
|
||||||
|
# postgres - store data in postgres database
|
||||||
|
APP_STORAGE=inmemory
|
37
go.mod
Normal file
37
go.mod
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
module git.obamna.ru/erius/ozon-task
|
||||||
|
|
||||||
|
go 1.22.4
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/99designs/gqlgen v0.17.49
|
||||||
|
github.com/vektah/gqlparser/v2 v2.5.16
|
||||||
|
gorm.io/driver/postgres v1.5.9
|
||||||
|
gorm.io/gorm v1.25.10
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/agnivade/levenshtein v1.1.1 // indirect
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/gorilla/websocket v1.5.0 // indirect
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7 // 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/kr/text v0.2.0 // indirect
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
|
github.com/rogpeppe/go-internal v1.12.0 // indirect
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
|
github.com/sosodev/duration v1.3.1 // indirect
|
||||||
|
github.com/urfave/cli/v2 v2.27.2 // indirect
|
||||||
|
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect
|
||||||
|
golang.org/x/crypto v0.17.0 // indirect
|
||||||
|
golang.org/x/mod v0.18.0 // indirect
|
||||||
|
golang.org/x/sync v0.7.0 // indirect
|
||||||
|
golang.org/x/text v0.16.0 // indirect
|
||||||
|
golang.org/x/tools v0.22.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
89
go.sum
Normal file
89
go.sum
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
github.com/99designs/gqlgen v0.17.49 h1:b3hNGexHd33fBSAd4NDT/c3NCcQzcAVkknhN9ym36YQ=
|
||||||
|
github.com/99designs/gqlgen v0.17.49/go.mod h1:tC8YFVZMed81x7UJ7ORUwXF4Kn6SXuucFqQBhN8+BU0=
|
||||||
|
github.com/PuerkitoBio/goquery v1.9.2 h1:4/wZksC3KgkQw7SQgkKotmKljk0M6V8TUvA8Wb4yPeE=
|
||||||
|
github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk=
|
||||||
|
github.com/agnivade/levenshtein v1.1.1 h1:QY8M92nrzkmr798gCo3kmMyqXFzdQVpxLlGPRBij0P8=
|
||||||
|
github.com/agnivade/levenshtein v1.1.1/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
|
||||||
|
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
|
||||||
|
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
|
||||||
|
github.com/andybalholm/cascadia v1.3.2 h1:3Xi6Dw5lHF15JtdcmAHD3i1+T8plmv7BQ/nsViSLyss=
|
||||||
|
github.com/andybalholm/cascadia v1.3.2/go.mod h1:7gtRlve5FxPPgIgX36uWBX58OdBsSS6lUvCFb+h7KvU=
|
||||||
|
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
|
||||||
|
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
|
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g=
|
||||||
|
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
|
||||||
|
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/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||||
|
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||||
|
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||||
|
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/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
|
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/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||||
|
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8=
|
||||||
|
github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I=
|
||||||
|
github.com/sosodev/duration v1.3.1 h1:qtHBDMQ6lvMQsL15g4aopM4HEfOaYuhWBw3NPTtlqq4=
|
||||||
|
github.com/sosodev/duration v1.3.1/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERAikUR6SDg=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
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.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI=
|
||||||
|
github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM=
|
||||||
|
github.com/vektah/gqlparser/v2 v2.5.16 h1:1gcmLTvs3JLKXckwCwlUagVn/IlV2bwqle0vJ0vy5p8=
|
||||||
|
github.com/vektah/gqlparser/v2 v2.5.16/go.mod h1:1lz1OeCqgQbQepsGxPVywrjdBHW2T08PUS3pJqepRww=
|
||||||
|
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw=
|
||||||
|
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk=
|
||||||
|
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||||
|
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||||
|
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
||||||
|
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
|
golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
|
||||||
|
golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
||||||
|
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||||
|
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||||
|
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||||
|
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
||||||
|
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
||||||
|
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/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
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=
|
88
gqlgen.yml
Normal file
88
gqlgen.yml
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
# Where are all the schema files located? globs are supported eg src/**/*.graphqls
|
||||||
|
schema:
|
||||||
|
- graph/*.graphqls
|
||||||
|
|
||||||
|
# Where should the generated server code go?
|
||||||
|
exec:
|
||||||
|
filename: graph/generated.go
|
||||||
|
package: graph
|
||||||
|
|
||||||
|
# Uncomment to enable federation
|
||||||
|
# federation:
|
||||||
|
# filename: graph/federation.go
|
||||||
|
# package: graph
|
||||||
|
|
||||||
|
# Where should any generated models go?
|
||||||
|
model:
|
||||||
|
filename: graph/model/models_gen.go
|
||||||
|
package: model
|
||||||
|
|
||||||
|
# Where should the resolver implementations go?
|
||||||
|
resolver:
|
||||||
|
layout: follow-schema
|
||||||
|
dir: graph
|
||||||
|
package: graph
|
||||||
|
filename_template: "{name}.resolvers.go"
|
||||||
|
# Optional: turn on to not generate template comments above resolvers
|
||||||
|
# omit_template_comment: false
|
||||||
|
|
||||||
|
# Optional: turn on use ` + "`" + `gqlgen:"fieldName"` + "`" + ` tags in your models
|
||||||
|
# struct_tag: json
|
||||||
|
|
||||||
|
# Optional: turn on to use []Thing instead of []*Thing
|
||||||
|
# omit_slice_element_pointers: false
|
||||||
|
|
||||||
|
# Optional: turn on to omit Is<Name>() methods to interface and unions
|
||||||
|
# omit_interface_checks : true
|
||||||
|
|
||||||
|
# Optional: turn on to skip generation of ComplexityRoot struct content and Complexity function
|
||||||
|
# omit_complexity: false
|
||||||
|
|
||||||
|
# Optional: turn on to not generate any file notice comments in generated files
|
||||||
|
# omit_gqlgen_file_notice: false
|
||||||
|
|
||||||
|
# Optional: turn on to exclude the gqlgen version in the generated file notice. No effect if `omit_gqlgen_file_notice` is true.
|
||||||
|
# omit_gqlgen_version_in_file_notice: false
|
||||||
|
|
||||||
|
# Optional: turn off to make struct-type struct fields not use pointers
|
||||||
|
# e.g. type Thing struct { FieldA OtherThing } instead of { FieldA *OtherThing }
|
||||||
|
# struct_fields_always_pointers: true
|
||||||
|
|
||||||
|
# Optional: turn off to make resolvers return values instead of pointers for structs
|
||||||
|
# resolvers_always_return_pointers: true
|
||||||
|
|
||||||
|
# Optional: turn on to return pointers instead of values in unmarshalInput
|
||||||
|
# return_pointers_in_unmarshalinput: false
|
||||||
|
|
||||||
|
# Optional: wrap nullable input fields with Omittable
|
||||||
|
# nullable_input_omittable: true
|
||||||
|
|
||||||
|
# Optional: set to speed up generation time by not performing a final validation pass.
|
||||||
|
# skip_validation: true
|
||||||
|
|
||||||
|
# Optional: set to skip running `go mod tidy` when generating server code
|
||||||
|
# skip_mod_tidy: true
|
||||||
|
|
||||||
|
# gqlgen will search for any type names in the schema in these go packages
|
||||||
|
# if they match it will use them, otherwise it will generate them.
|
||||||
|
autobind:
|
||||||
|
- "git.obamna.ru/erius/ozon-task/graph/model"
|
||||||
|
|
||||||
|
# This section declares type mapping between the GraphQL and go type systems
|
||||||
|
#
|
||||||
|
# The first line in each type will be used as defaults for resolver arguments and
|
||||||
|
# modelgen, the others will be allowed when binding to fields. Configure them to
|
||||||
|
# your liking
|
||||||
|
models:
|
||||||
|
ID:
|
||||||
|
model:
|
||||||
|
- github.com/99designs/gqlgen/graphql.ID
|
||||||
|
- github.com/99designs/gqlgen/graphql.Int
|
||||||
|
- github.com/99designs/gqlgen/graphql.Int64
|
||||||
|
- github.com/99designs/gqlgen/graphql.Int32
|
||||||
|
- github.com/99designs/gqlgen/graphql.Uint
|
||||||
|
Int:
|
||||||
|
model:
|
||||||
|
- github.com/99designs/gqlgen/graphql.Int
|
||||||
|
- github.com/99designs/gqlgen/graphql.Int64
|
||||||
|
- github.com/99designs/gqlgen/graphql.Int32
|
4593
graph/generated.go
Normal file
4593
graph/generated.go
Normal file
File diff suppressed because it is too large
Load diff
61
graph/model/data.go
Normal file
61
graph/model/data.go
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
const CommentLengthLimit = 2000
|
||||||
|
|
||||||
|
type Comment struct {
|
||||||
|
// db fields
|
||||||
|
ID uint `json:"id" gorm:"primarykey"`
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||||
|
|
||||||
|
// db foreign key to reference parent post
|
||||||
|
PostID uint
|
||||||
|
|
||||||
|
Post *Post `json:"post"`
|
||||||
|
Author string `json:"author"`
|
||||||
|
Contents string `json:"contents"`
|
||||||
|
|
||||||
|
// db key to reference parent comment
|
||||||
|
ReplyToID *uint
|
||||||
|
|
||||||
|
ReplyTo *Comment `json:"reply_to,omitempty"`
|
||||||
|
Replies []*Comment `json:"replies" gorm:"many2many:comment_replies"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Post struct {
|
||||||
|
// db fields
|
||||||
|
ID uint `json:"id" gorm:"primarykey"`
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||||
|
|
||||||
|
Title string `json:"title"`
|
||||||
|
Author string `json:"author"`
|
||||||
|
Contents string `json:"contents"`
|
||||||
|
Comments []*Comment `json:"comments" gorm:"foreignKey:PostID"`
|
||||||
|
AllowComments bool `json:"allowComments"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func PostFromInput(input *PostInput) *Post {
|
||||||
|
return &Post{
|
||||||
|
Author: input.Author,
|
||||||
|
Title: input.Title,
|
||||||
|
Contents: input.Contents,
|
||||||
|
AllowComments: input.AllowComments,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CommentFromInput(input *CommentInput) *Comment {
|
||||||
|
return &Comment{
|
||||||
|
Author: input.Author,
|
||||||
|
Contents: input.Contents,
|
||||||
|
Replies: make([]*Comment, 0),
|
||||||
|
}
|
||||||
|
}
|
9
graph/model/models_gen.go
Normal file
9
graph/model/models_gen.go
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
// Code generated by github.com/99designs/gqlgen, DO NOT EDIT.
|
||||||
|
|
||||||
|
package model
|
||||||
|
|
||||||
|
type Mutation struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type Query struct {
|
||||||
|
}
|
19
graph/model/mutation.go
Normal file
19
graph/model/mutation.go
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
type AddResult struct {
|
||||||
|
ItemID *uint `json:"item_id,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommentInput struct {
|
||||||
|
ParentPostID *uint `json:"parent_post_id,omitempty"`
|
||||||
|
ParentCommentID *uint `json:"parent_comment_id,omitempty"`
|
||||||
|
Author string `json:"author"`
|
||||||
|
Contents string `json:"contents"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PostInput struct {
|
||||||
|
Title string `json:"title"`
|
||||||
|
Author string `json:"author"`
|
||||||
|
Contents string `json:"contents"`
|
||||||
|
AllowComments bool `json:"allowComments"`
|
||||||
|
}
|
19
graph/resolver.go
Normal file
19
graph/resolver.go
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
package graph
|
||||||
|
|
||||||
|
//go:generate go run github.com/99designs/gqlgen generate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.obamna.ru/erius/ozon-task/internal/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
// This file will not be regenerated automatically.
|
||||||
|
//
|
||||||
|
// It serves as dependency injection for your app, add any dependencies you require here.
|
||||||
|
|
||||||
|
type Resolver struct {
|
||||||
|
Storage storage.Storage
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitResolver(s storage.Storage) *Resolver {
|
||||||
|
return &Resolver{Storage: s}
|
||||||
|
}
|
44
graph/schema.graphqls
Normal file
44
graph/schema.graphqls
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
type Post {
|
||||||
|
id: ID!
|
||||||
|
title: String!
|
||||||
|
author: String!
|
||||||
|
contents: String!
|
||||||
|
comments: [Comment!]!
|
||||||
|
allowComments: Boolean!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Comment {
|
||||||
|
id: ID!
|
||||||
|
post: Post!
|
||||||
|
author: String!
|
||||||
|
contents: String!
|
||||||
|
replyTo: Comment
|
||||||
|
replies: [Comment!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Query {
|
||||||
|
allPosts: [Post!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Mutation {
|
||||||
|
addPost(input: PostInput): AddResult!
|
||||||
|
addComment(input: CommentInput): AddResult!
|
||||||
|
}
|
||||||
|
|
||||||
|
input PostInput {
|
||||||
|
title: String!
|
||||||
|
author: String!
|
||||||
|
contents: String!
|
||||||
|
allowComments: Boolean! = true
|
||||||
|
}
|
||||||
|
|
||||||
|
input CommentInput {
|
||||||
|
parentPostId: ID
|
||||||
|
parentCommentId: ID
|
||||||
|
author: String!
|
||||||
|
contents: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
type AddResult {
|
||||||
|
itemId: ID!
|
||||||
|
}
|
42
graph/schema.resolvers.go
Normal file
42
graph/schema.resolvers.go
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
package graph
|
||||||
|
|
||||||
|
// This file will be automatically regenerated based on the schema, any resolver implementations
|
||||||
|
// will be copied through when generating and any unknown code will be moved to the end.
|
||||||
|
// Code generated by github.com/99designs/gqlgen version v0.17.49
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.obamna.ru/erius/ozon-task/graph/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AddPost is the resolver for the add_post field.
|
||||||
|
func (r *mutationResolver) AddPost(ctx context.Context, input *model.PostInput) (*model.AddResult, error) {
|
||||||
|
return r.Storage.AddPost(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddComment is the resolver for the add_comment field.
|
||||||
|
func (r *mutationResolver) AddComment(ctx context.Context, input *model.CommentInput) (*model.AddResult, error) {
|
||||||
|
if len(input.Contents) > model.CommentLengthLimit {
|
||||||
|
return nil, fmt.Errorf("exceeded max comment length of %d chars", model.CommentLengthLimit)
|
||||||
|
}
|
||||||
|
if input.ParentCommentID == nil && input.ParentPostID == nil {
|
||||||
|
return nil, fmt.Errorf("parent post or parent comment ids weren't specified")
|
||||||
|
}
|
||||||
|
return r.Storage.AddComment(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllPosts is the resolver for the all_posts field.
|
||||||
|
func (r *queryResolver) AllPosts(ctx context.Context) ([]*model.Post, error) {
|
||||||
|
return r.Storage.GetPosts()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mutation returns MutationResolver implementation.
|
||||||
|
func (r *Resolver) Mutation() MutationResolver { return &mutationResolver{r} }
|
||||||
|
|
||||||
|
// Query returns QueryResolver implementation.
|
||||||
|
func (r *Resolver) Query() QueryResolver { return &queryResolver{r} }
|
||||||
|
|
||||||
|
type mutationResolver struct{ *Resolver }
|
||||||
|
type queryResolver struct{ *Resolver }
|
62
internal/storage/db/database.go
Normal file
62
internal/storage/db/database.go
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.obamna.ru/erius/ozon-task/graph/model"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Database struct {
|
||||||
|
db *gorm.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Database) AddPost(input *model.PostInput) (*model.AddResult, error) {
|
||||||
|
post := model.PostFromInput(input)
|
||||||
|
err := s.db.Create(post).Error
|
||||||
|
return &model.AddResult{ItemID: &post.ID}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Database) AddComment(input *model.CommentInput) (*model.AddResult, error) {
|
||||||
|
comment := model.CommentFromInput(input)
|
||||||
|
if input.ParentPostID == nil {
|
||||||
|
// multiple operations performed in one transaction
|
||||||
|
err := s.db.Transaction(func(tx *gorm.DB) error {
|
||||||
|
var parent model.Comment
|
||||||
|
// find parent comment in db
|
||||||
|
err := s.db.First(&parent, *input.ParentCommentID).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// set new comment fields to reference parent post and comment
|
||||||
|
comment.ReplyTo = &parent
|
||||||
|
comment.PostID = parent.PostID
|
||||||
|
// insert comment
|
||||||
|
err = s.db.Create(comment).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// add new reply to parent and save
|
||||||
|
parent.Replies = append(parent.Replies, comment)
|
||||||
|
err = s.db.Save(parent).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
comment.PostID = *input.ParentPostID
|
||||||
|
err := s.db.Create(comment).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &model.AddResult{ItemID: &comment.ID}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s Database) GetPosts() ([]*model.Post, error) {
|
||||||
|
var posts []*model.Post
|
||||||
|
err := s.db.Find(&posts).Error
|
||||||
|
return posts, err
|
||||||
|
}
|
29
internal/storage/db/postgres.go
Normal file
29
internal/storage/db/postgres.go
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.obamna.ru/erius/ozon-task/graph/model"
|
||||||
|
"gorm.io/driver/postgres"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
host = os.Getenv("APP_POSTGRES_HOST")
|
||||||
|
port = os.Getenv("APP_POSTGRES_PORT")
|
||||||
|
user = os.Getenv("APP_POSTGRES_USER")
|
||||||
|
passwd = os.Getenv("APP_POSTGRES_PASSWORD")
|
||||||
|
db = os.Getenv("APP_POSTGRES_DB")
|
||||||
|
con = fmt.Sprintf("postgres://%s:%s@%s:%s/%s", user, passwd, host, port, db)
|
||||||
|
)
|
||||||
|
|
||||||
|
func InitPostgres() (*Database, error) {
|
||||||
|
// PrepareStmt is true for caching complex sql statements when adding comments or replies
|
||||||
|
db, err := gorm.Open(postgres.Open(con), &gorm.Config{PrepareStmt: true})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
db.AutoMigrate(&model.Post{}, &model.Comment{})
|
||||||
|
return &Database{db}, nil
|
||||||
|
}
|
41
internal/storage/memory.go
Normal file
41
internal/storage/memory.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.obamna.ru/erius/ozon-task/graph/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
type InMemory struct {
|
||||||
|
postId uint
|
||||||
|
commentId uint
|
||||||
|
posts []*model.Post
|
||||||
|
comments []*model.Comment
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitInMemory() *InMemory {
|
||||||
|
return &InMemory{
|
||||||
|
postId: 0,
|
||||||
|
commentId: 0,
|
||||||
|
posts: make([]*model.Post, 0),
|
||||||
|
comments: make([]*model.Comment, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InMemory) AddPost(input *model.PostInput) (*model.AddResult, error) {
|
||||||
|
post := model.PostFromInput(input)
|
||||||
|
post.ID = s.postId
|
||||||
|
s.posts = append(s.posts, post)
|
||||||
|
s.postId++
|
||||||
|
return &model.AddResult{ItemID: &post.ID}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InMemory) AddComment(input *model.CommentInput) (*model.AddResult, error) {
|
||||||
|
comment := model.CommentFromInput(input)
|
||||||
|
comment.ID = s.commentId
|
||||||
|
s.comments = append(s.comments, comment)
|
||||||
|
s.commentId++
|
||||||
|
return &model.AddResult{ItemID: &comment.ID}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s InMemory) GetPosts() ([]*model.Post, error) {
|
||||||
|
return s.posts, nil
|
||||||
|
}
|
32
internal/storage/storage.go
Normal file
32
internal/storage/storage.go
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"git.obamna.ru/erius/ozon-task/graph/model"
|
||||||
|
"git.obamna.ru/erius/ozon-task/internal/storage/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
inMemory = "inmemory"
|
||||||
|
postgres = "postgres"
|
||||||
|
)
|
||||||
|
|
||||||
|
var storage = os.Getenv("APP_STORAGE")
|
||||||
|
|
||||||
|
type Storage interface {
|
||||||
|
AddPost(input *model.PostInput) (*model.AddResult, error)
|
||||||
|
AddComment(input *model.CommentInput) (*model.AddResult, error)
|
||||||
|
GetPosts() ([]*model.Post, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitStorage() (Storage, error) {
|
||||||
|
switch storage {
|
||||||
|
case inMemory:
|
||||||
|
return InitInMemory(), nil
|
||||||
|
case postgres:
|
||||||
|
return db.InitPostgres()
|
||||||
|
default:
|
||||||
|
return InitInMemory(), nil
|
||||||
|
}
|
||||||
|
}
|
7
tools.go
Normal file
7
tools.go
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
//go:build tools
|
||||||
|
|
||||||
|
package tools
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "github.com/99designs/gqlgen"
|
||||||
|
)
|
Loading…
Reference in a new issue