Compare commits
10 Commits
6846cca5b0
...
875b8e21da
Author | SHA1 | Date | |
---|---|---|---|
875b8e21da | |||
9e4553c6f3 | |||
238421c69f | |||
c33fa8a4f4 | |||
db55335add | |||
0dfeb43124 | |||
d302522256 | |||
bd86426553 | |||
2f01c1e500 | |||
81fcc84741 |
@ -80,5 +80,11 @@ func main() {
|
|||||||
targetService,
|
targetService,
|
||||||
fileService,
|
fileService,
|
||||||
authClient,
|
authClient,
|
||||||
|
cfg.Cors.AllowedOrigins,
|
||||||
|
cfg.Cors.AllowedMethods,
|
||||||
|
cfg.Cors.AllowedHeaders,
|
||||||
|
cfg.Cors.AllowCredentials,
|
||||||
|
cfg.Cors.ExposeHeaders,
|
||||||
|
cfg.Cors.MaxAge,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,8 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
- GO_ENV=production
|
- GO_ENV=production
|
||||||
restart: always
|
restart: always
|
||||||
networks:
|
# networks:
|
||||||
- donat-network
|
# - donat-network
|
||||||
volumes:
|
volumes:
|
||||||
- ./storage:/storage
|
- ./storage:/storage
|
||||||
|
|
||||||
@ -20,12 +20,13 @@ services:
|
|||||||
image: postgres:16.3-alpine3.20
|
image: postgres:16.3-alpine3.20
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
networks:
|
# networks:
|
||||||
- donat-network
|
# - donat-network
|
||||||
ports:
|
ports:
|
||||||
- "31002:5432"
|
- "31002:5432"
|
||||||
volumes:
|
volumes:
|
||||||
- ./docker/postgres/data:/var/lib/postgresql/data
|
- ./docker/postgres/data:/var/lib/postgresql/data
|
||||||
networks:
|
- ./sql:/docker-entrypoint-initdb.d
|
||||||
donat-network:
|
#networks:
|
||||||
external: true
|
# donat-network:
|
||||||
|
# external: true
|
@ -46,6 +46,7 @@ func CreateDonat(donatService model.DonatService) echo.HandlerFunc {
|
|||||||
body.DonatUser,
|
body.DonatUser,
|
||||||
body.TargetID,
|
body.TargetID,
|
||||||
body.Amount,
|
body.Amount,
|
||||||
|
body.MediaUrl,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return request.JSON(http.StatusInternalServerError, err.Error())
|
return request.JSON(http.StatusInternalServerError, err.Error())
|
||||||
@ -91,6 +92,7 @@ func CreateTestDonat(donatService model.DonatService) echo.HandlerFunc {
|
|||||||
body.Text,
|
body.Text,
|
||||||
body.DonatUser,
|
body.DonatUser,
|
||||||
body.TargetID,
|
body.TargetID,
|
||||||
|
body.MediaUrl,
|
||||||
body.Amount,
|
body.Amount,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -48,6 +48,7 @@ func CreateWidget(widgetService model.WidgetService) echo.HandlerFunc {
|
|||||||
body.Audio,
|
body.Audio,
|
||||||
body.Name,
|
body.Name,
|
||||||
body.IsActive,
|
body.IsActive,
|
||||||
|
body.VolumePercent,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error(err.Error())
|
slog.Error(err.Error())
|
||||||
|
@ -28,23 +28,24 @@ func NewApp(
|
|||||||
targetService model.TargetService,
|
targetService model.TargetService,
|
||||||
fileService model.FileService,
|
fileService model.FileService,
|
||||||
authClient model.AuthClient,
|
authClient model.AuthClient,
|
||||||
|
allowOrigins []string,
|
||||||
|
allowMethods []string,
|
||||||
|
allowHeaders []string,
|
||||||
|
allowCredentials bool,
|
||||||
|
exposeHeaders []string,
|
||||||
|
maxAge int,
|
||||||
) {
|
) {
|
||||||
server := echo.New()
|
server := echo.New()
|
||||||
|
|
||||||
server.Use(
|
server.Use(
|
||||||
middleware.CORSWithConfig(
|
middleware.CORSWithConfig(
|
||||||
middleware.CORSConfig{
|
middleware.CORSConfig{
|
||||||
AllowOrigins: []string{
|
AllowOrigins: allowOrigins,
|
||||||
"https://donatehelper.com",
|
AllowMethods: allowMethods,
|
||||||
"https://donatehelper.com",
|
AllowHeaders: allowHeaders,
|
||||||
"https://widget.donatehelper.com",
|
AllowCredentials: allowCredentials,
|
||||||
"http://widget.donatehelper.com",
|
ExposeHeaders: exposeHeaders,
|
||||||
"http://127.0.0.1:8002",
|
MaxAge: maxAge,
|
||||||
},
|
|
||||||
AllowHeaders: []string{"Content-Type", "Authorization", "X-Requested-With"},
|
|
||||||
AllowCredentials: true,
|
|
||||||
ExposeHeaders: []string{"Content-Length"},
|
|
||||||
MaxAge: 86400,
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -13,6 +13,7 @@ type Config struct {
|
|||||||
StreamerService StreamerService `yaml:"streamerService"`
|
StreamerService StreamerService `yaml:"streamerService"`
|
||||||
TtsHost string `yaml:"ttsHost"`
|
TtsHost string `yaml:"ttsHost"`
|
||||||
HOST string `yaml:"host"`
|
HOST string `yaml:"host"`
|
||||||
|
Cors CorsConfig `yaml:"cors"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Database struct {
|
type Database struct {
|
||||||
@ -46,6 +47,15 @@ type StreamerService struct {
|
|||||||
Port string `yaml:"port"`
|
Port string `yaml:"port"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CorsConfig struct {
|
||||||
|
AllowedOrigins []string `yaml:"allow_origins"`
|
||||||
|
AllowedMethods []string `yaml:"allow_methods"`
|
||||||
|
AllowedHeaders []string `yaml:"allow_headers"`
|
||||||
|
AllowCredentials bool `yaml:"allow_credentials"`
|
||||||
|
ExposeHeaders []string `yaml:"expose_headers"`
|
||||||
|
MaxAge int `yaml:"max_age"`
|
||||||
|
}
|
||||||
|
|
||||||
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 {
|
||||||
|
@ -1665,6 +1665,9 @@ const docTemplate = `{
|
|||||||
"donatUser": {
|
"donatUser": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"mediaUrl": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"targetID": {
|
"targetID": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
@ -1729,6 +1732,10 @@ const docTemplate = `{
|
|||||||
"template_id": {
|
"template_id": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"example": 1
|
"example": 1
|
||||||
|
},
|
||||||
|
"volume_percent": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 50
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2041,6 +2048,11 @@ const docTemplate = `{
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "date-time",
|
"format": "date-time",
|
||||||
"example": "2025-03-07T10:15:30Z"
|
"example": "2025-03-07T10:15:30Z"
|
||||||
|
},
|
||||||
|
"volume_percent": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"example": 50
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2257,6 +2269,10 @@ const docTemplate = `{
|
|||||||
"voice_speed": {
|
"voice_speed": {
|
||||||
"description": "Добавляем новые поля для настроек голоса",
|
"description": "Добавляем новые поля для настроек голоса",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"volume_percent": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 100
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2371,6 +2387,10 @@ const docTemplate = `{
|
|||||||
"name": {
|
"name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "Awesome Widget"
|
"example": "Awesome Widget"
|
||||||
|
},
|
||||||
|
"volume_percent": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 50
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1658,6 +1658,9 @@
|
|||||||
"donatUser": {
|
"donatUser": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"mediaUrl": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"targetID": {
|
"targetID": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
@ -1722,6 +1725,10 @@
|
|||||||
"template_id": {
|
"template_id": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"example": 1
|
"example": 1
|
||||||
|
},
|
||||||
|
"volume_percent": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 50
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2034,6 +2041,11 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "date-time",
|
"format": "date-time",
|
||||||
"example": "2025-03-07T10:15:30Z"
|
"example": "2025-03-07T10:15:30Z"
|
||||||
|
},
|
||||||
|
"volume_percent": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"example": 50
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2250,6 +2262,10 @@
|
|||||||
"voice_speed": {
|
"voice_speed": {
|
||||||
"description": "Добавляем новые поля для настроек голоса",
|
"description": "Добавляем новые поля для настроек голоса",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"volume_percent": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 100
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2364,6 +2380,10 @@
|
|||||||
"name": {
|
"name": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"example": "Awesome Widget"
|
"example": "Awesome Widget"
|
||||||
|
},
|
||||||
|
"volume_percent": {
|
||||||
|
"type": "integer",
|
||||||
|
"example": 50
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -15,6 +15,8 @@ definitions:
|
|||||||
type: integer
|
type: integer
|
||||||
donatUser:
|
donatUser:
|
||||||
type: string
|
type: string
|
||||||
|
mediaUrl:
|
||||||
|
type: string
|
||||||
targetID:
|
targetID:
|
||||||
type: integer
|
type: integer
|
||||||
text:
|
text:
|
||||||
@ -55,6 +57,9 @@ definitions:
|
|||||||
template_id:
|
template_id:
|
||||||
example: 1
|
example: 1
|
||||||
type: integer
|
type: integer
|
||||||
|
volume_percent:
|
||||||
|
example: 50
|
||||||
|
type: integer
|
||||||
required:
|
required:
|
||||||
- audio
|
- audio
|
||||||
- duration
|
- duration
|
||||||
@ -294,6 +299,10 @@ definitions:
|
|||||||
example: "2025-03-07T10:15:30Z"
|
example: "2025-03-07T10:15:30Z"
|
||||||
format: date-time
|
format: date-time
|
||||||
type: string
|
type: string
|
||||||
|
volume_percent:
|
||||||
|
example: 50
|
||||||
|
format: int64
|
||||||
|
type: integer
|
||||||
type: object
|
type: object
|
||||||
donat-widget_internal_model.InitNewStreamerBody:
|
donat-widget_internal_model.InitNewStreamerBody:
|
||||||
properties:
|
properties:
|
||||||
@ -440,6 +449,9 @@ definitions:
|
|||||||
voice_speed:
|
voice_speed:
|
||||||
description: Добавляем новые поля для настроек голоса
|
description: Добавляем новые поля для настроек голоса
|
||||||
type: string
|
type: string
|
||||||
|
volume_percent:
|
||||||
|
example: 100
|
||||||
|
type: integer
|
||||||
type: object
|
type: object
|
||||||
donat-widget_internal_model.TextAfterPaidDonat:
|
donat-widget_internal_model.TextAfterPaidDonat:
|
||||||
properties:
|
properties:
|
||||||
@ -517,6 +529,9 @@ definitions:
|
|||||||
name:
|
name:
|
||||||
example: Awesome Widget
|
example: Awesome Widget
|
||||||
type: string
|
type: string
|
||||||
|
volume_percent:
|
||||||
|
example: 50
|
||||||
|
type: integer
|
||||||
type: object
|
type: object
|
||||||
donat-widget_internal_model.VoiceSettingsResponse:
|
donat-widget_internal_model.VoiceSettingsResponse:
|
||||||
properties:
|
properties:
|
||||||
|
@ -26,6 +26,7 @@ type WidgetService interface {
|
|||||||
audio string,
|
audio string,
|
||||||
name string,
|
name string,
|
||||||
isActive bool,
|
isActive bool,
|
||||||
|
volumePercent int,
|
||||||
) (GetWidgetDb, error)
|
) (GetWidgetDb, error)
|
||||||
GetWidgetsByStreamer(ctx context.Context, streamerID int) (AllWidgets, error)
|
GetWidgetsByStreamer(ctx context.Context, streamerID int) (AllWidgets, error)
|
||||||
UpdateWidget(ctx context.Context, updateWidget UpdateWidget, widgetID, accountID int) error
|
UpdateWidget(ctx context.Context, updateWidget UpdateWidget, widgetID, accountID int) error
|
||||||
@ -45,6 +46,7 @@ type WidgetRepo interface {
|
|||||||
audio string,
|
audio string,
|
||||||
name string,
|
name string,
|
||||||
isActive bool,
|
isActive bool,
|
||||||
|
volumePercent int,
|
||||||
) (WidgetID, error)
|
) (WidgetID, error)
|
||||||
CheckWidgetName(ctx context.Context, streamerID int, name string) (bool, error)
|
CheckWidgetName(ctx context.Context, streamerID int, name string) (bool, error)
|
||||||
GetWidgetsByStreamerID(ctx context.Context, streamerID int) ([]*GetWidgetDb, error)
|
GetWidgetsByStreamerID(ctx context.Context, streamerID int) ([]*GetWidgetDb, error)
|
||||||
@ -58,6 +60,7 @@ type WidgetRepo interface {
|
|||||||
image *uuid.UUID,
|
image *uuid.UUID,
|
||||||
audio *uuid.UUID,
|
audio *uuid.UUID,
|
||||||
name *string,
|
name *string,
|
||||||
|
volumePercent *int,
|
||||||
) error
|
) error
|
||||||
CheckExistsWidget(ctx context.Context, widgetID, accountID int) error
|
CheckExistsWidget(ctx context.Context, widgetID, accountID int) error
|
||||||
GetWidgetByID(ctx context.Context, widgetID int) (GetWidgetDb, error)
|
GetWidgetByID(ctx context.Context, widgetID int) (GetWidgetDb, error)
|
||||||
@ -73,13 +76,21 @@ type DonatService interface {
|
|||||||
|
|
||||||
CheckToken(request echo.Context) (api.CheckTokenResponse, error)
|
CheckToken(request echo.Context) (api.CheckTokenResponse, error)
|
||||||
|
|
||||||
CreateDonat(ctx context.Context, streamerLogin, text, donatUser string, targetID *int, amount int) (CreateDonatResponse, error)
|
CreateDonat(
|
||||||
|
ctx context.Context,
|
||||||
|
streamerLogin, text,
|
||||||
|
donatUser string,
|
||||||
|
targetID *int,
|
||||||
|
amount int,
|
||||||
|
mediaUrl *string,
|
||||||
|
) (CreateDonatResponse, error)
|
||||||
CreateTestDonat(
|
CreateTestDonat(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
streamerID int,
|
streamerID int,
|
||||||
text string,
|
text string,
|
||||||
donatUser string,
|
donatUser string,
|
||||||
targetID *int,
|
targetID *int,
|
||||||
|
mediaURL *string,
|
||||||
amount int,
|
amount int,
|
||||||
) error
|
) error
|
||||||
|
|
||||||
@ -139,6 +150,7 @@ type DonatRepo interface {
|
|||||||
amount int,
|
amount int,
|
||||||
text string,
|
text string,
|
||||||
donatUser string,
|
donatUser string,
|
||||||
|
mediaUrl *string,
|
||||||
status string,
|
status string,
|
||||||
) error
|
) error
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ type GetWidgetDb struct {
|
|||||||
Duration int `db:"duration" json:"duration" example:"30" format:"int64" description:"Duration of the widget"`
|
Duration int `db:"duration" json:"duration" example:"30" format:"int64" description:"Duration of the widget"`
|
||||||
MinAmount int `db:"min_amount" json:"min_amount" example:"100" format:"int64" description:"Minimum donation amount"`
|
MinAmount int `db:"min_amount" json:"min_amount" example:"100" format:"int64" description:"Minimum donation amount"`
|
||||||
MaxAmount int `db:"max_amount" json:"max_amount" example:"1000" format:"int64" description:"Maximum donation amount"`
|
MaxAmount int `db:"max_amount" json:"max_amount" example:"1000" format:"int64" description:"Maximum donation amount"`
|
||||||
|
VolumePercent int `db:"volume_percent" json:"volume_percent" example:"50" format:"int64" description:"Volume percentage of the widget"`
|
||||||
CreatedAt time.Time `db:"created_at" json:"created_at" format:"date-time" example:"2025-03-06T13:37:36Z" description:"Timestamp when the widget was created"`
|
CreatedAt time.Time `db:"created_at" json:"created_at" format:"date-time" example:"2025-03-06T13:37:36Z" description:"Timestamp when the widget was created"`
|
||||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at" format:"date-time" example:"2025-03-07T10:15:30Z" description:"Timestamp when the widget was last updated"`
|
UpdatedAt time.Time `db:"updated_at" json:"updated_at" format:"date-time" example:"2025-03-07T10:15:30Z" description:"Timestamp when the widget was last updated"`
|
||||||
GroupID int `db:"group_id" json:"group_id" example:"2" format:"int64" description:"Group ID associated with the widget"`
|
GroupID int `db:"group_id" json:"group_id" example:"2" format:"int64" description:"Group ID associated with the widget"`
|
||||||
@ -58,6 +59,7 @@ type UpdateWidget struct {
|
|||||||
Name *string `json:"name" example:"Awesome Widget" description:"Name of the widget"`
|
Name *string `json:"name" example:"Awesome Widget" description:"Name of the widget"`
|
||||||
Image *uuid.UUID `json:"image" example:"d2c2f03f-3fe5-4bfc-b963-5478a057149e" description:"UUID of the widget image"`
|
Image *uuid.UUID `json:"image" example:"d2c2f03f-3fe5-4bfc-b963-5478a057149e" description:"UUID of the widget image"`
|
||||||
Audio *uuid.UUID `json:"audio" example:"a0f9e244-f61f-4bfe-a7a0-3b5e91fe7364" description:"UUID of the widget audio file"`
|
Audio *uuid.UUID `json:"audio" example:"a0f9e244-f61f-4bfe-a7a0-3b5e91fe7364" description:"UUID of the widget audio file"`
|
||||||
|
VolumePercent *int `json:"volume_percent" example:"50" description:"Volume percentage of the widget"`
|
||||||
Duration *int `json:"duration" example:"120" description:"Duration of the widget in seconds"`
|
Duration *int `json:"duration" example:"120" description:"Duration of the widget in seconds"`
|
||||||
MinAmount *int `json:"min_amount" example:"10" description:"Minimum amount for the widget"`
|
MinAmount *int `json:"min_amount" example:"10" description:"Minimum amount for the widget"`
|
||||||
MaxAmount *int `json:"max_amount" example:"100" description:"Maximum amount for the widget"`
|
MaxAmount *int `json:"max_amount" example:"100" description:"Maximum amount for the widget"`
|
||||||
@ -255,6 +257,7 @@ type CreateWidgetBody struct {
|
|||||||
Audio string `json:"audio" validate:"required" format:"uuid" example:"550e8400-e29b-41d4-a716-446655440001"`
|
Audio string `json:"audio" validate:"required" format:"uuid" example:"550e8400-e29b-41d4-a716-446655440001"`
|
||||||
MinAmount int `json:"min_amount" validate:"required" example:"10"`
|
MinAmount int `json:"min_amount" validate:"required" example:"10"`
|
||||||
MaxAmount int `json:"max_amount" validate:"required" example:"100"`
|
MaxAmount int `json:"max_amount" validate:"required" example:"100"`
|
||||||
|
VolumePercent int `json:"volume_percent" example:"50" description:"Volume percentage of the widget"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DonatAndWidget struct {
|
type DonatAndWidget struct {
|
||||||
@ -267,6 +270,7 @@ type CreateDonatBody struct {
|
|||||||
Amount int `json:"amount"`
|
Amount int `json:"amount"`
|
||||||
Text string `json:"text"`
|
Text string `json:"text"`
|
||||||
DonatUser string `json:"donatUser"`
|
DonatUser string `json:"donatUser"`
|
||||||
|
MediaUrl *string `json:"mediaUrl"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DonationStat struct {
|
type DonationStat struct {
|
||||||
@ -328,6 +332,7 @@ type MarkDonatViewed struct {
|
|||||||
|
|
||||||
type PlayingDonat struct {
|
type PlayingDonat struct {
|
||||||
Duration int `json:"duration" example:"30"`
|
Duration int `json:"duration" example:"30"`
|
||||||
|
VolumePercent int `json:"volume_percent" example:"100"`
|
||||||
Image *string `json:"image_link"`
|
Image *string `json:"image_link"`
|
||||||
Audio *string `json:"audio_link"`
|
Audio *string `json:"audio_link"`
|
||||||
Text string `json:"text"`
|
Text string `json:"text"`
|
||||||
@ -341,6 +346,7 @@ type PlayingDonat struct {
|
|||||||
|
|
||||||
type PlayingDonatResponse struct {
|
type PlayingDonatResponse struct {
|
||||||
Duration int `json:"duration" example:"30"`
|
Duration int `json:"duration" example:"30"`
|
||||||
|
VolumePercent int `json:"volume_percent" example:"100"`
|
||||||
Image *string `json:"image_link"`
|
Image *string `json:"image_link"`
|
||||||
Audio *string `json:"audio_link"`
|
Audio *string `json:"audio_link"`
|
||||||
Text string `json:"text"`
|
Text string `json:"text"`
|
||||||
|
@ -15,7 +15,7 @@ CREATE TABLE IF NOT EXISTS widgets (
|
|||||||
duration INTEGER NOT NULL,
|
duration INTEGER NOT NULL,
|
||||||
min_amount INTEGER NOT NUll,
|
min_amount INTEGER NOT NUll,
|
||||||
max_amount INTEGER NOT NULL,
|
max_amount INTEGER NOT NULL,
|
||||||
|
volume_percent INTEGER NOT NULL DEFAULT 75,
|
||||||
created_at TIMESTAMP NOT NULL DEFAULT now(),
|
created_at TIMESTAMP NOT NULL DEFAULT now(),
|
||||||
updated_at TIMESTAMP NOT NULL DEFAULT now()
|
updated_at TIMESTAMP NOT NULL DEFAULT now()
|
||||||
);
|
);
|
||||||
@ -39,6 +39,7 @@ CREATE TABLE IF NOT EXISTS donats (
|
|||||||
target_id INTEGER,
|
target_id INTEGER,
|
||||||
paid_time TIMESTAMP,
|
paid_time TIMESTAMP,
|
||||||
is_test BOOLEAN DEFAULT 'false',
|
is_test BOOLEAN DEFAULT 'false',
|
||||||
|
media_url VARCHAR(255),
|
||||||
|
|
||||||
status VARCHAR(50) NOT NULL DEFAULT 'pending',
|
status VARCHAR(50) NOT NULL DEFAULT 'pending',
|
||||||
|
|
||||||
|
@ -6,8 +6,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var CreateWidget = `
|
var CreateWidget = `
|
||||||
INSERT INTO widgets (streamer_id, template_id, image, audio, duration, min_amount, max_amount, name)
|
INSERT INTO widgets (streamer_id, template_id, image, audio, duration, min_amount, max_amount, name, volume_percent)
|
||||||
VALUES (@streamer_id, @template_id, @image, @audio, @duration, @min_amount, @max_amount, @name)
|
VALUES (@streamer_id, @template_id, @image, @audio, @duration, @min_amount, @max_amount, @name, @volume_percent)
|
||||||
RETURNING id;
|
RETURNING id;
|
||||||
`
|
`
|
||||||
|
|
||||||
@ -40,8 +40,8 @@ func GetMediaUrl(mediaType model.MediaType) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var CreateDonat = `
|
var CreateDonat = `
|
||||||
INSERT INTO donats (streamer_id, widget_id, text, amount, donat_user, order_id, target_id, status, paid_time, is_test)
|
INSERT INTO donats (streamer_id, widget_id, text, amount, donat_user, order_id, target_id, status, paid_time, is_test, media_url)
|
||||||
VALUES (@streamer_id, @widget_id, @text, @amount, @donat_user, @order_id, @target_id, @status, @paid_time, @is_test)
|
VALUES (@streamer_id, @widget_id, @text, @amount, @donat_user, @order_id, @target_id, @status, @paid_time, @is_test, @media_url)
|
||||||
RETURNING id;
|
RETURNING id;
|
||||||
`
|
`
|
||||||
var MarkDonatView = `
|
var MarkDonatView = `
|
||||||
@ -199,6 +199,7 @@ SELECT
|
|||||||
w.group_id,
|
w.group_id,
|
||||||
w.name,
|
w.name,
|
||||||
w.is_active,
|
w.is_active,
|
||||||
|
w.volume_percent,
|
||||||
img.id AS image_id,
|
img.id AS image_id,
|
||||||
img.file_name AS image_file_name,
|
img.file_name AS image_file_name,
|
||||||
img.file_type AS image_type,
|
img.file_type AS image_type,
|
||||||
@ -280,7 +281,8 @@ SET
|
|||||||
is_active = COALESCE(@is_active, is_active),
|
is_active = COALESCE(@is_active, is_active),
|
||||||
image = COALESCE(@image, image),
|
image = COALESCE(@image, image),
|
||||||
audio = COALESCE(@audio, audio),
|
audio = COALESCE(@audio, audio),
|
||||||
name = COALESCE(@name, name)
|
name = COALESCE(@name, name),
|
||||||
|
volume_percent = COALESCE(@volume_percent, volume_percent)
|
||||||
WHERE id = @id;
|
WHERE id = @id;
|
||||||
`
|
`
|
||||||
|
|
||||||
@ -402,7 +404,7 @@ WHERE
|
|||||||
streamer_id = @streamer_id
|
streamer_id = @streamer_id
|
||||||
AND created_at >= NOW() - INTERVAL '24 hours'
|
AND created_at >= NOW() - INTERVAL '24 hours'
|
||||||
AND paid_time IS NOT NULL
|
AND paid_time IS NOT NULL
|
||||||
AND is_test IS 'false'
|
AND is_test IS false
|
||||||
|
|
||||||
GROUP BY
|
GROUP BY
|
||||||
DATE_TRUNC('hour', created_at) -- Группировка по часам
|
DATE_TRUNC('hour', created_at) -- Группировка по часам
|
||||||
@ -421,7 +423,7 @@ WHERE
|
|||||||
streamer_id = @streamer_id
|
streamer_id = @streamer_id
|
||||||
AND created_at >= NOW() - INTERVAL '7 days'
|
AND created_at >= NOW() - INTERVAL '7 days'
|
||||||
AND paid_time IS NOT NULL
|
AND paid_time IS NOT NULL
|
||||||
AND is_test IS 'false'
|
AND is_test IS false
|
||||||
|
|
||||||
|
|
||||||
GROUP BY
|
GROUP BY
|
||||||
@ -441,7 +443,7 @@ WHERE
|
|||||||
streamer_id = @streamer_id
|
streamer_id = @streamer_id
|
||||||
AND created_at >= NOW() - INTERVAL '1 month'
|
AND created_at >= NOW() - INTERVAL '1 month'
|
||||||
AND paid_time IS NOT NULL
|
AND paid_time IS NOT NULL
|
||||||
AND is_test IS 'false'
|
AND is_test IS false
|
||||||
|
|
||||||
GROUP BY
|
GROUP BY
|
||||||
DATE(created_at) -- Группировка по дням
|
DATE(created_at) -- Группировка по дням
|
||||||
@ -460,7 +462,7 @@ WHERE
|
|||||||
streamer_id = @streamer_id
|
streamer_id = @streamer_id
|
||||||
AND created_at >= NOW() - INTERVAL '1 year'
|
AND created_at >= NOW() - INTERVAL '1 year'
|
||||||
AND paid_time IS NOT NULL
|
AND paid_time IS NOT NULL
|
||||||
AND is_test IS 'false'
|
AND is_test IS false
|
||||||
|
|
||||||
GROUP BY
|
GROUP BY
|
||||||
DATE_TRUNC('month', created_at) -- Группировка по месяцам
|
DATE_TRUNC('month', created_at) -- Группировка по месяцам
|
||||||
@ -478,7 +480,7 @@ WHERE
|
|||||||
streamer_id = @streamer_id
|
streamer_id = @streamer_id
|
||||||
AND created_at >= NOW() - INTERVAL '24 hours'
|
AND created_at >= NOW() - INTERVAL '24 hours'
|
||||||
AND paid_time IS NOT NULL
|
AND paid_time IS NOT NULL
|
||||||
AND is_test IS 'false';
|
AND is_test IS false;
|
||||||
`
|
`
|
||||||
|
|
||||||
const GetDonationsSummaryLast7Days = `
|
const GetDonationsSummaryLast7Days = `
|
||||||
@ -491,7 +493,7 @@ WHERE
|
|||||||
streamer_id = @streamer_id
|
streamer_id = @streamer_id
|
||||||
AND created_at >= NOW() - INTERVAL '7 days'
|
AND created_at >= NOW() - INTERVAL '7 days'
|
||||||
AND paid_time IS NOT NULL
|
AND paid_time IS NOT NULL
|
||||||
AND is_test IS 'false';
|
AND is_test IS false;
|
||||||
`
|
`
|
||||||
|
|
||||||
const GetDonationsSummaryLastMonth = `
|
const GetDonationsSummaryLastMonth = `
|
||||||
@ -504,7 +506,7 @@ WHERE
|
|||||||
streamer_id = @streamer_id
|
streamer_id = @streamer_id
|
||||||
AND created_at >= NOW() - INTERVAL '1 month'
|
AND created_at >= NOW() - INTERVAL '1 month'
|
||||||
AND paid_time IS NOT NULL
|
AND paid_time IS NOT NULL
|
||||||
AND is_test IS 'false';
|
AND is_test IS false;
|
||||||
`
|
`
|
||||||
|
|
||||||
const GetDonationsSummaryLastYear = `
|
const GetDonationsSummaryLastYear = `
|
||||||
@ -517,7 +519,7 @@ WHERE
|
|||||||
streamer_id = @streamer_id
|
streamer_id = @streamer_id
|
||||||
AND created_at >= NOW() - INTERVAL '1 year'
|
AND created_at >= NOW() - INTERVAL '1 year'
|
||||||
AND paid_time IS NOT NULL
|
AND paid_time IS NOT NULL
|
||||||
AND is_test IS 'false';
|
AND is_test IS false;
|
||||||
`
|
`
|
||||||
|
|
||||||
const GetLastModeration = `
|
const GetLastModeration = `
|
||||||
@ -588,7 +590,8 @@ SELECT
|
|||||||
d.donat_user,
|
d.donat_user,
|
||||||
d.play_content,
|
d.play_content,
|
||||||
d.show_name,
|
d.show_name,
|
||||||
d.show_text
|
d.show_text,
|
||||||
|
w.volume_percent
|
||||||
FROM widgets AS w
|
FROM widgets AS w
|
||||||
INNER JOIN
|
INNER JOIN
|
||||||
donats AS d ON d.widget_id = w.id
|
donats AS d ON d.widget_id = w.id
|
||||||
|
@ -166,8 +166,10 @@ async function playMedia(donat, voiceSettings) {
|
|||||||
const mediaPromise = (async () => {
|
const mediaPromise = (async () => {
|
||||||
if (donat.play_content && donat.audio_link) {
|
if (donat.play_content && donat.audio_link) {
|
||||||
await playAudio(donat.audio_link, (voiceSettings.voice_sound_percent || 100) / 100, controller.signal);
|
await playAudio(donat.audio_link, (voiceSettings.voice_sound_percent || 100) / 100, controller.signal);
|
||||||
if (donat.text) await playSpeech(donat.text, voiceSettings, controller.signal);
|
if (donat.text && donat.show_text) {
|
||||||
} else if (donat.text && donat.voice_enabled) {
|
await playSpeech(donat.text, voiceSettings, controller.signal);
|
||||||
|
}
|
||||||
|
} else if (donat.text && donat.voice_enabled && donat.show_text) {
|
||||||
await playSpeech(donat.text, voiceSettings, controller.signal);
|
await playSpeech(donat.text, voiceSettings, controller.signal);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
@ -179,8 +181,6 @@ async function playMedia(donat, voiceSettings) {
|
|||||||
}, timeoutDuration);
|
}, timeoutDuration);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Promise.race будет ждать либо завершения медиа, либо таймаута
|
|
||||||
// Если медиа завершится с ошибкой (например, автоплей заблокирован), она пробросится дальше.
|
|
||||||
return Promise.race([mediaPromise, timeoutPromise]);
|
return Promise.race([mediaPromise, timeoutPromise]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,16 +226,12 @@ async function widgetView() {
|
|||||||
contentDiv.appendChild(textElem);
|
contentDiv.appendChild(textElem);
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Воспроизведение ---
|
|
||||||
const voiceSettings = { voice_speed: currentDonat.voice_speed, scenery: currentDonat.scenery, voice_sound_percent: currentDonat.voice_sound_percent, min_price: currentDonat.min_price, languages: currentDonat.languages, voice_enabled: currentDonat.voice_enabled };
|
const voiceSettings = { voice_speed: currentDonat.voice_speed, scenery: currentDonat.scenery, voice_sound_percent: currentDonat.voice_sound_percent, min_price: currentDonat.min_price, languages: currentDonat.languages, voice_enabled: currentDonat.voice_enabled };
|
||||||
await playMedia(currentDonat, voiceSettings);
|
await playMedia(currentDonat, voiceSettings);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Логируем ошибку, но не прерываем логику ожидания и очистки
|
|
||||||
console.error('Ошибка обработки доната:', error.name === 'AbortError' ? 'Таймаут или блокировка звука' : error);
|
console.error('Ошибка обработки доната:', error.name === 'AbortError' ? 'Таймаут или блокировка звука' : error);
|
||||||
} finally {
|
} finally {
|
||||||
// --- Очистка и завершение ---
|
|
||||||
// Этот блок выполнится ВСЕГДА: и после успеха, и после ошибки.
|
|
||||||
if (currentDonat) {
|
if (currentDonat) {
|
||||||
// Отмечаем донат как просмотренный
|
// Отмечаем донат как просмотренный
|
||||||
if (currentDonat.order_id) {
|
if (currentDonat.order_id) {
|
||||||
@ -250,7 +246,6 @@ async function widgetView() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ждем оставшееся время до конца показа доната
|
|
||||||
const elapsedMs = Date.now() - iterationStart;
|
const elapsedMs = Date.now() - iterationStart;
|
||||||
const durationMs = (currentDonat.duration || 5) * 1000;
|
const durationMs = (currentDonat.duration || 5) * 1000;
|
||||||
const remainingTimeMs = Math.max(0, durationMs - elapsedMs);
|
const remainingTimeMs = Math.max(0, durationMs - elapsedMs);
|
||||||
@ -259,7 +254,6 @@ async function widgetView() {
|
|||||||
await new Promise(r => setTimeout(r, remainingTimeMs));
|
await new Promise(r => setTimeout(r, remainingTimeMs));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Гарантированно очищаем контейнер
|
|
||||||
clearContainer(contentDiv);
|
clearContainer(contentDiv);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ func (repoDonat *RepoDonat) CreateDonat(
|
|||||||
amount int,
|
amount int,
|
||||||
text string,
|
text string,
|
||||||
donatUser string,
|
donatUser string,
|
||||||
|
mediaUrl *string,
|
||||||
status string,
|
status string,
|
||||||
) error {
|
) error {
|
||||||
args := pgx.NamedArgs{
|
args := pgx.NamedArgs{
|
||||||
@ -44,6 +45,7 @@ func (repoDonat *RepoDonat) CreateDonat(
|
|||||||
"amount": amount,
|
"amount": amount,
|
||||||
"status": status,
|
"status": status,
|
||||||
"donat_user": donatUser,
|
"donat_user": donatUser,
|
||||||
|
"media_url": mediaUrl,
|
||||||
"is_test": false,
|
"is_test": false,
|
||||||
"paid_time": nil,
|
"paid_time": nil,
|
||||||
}
|
}
|
||||||
@ -858,6 +860,7 @@ func (repoDonat *RepoDonat) GetPlayingDonat(
|
|||||||
&donatForPlaying.PlayContent,
|
&donatForPlaying.PlayContent,
|
||||||
&donatForPlaying.ShowName,
|
&donatForPlaying.ShowName,
|
||||||
&donatForPlaying.ShowText,
|
&donatForPlaying.ShowText,
|
||||||
|
&donatForPlaying.VolumePercent,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -37,6 +37,7 @@ func (widgetRepo *RepoWidget) CreateWidget(
|
|||||||
audio string,
|
audio string,
|
||||||
name string,
|
name string,
|
||||||
isActive bool,
|
isActive bool,
|
||||||
|
volumePercent int,
|
||||||
) (model.WidgetID, error) {
|
) (model.WidgetID, error) {
|
||||||
args := pgx.NamedArgs{
|
args := pgx.NamedArgs{
|
||||||
"streamer_id": streamerID,
|
"streamer_id": streamerID,
|
||||||
@ -48,6 +49,7 @@ func (widgetRepo *RepoWidget) CreateWidget(
|
|||||||
"audio": audio,
|
"audio": audio,
|
||||||
"name": name,
|
"name": name,
|
||||||
"is_active": isActive,
|
"is_active": isActive,
|
||||||
|
"volume_percent": volumePercent,
|
||||||
}
|
}
|
||||||
widgetID, err := widgetRepo.db.Insert(ctx, sql.CreateWidget, args)
|
widgetID, err := widgetRepo.db.Insert(ctx, sql.CreateWidget, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -141,6 +143,7 @@ func (widgetRepo *RepoWidget) UpdateWidget(
|
|||||||
image *uuid.UUID,
|
image *uuid.UUID,
|
||||||
audio *uuid.UUID,
|
audio *uuid.UUID,
|
||||||
name *string,
|
name *string,
|
||||||
|
volumePercent *int,
|
||||||
) error {
|
) error {
|
||||||
args := pgx.NamedArgs{
|
args := pgx.NamedArgs{
|
||||||
"id": widgetID,
|
"id": widgetID,
|
||||||
@ -167,6 +170,9 @@ func (widgetRepo *RepoWidget) UpdateWidget(
|
|||||||
if isActive != nil {
|
if isActive != nil {
|
||||||
args["is_active"] = *isActive
|
args["is_active"] = *isActive
|
||||||
}
|
}
|
||||||
|
if volumePercent != nil {
|
||||||
|
args["volume_percent"] = *volumePercent
|
||||||
|
}
|
||||||
|
|
||||||
err := widgetRepo.db.Update(ctx, sql.UpdateWidget, args)
|
err := widgetRepo.db.Update(ctx, sql.UpdateWidget, args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -95,6 +95,7 @@ func (donatService *ServiceDonat) CreateDonat(
|
|||||||
donatUser string,
|
donatUser string,
|
||||||
targetID *int,
|
targetID *int,
|
||||||
amount int,
|
amount int,
|
||||||
|
mediaUrl *string,
|
||||||
) (model.CreateDonatResponse, error) {
|
) (model.CreateDonatResponse, error) {
|
||||||
|
|
||||||
donatePage, err := donatService.donatRepo.GetDonatPageByLogin(ctx, streamerLogin)
|
donatePage, err := donatService.donatRepo.GetDonatPageByLogin(ctx, streamerLogin)
|
||||||
@ -128,6 +129,7 @@ func (donatService *ServiceDonat) CreateDonat(
|
|||||||
amount,
|
amount,
|
||||||
text,
|
text,
|
||||||
donatUser,
|
donatUser,
|
||||||
|
mediaUrl,
|
||||||
"pending",
|
"pending",
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -153,13 +155,26 @@ func (donatService *ServiceDonat) CreateTestDonat(
|
|||||||
text string,
|
text string,
|
||||||
donatUser string,
|
donatUser string,
|
||||||
targetID *int,
|
targetID *int,
|
||||||
|
mediaUrl *string,
|
||||||
amount int,
|
amount int,
|
||||||
) error {
|
) error {
|
||||||
orderID := uuid.New()
|
orderID := uuid.New()
|
||||||
|
|
||||||
widgetID := donatService.defaultWidgetID
|
widgets, err := donatService.widgetRepo.GetWidgetsByStreamerID(ctx, streamerID)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error(err.Error())
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
err := donatService.donatRepo.CreateDonat(
|
widgetID := donatService.defaultWidgetID
|
||||||
|
for _, widget := range widgets {
|
||||||
|
if amount >= widget.MinAmount && amount <= widget.MaxAmount && widget.IsActive == true {
|
||||||
|
widgetID = widget.ID
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = donatService.donatRepo.CreateDonat(
|
||||||
ctx,
|
ctx,
|
||||||
streamerID,
|
streamerID,
|
||||||
targetID,
|
targetID,
|
||||||
@ -168,6 +183,7 @@ func (donatService *ServiceDonat) CreateTestDonat(
|
|||||||
amount,
|
amount,
|
||||||
text,
|
text,
|
||||||
donatUser,
|
donatUser,
|
||||||
|
mediaUrl,
|
||||||
"test_donat",
|
"test_donat",
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -818,6 +834,7 @@ func (donatService *ServiceDonat) GetPlayingDonat(
|
|||||||
PlayContent: playingDonat.PlayContent,
|
PlayContent: playingDonat.PlayContent,
|
||||||
ShowName: playingDonat.ShowName,
|
ShowName: playingDonat.ShowName,
|
||||||
ShowText: playingDonat.ShowText,
|
ShowText: playingDonat.ShowText,
|
||||||
|
VolumePercent: playingDonat.VolumePercent,
|
||||||
}
|
}
|
||||||
|
|
||||||
filteredSettings, err := donatService.GetFiltersSettings(ctx, streamerID)
|
filteredSettings, err := donatService.GetFiltersSettings(ctx, streamerID)
|
||||||
@ -839,7 +856,7 @@ func (donatService *ServiceDonat) GetPlayingDonat(
|
|||||||
if !filteredSettings.EnableLinks {
|
if !filteredSettings.EnableLinks {
|
||||||
response.Text = donatService.replaceLinks(response.Text)
|
response.Text = donatService.replaceLinks(response.Text)
|
||||||
}
|
}
|
||||||
fmt.Println(response.Text)
|
|
||||||
response.VoiceSpeed = voiceSettings.VoiceSpeed
|
response.VoiceSpeed = voiceSettings.VoiceSpeed
|
||||||
response.Scenery = voiceSettings.Scenery
|
response.Scenery = voiceSettings.Scenery
|
||||||
response.VoiceSoundPercent = voiceSettings.VoiceSoundPercent
|
response.VoiceSoundPercent = voiceSettings.VoiceSoundPercent
|
||||||
|
@ -49,12 +49,12 @@ func (widgetService *ServiceWidget) CreateWidget(
|
|||||||
audio string,
|
audio string,
|
||||||
name string,
|
name string,
|
||||||
isActive bool,
|
isActive bool,
|
||||||
|
volumePercent int,
|
||||||
) (model.GetWidgetDb, error) {
|
) (model.GetWidgetDb, error) {
|
||||||
exists, err := widgetService.widgetRepo.CheckWidgetName(ctx, streamerID, name)
|
exists, err := widgetService.widgetRepo.CheckWidgetName(ctx, streamerID, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return model.GetWidgetDb{}, err
|
return model.GetWidgetDb{}, err
|
||||||
}
|
}
|
||||||
fmt.Println(exists)
|
|
||||||
if exists == true {
|
if exists == true {
|
||||||
slog.Error("GetWidgetDb with name %s already exists", name)
|
slog.Error("GetWidgetDb with name %s already exists", name)
|
||||||
return model.GetWidgetDb{}, fmt.Errorf("widget with name %s already exists", name)
|
return model.GetWidgetDb{}, fmt.Errorf("widget with name %s already exists", name)
|
||||||
@ -71,6 +71,7 @@ func (widgetService *ServiceWidget) CreateWidget(
|
|||||||
audio,
|
audio,
|
||||||
name,
|
name,
|
||||||
isActive,
|
isActive,
|
||||||
|
volumePercent,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error(err.Error())
|
slog.Error(err.Error())
|
||||||
@ -149,6 +150,7 @@ func (widgetService *ServiceWidget) UpdateWidget(
|
|||||||
updateWidget.Image,
|
updateWidget.Image,
|
||||||
updateWidget.Audio,
|
updateWidget.Audio,
|
||||||
updateWidget.Name,
|
updateWidget.Name,
|
||||||
|
updateWidget.VolumePercent,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Error(err.Error())
|
slog.Error(err.Error())
|
||||||
|
@ -20,6 +20,7 @@ CREATE TABLE IF NOT EXISTS widgets (
|
|||||||
name VARCHAR(50) NOT NULL,
|
name VARCHAR(50) NOT NULL,
|
||||||
image UUID REFERENCES files(id) ON DELETE CASCADE,
|
image UUID REFERENCES files(id) ON DELETE CASCADE,
|
||||||
audio UUID REFERENCES files(id) ON DELETE CASCADE,
|
audio UUID REFERENCES files(id) ON DELETE CASCADE,
|
||||||
|
volume_percent INTEGER NOT NULL DEFAULT 70,
|
||||||
duration INTEGER NOT NULL,
|
duration INTEGER NOT NULL,
|
||||||
min_amount INTEGER NOT NULL,
|
min_amount INTEGER NOT NULL,
|
||||||
max_amount INTEGER NOT NULL,
|
max_amount INTEGER NOT NULL,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user