From 3a51c420614ba3730c17c5ca798989662566caa8 Mon Sep 17 00:00:00 2001 From: harold Date: Sat, 8 Mar 2025 23:51:29 +0500 Subject: [PATCH] add voice settings update and get router --- infrastructure/pg/connection.go | 8 ++ internal/api/http/handlers/donat/donat.go | 29 ++++- internal/app/http/app.go | 2 + internal/docs/docs.go | 17 ++- internal/docs/swagger.json | 17 ++- internal/docs/swagger.yaml | 10 +- internal/model/interfaces.go | 9 +- internal/model/models.go | 11 +- internal/model/sql/query.go | 30 +++++ internal/repository/donat/donat.go | 139 ++++++++++++++++++++++ internal/service/donat/donat.go | 36 +++++- 11 files changed, 281 insertions(+), 27 deletions(-) diff --git a/infrastructure/pg/connection.go b/infrastructure/pg/connection.go index bd3e746..7b3eeff 100644 --- a/infrastructure/pg/connection.go +++ b/infrastructure/pg/connection.go @@ -78,6 +78,14 @@ func (pg *Postgres) Delete(ctx context.Context, query string, args ...interface{ return nil } +func (pg *Postgres) Exec(ctx context.Context, query string, args ...interface{}) error { + _, err := pg.db.Exec(ctx, query, args...) + if err != nil { + return err + } + return nil +} + func (pg *Postgres) CreateTable(ctx context.Context, query string) error { _, err := pg.db.Exec(ctx, query) if err != nil { diff --git a/internal/api/http/handlers/donat/donat.go b/internal/api/http/handlers/donat/donat.go index 183dbb8..815a22b 100644 --- a/internal/api/http/handlers/donat/donat.go +++ b/internal/api/http/handlers/donat/donat.go @@ -256,12 +256,13 @@ func UpdateDonatePage(donatService model.DonatService, fileService model.FileSer // @Router /voice-settings [get] func GetVoiceSettings(donatService model.DonatService) echo.HandlerFunc { return func(request echo.Context) error { + ctx := context.Background() + authData, err := donatService.CheckToken(request) if err != nil { slog.Error("Unauthorized") return echo.NewHTTPError(http.StatusUnauthorized, err.Error()) } - ctx := context.Background() voiceSettings, err := donatService.GetVoiceSettings(ctx, authData.AccountID) if err != nil { @@ -280,16 +281,36 @@ func GetVoiceSettings(donatService model.DonatService) echo.HandlerFunc { // @Tags Donate // @Accept json // @Produce json +// @Security BearerAuth // @Param request body model.UpdateVoiceSettings true "Update fields" -// @Param background formData file false "Background image" // @Success 200 {string} string "Voice settings 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" // @Router /voice-settings [patch] func UpdateVoiceSettings(donatService model.DonatService) echo.HandlerFunc { - return func(c echo.Context) error { - return nil + return func(request echo.Context) error { + ctx := context.Background() + + authData, err := donatService.CheckToken(request) + if err != nil { + slog.Error("Unauthorized") + return echo.NewHTTPError(http.StatusUnauthorized, err.Error()) + } + + var body model.UpdateVoiceSettings + err = validator.ParseAndValidate(&body, request) + if err != nil { + slog.Error(err.Error()) + return echo.NewHTTPError(http.StatusUnprocessableEntity, "Unprocessable Entity") + } + err = donatService.UpdateVoiceSettings(ctx, authData.AccountID, body) + if err != nil { + slog.Error("Failed to update voice settings", "error", err) + return echo.NewHTTPError(http.StatusInternalServerError, "Failed to update voice settings") + } + + return request.JSON(http.StatusOK, "Voice settings updated successfully") } } diff --git a/internal/app/http/app.go b/internal/app/http/app.go index aa8f9a1..e2ff799 100644 --- a/internal/app/http/app.go +++ b/internal/app/http/app.go @@ -74,7 +74,9 @@ func IncludeDonatHandlers( server.GET(PREFIX+"/inner-donate-page", GetInnerDonatePage(donatService)) server.GET(PREFIX+"/outer-donate-page/:streamer-login", GetOuterDonatePage(donatService)) server.PATCH(PREFIX+"/donat-page", UpdateDonatePage(donatService, fileService)) + server.GET(PREFIX+"/voice-settings", GetVoiceSettings(donatService)) + server.PATCH(PREFIX+"/voice-settings", UpdateVoiceSettings(donatService)) server.GET(PREFIX+"/donat/get/:streamerID", GetDonat(donatService)) diff --git a/internal/docs/docs.go b/internal/docs/docs.go index 685c244..378f56d 100644 --- a/internal/docs/docs.go +++ b/internal/docs/docs.go @@ -571,6 +571,11 @@ const docTemplate = `{ } }, "patch": { + "security": [ + { + "BearerAuth": [] + } + ], "description": "Update donat voice settings.", "consumes": [ "application/json" @@ -591,12 +596,6 @@ const docTemplate = `{ "schema": { "$ref": "#/definitions/donat-widget_internal_model.UpdateVoiceSettings" } - }, - { - "type": "file", - "description": "Background image", - "name": "background", - "in": "formData" } ], "responses": { @@ -1122,6 +1121,12 @@ const docTemplate = `{ "enable": { "type": "boolean" }, + "languages": { + "type": "array", + "items": { + "type": "string" + } + }, "min_price": { "type": "integer" }, diff --git a/internal/docs/swagger.json b/internal/docs/swagger.json index a7f2af1..5ed0e21 100644 --- a/internal/docs/swagger.json +++ b/internal/docs/swagger.json @@ -564,6 +564,11 @@ } }, "patch": { + "security": [ + { + "BearerAuth": [] + } + ], "description": "Update donat voice settings.", "consumes": [ "application/json" @@ -584,12 +589,6 @@ "schema": { "$ref": "#/definitions/donat-widget_internal_model.UpdateVoiceSettings" } - }, - { - "type": "file", - "description": "Background image", - "name": "background", - "in": "formData" } ], "responses": { @@ -1115,6 +1114,12 @@ "enable": { "type": "boolean" }, + "languages": { + "type": "array", + "items": { + "type": "string" + } + }, "min_price": { "type": "integer" }, diff --git a/internal/docs/swagger.yaml b/internal/docs/swagger.yaml index cc82e80..05b2345 100644 --- a/internal/docs/swagger.yaml +++ b/internal/docs/swagger.yaml @@ -228,6 +228,10 @@ definitions: properties: enable: type: boolean + languages: + items: + type: string + type: array min_price: type: integer scenery: @@ -659,10 +663,6 @@ paths: required: true schema: $ref: '#/definitions/donat-widget_internal_model.UpdateVoiceSettings' - - description: Background image - in: formData - name: background - type: file produces: - application/json responses: @@ -682,6 +682,8 @@ paths: description: Validation error schema: $ref: '#/definitions/echo.HTTPError' + security: + - BearerAuth: [] summary: Update donat voice settings. tags: - Donate diff --git a/internal/model/interfaces.go b/internal/model/interfaces.go index efcda84..f03791d 100644 --- a/internal/model/interfaces.go +++ b/internal/model/interfaces.go @@ -91,7 +91,7 @@ type DonatService interface { headImg multipart.FileHeader, ) error GetVoiceSettings(ctx context.Context, streamerID int) (VoiceSettingsResponse, error) - UpdateVoiceSettings(ctx context.Context, streamerID StreamerID, updateModel UpdateVoiceSettings) error + UpdateVoiceSettings(ctx context.Context, streamerID int, updateModel UpdateVoiceSettings) error GetFiltersSettings(ctx context.Context, streamerID StreamerID) (FilterSettingResponse, error) UpdateFiltersSettings(ctx context.Context, streamerID StreamerID, updateModel UpdateFilterSettings) error @@ -121,7 +121,13 @@ type DonatRepo interface { textAfterDonation *string, ) error GetVoiceSettingsByStreamerID(ctx context.Context, streamerID int) (VoiceSettingsResponse, error) + UpdateVoiceSettings(ctx context.Context, streamerID int, updateModel UpdateVoiceSettings) error + GetLanguagesByStreamerID(ctx context.Context, streamerID int) ([]Language, error) + DeleteLanguagesByVoiceSettingID(ctx context.Context, voiceSettingID int) error + InsertLanguagesForVoiceSetting(ctx context.Context, voiceSettingID int, languageIDs []int) error + GetLanguageIDsByISOCodes(ctx context.Context, isoCodes []string) ([]int, error) + GetVoiceSettingIDByStreamerID(ctx context.Context, streamerID int) (int, error) } type TargetService interface { @@ -158,6 +164,7 @@ type Db interface { SelectOne(ctx context.Context, query string, args ...interface{}) (pgx.Row, error) Delete(ctx context.Context, query string, args ...interface{}) error Update(ctx context.Context, query string, args ...interface{}) error + Exec(ctx context.Context, query string, args ...interface{}) error CreateTable(ctx context.Context, query string) error DropTable(ctx context.Context, query string) error diff --git a/internal/model/models.go b/internal/model/models.go index 32294de..c8cfbdf 100644 --- a/internal/model/models.go +++ b/internal/model/models.go @@ -199,11 +199,12 @@ type VoiceSettingsResponse struct { } type UpdateVoiceSettings struct { - Enable bool `json:"enable"` - VoiceSpeed int `json:"voice_speed"` - Scenery string `json:"scenery"` - VoiceSoundPercent int `json:"voice_sound_percent"` - MinPrice int `json:"min_price"` + Enable *bool `json:"enable"` + VoiceSpeed *int `json:"voice_speed"` + Scenery *string `json:"scenery"` + VoiceSoundPercent *int `json:"voice_sound_percent"` + MinPrice *int `json:"min_price"` + Languages []string `json:"languages"` } type FilterSettingResponse struct { diff --git a/internal/model/sql/query.go b/internal/model/sql/query.go index e329d9b..bc0c195 100644 --- a/internal/model/sql/query.go +++ b/internal/model/sql/query.go @@ -248,3 +248,33 @@ var GetLanguagesByStreamerID = ` JOIN voice_settings vs ON vl.voice_setting_id = vs.id WHERE vs.streamer_id = (@streamer_id); ` + +var DeleteLanguage = ` +DELETE FROM voices_languages +WHERE voice_setting_id = @voice_setting_id; +` + +var InsertLanguagesForVoiceSetting = ` +INSERT INTO voices_languages (voice_setting_id, language_id) +VALUES %s; +` + +const GetLangByISO = ` +SELECT id FROM languages WHERE iso_code = ANY(@iso_codes); +` + +var VoiceIDByStreamer = ` + SELECT id FROM voice_settings WHERE streamer_id = @streamer_id; + +` + +const UpdateVoiceSettings = ` +UPDATE voice_settings +SET + enable = COALESCE(@enable, enable), + voice_speed = COALESCE(@voice_speed, voice_speed), + scenery = COALESCE(@scenery, scenery), + voice_sound_percent = COALESCE(@voice_sound_percent, voice_sound_percent), + min_price = COALESCE(@min_price, min_price) +WHERE streamer_id = @streamer_id; +` diff --git a/internal/repository/donat/donat.go b/internal/repository/donat/donat.go index 5333774..93aea37 100644 --- a/internal/repository/donat/donat.go +++ b/internal/repository/donat/donat.go @@ -5,9 +5,11 @@ import ( "donat-widget/internal/model" "donat-widget/internal/model/sql" "errors" + "fmt" "github.com/georgysavva/scany/v2/pgxscan" "github.com/jackc/pgx/v5" "log/slog" + "strings" ) func New(db model.Db) *RepoDonat { @@ -279,3 +281,140 @@ func (repoDonat *RepoDonat) GetVoiceSettingsByStreamerID( return *voiceSettings[0], nil } + +func (repoDonat *RepoDonat) DeleteLanguagesByVoiceSettingID( + ctx context.Context, + voiceSettingID int, +) error { + args := pgx.NamedArgs{ + "voice_setting_id": voiceSettingID, + } + + err := repoDonat.db.Exec(ctx, sql.DeleteLanguage, args) + if err != nil { + slog.Error("Failed to delete languages", "error", err) + return err + } + + return nil +} + +func (repoDonat *RepoDonat) InsertLanguagesForVoiceSetting( + ctx context.Context, + voiceSettingID int, + languageIDs []int, +) error { + if len(languageIDs) == 0 { + return nil // Нет языков для вставки + } + + // Формируем список значений для вставки + var valueStrings []string + var valueArgs []interface{} + for i, languageID := range languageIDs { + valueStrings = append(valueStrings, fmt.Sprintf("($%d, $%d)", i*2+1, i*2+2)) + valueArgs = append(valueArgs, voiceSettingID, languageID) + } + + query := fmt.Sprintf(sql.InsertLanguagesForVoiceSetting, strings.Join(valueStrings, ",")) + err := repoDonat.db.Exec(ctx, query, valueArgs...) + if err != nil { + slog.Error("Failed to insert languages", "error", err) + return err + } + + return nil +} + +func (repoDonat *RepoDonat) GetLanguageIDsByISOCodes( + ctx context.Context, + isoCodes []string, +) ([]int, error) { + args := pgx.NamedArgs{ + "iso_codes": isoCodes, + } + + rows, err := repoDonat.db.Select(ctx, sql.GetLangByISO, args) + if err != nil { + slog.Error("Failed to get language IDs by ISO codes", "error", err) + return nil, err + } + + var languageIDs []int + for rows.Next() { + var languageID int + err := rows.Scan(&languageID) + if err != nil { + slog.Error("Failed to scan language ID", "error", err) + return nil, err + } + languageIDs = append(languageIDs, languageID) + } + + if err := rows.Err(); err != nil { + slog.Error("Error during rows iteration", "error", err) + return nil, err + } + + return languageIDs, nil +} + +func (repoDonat *RepoDonat) GetVoiceSettingIDByStreamerID( + ctx context.Context, + streamerID int, +) (int, error) { + args := pgx.NamedArgs{ + "streamer_id": streamerID, + } + + var voiceSettingID int + row, err := repoDonat.db.SelectOne(ctx, sql.VoiceIDByStreamer, args) + if err != nil { + slog.Error("Failed to get voice setting ID by streamer ID", "error", err) + return 0, err + } + + err = row.Scan(&voiceSettingID) + if err != nil { + slog.Error(err.Error()) + return 0, err + } + + return voiceSettingID, nil +} + +func (repoDonat *RepoDonat) UpdateVoiceSettings( + ctx context.Context, + streamerID int, + settings model.UpdateVoiceSettings, +) error { + args := pgx.NamedArgs{ + "streamer_id": streamerID, + } + + // Добавляем только те поля, которые не nil + if settings.Enable != nil { + args["enable"] = *settings.Enable + } + if settings.VoiceSpeed != nil { + args["voice_speed"] = *settings.VoiceSpeed + } + if settings.Scenery != nil { + args["scenery"] = *settings.Scenery + } + if settings.VoiceSoundPercent != nil { + args["voice_sound_percent"] = *settings.VoiceSoundPercent + } + if settings.MinPrice != nil { + args["min_price"] = *settings.MinPrice + } + + // Выполняем SQL-запрос + err := repoDonat.db.Update(ctx, sql.UpdateVoiceSettings, args) + if err != nil { + slog.Error("Failed to update voice settings", "error", err) + return err + } + + return nil +} diff --git a/internal/service/donat/donat.go b/internal/service/donat/donat.go index 19482b6..d948a35 100644 --- a/internal/service/donat/donat.go +++ b/internal/service/donat/donat.go @@ -310,9 +310,43 @@ func (donatService *ServiceDonat) GetVoiceSettings( func (donatService *ServiceDonat) UpdateVoiceSettings( ctx context.Context, - streamerID model.StreamerID, + streamerID int, updateModel model.UpdateVoiceSettings, ) error { + // Получаем voice_setting_id для streamerID + voiceSettingID, err := donatService.donatRepo.GetVoiceSettingIDByStreamerID(ctx, streamerID) + if err != nil { + slog.Error("Failed to get voice setting ID", "error", err) + return err + } + + // Удаляем старые языки + err = donatService.donatRepo.DeleteLanguagesByVoiceSettingID(ctx, voiceSettingID) + if err != nil { + slog.Error("Failed to delete old languages", "error", err) + return err + } + + // Получаем ID всех языков по их ISO-кодам + languageIDs, err := donatService.donatRepo.GetLanguageIDsByISOCodes(ctx, updateModel.Languages) + if err != nil { + slog.Error("Failed to get language IDs by ISO codes", "error", err) + return err + } + + // Добавляем новые языки (пакетная вставка) + err = donatService.donatRepo.InsertLanguagesForVoiceSetting(ctx, voiceSettingID, languageIDs) + if err != nil { + slog.Error("Failed to insert new languages", "error", err) + return err + } + + err = donatService.donatRepo.UpdateVoiceSettings(ctx, streamerID, updateModel) + if err != nil { + slog.Error("Failed to update voice settings", "error", err) + return err + } + return nil }