From 13a80221ed7ef2002f1c59a4a5cf0a3ee14579a9 Mon Sep 17 00:00:00 2001 From: mm Date: Sun, 20 Oct 2024 22:42:26 +0500 Subject: [PATCH] dev --- .github/workflows/CI-CD.yml | 40 +-- cmd/main.go | 24 +- go.mod | 3 +- go.sum | 2 + internal/api/http/handlers/donat/donat.go | 95 +++++++ internal/api/http/handlers/target/target.go | 1 + .../api/http/handlers/widget/donat/donat.go | 76 ------ .../api/http/handlers/widget/donat/models.go | 10 - .../api/http/handlers/widget/media/media.go | 165 ------------ .../api/http/handlers/widget/media/models.go | 8 - internal/api/http/handlers/widget/models.go | 27 -- internal/api/http/handlers/widget/widget.go | 235 +++++++++++++----- internal/app/http/app.go | 22 +- internal/config/config.go | 10 +- internal/config/config.yaml | 6 +- internal/model/api/tinkoff.go | 12 + internal/model/interfaces.go | 29 +-- internal/model/models.go | 70 ++++-- internal/model/sql/model.go | 36 ++- internal/model/sql/query.go | 47 ++-- internal/model/types.go | 6 +- .../repository/{widget => }/donat/donat.go | 36 ++- internal/repository/target/target.go | 1 + internal/repository/widget/media/media.go | 135 ---------- internal/repository/widget/widget.go | 150 ++++++++++- internal/service/donat/donat.go | 92 +++++++ internal/service/target/target.go | 1 + internal/service/widget/donat/donat.go | 55 ---- internal/service/widget/media/media.go | 131 ---------- internal/service/widget/widget.go | 114 +++++++++ pkg/api/payment/tinkoff.go | 71 ++++++ 31 files changed, 912 insertions(+), 798 deletions(-) create mode 100644 internal/api/http/handlers/donat/donat.go create mode 100644 internal/api/http/handlers/target/target.go delete mode 100644 internal/api/http/handlers/widget/donat/donat.go delete mode 100644 internal/api/http/handlers/widget/donat/models.go delete mode 100644 internal/api/http/handlers/widget/media/media.go delete mode 100644 internal/api/http/handlers/widget/media/models.go delete mode 100644 internal/api/http/handlers/widget/models.go create mode 100644 internal/model/api/tinkoff.go rename internal/repository/{widget => }/donat/donat.go (63%) create mode 100644 internal/repository/target/target.go delete mode 100644 internal/repository/widget/media/media.go create mode 100644 internal/service/donat/donat.go create mode 100644 internal/service/target/target.go delete mode 100644 internal/service/widget/donat/donat.go delete mode 100644 internal/service/widget/media/media.go create mode 100644 pkg/api/payment/tinkoff.go diff --git a/.github/workflows/CI-CD.yml b/.github/workflows/CI-CD.yml index 64c7649..976bdc8 100644 --- a/.github/workflows/CI-CD.yml +++ b/.github/workflows/CI-CD.yml @@ -22,23 +22,23 @@ jobs: dockerhub-st.ru/donat-widget:latest dockerhub-st.ru/donat-widget:${{ github.sha }} -# deploy: -# runs-on: ubuntu-latest -# steps: -# - name: Checkout code -# uses: actions/checkout@v2 -# -# - name: Install kubectl -# uses: azure/setup-kubectl@v1 -# with: -# version: 'v1.21.0' -# -# - name: Configure kube-config -# run: | -# mkdir -p $HOME/.kube -# echo "${{ secrets.KUBE_CONFIG }}" | base64 --decode > $HOME/.kube/config -# export KUBECONFIG=$HOME/.kube/config -# -# - name: Update deployment -# run: | -# kubectl restart rollout deployment donat-user-deployment \ No newline at end of file + deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Install kubectl + uses: azure/setup-kubectl@v1 + with: + version: 'v1.21.0' + + - name: Configure kube-config + run: | + mkdir -p $HOME/.kube + echo "${{ secrets.KUBE_CONFIG }}" | base64 --decode > $HOME/.kube/config + export KUBECONFIG=$HOME/.kube/config + + - name: Update deployment + run: | + kubectl restart rollout deployment donat-user-deployment \ No newline at end of file diff --git a/cmd/main.go b/cmd/main.go index eca285b..f089482 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -4,29 +4,21 @@ import ( "context" "donat-widget/internal/app/http" "donat-widget/internal/config" + DonatRepo "donat-widget/internal/repository/donat" + DonatService "donat-widget/internal/service/donat" ) import ( "donat-widget/infrastructure/pg" "donat-widget/infrastructure/weed" + PaymentClient "donat-widget/pkg/api/payment" ) import ( WidgetRepo "donat-widget/internal/repository/widget" - DonatRepo "donat-widget/internal/repository/widget/donat" - MediaRepo "donat-widget/internal/repository/widget/media" WidgetService "donat-widget/internal/service/widget" - DonatService "donat-widget/internal/service/widget/donat" - MediaService "donat-widget/internal/service/widget/media" ) -// @title Widget service -// @version 1.0 -// @description Описание. - -// @host localhost:8002 -// @BasePath /api/widget - func main() { cfg := config.Init() @@ -34,20 +26,20 @@ func main() { db := pg.NewPgPool(context.Background(), cfg.Db.Username, cfg.Db.Password, cfg.Db.Host, cfg.Db.Port, cfg.Db.DBName) storage := weed.NewWeed(cfg.Storage.Filer, cfg.Storage.Master) + // CLIENTS + paymentClient := PaymentClient.New(cfg.PaymentService.Host, cfg.PaymentService.Port) + // REPOSITORIES - widgetRepo := WidgetRepo.New(db) - mediaRepo := MediaRepo.New(db, storage) + widgetRepo := WidgetRepo.New(db, storage) donatRepo := DonatRepo.New(db) // SERVICES widgetService := WidgetService.New(widgetRepo, donatRepo) - mediaService := MediaService.New(mediaRepo) - donatService := DonatService.New(donatRepo) + donatService := DonatService.New(donatRepo, widgetRepo, paymentClient) http.NewApp( db, widgetService, - mediaService, donatService, ) } diff --git a/go.mod b/go.mod index b0837c3..a3b56a7 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,10 @@ go 1.23.0 require ( github.com/georgysavva/scany/v2 v2.1.3 github.com/go-playground/validator/v10 v10.22.0 + github.com/google/uuid v1.6.0 github.com/jackc/pgx/v5 v5.6.0 github.com/labstack/echo/v4 v4.12.0 + github.com/linxGnu/goseaweedfs v0.1.6 github.com/swaggo/echo-swagger v1.4.1 github.com/swaggo/swag v1.16.3 gopkg.in/yaml.v2 v2.4.0 @@ -29,7 +31,6 @@ require ( github.com/josharian/intern v1.0.0 // indirect github.com/labstack/gommon v0.4.2 // indirect github.com/leodido/go-urn v1.4.0 // indirect - github.com/linxGnu/goseaweedfs v0.1.6 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect diff --git a/go.sum b/go.sum index 0e85cec..46ebe73 100644 --- a/go.sum +++ b/go.sum @@ -30,6 +30,8 @@ github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4 github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= diff --git a/internal/api/http/handlers/donat/donat.go b/internal/api/http/handlers/donat/donat.go new file mode 100644 index 0000000..5077ca2 --- /dev/null +++ b/internal/api/http/handlers/donat/donat.go @@ -0,0 +1,95 @@ +package donat + +import ( + "context" + "donat-widget/internal/model" + "github.com/google/uuid" + "github.com/labstack/echo/v4" + "log/slog" + "net/http" + "strconv" +) + +func CreateDonat(donatService model.DonatService) echo.HandlerFunc { + type Body struct { + StreamerID model.StreamerID `json:"streamerID"` + TargetID model.TargetID `json:"targetID"` + Amount model.DonatAmount `json:"amount"` + Text string `json:"text"` + DonatUser string `json:"donatUser"` + } + return func(request echo.Context) error { + ctx := context.Background() + var body Body + if err := request.Bind(&body); err != nil { + slog.Error(err.Error()) + return echo.NewHTTPError(400, err.Error()) + } + if err := request.Validate(&body); err != nil { + slog.Error(err.Error()) + return echo.NewHTTPError(400, err.Error()) + } + + orderID := model.OrderID(uuid.New().String()) + + err := donatService.CreateDonat( + ctx, + body.StreamerID, + orderID, + body.Amount, + body.Text, + body.DonatUser, + ) + if err != nil { + return request.JSON(500, "Set donat error") + } + + return request.String(200, "Set donat success") + } +} + +func MarkDonatPaid(donatService model.DonatService) echo.HandlerFunc { + type Body struct { + OrderID model.OrderID `json:"orderID"` + } + return func(request echo.Context) error { + ctx := context.Background() + var body Body + if err := request.Bind(&body); err != nil { + slog.Error(err.Error()) + return echo.NewHTTPError(400, err.Error()) + } + if err := request.Validate(&body); err != nil { + slog.Error(err.Error()) + return echo.NewHTTPError(400, err.Error()) + } + + err := donatService.MarkDonatPaid(ctx, body.OrderID) + if err != nil { + slog.Error("donatService.MarkDonatPaid error: " + err.Error()) + return echo.NewHTTPError(http.StatusInternalServerError, err.Error()) + } + return request.String(200, "Donat paid success") + } +} + +func MarkDonatView(donatService model.DonatService) echo.HandlerFunc { + return func(request echo.Context) error { + ctx := context.Background() + + donatID, err := strconv.Atoi(request.Param("donatID")) + if err != nil { + return echo.NewHTTPError(400, "Path parameter 'donatID' is invalid") + } + + err = donatService.MarkDonatView( + ctx, + model.DonatID(donatID), + ) + if err != nil { + return echo.NewHTTPError(500, "Delete donat error") + } + slog.Info("Delete donat success") + return request.String(200, "donat view success") + } +} diff --git a/internal/api/http/handlers/target/target.go b/internal/api/http/handlers/target/target.go new file mode 100644 index 0000000..ec6c15d --- /dev/null +++ b/internal/api/http/handlers/target/target.go @@ -0,0 +1 @@ +package target diff --git a/internal/api/http/handlers/widget/donat/donat.go b/internal/api/http/handlers/widget/donat/donat.go deleted file mode 100644 index 3d3fa26..0000000 --- a/internal/api/http/handlers/widget/donat/donat.go +++ /dev/null @@ -1,76 +0,0 @@ -package donat - -import ( - "context" - "donat-widget/internal/model" - "github.com/labstack/echo/v4" - "log/slog" - "strconv" -) - -// SetDonat -// -// @Description Set donat -// @Tags Donat -// @Accept json -// @Produce json -// @Param RegisterData body SetDonatRequest true "Set donat" -// @Router /api/widget/donat/set [post] -func SetDonat(donatService model.DonatService) echo.HandlerFunc { - return func(request echo.Context) error { - ctx := context.Background() - - var donatData SetDonatRequest - if err := request.Bind(&donatData); err != nil { - slog.Error(err.Error()) - return echo.NewHTTPError(400, err.Error()) - } - - err := request.Validate(&donatData) - if err != nil { - slog.Error(err.Error()) - return echo.NewHTTPError(400, err.Error()) - } - - err = donatService.SetDonat( - ctx, - donatData.WidgetID, - donatData.Text, - donatData.Amount, - donatData.DonatUser, - ) - if err != nil { - return request.JSON(500, "Set donat error") - } - slog.Info("donat set success") - return request.String(200, "Set donat success") - } -} - -// DeleteDonat -// -// @Description Delete donat -// @Tags Donat -// @Accept json -// @Produce json -// @Router /api/widget/donat/delete/{donatID} [post] -func DeleteDonat(donatService model.DonatService) echo.HandlerFunc { - return func(request echo.Context) error { - ctx := context.Background() - - donatID, err := strconv.Atoi(request.Param("donatID")) - if err != nil { - return echo.NewHTTPError(400, "Path parameter 'donatID' is invalid") - } - - err = donatService.DeleteDonat( - ctx, - model.DonatID(donatID), - ) - if err != nil { - return echo.NewHTTPError(500, "Delete donat error") - } - slog.Info("Delete donat success") - return request.String(200, "Delete donat success") - } -} diff --git a/internal/api/http/handlers/widget/donat/models.go b/internal/api/http/handlers/widget/donat/models.go deleted file mode 100644 index 9acb383..0000000 --- a/internal/api/http/handlers/widget/donat/models.go +++ /dev/null @@ -1,10 +0,0 @@ -package donat - -import "donat-widget/internal/model" - -type SetDonatRequest struct { - WidgetID model.WidgetID `json:"widgetID" validate:"required"` - Text string `json:"text" validate:"required"` - Amount model.DonatAmount `json:"amount" validate:"required"` - DonatUser string `json:"donatUser" validate:"required"` -} diff --git a/internal/api/http/handlers/widget/media/media.go b/internal/api/http/handlers/widget/media/media.go deleted file mode 100644 index 35dfb5b..0000000 --- a/internal/api/http/handlers/widget/media/media.go +++ /dev/null @@ -1,165 +0,0 @@ -package media - -import ( - "context" - "donat-widget/internal/model" - "github.com/labstack/echo/v4" - "log/slog" - "strconv" -) - -// SetMediaFile -// -// @Description Upload media -// @Tags Media -// @Accept mpfd -// @Produce json -// @Param file formData file true "File to upload" -// @Param widgetId path int true "Widget ID" -// @Router /api/widget/media/{mediaType}/upload [post] -func SetMediaFile(service model.MediaService) echo.HandlerFunc { - return func(request echo.Context) error { - ctx := context.Background() - - widgetID, err := strconv.Atoi(request.FormValue("widgetID")) - if err != nil { - return echo.NewHTTPError(404, "Path parameter 'widgetID' is invalid") - } - - mediaType := request.Param("mediaType") - - file, err := request.FormFile("file") - if err != nil { - return echo.NewHTTPError(404, "Form parameter 'file' is invalid") - } - - src, err := file.Open() - if err != nil { - return echo.NewHTTPError(500, "File is invalid") - } - - err = service.SetMediaFile( - ctx, - model.MediaType(mediaType), - model.WidgetID(widgetID), - &src, - file.Filename, - file.Size, - "", - ) - if err != nil { - return echo.NewHTTPError(500, "File upload is failed") - } - slog.Info("set " + mediaType + " file successfully") - return request.String(200, "File successfully uploaded") - } -} - -// GetMediaFile -// -// @Description Get media -// @Produce application/octet-stream -// @Success 200 {array} byte -// @Param widgetId path int true "Widget ID" -// @Param mediaType path string true "'background' or 'image' or 'audio'" -// @Router /api/widget/media/{mediaType}/get/{widgetID} [post] -func GetMediaFile(service model.MediaService) echo.HandlerFunc { - return func(request echo.Context) error { - ctx := context.Background() - - mediaType := request.Param("mediaType") - if mediaType != "background" && mediaType != "image" && mediaType != "audio" { - return echo.NewHTTPError(400, "Path parameter 'mediaType' is invalid") - } - widgetID, err := strconv.Atoi(request.Param("widgetID")) - if err != nil { - return echo.NewHTTPError(400, "Path parameter 'widgetID' is invalid") - } - - file, err := service.GetMediaFile( - ctx, - model.WidgetID(widgetID), - model.MediaType(mediaType), - ) - if err != nil { - return echo.NewHTTPError(500, "Get File is failed") - } - slog.Info("get " + mediaType + " file successfully") - return request.Blob(200, "application/octet-stream", file) - } -} - -func UpdateMediaFile(service model.MediaService) echo.HandlerFunc { - return func(request echo.Context) error { - ctx := context.Background() - - widgetID, err := strconv.Atoi(request.FormValue("widgetID")) - if err != nil { - return echo.NewHTTPError(404, "Path parameter 'widgetID' is invalid") - } - - mediaType := request.Param("mediaType") - - file, err := request.FormFile("file") - if err != nil { - return echo.NewHTTPError(404, "Form parameter 'file' is invalid") - } - - src, err := file.Open() - if err != nil { - return echo.NewHTTPError(500, "File is invalid") - } - - err = service.UpdateMediaFile( - ctx, - model.WidgetID(widgetID), - model.MediaType(mediaType), - &src, - file.Filename, - file.Size, - "", - ) - - if err != nil { - return echo.NewHTTPError(500, "File update is failed") - } - slog.Info("update media file successfully") - return request.String(200, "Media successfully uploaded") - } -} - -// SetMediaUrl -// -// @Description Set media URL -// @Param mediaType path string true "'background' or 'image' or 'audio'" -// @Router /api/widget/media/{mediaType}/set [post] -func SetMediaUrl(service model.MediaService) echo.HandlerFunc { - return func(request echo.Context) error { - ctx := context.Background() - - var backgroundData SetRequest - if err := request.Bind(&backgroundData); err != nil { - return echo.NewHTTPError(400, err.Error()) - } - - mediaType := request.Param("mediaType") - - err := request.Validate(&backgroundData) - if err != nil { - - return echo.NewHTTPError(400, err.Error()) - } - - err = service.SetMediaUrl( - ctx, - model.MediaType(mediaType), - backgroundData.WidgetID, - backgroundData.MediaUrl, - ) - if err != nil { - return echo.NewHTTPError(500, "Set MediaUrl is failed") - } - slog.Info("Set media url successfully") - return request.String(200, "Media URL successfully set") - } -} diff --git a/internal/api/http/handlers/widget/media/models.go b/internal/api/http/handlers/widget/media/models.go deleted file mode 100644 index 9f2c5fd..0000000 --- a/internal/api/http/handlers/widget/media/models.go +++ /dev/null @@ -1,8 +0,0 @@ -package media - -import "donat-widget/internal/model" - -type SetRequest struct { - WidgetID model.WidgetID `json:"widgetID" validate:"required"` - MediaUrl model.MediaUrl `json:"mediaUrl" validate:"required"` -} diff --git a/internal/api/http/handlers/widget/models.go b/internal/api/http/handlers/widget/models.go deleted file mode 100644 index 8667f0d..0000000 --- a/internal/api/http/handlers/widget/models.go +++ /dev/null @@ -1,27 +0,0 @@ -package widget - -import "donat-widget/internal/model" - -type CreateWidgetRequest struct { - StreamerID model.StreamerID `json:"streamerID" validate:"required"` - TemplateID model.TemplateID `json:"templateID" validate:"required"` -} -type CreateWidgetResponse struct { - WidgetID model.WidgetID `json:"widgetID"` -} - -type GetWidgetInfoResponse struct { - AudioUrl model.MediaUrl `json:"audioUrl"` - ImageUrl model.MediaUrl `json:"imageUrl"` - Text string `json:"text"` - Amount model.DonatAmount `json:"amount"` - DonatUser string `json:"donatUser"` - Display model.Display `json:"display"` - Duration model.Duration `json:"duration"` - DonatID model.DonatID `json:"donatID"` -} - -type UpdateDurationRequest struct { - WidgetID model.WidgetID `json:"widgetID" validate:"required"` - Duration model.Duration `json:"duration" validate:"required"` -} diff --git a/internal/api/http/handlers/widget/widget.go b/internal/api/http/handlers/widget/widget.go index 13ee41e..4869abb 100644 --- a/internal/api/http/handlers/widget/widget.go +++ b/internal/api/http/handlers/widget/widget.go @@ -8,53 +8,42 @@ import ( "strconv" ) -// CreateWidget -// -// @Description Create widget -// @Tags Widget -// @Accept json -// @Produce json -// @Param RegisterData body CreateWidgetRequest true "Create widget" -// @Success 200 {object} CreateWidgetResponse -// @Router /api/widget/create [post] func CreateWidget(widgetService model.WidgetService) echo.HandlerFunc { + type Body struct { + StreamerID model.StreamerID `json:"streamerID" validate:"required"` + TemplateID model.TemplateID `json:"templateID" validate:"required"` + } + + type Response struct { + WidgetID model.WidgetID `json:"widgetID"` + } return func(request echo.Context) error { ctx := context.Background() - - var widgetData CreateWidgetRequest - if err := request.Bind(&widgetData); err != nil { + var body Body + if err := request.Bind(&body); err != nil { return echo.NewHTTPError(400, err.Error()) } - - err := request.Validate(&widgetData) - if err != nil { + if err := request.Validate(&body); err != nil { return echo.NewHTTPError(400, err.Error()) } widgetID, err := widgetService.CreateWidget( ctx, - widgetData.StreamerID, - widgetData.TemplateID, + body.StreamerID, + body.TemplateID, ) if err != nil { return request.JSON(422, "Create widget error") } - response := CreateWidgetResponse{ + response := Response{ WidgetID: widgetID, } - slog.Info("Widget created") + return request.JSON(200, response) } } -// GetWidgetHTML @Description Get widget -// -// @Tags Widget -// @Accept json -// @Produce json -// @Success 200 -// @Router /api/widget/html/{widgetID} [get] func GetWidgetHTML(widgetService model.WidgetService) echo.HandlerFunc { return func(request echo.Context) error { ctx := context.Background() @@ -76,15 +65,17 @@ func GetWidgetHTML(widgetService model.WidgetService) echo.HandlerFunc { } } -// GetWidgetInfo -// -// @Description Widget Info -// @Tags Widget -// @Accept json -// @Produce json -// @Param widgetID path int true "Widget ID" -// @Router /api/widget/info/{widgetID} [get] func GetWidgetInfo(widgetService model.WidgetService) echo.HandlerFunc { + type Response struct { + AudioUrl model.MediaUrl `json:"audioUrl"` + ImageUrl model.MediaUrl `json:"imageUrl"` + Text string `json:"text"` + Amount model.DonatAmount `json:"amount"` + DonatUser string `json:"donatUser"` + Display model.Display `json:"display"` + Duration model.Duration `json:"duration"` + DonatID model.DonatID `json:"donatID"` + } return func(request echo.Context) error { ctx := context.Background() @@ -99,14 +90,13 @@ func GetWidgetInfo(widgetService model.WidgetService) echo.HandlerFunc { } if !donatAndWidget.Display { - response := GetWidgetInfoResponse{ + response := Response{ Display: donatAndWidget.Display, } - slog.Info("Get widget info successfully") return request.JSON(200, response) } - response := GetWidgetInfoResponse{ + response := Response{ AudioUrl: donatAndWidget.Widget.AudioUrl, ImageUrl: donatAndWidget.Widget.ImageUrl, Text: donatAndWidget.Donat.Text, @@ -116,44 +106,171 @@ func GetWidgetInfo(widgetService model.WidgetService) echo.HandlerFunc { Amount: donatAndWidget.Donat.Amount, DonatID: donatAndWidget.Donat.ID, } - slog.Info("Get widget info successfully") + return request.JSON(200, response) } } -// UpdateDuration -// -// @Description UpdateDuration -// @Tags Widget -// @Accept json -// @Produce json -// @Param UpdateData body UpdateDurationRequest true "UpdateDuration" -// @Success 200 -// @Router /api/widget/duration/update [post] func UpdateDuration(widgetService model.WidgetService) echo.HandlerFunc { + type Body struct { + WidgetID model.WidgetID `json:"widgetID" validate:"required"` + Duration model.Duration `json:"duration" validate:"required"` + } + return func(request echo.Context) error { ctx := context.Background() - - var widgetData UpdateDurationRequest - if err := request.Bind(&widgetData); err != nil { + var body Body + if err := request.Bind(&body); err != nil { + return echo.NewHTTPError(400, err.Error()) + } + if err := request.Validate(&body); err != nil { return echo.NewHTTPError(400, err.Error()) } - err := request.Validate(&widgetData) - if err != nil { - return echo.NewHTTPError(400, err.Error()) - } - - err = widgetService.UpdateDuration( + err := widgetService.UpdateDuration( ctx, - widgetData.WidgetID, - widgetData.Duration, + body.WidgetID, + body.Duration, ) if err != nil { return request.JSON(422, "Update duration error") } - slog.Info("Duration updated") return request.JSON(200, "Update duration successfully") } } + +func SetMediaFile(service model.MediaService) echo.HandlerFunc { + return func(request echo.Context) error { + ctx := context.Background() + + widgetID, err := strconv.Atoi(request.FormValue("widgetID")) + if err != nil { + return echo.NewHTTPError(404, "Path parameter 'widgetID' is invalid") + } + + mediaType := request.Param("mediaType") + + file, err := request.FormFile("file") + if err != nil { + return echo.NewHTTPError(404, "Form parameter 'file' is invalid") + } + + src, err := file.Open() + if err != nil { + return echo.NewHTTPError(500, "File is invalid") + } + + err = service.SetMediaFile( + ctx, + model.MediaType(mediaType), + model.WidgetID(widgetID), + &src, + file.Filename, + file.Size, + "", + ) + if err != nil { + return echo.NewHTTPError(500, "File upload is failed") + } + slog.Info("set " + mediaType + " file successfully") + return request.String(200, "File successfully uploaded") + } +} + +func GetMediaFile(service model.MediaService) echo.HandlerFunc { + return func(request echo.Context) error { + ctx := context.Background() + + mediaType := request.Param("mediaType") + if mediaType != "background" && mediaType != "image" && mediaType != "audio" { + return echo.NewHTTPError(400, "Path parameter 'mediaType' is invalid") + } + widgetID, err := strconv.Atoi(request.Param("widgetID")) + if err != nil { + return echo.NewHTTPError(400, "Path parameter 'widgetID' is invalid") + } + + file, err := service.GetMediaFile( + ctx, + model.WidgetID(widgetID), + model.MediaType(mediaType), + ) + if err != nil { + return echo.NewHTTPError(500, "Get File is failed") + } + slog.Info("get " + mediaType + " file successfully") + return request.Blob(200, "application/octet-stream", file) + } +} + +func UpdateMediaFile(service model.MediaService) echo.HandlerFunc { + return func(request echo.Context) error { + ctx := context.Background() + + widgetID, err := strconv.Atoi(request.FormValue("widgetID")) + if err != nil { + return echo.NewHTTPError(404, "Path parameter 'widgetID' is invalid") + } + + mediaType := request.Param("mediaType") + + file, err := request.FormFile("file") + if err != nil { + return echo.NewHTTPError(404, "Form parameter 'file' is invalid") + } + + src, err := file.Open() + if err != nil { + return echo.NewHTTPError(500, "File is invalid") + } + + err = service.UpdateMediaFile( + ctx, + model.WidgetID(widgetID), + model.MediaType(mediaType), + &src, + file.Filename, + file.Size, + "", + ) + + if err != nil { + return echo.NewHTTPError(500, "File update is failed") + } + slog.Info("update media file successfully") + return request.String(200, "Media successfully uploaded") + } +} + +func SetMediaUrl(service model.MediaService) echo.HandlerFunc { + type Body struct { + WidgetID model.WidgetID `json:"widgetID" validate:"required"` + MediaUrl model.MediaUrl `json:"mediaUrl" validate:"required"` + } + return func(request echo.Context) error { + ctx := context.Background() + var body Body + if err := request.Bind(&body); err != nil { + return echo.NewHTTPError(400, err.Error()) + } + if err := request.Validate(&body); err != nil { + + return echo.NewHTTPError(400, err.Error()) + } + + mediaType := request.Param("mediaType") + + err := service.SetMediaUrl( + ctx, + model.MediaType(mediaType), + body.WidgetID, + body.MediaUrl, + ) + if err != nil { + return echo.NewHTTPError(500, "Set MediaUrl is failed") + } + + return request.String(200, "Media URL successfully set") + } +} diff --git a/internal/app/http/app.go b/internal/app/http/app.go index 69a6c5b..a2b6a7d 100644 --- a/internal/app/http/app.go +++ b/internal/app/http/app.go @@ -2,6 +2,7 @@ package http import ( "context" + . "donat-widget/internal/api/http/handlers/donat" "donat-widget/internal/model/sql" "github.com/labstack/echo/v4" "github.com/swaggo/echo-swagger" @@ -15,8 +16,6 @@ import ( import ( . "donat-widget/internal/api/http/handlers/widget" - . "donat-widget/internal/api/http/handlers/widget/donat" - . "donat-widget/internal/api/http/handlers/widget/media" ) var PREFIX = "/api/widget" @@ -24,7 +23,6 @@ var PREFIX = "/api/widget" func NewApp( db model.Db, widgetService model.WidgetService, - mediaService model.MediaService, donatService model.DonatService, ) { server := echo.New() @@ -35,7 +33,6 @@ func NewApp( server.GET(PREFIX+"/table/drop", DropTale(db)) IncludeWidgetHandlers(server, widgetService) - IncludeMediaHandlers(server, mediaService) IncludeDonatHandlers(server, donatService) server.Logger.Fatal(server.Start(":8002")) @@ -45,23 +42,18 @@ func IncludeDonatHandlers( server *echo.Echo, donatService model.DonatService, ) { - server.POST(PREFIX+"/donat/set", SetDonat(donatService)) - server.DELETE(PREFIX+"/donat/delete/:donatID", DeleteDonat(donatService)) -} - -func IncludeMediaHandlers( - server *echo.Echo, - mediaService model.MediaService, -) { - server.POST(PREFIX+"/media/:mediaType/upload", SetMediaFile(mediaService)) - server.POST(PREFIX+"/media/:mediaType/set", SetMediaUrl(mediaService)) - server.GET(PREFIX+"/media/:mediaType/get/:widgetID", GetMediaFile(mediaService)) + server.POST(PREFIX+"/donat/create", CreateDonat(donatService)) + server.POST(PREFIX+"/donat/view/:donatID", MarkDonatView(donatService)) + server.POST(PREFIX+"/donat/paid/:donatID", MarkDonatPaid(donatService)) } func IncludeWidgetHandlers( server *echo.Echo, widgetService model.WidgetService, ) { + server.POST(PREFIX+"/media/:mediaType/upload", SetMediaFile(widgetService)) + server.POST(PREFIX+"/media/:mediaType/set", SetMediaUrl(widgetService)) + server.GET(PREFIX+"/media/:mediaType/get/:widgetID", GetMediaFile(widgetService)) server.POST(PREFIX+"/create", CreateWidget(widgetService)) server.PATCH(PREFIX+"/duration/update", UpdateDuration(widgetService)) server.GET(PREFIX+"/html/:widgetID", GetWidgetHTML(widgetService)) diff --git a/internal/config/config.go b/internal/config/config.go index 70531b5..39e1644 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -6,8 +6,9 @@ import ( ) type Config struct { - Db Database `yaml:"db"` - Storage Storage `yaml:"storage"` + Db Database `yaml:"db"` + Storage Storage `yaml:"storage"` + PaymentService PaymentService `yaml:"paymentService"` } type Database struct { @@ -23,6 +24,11 @@ type Storage struct { Master string `yaml:"master"` } +type PaymentService struct { + Host string `yaml:"host"` + Port string `yaml:"port"` +} + func Init() *Config { data, err := os.ReadFile("internal/config/config.yaml") if err != nil { diff --git a/internal/config/config.yaml b/internal/config/config.yaml index c1a5d15..3f6a2f6 100644 --- a/internal/config/config.yaml +++ b/internal/config/config.yaml @@ -10,4 +10,8 @@ server: storage: filer: "http://92.63.193.151:8111" - master: "http://92.63.193.151:9333" \ No newline at end of file + master: "http://92.63.193.151:9333" + +paymentService: + host: "payment-service" + port: "8003" \ No newline at end of file diff --git a/internal/model/api/tinkoff.go b/internal/model/api/tinkoff.go new file mode 100644 index 0000000..9c60394 --- /dev/null +++ b/internal/model/api/tinkoff.go @@ -0,0 +1,12 @@ +package api + +type CreatePaymentResponse struct { + Amount int `json:"Amount"` + ErrorCode string `json:"ErrorCode"` + OrderId string `json:"OrderId"` + PaymentId string `json:"PaymentId"` + PaymentURL string `json:"PaymentURL"` + Status string `json:"Status"` + Success bool `json:"Success"` + TerminalKey string `json:"TerminalKey"` +} diff --git a/internal/model/interfaces.go b/internal/model/interfaces.go index 4b14b4d..6281bea 100644 --- a/internal/model/interfaces.go +++ b/internal/model/interfaces.go @@ -2,6 +2,7 @@ package model import ( "context" + "donat-widget/internal/model/api" "github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5/pgconn" @@ -13,6 +14,10 @@ type WidgetService interface { UpdateDuration(ctx context.Context, widgetID WidgetID, duration Duration) error GetWidgetInfo(ctx context.Context, widgetID WidgetID) (*DonatAndWidget, error) GetWidgetHTML(ctx context.Context, widgetID WidgetID) (WidgetHTML, error) + SetMediaFile(ctx context.Context, mediaType MediaType, widgetID WidgetID, file UploadFile, filename string, size int64, collection string) error + SetMediaUrl(ctx context.Context, mediaType MediaType, widgetID WidgetID, mediaURL MediaUrl) error + UpdateMediaFile(ctx context.Context, widgetID WidgetID, mediaType MediaType, file UploadFile, filename string, size int64, collection string) error + GetMediaFile(ctx context.Context, widgetID WidgetID, mediaType MediaType) (DownloadFile, error) } type WidgetRepo interface { @@ -20,16 +25,7 @@ type WidgetRepo interface { DeleteWidget(ctx context.Context, widgetID WidgetID) error UpdateDuration(ctx context.Context, widgetID WidgetID, duration Duration) error GetWidget(ctx context.Context, widgetID WidgetID) (*Widget, error) -} - -type MediaService interface { - SetMediaFile(ctx context.Context, mediaType MediaType, widgetID WidgetID, file UploadFile, filename string, size int64, collection string) error - SetMediaUrl(ctx context.Context, mediaType MediaType, widgetID WidgetID, mediaURL MediaUrl) error - UpdateMediaFile(ctx context.Context, widgetID WidgetID, mediaType MediaType, file UploadFile, filename string, size int64, collection string) error - GetMediaFile(ctx context.Context, widgetID WidgetID, mediaType MediaType) (DownloadFile, error) -} - -type MediaRepo interface { + GetAllWidget(ctx context.Context, streamerID StreamerID) ([]*Widget, error) SetMediaFile(file UploadFile, filename string, size int64, collection string) (FileID, error) GetMediaFile(fileID FileID) (DownloadFile, error) GetMediaUrl(ctx context.Context, widgetID WidgetID, mediaType MediaType) (MediaUrl, error) @@ -38,14 +34,16 @@ type MediaRepo interface { } type DonatService interface { - SetDonat(ctx context.Context, widgetID WidgetID, text string, amount DonatAmount, donatUser string) error - DeleteDonat(ctx context.Context, DonatID DonatID) error + CreateDonat(ctx context.Context, streamerID StreamerID, orderID OrderID, amount DonatAmount, text string, donatUser string) error + MarkDonatPaid(ctx context.Context, orderID OrderID) error + MarkDonatView(ctx context.Context, DonatID DonatID) error } type DonatRepo interface { - SetDonat(ctx context.Context, widgetID WidgetID, text string, amount DonatAmount, donatUser string) error + CreateDonat(ctx context.Context, widgetID WidgetID, orderID OrderID, amount DonatAmount, text string, donatUser string) error GetDonat(ctx context.Context, widgetID WidgetID) ([]*Donat, error) - DeleteDonat(ctx context.Context, donatID DonatID) error + MarkDonatPaid(ctx context.Context, orderID OrderID) error + MarkDonatView(ctx context.Context, DonatID DonatID) error } type Error interface { @@ -58,6 +56,9 @@ type Storage interface { Update(file UploadFile, fileID FileID, filename string, size int64, collection string) error } +type PaymentClient interface { + CreatePayment(streamerID StreamerID, amount DonatAmount, orderID OrderID) (api.CreatePaymentResponse, error) +} type Db interface { Exec(ctx context.Context, query string, args ...interface{}) (pgconn.CommandTag, error) Query(ctx context.Context, query string, args ...interface{}) (pgx.Rows, error) diff --git a/internal/model/models.go b/internal/model/models.go index c54443e..afad2ff 100644 --- a/internal/model/models.go +++ b/internal/model/models.go @@ -7,15 +7,51 @@ import ( ) type Widget struct { - ID WidgetID - StreamerID StreamerID - TemplateID TemplateID - BackgroundUrl MediaUrl - ImageUrl MediaUrl - AudioUrl MediaUrl - Duration Duration - CreatedAt time.Time - UpdatedAt time.Time + ID WidgetID `db:"id"` + StreamerID StreamerID `db:"streamer_id"` + TemplateID TemplateID `db:"template_id"` + + BackgroundUrl MediaUrl `db:"background_url"` + ImageUrl MediaUrl `db:"image_url"` + AudioUrl MediaUrl `db:"audio_url"` + + MinAmount DonatAmount `db:"min_amount"` + Duration Duration `db:"duration"` + + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` +} + +type Donat struct { + ID DonatID `db:"id"` + WidgetID WidgetID `db:"widget_id"` + + Text string `db:"text"` + DonatUser string `db:"donat_user"` + Amount DonatAmount `db:"amount"` + + Paid bool `db:"paid"` + View bool `db:"view"` + + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` +} + +type Target struct { + ID TargetID `db:"id"` + + Text string `db:"text"` + Collected DonatAmount `db:"collected"` + Amount DonatAmount `db:"amount"` + + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` +} + +type DonatAndWidget struct { + Widget *Widget + Donat *Donat + Display Display } func (widget *Widget) GetMediaUrl(mediaType MediaType) MediaUrl { @@ -45,19 +81,3 @@ func (widget *Widget) NormalizeUrl() { widget.AudioUrl = MediaUrl(selfDomain + "/audio/get/" + strWidgetID) } } - -type Donat struct { - ID DonatID - WidgetID WidgetID - Text string - DonatUser string - Amount DonatAmount - CreatedAt time.Time - UpdatedAt time.Time -} - -type DonatAndWidget struct { - Widget *Widget - Donat *Donat - Display Display -} diff --git a/internal/model/sql/model.go b/internal/model/sql/model.go index e200144..0860556 100644 --- a/internal/model/sql/model.go +++ b/internal/model/sql/model.go @@ -5,10 +5,14 @@ CREATE TABLE IF NOT EXISTS widgets ( id SERIAL PRIMARY KEY, streamer_id INTEGER NOT NULL, template_id INTEGER NOT NULL, + background_url TEXT DEFAULT '', image_url TEXT DEFAULT '', audio_url TEXT DEFAULT '', - duration INTEGER DEFAULT 30, + + duration INTEGER NOT NULL, + min_amount INTEGER NOT NUll, + created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP ); @@ -16,14 +20,30 @@ CREATE TABLE IF NOT EXISTS widgets ( CREATE TABLE IF NOT EXISTS donats ( id SERIAL PRIMARY KEY, widget_id INTEGER NOT NULL, - text TEXT DEFAULT '', - amount TEXT DEFAULT '', - donat_user TEXT DEFAULT '', + order_id TEXT NOT NULL, + + text TEXT NOT NULL, + amount INTEGER NOT NULL, + donat_user TEXT NOT NULL, + + paid BOOLEAN DEFAULT FALSE, + view BOOLEAN DEFAULT FALSE, + created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP ); - CREATE OR REPLACE FUNCTION update_updated_at() +CREATE TABLE IF NOT EXISTS targets ( + id SERIAL PRIMARY KEY, + text TEXT NOT NULL, + amount INTEGER NOT NULL, + collected INTEGER NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP +); + + +CREATE OR REPLACE FUNCTION update_updated_at() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at = NOW(); @@ -40,9 +60,15 @@ CREATE TRIGGER update_updated_at_trigger BEFORE UPDATE ON donats FOR EACH ROW EXECUTE PROCEDURE update_updated_at(); + +CREATE TRIGGER update_updated_at_trigger +BEFORE UPDATE ON targets +FOR EACH ROW +EXECUTE PROCEDURE update_updated_at(); ` var DropTableQuery = ` DROP TABLE IF EXISTS widgets; DROP TABLE IF EXISTS donats; +DROP TABLE IF EXISTS targets; ` diff --git a/internal/model/sql/query.go b/internal/model/sql/query.go index cdffb23..4cd843e 100644 --- a/internal/model/sql/query.go +++ b/internal/model/sql/query.go @@ -5,27 +5,21 @@ import ( "fmt" ) -var CreateWidgetQuery = ` +var CreateWidget = ` INSERT INTO widgets (streamer_id, template_id) VALUES (@streamer_id, @template_id); ` - -var SetDonatQuery = ` -INSERT INTO donats (widget_id, text, amount, donat_user) -VALUES (@widget_id, @text, @amount, @donat_user); -` - -var DeleteDonatQuery = ` -DELETE FROM donats WHERE id = (@id); -` - -var UpdateDurationQuery = ` +var UpdateDuration = ` UPDATE widgets SET duration = (@duration) WHERE id = (@id) ` +var GetWidget = ` +SELECT * FROM widgets +WHERE id = (@id); +` -func UpdateMediaUrlQuery(mediaType model.MediaType) string { +func UpdateMediaUrl(mediaType model.MediaType) string { query := fmt.Sprintf(` UPDATE widgets SET %s = (@%s) @@ -34,13 +28,9 @@ func UpdateMediaUrlQuery(mediaType model.MediaType) string { return query } -var GetDonatQuery = ` -SELECT * FROM donats WHERE widget_id = (@widget_id); -` - -var GetWidgetQuery = ` +var GetAllWidget = ` SELECT * FROM widgets -WHERE id = (@id); +WHERE streamer_id = (@streamer_id); ` func GetMediaUrl(mediaType model.MediaType) string { @@ -51,3 +41,22 @@ func GetMediaUrl(mediaType model.MediaType) string { `, mediaType) return query } + +var CreateDonat = ` +INSERT INTO donats (widget_id, text, amount, donat_user, order_id) +VALUES (@widget_id, @text, @amount, @donat_user, @order_id); +` +var MarkDonatView = ` +UPDATE donats +SET view = (@view) +WHERE id = (@id); +` +var MarkDonatPaid = ` +UPDATE donats +SET paid = (@paid) +WHERE order_id = (@order_id); +` +var GetDonat = ` +SELECT * FROM donats +WHERE widget_id = (@widget_id) AND paid = (@paid) AND view = (@view); +` diff --git a/internal/model/types.go b/internal/model/types.go index 9e20c61..68f2466 100644 --- a/internal/model/types.go +++ b/internal/model/types.go @@ -23,5 +23,9 @@ type UploadFile *multipart.File type DownloadFile []byte type FileID string -type DonatAmount string +type DonatAmount int type DonatID int +type OrderID string + +type TargetID int +type Collected int diff --git a/internal/repository/widget/donat/donat.go b/internal/repository/donat/donat.go similarity index 63% rename from internal/repository/widget/donat/donat.go rename to internal/repository/donat/donat.go index eb15154..f056db5 100644 --- a/internal/repository/widget/donat/donat.go +++ b/internal/repository/donat/donat.go @@ -19,20 +19,22 @@ type RepoDonat struct { db model.Db } -func (repoDonat *RepoDonat) SetDonat( +func (repoDonat *RepoDonat) CreateDonat( ctx context.Context, widgetID model.WidgetID, - text string, + orderID model.OrderID, amount model.DonatAmount, + text string, donatUser string, ) error { args := model.QueryArgs{ "widget_id": widgetID, + "order_id": orderID, "text": text, "amount": amount, "donat_user": donatUser, } - _, err := repoDonat.db.Query(ctx, sql.SetDonatQuery, args) + _, err := repoDonat.db.Query(ctx, sql.CreateDonat, args) if err != nil { slog.Error("repoDonat.db.Query: " + err.Error()) return err @@ -47,8 +49,10 @@ func (repoDonat *RepoDonat) GetDonat( ) ([]*model.Donat, error) { args := pgx.NamedArgs{ "widget_id": widgetID, + "paid": true, + "view": false, } - rows, err := repoDonat.db.Query(ctx, sql.GetDonatQuery, args) + rows, err := repoDonat.db.Query(ctx, sql.GetDonat, args) if err != nil { slog.Error("repoDonat.db.Query: " + err.Error()) return nil, err @@ -64,14 +68,32 @@ func (repoDonat *RepoDonat) GetDonat( return donats, nil } -func (repoDonat *RepoDonat) DeleteDonat( +func (repoDonat *RepoDonat) MarkDonatView( ctx context.Context, donatID model.DonatID, ) error { args := pgx.NamedArgs{ - "id": donatID, + "id": donatID, + "view": true, } - _, err := repoDonat.db.Query(ctx, sql.DeleteDonatQuery, args) + _, err := repoDonat.db.Query(ctx, sql.MarkDonatView, args) + if err != nil { + slog.Error("repoDonat.db.Query: " + err.Error()) + return err + } + + return nil +} + +func (repoDonat *RepoDonat) MarkDonatPaid( + ctx context.Context, + orderID model.OrderID, +) error { + args := pgx.NamedArgs{ + "order_id": orderID, + "paid": true, + } + _, err := repoDonat.db.Query(ctx, sql.MarkDonatPaid, args) if err != nil { slog.Error("repoDonat.db.Query: " + err.Error()) return err diff --git a/internal/repository/target/target.go b/internal/repository/target/target.go new file mode 100644 index 0000000..ec6c15d --- /dev/null +++ b/internal/repository/target/target.go @@ -0,0 +1 @@ +package target diff --git a/internal/repository/widget/media/media.go b/internal/repository/widget/media/media.go deleted file mode 100644 index 2538ca8..0000000 --- a/internal/repository/widget/media/media.go +++ /dev/null @@ -1,135 +0,0 @@ -package media - -import ( - "context" - "donat-widget/internal/model" - "donat-widget/internal/model/sql" - "errors" - "github.com/georgysavva/scany/v2/pgxscan" - "log/slog" -) - -func New( - db model.Db, - storage model.Storage, -) *RepoMedia { - return &RepoMedia{ - db: db, - storage: storage, - } -} - -type RepoMedia struct { - db model.Db - storage model.Storage -} - -func (repoMedia *RepoMedia) SetMediaFile( - file model.UploadFile, - filename string, - size int64, - collection string, -) (model.FileID, error) { - fileData, err := repoMedia.storage.Upload(file, filename, size, collection) - if err != nil { - slog.Error("repoMedia.storage.Upload: " + err.Error()) - return "", err - } - - return model.FileID(fileData.FileID), nil -} - -func (repoMedia *RepoMedia) GetMediaFile( - fileID model.FileID, -) (model.DownloadFile, error) { - file, err := repoMedia.storage.Download(fileID) - if err != nil { - slog.Error("repoMedia.storage.Download: " + err.Error()) - return nil, err - } - - return file, err -} - -func (repoMedia *RepoMedia) UpdateMediaFile( - ctx context.Context, - widgetID model.WidgetID, - file model.UploadFile, - fileID model.FileID, - filename string, - size int64, - collection string, - mediaType model.MediaType, -) error { - err := repoMedia.storage.Update(file, fileID, filename, size, collection) - if err != nil { - slog.Error("repoMedia.storage.Update: " + err.Error()) - return err - } - - mediaType = mediaType + "_url" - args := model.QueryArgs{ - string(mediaType): model.MediaUrl(fileID), - "id": widgetID, - } - _, err = repoMedia.db.Query(ctx, sql.UpdateMediaUrlQuery(mediaType), args) - if err != nil { - slog.Error("repoMedia.db.Query: " + err.Error()) - return err - } - - return nil -} - -func (repoMedia *RepoMedia) SetMediaUrl( - ctx context.Context, - widgetID model.WidgetID, - mediaUrl model.MediaUrl, - mediaType model.MediaType, -) error { - mediaType = mediaType + "_url" - - args := model.QueryArgs{ - string(mediaType): mediaUrl, - "id": widgetID, - } - _, err := repoMedia.db.Query(ctx, sql.UpdateMediaUrlQuery(mediaType), args) - if err != nil { - slog.Error("repoMedia.db.Query: " + err.Error()) - return err - } - return nil -} - -func (repoMedia *RepoMedia) GetMediaUrl( - ctx context.Context, - widgetID model.WidgetID, - mediaType model.MediaType, -) (model.MediaUrl, error) { - mediaType = mediaType + "_url" - args := model.QueryArgs{ - "id": widgetID, - } - rows, err := repoMedia.db.Query(ctx, sql.GetMediaUrl(mediaType), args) - if err != nil { - slog.Error("repoMedia.db.Query: " + err.Error()) - return "", err - } - - var widgets []*model.Widget - err = pgxscan.ScanAll(&widgets, rows) - if err != nil { - slog.Error("repoMedia.pgxscan.ScanAll: " + err.Error()) - return "", err - } - - if len(widgets) == 0 { - slog.Error("Widget does not exist") - return "", errors.New("widget does not exist") - } - - widget := widgets[0] - mediaUrl := widget.GetMediaUrl(mediaType) - - return mediaUrl, nil -} diff --git a/internal/repository/widget/widget.go b/internal/repository/widget/widget.go index 52be4b9..79f3ad9 100644 --- a/internal/repository/widget/widget.go +++ b/internal/repository/widget/widget.go @@ -9,14 +9,19 @@ import ( "log/slog" ) -func New(db model.Db) *RepoWidget { +func New( + db model.Db, + storage model.Storage, +) *RepoWidget { return &RepoWidget{ - db: db, + db: db, + storage: storage, } } type RepoWidget struct { - db model.Db + db model.Db + storage model.Storage } func (widgetRepo *RepoWidget) CreateWidget( @@ -28,7 +33,7 @@ func (widgetRepo *RepoWidget) CreateWidget( "streamer_id": streamerID, "template_id": templateID, } - _, err := widgetRepo.db.Query(ctx, sql.CreateWidgetQuery, args) + _, err := widgetRepo.db.Query(ctx, sql.CreateWidget, args) if err != nil { slog.Error("widgetRepo.db.Query: " + err.Error()) return 0, err @@ -45,7 +50,7 @@ func (widgetRepo *RepoWidget) GetWidget( args := model.QueryArgs{ "id": widgetID, } - rows, err := widgetRepo.db.Query(ctx, sql.GetWidgetQuery, args) + rows, err := widgetRepo.db.Query(ctx, sql.GetWidget, args) if err != nil { slog.Error("widgetRepo.db.Query: " + err.Error()) return nil, err @@ -69,6 +74,29 @@ func (widgetRepo *RepoWidget) GetWidget( return widget, nil } +func (widgetRepo *RepoWidget) GetAllWidget( + ctx context.Context, + streamerID model.StreamerID, +) ([]*model.Widget, error) { + args := model.QueryArgs{ + "streamerID": streamerID, + } + rows, err := widgetRepo.db.Query(ctx, sql.GetAllWidget, args) + if err != nil { + slog.Error("widgetRepo.db.Query: " + err.Error()) + return nil, err + } + + var widgets []*model.Widget + err = pgxscan.ScanAll(&widgets, rows) + if err != nil { + slog.Error(err.Error()) + return nil, err + } + + return widgets, nil +} + func (widgetRepo *RepoWidget) DeleteWidget( ctx context.Context, widgetID model.WidgetID, @@ -85,7 +113,7 @@ func (widgetRepo *RepoWidget) UpdateDuration( "id": widgetID, "duration": duration, } - _, err := widgetRepo.db.Query(ctx, sql.UpdateDurationQuery, args) + _, err := widgetRepo.db.Query(ctx, sql.UpdateDuration, args) if err != nil { slog.Error("widgetRepo.db.Query: " + err.Error()) return err @@ -93,3 +121,113 @@ func (widgetRepo *RepoWidget) UpdateDuration( return nil } + +func (widgetRepo *RepoWidget) SetMediaFile( + file model.UploadFile, + filename string, + size int64, + collection string, +) (model.FileID, error) { + fileData, err := widgetRepo.storage.Upload(file, filename, size, collection) + if err != nil { + slog.Error("repoMedia.storage.Upload: " + err.Error()) + return "", err + } + + return model.FileID(fileData.FileID), nil +} + +func (widgetRepo *RepoWidget) GetMediaFile( + fileID model.FileID, +) (model.DownloadFile, error) { + file, err := widgetRepo.storage.Download(fileID) + if err != nil { + slog.Error("repoMedia.storage.Download: " + err.Error()) + return nil, err + } + + return file, err +} + +func (widgetRepo *RepoWidget) UpdateMediaFile( + ctx context.Context, + widgetID model.WidgetID, + file model.UploadFile, + fileID model.FileID, + filename string, + size int64, + collection string, + mediaType model.MediaType, +) error { + err := widgetRepo.storage.Update(file, fileID, filename, size, collection) + if err != nil { + slog.Error("repoMedia.storage.Update: " + err.Error()) + return err + } + + mediaType = mediaType + "_url" + args := model.QueryArgs{ + string(mediaType): model.MediaUrl(fileID), + "id": widgetID, + } + _, err = widgetRepo.db.Query(ctx, sql.UpdateMediaUrl(mediaType), args) + if err != nil { + slog.Error("repoMedia.db.Query: " + err.Error()) + return err + } + + return nil +} + +func (widgetRepo *RepoWidget) SetMediaUrl( + ctx context.Context, + widgetID model.WidgetID, + mediaUrl model.MediaUrl, + mediaType model.MediaType, +) error { + mediaType = mediaType + "_url" + + args := model.QueryArgs{ + string(mediaType): mediaUrl, + "id": widgetID, + } + _, err := widgetRepo.db.Query(ctx, sql.UpdateMediaUrl(mediaType), args) + if err != nil { + slog.Error("repoMedia.db.Query: " + err.Error()) + return err + } + return nil +} + +func (widgetRepo *RepoWidget) GetMediaUrl( + ctx context.Context, + widgetID model.WidgetID, + mediaType model.MediaType, +) (model.MediaUrl, error) { + mediaType = mediaType + "_url" + args := model.QueryArgs{ + "id": widgetID, + } + rows, err := widgetRepo.db.Query(ctx, sql.GetMediaUrl(mediaType), args) + if err != nil { + slog.Error("repoMedia.db.Query: " + err.Error()) + return "", err + } + + var widgets []*model.Widget + err = pgxscan.ScanAll(&widgets, rows) + if err != nil { + slog.Error("repoMedia.pgxscan.ScanAll: " + err.Error()) + return "", err + } + + if len(widgets) == 0 { + slog.Error("Widget does not exist") + return "", errors.New("widget does not exist") + } + + widget := widgets[0] + mediaUrl := widget.GetMediaUrl(mediaType) + + return mediaUrl, nil +} diff --git a/internal/service/donat/donat.go b/internal/service/donat/donat.go new file mode 100644 index 0000000..b69ee6e --- /dev/null +++ b/internal/service/donat/donat.go @@ -0,0 +1,92 @@ +package donat + +import ( + "context" + "donat-widget/internal/model" + "log/slog" +) + +type ServiceDonat struct { + donatRepo model.DonatRepo + widgetRepo model.WidgetRepo + paymentClient model.PaymentClient +} + +func New( + donatRepo model.DonatRepo, + widgetRepo model.WidgetRepo, + paymentClient model.PaymentClient, +) *ServiceDonat { + return &ServiceDonat{ + donatRepo: donatRepo, + widgetRepo: widgetRepo, + paymentClient: paymentClient, + } +} + +func (donatService *ServiceDonat) CreateDonat( + ctx context.Context, + streamerID model.StreamerID, + orderID model.OrderID, + amount model.DonatAmount, + text string, + donatUser string, +) error { + widgets, err := donatService.widgetRepo.GetAllWidget(ctx, streamerID) + if err != nil { + slog.Error("donatService.widgetRepo.GetAllWidget: ", err) + return err + } + + var widgetID model.WidgetID + for _, widget := range widgets { + if widget.MinAmount <= amount { + widgetID = widget.ID + } + } + + err = donatService.donatRepo.CreateDonat( + ctx, + widgetID, + orderID, + amount, + text, + donatUser, + ) + if err != nil { + slog.Error("donatService.donatRepo.SetDonat: " + err.Error()) + return err + } + + return nil +} + +func (donatService *ServiceDonat) MarkDonatPaid( + ctx context.Context, + orderID model.OrderID, +) error { + err := donatService.donatRepo.MarkDonatPaid( + ctx, + orderID, + ) + if err != nil { + slog.Error("donatService.donatRepo.MarkDonatView: " + err.Error()) + return err + } + return nil +} + +func (donatService *ServiceDonat) MarkDonatView( + ctx context.Context, + donatID model.DonatID, +) error { + err := donatService.donatRepo.MarkDonatView( + ctx, + donatID, + ) + if err != nil { + slog.Error("donatService.donatRepo.MarkDonatView: " + err.Error()) + return err + } + return nil +} diff --git a/internal/service/target/target.go b/internal/service/target/target.go new file mode 100644 index 0000000..ec6c15d --- /dev/null +++ b/internal/service/target/target.go @@ -0,0 +1 @@ +package target diff --git a/internal/service/widget/donat/donat.go b/internal/service/widget/donat/donat.go deleted file mode 100644 index fb3ed87..0000000 --- a/internal/service/widget/donat/donat.go +++ /dev/null @@ -1,55 +0,0 @@ -package donat - -import ( - "context" - "donat-widget/internal/model" - "log/slog" -) - -type ServiceDonat struct { - donatRepo model.DonatRepo -} - -func New( - donatRepo model.DonatRepo, -) *ServiceDonat { - return &ServiceDonat{ - donatRepo: donatRepo, - } -} - -func (donatService *ServiceDonat) SetDonat( - ctx context.Context, - widgetID model.WidgetID, - text string, - amount model.DonatAmount, - donatUser string, -) error { - err := donatService.donatRepo.SetDonat( - ctx, - widgetID, - text, - amount, - donatUser, - ) - if err != nil { - slog.Error("donatService.donatRepo.SetDonat: " + err.Error()) - return err - } - return nil -} - -func (donatService *ServiceDonat) DeleteDonat( - ctx context.Context, - donatID model.DonatID, -) error { - err := donatService.donatRepo.DeleteDonat( - ctx, - donatID, - ) - if err != nil { - slog.Error("donatService.donatRepo.DeleteDonat: " + err.Error()) - return err - } - return nil -} diff --git a/internal/service/widget/media/media.go b/internal/service/widget/media/media.go deleted file mode 100644 index 24d72c0..0000000 --- a/internal/service/widget/media/media.go +++ /dev/null @@ -1,131 +0,0 @@ -package media - -import ( - "context" - "donat-widget/internal/model" - "log/slog" -) - -func New(mediaRepo model.MediaRepo) *ServiceMedia { - return &ServiceMedia{ - mediaRepo: mediaRepo, - } -} - -type ServiceMedia struct { - mediaRepo model.MediaRepo -} - -func (mediaService *ServiceMedia) SetMediaFile( - ctx context.Context, - mediaType model.MediaType, - widgetID model.WidgetID, - file model.UploadFile, - filename string, - size int64, - collection string, -) error { - fileID, err := mediaService.mediaRepo.SetMediaFile( - file, - filename, - size, - collection, - ) - if err != nil { - slog.Error("mediaService.mediaRepo.SetMediaFile: " + err.Error()) - return err - } - - err = mediaService.SetMediaUrl( - ctx, - mediaType, - widgetID, - model.MediaUrl(fileID), - ) - if err != nil { - slog.Error("mediaService.SetMediaUrl: " + err.Error()) - return err - } - - return nil -} - -func (mediaService *ServiceMedia) GetMediaFile( - ctx context.Context, - widgetID model.WidgetID, - mediaType model.MediaType, -) (model.DownloadFile, error) { - fileID, err := mediaService.mediaRepo.GetMediaUrl( - ctx, - widgetID, - mediaType, - ) - if err != nil { - slog.Error("mediaService.mediaRepo.GetMediaUrl: " + err.Error()) - return nil, err - } - - file, err := mediaService.mediaRepo.GetMediaFile( - model.FileID(fileID), - ) - if err != nil { - slog.Error("mediaService.mediaRepo.GetMediaFile: " + err.Error()) - return nil, err - } - return file, nil -} - -func (mediaService *ServiceMedia) UpdateMediaFile( - ctx context.Context, - widgetID model.WidgetID, - mediaType model.MediaType, - file model.UploadFile, - filename string, - size int64, - collection string, -) error { - fileID, err := mediaService.mediaRepo.GetMediaUrl( - ctx, - widgetID, - mediaType, - ) - if err != nil { - slog.Error("mediaService.mediaRepo.GetMediaUrl: " + err.Error()) - return err - } - - err = mediaService.mediaRepo.UpdateMediaFile( - ctx, - widgetID, - file, - model.FileID(fileID), - filename, - size, - collection, - mediaType, - ) - if err != nil { - slog.Error("mediaService.mediaRepo.UpdateMediaFile: " + err.Error()) - return err - } - return nil -} - -func (mediaService *ServiceMedia) SetMediaUrl( - ctx context.Context, - mediaType model.MediaType, - widgetID model.WidgetID, - mediaUrl model.MediaUrl, -) error { - err := mediaService.mediaRepo.SetMediaUrl( - ctx, - widgetID, - mediaUrl, - mediaType, - ) - if err != nil { - slog.Error("mediaService.mediaRepo.SetMediaUrl: " + err.Error()) - return err - } - return nil -} diff --git a/internal/service/widget/widget.go b/internal/service/widget/widget.go index 2c7e58d..d836d27 100644 --- a/internal/service/widget/widget.go +++ b/internal/service/widget/widget.go @@ -113,3 +113,117 @@ func (widgetService *ServiceWidget) GetWidgetInfo( return &donatAndWidget, nil } + +func (widgetService *ServiceWidget) SetMediaFile( + ctx context.Context, + mediaType model.MediaType, + widgetID model.WidgetID, + file model.UploadFile, + filename string, + size int64, + collection string, +) error { + fileID, err := widgetService.widgetRepo.SetMediaFile( + file, + filename, + size, + collection, + ) + if err != nil { + slog.Error("mediaService.mediaRepo.SetMediaFile: " + err.Error()) + return err + } + + err = widgetService.SetMediaUrl( + ctx, + mediaType, + widgetID, + model.MediaUrl(fileID), + ) + if err != nil { + slog.Error("mediaService.SetMediaUrl: " + err.Error()) + return err + } + + return nil +} + +func (widgetService *ServiceWidget) GetMediaFile( + ctx context.Context, + widgetID model.WidgetID, + mediaType model.MediaType, +) (model.DownloadFile, error) { + fileID, err := widgetService.widgetRepo.GetMediaUrl( + ctx, + widgetID, + mediaType, + ) + if err != nil { + slog.Error("mediaService.mediaRepo.GetMediaUrl: " + err.Error()) + return nil, err + } + + file, err := widgetService.widgetRepo.GetMediaFile( + model.FileID(fileID), + ) + if err != nil { + slog.Error("mediaService.mediaRepo.GetMediaFile: " + err.Error()) + return nil, err + } + return file, nil +} + +func (widgetService *ServiceWidget) UpdateMediaFile( + ctx context.Context, + widgetID model.WidgetID, + mediaType model.MediaType, + file model.UploadFile, + filename string, + size int64, + collection string, +) error { + fileID, err := widgetService.widgetRepo.GetMediaUrl( + ctx, + widgetID, + mediaType, + ) + if err != nil { + slog.Error("mediaService.mediaRepo.GetMediaUrl: " + err.Error()) + return err + } + + err = widgetService.widgetRepo.UpdateMediaFile( + ctx, + widgetID, + file, + model.FileID(fileID), + filename, + size, + collection, + mediaType, + ) + if err != nil { + slog.Error("mediaService.mediaRepo.UpdateMediaFile: " + err.Error()) + return err + } + return nil +} + +func (widgetService *ServiceWidget) SetMediaUrl( + ctx context.Context, + mediaType model.MediaType, + widgetID model.WidgetID, + mediaUrl model.MediaUrl, +) error { + err := widgetService.widgetRepo.SetMediaUrl( + ctx, + widgetID, + mediaUrl, + mediaType, + ) + if err != nil { + slog.Error("mediaService.mediaRepo.SetMediaUrl: " + err.Error()) + return err + } + return nil +} diff --git a/pkg/api/payment/tinkoff.go b/pkg/api/payment/tinkoff.go new file mode 100644 index 0000000..f4c9235 --- /dev/null +++ b/pkg/api/payment/tinkoff.go @@ -0,0 +1,71 @@ +package payment + +import ( + "bytes" + "donat-widget/internal/model" + "donat-widget/internal/model/api" + "encoding/json" + "errors" + "io" + "log/slog" + "net/http" +) + +func New(host, port string) *ClientPayment { + return &ClientPayment{ + client: &http.Client{}, + baseURL: "http://" + host + ":" + port + "/api/payment", + } +} + +type ClientPayment struct { + client *http.Client + baseURL string +} + +func (c *ClientPayment) CreatePayment( + streamerID model.StreamerID, + amount model.DonatAmount, + orderID model.OrderID, +) (api.CreatePaymentResponse, error) { + requestBody := map[string]any{ + "sellerID": streamerID, + "amount": amount, + "orderID": orderID, + } + response, err := c.post("/buyer/pay", requestBody) + if err != nil { + slog.Error("c.post /buyer/pay", err) + return api.CreatePaymentResponse{}, err + } + + var createPaymentResponse api.CreatePaymentResponse + if err = json.Unmarshal(response, &createPaymentResponse); err != nil { + slog.Error("json.Unmarshal", err) + return api.CreatePaymentResponse{}, err + } + + return createPaymentResponse, nil +} + +func (c *ClientPayment) post(path string, body map[string]any) ([]byte, error) { + bytesBody, _ := json.Marshal(body) + + resp, err := c.client.Post(c.baseURL+path, "application/json", bytes.NewReader(bytesBody)) + if err != nil { + slog.Error("c.client.Post: " + err.Error()) + return nil, err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return nil, errors.New("tinkoff: post failed: " + resp.Status) + } + + response, err := io.ReadAll(resp.Body) + if err != nil { + slog.Error("io.ReadAll: " + err.Error()) + return nil, err + } + + return response, nil +}