diff --git a/cmd/main.go b/cmd/main.go index e086a3f..eca285b 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,6 +1,24 @@ package main -import "donat-widget/internal/app/http" +import ( + "context" + "donat-widget/internal/app/http" + "donat-widget/internal/config" +) + +import ( + "donat-widget/infrastructure/pg" + "donat-widget/infrastructure/weed" +) + +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 @@ -10,6 +28,26 @@ import "donat-widget/internal/app/http" // @BasePath /api/widget func main() { - httpServer := http.NewApp() - http.Run(httpServer) + cfg := config.Init() + + // INFRASTRUCTURE + 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) + + // REPOSITORIES + widgetRepo := WidgetRepo.New(db) + mediaRepo := MediaRepo.New(db, storage) + donatRepo := DonatRepo.New(db) + + // SERVICES + widgetService := WidgetService.New(widgetRepo, donatRepo) + mediaService := MediaService.New(mediaRepo) + donatService := DonatService.New(donatRepo) + + http.NewApp( + db, + widgetService, + mediaService, + donatService, + ) } diff --git a/infrastructure/pg/connection.go b/infrastructure/pg/connection.go index 397efac..bf89766 100644 --- a/infrastructure/pg/connection.go +++ b/infrastructure/pg/connection.go @@ -33,28 +33,6 @@ func NewPgPool( return pgInstance } -func (pg *Postgres) CreateTable(ctx context.Context) error { - _, err := pg.db.Exec(ctx, createTableQuery) - if err != nil { - return err - } - - _, err = pg.db.Exec(ctx, onUpdateTableQuery) - if err != nil { - return err - } - - return nil -} - -func (pg *Postgres) DropTable(ctx context.Context) error { - _, err := pg.db.Exec(ctx, dropTableQuery) - if err != nil { - return err - } - return nil -} - func (pg *Postgres) Exec(ctx context.Context, query string, args ...interface{}) (pgconn.CommandTag, error) { result, err := pg.db.Exec(ctx, query, args...) if err != nil { @@ -70,3 +48,20 @@ func (pg *Postgres) Query(ctx context.Context, query string, args ...interface{} } return result, nil } + +func (pg *Postgres) CreateTable(ctx context.Context, query string) error { + _, err := pg.db.Exec(ctx, query) + if err != nil { + return err + } + + return nil +} + +func (pg *Postgres) DropTable(ctx context.Context, query string) error { + _, err := pg.db.Exec(ctx, query) + if err != nil { + return err + } + return nil +} diff --git a/infrastructure/pg/models.go b/infrastructure/pg/models.go deleted file mode 100644 index 0c2dc9d..0000000 --- a/infrastructure/pg/models.go +++ /dev/null @@ -1,50 +0,0 @@ -package pg - -var createTableQuery = ` - 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, - created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP - ); - - 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 '', - created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP - ); - ` - -var onUpdateTableQuery = ` - CREATE OR REPLACE FUNCTION update_updated_at() - RETURNS TRIGGER AS $$ - BEGIN - NEW.updated_at = NOW(); - RETURN NEW; - END; - $$ LANGUAGE 'plpgsql'; - - CREATE TRIGGER update_updated_at_trigger - BEFORE UPDATE ON widgets - FOR EACH ROW - EXECUTE PROCEDURE update_updated_at(); - - CREATE TRIGGER update_updated_at_trigger - BEFORE UPDATE ON donats - FOR EACH ROW - EXECUTE PROCEDURE update_updated_at(); -` - -var dropTableQuery = ` - DROP TABLE IF EXISTS widgets; - DROP TABLE IF EXISTS donats; - ` diff --git a/infrastructure/weed/weed.go b/infrastructure/weed/weed.go index 61de523..86daf9e 100644 --- a/infrastructure/weed/weed.go +++ b/infrastructure/weed/weed.go @@ -17,7 +17,7 @@ func NewWeed( filer string, masterUrl string, -) (*Weed, error) { +) *Weed { filers := []string{filer} sw, err := goseaweedfs.NewSeaweed( masterUrl, @@ -26,9 +26,9 @@ func NewWeed( &http.Client{Timeout: 5 * time.Minute}, ) if err != nil { - return &Weed{}, err + panic("Failed to create seaweed: " + err.Error()) } - return &Weed{sw}, err + return &Weed{sw} } diff --git a/internal/api/http/handlers/widget/donat/donat.go b/internal/api/http/handlers/widget/donat/donat.go index 81a3014..3d3fa26 100644 --- a/internal/api/http/handlers/widget/donat/donat.go +++ b/internal/api/http/handlers/widget/donat/donat.go @@ -8,22 +8,6 @@ import ( "strconv" ) -type donatSetter interface { - SetDonat( - ctx context.Context, - widgetID model.WidgetID, - text string, - amount model.DonatAmount, - donatUser string, - ) error -} -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"` -} - // SetDonat // // @Description Set donat @@ -32,7 +16,7 @@ type SetDonatRequest struct { // @Produce json // @Param RegisterData body SetDonatRequest true "Set donat" // @Router /api/widget/donat/set [post] -func SetDonat(donatService donatSetter) echo.HandlerFunc { +func SetDonat(donatService model.DonatService) echo.HandlerFunc { return func(request echo.Context) error { ctx := context.Background() @@ -63,13 +47,6 @@ func SetDonat(donatService donatSetter) echo.HandlerFunc { } } -type donatDeleter interface { - DeleteDonat( - ctx context.Context, - DonatID model.DonatID, - ) error -} - // DeleteDonat // // @Description Delete donat @@ -77,7 +54,7 @@ type donatDeleter interface { // @Accept json // @Produce json // @Router /api/widget/donat/delete/{donatID} [post] -func DeleteDonat(donatService donatDeleter) echo.HandlerFunc { +func DeleteDonat(donatService model.DonatService) echo.HandlerFunc { return func(request echo.Context) error { ctx := context.Background() diff --git a/internal/api/http/handlers/widget/donat/models.go b/internal/api/http/handlers/widget/donat/models.go new file mode 100644 index 0000000..9acb383 --- /dev/null +++ b/internal/api/http/handlers/widget/donat/models.go @@ -0,0 +1,10 @@ +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 index 10fea56..35dfb5b 100644 --- a/internal/api/http/handlers/widget/media/media.go +++ b/internal/api/http/handlers/widget/media/media.go @@ -8,18 +8,6 @@ import ( "strconv" ) -type FileSetter interface { - SetMediaFile( - ctx context.Context, - mediaType model.MediaType, - widgetID model.WidgetID, - file model.UploadFile, - filename string, - size int64, - collection string, - ) error -} - // SetMediaFile // // @Description Upload media @@ -29,7 +17,7 @@ type FileSetter interface { // @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 FileSetter) echo.HandlerFunc { +func SetMediaFile(service model.MediaService) echo.HandlerFunc { return func(request echo.Context) error { ctx := context.Background() @@ -67,14 +55,6 @@ func SetMediaFile(service FileSetter) echo.HandlerFunc { } } -type FileGetter interface { - GetMediaFile( - ctx context.Context, - widgetID model.WidgetID, - mediaType model.MediaType, - ) (model.DownloadFile, error) -} - // GetMediaFile // // @Description Get media @@ -83,7 +63,7 @@ type FileGetter interface { // @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 FileGetter) echo.HandlerFunc { +func GetMediaFile(service model.MediaService) echo.HandlerFunc { return func(request echo.Context) error { ctx := context.Background() @@ -109,19 +89,7 @@ func GetMediaFile(service FileGetter) echo.HandlerFunc { } } -type FileUpdater interface { - UpdateMediaFile( - ctx context.Context, - widgetID model.WidgetID, - mediaType model.MediaType, - file model.UploadFile, - filename string, - size int64, - collection string, - ) error -} - -func UpdateMediaFile(service FileUpdater) echo.HandlerFunc { +func UpdateMediaFile(service model.MediaService) echo.HandlerFunc { return func(request echo.Context) error { ctx := context.Background() @@ -160,25 +128,12 @@ func UpdateMediaFile(service FileUpdater) echo.HandlerFunc { } } -type UrlSetter interface { - SetMediaUrl( - ctx context.Context, - mediaType model.MediaType, - widgetID model.WidgetID, - mediaURL model.MediaUrl, - ) error -} -type SetRequest struct { - WidgetID model.WidgetID `json:"widgetID" validate:"required"` - MediaUrl model.MediaUrl `json:"mediaUrl" validate:"required"` -} - // 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 UrlSetter) echo.HandlerFunc { +func SetMediaUrl(service model.MediaService) echo.HandlerFunc { return func(request echo.Context) error { ctx := context.Background() diff --git a/internal/api/http/handlers/widget/media/models.go b/internal/api/http/handlers/widget/media/models.go new file mode 100644 index 0000000..9f2c5fd --- /dev/null +++ b/internal/api/http/handlers/widget/media/models.go @@ -0,0 +1,8 @@ +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 new file mode 100644 index 0000000..8667f0d --- /dev/null +++ b/internal/api/http/handlers/widget/models.go @@ -0,0 +1,27 @@ +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 583aa0d..13ee41e 100644 --- a/internal/api/http/handlers/widget/widget.go +++ b/internal/api/http/handlers/widget/widget.go @@ -8,21 +8,6 @@ import ( "strconv" ) -type widgetCreator interface { - CreateWidget( - ctx context.Context, - streamerID model.StreamerID, - templateID model.TemplateID, - ) (model.WidgetID, error) -} -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"` -} - // CreateWidget // // @Description Create widget @@ -32,7 +17,7 @@ type CreateWidgetResponse struct { // @Param RegisterData body CreateWidgetRequest true "Create widget" // @Success 200 {object} CreateWidgetResponse // @Router /api/widget/create [post] -func CreateWidget(widgetService widgetCreator) echo.HandlerFunc { +func CreateWidget(widgetService model.WidgetService) echo.HandlerFunc { return func(request echo.Context) error { ctx := context.Background() @@ -63,13 +48,6 @@ func CreateWidget(widgetService widgetCreator) echo.HandlerFunc { } } -type widgetHTMLGetter interface { - GetWidgetHTML( - ctx context.Context, - widgetID model.WidgetID, - ) (model.WidgetHTML, error) -} - // GetWidgetHTML @Description Get widget // // @Tags Widget @@ -77,7 +55,7 @@ type widgetHTMLGetter interface { // @Produce json // @Success 200 // @Router /api/widget/html/{widgetID} [get] -func GetWidgetHTML(widgetService widgetHTMLGetter) echo.HandlerFunc { +func GetWidgetHTML(widgetService model.WidgetService) echo.HandlerFunc { return func(request echo.Context) error { ctx := context.Background() @@ -98,24 +76,6 @@ func GetWidgetHTML(widgetService widgetHTMLGetter) echo.HandlerFunc { } } -type widgetInfoGetter interface { - GetWidgetInfo( - ctx context.Context, - widgetID model.WidgetID, - ) (*model.DonatAndWidget, error) -} - -type GetInfoResponse 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"` -} - // GetWidgetInfo // // @Description Widget Info @@ -124,7 +84,7 @@ type GetInfoResponse struct { // @Produce json // @Param widgetID path int true "Widget ID" // @Router /api/widget/info/{widgetID} [get] -func GetWidgetInfo(widgetService widgetInfoGetter) echo.HandlerFunc { +func GetWidgetInfo(widgetService model.WidgetService) echo.HandlerFunc { return func(request echo.Context) error { ctx := context.Background() @@ -139,14 +99,14 @@ func GetWidgetInfo(widgetService widgetInfoGetter) echo.HandlerFunc { } if !donatAndWidget.Display { - response := GetInfoResponse{ + response := GetWidgetInfoResponse{ Display: donatAndWidget.Display, } slog.Info("Get widget info successfully") return request.JSON(200, response) } - response := GetInfoResponse{ + response := GetWidgetInfoResponse{ AudioUrl: donatAndWidget.Widget.AudioUrl, ImageUrl: donatAndWidget.Widget.ImageUrl, Text: donatAndWidget.Donat.Text, @@ -161,19 +121,6 @@ func GetWidgetInfo(widgetService widgetInfoGetter) echo.HandlerFunc { } } -type widgetDurationUpdater interface { - UpdateWidgetDuration( - ctx context.Context, - widgetID model.WidgetID, - duration model.Duration, - ) error -} - -type UpdateDurationRequest struct { - WidgetID model.WidgetID `json:"widgetID" validate:"required"` - Duration model.Duration `json:"duration" validate:"required"` -} - // UpdateDuration // // @Description UpdateDuration @@ -183,7 +130,7 @@ type UpdateDurationRequest struct { // @Param UpdateData body UpdateDurationRequest true "UpdateDuration" // @Success 200 // @Router /api/widget/duration/update [post] -func UpdateDuration(widgetService widgetDurationUpdater) echo.HandlerFunc { +func UpdateDuration(widgetService model.WidgetService) echo.HandlerFunc { return func(request echo.Context) error { ctx := context.Background() @@ -197,7 +144,7 @@ func UpdateDuration(widgetService widgetDurationUpdater) echo.HandlerFunc { return echo.NewHTTPError(400, err.Error()) } - err = widgetService.UpdateWidgetDuration( + err = widgetService.UpdateDuration( ctx, widgetData.WidgetID, widgetData.Duration, diff --git a/internal/app/http/app.go b/internal/app/http/app.go index 8188c2e..69a6c5b 100644 --- a/internal/app/http/app.go +++ b/internal/app/http/app.go @@ -2,124 +2,77 @@ package http import ( "context" + "donat-widget/internal/model/sql" "github.com/labstack/echo/v4" "github.com/swaggo/echo-swagger" "log/slog" - "os" ) import ( - "donat-widget/infrastructure/pg" - "donat-widget/infrastructure/weed" - "donat-widget/internal/config" "donat-widget/internal/model" "donat-widget/pkg/validator" ) import ( - widgetHandler "donat-widget/internal/api/http/handlers/widget" - widgetService "donat-widget/internal/service/widget" - - mediaHandler "donat-widget/internal/api/http/handlers/widget/media" - mediaService "donat-widget/internal/service/widget/media" - - donatHandler "donat-widget/internal/api/http/handlers/widget/donat" - donatService "donat-widget/internal/service/widget/donat" + . "donat-widget/internal/api/http/handlers/widget" + . "donat-widget/internal/api/http/handlers/widget/donat" + . "donat-widget/internal/api/http/handlers/widget/media" ) -type App struct { - Config *config.Config -} +var PREFIX = "/api/widget" -func NewApp() *echo.Echo { - app := &App{} - _ = app.InitLogger() - app.Config = app.InitConfig() - db := app.initDB() - storage := app.InitStorage() - server := InitHTTPServer() - InitHandlers(server, db, storage) - - return server -} - -func Run(server *echo.Echo) { - server.Logger.Fatal(server.Start(":8002")) -} - -func InitHTTPServer() *echo.Echo { +func NewApp( + db model.Db, + widgetService model.WidgetService, + mediaService model.MediaService, + donatService model.DonatService, +) { server := echo.New() server.Validator = validator.NewValidator() - return server -} -func (a *App) InitConfig() *config.Config { - cfg := config.Init() - return cfg -} - -func (a *App) InitLogger() *slog.Logger { - logger := slog.New(slog.NewJSONHandler(os.Stdout, nil)) - slog.SetDefault(logger) - return logger -} - -func (a *App) InitStorage() *weed.Weed { - storage, err := weed.NewWeed( - a.Config.Storage.Filer, - a.Config.Storage.Master, - ) - if err != nil { - panic(err) - } - - return storage -} - -func (a *App) initDB() *pg.Postgres { - - db := pg.NewPgPool( - context.Background(), - a.Config.Db.Username, - a.Config.Db.Password, - a.Config.Db.Host, - a.Config.Db.Port, - a.Config.Db.DBName, - ) - return db -} - -func InitHandlers( - server *echo.Echo, - db *pg.Postgres, - storage model.Storage, -) { - PREFIX := "/api/widget" server.GET(PREFIX+"/docs/*", echoSwagger.WrapHandler) server.GET(PREFIX+"/table/create", CreateTale(db)) server.GET(PREFIX+"/table/drop", DropTale(db)) - widgetSvc := widgetService.New(db) - server.GET(PREFIX+"/create", widgetHandler.CreateWidget(widgetSvc)) - server.GET(PREFIX+"/html/:widgetID", widgetHandler.GetWidgetHTML(widgetSvc)) - server.GET(PREFIX+"/info/:widgetID", widgetHandler.GetWidgetInfo(widgetSvc)) - server.GET(PREFIX+"/duration/update", widgetHandler.UpdateDuration(widgetSvc)) + IncludeWidgetHandlers(server, widgetService) + IncludeMediaHandlers(server, mediaService) + IncludeDonatHandlers(server, donatService) - mediaSvc := mediaService.New(db, storage) - server.POST(PREFIX+"/media/:mediaType/upload", mediaHandler.SetMediaFile(mediaSvc)) - server.GET(PREFIX+"/media/:mediaType/get/:widgetID", mediaHandler.GetMediaFile(mediaSvc)) - server.POST(PREFIX+"/media/:mediaType/set", mediaHandler.SetMediaUrl(mediaSvc)) - - donatSvc := donatService.New(db) - server.POST(PREFIX+"/donat/set", donatHandler.SetDonat(donatSvc)) - server.DELETE(PREFIX+"/donat/delete/:donatID", donatHandler.DeleteDonat(donatSvc)) + server.Logger.Fatal(server.Start(":8002")) } -func CreateTale(db *pg.Postgres) echo.HandlerFunc { +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)) +} + +func IncludeWidgetHandlers( + server *echo.Echo, + widgetService model.WidgetService, +) { + server.POST(PREFIX+"/create", CreateWidget(widgetService)) + server.PATCH(PREFIX+"/duration/update", UpdateDuration(widgetService)) + server.GET(PREFIX+"/html/:widgetID", GetWidgetHTML(widgetService)) + server.GET(PREFIX+"/info/:widgetID", GetWidgetInfo(widgetService)) +} + +func CreateTale(db model.Db) echo.HandlerFunc { return func(request echo.Context) error { ctx := context.Background() - err := db.CreateTable(ctx) + err := db.CreateTable(ctx, sql.CreateTableQuery) if err != nil { slog.Error("db.CreateTable: ", err) return err @@ -130,11 +83,11 @@ func CreateTale(db *pg.Postgres) echo.HandlerFunc { } } -func DropTale(db *pg.Postgres) echo.HandlerFunc { +func DropTale(db model.Db) echo.HandlerFunc { return func(request echo.Context) error { ctx := context.Background() - err := db.DropTable(ctx) + err := db.DropTable(ctx, sql.DropTableQuery) if err != nil { slog.Error("db.DropTable: ", err) return err diff --git a/internal/model/interfaces.go b/internal/model/interfaces.go index bd8e5b3..4b14b4d 100644 --- a/internal/model/interfaces.go +++ b/internal/model/interfaces.go @@ -3,108 +3,64 @@ package model import ( "context" "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" ) -type Storage interface { - Upload( - file UploadFile, - filename string, - size int64, - collection string, - ) (FileData, error) - Download( - FileID FileID, - ) (DownloadFile, error) - Update( - file UploadFile, - fileID FileID, - filename string, - size int64, - collection string, - ) error +type WidgetService interface { + CreateWidget(ctx context.Context, streamerID StreamerID, templateId TemplateID) (WidgetID, error) + DeleteWidget(ctx context.Context, widgetID WidgetID) error + 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) } type WidgetRepo interface { - CreateWidget( - ctx context.Context, - streamerID StreamerID, - templateId TemplateID, - ) (WidgetID, error) - - DeleteWidget( - ctx context.Context, - widgetID WidgetID, - ) error - - GetWidget( - ctx context.Context, - widgetID WidgetID, - ) (*Widget, error) - - UpdateWidgetDuration( - ctx context.Context, - widgetID WidgetID, - duration Duration, - ) error + CreateWidget(ctx context.Context, streamerID StreamerID, templateId TemplateID) (WidgetID, error) + 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 DonatRepo interface { - SetDonat( - ctx context.Context, - widgetID WidgetID, - text string, - amount DonatAmount, - donatUser string, - ) error - GetDonat( - ctx context.Context, - widgetID WidgetID, - ) ([]*Donat, error) - DeleteDonat( - ctx context.Context, - donatID DonatID, - ) 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) - GetMediaFile( - fileID FileID, - ) (DownloadFile, error) - GetMediaUrl( - ctx context.Context, - widgetID WidgetID, - mediaType MediaType, - ) (MediaUrl, error) - SetMediaUrl( - ctx context.Context, - widgetID WidgetID, - mediaUrl MediaUrl, - mediaType MediaType, - ) error - UpdateMediaFile( - ctx context.Context, - widgetID WidgetID, - file UploadFile, - fileID FileID, - filename string, - size int64, - collection string, - mediaType MediaType, - ) 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) + SetMediaUrl(ctx context.Context, widgetID WidgetID, mediaUrl MediaUrl, mediaType MediaType) error + UpdateMediaFile(ctx context.Context, widgetID WidgetID, file UploadFile, fileID FileID, filename string, size int64, collection string, mediaType MediaType) error +} + +type DonatService interface { + SetDonat(ctx context.Context, widgetID WidgetID, text string, amount DonatAmount, donatUser string) error + DeleteDonat(ctx context.Context, DonatID DonatID) error +} + +type DonatRepo interface { + SetDonat(ctx context.Context, widgetID WidgetID, text string, amount DonatAmount, donatUser string) error + GetDonat(ctx context.Context, widgetID WidgetID) ([]*Donat, error) + DeleteDonat(ctx context.Context, donatID DonatID) error } type Error interface { Error() string } +type Storage interface { + Upload(file UploadFile, filename string, size int64, collection string) (FileData, error) + Download(FileID FileID) (DownloadFile, error) + Update(file UploadFile, fileID FileID, filename string, size int64, collection string) 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) + CreateTable(ctx context.Context, query string) error + DropTable(ctx context.Context, query string) error } diff --git a/internal/model/models.go b/internal/model/models.go index 20f0837..c54443e 100644 --- a/internal/model/models.go +++ b/internal/model/models.go @@ -1,6 +1,8 @@ package model import ( + "strconv" + "strings" "time" ) @@ -16,6 +18,34 @@ type Widget struct { UpdatedAt time.Time } +func (widget *Widget) GetMediaUrl(mediaType MediaType) MediaUrl { + var mediaUrl MediaUrl + if mediaType == "background_url" { + mediaUrl = widget.BackgroundUrl + } else if mediaType == "image_url" { + mediaUrl = widget.ImageUrl + } else if mediaType == "audio_url" { + mediaUrl = widget.AudioUrl + } + return mediaUrl +} + +func (widget *Widget) NormalizeUrl() { + selfDomain := "http://localhost:8002/api/widget/media" + strWidgetID := strconv.Itoa(int(widget.ID)) + if !strings.Contains(string(widget.ImageUrl), "http") && widget.ImageUrl != "" { + widget.ImageUrl = MediaUrl(selfDomain + "/image/get/" + strWidgetID) + } + + if !strings.Contains(string(widget.BackgroundUrl), "http") && widget.BackgroundUrl != "" { + widget.BackgroundUrl = MediaUrl(selfDomain + "/background/get/" + strWidgetID) + } + + if !strings.Contains(string(widget.AudioUrl), "http") && widget.AudioUrl != "" { + widget.AudioUrl = MediaUrl(selfDomain + "/audio/get/" + strWidgetID) + } +} + type Donat struct { ID DonatID WidgetID WidgetID diff --git a/internal/model/sql/model.go b/internal/model/sql/model.go new file mode 100644 index 0000000..e200144 --- /dev/null +++ b/internal/model/sql/model.go @@ -0,0 +1,48 @@ +package sql + +var CreateTableQuery = ` +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, + created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP +); + +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 '', + 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(); + RETURN NEW; +END; +$$ LANGUAGE 'plpgsql'; + +CREATE TRIGGER update_updated_at_trigger +BEFORE UPDATE ON widgets +FOR EACH ROW +EXECUTE PROCEDURE update_updated_at(); + +CREATE TRIGGER update_updated_at_trigger +BEFORE UPDATE ON donats +FOR EACH ROW +EXECUTE PROCEDURE update_updated_at(); +` + +var DropTableQuery = ` +DROP TABLE IF EXISTS widgets; +DROP TABLE IF EXISTS donats; +` diff --git a/internal/model/sql/query.go b/internal/model/sql/query.go new file mode 100644 index 0000000..cdffb23 --- /dev/null +++ b/internal/model/sql/query.go @@ -0,0 +1,53 @@ +package sql + +import ( + "donat-widget/internal/model" + "fmt" +) + +var CreateWidgetQuery = ` +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 = ` +UPDATE widgets +SET duration = (@duration) +WHERE id = (@id) +` + +func UpdateMediaUrlQuery(mediaType model.MediaType) string { + query := fmt.Sprintf(` + UPDATE widgets + SET %s = (@%s) + WHERE id = (@id) + `, mediaType, mediaType) + return query +} + +var GetDonatQuery = ` +SELECT * FROM donats WHERE widget_id = (@widget_id); +` + +var GetWidgetQuery = ` +SELECT * FROM widgets +WHERE id = (@id); +` + +func GetMediaUrl(mediaType model.MediaType) string { + query := fmt.Sprintf(` + SELECT %s + FROM widgets + WHERE id = (@id) + `, mediaType) + return query +} diff --git a/internal/model/types.go b/internal/model/types.go index 631c79c..9e20c61 100644 --- a/internal/model/types.go +++ b/internal/model/types.go @@ -5,6 +5,8 @@ import ( "mime/multipart" ) +type QueryArgs map[string]any + type StreamerID int type WidgetID int type TemplateID int diff --git a/internal/repository/widget/donat/donat.go b/internal/repository/widget/donat/donat.go index 83cc862..eb15154 100644 --- a/internal/repository/widget/donat/donat.go +++ b/internal/repository/widget/donat/donat.go @@ -3,15 +3,16 @@ package donat import ( "context" "donat-widget/internal/model" + "donat-widget/internal/model/sql" "github.com/georgysavva/scany/v2/pgxscan" "github.com/jackc/pgx/v5" "log/slog" ) -func New( - db model.Db, -) *RepoDonat { - return &RepoDonat{db} +func New(db model.Db) *RepoDonat { + return &RepoDonat{ + db: db, + } } type RepoDonat struct { @@ -25,23 +26,18 @@ func (repoDonat *RepoDonat) SetDonat( amount model.DonatAmount, donatUser string, ) error { - query := ` - INSERT INTO donats (widget_id, text, amount, donat_user) - VALUES (@widget_id, @text, @amount, @donat_user); - ` - - args := pgx.NamedArgs{ + args := model.QueryArgs{ "widget_id": widgetID, "text": text, "amount": amount, "donat_user": donatUser, } - - _, err := repoDonat.db.Query(ctx, query, args) + _, err := repoDonat.db.Query(ctx, sql.SetDonatQuery, args) if err != nil { slog.Error("repoDonat.db.Query: " + err.Error()) return err } + return nil } @@ -49,21 +45,16 @@ func (repoDonat *RepoDonat) GetDonat( ctx context.Context, widgetID model.WidgetID, ) ([]*model.Donat, error) { - query := ` - SELECT * FROM donats WHERE widget_id = (@widget_id); - ` - args := pgx.NamedArgs{ "widget_id": widgetID, } - - rows, err := repoDonat.db.Query(ctx, query, args) + rows, err := repoDonat.db.Query(ctx, sql.GetDonatQuery, args) if err != nil { slog.Error("repoDonat.db.Query: " + err.Error()) return nil, err } - var donats []*model.Donat + var donats []*model.Donat err = pgxscan.ScanAll(&donats, rows) if err != nil { slog.Error("repoMedia.pgxscan.ScanAll: " + err.Error()) @@ -77,15 +68,10 @@ func (repoDonat *RepoDonat) DeleteDonat( ctx context.Context, donatID model.DonatID, ) error { - query := ` - DELETE FROM donats WHERE id = (@id); - ` - args := pgx.NamedArgs{ "id": donatID, } - - _, err := repoDonat.db.Query(ctx, query, args) + _, err := repoDonat.db.Query(ctx, sql.DeleteDonatQuery, args) if err != nil { slog.Error("repoDonat.db.Query: " + err.Error()) return err diff --git a/internal/repository/widget/media/media.go b/internal/repository/widget/media/media.go index b10adb1..2538ca8 100644 --- a/internal/repository/widget/media/media.go +++ b/internal/repository/widget/media/media.go @@ -3,10 +3,9 @@ package media import ( "context" "donat-widget/internal/model" + "donat-widget/internal/model/sql" "errors" - "fmt" "github.com/georgysavva/scany/v2/pgxscan" - "github.com/jackc/pgx/v5" "log/slog" ) @@ -14,7 +13,10 @@ func New( db model.Db, storage model.Storage, ) *RepoMedia { - return &RepoMedia{db, storage} + return &RepoMedia{ + db: db, + storage: storage, + } } type RepoMedia struct { @@ -28,19 +30,13 @@ func (repoMedia *RepoMedia) SetMediaFile( size int64, collection string, ) (model.FileID, error) { - fileData, err := repoMedia.storage.Upload( - file, - filename, - size, - collection, - ) + fileData, err := repoMedia.storage.Upload(file, filename, size, collection) if err != nil { slog.Error("repoMedia.storage.Upload: " + err.Error()) return "", err } - fileID := fileData.FileID - return model.FileID(fileID), nil + return model.FileID(fileData.FileID), nil } func (repoMedia *RepoMedia) GetMediaFile( @@ -51,6 +47,7 @@ func (repoMedia *RepoMedia) GetMediaFile( slog.Error("repoMedia.storage.Download: " + err.Error()) return nil, err } + return file, err } @@ -64,30 +61,18 @@ func (repoMedia *RepoMedia) UpdateMediaFile( collection string, mediaType model.MediaType, ) error { - err := repoMedia.storage.Update( - file, - fileID, - filename, - size, - collection, - ) + err := repoMedia.storage.Update(file, fileID, filename, size, collection) if err != nil { slog.Error("repoMedia.storage.Update: " + err.Error()) return err } mediaType = mediaType + "_url" - query := fmt.Sprintf(` - UPDATE widgets - SET %s = (@%s) - WHERE id = (@id) - `, mediaType, mediaType) - - args := pgx.NamedArgs{ + args := model.QueryArgs{ string(mediaType): model.MediaUrl(fileID), "id": widgetID, } - _, err = repoMedia.db.Query(ctx, query, args) + _, err = repoMedia.db.Query(ctx, sql.UpdateMediaUrlQuery(mediaType), args) if err != nil { slog.Error("repoMedia.db.Query: " + err.Error()) return err @@ -103,17 +88,12 @@ func (repoMedia *RepoMedia) SetMediaUrl( mediaType model.MediaType, ) error { mediaType = mediaType + "_url" - query := fmt.Sprintf(` - UPDATE widgets - SET %s = (@%s) - WHERE id = (@id) - `, mediaType, mediaType) - args := pgx.NamedArgs{ + args := model.QueryArgs{ string(mediaType): mediaUrl, "id": widgetID, } - _, err := repoMedia.db.Query(ctx, query, args) + _, err := repoMedia.db.Query(ctx, sql.UpdateMediaUrlQuery(mediaType), args) if err != nil { slog.Error("repoMedia.db.Query: " + err.Error()) return err @@ -127,22 +107,16 @@ func (repoMedia *RepoMedia) GetMediaUrl( mediaType model.MediaType, ) (model.MediaUrl, error) { mediaType = mediaType + "_url" - query := fmt.Sprintf(` - SELECT %s - FROM widgets - WHERE id = (@id) - `, mediaType) - args := pgx.NamedArgs{ + args := model.QueryArgs{ "id": widgetID, } - - var widgets []*model.Widget - rows, err := repoMedia.db.Query(ctx, query, args) + 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()) @@ -155,15 +129,7 @@ func (repoMedia *RepoMedia) GetMediaUrl( } widget := widgets[0] - var mediaUrl model.MediaUrl - - if mediaType == "background_url" { - mediaUrl = widget.BackgroundUrl - } else if mediaType == "image_url" { - mediaUrl = widget.ImageUrl - } else if mediaType == "audio_url" { - mediaUrl = widget.AudioUrl - } + mediaUrl := widget.GetMediaUrl(mediaType) return mediaUrl, nil } diff --git a/internal/repository/widget/widget.go b/internal/repository/widget/widget.go index 8c03e15..52be4b9 100644 --- a/internal/repository/widget/widget.go +++ b/internal/repository/widget/widget.go @@ -3,22 +3,20 @@ package widget import ( "context" "donat-widget/internal/model" + "donat-widget/internal/model/sql" "errors" "github.com/georgysavva/scany/v2/pgxscan" - "github.com/jackc/pgx/v5" "log/slog" - "strconv" - "strings" ) -func New( - db model.Db, -) *RepoWidget { - return &RepoWidget{db} +func New(db model.Db) *RepoWidget { + return &RepoWidget{ + db: db, + } } type RepoWidget struct { - model.Db + db model.Db } func (widgetRepo *RepoWidget) CreateWidget( @@ -26,19 +24,13 @@ func (widgetRepo *RepoWidget) CreateWidget( streamerID model.StreamerID, templateID model.TemplateID, ) (model.WidgetID, error) { - query := ` - INSERT INTO widgets (streamer_id, template_id) - VALUES (@streamer_id, @template_id); - ` - - args := pgx.NamedArgs{ + args := model.QueryArgs{ "streamer_id": streamerID, "template_id": templateID, } - - _, err := widgetRepo.Query(ctx, query, args) + _, err := widgetRepo.db.Query(ctx, sql.CreateWidgetQuery, args) if err != nil { - slog.Error("widgetRepo.Query: " + err.Error()) + slog.Error("widgetRepo.db.Query: " + err.Error()) return 0, err } @@ -50,21 +42,16 @@ func (widgetRepo *RepoWidget) GetWidget( ctx context.Context, widgetID model.WidgetID, ) (*model.Widget, error) { - query := ` - SELECT * FROM widgets - WHERE id = (@id); - ` - args := pgx.NamedArgs{ + args := model.QueryArgs{ "id": widgetID, } - - var widgets []*model.Widget - rows, err := widgetRepo.Query(ctx, query, args) + rows, err := widgetRepo.db.Query(ctx, sql.GetWidgetQuery, args) if err != nil { - slog.Error("widgetRepo.Query: " + err.Error()) + 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()) @@ -77,20 +64,7 @@ func (widgetRepo *RepoWidget) GetWidget( } widget := widgets[0] - - selfDomain := "http://localhost:8002/api/widget/media" - strWidgetID := strconv.Itoa(int(widgetID)) - if !strings.Contains(string(widget.ImageUrl), "http") && widget.ImageUrl != "" { - widget.ImageUrl = model.MediaUrl(selfDomain + "/image/get/" + strWidgetID) - } - - if !strings.Contains(string(widget.BackgroundUrl), "http") && widget.BackgroundUrl != "" { - widget.BackgroundUrl = model.MediaUrl(selfDomain + "/background/get/" + strWidgetID) - } - - if !strings.Contains(string(widget.AudioUrl), "http") && widget.AudioUrl != "" { - widget.AudioUrl = model.MediaUrl(selfDomain + "/audio/get/" + strWidgetID) - } + widget.NormalizeUrl() return widget, nil } @@ -102,25 +76,18 @@ func (widgetRepo *RepoWidget) DeleteWidget( panic("implement me") } -func (widgetRepo *RepoWidget) UpdateWidgetDuration( +func (widgetRepo *RepoWidget) UpdateDuration( ctx context.Context, widgetID model.WidgetID, duration model.Duration, ) error { - query := ` - UPDATE widgets - SET duration = (@duration) - WHERE id = (@id) - ` - - args := pgx.NamedArgs{ + args := model.QueryArgs{ "id": widgetID, "duration": duration, } - - _, err := widgetRepo.Query(ctx, query, args) + _, err := widgetRepo.db.Query(ctx, sql.UpdateDurationQuery, args) if err != nil { - slog.Error("widgetRepo.Query: " + err.Error()) + slog.Error("widgetRepo.db.Query: " + err.Error()) return err } diff --git a/internal/service/widget/donat/donat.go b/internal/service/widget/donat/donat.go index 43f7c67..fb3ed87 100644 --- a/internal/service/widget/donat/donat.go +++ b/internal/service/widget/donat/donat.go @@ -3,7 +3,6 @@ package donat import ( "context" "donat-widget/internal/model" - mediaRepo "donat-widget/internal/repository/widget/donat" "log/slog" ) @@ -12,10 +11,10 @@ type ServiceDonat struct { } func New( - db model.Db, + donatRepo model.DonatRepo, ) *ServiceDonat { return &ServiceDonat{ - donatRepo: mediaRepo.New(db), + donatRepo: donatRepo, } } diff --git a/internal/service/widget/media/media.go b/internal/service/widget/media/media.go index 9ba3930..24d72c0 100644 --- a/internal/service/widget/media/media.go +++ b/internal/service/widget/media/media.go @@ -3,21 +3,17 @@ package media import ( "context" "donat-widget/internal/model" - mediaRepo "donat-widget/internal/repository/widget/media" "log/slog" ) -type ServiceMedia struct { - mediaRepo model.MediaRepo +func New(mediaRepo model.MediaRepo) *ServiceMedia { + return &ServiceMedia{ + mediaRepo: mediaRepo, + } } -func New( - db model.Db, - storage model.Storage, -) *ServiceMedia { - return &ServiceMedia{ - mediaRepo: mediaRepo.New(db, storage), - } +type ServiceMedia struct { + mediaRepo model.MediaRepo } func (mediaService *ServiceMedia) SetMediaFile( diff --git a/internal/service/widget/widget.go b/internal/service/widget/widget.go index 3ad2687..2c7e58d 100644 --- a/internal/service/widget/widget.go +++ b/internal/service/widget/widget.go @@ -3,23 +3,24 @@ package widget import ( "context" "donat-widget/internal/model" - widgetRepo "donat-widget/internal/repository/widget" - donatRepo "donat-widget/internal/repository/widget/donat" "log/slog" ) +func New( + widgetRepo model.WidgetRepo, + donatRepo model.DonatRepo, +) *ServiceWidget { + return &ServiceWidget{ + widgetRepo: widgetRepo, + donatRepo: donatRepo, + } +} + type ServiceWidget struct { widgetRepo model.WidgetRepo donatRepo model.DonatRepo } -func New(db model.Db) *ServiceWidget { - return &ServiceWidget{ - widgetRepo: widgetRepo.New(db), - donatRepo: donatRepo.New(db), - } -} - func (widgetService *ServiceWidget) CreateWidget( ctx context.Context, streamerID model.StreamerID, @@ -45,6 +46,20 @@ func (widgetService *ServiceWidget) DeleteWidget( return nil } +func (widgetService *ServiceWidget) UpdateDuration( + ctx context.Context, + widgetID model.WidgetID, + duration model.Duration, +) error { + err := widgetService.widgetRepo.UpdateDuration(ctx, widgetID, duration) + if err != nil { + slog.Error("widgetService.widgetRepo.UpdateDuration: " + err.Error()) + return err + } + + return nil +} + func (widgetService *ServiceWidget) GetWidgetHTML( ctx context.Context, widgetID model.WidgetID, @@ -57,14 +72,10 @@ func (widgetService *ServiceWidget) GetWidgetHTML( return widgetHTML, err } - backgroundUrl := widget.BackgroundUrl - - templateID := widget.TemplateID - - if templateID == 1 { + if widget.TemplateID == 1 { widgetHTML = model.GetTemplate1( widgetID, - backgroundUrl, + widget.BackgroundUrl, ) } return widgetHTML, nil @@ -102,21 +113,3 @@ func (widgetService *ServiceWidget) GetWidgetInfo( return &donatAndWidget, nil } - -func (widgetService *ServiceWidget) UpdateWidgetDuration( - ctx context.Context, - widgetID model.WidgetID, - duration model.Duration, -) error { - err := widgetService.widgetRepo.UpdateWidgetDuration( - ctx, - widgetID, - duration, - ) - if err != nil { - slog.Error("widgetService.widgetRepo.UpdateWidgetDuration: " + err.Error()) - return err - } - - return nil -}