This commit is contained in:
mm 2024-10-20 22:42:26 +05:00
parent b94abfe75b
commit 13a80221ed
31 changed files with 912 additions and 798 deletions

View File

@ -22,23 +22,23 @@ jobs:
dockerhub-st.ru/donat-widget:latest dockerhub-st.ru/donat-widget:latest
dockerhub-st.ru/donat-widget:${{ github.sha }} dockerhub-st.ru/donat-widget:${{ github.sha }}
# deploy: deploy:
# runs-on: ubuntu-latest runs-on: ubuntu-latest
# steps: steps:
# - name: Checkout code - name: Checkout code
# uses: actions/checkout@v2 uses: actions/checkout@v2
#
# - name: Install kubectl - name: Install kubectl
# uses: azure/setup-kubectl@v1 uses: azure/setup-kubectl@v1
# with: with:
# version: 'v1.21.0' version: 'v1.21.0'
#
# - name: Configure kube-config - name: Configure kube-config
# run: | run: |
# mkdir -p $HOME/.kube mkdir -p $HOME/.kube
# echo "${{ secrets.KUBE_CONFIG }}" | base64 --decode > $HOME/.kube/config echo "${{ secrets.KUBE_CONFIG }}" | base64 --decode > $HOME/.kube/config
# export KUBECONFIG=$HOME/.kube/config export KUBECONFIG=$HOME/.kube/config
#
# - name: Update deployment - name: Update deployment
# run: | run: |
# kubectl restart rollout deployment donat-user-deployment kubectl restart rollout deployment donat-user-deployment

View File

@ -4,29 +4,21 @@ import (
"context" "context"
"donat-widget/internal/app/http" "donat-widget/internal/app/http"
"donat-widget/internal/config" "donat-widget/internal/config"
DonatRepo "donat-widget/internal/repository/donat"
DonatService "donat-widget/internal/service/donat"
) )
import ( import (
"donat-widget/infrastructure/pg" "donat-widget/infrastructure/pg"
"donat-widget/infrastructure/weed" "donat-widget/infrastructure/weed"
PaymentClient "donat-widget/pkg/api/payment"
) )
import ( import (
WidgetRepo "donat-widget/internal/repository/widget" 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" 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() { func main() {
cfg := config.Init() 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) 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) storage := weed.NewWeed(cfg.Storage.Filer, cfg.Storage.Master)
// CLIENTS
paymentClient := PaymentClient.New(cfg.PaymentService.Host, cfg.PaymentService.Port)
// REPOSITORIES // REPOSITORIES
widgetRepo := WidgetRepo.New(db) widgetRepo := WidgetRepo.New(db, storage)
mediaRepo := MediaRepo.New(db, storage)
donatRepo := DonatRepo.New(db) donatRepo := DonatRepo.New(db)
// SERVICES // SERVICES
widgetService := WidgetService.New(widgetRepo, donatRepo) widgetService := WidgetService.New(widgetRepo, donatRepo)
mediaService := MediaService.New(mediaRepo) donatService := DonatService.New(donatRepo, widgetRepo, paymentClient)
donatService := DonatService.New(donatRepo)
http.NewApp( http.NewApp(
db, db,
widgetService, widgetService,
mediaService,
donatService, donatService,
) )
} }

3
go.mod
View File

@ -5,8 +5,10 @@ go 1.23.0
require ( require (
github.com/georgysavva/scany/v2 v2.1.3 github.com/georgysavva/scany/v2 v2.1.3
github.com/go-playground/validator/v10 v10.22.0 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/jackc/pgx/v5 v5.6.0
github.com/labstack/echo/v4 v4.12.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/echo-swagger v1.4.1
github.com/swaggo/swag v1.16.3 github.com/swaggo/swag v1.16.3
gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v2 v2.4.0
@ -29,7 +31,6 @@ require (
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/labstack/gommon v0.4.2 // indirect github.com/labstack/gommon v0.4.2 // indirect
github.com/leodido/go-urn v1.4.0 // 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/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect

2
go.sum
View File

@ -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/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 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= 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 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= 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= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=

View File

@ -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")
}
}

View File

@ -0,0 +1 @@
package target

View File

@ -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")
}
}

View File

@ -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"`
}

View File

@ -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")
}
}

View File

@ -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"`
}

View File

@ -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"`
}

View File

@ -8,53 +8,42 @@ import (
"strconv" "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 { func CreateWidget(widgetService model.WidgetService) echo.HandlerFunc {
return func(request echo.Context) error { type Body struct {
ctx := context.Background() StreamerID model.StreamerID `json:"streamerID" validate:"required"`
TemplateID model.TemplateID `json:"templateID" validate:"required"`
var widgetData CreateWidgetRequest
if err := request.Bind(&widgetData); err != nil {
return echo.NewHTTPError(400, err.Error())
} }
err := request.Validate(&widgetData) type Response struct {
if err != nil { WidgetID model.WidgetID `json:"widgetID"`
}
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()) return echo.NewHTTPError(400, err.Error())
} }
widgetID, err := widgetService.CreateWidget( widgetID, err := widgetService.CreateWidget(
ctx, ctx,
widgetData.StreamerID, body.StreamerID,
widgetData.TemplateID, body.TemplateID,
) )
if err != nil { if err != nil {
return request.JSON(422, "Create widget error") return request.JSON(422, "Create widget error")
} }
response := CreateWidgetResponse{ response := Response{
WidgetID: widgetID, WidgetID: widgetID,
} }
slog.Info("Widget created")
return request.JSON(200, response) 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 { func GetWidgetHTML(widgetService model.WidgetService) echo.HandlerFunc {
return func(request echo.Context) error { return func(request echo.Context) error {
ctx := context.Background() 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 { 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 { return func(request echo.Context) error {
ctx := context.Background() ctx := context.Background()
@ -99,14 +90,13 @@ func GetWidgetInfo(widgetService model.WidgetService) echo.HandlerFunc {
} }
if !donatAndWidget.Display { if !donatAndWidget.Display {
response := GetWidgetInfoResponse{ response := Response{
Display: donatAndWidget.Display, Display: donatAndWidget.Display,
} }
slog.Info("Get widget info successfully")
return request.JSON(200, response) return request.JSON(200, response)
} }
response := GetWidgetInfoResponse{ response := Response{
AudioUrl: donatAndWidget.Widget.AudioUrl, AudioUrl: donatAndWidget.Widget.AudioUrl,
ImageUrl: donatAndWidget.Widget.ImageUrl, ImageUrl: donatAndWidget.Widget.ImageUrl,
Text: donatAndWidget.Donat.Text, Text: donatAndWidget.Donat.Text,
@ -116,44 +106,171 @@ func GetWidgetInfo(widgetService model.WidgetService) echo.HandlerFunc {
Amount: donatAndWidget.Donat.Amount, Amount: donatAndWidget.Donat.Amount,
DonatID: donatAndWidget.Donat.ID, DonatID: donatAndWidget.Donat.ID,
} }
slog.Info("Get widget info successfully")
return request.JSON(200, response) 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 { 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 { return func(request echo.Context) error {
ctx := context.Background() ctx := context.Background()
var body Body
var widgetData UpdateDurationRequest if err := request.Bind(&body); err != nil {
if err := request.Bind(&widgetData); err != nil { return echo.NewHTTPError(400, err.Error())
}
if err := request.Validate(&body); err != nil {
return echo.NewHTTPError(400, err.Error()) return echo.NewHTTPError(400, err.Error())
} }
err := request.Validate(&widgetData) err := widgetService.UpdateDuration(
if err != nil {
return echo.NewHTTPError(400, err.Error())
}
err = widgetService.UpdateDuration(
ctx, ctx,
widgetData.WidgetID, body.WidgetID,
widgetData.Duration, body.Duration,
) )
if err != nil { if err != nil {
return request.JSON(422, "Update duration error") return request.JSON(422, "Update duration error")
} }
slog.Info("Duration updated")
return request.JSON(200, "Update duration successfully") 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")
}
}

View File

@ -2,6 +2,7 @@ package http
import ( import (
"context" "context"
. "donat-widget/internal/api/http/handlers/donat"
"donat-widget/internal/model/sql" "donat-widget/internal/model/sql"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"github.com/swaggo/echo-swagger" "github.com/swaggo/echo-swagger"
@ -15,8 +16,6 @@ import (
import ( import (
. "donat-widget/internal/api/http/handlers/widget" . "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" var PREFIX = "/api/widget"
@ -24,7 +23,6 @@ var PREFIX = "/api/widget"
func NewApp( func NewApp(
db model.Db, db model.Db,
widgetService model.WidgetService, widgetService model.WidgetService,
mediaService model.MediaService,
donatService model.DonatService, donatService model.DonatService,
) { ) {
server := echo.New() server := echo.New()
@ -35,7 +33,6 @@ func NewApp(
server.GET(PREFIX+"/table/drop", DropTale(db)) server.GET(PREFIX+"/table/drop", DropTale(db))
IncludeWidgetHandlers(server, widgetService) IncludeWidgetHandlers(server, widgetService)
IncludeMediaHandlers(server, mediaService)
IncludeDonatHandlers(server, donatService) IncludeDonatHandlers(server, donatService)
server.Logger.Fatal(server.Start(":8002")) server.Logger.Fatal(server.Start(":8002"))
@ -45,23 +42,18 @@ func IncludeDonatHandlers(
server *echo.Echo, server *echo.Echo,
donatService model.DonatService, donatService model.DonatService,
) { ) {
server.POST(PREFIX+"/donat/set", SetDonat(donatService)) server.POST(PREFIX+"/donat/create", CreateDonat(donatService))
server.DELETE(PREFIX+"/donat/delete/:donatID", DeleteDonat(donatService)) server.POST(PREFIX+"/donat/view/:donatID", MarkDonatView(donatService))
} server.POST(PREFIX+"/donat/paid/:donatID", MarkDonatPaid(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))
} }
func IncludeWidgetHandlers( func IncludeWidgetHandlers(
server *echo.Echo, server *echo.Echo,
widgetService model.WidgetService, 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.POST(PREFIX+"/create", CreateWidget(widgetService))
server.PATCH(PREFIX+"/duration/update", UpdateDuration(widgetService)) server.PATCH(PREFIX+"/duration/update", UpdateDuration(widgetService))
server.GET(PREFIX+"/html/:widgetID", GetWidgetHTML(widgetService)) server.GET(PREFIX+"/html/:widgetID", GetWidgetHTML(widgetService))

View File

@ -8,6 +8,7 @@ import (
type Config struct { type Config struct {
Db Database `yaml:"db"` Db Database `yaml:"db"`
Storage Storage `yaml:"storage"` Storage Storage `yaml:"storage"`
PaymentService PaymentService `yaml:"paymentService"`
} }
type Database struct { type Database struct {
@ -23,6 +24,11 @@ type Storage struct {
Master string `yaml:"master"` Master string `yaml:"master"`
} }
type PaymentService struct {
Host string `yaml:"host"`
Port string `yaml:"port"`
}
func Init() *Config { func Init() *Config {
data, err := os.ReadFile("internal/config/config.yaml") data, err := os.ReadFile("internal/config/config.yaml")
if err != nil { if err != nil {

View File

@ -11,3 +11,7 @@ server:
storage: storage:
filer: "http://92.63.193.151:8111" filer: "http://92.63.193.151:8111"
master: "http://92.63.193.151:9333" master: "http://92.63.193.151:9333"
paymentService:
host: "payment-service"
port: "8003"

View File

@ -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"`
}

View File

@ -2,6 +2,7 @@ package model
import ( import (
"context" "context"
"donat-widget/internal/model/api"
"github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgconn" "github.com/jackc/pgx/v5/pgconn"
@ -13,6 +14,10 @@ type WidgetService interface {
UpdateDuration(ctx context.Context, widgetID WidgetID, duration Duration) error UpdateDuration(ctx context.Context, widgetID WidgetID, duration Duration) error
GetWidgetInfo(ctx context.Context, widgetID WidgetID) (*DonatAndWidget, error) GetWidgetInfo(ctx context.Context, widgetID WidgetID) (*DonatAndWidget, error)
GetWidgetHTML(ctx context.Context, widgetID WidgetID) (WidgetHTML, 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 { type WidgetRepo interface {
@ -20,16 +25,7 @@ type WidgetRepo interface {
DeleteWidget(ctx context.Context, widgetID WidgetID) error DeleteWidget(ctx context.Context, widgetID WidgetID) error
UpdateDuration(ctx context.Context, widgetID WidgetID, duration Duration) error UpdateDuration(ctx context.Context, widgetID WidgetID, duration Duration) error
GetWidget(ctx context.Context, widgetID WidgetID) (*Widget, error) GetWidget(ctx context.Context, widgetID WidgetID) (*Widget, error)
} GetAllWidget(ctx context.Context, streamerID StreamerID) ([]*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 {
SetMediaFile(file UploadFile, filename string, size int64, collection string) (FileID, error) SetMediaFile(file UploadFile, filename string, size int64, collection string) (FileID, error)
GetMediaFile(fileID FileID) (DownloadFile, error) GetMediaFile(fileID FileID) (DownloadFile, error)
GetMediaUrl(ctx context.Context, widgetID WidgetID, mediaType MediaType) (MediaUrl, error) GetMediaUrl(ctx context.Context, widgetID WidgetID, mediaType MediaType) (MediaUrl, error)
@ -38,14 +34,16 @@ type MediaRepo interface {
} }
type DonatService interface { type DonatService interface {
SetDonat(ctx context.Context, widgetID WidgetID, text string, amount DonatAmount, donatUser string) error CreateDonat(ctx context.Context, streamerID StreamerID, orderID OrderID, amount DonatAmount, text string, donatUser string) error
DeleteDonat(ctx context.Context, DonatID DonatID) error MarkDonatPaid(ctx context.Context, orderID OrderID) error
MarkDonatView(ctx context.Context, DonatID DonatID) error
} }
type DonatRepo interface { 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) 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 { type Error interface {
@ -58,6 +56,9 @@ type Storage interface {
Update(file UploadFile, fileID FileID, filename string, size int64, collection string) error 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 { type Db interface {
Exec(ctx context.Context, query string, args ...interface{}) (pgconn.CommandTag, error) Exec(ctx context.Context, query string, args ...interface{}) (pgconn.CommandTag, error)
Query(ctx context.Context, query string, args ...interface{}) (pgx.Rows, error) Query(ctx context.Context, query string, args ...interface{}) (pgx.Rows, error)

View File

@ -7,15 +7,51 @@ import (
) )
type Widget struct { type Widget struct {
ID WidgetID ID WidgetID `db:"id"`
StreamerID StreamerID StreamerID StreamerID `db:"streamer_id"`
TemplateID TemplateID TemplateID TemplateID `db:"template_id"`
BackgroundUrl MediaUrl
ImageUrl MediaUrl BackgroundUrl MediaUrl `db:"background_url"`
AudioUrl MediaUrl ImageUrl MediaUrl `db:"image_url"`
Duration Duration AudioUrl MediaUrl `db:"audio_url"`
CreatedAt time.Time
UpdatedAt time.Time 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 { func (widget *Widget) GetMediaUrl(mediaType MediaType) MediaUrl {
@ -45,19 +81,3 @@ func (widget *Widget) NormalizeUrl() {
widget.AudioUrl = MediaUrl(selfDomain + "/audio/get/" + strWidgetID) 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
}

View File

@ -5,10 +5,14 @@ CREATE TABLE IF NOT EXISTS widgets (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
streamer_id INTEGER NOT NULL, streamer_id INTEGER NOT NULL,
template_id INTEGER NOT NULL, template_id INTEGER NOT NULL,
background_url TEXT DEFAULT '', background_url TEXT DEFAULT '',
image_url TEXT DEFAULT '', image_url TEXT DEFAULT '',
audio_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, created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_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 ( CREATE TABLE IF NOT EXISTS donats (
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
widget_id INTEGER NOT NULL, widget_id INTEGER NOT NULL,
text TEXT DEFAULT '', order_id TEXT NOT NULL,
amount TEXT DEFAULT '',
donat_user TEXT DEFAULT '', 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, created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_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 $$ RETURNS TRIGGER AS $$
BEGIN BEGIN
NEW.updated_at = NOW(); NEW.updated_at = NOW();
@ -40,9 +60,15 @@ CREATE TRIGGER update_updated_at_trigger
BEFORE UPDATE ON donats BEFORE UPDATE ON donats
FOR EACH ROW FOR EACH ROW
EXECUTE PROCEDURE update_updated_at(); 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 = ` var DropTableQuery = `
DROP TABLE IF EXISTS widgets; DROP TABLE IF EXISTS widgets;
DROP TABLE IF EXISTS donats; DROP TABLE IF EXISTS donats;
DROP TABLE IF EXISTS targets;
` `

View File

@ -5,27 +5,21 @@ import (
"fmt" "fmt"
) )
var CreateWidgetQuery = ` var CreateWidget = `
INSERT INTO widgets (streamer_id, template_id) INSERT INTO widgets (streamer_id, template_id)
VALUES (@streamer_id, @template_id); VALUES (@streamer_id, @template_id);
` `
var UpdateDuration = `
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 = `
UPDATE widgets UPDATE widgets
SET duration = (@duration) SET duration = (@duration)
WHERE id = (@id) 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(` query := fmt.Sprintf(`
UPDATE widgets UPDATE widgets
SET %s = (@%s) SET %s = (@%s)
@ -34,13 +28,9 @@ func UpdateMediaUrlQuery(mediaType model.MediaType) string {
return query return query
} }
var GetDonatQuery = ` var GetAllWidget = `
SELECT * FROM donats WHERE widget_id = (@widget_id);
`
var GetWidgetQuery = `
SELECT * FROM widgets SELECT * FROM widgets
WHERE id = (@id); WHERE streamer_id = (@streamer_id);
` `
func GetMediaUrl(mediaType model.MediaType) string { func GetMediaUrl(mediaType model.MediaType) string {
@ -51,3 +41,22 @@ func GetMediaUrl(mediaType model.MediaType) string {
`, mediaType) `, mediaType)
return query 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);
`

View File

@ -23,5 +23,9 @@ type UploadFile *multipart.File
type DownloadFile []byte type DownloadFile []byte
type FileID string type FileID string
type DonatAmount string type DonatAmount int
type DonatID int type DonatID int
type OrderID string
type TargetID int
type Collected int

View File

@ -19,20 +19,22 @@ type RepoDonat struct {
db model.Db db model.Db
} }
func (repoDonat *RepoDonat) SetDonat( func (repoDonat *RepoDonat) CreateDonat(
ctx context.Context, ctx context.Context,
widgetID model.WidgetID, widgetID model.WidgetID,
text string, orderID model.OrderID,
amount model.DonatAmount, amount model.DonatAmount,
text string,
donatUser string, donatUser string,
) error { ) error {
args := model.QueryArgs{ args := model.QueryArgs{
"widget_id": widgetID, "widget_id": widgetID,
"order_id": orderID,
"text": text, "text": text,
"amount": amount, "amount": amount,
"donat_user": donatUser, "donat_user": donatUser,
} }
_, err := repoDonat.db.Query(ctx, sql.SetDonatQuery, args) _, err := repoDonat.db.Query(ctx, sql.CreateDonat, args)
if err != nil { if err != nil {
slog.Error("repoDonat.db.Query: " + err.Error()) slog.Error("repoDonat.db.Query: " + err.Error())
return err return err
@ -47,8 +49,10 @@ func (repoDonat *RepoDonat) GetDonat(
) ([]*model.Donat, error) { ) ([]*model.Donat, error) {
args := pgx.NamedArgs{ args := pgx.NamedArgs{
"widget_id": widgetID, "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 { if err != nil {
slog.Error("repoDonat.db.Query: " + err.Error()) slog.Error("repoDonat.db.Query: " + err.Error())
return nil, err return nil, err
@ -64,14 +68,32 @@ func (repoDonat *RepoDonat) GetDonat(
return donats, nil return donats, nil
} }
func (repoDonat *RepoDonat) DeleteDonat( func (repoDonat *RepoDonat) MarkDonatView(
ctx context.Context, ctx context.Context,
donatID model.DonatID, donatID model.DonatID,
) error { ) error {
args := pgx.NamedArgs{ 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 { if err != nil {
slog.Error("repoDonat.db.Query: " + err.Error()) slog.Error("repoDonat.db.Query: " + err.Error())
return err return err

View File

@ -0,0 +1 @@
package target

View File

@ -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
}

View File

@ -9,14 +9,19 @@ import (
"log/slog" "log/slog"
) )
func New(db model.Db) *RepoWidget { func New(
db model.Db,
storage model.Storage,
) *RepoWidget {
return &RepoWidget{ return &RepoWidget{
db: db, db: db,
storage: storage,
} }
} }
type RepoWidget struct { type RepoWidget struct {
db model.Db db model.Db
storage model.Storage
} }
func (widgetRepo *RepoWidget) CreateWidget( func (widgetRepo *RepoWidget) CreateWidget(
@ -28,7 +33,7 @@ func (widgetRepo *RepoWidget) CreateWidget(
"streamer_id": streamerID, "streamer_id": streamerID,
"template_id": templateID, "template_id": templateID,
} }
_, err := widgetRepo.db.Query(ctx, sql.CreateWidgetQuery, args) _, err := widgetRepo.db.Query(ctx, sql.CreateWidget, args)
if err != nil { if err != nil {
slog.Error("widgetRepo.db.Query: " + err.Error()) slog.Error("widgetRepo.db.Query: " + err.Error())
return 0, err return 0, err
@ -45,7 +50,7 @@ func (widgetRepo *RepoWidget) GetWidget(
args := model.QueryArgs{ args := model.QueryArgs{
"id": widgetID, "id": widgetID,
} }
rows, err := widgetRepo.db.Query(ctx, sql.GetWidgetQuery, args) rows, err := widgetRepo.db.Query(ctx, sql.GetWidget, args)
if err != nil { if err != nil {
slog.Error("widgetRepo.db.Query: " + err.Error()) slog.Error("widgetRepo.db.Query: " + err.Error())
return nil, err return nil, err
@ -69,6 +74,29 @@ func (widgetRepo *RepoWidget) GetWidget(
return widget, nil 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( func (widgetRepo *RepoWidget) DeleteWidget(
ctx context.Context, ctx context.Context,
widgetID model.WidgetID, widgetID model.WidgetID,
@ -85,7 +113,7 @@ func (widgetRepo *RepoWidget) UpdateDuration(
"id": widgetID, "id": widgetID,
"duration": duration, "duration": duration,
} }
_, err := widgetRepo.db.Query(ctx, sql.UpdateDurationQuery, args) _, err := widgetRepo.db.Query(ctx, sql.UpdateDuration, args)
if err != nil { if err != nil {
slog.Error("widgetRepo.db.Query: " + err.Error()) slog.Error("widgetRepo.db.Query: " + err.Error())
return err return err
@ -93,3 +121,113 @@ func (widgetRepo *RepoWidget) UpdateDuration(
return nil 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
}

View File

@ -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
}

View File

@ -0,0 +1 @@
package target

View File

@ -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
}

View File

@ -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
}

View File

@ -113,3 +113,117 @@ func (widgetService *ServiceWidget) GetWidgetInfo(
return &donatAndWidget, nil 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
}

View File

@ -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
}