Separated graphql schema into multiple files and moved them to graph/schema dir, update the gqlgen.yml accordingly
Removed unnecesary bindings for ID and Int in gqlgen.yml Minor changes to Makefile docker-clean target Reduced docker-compose postgres healthcheck interval for faster local db connections Added pagination to graphql schema, the paginated queries are WIP Removed unneeded fields from the model structs Added allowComment check when adding comments Switched gorm logger to Info mode App now panics if the specified APP_STORAGE in env doesn't exist Reworked database methods, still barely working and are WIP
This commit is contained in:
parent
ab4c41c7f3
commit
e0aa12b126
16 changed files with 1813 additions and 369 deletions
4
Makefile
4
Makefile
|
@ -13,4 +13,6 @@ go-generate:
|
||||||
clean: docker-clean
|
clean: docker-clean
|
||||||
|
|
||||||
docker-clean:
|
docker-clean:
|
||||||
docker container rm postgres-ozon-task-1 postgres-postgres-1 && docker volume rm postgres_postgres-data
|
-docker container rm postgres-ozon-task-1 postgres-postgres-1 standalone-ozon-task-1;\
|
||||||
|
docker volume rm postgres_postgres-data;\
|
||||||
|
docker network rm postgres_default standalone_defaul\
|
||||||
|
|
|
@ -13,7 +13,7 @@ services:
|
||||||
env_file: postgres.env
|
env_file: postgres.env
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
|
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
|
||||||
interval: 5s
|
interval: 1s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
volumes:
|
volumes:
|
||||||
|
|
14
gqlgen.yml
14
gqlgen.yml
|
@ -1,6 +1,6 @@
|
||||||
# Where are all the schema files located? globs are supported eg src/**/*.graphqls
|
# Where are all the schema files located? globs are supported eg src/**/*.graphqls
|
||||||
schema:
|
schema:
|
||||||
- graph/*.graphqls
|
- graph/schema/*.graphqls
|
||||||
|
|
||||||
# Where should the generated server code go?
|
# Where should the generated server code go?
|
||||||
exec:
|
exec:
|
||||||
|
@ -75,14 +75,6 @@ autobind:
|
||||||
# your liking
|
# your liking
|
||||||
models:
|
models:
|
||||||
ID:
|
ID:
|
||||||
model:
|
model: github.com/99designs/gqlgen/graphql.Uint
|
||||||
- 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:
|
Int:
|
||||||
model:
|
model: github.com/99designs/gqlgen/graphql.Int
|
||||||
- github.com/99designs/gqlgen/graphql.Int
|
|
||||||
- github.com/99designs/gqlgen/graphql.Int64
|
|
||||||
- github.com/99designs/gqlgen/graphql.Int32
|
|
||||||
|
|
1649
graph/generated.go
1649
graph/generated.go
File diff suppressed because it is too large
Load diff
|
@ -15,18 +15,10 @@ type Comment struct {
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||||
|
|
||||||
// db foreign key to reference parent post
|
PostID uint `json:"post_id"`
|
||||||
PostID uint
|
Author string `json:"author"`
|
||||||
|
Contents string `json:"contents"`
|
||||||
Post *Post `json:"post"`
|
Replies []*Comment `json:"replies" gorm:"many2many:comment_replies"`
|
||||||
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 {
|
type Post struct {
|
||||||
|
@ -49,6 +41,7 @@ func PostFromInput(input *PostInput) *Post {
|
||||||
Title: input.Title,
|
Title: input.Title,
|
||||||
Contents: input.Contents,
|
Contents: input.Contents,
|
||||||
AllowComments: input.AllowComments,
|
AllowComments: input.AllowComments,
|
||||||
|
Comments: make([]*Comment, 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,8 +2,34 @@
|
||||||
|
|
||||||
package model
|
package model
|
||||||
|
|
||||||
|
type CommentsConnection struct {
|
||||||
|
Edges []*CommentsEdge `json:"edges"`
|
||||||
|
PageInfo *PageInfo `json:"pageInfo"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommentsEdge struct {
|
||||||
|
Cursor uint `json:"cursor"`
|
||||||
|
Node *Comment `json:"node"`
|
||||||
|
}
|
||||||
|
|
||||||
type Mutation struct {
|
type Mutation struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PageInfo struct {
|
||||||
|
StartCursor uint `json:"startCursor"`
|
||||||
|
EndCursor uint `json:"endCursor"`
|
||||||
|
HasNextPage bool `json:"hasNextPage"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PostsConnection struct {
|
||||||
|
Edges []*PostsEdge `json:"edges"`
|
||||||
|
PageInfo *PageInfo `json:"pageInfo"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PostsEdge struct {
|
||||||
|
Cursor uint `json:"cursor"`
|
||||||
|
Node *Post `json:"node"`
|
||||||
|
}
|
||||||
|
|
||||||
type Query struct {
|
type Query struct {
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,31 +12,26 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// AddPost is the resolver for the add_post field.
|
// AddPost is the resolver for the add_post field.
|
||||||
func (r *mutationResolver) AddPost(ctx context.Context, input *model.PostInput) (*model.AddResult, error) {
|
func (r *mutationResolver) AddPost(ctx context.Context, input model.PostInput) (*model.AddResult, error) {
|
||||||
return r.Storage.AddPost(input)
|
return r.Storage.AddPost(&input)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddComment is the resolver for the add_comment field.
|
// AddComment is the resolver for the add_comment field.
|
||||||
func (r *mutationResolver) AddComment(ctx context.Context, input *model.CommentInput) (*model.AddResult, error) {
|
func (r *mutationResolver) AddComment(ctx context.Context, input model.CommentInput) (*model.AddResult, error) {
|
||||||
if len(input.Contents) > model.CommentLengthLimit {
|
if len(input.Contents) > model.CommentLengthLimit {
|
||||||
return nil, fmt.Errorf("exceeded max comment length of %d chars", model.CommentLengthLimit)
|
return nil, fmt.Errorf("exceeded max comment length of %d chars", model.CommentLengthLimit)
|
||||||
}
|
}
|
||||||
if input.ParentCommentID == nil && input.ParentPostID == nil {
|
switch {
|
||||||
|
case input.ParentPostID != nil:
|
||||||
|
return r.Storage.AddCommentToPost(&input)
|
||||||
|
case input.ParentCommentID != nil:
|
||||||
|
return r.Storage.AddReplyToComment(&input)
|
||||||
|
default:
|
||||||
return nil, fmt.Errorf("parent post or parent comment ids weren't specified")
|
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.
|
// Mutation returns MutationResolver implementation.
|
||||||
func (r *Resolver) Mutation() MutationResolver { return &mutationResolver{r} }
|
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 mutationResolver struct{ *Resolver }
|
||||||
type queryResolver struct{ *Resolver }
|
|
49
graph/query.resolvers.go
Normal file
49
graph/query.resolvers.go
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
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"
|
||||||
|
|
||||||
|
"git.obamna.ru/erius/ozon-task/graph/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Replies is the resolver for the replies field.
|
||||||
|
func (r *commentResolver) Replies(ctx context.Context, obj *model.Comment, first uint, after uint) (*model.CommentsConnection, error) {
|
||||||
|
return r.Storage.GetReplies(obj, first, after, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Comments is the resolver for the comments field.
|
||||||
|
func (r *postResolver) Comments(ctx context.Context, obj *model.Post, first uint, after uint) (*model.CommentsConnection, error) {
|
||||||
|
return r.Storage.GetComments(obj, first, after, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post is the resolver for the post field.
|
||||||
|
func (r *queryResolver) Post(ctx context.Context, id uint) (*model.Post, error) {
|
||||||
|
return r.Storage.GetPost(id, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Posts is the resolver for the posts field.
|
||||||
|
func (r *queryResolver) Posts(ctx context.Context, first uint, after uint) (*model.PostsConnection, error) {
|
||||||
|
return r.Storage.GetPosts(first, after, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Comment is the resolver for the comment field.
|
||||||
|
func (r *queryResolver) Comment(ctx context.Context, id uint) (*model.Comment, error) {
|
||||||
|
return r.Storage.GetComment(id, ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Comment returns CommentResolver implementation.
|
||||||
|
func (r *Resolver) Comment() CommentResolver { return &commentResolver{r} }
|
||||||
|
|
||||||
|
// Post returns PostResolver implementation.
|
||||||
|
func (r *Resolver) Post() PostResolver { return &postResolver{r} }
|
||||||
|
|
||||||
|
// Query returns QueryResolver implementation.
|
||||||
|
func (r *Resolver) Query() QueryResolver { return &queryResolver{r} }
|
||||||
|
|
||||||
|
type commentResolver struct{ *Resolver }
|
||||||
|
type postResolver struct{ *Resolver }
|
||||||
|
type queryResolver struct{ *Resolver }
|
|
@ -1,44 +0,0 @@
|
||||||
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!
|
|
||||||
}
|
|
22
graph/schema/mutation.graphqls
Normal file
22
graph/schema/mutation.graphqls
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
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!
|
||||||
|
}
|
25
graph/schema/pagination.graphqls
Normal file
25
graph/schema/pagination.graphqls
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
type PostsConnection {
|
||||||
|
edges: [PostsEdge!]!
|
||||||
|
pageInfo: PageInfo!
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommentsConnection {
|
||||||
|
edges: [CommentsEdge!]!
|
||||||
|
pageInfo: PageInfo!
|
||||||
|
}
|
||||||
|
|
||||||
|
type PostsEdge {
|
||||||
|
cursor: ID!
|
||||||
|
node: Post!
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommentsEdge {
|
||||||
|
cursor: ID!
|
||||||
|
node: Comment!
|
||||||
|
}
|
||||||
|
|
||||||
|
type PageInfo {
|
||||||
|
startCursor: ID!
|
||||||
|
endCursor: ID!
|
||||||
|
hasNextPage: Boolean!
|
||||||
|
}
|
21
graph/schema/query.graphqls
Normal file
21
graph/schema/query.graphqls
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
type Query {
|
||||||
|
post(id: ID!): Post!
|
||||||
|
posts(first: ID!, after: ID!): PostsConnection!
|
||||||
|
comment(id: ID!): Comment!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Post {
|
||||||
|
id: ID!
|
||||||
|
title: String!
|
||||||
|
author: String!
|
||||||
|
contents: String!
|
||||||
|
comments(first: ID!, after: ID!): CommentsConnection!
|
||||||
|
allowComments: Boolean!
|
||||||
|
}
|
||||||
|
|
||||||
|
type Comment {
|
||||||
|
id: ID!
|
||||||
|
author: String!
|
||||||
|
contents: String!
|
||||||
|
replies(first: ID!, after: ID!): CommentsConnection!
|
||||||
|
}
|
|
@ -1,7 +1,12 @@
|
||||||
package db
|
package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
"git.obamna.ru/erius/ozon-task/graph/model"
|
"git.obamna.ru/erius/ozon-task/graph/model"
|
||||||
|
"github.com/99designs/gqlgen/graphql"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -15,48 +20,64 @@ func (s *Database) AddPost(input *model.PostInput) (*model.AddResult, error) {
|
||||||
return &model.AddResult{ItemID: &post.ID}, err
|
return &model.AddResult{ItemID: &post.ID}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Database) AddComment(input *model.CommentInput) (*model.AddResult, error) {
|
func (s *Database) AddReplyToComment(input *model.CommentInput) (*model.AddResult, error) {
|
||||||
comment := model.CommentFromInput(input)
|
comment := model.CommentFromInput(input)
|
||||||
if input.ParentPostID == nil {
|
// multiple operations performed in one transaction
|
||||||
// multiple operations performed in one transaction
|
err := s.db.Transaction(func(tx *gorm.DB) error {
|
||||||
err := s.db.Transaction(func(tx *gorm.DB) error {
|
// insert comment
|
||||||
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
|
err := s.db.Create(comment).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
}
|
// add new reply to parent
|
||||||
return &model.AddResult{ItemID: &comment.ID}, nil
|
err = s.db.Table("comment_replies").Create(map[string]interface{}{
|
||||||
|
"comment_id": *input.ParentCommentID,
|
||||||
|
"reply_id": comment.ID,
|
||||||
|
}).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return &model.AddResult{ItemID: &comment.ID}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Database) GetPosts() ([]*model.Post, error) {
|
func (s *Database) AddCommentToPost(input *model.CommentInput) (*model.AddResult, error) {
|
||||||
var posts []*model.Post
|
comment := model.CommentFromInput(input)
|
||||||
err := s.db.Find(&posts).Error
|
var allowComments bool
|
||||||
return posts, err
|
err := s.db.Table("posts").Select("allow_comments").Where("id = ?", *input.ParentPostID).Scan(&allowComments).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !allowComments {
|
||||||
|
return nil, fmt.Errorf("author disabled comments for this post")
|
||||||
|
}
|
||||||
|
err = s.db.Create(comment).Error
|
||||||
|
return &model.AddResult{ItemID: &comment.ID}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Database) GetPost(id uint, ctx context.Context) (*model.Post, error) {
|
||||||
|
log.Println(graphql.CollectAllFields(ctx))
|
||||||
|
log.Println(graphql.CollectFieldsCtx(ctx, nil))
|
||||||
|
var post model.Post
|
||||||
|
err := s.db.Find(&post, id).Error
|
||||||
|
return &post, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Database) GetComment(id uint, ctx context.Context) (*model.Comment, error) {
|
||||||
|
var comment model.Comment
|
||||||
|
err := s.db.Find(&comment, id).Error
|
||||||
|
return &comment, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Database) GetPosts(first uint, after uint, ctx context.Context) (*model.PostsConnection, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Database) GetComments(post *model.Post, first uint, after uint, ctx context.Context) (*model.CommentsConnection, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Database) GetReplies(comment *model.Comment, first uint, after uint, ctx context.Context) (*model.CommentsConnection, error) {
|
||||||
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"git.obamna.ru/erius/ozon-task/graph/model"
|
"git.obamna.ru/erius/ozon-task/graph/model"
|
||||||
"gorm.io/driver/postgres"
|
"gorm.io/driver/postgres"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -22,7 +23,10 @@ var (
|
||||||
func InitPostgres() (*Database, error) {
|
func InitPostgres() (*Database, error) {
|
||||||
log.Printf("connecting to PostgreSQL database at %s...", con)
|
log.Printf("connecting to PostgreSQL database at %s...", con)
|
||||||
// PrepareStmt is true for caching complex sql statements when adding comments or replies
|
// PrepareStmt is true for caching complex sql statements when adding comments or replies
|
||||||
db, err := gorm.Open(postgres.Open(con), &gorm.Config{PrepareStmt: true})
|
db, err := gorm.Open(postgres.Open(con), &gorm.Config{
|
||||||
|
PrepareStmt: true,
|
||||||
|
Logger: logger.Default.LogMode(logger.Info),
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("failed to connect to database: %s", err)
|
log.Printf("failed to connect to database: %s", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package storage
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"git.obamna.ru/erius/ozon-task/graph/model"
|
"git.obamna.ru/erius/ozon-task/graph/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -22,20 +25,142 @@ func InitInMemory() *InMemory {
|
||||||
|
|
||||||
func (s *InMemory) AddPost(input *model.PostInput) (*model.AddResult, error) {
|
func (s *InMemory) AddPost(input *model.PostInput) (*model.AddResult, error) {
|
||||||
post := model.PostFromInput(input)
|
post := model.PostFromInput(input)
|
||||||
post.ID = s.postId
|
s.insertPost(post)
|
||||||
s.posts = append(s.posts, post)
|
|
||||||
s.postId++
|
|
||||||
return &model.AddResult{ItemID: &post.ID}, nil
|
return &model.AddResult{ItemID: &post.ID}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *InMemory) AddComment(input *model.CommentInput) (*model.AddResult, error) {
|
func (s *InMemory) AddReplyToComment(input *model.CommentInput) (*model.AddResult, error) {
|
||||||
comment := model.CommentFromInput(input)
|
if !s.commentExists(*input.ParentCommentID) {
|
||||||
comment.ID = s.commentId
|
return nil, &IDNotFoundError{objName: "comment", id: *input.ParentCommentID}
|
||||||
s.comments = append(s.comments, comment)
|
}
|
||||||
s.commentId++
|
comment, parent := model.CommentFromInput(input), s.comments[*input.ParentCommentID]
|
||||||
|
s.insertComment(comment)
|
||||||
|
parent.Replies = append(parent.Replies, comment)
|
||||||
return &model.AddResult{ItemID: &comment.ID}, nil
|
return &model.AddResult{ItemID: &comment.ID}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s InMemory) GetPosts() ([]*model.Post, error) {
|
func (s *InMemory) AddCommentToPost(input *model.CommentInput) (*model.AddResult, error) {
|
||||||
return s.posts, nil
|
if !s.postExists(*input.ParentPostID) {
|
||||||
|
return nil, &IDNotFoundError{objName: "post", id: *input.ParentPostID}
|
||||||
|
}
|
||||||
|
parent := s.posts[*input.ParentPostID]
|
||||||
|
if !parent.AllowComments {
|
||||||
|
return nil, fmt.Errorf("author disabled comments for this post")
|
||||||
|
}
|
||||||
|
comment := model.CommentFromInput(input)
|
||||||
|
s.insertComment(comment)
|
||||||
|
parent.Comments = append(parent.Comments, comment)
|
||||||
|
return &model.AddResult{ItemID: &comment.ID}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InMemory) GetPost(id uint, ctx context.Context) (*model.Post, error) {
|
||||||
|
if !s.postExists(id) {
|
||||||
|
return nil, &IDNotFoundError{objName: "post", id: id}
|
||||||
|
}
|
||||||
|
return s.posts[id], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InMemory) GetComment(id uint, ctx context.Context) (*model.Comment, error) {
|
||||||
|
if !s.commentExists(id) {
|
||||||
|
return nil, &IDNotFoundError{objName: "comment", id: id}
|
||||||
|
}
|
||||||
|
return s.comments[id], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InMemory) GetPosts(first uint, after uint, ctx context.Context) (*model.PostsConnection, error) {
|
||||||
|
if !s.postExists(after) {
|
||||||
|
return nil, &IDNotFoundError{objName: "post", id: after}
|
||||||
|
}
|
||||||
|
nextPage, until := true, after+first
|
||||||
|
if !s.postExists(until) {
|
||||||
|
nextPage = false
|
||||||
|
until = uint(len(s.posts))
|
||||||
|
}
|
||||||
|
info, edges := &model.PageInfo{
|
||||||
|
StartCursor: after,
|
||||||
|
EndCursor: until - 1,
|
||||||
|
HasNextPage: nextPage,
|
||||||
|
}, make([]*model.PostsEdge, until-after)
|
||||||
|
for i, p := range s.posts[after:until] {
|
||||||
|
edges[i] = &model.PostsEdge{
|
||||||
|
Cursor: p.ID,
|
||||||
|
Node: p,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &model.PostsConnection{
|
||||||
|
Edges: edges,
|
||||||
|
PageInfo: info,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InMemory) GetComments(post *model.Post, first uint, after uint, ctx context.Context) (*model.CommentsConnection, error) {
|
||||||
|
if !s.commentExists(after) {
|
||||||
|
return nil, &IDNotFoundError{objName: "comment", id: after}
|
||||||
|
}
|
||||||
|
nextPage, until := true, after+first
|
||||||
|
if !s.commentExists(until) {
|
||||||
|
nextPage = false
|
||||||
|
until = uint(len(s.comments))
|
||||||
|
}
|
||||||
|
info, edges := &model.PageInfo{
|
||||||
|
StartCursor: after,
|
||||||
|
EndCursor: until - 1,
|
||||||
|
HasNextPage: nextPage,
|
||||||
|
}, make([]*model.CommentsEdge, until-after)
|
||||||
|
for i, c := range post.Comments[after:until] {
|
||||||
|
edges[i] = &model.CommentsEdge{
|
||||||
|
Cursor: c.ID,
|
||||||
|
Node: c,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &model.CommentsConnection{
|
||||||
|
Edges: edges,
|
||||||
|
PageInfo: info,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InMemory) GetReplies(comment *model.Comment, first uint, after uint, ctx context.Context) (*model.CommentsConnection, error) {
|
||||||
|
if !s.commentExists(after) {
|
||||||
|
return nil, &IDNotFoundError{objName: "comment", id: after}
|
||||||
|
}
|
||||||
|
nextPage, until := true, after+first
|
||||||
|
if !s.commentExists(until) {
|
||||||
|
nextPage = false
|
||||||
|
until = uint(len(s.comments))
|
||||||
|
}
|
||||||
|
info, edges := &model.PageInfo{
|
||||||
|
StartCursor: after,
|
||||||
|
EndCursor: until - 1,
|
||||||
|
HasNextPage: nextPage,
|
||||||
|
}, make([]*model.CommentsEdge, until-after)
|
||||||
|
for i, c := range comment.Replies[after:until] {
|
||||||
|
edges[i] = &model.CommentsEdge{
|
||||||
|
Cursor: c.ID,
|
||||||
|
Node: c,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &model.CommentsConnection{
|
||||||
|
Edges: edges,
|
||||||
|
PageInfo: info,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InMemory) postExists(id uint) bool {
|
||||||
|
return id < uint(len(s.posts))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InMemory) commentExists(id uint) bool {
|
||||||
|
return id < uint(len(s.comments))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InMemory) insertComment(comment *model.Comment) {
|
||||||
|
comment.ID = s.commentId
|
||||||
|
s.comments = append(s.comments, comment)
|
||||||
|
s.commentId++
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *InMemory) insertPost(post *model.Post) {
|
||||||
|
post.ID = s.postId
|
||||||
|
s.posts = append(s.posts, post)
|
||||||
|
s.postId++
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package storage
|
package storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
@ -8,20 +10,47 @@ import (
|
||||||
"git.obamna.ru/erius/ozon-task/internal/storage/db"
|
"git.obamna.ru/erius/ozon-task/internal/storage/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type IDNotFoundError struct {
|
||||||
|
objName string
|
||||||
|
id uint
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *IDNotFoundError) Error() string {
|
||||||
|
return fmt.Sprintf("%s with id %d doesn't exist", e.objName, e.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// storage types enum
|
||||||
const (
|
const (
|
||||||
inMemory = "inmemory"
|
inMemory = "inmemory"
|
||||||
postgres = "postgres"
|
postgres = "postgres"
|
||||||
)
|
)
|
||||||
|
|
||||||
var storage = os.Getenv("APP_STORAGE")
|
var storage, storageSpecified = os.LookupEnv("APP_STORAGE")
|
||||||
|
|
||||||
type Storage interface {
|
type Storage interface {
|
||||||
AddPost(input *model.PostInput) (*model.AddResult, error)
|
AddPost(input *model.PostInput) (*model.AddResult, error)
|
||||||
AddComment(input *model.CommentInput) (*model.AddResult, error)
|
|
||||||
GetPosts() ([]*model.Post, error)
|
// assumes that input.ParentCommentID is not nil
|
||||||
|
AddReplyToComment(input *model.CommentInput) (*model.AddResult, error)
|
||||||
|
|
||||||
|
// assumes that input.ParentPostID is not nil
|
||||||
|
AddCommentToPost(input *model.CommentInput) (*model.AddResult, error)
|
||||||
|
|
||||||
|
// passing query context to analyze requested fields and prevent overfetching
|
||||||
|
GetPost(id uint, ctx context.Context) (*model.Post, error)
|
||||||
|
GetComment(id uint, ctx context.Context) (*model.Comment, error)
|
||||||
|
|
||||||
|
// returns paginated data in the form of model.*Connection (passing context to prevent overfetching)
|
||||||
|
GetPosts(first uint, after uint, ctx context.Context) (*model.PostsConnection, error)
|
||||||
|
GetComments(post *model.Post, first uint, after uint, ctx context.Context) (*model.CommentsConnection, error)
|
||||||
|
GetReplies(comment *model.Comment, first uint, after uint, ctx context.Context) (*model.CommentsConnection, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitStorage() (Storage, error) {
|
func InitStorage() (Storage, error) {
|
||||||
|
if !storageSpecified {
|
||||||
|
log.Println("APP_STORAGE isn't specified, falling back to default in-memory storage", storage)
|
||||||
|
return InitInMemory(), nil
|
||||||
|
}
|
||||||
log.Printf("initializing storage of type %s...", storage)
|
log.Printf("initializing storage of type %s...", storage)
|
||||||
switch storage {
|
switch storage {
|
||||||
case inMemory:
|
case inMemory:
|
||||||
|
@ -29,7 +58,6 @@ func InitStorage() (Storage, error) {
|
||||||
case postgres:
|
case postgres:
|
||||||
return db.InitPostgres()
|
return db.InitPostgres()
|
||||||
default:
|
default:
|
||||||
log.Printf("storage of type %s doesn't exists, falling back to default in-memory storage", storage)
|
return nil, fmt.Errorf("storage of type %s doesn't exists, change the value of APP_STORAGE env variable", storage)
|
||||||
return InitInMemory(), nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue