diff --git a/infrastructure/pg/connection.go b/infrastructure/pg/connection.go index c092615..bd3e746 100644 --- a/infrastructure/pg/connection.go +++ b/infrastructure/pg/connection.go @@ -63,7 +63,7 @@ func (pg *Postgres) SelectOne(ctx context.Context, query string, args ...interfa } func (pg *Postgres) Update(ctx context.Context, query string, args ...interface{}) error { - _, err := pg.db.Query(ctx, query, args...) + _, err := pg.db.Exec(ctx, query, args...) if err != nil { return err } diff --git a/internal/api/http/handlers/widget/widget.go b/internal/api/http/handlers/widget/widget.go index 8b4678e..d70d67b 100644 --- a/internal/api/http/handlers/widget/widget.go +++ b/internal/api/http/handlers/widget/widget.go @@ -7,12 +7,13 @@ import ( "github.com/labstack/echo/v4" "log/slog" "net/http" + "strconv" ) // CreateWidget godoc // @Summary Create new widget // @Description Create new widget -// @Tags GetWidgetDb +// @Tags Widget // @Accept json // @Produce json // @Security BearerAuth @@ -62,7 +63,7 @@ func CreateWidget(widgetService model.WidgetService) echo.HandlerFunc { // GetStreamersWidgets godoc // @Summary Get all streamer's widgets // @Description Get all streamer's widgets -// @Tags GetWidgetDb +// @Tags Widget // @Accept json // @Produce json // @Security BearerAuth @@ -101,6 +102,58 @@ func GetStreamersWidgets(widgetService model.WidgetService) echo.HandlerFunc { } } +// UpdateWidget godoc +// @Summary Update an existing widget +// @Description Update an existing widget +// @Tags Widget +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param widget_id path int true "Widget ID" +// @Param request body model.UpdateWidget true "Update widget" +// @Success 200 {string} string "Widget has been updated successfully!" +// @Failure 400 {object} echo.HTTPError "Bad request" +// @Failure 401 {object} echo.HTTPError "Unauthorized or expired token" +// @Failure 422 {object} echo.HTTPError "Validation error" +// @Failure 404 {object} echo.HTTPError "Widget not found" +// @Router /widgets/{widget_id} [patch] +func UpdateWidget(widgetService model.WidgetService) echo.HandlerFunc { + return func(request echo.Context) error { + ctx := context.Background() + widgetIDStr := request.Param("widgetID") + + widgetID, err := strconv.Atoi(widgetIDStr) + if err != nil { + return echo.NewHTTPError(http.StatusUnprocessableEntity, "Invalid widget ID") + } + + var body model.UpdateWidget + err = validator.ParseAndValidate(&body, request) + if err != nil { + return echo.NewHTTPError(http.StatusUnprocessableEntity, err.Error()) + } + + authData, err := widgetService.CheckToken(request) + if err != nil { + slog.Error(err.Error()) + return echo.NewHTTPError(http.StatusUnauthorized, err.Error()) + } + + err = widgetService.UpdateWidget( + ctx, + body, + widgetID, + authData.AccountID, + ) + if err != nil { + slog.Error(err.Error()) + return echo.NewHTTPError(http.StatusInternalServerError, "Internal Server Error") + } + + return request.JSON(http.StatusOK, "Widget has been updated successfully!") + } +} + //func GetWidgetHTML(widgetService model.WidgetService) echo.HandlerFunc { // return func(request echo.Context) error { // ctx := context.Background() diff --git a/internal/app/http/app.go b/internal/app/http/app.go index 2e79efa..375276a 100644 --- a/internal/app/http/app.go +++ b/internal/app/http/app.go @@ -86,7 +86,7 @@ func IncludeWidgetHandlers( ) { server.POST(PREFIX+"/widgets", widget.CreateWidget(widgetService)) server.GET(PREFIX+"/widgets", widget.GetStreamersWidgets(widgetService)) - + server.PATCH(PREFIX+"/widgets/:widgetID", widget.UpdateWidget(widgetService)) //server.GET(PREFIX+"/html/:streamerID", model.GetWidgetHTML(widgetService)) //server.GET(PREFIX+"/info/:widgetID", model.GetWidgetInfo(widgetService)) // diff --git a/internal/docs/docs.go b/internal/docs/docs.go index 08bbdf4..36dbac8 100644 --- a/internal/docs/docs.go +++ b/internal/docs/docs.go @@ -628,7 +628,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "GetWidgetDb" + "Widget" ], "summary": "Get all streamer's widgets", "responses": { @@ -675,7 +675,7 @@ const docTemplate = `{ "application/json" ], "tags": [ - "GetWidgetDb" + "Widget" ], "summary": "Create new widget", "parameters": [ @@ -716,6 +716,76 @@ const docTemplate = `{ } } } + }, + "/widgets/{widget_id}": { + "patch": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Update an existing widget", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Widget" + ], + "summary": "Update an existing widget", + "parameters": [ + { + "type": "integer", + "description": "Widget ID", + "name": "widget_id", + "in": "path", + "required": true + }, + { + "description": "Update widget", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/donat-widget_internal_model.UpdateWidget" + } + } + ], + "responses": { + "200": { + "description": "Widget has been updated successfully!", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "#/definitions/echo.HTTPError" + } + }, + "401": { + "description": "Unauthorized or expired token", + "schema": { + "$ref": "#/definitions/echo.HTTPError" + } + }, + "404": { + "description": "Widget not found", + "schema": { + "$ref": "#/definitions/echo.HTTPError" + } + }, + "422": { + "description": "Validation error", + "schema": { + "$ref": "#/definitions/echo.HTTPError" + } + } + } + } } }, "definitions": { @@ -784,6 +854,9 @@ const docTemplate = `{ "id": { "type": "string" }, + "size": { + "type": "number" + }, "streamer_id": { "type": "integer" } @@ -1015,6 +1088,39 @@ const docTemplate = `{ } } }, + "donat-widget_internal_model.UpdateWidget": { + "type": "object", + "properties": { + "audio": { + "type": "string", + "example": "a0f9e244-f61f-4bfe-a7a0-3b5e91fe7364" + }, + "duration": { + "type": "integer", + "example": 120 + }, + "image": { + "type": "string", + "example": "d2c2f03f-3fe5-4bfc-b963-5478a057149e" + }, + "is_active": { + "type": "boolean", + "example": true + }, + "max_amount": { + "type": "integer", + "example": 100 + }, + "min_amount": { + "type": "integer", + "example": 10 + }, + "name": { + "type": "string", + "example": "Awesome Widget" + } + } + }, "donat-widget_internal_model.VoiceSettingsResponse": { "type": "object", "properties": { diff --git a/internal/docs/swagger.json b/internal/docs/swagger.json index 238d85f..cc3472d 100644 --- a/internal/docs/swagger.json +++ b/internal/docs/swagger.json @@ -621,7 +621,7 @@ "application/json" ], "tags": [ - "GetWidgetDb" + "Widget" ], "summary": "Get all streamer's widgets", "responses": { @@ -668,7 +668,7 @@ "application/json" ], "tags": [ - "GetWidgetDb" + "Widget" ], "summary": "Create new widget", "parameters": [ @@ -709,6 +709,76 @@ } } } + }, + "/widgets/{widget_id}": { + "patch": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Update an existing widget", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "Widget" + ], + "summary": "Update an existing widget", + "parameters": [ + { + "type": "integer", + "description": "Widget ID", + "name": "widget_id", + "in": "path", + "required": true + }, + { + "description": "Update widget", + "name": "request", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/donat-widget_internal_model.UpdateWidget" + } + } + ], + "responses": { + "200": { + "description": "Widget has been updated successfully!", + "schema": { + "type": "string" + } + }, + "400": { + "description": "Bad request", + "schema": { + "$ref": "#/definitions/echo.HTTPError" + } + }, + "401": { + "description": "Unauthorized or expired token", + "schema": { + "$ref": "#/definitions/echo.HTTPError" + } + }, + "404": { + "description": "Widget not found", + "schema": { + "$ref": "#/definitions/echo.HTTPError" + } + }, + "422": { + "description": "Validation error", + "schema": { + "$ref": "#/definitions/echo.HTTPError" + } + } + } + } } }, "definitions": { @@ -777,6 +847,9 @@ "id": { "type": "string" }, + "size": { + "type": "number" + }, "streamer_id": { "type": "integer" } @@ -1008,6 +1081,39 @@ } } }, + "donat-widget_internal_model.UpdateWidget": { + "type": "object", + "properties": { + "audio": { + "type": "string", + "example": "a0f9e244-f61f-4bfe-a7a0-3b5e91fe7364" + }, + "duration": { + "type": "integer", + "example": 120 + }, + "image": { + "type": "string", + "example": "d2c2f03f-3fe5-4bfc-b963-5478a057149e" + }, + "is_active": { + "type": "boolean", + "example": true + }, + "max_amount": { + "type": "integer", + "example": 100 + }, + "min_amount": { + "type": "integer", + "example": 10 + }, + "name": { + "type": "string", + "example": "Awesome Widget" + } + } + }, "donat-widget_internal_model.VoiceSettingsResponse": { "type": "object", "properties": { diff --git a/internal/docs/swagger.yaml b/internal/docs/swagger.yaml index 461b3f5..09cae73 100644 --- a/internal/docs/swagger.yaml +++ b/internal/docs/swagger.yaml @@ -48,6 +48,8 @@ definitions: type: string id: type: string + size: + type: number streamer_id: type: integer type: object @@ -209,6 +211,30 @@ definitions: voice_speed: type: integer type: object + donat-widget_internal_model.UpdateWidget: + properties: + audio: + example: a0f9e244-f61f-4bfe-a7a0-3b5e91fe7364 + type: string + duration: + example: 120 + type: integer + image: + example: d2c2f03f-3fe5-4bfc-b963-5478a057149e + type: string + is_active: + example: true + type: boolean + max_amount: + example: 100 + type: integer + min_amount: + example: 10 + type: integer + name: + example: Awesome Widget + type: string + type: object donat-widget_internal_model.VoiceSettingsResponse: properties: min_price: @@ -649,7 +675,7 @@ paths: - BearerAuth: [] summary: Get all streamer's widgets tags: - - GetWidgetDb + - Widget post: consumes: - application/json @@ -684,7 +710,52 @@ paths: - BearerAuth: [] summary: Create new widget tags: - - GetWidgetDb + - Widget + /widgets/{widget_id}: + patch: + consumes: + - application/json + description: Update an existing widget + parameters: + - description: Widget ID + in: path + name: widget_id + required: true + type: integer + - description: Update widget + in: body + name: request + required: true + schema: + $ref: '#/definitions/donat-widget_internal_model.UpdateWidget' + produces: + - application/json + responses: + "200": + description: Widget has been updated successfully! + schema: + type: string + "400": + description: Bad request + schema: + $ref: '#/definitions/echo.HTTPError' + "401": + description: Unauthorized or expired token + schema: + $ref: '#/definitions/echo.HTTPError' + "404": + description: Widget not found + schema: + $ref: '#/definitions/echo.HTTPError' + "422": + description: Validation error + schema: + $ref: '#/definitions/echo.HTTPError' + security: + - BearerAuth: [] + summary: Update an existing widget + tags: + - Widget securityDefinitions: BearerAuth: in: header diff --git a/internal/model/interfaces.go b/internal/model/interfaces.go index 6e74993..80a615e 100644 --- a/internal/model/interfaces.go +++ b/internal/model/interfaces.go @@ -2,6 +2,7 @@ package model import ( "donat-widget/internal/model/api" + "github.com/google/uuid" "mime/multipart" ) @@ -25,6 +26,7 @@ type WidgetService interface { name string, ) (WidgetID, error) GetWidgetsByStreamer(ctx context.Context, streamerID int) ([]*WidgetWithFileLink, error) + UpdateWidget(ctx context.Context, updateWidget UpdateWidget, widgetID, accountID int) error //GetWidgetByID(ctx context.Context, widgetID WidgetID) ([]*GetWidgetDb, error) //GetWidgetHTML(ctx context.Context, streamerID StreamerID) (WidgetHTML, error) @@ -47,7 +49,18 @@ type WidgetRepo interface { ) (WidgetID, error) CheckWidgetName(ctx context.Context, streamerID int, name string) (bool, error) GetWidgetsByStreamerID(ctx context.Context, streamerID int) ([]*GetWidgetDb, error) - + UpdateWidget( + ctx context.Context, + widgetID int, + duration *int, + minAmount *int, + maxAmount *int, + isActive *bool, + image *uuid.UUID, + audio *uuid.UUID, + name *string, + ) error + WidgetByID(ctx context.Context, widgetID, accountID int) error //GetWidgetByID(ctx context.Context, widgetID WidgetID) ([]*GetWidgetDb, error) //GetAllWidget(ctx context.Context, streamerID StreamerID) ([]*GetWidgetDb, error) //GetMediaFile(fileID FileID) (DownloadFile, error) diff --git a/internal/model/models.go b/internal/model/models.go index 0c91c32..e8c9b90 100644 --- a/internal/model/models.go +++ b/internal/model/models.go @@ -46,6 +46,16 @@ type WidgetWithFileLink struct { Widgets []*GetWidgetDb `json:"widgets"` } +type UpdateWidget struct { + IsActive *bool `json:"is_active" example:"true" description:"Indicates whether the widget is active or not"` + Name *string `json:"name" example:"Awesome Widget" description:"Name of the widget"` + Image *uuid.UUID `json:"image" example:"d2c2f03f-3fe5-4bfc-b963-5478a057149e" description:"UUID of the widget image"` + Audio *uuid.UUID `json:"audio" example:"a0f9e244-f61f-4bfe-a7a0-3b5e91fe7364" description:"UUID of the widget audio file"` + Duration *int `json:"duration" example:"120" description:"Duration of the widget in seconds"` + MinAmount *int `json:"min_amount" example:"10" description:"Minimum amount for the widget"` + MaxAmount *int `json:"max_amount" example:"100" description:"Maximum amount for the widget"` +} + type Donat struct { ID DonatID `db:"id"` StreamerID StreamerID `db:"streamer_id"` diff --git a/internal/model/sql/query.go b/internal/model/sql/query.go index 05a5921..a9a25df 100644 --- a/internal/model/sql/query.go +++ b/internal/model/sql/query.go @@ -139,3 +139,20 @@ FROM files AS f JOIN widgets AS w ON w.image = f.id WHERE f.streamer_id = (@streamer_id); ` + +var UpdateWidget = ` +UPDATE widgets +SET + duration = COALESCE(@duration, duration), + min_amount = COALESCE(@min_amount, min_amount), + max_amount = COALESCE(@max_amount, max_amount), + is_active = COALESCE(@is_active, is_active), + image = COALESCE(@image, image), + audio = COALESCE(@audio, audio), + name = COALESCE(@name, name) +WHERE id = @id; +` + +var GetWidgetById = ` +SELECT id FROM widgets WHERE id = (@widget_id) AND streamer_id = (@streamer_id); +` diff --git a/internal/repository/widget/widget.go b/internal/repository/widget/widget.go index dfb5d37..c9e6df3 100644 --- a/internal/repository/widget/widget.go +++ b/internal/repository/widget/widget.go @@ -4,7 +4,9 @@ import ( "context" "donat-widget/internal/model" "donat-widget/internal/model/sql" + "errors" "github.com/georgysavva/scany/v2/pgxscan" + "github.com/google/uuid" "github.com/jackc/pgx/v5" "log/slog" ) @@ -97,6 +99,74 @@ func (widgetRepo *RepoWidget) GetWidgetsByStreamerID( return widgets, nil } +func (widgetRepo *RepoWidget) UpdateWidget( + ctx context.Context, + widgetID int, + duration *int, + minAmount *int, + maxAmount *int, + isActive *bool, + image *uuid.UUID, + audio *uuid.UUID, + name *string, +) error { + args := pgx.NamedArgs{ + "id": widgetID, + } + + if duration != nil { + args["duration"] = *duration + } + if minAmount != nil { + args["min_amount"] = *minAmount + } + if maxAmount != nil { + args["max_amount"] = *maxAmount + } + if image != nil { + args["image"] = *image + } + if audio != nil { + args["audio"] = *audio + } + if name != nil { + args["name"] = *name + } + if isActive != nil { + args["is_active"] = *isActive + } + + err := widgetRepo.db.Update(ctx, sql.UpdateWidget, args) + if err != nil { + slog.Error(err.Error()) + return err + } + + return nil +} + +func (widgetRepo *RepoWidget) WidgetByID( + ctx context.Context, + widgetID int, + accountID int, +) error { + args := pgx.NamedArgs{ + "widget_id": widgetID, + "streamer_id": accountID, + } + + row, err := widgetRepo.db.SelectOne(ctx, sql.GetWidgetById, args) + if err != nil { + slog.Error(err.Error()) + return err + } + + if row == nil { + return errors.New("Widget not exists!") + } + return nil +} + // //func (widgetRepo *RepoWidget) GetWidgetByID( // ctx context.Context, diff --git a/internal/service/widget/widget.go b/internal/service/widget/widget.go index c9a105c..67b1bb8 100644 --- a/internal/service/widget/widget.go +++ b/internal/service/widget/widget.go @@ -97,6 +97,37 @@ func (widgetService *ServiceWidget) GetWidgetsByStreamer( return result, nil } +func (widgetService *ServiceWidget) UpdateWidget( + ctx context.Context, + updateWidget model.UpdateWidget, + widgetID int, + accountID int, +) error { + err := widgetService.widgetRepo.WidgetByID(ctx, widgetID, accountID) + if err != nil { + slog.Error(err.Error()) + return err + } + + err = widgetService.widgetRepo.UpdateWidget( + ctx, + widgetID, + updateWidget.Duration, + updateWidget.MinAmount, + updateWidget.MaxAmount, + updateWidget.IsActive, + updateWidget.Image, + updateWidget.Audio, + updateWidget.Name, + ) + if err != nil { + slog.Error(err.Error()) + return err + } + + return nil +} + // //func (widgetService *ServiceWidget) GetWidgetHTML( // ctx context.Context,