This commit is contained in:
mm 2024-09-27 13:50:20 +05:00
parent 14e82f3298
commit b94abfe75b
22 changed files with 418 additions and 562 deletions

View File

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

View File

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

View File

@ -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;
`

View File

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

View File

@ -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()

View File

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

View File

@ -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()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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;
`

View File

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

View File

@ -5,6 +5,8 @@ import (
"mime/multipart"
)
type QueryArgs map[string]any
type StreamerID int
type WidgetID int
type TemplateID int

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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