Skip to content

Commit 42988be

Browse files
authored
Comments (#36)
1 parent a1f09cd commit 42988be

File tree

14 files changed

+644
-60
lines changed

14 files changed

+644
-60
lines changed

backend/cmd/server/main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ func run(stderr io.Writer, args []string) {
3535
os.Exit(1)
3636
}
3737
logger := newLogger(*logLevelFlag, *verboseFlag, stderr)
38+
3839
slog.SetDefault(logger)
3940

4041
ctx := context.Background()

backend/go.mod

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,21 @@ require (
2323
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.9 // indirect
2424
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.9 // indirect
2525
github.com/aws/smithy-go v1.22.1 // indirect
26+
github.com/gabriel-vasile/mimetype v1.4.8 // indirect
27+
github.com/go-playground/locales v0.14.1 // indirect
28+
github.com/go-playground/universal-translator v0.18.1 // indirect
2629
github.com/kr/pretty v0.3.1 // indirect
30+
github.com/leodido/go-urn v1.4.0 // indirect
2731
github.com/rogpeppe/go-internal v1.11.0 // indirect
32+
golang.org/x/net v0.34.0 // indirect
2833
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
2934
)
3035

3136
require (
3237
github.com/andybalholm/brotli v1.1.1 // indirect
3338
github.com/caarlos0/env/v11 v11.3.1
3439
github.com/davecgh/go-spew v1.1.1 // indirect
40+
github.com/go-playground/validator/v10 v10.24.0
3541
github.com/golang/snappy v0.0.4 // indirect
3642
github.com/google/uuid v1.6.0
3743
github.com/klauspost/compress v1.17.11 // indirect
@@ -48,9 +54,9 @@ require (
4854
github.com/xdg-go/scram v1.1.2 // indirect
4955
github.com/xdg-go/stringprep v1.0.4 // indirect
5056
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
51-
golang.org/x/crypto v0.29.0 // indirect
52-
golang.org/x/sync v0.9.0
53-
golang.org/x/sys v0.28.0 // indirect
54-
golang.org/x/text v0.20.0 // indirect
57+
golang.org/x/crypto v0.32.0 // indirect
58+
golang.org/x/sync v0.10.0
59+
golang.org/x/sys v0.29.0 // indirect
60+
golang.org/x/text v0.21.0 // indirect
5561
gopkg.in/yaml.v3 v3.0.1 // indirect
5662
)

backend/go.sum

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,16 @@ github.com/caarlos0/env/v11 v11.3.1/go.mod h1:qupehSf/Y0TUTsxKywqRt/vJjN5nz6vaui
2929
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
3030
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3131
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
32+
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
33+
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
34+
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
35+
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
36+
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
37+
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
38+
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
39+
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
40+
github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg=
41+
github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
3242
github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM=
3343
github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
3444
github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI=
@@ -50,6 +60,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
5060
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
5161
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
5262
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
63+
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
64+
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
5365
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
5466
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
5567
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
@@ -91,33 +103,35 @@ go.mongodb.org/mongo-driver v1.17.1 h1:Wic5cJIwJgSpBhe3lx3+/RybR5PiYRMpVFgO7cOHy
91103
go.mongodb.org/mongo-driver v1.17.1/go.mod h1:wwWm/+BuOddhcq3n68LKRmgk2wXzmF6s0SFOa0GINL4=
92104
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
93105
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
94-
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
95-
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
106+
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
107+
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
96108
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
97109
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
98110
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
99111
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
112+
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
113+
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
100114
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
101115
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
102-
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
103-
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
116+
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
117+
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
104118
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
105119
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
106120
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
107121
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
108122
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
109123
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
110124
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
111-
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
112-
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
125+
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
126+
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
113127
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
114128
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
115129
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
116130
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
117131
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
118132
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
119-
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
120-
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
133+
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
134+
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
121135
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
122136
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
123137
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=

backend/internal/handlers/review/review.go

Lines changed: 83 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ package review
22

33
import (
44
"errors"
5+
"time"
56

67
"github.com/GenerateNU/platemate/internal/xerr"
8+
"github.com/GenerateNU/platemate/internal/xvalidator"
79
go_json "github.com/goccy/go-json"
810
"github.com/gofiber/fiber/v2"
911
"go.mongodb.org/mongo-driver/bson/primitive"
@@ -20,15 +22,34 @@ type Handler struct {
2022
// Create a review
2123
func (h *Handler) CreateReview(c *fiber.Ctx) error {
2224
var review ReviewDocument
23-
if err := go_json.Unmarshal(c.Body(), &review); err != nil {
24-
return c.Status(fiber.StatusBadRequest).JSON(xerr.BadRequest(err))
25+
var params CreateReviewParams
26+
27+
if err := go_json.Unmarshal(c.Body(), &params); err != nil {
28+
return c.Status(fiber.StatusBadRequest).JSON(xerr.ErrorHandler(c, err))
29+
}
30+
31+
// do some validations on the inputs
32+
33+
review = ReviewDocument{
34+
Rating: params.Rating,
35+
Picture: params.Picture,
36+
Content: params.Content,
37+
Reviewer: params.Reviewer,
38+
Timestamp: time.Now(),
39+
MenuItem: params.MenuItem,
40+
ID: primitive.NewObjectID(),
41+
Comments: []CommentDocument{},
2542
}
2643

2744
result, err := h.service.InsertReview(review)
45+
2846
if err != nil {
29-
// Central error handler take 500
30-
return err
47+
sErr := err.(mongo.WriteException) // Convert to Command Error
48+
if sErr.HasErrorCode(121) { // Indicates that the document failed validation
49+
return xerr.WriteException(c, sErr) // Handle the error by returning a 121 and the error message
50+
}
3151
}
52+
3253
return c.JSON(result)
3354
}
3455

@@ -76,8 +97,7 @@ func (h *Handler) UpdateReview(c *fiber.Ctx) error {
7697

7798
err = h.service.UpdateReview(id, review)
7899
if err != nil {
79-
// Central error handler take 500
80-
return err
100+
return c.Status(fiber.StatusBadRequest).JSON(xerr.ErrorHandler(c, err))
81101
}
82102
return c.SendStatus(fiber.StatusOK)
83103
}
@@ -115,3 +135,60 @@ func (h *Handler) DeleteReview(c *fiber.Ctx) error {
115135
}
116136
return c.SendStatus(fiber.StatusNoContent)
117137
}
138+
139+
func (h *Handler) CreateComment(c *fiber.Ctx) error {
140+
var comment CommentDocument
141+
142+
reqInputs := CreateCommentParams{}
143+
err := c.BodyParser(&reqInputs)
144+
if err != nil {
145+
return c.Status(fiber.StatusBadRequest).JSON(xerr.InvalidJSON())
146+
}
147+
148+
errs := xvalidator.Validator.Validate(reqInputs)
149+
if len(errs) > 0 {
150+
return c.Status(fiber.StatusBadRequest).JSON(errs)
151+
}
152+
153+
id, err := primitive.ObjectIDFromHex(reqInputs.Review)
154+
if err != nil {
155+
return c.Status(fiber.StatusBadRequest).JSON(xerr.BadRequest(err))
156+
}
157+
comment = CommentDocument{
158+
Content: reqInputs.Content,
159+
User: Commenter{
160+
ID: reqInputs.User.ID,
161+
PFP: reqInputs.User.PFP,
162+
Username: reqInputs.User.Username,
163+
},
164+
Mention: reqInputs.Mentions,
165+
Timestamp: time.Now(),
166+
Review: id,
167+
ID: primitive.NewObjectID(),
168+
}
169+
170+
err = h.service.CreateComment(comment) // Insert operation
171+
172+
if err != nil {
173+
sErr := err.(mongo.CommandError) // Convert to Command Error
174+
if sErr.HasErrorCode(121) { // Indicates that the document failed validation
175+
return xerr.FailedValidation(c, sErr) // Handle the error by returning a 121 and the error message
176+
}
177+
}
178+
179+
return c.SendStatus(fiber.StatusOK)
180+
}
181+
182+
func (h *Handler) GetComments(c *fiber.Ctx) error {
183+
id, err := primitive.ObjectIDFromHex(c.Params("id"))
184+
if err != nil {
185+
return c.Status(fiber.StatusBadRequest).JSON(xerr.BadRequest(err))
186+
}
187+
188+
comments, err := h.service.GetComments(id)
189+
if err != nil {
190+
// Central error handler take 500
191+
return err
192+
}
193+
return c.JSON(comments)
194+
}

backend/internal/handlers/review/routes.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,6 @@ func Routes(app *fiber.App, collections map[string]*mongo.Collection) {
2424
review.Put("/:id", handler.UpdateReview)
2525
review.Patch("/:id", handler.UpdatePartialReview)
2626
review.Delete("/:id", handler.DeleteReview)
27+
review.Get("/:id/comments", handler.GetComments)
28+
review.Post("/:id/comments", handler.CreateComment)
2729
}

backend/internal/handlers/review/service.go

Lines changed: 53 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,48 +2,12 @@ package review
22

33
import (
44
"context"
5-
"time"
65

76
"go.mongodb.org/mongo-driver/bson"
87
"go.mongodb.org/mongo-driver/bson/primitive"
98
"go.mongodb.org/mongo-driver/mongo"
109
)
1110

12-
type ReviewDocument struct {
13-
ID primitive.ObjectID `bson:"_id,omitempty" json:"_id,omitempty"`
14-
Rating Rating `bson:"rating" json:"rating"`
15-
Picture string `bson:"picture" json:"picture"`
16-
Content string `bson:"content" json:"content"`
17-
Reviewer Reviewer `bson:"reviewer" json:"reviewer"`
18-
Timestamp time.Time `bson:"timestamp" json:"timestamp"`
19-
Comments []map[string]interface{} `bson:"comments,omitempty" json:"comments,omitempty"`
20-
MenuItem string `bson:"menuItem" json:"menuItem"`
21-
}
22-
23-
// Rating is a nested struct in ReviewDocument.
24-
type Rating struct {
25-
Portion int `bson:"portion" json:"portion"`
26-
Taste int `bson:"taste" json:"taste"`
27-
Value int `bson:"value" json:"value"`
28-
Overall int `bson:"overall" json:"overall"`
29-
Return bool `bson:"return" json:"return"`
30-
}
31-
32-
// Reviewer is a nested struct in ReviewDocument.
33-
type Reviewer struct {
34-
ID string `bson:"id" json:"id"`
35-
PFP string `bson:"pfp" json:"pfp"`
36-
Username string `bson:"username" json:"username"`
37-
}
38-
39-
/*
40-
Review Service to be used by Review Handler to interact with the
41-
Database layer of the application
42-
*/
43-
type Service struct {
44-
reviews *mongo.Collection
45-
}
46-
4711
// newService receives the map of collections and picks out reviews
4812
func newService(collections map[string]*mongo.Collection) *Service {
4913
return &Service{
@@ -179,3 +143,56 @@ func (s *Service) DeleteReview(id primitive.ObjectID) error {
179143
_, err := s.reviews.DeleteOne(ctx, filter)
180144
return err
181145
}
146+
147+
// CreateComment adds a new comment to a review
148+
func (s *Service) CreateComment(comment CommentDocument) error {
149+
ctx := context.Background()
150+
filter := bson.M{"_id": comment.Review}
151+
update := bson.M{"$push": bson.M{"comments": comment}}
152+
res := s.reviews.FindOneAndUpdate(ctx, filter, update)
153+
154+
return res.Err()
155+
}
156+
157+
/***
158+
*
159+
* GetComments returns all comments for a review
160+
* Sorted by the most recent timestamp
161+
*
162+
*/
163+
164+
func (s *Service) GetComments(reviewID primitive.ObjectID) ([]CommentDocument, error) {
165+
ctx := context.Background()
166+
filter := bson.M{"_id": reviewID}
167+
pipeline := []bson.M{
168+
bson.M{
169+
"$match": filter,
170+
},
171+
bson.M{
172+
"$project": bson.M{"comments": 1, "_id": 0},
173+
},
174+
bson.M{"$unwind": "$comments"},
175+
bson.M{
176+
"$sort": bson.M{
177+
"comments.timestamp": -1,
178+
},
179+
},
180+
}
181+
182+
cursor, err := s.reviews.Aggregate(ctx, pipeline)
183+
if err != nil {
184+
return nil, err
185+
}
186+
defer cursor.Close(ctx)
187+
var res []CommentPipelineEntry
188+
if err := cursor.All(ctx, &res); err != nil {
189+
return nil, err
190+
}
191+
192+
var comments = make([]CommentDocument, 0, len(res))
193+
for _, entry := range res {
194+
comments = append(comments, entry.Comments)
195+
}
196+
197+
return comments, err
198+
}

0 commit comments

Comments
 (0)