upvotes
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/promote/production Build is passing

This commit is contained in:
2024-04-23 19:13:48 +03:00
parent a1df10c37e
commit ed403c2c72
11 changed files with 147 additions and 17 deletions

View File

@@ -36,7 +36,7 @@ paths:
403: 403:
description: "Forbidden" description: "Forbidden"
/feedback/{feedback_uuid}/upvote: /feedback/{feedback_uuid}/vote:
post: post:
summary: "Upvote a feedback" summary: "Upvote a feedback"
tags: tags:
@@ -54,12 +54,20 @@ paths:
description: "Session UUID" description: "Session UUID"
required: true required: true
type: string type: string
- in: query
type: string
name: action
enum:
- "upvote"
- "downvote"
required: true
responses: responses:
200: 200:
description: "successful operation" description: "successful operation"
403: 403:
description: "Forbidden" description: "Forbidden"
/feedbacks: /feedbacks:
get: get:
summary: "Get all feedbacks" summary: "Get all feedbacks"
@@ -128,6 +136,10 @@ definitions:
- support - support
text: text:
type: string type: string
upvoted:
type: boolean
upvotes:
type: integer
created_at: created_at:
type: string type: string
format: date-time format: date-time

View File

@@ -49,8 +49,8 @@ func (h *FeedbackHandler) GetFeedback(params feedback.GetFeedbacksForProjectPara
} }
func (h *FeedbackHandler) UpvoteFeedback(params feedback.UpvoteFeedbackParams) middleware.Responder { func (h *FeedbackHandler) VoteFeedback(params feedback.UpvoteFeedbackParams) middleware.Responder {
err := h.feedback.Upvote(params.SessionUUID, params.FeedbackUUID) err := h.feedback.Vote(params.SessionUUID, params.FeedbackUUID, params.Action)
if err != nil { if err != nil {
return feedback.NewUpvoteFeedbackForbidden() return feedback.NewUpvoteFeedbackForbidden()
} }
@@ -77,6 +77,14 @@ func feedbacksToAPI(feedbacks []*dmodels.Feedback) []*models.Feedback {
} }
func feedbackToAPI(feedback *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{ return &models.Feedback{
FeedbackUUID: feedback.UUID, FeedbackUUID: feedback.UUID,
SessionUUID: feedback.SessionUUID, SessionUUID: feedback.SessionUUID,
@@ -84,6 +92,8 @@ func feedbackToAPI(feedback *dmodels.Feedback) *models.Feedback {
Text: feedback.Text, Text: feedback.Text,
Type: string(feedback.Type), Type: string(feedback.Type),
UserID: feedback.UserID, UserID: feedback.UserID,
Upvotes: int64(len(feedback.Upvote)),
Upvoted: upvoted,
CreatedAt: strfmt.DateTime(feedback.CreatedAt), CreatedAt: strfmt.DateTime(feedback.CreatedAt),
} }
} }

View File

@@ -40,6 +40,12 @@ type Feedback struct {
// Enum: [feature feedback support] // Enum: [feature feedback support]
Type string `json:"type,omitempty"` Type string `json:"type,omitempty"`
// upvoted
Upvoted bool `json:"upvoted,omitempty"`
// upvotes
Upvotes int64 `json:"upvotes,omitempty"`
// to have more context about the user // to have more context about the user
UserID string `json:"user_id,omitempty"` UserID string `json:"user_id,omitempty"`
} }

View File

@@ -68,7 +68,7 @@ func init() {
} }
} }
}, },
"/feedback/{feedback_uuid}/upvote": { "/feedback/{feedback_uuid}/vote": {
"post": { "post": {
"description": "Upvote a feedback", "description": "Upvote a feedback",
"tags": [ "tags": [
@@ -90,6 +90,16 @@ func init() {
"name": "session_uuid", "name": "session_uuid",
"in": "query", "in": "query",
"required": true "required": true
},
{
"enum": [
"upvote",
"downvote"
],
"type": "string",
"name": "action",
"in": "query",
"required": true
} }
], ],
"responses": { "responses": {
@@ -198,6 +208,12 @@ func init() {
"support" "support"
] ]
}, },
"upvoted": {
"type": "boolean"
},
"upvotes": {
"type": "integer"
},
"user_id": { "user_id": {
"description": "to have more context about the user", "description": "to have more context about the user",
"type": "string" "type": "string"
@@ -257,7 +273,7 @@ func init() {
} }
} }
}, },
"/feedback/{feedback_uuid}/upvote": { "/feedback/{feedback_uuid}/vote": {
"post": { "post": {
"description": "Upvote a feedback", "description": "Upvote a feedback",
"tags": [ "tags": [
@@ -279,6 +295,16 @@ func init() {
"name": "session_uuid", "name": "session_uuid",
"in": "query", "in": "query",
"required": true "required": true
},
{
"enum": [
"upvote",
"downvote"
],
"type": "string",
"name": "action",
"in": "query",
"required": true
} }
], ],
"responses": { "responses": {
@@ -387,6 +413,12 @@ func init() {
"support" "support"
] ]
}, },
"upvoted": {
"type": "boolean"
},
"upvotes": {
"type": "integer"
},
"user_id": { "user_id": {
"description": "to have more context about the user", "description": "to have more context about the user",
"type": "string" "type": "string"

View File

@@ -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 # Upvote a feedback

View File

@@ -32,6 +32,11 @@ type UpvoteFeedbackParams struct {
// HTTP Request Object // HTTP Request Object
HTTPRequest *http.Request `json:"-"` HTTPRequest *http.Request `json:"-"`
/*
Required: true
In: query
*/
Action string
/*Feedback UUID /*Feedback UUID
Required: true Required: true
In: path In: path
@@ -55,6 +60,11 @@ func (o *UpvoteFeedbackParams) BindRequest(r *http.Request, route *middleware.Ma
qs := runtime.Values(r.URL.Query()) 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") rFeedbackUUID, rhkFeedbackUUID, _ := route.Params.GetOK("feedback_uuid")
if err := o.bindFeedbackUUID(rFeedbackUUID, rhkFeedbackUUID, route.Formats); err != nil { if err := o.bindFeedbackUUID(rFeedbackUUID, rhkFeedbackUUID, route.Formats); err != nil {
res = append(res, err) res = append(res, err)
@@ -70,6 +80,41 @@ func (o *UpvoteFeedbackParams) BindRequest(r *http.Request, route *middleware.Ma
return nil 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. // bindFeedbackUUID binds and validates parameter FeedbackUUID from path.
func (o *UpvoteFeedbackParams) bindFeedbackUUID(rawData []string, hasKey bool, formats strfmt.Registry) error { func (o *UpvoteFeedbackParams) bindFeedbackUUID(rawData []string, hasKey bool, formats strfmt.Registry) error {
var raw string var raw string

View File

@@ -16,6 +16,7 @@ import (
type UpvoteFeedbackURL struct { type UpvoteFeedbackURL struct {
FeedbackUUID string FeedbackUUID string
Action string
SessionUUID string SessionUUID string
_basePath string _basePath string
@@ -42,7 +43,7 @@ func (o *UpvoteFeedbackURL) SetBasePath(bp string) {
func (o *UpvoteFeedbackURL) Build() (*url.URL, error) { func (o *UpvoteFeedbackURL) Build() (*url.URL, error) {
var _result url.URL var _result url.URL
var _path = "/feedback/{feedback_uuid}/upvote" var _path = "/feedback/{feedback_uuid}/vote"
feedbackUUID := o.FeedbackUUID feedbackUUID := o.FeedbackUUID
if feedbackUUID != "" { if feedbackUUID != "" {
@@ -59,6 +60,11 @@ func (o *UpvoteFeedbackURL) Build() (*url.URL, error) {
qs := make(url.Values) qs := make(url.Values)
actionQ := o.Action
if actionQ != "" {
qs.Set("action", actionQ)
}
sessionUUIDQ := o.SessionUUID sessionUUIDQ := o.SessionUUID
if sessionUUIDQ != "" { if sessionUUIDQ != "" {
qs.Set("session_uuid", sessionUUIDQ) qs.Set("session_uuid", sessionUUIDQ)

View File

@@ -292,7 +292,7 @@ func (o *GeraldAPI) initHandlerCache() {
if o.handlers["POST"] == nil { if o.handlers["POST"] == nil {
o.handlers["POST"] = make(map[string]http.Handler) 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 // Serve creates a http handler to serve the API over HTTP

View File

@@ -50,7 +50,7 @@ func StartHTTPServer(s *Server, lifecycle fx.Lifecycle) {
// Feedback // Feedback
api.FeedbackCreateFeedbackHandler = feedback.CreateFeedbackHandlerFunc(s.feedback.CreateFeedback) api.FeedbackCreateFeedbackHandler = feedback.CreateFeedbackHandlerFunc(s.feedback.CreateFeedback)
api.FeedbackGetFeedbacksHandler = feedback.GetFeedbacksHandlerFunc(s.feedback.GetFeedbacks) 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) api.FeedbackGetFeedbacksForProjectHandler = feedback.GetFeedbacksForProjectHandlerFunc(s.feedback.GetFeedbacksForProject)
// Add global middleware here // Add global middleware here

View File

@@ -36,7 +36,7 @@ func (f *FeedbackService) GetByProjectID(projectID string) ([]*models.Feedback,
return f.db.Feedback.GetByProjectID(projectID) 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) feedback, err := f.db.Feedback.GetByUUID(feedbackUUID)
if err != nil { if err != nil {
return err return err
@@ -44,15 +44,27 @@ func (f *FeedbackService) Upvote(sessionID, feedbackUUID string) error {
// check if project type is feature // check if project type is feature
if feedback.Type != models.FeedbackTypeFeature { 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 switch action {
for _, upvote := range feedback.Upvote { case "upvote":
if upvote.SessionUUID == sessionID { // check if user has already upvoted
return errors.New("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
} }

View File

@@ -36,7 +36,7 @@ func (t *Feedback) GetByUUID(uuid string) (*models.Feedback, error) {
var ticket models.Feedback var ticket models.Feedback
res := t.db. res := t.db.
Preload("Upvote"). Preload("Vote").
Where("uuid = ?", uuid). Where("uuid = ?", uuid).
First(&ticket) First(&ticket)
if res.Error != nil { 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 { func (t *Feedback) CreateUpvote(upvote *models.Upvote) error {
return t.db.Create(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
}