diff --git a/cmd/main.go b/cmd/main.go index a0f0c70..c608806 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -53,7 +53,13 @@ func main() { // SERVICES widgetService := WidgetService.New(widgetRepo, authClient) - donatService := DonatService.New(donatRepo, widgetRepo, paymentClient, authClient) + donatService := DonatService.New( + donatRepo, + widgetRepo, + paymentClient, + authClient, + storage, + ) targetService := TargetService.New(targetRepo, authClient) http.NewApp( diff --git a/infrastructure/sysStorage/sysStorage.go b/infrastructure/sysStorage/sysStorage.go index 1ad56fe..0dd46ba 100644 --- a/infrastructure/sysStorage/sysStorage.go +++ b/infrastructure/sysStorage/sysStorage.go @@ -4,6 +4,7 @@ import ( "bytes" "donat-widget/internal/model" "io" + "mime/multipart" "os" "path/filepath" ) @@ -28,16 +29,18 @@ func NewLocalStorage(basePath string) *LocalStorage { } func (ls *LocalStorage) Upload( - file model.UploadFile, + file *multipart.FileHeader, filename string, size int64, collection string, ) (model.FileID, error) { + // Создаем путь для коллекции, если он не существует collectionPath := filepath.Join(ls.basePath, collection) if err := os.MkdirAll(collectionPath, os.ModePerm); err != nil { return "error", err } + // Создаем путь для файла filePath := filepath.Join(collectionPath, filename) f, err := os.Create(filePath) if err != nil { @@ -45,17 +48,26 @@ func (ls *LocalStorage) Upload( } defer f.Close() - _, err = io.Copy(f, *file) + // Открываем исходный файл для чтения + srcFile, err := file.Open() + if err != nil { + return "error opening file", err + } + defer srcFile.Close() + + // Копируем содержимое файла + _, err = io.Copy(f, srcFile) if err != nil { return "error copy", err } + // Возвращаем идентификатор файла return model.FileID(filename), nil } func (ls *LocalStorage) Download( FileID model.FileID, -) (model.DownloadFile, error) { +) ([]byte, error) { filePath := string(FileID) f, err := os.Open(filePath) if err != nil { @@ -73,20 +85,31 @@ func (ls *LocalStorage) Download( } func (ls *LocalStorage) Update( - file model.UploadFile, + file *multipart.FileHeader, fileID model.FileID, filename string, size int64, collection string, ) error { + // Получаем путь для хранения файла filePath := string(fileID) + + // Создаем новый файл f, err := os.Create(filePath) if err != nil { return err } defer f.Close() - _, err = io.Copy(f, *file) + // Открываем файл из multipart.FileHeader + srcFile, err := file.Open() + if err != nil { + return err + } + defer srcFile.Close() + + // Копируем содержимое из исходного файла в новый файл + _, err = io.Copy(f, srcFile) if err != nil { return err } diff --git a/internal/api/http/handlers/donat/donat.go b/internal/api/http/handlers/donat/donat.go index 40920b2..afff574 100644 --- a/internal/api/http/handlers/donat/donat.go +++ b/internal/api/http/handlers/donat/donat.go @@ -3,8 +3,8 @@ package donat import ( "context" "donat-widget/internal/model" - "donat-widget/pkg/validator" "donat-widget/pkg/custom_response" + "donat-widget/pkg/validator" "fmt" "github.com/google/uuid" "github.com/labstack/echo/v4" @@ -125,6 +125,7 @@ func MarkDonatView(donatService model.DonatService) echo.HandlerFunc { // @Tags Donate // @Accept json // @Produce json +// @Security BearerAuth // @Success 200 {object} model.InnerDonatePageResponse "Current donate page state" // @Failure 400 {object} echo.HTTPError "Bad request" // @Failure 401 {object} echo.HTTPError "Unauthorized or expired token" @@ -159,7 +160,7 @@ func GetInnerDonatePage(donatService model.DonatService) echo.HandlerFunc { // @Failure 400 {object} echo.HTTPError "Bad request" // @Failure 401 {object} echo.HTTPError "Unauthorized or expired token" // @Failure 422 {object} echo.HTTPError "Validation error" -// @Router /outer-donate-page/:streamer-login [get] +// @Router /outer-donate-page/{streamer-login} [get] func GetOuterDonatePage(donatService model.DonatService) echo.HandlerFunc { return func(request echo.Context) error { ctx := context.Background() @@ -181,8 +182,10 @@ func GetOuterDonatePage(donatService model.DonatService) echo.HandlerFunc { // @Tags Donate // @Accept json // @Produce json +// @Security BearerAuth // @Param request body model.UpdateDonatPage true "Update fields" // @Param background formData file false "Background image" +// @Param head-img formData file false "Head image" // @Success 200 {string} string "Donat page updated successfully" // @Failure 400 {object} echo.HTTPError "Bad request" // @Failure 401 {object} echo.HTTPError "Unauthorized or expired token" @@ -190,31 +193,41 @@ func GetOuterDonatePage(donatService model.DonatService) echo.HandlerFunc { // @Router /donat-page [patch] func UpdateDonatePage(donatService model.DonatService) echo.HandlerFunc { return func(request echo.Context) error { + ctx := context.Background() + var body model.UpdateDonatPage err := validator.ParseAndValidate(&body, request) if err != nil { slog.Error(err.Error()) return echo.NewHTTPError(http.StatusUnprocessableEntity, "Unprocessable Entity") } - form, err := request.MultipartForm() + + authData, err := donatService.CheckToken(request) + //if err != nil { + // slog.Error(err.Error()) + // return echo.NewHTTPError(http.StatusUnauthorized, custom_response.Unauthorized) + //} + + background, err := request.FormFile("background") if err != nil { - slog.Error(err.Error()) - return echo.NewHTTPError(http.StatusUnprocessableEntity, "Unprocessable Entity") + background = nil + } + headImg, err := request.FormFile("head_img") + if err != nil { + headImg = nil } - if form != nil { - if backgroundFiles, ok := form.File["background"]; ok && len(backgroundFiles) > 0 { - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to save background") - } + err = donatService.UpdateDonatePage( + ctx, + model.StreamerID(authData.AccountID), + body, + background, + headImg, + ) + if err != nil { + return echo.NewHTTPError(http.StatusInternalServerError, custom_response.InternalError) } - - // Проверяем и сохраняем avatar, если он передан - if avatarFiles, ok := form.File["avatar"]; ok && len(avatarFiles) > 0 { - - return echo.NewHTTPError(http.StatusInternalServerError, "Failed to save avatar") - } - - return nil + return request.JSON(http.StatusOK, "Success") } } diff --git a/internal/app/http/app.go b/internal/app/http/app.go index 8c2b2a9..e6f57e8 100644 --- a/internal/app/http/app.go +++ b/internal/app/http/app.go @@ -74,6 +74,8 @@ func IncludeDonatHandlers( server.POST(PREFIX+"/donat/create", CreateDonat(donatService)) 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)) server.GET(PREFIX+"/donat/get/:streamerID", GetDonat(donatService)) diff --git a/internal/docs/docs.go b/internal/docs/docs.go index eb49dd7..8973513 100644 --- a/internal/docs/docs.go +++ b/internal/docs/docs.go @@ -17,6 +17,11 @@ const docTemplate = `{ "paths": { "/donat-page": { "patch": { + "security": [ + { + "BearerAuth": [] + } + ], "description": "Update personal streamer donate page.", "consumes": [ "application/json" @@ -43,6 +48,12 @@ const docTemplate = `{ "description": "Background image", "name": "background", "in": "formData" + }, + { + "type": "file", + "description": "Head image", + "name": "head-img", + "in": "formData" } ], "responses": { @@ -172,6 +183,11 @@ const docTemplate = `{ }, "/inner-donate-page": { "get": { + "security": [ + { + "BearerAuth": [] + } + ], "description": "Get inner donate page info", "consumes": [ "application/json" @@ -291,7 +307,7 @@ const docTemplate = `{ } } }, - "/outer-donate-page": { + "/outer-donate-page/{streamer-login}": { "get": { "description": "Get outer donate page info", "consumes": [ @@ -304,6 +320,15 @@ const docTemplate = `{ "Donate" ], "summary": "Get outer donate page info", + "parameters": [ + { + "type": "string", + "description": "Login стримера", + "name": "streamer-login", + "in": "path", + "required": true + } + ], "responses": { "200": { "description": "Current donate page state", @@ -457,7 +482,7 @@ const docTemplate = `{ "description": { "type": "string" }, - "pageBackground": { + "headImg": { "type": "string" }, "textAfterDonat": { @@ -479,9 +504,18 @@ const docTemplate = `{ "donat-widget_internal_model.OuterDonatePageResponse": { "type": "object", "properties": { + "avatar_url": { + "type": "string" + }, + "background_url": { + "type": "string" + }, "description": { "type": "string" }, + "headImg": { + "type": "string" + }, "login": { "type": "string" }, diff --git a/internal/docs/swagger.json b/internal/docs/swagger.json index 9abfd06..e03811a 100644 --- a/internal/docs/swagger.json +++ b/internal/docs/swagger.json @@ -10,6 +10,11 @@ "paths": { "/donat-page": { "patch": { + "security": [ + { + "BearerAuth": [] + } + ], "description": "Update personal streamer donate page.", "consumes": [ "application/json" @@ -36,6 +41,12 @@ "description": "Background image", "name": "background", "in": "formData" + }, + { + "type": "file", + "description": "Head image", + "name": "head-img", + "in": "formData" } ], "responses": { @@ -165,6 +176,11 @@ }, "/inner-donate-page": { "get": { + "security": [ + { + "BearerAuth": [] + } + ], "description": "Get inner donate page info", "consumes": [ "application/json" @@ -284,7 +300,7 @@ } } }, - "/outer-donate-page": { + "/outer-donate-page/{streamer-login}": { "get": { "description": "Get outer donate page info", "consumes": [ @@ -297,6 +313,15 @@ "Donate" ], "summary": "Get outer donate page info", + "parameters": [ + { + "type": "string", + "description": "Login стримера", + "name": "streamer-login", + "in": "path", + "required": true + } + ], "responses": { "200": { "description": "Current donate page state", @@ -450,7 +475,7 @@ "description": { "type": "string" }, - "pageBackground": { + "headImg": { "type": "string" }, "textAfterDonat": { @@ -472,9 +497,18 @@ "donat-widget_internal_model.OuterDonatePageResponse": { "type": "object", "properties": { + "avatar_url": { + "type": "string" + }, + "background_url": { + "type": "string" + }, "description": { "type": "string" }, + "headImg": { + "type": "string" + }, "login": { "type": "string" }, diff --git a/internal/docs/swagger.yaml b/internal/docs/swagger.yaml index 82d3bd4..30c793c 100644 --- a/internal/docs/swagger.yaml +++ b/internal/docs/swagger.yaml @@ -17,7 +17,7 @@ definitions: type: string description: type: string - pageBackground: + headImg: type: string textAfterDonat: type: string @@ -31,8 +31,14 @@ definitions: type: object donat-widget_internal_model.OuterDonatePageResponse: properties: + avatar_url: + type: string + background_url: + type: string description: type: string + headImg: + type: string login: type: string online: @@ -119,6 +125,10 @@ paths: in: formData name: background type: file + - description: Head image + in: formData + name: head-img + type: file produces: - application/json responses: @@ -138,6 +148,8 @@ paths: description: Validation error schema: $ref: '#/definitions/echo.HTTPError' + security: + - BearerAuth: [] summary: Update personal streamer donate page. tags: - Donate @@ -229,6 +241,8 @@ paths: description: Validation error schema: $ref: '#/definitions/echo.HTTPError' + security: + - BearerAuth: [] summary: Get inner donate page info tags: - Donate @@ -285,11 +299,17 @@ paths: summary: Update donat moderation settings tags: - Donate - /outer-donate-page: + /outer-donate-page/{streamer-login}: get: consumes: - application/json description: Get outer donate page info + parameters: + - description: Login стримера + in: path + name: streamer-login + required: true + type: string produces: - application/json responses: diff --git a/internal/model/interfaces.go b/internal/model/interfaces.go index 4e22b60..f949791 100644 --- a/internal/model/interfaces.go +++ b/internal/model/interfaces.go @@ -5,6 +5,7 @@ import ( "donat-widget/internal/model/api" "github.com/jackc/pgx/v5" "github.com/labstack/echo/v4" + "mime/multipart" ) type WidgetService interface { @@ -46,8 +47,13 @@ type DonatService interface { GetInnerDonatPage(ctx context.Context, streamerID StreamerID) (InnerDonatePageResponse, error) GetOuterDonatPage(ctx context.Context, streamerLogin string) (OuterDonatePageResponse, error) - UpdateDonatePage(ctx context.Context, streamerID StreamerID, updateModel UpdateDonatPage) error - + UpdateDonatePage( + ctx context.Context, + streamerID StreamerID, + updateModel UpdateDonatPage, + background *multipart.FileHeader, + headImg *multipart.FileHeader, + ) error GetVoiceSettings(ctx context.Context, streamerID StreamerID) (VoiceSettingsResponse, error) UpdateVoiceSettings(ctx context.Context, streamerID StreamerID, updateModel UpdateVoiceSettings) error @@ -90,9 +96,9 @@ type Error interface { } type Storage interface { - Upload(file UploadFile, filename string, size int64, collection string) (FileID, error) - Download(FileID FileID) (DownloadFile, error) - Update(file UploadFile, fileID FileID, filename string, size int64, collection string) error + Upload(file *multipart.FileHeader, filename string, size int64, collection string) (FileID, error) + Download(FileID FileID) ([]byte, error) + Update(file *multipart.FileHeader, fileID FileID, filename string, size int64, collection string) error } type PaymentClient interface { diff --git a/internal/model/types.go b/internal/model/types.go index 5f974ce..dfc4cac 100644 --- a/internal/model/types.go +++ b/internal/model/types.go @@ -18,7 +18,7 @@ type Widgets []*Widget type WidgetHTML string type WeedData *goseaweedfs.FilePart -type UploadFile *multipart.File +type UploadFile *multipart.FileHeader type DownloadFile []byte type FileID string diff --git a/internal/service/donat/donat.go b/internal/service/donat/donat.go index ad79b19..655cfbf 100644 --- a/internal/service/donat/donat.go +++ b/internal/service/donat/donat.go @@ -6,6 +6,7 @@ import ( "donat-widget/internal/model/api" "github.com/labstack/echo/v4" "log/slog" + "mime/multipart" "net/http" ) @@ -14,6 +15,7 @@ type ServiceDonat struct { widgetRepo model.WidgetRepo authClient model.AuthClient paymentClient model.PaymentClient + storage model.Storage } func New( @@ -21,12 +23,14 @@ func New( widgetRepo model.WidgetRepo, paymentClient model.PaymentClient, authClient model.AuthClient, + storage model.Storage, ) *ServiceDonat { return &ServiceDonat{ donatRepo: donatRepo, widgetRepo: widgetRepo, paymentClient: paymentClient, authClient: authClient, + storage: storage, } } @@ -193,8 +197,24 @@ func (donatService *ServiceDonat) UpdateDonatePage( ctx context.Context, streamerID model.StreamerID, updateModel model.UpdateDonatPage, + background *multipart.FileHeader, + headImg *multipart.FileHeader, ) error { + if background != nil { + err := donatService.storage.Update( + background, // ❌ Было &background → ✅ Просто background + model.FileID("some-id"), + "test-filename", + 999999, + "hello world", + ) + if err != nil { + slog.Error(err.Error()) + } + } + return nil + } func (donatService *ServiceDonat) GetVoiceSettings( diff --git a/pkg/custom_response/custom_response.go b/pkg/custom_response/custom_response.go index de34ea7..6d310b1 100644 --- a/pkg/custom_response/custom_response.go +++ b/pkg/custom_response/custom_response.go @@ -12,6 +12,7 @@ const ( AccountNotFound = "Account not found" NotVerified2FA = "2FA not verified" NotExists2FA = "2FA does not exist" + Unauthorized = "Unauthorized" ) // Success response