From ed403c2c720c38cb06db36860697bab32479daf9 Mon Sep 17 00:00:00 2001 From: aspasskiy Date: Tue, 23 Apr 2024 19:13:48 +0300 Subject: [PATCH] upvotes --- api/gerald.yaml | 14 +++++- internal/interfaces/rest/handlers/feedback.go | 14 +++++- internal/interfaces/rest/models/feedback.go | 6 +++ .../interfaces/rest/restapi/embedded_spec.go | 36 ++++++++++++++- .../operations/feedback/upvote_feedback.go | 2 +- .../feedback/upvote_feedback_parameters.go | 45 +++++++++++++++++++ .../feedback/upvote_feedback_urlbuilder.go | 8 +++- .../rest/restapi/operations/gerald_api.go | 2 +- internal/interfaces/rest/serve.go | 2 +- internal/services/feedback.go | 26 ++++++++--- repo/pg/feedback.go | 9 +++- 11 files changed, 147 insertions(+), 17 deletions(-) diff --git a/api/gerald.yaml b/api/gerald.yaml index 74028c1..d6ece64 100644 --- a/api/gerald.yaml +++ b/api/gerald.yaml @@ -36,7 +36,7 @@ paths: 403: description: "Forbidden" - /feedback/{feedback_uuid}/upvote: + /feedback/{feedback_uuid}/vote: post: summary: "Upvote a feedback" tags: @@ -54,12 +54,20 @@ paths: description: "Session UUID" required: true type: string + - in: query + type: string + name: action + enum: + - "upvote" + - "downvote" + required: true responses: 200: description: "successful operation" 403: description: "Forbidden" + /feedbacks: get: summary: "Get all feedbacks" @@ -128,6 +136,10 @@ definitions: - support text: type: string + upvoted: + type: boolean + upvotes: + type: integer created_at: type: string format: date-time \ No newline at end of file diff --git a/internal/interfaces/rest/handlers/feedback.go b/internal/interfaces/rest/handlers/feedback.go index c59e4e6..0f594eb 100644 --- a/internal/interfaces/rest/handlers/feedback.go +++ b/internal/interfaces/rest/handlers/feedback.go @@ -49,8 +49,8 @@ func (h *FeedbackHandler) GetFeedback(params feedback.GetFeedbacksForProjectPara } -func (h *FeedbackHandler) UpvoteFeedback(params feedback.UpvoteFeedbackParams) middleware.Responder { - err := h.feedback.Upvote(params.SessionUUID, params.FeedbackUUID) +func (h *FeedbackHandler) VoteFeedback(params feedback.UpvoteFeedbackParams) middleware.Responder { + err := h.feedback.Vote(params.SessionUUID, params.FeedbackUUID, params.Action) if err != nil { return feedback.NewUpvoteFeedbackForbidden() } @@ -77,6 +77,14 @@ func feedbacksToAPI(feedbacks []*dmodels.Feedback) []*models.Feedback { } func feedbackToAPI(feedback *dmodels.Feedback) *models.Feedback { + var upvoted bool + for _, upvote := range feedback.Upvote { + if upvote.SessionUUID == feedback.SessionUUID { + upvoted = true + break + } + } + return &models.Feedback{ FeedbackUUID: feedback.UUID, SessionUUID: feedback.SessionUUID, @@ -84,6 +92,8 @@ func feedbackToAPI(feedback *dmodels.Feedback) *models.Feedback { Text: feedback.Text, Type: string(feedback.Type), UserID: feedback.UserID, + Upvotes: int64(len(feedback.Upvote)), + Upvoted: upvoted, CreatedAt: strfmt.DateTime(feedback.CreatedAt), } } diff --git a/internal/interfaces/rest/models/feedback.go b/internal/interfaces/rest/models/feedback.go index 27b86d6..704582e 100644 --- a/internal/interfaces/rest/models/feedback.go +++ b/internal/interfaces/rest/models/feedback.go @@ -40,6 +40,12 @@ type Feedback struct { // Enum: [feature feedback support] Type string `json:"type,omitempty"` + // upvoted + Upvoted bool `json:"upvoted,omitempty"` + + // upvotes + Upvotes int64 `json:"upvotes,omitempty"` + // to have more context about the user UserID string `json:"user_id,omitempty"` } diff --git a/internal/interfaces/rest/restapi/embedded_spec.go b/internal/interfaces/rest/restapi/embedded_spec.go index 390a34d..a516f35 100644 --- a/internal/interfaces/rest/restapi/embedded_spec.go +++ b/internal/interfaces/rest/restapi/embedded_spec.go @@ -68,7 +68,7 @@ func init() { } } }, - "/feedback/{feedback_uuid}/upvote": { + "/feedback/{feedback_uuid}/vote": { "post": { "description": "Upvote a feedback", "tags": [ @@ -90,6 +90,16 @@ func init() { "name": "session_uuid", "in": "query", "required": true + }, + { + "enum": [ + "upvote", + "downvote" + ], + "type": "string", + "name": "action", + "in": "query", + "required": true } ], "responses": { @@ -198,6 +208,12 @@ func init() { "support" ] }, + "upvoted": { + "type": "boolean" + }, + "upvotes": { + "type": "integer" + }, "user_id": { "description": "to have more context about the user", "type": "string" @@ -257,7 +273,7 @@ func init() { } } }, - "/feedback/{feedback_uuid}/upvote": { + "/feedback/{feedback_uuid}/vote": { "post": { "description": "Upvote a feedback", "tags": [ @@ -279,6 +295,16 @@ func init() { "name": "session_uuid", "in": "query", "required": true + }, + { + "enum": [ + "upvote", + "downvote" + ], + "type": "string", + "name": "action", + "in": "query", + "required": true } ], "responses": { @@ -387,6 +413,12 @@ func init() { "support" ] }, + "upvoted": { + "type": "boolean" + }, + "upvotes": { + "type": "integer" + }, "user_id": { "description": "to have more context about the user", "type": "string" diff --git a/internal/interfaces/rest/restapi/operations/feedback/upvote_feedback.go b/internal/interfaces/rest/restapi/operations/feedback/upvote_feedback.go index 6d29505..fd393d5 100644 --- a/internal/interfaces/rest/restapi/operations/feedback/upvote_feedback.go +++ b/internal/interfaces/rest/restapi/operations/feedback/upvote_feedback.go @@ -30,7 +30,7 @@ func NewUpvoteFeedback(ctx *middleware.Context, handler UpvoteFeedbackHandler) * } /* - UpvoteFeedback swagger:route POST /feedback/{feedback_uuid}/upvote feedback upvoteFeedback + UpvoteFeedback swagger:route POST /feedback/{feedback_uuid}/vote feedback upvoteFeedback # Upvote a feedback diff --git a/internal/interfaces/rest/restapi/operations/feedback/upvote_feedback_parameters.go b/internal/interfaces/rest/restapi/operations/feedback/upvote_feedback_parameters.go index 3f6eb51..87a727c 100644 --- a/internal/interfaces/rest/restapi/operations/feedback/upvote_feedback_parameters.go +++ b/internal/interfaces/rest/restapi/operations/feedback/upvote_feedback_parameters.go @@ -32,6 +32,11 @@ type UpvoteFeedbackParams struct { // HTTP Request Object HTTPRequest *http.Request `json:"-"` + /* + Required: true + In: query + */ + Action string /*Feedback UUID Required: true In: path @@ -55,6 +60,11 @@ func (o *UpvoteFeedbackParams) BindRequest(r *http.Request, route *middleware.Ma qs := runtime.Values(r.URL.Query()) + qAction, qhkAction, _ := qs.GetOK("action") + if err := o.bindAction(qAction, qhkAction, route.Formats); err != nil { + res = append(res, err) + } + rFeedbackUUID, rhkFeedbackUUID, _ := route.Params.GetOK("feedback_uuid") if err := o.bindFeedbackUUID(rFeedbackUUID, rhkFeedbackUUID, route.Formats); err != nil { res = append(res, err) @@ -70,6 +80,41 @@ func (o *UpvoteFeedbackParams) BindRequest(r *http.Request, route *middleware.Ma return nil } +// bindAction binds and validates parameter Action from query. +func (o *UpvoteFeedbackParams) bindAction(rawData []string, hasKey bool, formats strfmt.Registry) error { + if !hasKey { + return errors.Required("action", "query", rawData) + } + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // AllowEmptyValue: false + + if err := validate.RequiredString("action", "query", raw); err != nil { + return err + } + o.Action = raw + + if err := o.validateAction(formats); err != nil { + return err + } + + return nil +} + +// validateAction carries on validations for parameter Action +func (o *UpvoteFeedbackParams) validateAction(formats strfmt.Registry) error { + + if err := validate.EnumCase("action", "query", o.Action, []interface{}{"upvote", "downvote"}, true); err != nil { + return err + } + + return nil +} + // bindFeedbackUUID binds and validates parameter FeedbackUUID from path. func (o *UpvoteFeedbackParams) bindFeedbackUUID(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string diff --git a/internal/interfaces/rest/restapi/operations/feedback/upvote_feedback_urlbuilder.go b/internal/interfaces/rest/restapi/operations/feedback/upvote_feedback_urlbuilder.go index c4def22..58cf32f 100644 --- a/internal/interfaces/rest/restapi/operations/feedback/upvote_feedback_urlbuilder.go +++ b/internal/interfaces/rest/restapi/operations/feedback/upvote_feedback_urlbuilder.go @@ -16,6 +16,7 @@ import ( type UpvoteFeedbackURL struct { FeedbackUUID string + Action string SessionUUID string _basePath string @@ -42,7 +43,7 @@ func (o *UpvoteFeedbackURL) SetBasePath(bp string) { func (o *UpvoteFeedbackURL) Build() (*url.URL, error) { var _result url.URL - var _path = "/feedback/{feedback_uuid}/upvote" + var _path = "/feedback/{feedback_uuid}/vote" feedbackUUID := o.FeedbackUUID if feedbackUUID != "" { @@ -59,6 +60,11 @@ func (o *UpvoteFeedbackURL) Build() (*url.URL, error) { qs := make(url.Values) + actionQ := o.Action + if actionQ != "" { + qs.Set("action", actionQ) + } + sessionUUIDQ := o.SessionUUID if sessionUUIDQ != "" { qs.Set("session_uuid", sessionUUIDQ) diff --git a/internal/interfaces/rest/restapi/operations/gerald_api.go b/internal/interfaces/rest/restapi/operations/gerald_api.go index b7c7d7c..493f75c 100644 --- a/internal/interfaces/rest/restapi/operations/gerald_api.go +++ b/internal/interfaces/rest/restapi/operations/gerald_api.go @@ -292,7 +292,7 @@ func (o *GeraldAPI) initHandlerCache() { if o.handlers["POST"] == nil { o.handlers["POST"] = make(map[string]http.Handler) } - o.handlers["POST"]["/feedback/{feedback_uuid}/upvote"] = feedback.NewUpvoteFeedback(o.context, o.FeedbackUpvoteFeedbackHandler) + o.handlers["POST"]["/feedback/{feedback_uuid}/vote"] = feedback.NewUpvoteFeedback(o.context, o.FeedbackUpvoteFeedbackHandler) } // Serve creates a http handler to serve the API over HTTP diff --git a/internal/interfaces/rest/serve.go b/internal/interfaces/rest/serve.go index 4c7b25e..3fa6957 100644 --- a/internal/interfaces/rest/serve.go +++ b/internal/interfaces/rest/serve.go @@ -50,7 +50,7 @@ func StartHTTPServer(s *Server, lifecycle fx.Lifecycle) { // Feedback api.FeedbackCreateFeedbackHandler = feedback.CreateFeedbackHandlerFunc(s.feedback.CreateFeedback) api.FeedbackGetFeedbacksHandler = feedback.GetFeedbacksHandlerFunc(s.feedback.GetFeedbacks) - api.FeedbackUpvoteFeedbackHandler = feedback.UpvoteFeedbackHandlerFunc(s.feedback.UpvoteFeedback) + api.FeedbackUpvoteFeedbackHandler = feedback.UpvoteFeedbackHandlerFunc(s.feedback.VoteFeedback) api.FeedbackGetFeedbacksForProjectHandler = feedback.GetFeedbacksForProjectHandlerFunc(s.feedback.GetFeedbacksForProject) // Add global middleware here diff --git a/internal/services/feedback.go b/internal/services/feedback.go index bdc108f..234fa81 100644 --- a/internal/services/feedback.go +++ b/internal/services/feedback.go @@ -36,7 +36,7 @@ func (f *FeedbackService) GetByProjectID(projectID string) ([]*models.Feedback, return f.db.Feedback.GetByProjectID(projectID) } -func (f *FeedbackService) Upvote(sessionID, feedbackUUID string) error { +func (f *FeedbackService) Vote(sessionID, feedbackUUID, action string) error { feedback, err := f.db.Feedback.GetByUUID(feedbackUUID) if err != nil { return err @@ -44,15 +44,27 @@ func (f *FeedbackService) Upvote(sessionID, feedbackUUID string) error { // check if project type is feature if feedback.Type != models.FeedbackTypeFeature { - return errors.New("only feature tickets can be upvoted") + return errors.New("only feature tickets can be voted") } - // check if user has already upvoted - for _, upvote := range feedback.Upvote { - if upvote.SessionUUID == sessionID { - return errors.New("user has already upvoted") + switch action { + case "upvote": + // check if user has already upvoted + for _, upvote := range feedback.Upvote { + if upvote.SessionUUID == sessionID { + return errors.New("user has already upvoted") + } + } + return f.db.Feedback.CreateUpvote(models.NewUpvote(sessionID, feedbackUUID)) + + case "downvote": + // check if user has already upvoted + for _, upvote := range feedback.Upvote { + if upvote.SessionUUID == sessionID { + return f.db.Feedback.DeleteUpvoteBySessionIDAndFeedbackUUID(sessionID, feedbackUUID) + } } } - return f.db.Feedback.CreateUpvote(models.NewUpvote(sessionID, feedbackUUID)) + return nil } diff --git a/repo/pg/feedback.go b/repo/pg/feedback.go index 4758dd5..6360b61 100644 --- a/repo/pg/feedback.go +++ b/repo/pg/feedback.go @@ -36,7 +36,7 @@ func (t *Feedback) GetByUUID(uuid string) (*models.Feedback, error) { var ticket models.Feedback res := t.db. - Preload("Upvote"). + Preload("Vote"). Where("uuid = ?", uuid). First(&ticket) if res.Error != nil { @@ -63,3 +63,10 @@ func (t *Feedback) GetByProjectID(projectID string) ([]*models.Feedback, error) func (t *Feedback) CreateUpvote(upvote *models.Upvote) error { return t.db.Create(upvote).Error } + +func (t *Feedback) DeleteUpvoteBySessionIDAndFeedbackUUID(sessionID, feedbackUUID string) error { + return t.db. + Where("session_uuid = ?", sessionID). + Where("feedback_uuid = ?", feedbackUUID). + Delete(&models.Upvote{}).Error +}