diff --git a/api/gerald.yaml b/api/gerald.yaml index 1ced38e..59e969e 100644 --- a/api/gerald.yaml +++ b/api/gerald.yaml @@ -36,6 +36,30 @@ paths: 403: description: "Forbidden" + /feedback/{feedback_uuid}/upvote: + post: + summary: "Upvote a feedback" + tags: + - "feedback" + description: "Upvote a feedback" + operationId: "upvoteFeedback" + parameters: + - in: path + name: feedback_uuid + description: "Feedback UUID" + required: true + type: string + - in: query + name: session_uuid + description: "Session UUID" + required: true + type: string + responses: + 200: + description: "successful operation" + 403: + description: "Forbidden" + /feedbacks: get: summary: "Get all feedbacks" @@ -59,6 +83,29 @@ paths: 403: description: "Forbidden" + /feedbacks/{project_id}: + get: + summary: "Get all feedbacks for a project" + tags: + - "feedback" + description: "Get all feedbacks for a project" + operationId: "getFeedbacksForProject" + parameters: + - in: path + name: project_id + description: "Project id" + required: true + type: string + responses: + 200: + description: "successful operation" + schema: + type: array + items: + $ref: "#/definitions/Feedback" + 403: + description: "Forbidden" + definitions: Feedback: type: object diff --git a/internal/interfaces/rest/handlers/feedback.go b/internal/interfaces/rest/handlers/feedback.go index db11c1f..d954e61 100644 --- a/internal/interfaces/rest/handlers/feedback.go +++ b/internal/interfaces/rest/handlers/feedback.go @@ -38,6 +38,26 @@ func (h *FeedbackHandler) GetFeedbacks(params feedback.GetFeedbacksParams) middl return feedback.NewGetFeedbacksOK().WithPayload(feedbacksToAPI(feedbacks)) } +func (h *FeedbackHandler) GetFeedback(params feedback.GetFeedbacksForProjectParams) middleware.Responder { + feedbacks, err := h.feedback.GetByProjectID(params.ProjectID) + if err != nil { + return feedback.NewGetFeedbacksForProjectForbidden() + + } + + return feedback.NewGetFeedbacksForProjectOK().WithPayload(feedbacksToAPI(feedbacks)) + +} + +func (h *FeedbackHandler) UpvoteFeedback(params feedback.UpvoteFeedbackParams) middleware.Responder { + err := h.feedback.Upvote(params.SessionUUID, params.FeedbackUUID) + if err != nil { + return feedback.NewUpvoteFeedbackForbidden() + } + + return feedback.NewUpvoteFeedbackOK() +} + func feedbacksToAPI(feedbacks []*dmodels.Feedback) []*models.Feedback { apiFeedbacks := make([]*models.Feedback, 0, len(feedbacks)) for _, f := range feedbacks { @@ -51,7 +71,7 @@ func feedbackToAPI(feedback *dmodels.Feedback) *models.Feedback { return &models.Feedback{ ProjectID: feedback.ProjectID, Text: feedback.Text, - Type: feedback.Type, + Type: string(feedback.Type), UserID: feedback.UserID, CreatedAt: strfmt.DateTime(feedback.CreatedAt), } diff --git a/internal/interfaces/rest/restapi/embedded_spec.go b/internal/interfaces/rest/restapi/embedded_spec.go index ff64899..2ac03a3 100644 --- a/internal/interfaces/rest/restapi/embedded_spec.go +++ b/internal/interfaces/rest/restapi/embedded_spec.go @@ -68,6 +68,40 @@ func init() { } } }, + "/feedback/{feedback_uuid}/upvote": { + "post": { + "description": "Upvote a feedback", + "tags": [ + "feedback" + ], + "summary": "Upvote a feedback", + "operationId": "upvoteFeedback", + "parameters": [ + { + "type": "string", + "description": "Feedback UUID", + "name": "feedback_uuid", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Session UUID", + "name": "session_uuid", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "successful operation" + }, + "403": { + "description": "Forbidden" + } + } + } + }, "/feedbacks": { "get": { "description": "Get all feedbacks", @@ -100,6 +134,39 @@ func init() { } } } + }, + "/feedbacks/{project_id}": { + "get": { + "description": "Get all feedbacks for a project", + "tags": [ + "feedback" + ], + "summary": "Get all feedbacks for a project", + "operationId": "getFeedbacksForProject", + "parameters": [ + { + "type": "string", + "description": "Project id", + "name": "project_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Feedback" + } + } + }, + "403": { + "description": "Forbidden" + } + } + } } }, "definitions": { @@ -184,6 +251,40 @@ func init() { } } }, + "/feedback/{feedback_uuid}/upvote": { + "post": { + "description": "Upvote a feedback", + "tags": [ + "feedback" + ], + "summary": "Upvote a feedback", + "operationId": "upvoteFeedback", + "parameters": [ + { + "type": "string", + "description": "Feedback UUID", + "name": "feedback_uuid", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Session UUID", + "name": "session_uuid", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "successful operation" + }, + "403": { + "description": "Forbidden" + } + } + } + }, "/feedbacks": { "get": { "description": "Get all feedbacks", @@ -216,6 +317,39 @@ func init() { } } } + }, + "/feedbacks/{project_id}": { + "get": { + "description": "Get all feedbacks for a project", + "tags": [ + "feedback" + ], + "summary": "Get all feedbacks for a project", + "operationId": "getFeedbacksForProject", + "parameters": [ + { + "type": "string", + "description": "Project id", + "name": "project_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Feedback" + } + } + }, + "403": { + "description": "Forbidden" + } + } + } } }, "definitions": { diff --git a/internal/interfaces/rest/restapi/operations/feedback/get_feedbacks_for_project.go b/internal/interfaces/rest/restapi/operations/feedback/get_feedbacks_for_project.go new file mode 100644 index 0000000..6194270 --- /dev/null +++ b/internal/interfaces/rest/restapi/operations/feedback/get_feedbacks_for_project.go @@ -0,0 +1,58 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package feedback + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// GetFeedbacksForProjectHandlerFunc turns a function with the right signature into a get feedbacks for project handler +type GetFeedbacksForProjectHandlerFunc func(GetFeedbacksForProjectParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn GetFeedbacksForProjectHandlerFunc) Handle(params GetFeedbacksForProjectParams) middleware.Responder { + return fn(params) +} + +// GetFeedbacksForProjectHandler interface for that can handle valid get feedbacks for project params +type GetFeedbacksForProjectHandler interface { + Handle(GetFeedbacksForProjectParams) middleware.Responder +} + +// NewGetFeedbacksForProject creates a new http.Handler for the get feedbacks for project operation +func NewGetFeedbacksForProject(ctx *middleware.Context, handler GetFeedbacksForProjectHandler) *GetFeedbacksForProject { + return &GetFeedbacksForProject{Context: ctx, Handler: handler} +} + +/* + GetFeedbacksForProject swagger:route GET /feedbacks/{project_id} feedback getFeedbacksForProject + +# Get all feedbacks for a project + +Get all feedbacks for a project +*/ +type GetFeedbacksForProject struct { + Context *middleware.Context + Handler GetFeedbacksForProjectHandler +} + +func (o *GetFeedbacksForProject) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewGetFeedbacksForProjectParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/internal/interfaces/rest/restapi/operations/feedback/get_feedbacks_for_project_parameters.go b/internal/interfaces/rest/restapi/operations/feedback/get_feedbacks_for_project_parameters.go new file mode 100644 index 0000000..c95cfb3 --- /dev/null +++ b/internal/interfaces/rest/restapi/operations/feedback/get_feedbacks_for_project_parameters.go @@ -0,0 +1,71 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package feedback + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" +) + +// NewGetFeedbacksForProjectParams creates a new GetFeedbacksForProjectParams object +// +// There are no default values defined in the spec. +func NewGetFeedbacksForProjectParams() GetFeedbacksForProjectParams { + + return GetFeedbacksForProjectParams{} +} + +// GetFeedbacksForProjectParams contains all the bound params for the get feedbacks for project operation +// typically these are obtained from a http.Request +// +// swagger:parameters getFeedbacksForProject +type GetFeedbacksForProjectParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /*Project id + Required: true + In: path + */ + ProjectID string +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewGetFeedbacksForProjectParams() beforehand. +func (o *GetFeedbacksForProjectParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + rProjectID, rhkProjectID, _ := route.Params.GetOK("project_id") + if err := o.bindProjectID(rProjectID, rhkProjectID, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindProjectID binds and validates parameter ProjectID from path. +func (o *GetFeedbacksForProjectParams) bindProjectID(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + o.ProjectID = raw + + return nil +} diff --git a/internal/interfaces/rest/restapi/operations/feedback/get_feedbacks_for_project_responses.go b/internal/interfaces/rest/restapi/operations/feedback/get_feedbacks_for_project_responses.go new file mode 100644 index 0000000..ed485fe --- /dev/null +++ b/internal/interfaces/rest/restapi/operations/feedback/get_feedbacks_for_project_responses.go @@ -0,0 +1,87 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package feedback + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "gerald/internal/interfaces/rest/models" +) + +// GetFeedbacksForProjectOKCode is the HTTP code returned for type GetFeedbacksForProjectOK +const GetFeedbacksForProjectOKCode int = 200 + +/* +GetFeedbacksForProjectOK successful operation + +swagger:response getFeedbacksForProjectOK +*/ +type GetFeedbacksForProjectOK struct { + + /* + In: Body + */ + Payload []*models.Feedback `json:"body,omitempty"` +} + +// NewGetFeedbacksForProjectOK creates GetFeedbacksForProjectOK with default headers values +func NewGetFeedbacksForProjectOK() *GetFeedbacksForProjectOK { + + return &GetFeedbacksForProjectOK{} +} + +// WithPayload adds the payload to the get feedbacks for project o k response +func (o *GetFeedbacksForProjectOK) WithPayload(payload []*models.Feedback) *GetFeedbacksForProjectOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get feedbacks for project o k response +func (o *GetFeedbacksForProjectOK) SetPayload(payload []*models.Feedback) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetFeedbacksForProjectOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + payload := o.Payload + if payload == nil { + // return empty array + payload = make([]*models.Feedback, 0, 50) + } + + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } +} + +// GetFeedbacksForProjectForbiddenCode is the HTTP code returned for type GetFeedbacksForProjectForbidden +const GetFeedbacksForProjectForbiddenCode int = 403 + +/* +GetFeedbacksForProjectForbidden Forbidden + +swagger:response getFeedbacksForProjectForbidden +*/ +type GetFeedbacksForProjectForbidden struct { +} + +// NewGetFeedbacksForProjectForbidden creates GetFeedbacksForProjectForbidden with default headers values +func NewGetFeedbacksForProjectForbidden() *GetFeedbacksForProjectForbidden { + + return &GetFeedbacksForProjectForbidden{} +} + +// WriteResponse to the client +func (o *GetFeedbacksForProjectForbidden) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + + rw.WriteHeader(403) +} diff --git a/internal/interfaces/rest/restapi/operations/feedback/get_feedbacks_for_project_urlbuilder.go b/internal/interfaces/rest/restapi/operations/feedback/get_feedbacks_for_project_urlbuilder.go new file mode 100644 index 0000000..eec28f3 --- /dev/null +++ b/internal/interfaces/rest/restapi/operations/feedback/get_feedbacks_for_project_urlbuilder.go @@ -0,0 +1,99 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package feedback + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + "strings" +) + +// GetFeedbacksForProjectURL generates an URL for the get feedbacks for project operation +type GetFeedbacksForProjectURL struct { + ProjectID string + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetFeedbacksForProjectURL) WithBasePath(bp string) *GetFeedbacksForProjectURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetFeedbacksForProjectURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *GetFeedbacksForProjectURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/feedbacks/{project_id}" + + projectID := o.ProjectID + if projectID != "" { + _path = strings.Replace(_path, "{project_id}", projectID, -1) + } else { + return nil, errors.New("projectId is required on GetFeedbacksForProjectURL") + } + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *GetFeedbacksForProjectURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *GetFeedbacksForProjectURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *GetFeedbacksForProjectURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on GetFeedbacksForProjectURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on GetFeedbacksForProjectURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *GetFeedbacksForProjectURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/internal/interfaces/rest/restapi/operations/feedback/upvote_feedback.go b/internal/interfaces/rest/restapi/operations/feedback/upvote_feedback.go new file mode 100644 index 0000000..6d29505 --- /dev/null +++ b/internal/interfaces/rest/restapi/operations/feedback/upvote_feedback.go @@ -0,0 +1,58 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package feedback + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// UpvoteFeedbackHandlerFunc turns a function with the right signature into a upvote feedback handler +type UpvoteFeedbackHandlerFunc func(UpvoteFeedbackParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn UpvoteFeedbackHandlerFunc) Handle(params UpvoteFeedbackParams) middleware.Responder { + return fn(params) +} + +// UpvoteFeedbackHandler interface for that can handle valid upvote feedback params +type UpvoteFeedbackHandler interface { + Handle(UpvoteFeedbackParams) middleware.Responder +} + +// NewUpvoteFeedback creates a new http.Handler for the upvote feedback operation +func NewUpvoteFeedback(ctx *middleware.Context, handler UpvoteFeedbackHandler) *UpvoteFeedback { + return &UpvoteFeedback{Context: ctx, Handler: handler} +} + +/* + UpvoteFeedback swagger:route POST /feedback/{feedback_uuid}/upvote feedback upvoteFeedback + +# Upvote a feedback + +Upvote a feedback +*/ +type UpvoteFeedback struct { + Context *middleware.Context + Handler UpvoteFeedbackHandler +} + +func (o *UpvoteFeedback) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewUpvoteFeedbackParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/internal/interfaces/rest/restapi/operations/feedback/upvote_feedback_parameters.go b/internal/interfaces/rest/restapi/operations/feedback/upvote_feedback_parameters.go new file mode 100644 index 0000000..3f6eb51 --- /dev/null +++ b/internal/interfaces/rest/restapi/operations/feedback/upvote_feedback_parameters.go @@ -0,0 +1,106 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package feedback + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/validate" +) + +// NewUpvoteFeedbackParams creates a new UpvoteFeedbackParams object +// +// There are no default values defined in the spec. +func NewUpvoteFeedbackParams() UpvoteFeedbackParams { + + return UpvoteFeedbackParams{} +} + +// UpvoteFeedbackParams contains all the bound params for the upvote feedback operation +// typically these are obtained from a http.Request +// +// swagger:parameters upvoteFeedback +type UpvoteFeedbackParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /*Feedback UUID + Required: true + In: path + */ + FeedbackUUID string + /*Session UUID + Required: true + In: query + */ + SessionUUID string +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewUpvoteFeedbackParams() beforehand. +func (o *UpvoteFeedbackParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + qs := runtime.Values(r.URL.Query()) + + rFeedbackUUID, rhkFeedbackUUID, _ := route.Params.GetOK("feedback_uuid") + if err := o.bindFeedbackUUID(rFeedbackUUID, rhkFeedbackUUID, route.Formats); err != nil { + res = append(res, err) + } + + qSessionUUID, qhkSessionUUID, _ := qs.GetOK("session_uuid") + if err := o.bindSessionUUID(qSessionUUID, qhkSessionUUID, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + 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 + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + o.FeedbackUUID = raw + + return nil +} + +// bindSessionUUID binds and validates parameter SessionUUID from query. +func (o *UpvoteFeedbackParams) bindSessionUUID(rawData []string, hasKey bool, formats strfmt.Registry) error { + if !hasKey { + return errors.Required("session_uuid", "query", rawData) + } + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // AllowEmptyValue: false + + if err := validate.RequiredString("session_uuid", "query", raw); err != nil { + return err + } + o.SessionUUID = raw + + return nil +} diff --git a/internal/interfaces/rest/restapi/operations/feedback/upvote_feedback_responses.go b/internal/interfaces/rest/restapi/operations/feedback/upvote_feedback_responses.go new file mode 100644 index 0000000..6f1792b --- /dev/null +++ b/internal/interfaces/rest/restapi/operations/feedback/upvote_feedback_responses.go @@ -0,0 +1,62 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package feedback + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" +) + +// UpvoteFeedbackOKCode is the HTTP code returned for type UpvoteFeedbackOK +const UpvoteFeedbackOKCode int = 200 + +/* +UpvoteFeedbackOK successful operation + +swagger:response upvoteFeedbackOK +*/ +type UpvoteFeedbackOK struct { +} + +// NewUpvoteFeedbackOK creates UpvoteFeedbackOK with default headers values +func NewUpvoteFeedbackOK() *UpvoteFeedbackOK { + + return &UpvoteFeedbackOK{} +} + +// WriteResponse to the client +func (o *UpvoteFeedbackOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + + rw.WriteHeader(200) +} + +// UpvoteFeedbackForbiddenCode is the HTTP code returned for type UpvoteFeedbackForbidden +const UpvoteFeedbackForbiddenCode int = 403 + +/* +UpvoteFeedbackForbidden Forbidden + +swagger:response upvoteFeedbackForbidden +*/ +type UpvoteFeedbackForbidden struct { +} + +// NewUpvoteFeedbackForbidden creates UpvoteFeedbackForbidden with default headers values +func NewUpvoteFeedbackForbidden() *UpvoteFeedbackForbidden { + + return &UpvoteFeedbackForbidden{} +} + +// WriteResponse to the client +func (o *UpvoteFeedbackForbidden) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + + rw.WriteHeader(403) +} diff --git a/internal/interfaces/rest/restapi/operations/feedback/upvote_feedback_urlbuilder.go b/internal/interfaces/rest/restapi/operations/feedback/upvote_feedback_urlbuilder.go new file mode 100644 index 0000000..c4def22 --- /dev/null +++ b/internal/interfaces/rest/restapi/operations/feedback/upvote_feedback_urlbuilder.go @@ -0,0 +1,110 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package feedback + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + "strings" +) + +// UpvoteFeedbackURL generates an URL for the upvote feedback operation +type UpvoteFeedbackURL struct { + FeedbackUUID string + + SessionUUID string + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *UpvoteFeedbackURL) WithBasePath(bp string) *UpvoteFeedbackURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *UpvoteFeedbackURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *UpvoteFeedbackURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/feedback/{feedback_uuid}/upvote" + + feedbackUUID := o.FeedbackUUID + if feedbackUUID != "" { + _path = strings.Replace(_path, "{feedback_uuid}", feedbackUUID, -1) + } else { + return nil, errors.New("feedbackUuid is required on UpvoteFeedbackURL") + } + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + qs := make(url.Values) + + sessionUUIDQ := o.SessionUUID + if sessionUUIDQ != "" { + qs.Set("session_uuid", sessionUUIDQ) + } + + _result.RawQuery = qs.Encode() + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *UpvoteFeedbackURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *UpvoteFeedbackURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *UpvoteFeedbackURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on UpvoteFeedbackURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on UpvoteFeedbackURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *UpvoteFeedbackURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/internal/interfaces/rest/restapi/operations/gerald_api.go b/internal/interfaces/rest/restapi/operations/gerald_api.go index 7b3cefb..b7c7d7c 100644 --- a/internal/interfaces/rest/restapi/operations/gerald_api.go +++ b/internal/interfaces/rest/restapi/operations/gerald_api.go @@ -50,6 +50,12 @@ func NewGeraldAPI(spec *loads.Document) *GeraldAPI { FeedbackGetFeedbacksHandler: feedback.GetFeedbacksHandlerFunc(func(params feedback.GetFeedbacksParams) middleware.Responder { return middleware.NotImplemented("operation feedback.GetFeedbacks has not yet been implemented") }), + FeedbackGetFeedbacksForProjectHandler: feedback.GetFeedbacksForProjectHandlerFunc(func(params feedback.GetFeedbacksForProjectParams) middleware.Responder { + return middleware.NotImplemented("operation feedback.GetFeedbacksForProject has not yet been implemented") + }), + FeedbackUpvoteFeedbackHandler: feedback.UpvoteFeedbackHandlerFunc(func(params feedback.UpvoteFeedbackParams) middleware.Responder { + return middleware.NotImplemented("operation feedback.UpvoteFeedback has not yet been implemented") + }), } } @@ -90,6 +96,10 @@ type GeraldAPI struct { FeedbackCreateFeedbackHandler feedback.CreateFeedbackHandler // FeedbackGetFeedbacksHandler sets the operation handler for the get feedbacks operation FeedbackGetFeedbacksHandler feedback.GetFeedbacksHandler + // FeedbackGetFeedbacksForProjectHandler sets the operation handler for the get feedbacks for project operation + FeedbackGetFeedbacksForProjectHandler feedback.GetFeedbacksForProjectHandler + // FeedbackUpvoteFeedbackHandler sets the operation handler for the upvote feedback operation + FeedbackUpvoteFeedbackHandler feedback.UpvoteFeedbackHandler // ServeError is called when an error is received, there is a default handler // but you can set your own with this @@ -173,6 +183,12 @@ func (o *GeraldAPI) Validate() error { if o.FeedbackGetFeedbacksHandler == nil { unregistered = append(unregistered, "feedback.GetFeedbacksHandler") } + if o.FeedbackGetFeedbacksForProjectHandler == nil { + unregistered = append(unregistered, "feedback.GetFeedbacksForProjectHandler") + } + if o.FeedbackUpvoteFeedbackHandler == nil { + unregistered = append(unregistered, "feedback.UpvoteFeedbackHandler") + } if len(unregistered) > 0 { return fmt.Errorf("missing registration: %s", strings.Join(unregistered, ", ")) @@ -269,6 +285,14 @@ func (o *GeraldAPI) initHandlerCache() { o.handlers["GET"] = make(map[string]http.Handler) } o.handlers["GET"]["/feedbacks"] = feedback.NewGetFeedbacks(o.context, o.FeedbackGetFeedbacksHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } + o.handlers["GET"]["/feedbacks/{project_id}"] = feedback.NewGetFeedbacksForProject(o.context, o.FeedbackGetFeedbacksForProjectHandler) + 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) } // Serve creates a http handler to serve the API over HTTP diff --git a/internal/models/feedback.go b/internal/models/feedback.go index ad85da4..ba69b03 100644 --- a/internal/models/feedback.go +++ b/internal/models/feedback.go @@ -11,23 +11,32 @@ type Feedback struct { ProjectID string `gorm:"type:varchar(255)"` SessionUUID string `gorm:"type:varchar(255)"` - SessionName string `gorm:"type:varchar(255)"` - Type string `gorm:"type:varchar(255)"` - Text string `gorm:"type:varchar(300)"` + Type FeedbackType `gorm:"type:varchar(255)"` + Text string `gorm:"type:varchar(300)"` + + Upvote []Upvote `gorm:"foreignKey:FeedbackUUID;references:UUID"` CreatedAt time.Time `gorm:"type:timestamp"` UpdatedAt time.Time `gorm:"type:timestamp"` } -func NewFeedback(userUUID, sessionUUID, ticketType, text, projectID string) *Feedback { +type FeedbackType string + +const ( + FeedbackTypeFeature FeedbackType = "feature" + FeedbackTypeFeedback FeedbackType = "feedback" + FeedbackTypeSupport FeedbackType = "support" +) + +func NewFeedback(userUUID, sessionUUID, text, projectID string, feedbackType FeedbackType) *Feedback { return &Feedback{ UUID: uuid.New().String(), UserID: userUUID, SessionUUID: sessionUUID, ProjectID: projectID, - Type: ticketType, + Type: feedbackType, Text: text, } } diff --git a/internal/models/upvote.go b/internal/models/upvote.go new file mode 100644 index 0000000..36f0d86 --- /dev/null +++ b/internal/models/upvote.go @@ -0,0 +1,21 @@ +package models + +import ( + "github.com/google/uuid" + "time" +) + +type Upvote struct { + UUID string `gorm:"primaryKey"` + SessionUUID string `gorm:"type:varchar(255);unique"` + FeedbackUUID string `gorm:"type:varchar(255)"` + CreatedAt time.Time `gorm:"type:timestamp"` +} + +func NewUpvote(sessionUUID, feedbackUUID string) *Upvote { + return &Upvote{ + UUID: uuid.New().String(), + SessionUUID: sessionUUID, + FeedbackUUID: feedbackUUID, + } +} diff --git a/internal/services/feedback.go b/internal/services/feedback.go index c755b4a..0742e14 100644 --- a/internal/services/feedback.go +++ b/internal/services/feedback.go @@ -1,6 +1,7 @@ package services import ( + "errors" apiModels "gerald/internal/interfaces/rest/models" "gerald/internal/models" "gerald/repo/pg" @@ -17,9 +18,41 @@ func NewFeedbackService(db *pg.DB) *FeedbackService { } func (f *FeedbackService) Create(sessionID string, feedback *apiModels.Feedback) error { - return f.db.Ticket.Create(models.NewFeedback(feedback.UserID, sessionID, feedback.Type, feedback.Text, feedback.ProjectID)) + return f.db.Feedback.Create( + models.NewFeedback( + feedback.UserID, + sessionID, + feedback.Type, + feedback.Text, + models.FeedbackType(feedback.ProjectID)), + ) } func (f *FeedbackService) GetBySessionID(sessionID string) ([]*models.Feedback, error) { - return f.db.Ticket.GetBySessionID(sessionID) + return f.db.Feedback.GetBySessionID(sessionID) +} + +func (f *FeedbackService) GetByProjectID(projectID string) ([]*models.Feedback, error) { + return f.db.Feedback.GetByProjectID(models.FeedbackType(projectID)) +} + +func (f *FeedbackService) Upvote(sessionID, feedbackUUID string) error { + feedback, err := f.db.Feedback.GetByUUID(feedbackUUID) + if err != nil { + return err + } + + // check if project type is feature + if feedback.Type != models.FeedbackTypeFeature { + return errors.New("only feature tickets can be upvoted") + } + + // 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)) } diff --git a/repo/pg/client.go b/repo/pg/client.go index 624b548..f8a131e 100644 --- a/repo/pg/client.go +++ b/repo/pg/client.go @@ -10,8 +10,8 @@ import ( ) type DB struct { - Ticket *Feedback - logger zerolog.Logger + Feedback *Feedback + logger zerolog.Logger } func NewDB( @@ -19,8 +19,8 @@ func NewDB( logger zerolog.Logger, ) *DB { return &DB{ - Ticket: NewFeedback(db), - logger: logger, + Feedback: NewFeedback(db), + logger: logger, } } diff --git a/repo/pg/feedback.go b/repo/pg/feedback.go index 5a1546a..b2b0763 100644 --- a/repo/pg/feedback.go +++ b/repo/pg/feedback.go @@ -31,3 +31,35 @@ func (t *Feedback) GetBySessionID(sessionID string) ([]*models.Feedback, error) return tickets, nil } + +func (t *Feedback) GetByUUID(uuid string) (*models.Feedback, error) { + var ticket models.Feedback + + res := t.db. + Preload("Upvote"). + Where("uuid = ?", uuid). + First(&ticket) + if res.Error != nil { + return nil, res.Error + } + + return &ticket, nil +} + +func (t *Feedback) GetByProjectID(projectID models.FeedbackType) ([]*models.Feedback, error) { + var tickets []*models.Feedback + + res := t.db. + Where("project_id = ?", projectID). + Where("type = ?", models.FeedbackTypeFeature). + Find(&tickets) + if res.Error != nil { + return nil, res.Error + } + + return tickets, nil +} + +func (t *Feedback) CreateUpvote(upvote *models.Upvote) error { + return t.db.Create(upvote).Error +}