Compare commits
4 Commits
main
...
BLOG-43_po
Author | SHA1 | Date | |
---|---|---|---|
2eae8c2667 | |||
d7c6c97051 | |||
a4394eea9e | |||
a9081734b3 |
37
README.md
37
README.md
@ -2,15 +2,46 @@
|
|||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
- Frontend: Next.js
|
- Frontend: Typescript (Next.js)
|
||||||
- Backend: Go (gin)
|
- Backend: Go (gqlgen)
|
||||||
|
- Database: PostgreSQL
|
||||||
|
|
||||||
Despite Next.js being a full-stack framework, I still decided to adopt a separate front-end and back-end architecture for this blog project. I believe that this separation makes the project cleaner, reduces coupling, and aligns with modern development practices. Furthermore, I wanted to practice developing a purely back-end API.
|
Despite Next.js being a full-stack framework, I still decided to adopt a separate front-end and back-end architecture for this blog project. I believe that this separation makes the project cleaner, reduces coupling, and aligns with modern development practices. Furthermore, I wanted to practice developing a purely back-end API.
|
||||||
|
|
||||||
As for the more detailed development approach, I plan to use Clean Architecture for the overall structure and ATDD for testing. Of course, such a small project may not necessarily require such complex design patterns, but I want to give myself an opportunity to practice them.
|
As for the more detailed development approach, I plan to use Clean Architecture for the overall structure and ATDD for testing. Of course, such a small project may not necessarily require such complex design patterns, but I want to give myself an opportunity to practice them.
|
||||||
|
|
||||||
These will allow me to become more proficient in these modern development practices and leave a lot of flexibility and room for adjustments in the future.
|
These will allow me to become more proficient in these modern development practices and leave a lot of flexibility and room for adjustments in the future.
|
||||||
|
|
||||||
|
## Environment
|
||||||
|
|
||||||
|
### Database Instance
|
||||||
|
|
||||||
|
Using docker to run a database instance.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
podman run -d -p 127.0.0.1:5432:5432 -e POSTGRES_HOST_AUTH_METHOD=trust --rm --name postgres --replace postgres:alpine
|
||||||
|
```
|
||||||
|
|
||||||
|
For more infomation, please refer to [PostgreSQL Docker Hub](https://hub.docker.com/_/postgres).
|
||||||
|
|
||||||
|
### Database Migration
|
||||||
|
|
||||||
|
Using golang-migrate to manage database migration.
|
||||||
|
|
||||||
|
- Create a new migration file
|
||||||
|
|
||||||
|
```bash
|
||||||
|
migrate create -ext sql -dir backend/internal/framework/db/postgres/migration -seq migration
|
||||||
|
```
|
||||||
|
|
||||||
|
- Run migration
|
||||||
|
|
||||||
|
```bash
|
||||||
|
migrate -path db/migrations -database "postgresql://postgres@localhost:5432/postgres?sslmode=disable" up
|
||||||
|
```
|
||||||
|
|
||||||
|
For more information, please refer to [golang-migrate GitHub](https://github.com/golang-migrate/migrate/tree/master/cmd/migrate).
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This project uses a combination of the [MIT License and a custom license](./LICENSE.md). Based on the MIT License, anyone is permitted to use the code. However, before deploying the code, they must first replace any information belonging to "me" or any content that could identify "me," such as logos, names, and "about me" sections.
|
This project uses a combination of the [MIT License and a custom license](./LICENSE.md). Based on the MIT License, anyone is permitted to use the code. However, before deploying the code, they must first replace any information belonging to "me" or any content that could identify "me," such as logos, names, and "about me" sections.
|
||||||
|
61
backend/cmd/server/main.go
Normal file
61
backend/cmd/server/main.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"git.squidspirit.com/squid/blog.git/backend/internal/adapter/repository"
|
||||||
|
"git.squidspirit.com/squid/blog.git/backend/internal/application"
|
||||||
|
"git.squidspirit.com/squid/blog.git/backend/internal/framework/api/graph"
|
||||||
|
"git.squidspirit.com/squid/blog.git/backend/internal/framework/db/postgres"
|
||||||
|
"git.squidspirit.com/squid/blog.git/backend/internal/pkg/env"
|
||||||
|
"github.com/99designs/gqlgen/graphql/handler"
|
||||||
|
"github.com/99designs/gqlgen/graphql/handler/extension"
|
||||||
|
"github.com/99designs/gqlgen/graphql/handler/lru"
|
||||||
|
"github.com/99designs/gqlgen/graphql/handler/transport"
|
||||||
|
"github.com/99designs/gqlgen/graphql/playground"
|
||||||
|
_ "github.com/lib/pq"
|
||||||
|
"github.com/vektah/gqlparser/v2/ast"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
db, err := postgres.Connect(env.GetDSN())
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
initGraphqlHandler(createResolver(db))
|
||||||
|
|
||||||
|
port := env.GetPort()
|
||||||
|
log.Printf("connect to http://localhost:%s/ for GraphQL playground", port)
|
||||||
|
log.Fatal(http.ListenAndServe(":"+port, nil))
|
||||||
|
}
|
||||||
|
|
||||||
|
func initGraphqlHandler(resolver *graph.Resolver) *handler.Server {
|
||||||
|
srv := handler.New(graph.NewExecutableSchema(graph.Config{Resolvers: resolver}))
|
||||||
|
|
||||||
|
srv.AddTransport(transport.Options{})
|
||||||
|
srv.AddTransport(transport.GET{})
|
||||||
|
srv.AddTransport(transport.POST{})
|
||||||
|
|
||||||
|
srv.SetQueryCache(lru.New[*ast.QueryDocument](1000))
|
||||||
|
|
||||||
|
srv.Use(extension.Introspection{})
|
||||||
|
srv.Use(extension.AutomaticPersistedQuery{
|
||||||
|
Cache: lru.New[string](100),
|
||||||
|
})
|
||||||
|
|
||||||
|
http.Handle("/", playground.Handler("GraphQL playground", "/query"))
|
||||||
|
http.Handle("/query", srv)
|
||||||
|
|
||||||
|
return srv
|
||||||
|
}
|
||||||
|
|
||||||
|
func createResolver(db *sql.DB) *graph.Resolver {
|
||||||
|
postRepo := repository.NewPostRepo(postgres.NewPostDBService(db))
|
||||||
|
|
||||||
|
return &graph.Resolver{
|
||||||
|
GetAllPostsUseCase: application.NewGetAllPostsUseCase(postRepo),
|
||||||
|
}
|
||||||
|
}
|
32
backend/go.mod
Normal file
32
backend/go.mod
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
module git.squidspirit.com/squid/blog.git/backend
|
||||||
|
|
||||||
|
go 1.24.1
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/99designs/gqlgen v0.17.66
|
||||||
|
github.com/lib/pq v1.10.9
|
||||||
|
github.com/thoas/go-funk v0.9.3
|
||||||
|
github.com/vektah/gqlparser/v2 v2.5.22
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/agnivade/levenshtein v1.2.0 // indirect
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
||||||
|
github.com/google/go-cmp v0.7.0 // 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/kr/pretty v0.3.1 // indirect
|
||||||
|
github.com/rogpeppe/go-internal v1.13.1 // 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.5 // indirect
|
||||||
|
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
|
||||||
|
golang.org/x/mod v0.24.0 // indirect
|
||||||
|
golang.org/x/sync v0.12.0 // indirect
|
||||||
|
golang.org/x/text v0.23.0 // indirect
|
||||||
|
golang.org/x/tools v0.31.0 // indirect
|
||||||
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
79
backend/go.sum
Normal file
79
backend/go.sum
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
github.com/99designs/gqlgen v0.17.66 h1:2/SRc+h3115fCOZeTtsqrB5R5gTGm+8qCAwcrZa+CXA=
|
||||||
|
github.com/99designs/gqlgen v0.17.66/go.mod h1:gucrb5jK5pgCKzAGuOMMVU9C8PnReecHEHd2UxLQwCg=
|
||||||
|
github.com/PuerkitoBio/goquery v1.9.3 h1:mpJr/ikUA9/GNJB/DBZcGeFDXUtosHRyRrwh7KGdTG0=
|
||||||
|
github.com/PuerkitoBio/goquery v1.9.3/go.mod h1:1ndLHPdTz+DyQPICCWYlYQMPl0oXZj0G6D4LCYA6u4U=
|
||||||
|
github.com/agnivade/levenshtein v1.2.0 h1:U9L4IOT0Y3i0TIlUIDJ7rVUziKi/zPbrJGaFrtYH3SY=
|
||||||
|
github.com/agnivade/levenshtein v1.2.0/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU=
|
||||||
|
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.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.5/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-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo=
|
||||||
|
github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
|
||||||
|
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
|
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/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
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/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||||
|
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.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
|
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
|
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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/thoas/go-funk v0.9.3 h1:7+nAEx3kn5ZJcnDm2Bh23N2yOtweO14bi//dvRtgLpw=
|
||||||
|
github.com/thoas/go-funk v0.9.3/go.mod h1:+IWnUfUmFO1+WVYQWQtIJHeRRdaIyyYglZN7xzUPe4Q=
|
||||||
|
github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w=
|
||||||
|
github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
|
||||||
|
github.com/vektah/gqlparser/v2 v2.5.22 h1:yaaeJ0fu+nv1vUMW0Hl+aS1eiv1vMfapBNjpffAda1I=
|
||||||
|
github.com/vektah/gqlparser/v2 v2.5.22/go.mod h1:xMl+ta8a5M1Yo1A1Iwt/k7gSpscwSnHZdw7tfhEGfTM=
|
||||||
|
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
|
||||||
|
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
|
||||||
|
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
|
||||||
|
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||||
|
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
||||||
|
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||||
|
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
||||||
|
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
|
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||||
|
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||||
|
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
|
||||||
|
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
|
||||||
|
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.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
152
backend/gqlgen.yml
Normal file
152
backend/gqlgen.yml
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
# Where are all the schema files located? globs are supported eg src/**/*.graphqls
|
||||||
|
schema:
|
||||||
|
- internal/framework/api/graph/*.graphqls
|
||||||
|
|
||||||
|
# Where should the generated server code go?
|
||||||
|
exec:
|
||||||
|
package: graph
|
||||||
|
layout: single-file # Only other option is "follow-schema," ie multi-file.
|
||||||
|
|
||||||
|
# Only for single-file layout:
|
||||||
|
filename: internal/framework/api/graph/generated.go
|
||||||
|
|
||||||
|
# Only for follow-schema layout:
|
||||||
|
# dir: graph
|
||||||
|
# filename_template: "{name}.generated.go"
|
||||||
|
|
||||||
|
# Optional: Maximum number of goroutines in concurrency to use per child resolvers(default: unlimited)
|
||||||
|
# worker_limit: 1000
|
||||||
|
|
||||||
|
# Uncomment to enable federation
|
||||||
|
# federation:
|
||||||
|
# filename: graph/federation.go
|
||||||
|
# package: graph
|
||||||
|
# version: 2
|
||||||
|
# options:
|
||||||
|
# computed_requires: true
|
||||||
|
|
||||||
|
# Where should any generated models go?
|
||||||
|
model:
|
||||||
|
filename: internal/adapter/controller/graphdto/dtos.go
|
||||||
|
package: graphdto
|
||||||
|
|
||||||
|
# Optional: Pass in a path to a new gotpl template to use for generating the models
|
||||||
|
# model_template: [your/path/model.gotpl]
|
||||||
|
|
||||||
|
# Where should the resolver implementations go?
|
||||||
|
resolver:
|
||||||
|
package: graph
|
||||||
|
layout: follow-schema # Only other option is "single-file."
|
||||||
|
|
||||||
|
# Only for single-file layout:
|
||||||
|
# filename: graph/resolver.go
|
||||||
|
|
||||||
|
# Only for follow-schema layout:
|
||||||
|
dir: internal/framework/api/graph
|
||||||
|
filename_template: "{name}.resolvers.go"
|
||||||
|
|
||||||
|
# Optional: turn on to not generate template comments above resolvers
|
||||||
|
# omit_template_comment: false
|
||||||
|
# Optional: Pass in a path to a new gotpl template to use for generating resolvers
|
||||||
|
# resolver_template: [your/path/resolver.gotpl]
|
||||||
|
# Optional: turn on to avoid rewriting existing resolver(s) when generating
|
||||||
|
# preserve_resolver: 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 on to exclude root models such as Query and Mutation from the generated models file.
|
||||||
|
# omit_root_models: false
|
||||||
|
|
||||||
|
# Optional: turn on to exclude resolver fields from the generated models file.
|
||||||
|
# omit_resolver_fields: 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
|
||||||
|
|
||||||
|
# Optional: if this is set to true, argument directives that
|
||||||
|
# decorate a field with a null value will still be called.
|
||||||
|
#
|
||||||
|
# This enables argumment directives to not just mutate
|
||||||
|
# argument values but to set them even if they're null.
|
||||||
|
call_argument_directives_with_null: true
|
||||||
|
|
||||||
|
# Optional: set build tags that will be used to load packages
|
||||||
|
# go_build_tags:
|
||||||
|
# - private
|
||||||
|
# - enterprise
|
||||||
|
|
||||||
|
# Optional: set to modify the initialisms regarded for Go names
|
||||||
|
# go_initialisms:
|
||||||
|
# replace_defaults: false # if true, the default initialisms will get dropped in favor of the new ones instead of being added
|
||||||
|
# initialisms: # List of initialisms to for Go names
|
||||||
|
# - 'CC'
|
||||||
|
# - 'BCC'
|
||||||
|
|
||||||
|
# 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.squidspirit.com/squid/blog.git/backend/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.Uint32
|
||||||
|
# gqlgen provides a default GraphQL UUID convenience wrapper for github.com/google/uuid
|
||||||
|
# but you can override this to provide your own GraphQL UUID implementation
|
||||||
|
UUID:
|
||||||
|
model:
|
||||||
|
- github.com/99designs/gqlgen/graphql.UUID
|
||||||
|
|
||||||
|
# The GraphQL spec explicitly states that the Int type is a signed 32-bit
|
||||||
|
# integer. Using Go int or int64 to represent it can lead to unexpected
|
||||||
|
# behavior, and some GraphQL tools like Apollo Router will fail when
|
||||||
|
# communicating numbers that overflow 32-bits.
|
||||||
|
#
|
||||||
|
# You may choose to use the custom, built-in Int64 scalar to represent 64-bit
|
||||||
|
# integers, or ignore the spec and bind Int to graphql.Int / graphql.Int64
|
||||||
|
# (the default behavior of gqlgen). This is fine in simple use cases when you
|
||||||
|
# do not need to worry about interoperability and only expect small numbers.
|
||||||
|
Int:
|
||||||
|
model:
|
||||||
|
- github.com/99designs/gqlgen/graphql.Int32
|
||||||
|
Int64:
|
||||||
|
model:
|
||||||
|
- github.com/99designs/gqlgen/graphql.Int
|
||||||
|
- github.com/99designs/gqlgen/graphql.Int64
|
22
backend/internal/adapter/controller/graphdto/dtos.go
Normal file
22
backend/internal/adapter/controller/graphdto/dtos.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Code generated by github.com/99designs/gqlgen, DO NOT EDIT.
|
||||||
|
|
||||||
|
package graphdto
|
||||||
|
|
||||||
|
type Label struct {
|
||||||
|
ID uint32 `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Color string `json:"color"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Post struct {
|
||||||
|
ID uint32 `json:"id"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
PreviewImageURL string `json:"previewImageUrl"`
|
||||||
|
Labels []*Label `json:"labels"`
|
||||||
|
PublishedTime *string `json:"publishedTime,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Query struct {
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package graphdto
|
||||||
|
|
||||||
|
import "git.squidspirit.com/squid/blog.git/backend/internal/domain"
|
||||||
|
|
||||||
|
func NewLabelDTO(labelEntity *domain.Label) *Label {
|
||||||
|
return &Label{
|
||||||
|
ID: labelEntity.ID,
|
||||||
|
Name: labelEntity.Name,
|
||||||
|
Color: labelEntity.Color,
|
||||||
|
}
|
||||||
|
}
|
19
backend/internal/adapter/controller/graphdto/new_post_dto.go
Normal file
19
backend/internal/adapter/controller/graphdto/new_post_dto.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package graphdto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.squidspirit.com/squid/blog.git/backend/internal/domain"
|
||||||
|
"github.com/thoas/go-funk"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewPostDTO(entity *domain.Post) *Post {
|
||||||
|
return &Post{
|
||||||
|
ID: entity.ID,
|
||||||
|
Title: entity.Title,
|
||||||
|
Content: entity.Content,
|
||||||
|
Description: entity.Description,
|
||||||
|
PreviewImageURL: entity.PreviewImageURL,
|
||||||
|
Labels: funk.Map(entity.Labels, func(label *domain.Label) *Label {
|
||||||
|
return NewLabelDTO(label)
|
||||||
|
}).([]*Label),
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.squidspirit.com/squid/blog.git/backend/internal/adapter/controller/graphdto"
|
||||||
|
"git.squidspirit.com/squid/blog.git/backend/internal/application"
|
||||||
|
"git.squidspirit.com/squid/blog.git/backend/internal/domain"
|
||||||
|
"github.com/thoas/go-funk"
|
||||||
|
)
|
||||||
|
|
||||||
|
type QueryPostsController interface {
|
||||||
|
Handle() ([]*graphdto.Post, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type queryPostsControllerImpl struct {
|
||||||
|
getAllPostsUseCase application.GetAllPostsUseCase
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewQueryPostsController(getAllPostsUseCase application.GetAllPostsUseCase) QueryPostsController {
|
||||||
|
return &queryPostsControllerImpl{
|
||||||
|
getAllPostsUseCase: getAllPostsUseCase,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *queryPostsControllerImpl) Handle() ([]*graphdto.Post, error) {
|
||||||
|
entities, err := c.getAllPostsUseCase.Execute()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return funk.Map(entities, func(entity *domain.Post) *graphdto.Post {
|
||||||
|
return graphdto.NewPostDTO(entity)
|
||||||
|
}).([]*graphdto.Post), nil
|
||||||
|
}
|
23
backend/internal/adapter/repository/dbdto/label.go
Normal file
23
backend/internal/adapter/repository/dbdto/label.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package dbdto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.squidspirit.com/squid/blog.git/backend/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Label struct {
|
||||||
|
ID uint32 `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Color string `json:"color"`
|
||||||
|
CreatedTime time.Time
|
||||||
|
UpdatedTime time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Label) ToEntity() *domain.Label {
|
||||||
|
return &domain.Label{
|
||||||
|
ID: l.ID,
|
||||||
|
Name: l.Name,
|
||||||
|
Color: l.Color,
|
||||||
|
}
|
||||||
|
}
|
34
backend/internal/adapter/repository/dbdto/post.go
Normal file
34
backend/internal/adapter/repository/dbdto/post.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package dbdto
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"git.squidspirit.com/squid/blog.git/backend/internal/domain"
|
||||||
|
"github.com/thoas/go-funk"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Post struct {
|
||||||
|
ID uint32
|
||||||
|
Title string
|
||||||
|
Content string
|
||||||
|
Description string
|
||||||
|
PreviewImageURL string
|
||||||
|
Labels []*Label
|
||||||
|
PublishedTime *time.Time
|
||||||
|
CreatedTime time.Time
|
||||||
|
UpdatedTime time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Post) ToEntity() *domain.Post {
|
||||||
|
return &domain.Post{
|
||||||
|
ID: p.ID,
|
||||||
|
Title: p.Title,
|
||||||
|
Content: p.Content,
|
||||||
|
Description: p.Description,
|
||||||
|
PreviewImageURL: p.PreviewImageURL,
|
||||||
|
Labels: funk.Map(p.Labels, func(label *Label) *domain.Label {
|
||||||
|
return label.ToEntity()
|
||||||
|
}).([]*domain.Label),
|
||||||
|
PublishedTime: p.PublishedTime,
|
||||||
|
}
|
||||||
|
}
|
34
backend/internal/adapter/repository/post_repo_impl.go
Normal file
34
backend/internal/adapter/repository/post_repo_impl.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.squidspirit.com/squid/blog.git/backend/internal/adapter/repository/dbdto"
|
||||||
|
"git.squidspirit.com/squid/blog.git/backend/internal/application"
|
||||||
|
"git.squidspirit.com/squid/blog.git/backend/internal/domain"
|
||||||
|
"github.com/thoas/go-funk"
|
||||||
|
)
|
||||||
|
|
||||||
|
type postRepo struct {
|
||||||
|
dbService PostDBService
|
||||||
|
}
|
||||||
|
|
||||||
|
type PostDBService interface {
|
||||||
|
QueryAll() ([]*dbdto.Post, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPostRepo(dbService PostDBService) application.PostRepo {
|
||||||
|
return &postRepo{dbService}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *postRepo) GetAll() ([]*domain.Post, error) {
|
||||||
|
postDtos, err := r.dbService.QueryAll()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return funk.Map(postDtos, func(postDto *dbdto.Post) *domain.Post {
|
||||||
|
return postDto.ToEntity()
|
||||||
|
}).([]*domain.Post), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *postRepo) GetByID(id int) (*domain.Post, error) {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
21
backend/internal/application/get_all_posts_use_case.go
Normal file
21
backend/internal/application/get_all_posts_use_case.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import "git.squidspirit.com/squid/blog.git/backend/internal/domain"
|
||||||
|
|
||||||
|
type GetAllPostsUseCase interface {
|
||||||
|
Execute() ([]*domain.Post, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type getAllPostsUseCaseImpl struct {
|
||||||
|
postRepo PostRepo
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGetAllPostsUseCase(postRepo PostRepo) GetAllPostsUseCase {
|
||||||
|
return &getAllPostsUseCaseImpl{
|
||||||
|
postRepo: postRepo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uc *getAllPostsUseCaseImpl) Execute() ([]*domain.Post, error) {
|
||||||
|
return uc.postRepo.GetAll()
|
||||||
|
}
|
7
backend/internal/application/label_repo.go
Normal file
7
backend/internal/application/label_repo.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import "git.squidspirit.com/squid/blog.git/backend/internal/domain"
|
||||||
|
|
||||||
|
type LabelRepo interface {
|
||||||
|
GetByIDs(ids []int) ([]*domain.Label, error)
|
||||||
|
}
|
8
backend/internal/application/post_repo.go
Normal file
8
backend/internal/application/post_repo.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
package application
|
||||||
|
|
||||||
|
import "git.squidspirit.com/squid/blog.git/backend/internal/domain"
|
||||||
|
|
||||||
|
type PostRepo interface {
|
||||||
|
GetAll() ([]*domain.Post, error)
|
||||||
|
GetByID(id int) (*domain.Post, error)
|
||||||
|
}
|
7
backend/internal/domain/label.go
Normal file
7
backend/internal/domain/label.go
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
type Label struct {
|
||||||
|
ID uint32
|
||||||
|
Name string
|
||||||
|
Color string
|
||||||
|
}
|
13
backend/internal/domain/post.go
Normal file
13
backend/internal/domain/post.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
type Post struct {
|
||||||
|
ID uint32
|
||||||
|
Title string
|
||||||
|
Content string
|
||||||
|
Description string
|
||||||
|
PreviewImageURL string
|
||||||
|
Labels []*Label
|
||||||
|
PublishedTime *time.Time
|
||||||
|
}
|
4444
backend/internal/framework/api/graph/generated.go
Normal file
4444
backend/internal/framework/api/graph/generated.go
Normal file
File diff suppressed because it is too large
Load Diff
11
backend/internal/framework/api/graph/resolver.go
Normal file
11
backend/internal/framework/api/graph/resolver.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package graph
|
||||||
|
|
||||||
|
import "git.squidspirit.com/squid/blog.git/backend/internal/application"
|
||||||
|
|
||||||
|
// This file will not be regenerated automatically.
|
||||||
|
//
|
||||||
|
// It serves as dependency injection for your app, add any dependencies you require here.
|
||||||
|
|
||||||
|
type Resolver struct {
|
||||||
|
GetAllPostsUseCase application.GetAllPostsUseCase
|
||||||
|
}
|
23
backend/internal/framework/api/graph/schema.graphqls
Normal file
23
backend/internal/framework/api/graph/schema.graphqls
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
scalar Date
|
||||||
|
|
||||||
|
type Label {
|
||||||
|
id: ID!
|
||||||
|
name: String!
|
||||||
|
color: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Post {
|
||||||
|
id: ID!
|
||||||
|
title: String!
|
||||||
|
content: String!
|
||||||
|
description: String!
|
||||||
|
previewImageUrl: String!
|
||||||
|
labels: [Label!]!
|
||||||
|
publishedTime: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
type Query {
|
||||||
|
posts: [Post!]!
|
||||||
|
post(id: ID!): Post!
|
||||||
|
label(id: ID!): Label!
|
||||||
|
}
|
38
backend/internal/framework/api/graph/schema.resolvers.go
Normal file
38
backend/internal/framework/api/graph/schema.resolvers.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
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.66
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.squidspirit.com/squid/blog.git/backend/internal/adapter/controller"
|
||||||
|
"git.squidspirit.com/squid/blog.git/backend/internal/adapter/controller/graphdto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Posts is the resolver for the posts field.
|
||||||
|
func (r *queryResolver) Posts(ctx context.Context) ([]*graphdto.Post, error) {
|
||||||
|
c := controller.NewQueryPostsController(r.GetAllPostsUseCase)
|
||||||
|
dtos, err := c.Handle()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return dtos, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post is the resolver for the post field.
|
||||||
|
func (r *queryResolver) Post(ctx context.Context, id uint32) (*graphdto.Post, error) {
|
||||||
|
panic(fmt.Errorf("not implemented: Post - post"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Label is the resolver for the label field.
|
||||||
|
func (r *queryResolver) Label(ctx context.Context, id uint32) (*graphdto.Label, error) {
|
||||||
|
panic(fmt.Errorf("not implemented: Label - label"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query returns QueryResolver implementation.
|
||||||
|
func (r *Resolver) Query() QueryResolver { return &queryResolver{r} }
|
||||||
|
|
||||||
|
type queryResolver struct{ *Resolver }
|
@ -0,0 +1,31 @@
|
|||||||
|
BEGIN;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS "post" (
|
||||||
|
"id" SERIAL PRIMARY KEY,
|
||||||
|
"title" TEXT NOT NULL,
|
||||||
|
"content" TEXT NOT NULL,
|
||||||
|
"description" TEXT NOT NULL,
|
||||||
|
"preview_image_url" TEXT NOT NULL,
|
||||||
|
"published_time" TIMESTAMP,
|
||||||
|
"created_time" TIMESTAMP NOT NULL,
|
||||||
|
"updated_time" TIMESTAMP NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS "label" (
|
||||||
|
"id" SERIAL PRIMARY KEY,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"color" TEXT NOT NULL,
|
||||||
|
"created_time" TIMESTAMP NOT NULL,
|
||||||
|
"updated_time" TIMESTAMP NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS "post_label" (
|
||||||
|
"post_id" SERIAL NOT NULL,
|
||||||
|
"label_id" SERIAL NOT NULL,
|
||||||
|
"label_order" INTEGER NOT NULL,
|
||||||
|
PRIMARY KEY ("post_id", "label_id"),
|
||||||
|
FOREIGN KEY ("post_id") REFERENCES "post" ("id") ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY ("label_id") REFERENCES "label" ("id") ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
COMMIT;
|
@ -0,0 +1,79 @@
|
|||||||
|
package postgres
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"git.squidspirit.com/squid/blog.git/backend/internal/adapter/repository"
|
||||||
|
"git.squidspirit.com/squid/blog.git/backend/internal/adapter/repository/dbdto"
|
||||||
|
)
|
||||||
|
|
||||||
|
type postDBService struct {
|
||||||
|
db *sql.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPostDBService(db *sql.DB) repository.PostDBService {
|
||||||
|
return &postDBService{db}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *postDBService) QueryAll() ([]*dbdto.Post, error) {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
rows, err := srv.db.QueryContext(ctx, `
|
||||||
|
SELECT
|
||||||
|
p.*,
|
||||||
|
COALESCE(
|
||||||
|
jsonb_agg(
|
||||||
|
jsonb_build_object(
|
||||||
|
'id', l.id,
|
||||||
|
'name', l.name,
|
||||||
|
'color', l.color
|
||||||
|
) ORDER BY pl.label_order ASC
|
||||||
|
)
|
||||||
|
FILTER (WHERE l.id IS NOT NULL),
|
||||||
|
'[]'::jsonb
|
||||||
|
) AS labels
|
||||||
|
FROM post p
|
||||||
|
LEFT JOIN post_label pl ON p.id = pl.post_id
|
||||||
|
LEFT JOIN label l ON pl.label_id = l.id
|
||||||
|
GROUP BY p.id
|
||||||
|
ORDER BY p.created_time DESC
|
||||||
|
`)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
var labelsJSON []byte
|
||||||
|
|
||||||
|
var postDtos []*dbdto.Post
|
||||||
|
for rows.Next() {
|
||||||
|
var postDto dbdto.Post
|
||||||
|
err = rows.Scan(
|
||||||
|
&postDto.ID,
|
||||||
|
&postDto.Title,
|
||||||
|
&postDto.Content,
|
||||||
|
&postDto.Description,
|
||||||
|
&postDto.PreviewImageURL,
|
||||||
|
&postDto.PublishedTime,
|
||||||
|
&postDto.CreatedTime,
|
||||||
|
&postDto.UpdatedTime,
|
||||||
|
&labelsJSON,
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err := json.Unmarshal(labelsJSON, &postDto.Labels)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
postDtos = append(postDtos, &postDto)
|
||||||
|
}
|
||||||
|
|
||||||
|
return postDtos, nil
|
||||||
|
}
|
21
backend/internal/framework/db/postgres/postgres.go
Normal file
21
backend/internal/framework/db/postgres/postgres.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package postgres
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
|
||||||
|
_ "github.com/lib/pq"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Connect(dsn string) (*sql.DB, error) {
|
||||||
|
db, err := sql.Open("postgres", dsn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.Ping()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return db, nil
|
||||||
|
}
|
21
backend/internal/pkg/env/env.go
vendored
Normal file
21
backend/internal/pkg/env/env.go
vendored
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package env
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetPort() string {
|
||||||
|
port, exists := os.LookupEnv("PORT")
|
||||||
|
if !exists {
|
||||||
|
return "8000"
|
||||||
|
}
|
||||||
|
return port
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDSN() string {
|
||||||
|
dsn, exists := os.LookupEnv("DSN")
|
||||||
|
if !exists {
|
||||||
|
return "postgres://postgres@127.0.0.1/postgres?sslmode=disable"
|
||||||
|
}
|
||||||
|
return dsn
|
||||||
|
}
|
8
backend/tools.go
Normal file
8
backend/tools.go
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
//go:build tools
|
||||||
|
|
||||||
|
package tools
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "github.com/99designs/gqlgen"
|
||||||
|
_ "github.com/99designs/gqlgen/graphql/introspection"
|
||||||
|
)
|
Loading…
x
Reference in New Issue
Block a user