Compare commits

..

10 Commits

Author SHA1 Message Date
875b8e21da modify swagger docs 2025-08-26 10:32:11 +05:00
9e4553c6f3 add media url for create donate case 2025-08-26 10:31:32 +05:00
238421c69f add config cors for yaml file 2025-08-20 11:20:29 +05:00
c33fa8a4f4 add volume percent for create new widget 2025-08-17 21:45:58 +05:00
db55335add Merge branch 'main' into dev 2025-08-17 21:39:18 +05:00
0dfeb43124 add fix for voice text moderation
Some checks failed
CI/CD / Build (push) Has been cancelled
2025-08-14 10:11:34 +05:00
d302522256 add fix for donate is_active condition during create donat 2025-08-13 23:55:54 +05:00
bd86426553 add volume percent for every widget(patch, get, post) 2025-08-06 10:03:51 +05:00
2f01c1e500 add fix for test donat 2025-07-26 00:27:48 +05:00
81fcc84741 add fix for false from 'false' 2025-07-11 15:50:48 +05:00
19 changed files with 238 additions and 117 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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