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,
fileService,
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:
- GO_ENV=production
restart: always
networks:
- donat-network
# networks:
# - donat-network
volumes:
- ./storage:/storage
@ -20,12 +20,13 @@ services:
image: postgres:16.3-alpine3.20
env_file:
- .env
networks:
- donat-network
# networks:
# - donat-network
ports:
- "31002:5432"
volumes:
- ./docker/postgres/data:/var/lib/postgresql/data
networks:
donat-network:
external: true
- ./sql:/docker-entrypoint-initdb.d
#networks:
# donat-network:
# external: true

View File

@ -46,6 +46,7 @@ func CreateDonat(donatService model.DonatService) echo.HandlerFunc {
body.DonatUser,
body.TargetID,
body.Amount,
body.MediaUrl,
)
if err != nil {
return request.JSON(http.StatusInternalServerError, err.Error())
@ -91,6 +92,7 @@ func CreateTestDonat(donatService model.DonatService) echo.HandlerFunc {
body.Text,
body.DonatUser,
body.TargetID,
body.MediaUrl,
body.Amount,
)

View File

@ -48,6 +48,7 @@ func CreateWidget(widgetService model.WidgetService) echo.HandlerFunc {
body.Audio,
body.Name,
body.IsActive,
body.VolumePercent,
)
if err != nil {
slog.Error(err.Error())

View File

@ -28,23 +28,24 @@ func NewApp(
targetService model.TargetService,
fileService model.FileService,
authClient model.AuthClient,
allowOrigins []string,
allowMethods []string,
allowHeaders []string,
allowCredentials bool,
exposeHeaders []string,
maxAge int,
) {
server := echo.New()
server.Use(
middleware.CORSWithConfig(
middleware.CORSConfig{
AllowOrigins: []string{
"https://donatehelper.com",
"https://donatehelper.com",
"https://widget.donatehelper.com",
"http://widget.donatehelper.com",
"http://127.0.0.1:8002",
},
AllowHeaders: []string{"Content-Type", "Authorization", "X-Requested-With"},
AllowCredentials: true,
ExposeHeaders: []string{"Content-Length"},
MaxAge: 86400,
AllowOrigins: allowOrigins,
AllowMethods: allowMethods,
AllowHeaders: allowHeaders,
AllowCredentials: allowCredentials,
ExposeHeaders: exposeHeaders,
MaxAge: maxAge,
},
),
)

View File

@ -13,6 +13,7 @@ type Config struct {
StreamerService StreamerService `yaml:"streamerService"`
TtsHost string `yaml:"ttsHost"`
HOST string `yaml:"host"`
Cors CorsConfig `yaml:"cors"`
}
type Database struct {
@ -46,6 +47,15 @@ type StreamerService struct {
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 {
data, err := os.ReadFile("internal/config/config.yaml")
if err != nil {

View File

@ -1665,6 +1665,9 @@ const docTemplate = `{
"donatUser": {
"type": "string"
},
"mediaUrl": {
"type": "string"
},
"targetID": {
"type": "integer"
},
@ -1729,6 +1732,10 @@ const docTemplate = `{
"template_id": {
"type": "integer",
"example": 1
},
"volume_percent": {
"type": "integer",
"example": 50
}
}
},
@ -2041,6 +2048,11 @@ const docTemplate = `{
"type": "string",
"format": "date-time",
"example": "2025-03-07T10:15:30Z"
},
"volume_percent": {
"type": "integer",
"format": "int64",
"example": 50
}
}
},
@ -2257,6 +2269,10 @@ const docTemplate = `{
"voice_speed": {
"description": "Добавляем новые поля для настроек голоса",
"type": "string"
},
"volume_percent": {
"type": "integer",
"example": 100
}
}
},
@ -2371,6 +2387,10 @@ const docTemplate = `{
"name": {
"type": "string",
"example": "Awesome Widget"
},
"volume_percent": {
"type": "integer",
"example": 50
}
}
},

View File

@ -1658,6 +1658,9 @@
"donatUser": {
"type": "string"
},
"mediaUrl": {
"type": "string"
},
"targetID": {
"type": "integer"
},
@ -1722,6 +1725,10 @@
"template_id": {
"type": "integer",
"example": 1
},
"volume_percent": {
"type": "integer",
"example": 50
}
}
},
@ -2034,6 +2041,11 @@
"type": "string",
"format": "date-time",
"example": "2025-03-07T10:15:30Z"
},
"volume_percent": {
"type": "integer",
"format": "int64",
"example": 50
}
}
},
@ -2250,6 +2262,10 @@
"voice_speed": {
"description": "Добавляем новые поля для настроек голоса",
"type": "string"
},
"volume_percent": {
"type": "integer",
"example": 100
}
}
},
@ -2364,6 +2380,10 @@
"name": {
"type": "string",
"example": "Awesome Widget"
},
"volume_percent": {
"type": "integer",
"example": 50
}
}
},

View File

@ -15,6 +15,8 @@ definitions:
type: integer
donatUser:
type: string
mediaUrl:
type: string
targetID:
type: integer
text:
@ -55,6 +57,9 @@ definitions:
template_id:
example: 1
type: integer
volume_percent:
example: 50
type: integer
required:
- audio
- duration
@ -294,6 +299,10 @@ definitions:
example: "2025-03-07T10:15:30Z"
format: date-time
type: string
volume_percent:
example: 50
format: int64
type: integer
type: object
donat-widget_internal_model.InitNewStreamerBody:
properties:
@ -440,6 +449,9 @@ definitions:
voice_speed:
description: Добавляем новые поля для настроек голоса
type: string
volume_percent:
example: 100
type: integer
type: object
donat-widget_internal_model.TextAfterPaidDonat:
properties:
@ -517,6 +529,9 @@ definitions:
name:
example: Awesome Widget
type: string
volume_percent:
example: 50
type: integer
type: object
donat-widget_internal_model.VoiceSettingsResponse:
properties:

View File

@ -26,6 +26,7 @@ type WidgetService interface {
audio string,
name string,
isActive bool,
volumePercent int,
) (GetWidgetDb, error)
GetWidgetsByStreamer(ctx context.Context, streamerID int) (AllWidgets, error)
UpdateWidget(ctx context.Context, updateWidget UpdateWidget, widgetID, accountID int) error
@ -45,6 +46,7 @@ type WidgetRepo interface {
audio string,
name string,
isActive bool,
volumePercent int,
) (WidgetID, error)
CheckWidgetName(ctx context.Context, streamerID int, name string) (bool, error)
GetWidgetsByStreamerID(ctx context.Context, streamerID int) ([]*GetWidgetDb, error)
@ -58,6 +60,7 @@ type WidgetRepo interface {
image *uuid.UUID,
audio *uuid.UUID,
name *string,
volumePercent *int,
) error
CheckExistsWidget(ctx context.Context, widgetID, accountID int) error
GetWidgetByID(ctx context.Context, widgetID int) (GetWidgetDb, error)
@ -73,13 +76,21 @@ type DonatService interface {
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(
ctx context.Context,
streamerID int,
text string,
donatUser string,
targetID *int,
mediaURL *string,
amount int,
) error
@ -139,6 +150,7 @@ type DonatRepo interface {
amount int,
text string,
donatUser string,
mediaUrl *string,
status string,
) error

View File

@ -18,17 +18,18 @@ type DataFile struct {
}
type GetWidgetDb struct {
ID int `db:"id" json:"id" example:"1" format:"int64" description:"Unique identifier of the widget"`
StreamerID int `db:"streamer_id" json:"streamer_id" example:"1001" format:"int64" description:"ID of the streamer"`
TemplateID int `db:"template_id" json:"template_id" example:"5" format:"int64" description:"ID of the template"`
Name string `db:"name" json:"name" example:"Мой виджет 10" format:"string" description:"Имя виджета"`
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"`
MaxAmount int `db:"max_amount" json:"max_amount" example:"1000" format:"int64" description:"Maximum donation amount"`
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"`
GroupID int `db:"group_id" json:"group_id" example:"2" format:"int64" description:"Group ID associated with the widget"`
IsActive bool `db:"is_active" json:"is_active" example:"true" description:"Whether or not this widget is active"`
ID int `db:"id" json:"id" example:"1" format:"int64" description:"Unique identifier of the widget"`
StreamerID int `db:"streamer_id" json:"streamer_id" example:"1001" format:"int64" description:"ID of the streamer"`
TemplateID int `db:"template_id" json:"template_id" example:"5" format:"int64" description:"ID of the template"`
Name string `db:"name" json:"name" example:"Мой виджет 10" format:"string" description:"Имя виджета"`
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"`
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"`
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"`
IsActive bool `db:"is_active" json:"is_active" example:"true" description:"Whether or not this widget is active"`
// Поля для изображения
ImageFileId uuid.UUID `db:"image_id" json:"image_id" format:"uuid" example:"550e8400-e29b-41d4-a716-446655440000" description:"UUID of the image file"`
@ -54,13 +55,14 @@ type AllWidgets struct {
}
type UpdateWidget struct {
IsActive *bool `json:"is_active" example:"true" description:"Indicates whether the widget is active or not"`
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"`
Audio *uuid.UUID `json:"audio" example:"a0f9e244-f61f-4bfe-a7a0-3b5e91fe7364" description:"UUID of the widget audio file"`
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"`
MaxAmount *int `json:"max_amount" example:"100" description:"Maximum amount for the widget"`
IsActive *bool `json:"is_active" example:"true" description:"Indicates whether the widget is active or not"`
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"`
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"`
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"`
}
type Donat struct {
@ -247,14 +249,15 @@ type CreateWidgetResponse struct {
// CreateWidgetBody структура для создания виджета
type CreateWidgetBody struct {
TemplateID int `json:"template_id" validate:"required" example:"1"`
Duration int `json:"duration" validate:"required" example:"30"`
IsActive bool `json:"is_active" example:"true"`
Name string `json:"name" validate:"required" example:"My GetWidgetDb"`
Image string `json:"image" validate:"required" format:"uuid" example:"550e8400-e29b-41d4-a716-446655440000"`
Audio string `json:"audio" validate:"required" format:"uuid" example:"550e8400-e29b-41d4-a716-446655440001"`
MinAmount int `json:"min_amount" validate:"required" example:"10"`
MaxAmount int `json:"max_amount" validate:"required" example:"100"`
TemplateID int `json:"template_id" validate:"required" example:"1"`
Duration int `json:"duration" validate:"required" example:"30"`
IsActive bool `json:"is_active" example:"true"`
Name string `json:"name" validate:"required" example:"My GetWidgetDb"`
Image string `json:"image" validate:"required" format:"uuid" example:"550e8400-e29b-41d4-a716-446655440000"`
Audio string `json:"audio" validate:"required" format:"uuid" example:"550e8400-e29b-41d4-a716-446655440001"`
MinAmount int `json:"min_amount" validate:"required" example:"10"`
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 {
@ -263,10 +266,11 @@ type DonatAndWidget struct {
}
type CreateDonatBody struct {
TargetID *int `json:"targetID"`
Amount int `json:"amount"`
Text string `json:"text"`
DonatUser string `json:"donatUser"`
TargetID *int `json:"targetID"`
Amount int `json:"amount"`
Text string `json:"text"`
DonatUser string `json:"donatUser"`
MediaUrl *string `json:"mediaUrl"`
}
type DonationStat struct {
@ -327,29 +331,31 @@ type MarkDonatViewed struct {
}
type PlayingDonat struct {
Duration int `json:"duration" example:"30"`
Image *string `json:"image_link"`
Audio *string `json:"audio_link"`
Text string `json:"text"`
Amount int `json:"amount"`
OrderID string `json:"order_id"`
DonatUser *string `json:"donat_user"`
PlayContent bool `json:"play_content"`
ShowName bool `json:"show_name"`
ShowText bool `json:"show_text"`
Duration int `json:"duration" example:"30"`
VolumePercent int `json:"volume_percent" example:"100"`
Image *string `json:"image_link"`
Audio *string `json:"audio_link"`
Text string `json:"text"`
Amount int `json:"amount"`
OrderID string `json:"order_id"`
DonatUser *string `json:"donat_user"`
PlayContent bool `json:"play_content"`
ShowName bool `json:"show_name"`
ShowText bool `json:"show_text"`
}
type PlayingDonatResponse struct {
Duration int `json:"duration" example:"30"`
Image *string `json:"image_link"`
Audio *string `json:"audio_link"`
Text string `json:"text"`
Amount int `json:"amount"`
OrderID string `json:"order_id"`
DonatUser *string `json:"donat_user"`
PlayContent bool `json:"play_content"`
ShowName bool `json:"show_name"`
ShowText bool `json:"show_text"`
Duration int `json:"duration" example:"30"`
VolumePercent int `json:"volume_percent" example:"100"`
Image *string `json:"image_link"`
Audio *string `json:"audio_link"`
Text string `json:"text"`
Amount int `json:"amount"`
OrderID string `json:"order_id"`
DonatUser *string `json:"donat_user"`
PlayContent bool `json:"play_content"`
ShowName bool `json:"show_name"`
ShowText bool `json:"show_text"`
// Добавляем новые поля для настроек голоса
VoiceSpeed string `json:"voice_speed,omitempty"`
Scenery string `json:"scenery,omitempty"`

View File

@ -15,7 +15,7 @@ CREATE TABLE IF NOT EXISTS widgets (
duration INTEGER NOT NULL,
min_amount INTEGER NOT NUll,
max_amount INTEGER NOT NULL,
volume_percent INTEGER NOT NULL DEFAULT 75,
created_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,
paid_time TIMESTAMP,
is_test BOOLEAN DEFAULT 'false',
media_url VARCHAR(255),
status VARCHAR(50) NOT NULL DEFAULT 'pending',

View File

@ -6,8 +6,8 @@ import (
)
var CreateWidget = `
INSERT INTO widgets (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)
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, @volume_percent)
RETURNING id;
`
@ -40,8 +40,8 @@ func GetMediaUrl(mediaType model.MediaType) string {
}
var CreateDonat = `
INSERT INTO donats (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)
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, @media_url)
RETURNING id;
`
var MarkDonatView = `
@ -199,6 +199,7 @@ SELECT
w.group_id,
w.name,
w.is_active,
w.volume_percent,
img.id AS image_id,
img.file_name AS image_file_name,
img.file_type AS image_type,
@ -280,7 +281,8 @@ SET
is_active = COALESCE(@is_active, is_active),
image = COALESCE(@image, image),
audio = COALESCE(@audio, audio),
name = COALESCE(@name, name)
name = COALESCE(@name, name),
volume_percent = COALESCE(@volume_percent, volume_percent)
WHERE id = @id;
`
@ -402,7 +404,7 @@ WHERE
streamer_id = @streamer_id
AND created_at >= NOW() - INTERVAL '24 hours'
AND paid_time IS NOT NULL
AND is_test IS 'false'
AND is_test IS false
GROUP BY
DATE_TRUNC('hour', created_at) -- Группировка по часам
@ -421,7 +423,7 @@ WHERE
streamer_id = @streamer_id
AND created_at >= NOW() - INTERVAL '7 days'
AND paid_time IS NOT NULL
AND is_test IS 'false'
AND is_test IS false
GROUP BY
@ -441,7 +443,7 @@ WHERE
streamer_id = @streamer_id
AND created_at >= NOW() - INTERVAL '1 month'
AND paid_time IS NOT NULL
AND is_test IS 'false'
AND is_test IS false
GROUP BY
DATE(created_at) -- Группировка по дням
@ -460,7 +462,7 @@ WHERE
streamer_id = @streamer_id
AND created_at >= NOW() - INTERVAL '1 year'
AND paid_time IS NOT NULL
AND is_test IS 'false'
AND is_test IS false
GROUP BY
DATE_TRUNC('month', created_at) -- Группировка по месяцам
@ -478,7 +480,7 @@ WHERE
streamer_id = @streamer_id
AND created_at >= NOW() - INTERVAL '24 hours'
AND paid_time IS NOT NULL
AND is_test IS 'false';
AND is_test IS false;
`
const GetDonationsSummaryLast7Days = `
@ -491,7 +493,7 @@ WHERE
streamer_id = @streamer_id
AND created_at >= NOW() - INTERVAL '7 days'
AND paid_time IS NOT NULL
AND is_test IS 'false';
AND is_test IS false;
`
const GetDonationsSummaryLastMonth = `
@ -504,7 +506,7 @@ WHERE
streamer_id = @streamer_id
AND created_at >= NOW() - INTERVAL '1 month'
AND paid_time IS NOT NULL
AND is_test IS 'false';
AND is_test IS false;
`
const GetDonationsSummaryLastYear = `
@ -517,7 +519,7 @@ WHERE
streamer_id = @streamer_id
AND created_at >= NOW() - INTERVAL '1 year'
AND paid_time IS NOT NULL
AND is_test IS 'false';
AND is_test IS false;
`
const GetLastModeration = `
@ -588,7 +590,8 @@ SELECT
d.donat_user,
d.play_content,
d.show_name,
d.show_text
d.show_text,
w.volume_percent
FROM widgets AS w
INNER JOIN
donats AS d ON d.widget_id = w.id

View File

@ -166,8 +166,10 @@ async function playMedia(donat, voiceSettings) {
const mediaPromise = (async () => {
if (donat.play_content && donat.audio_link) {
await playAudio(donat.audio_link, (voiceSettings.voice_sound_percent || 100) / 100, controller.signal);
if (donat.text) await playSpeech(donat.text, voiceSettings, controller.signal);
} else if (donat.text && donat.voice_enabled) {
if (donat.text && donat.show_text) {
await playSpeech(donat.text, voiceSettings, controller.signal);
}
} else if (donat.text && donat.voice_enabled && donat.show_text) {
await playSpeech(donat.text, voiceSettings, controller.signal);
}
})();
@ -179,8 +181,6 @@ async function playMedia(donat, voiceSettings) {
}, timeoutDuration);
});
// Promise.race будет ждать либо завершения медиа, либо таймаута
// Если медиа завершится с ошибкой (например, автоплей заблокирован), она пробросится дальше.
return Promise.race([mediaPromise, timeoutPromise]);
}
@ -226,16 +226,12 @@ async function widgetView() {
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 };
await playMedia(currentDonat, voiceSettings);
} catch (error) {
// Логируем ошибку, но не прерываем логику ожидания и очистки
console.error('Ошибка обработки доната:', error.name === 'AbortError' ? 'Таймаут или блокировка звука' : error);
} finally {
// --- Очистка и завершение ---
// Этот блок выполнится ВСЕГДА: и после успеха, и после ошибки.
if (currentDonat) {
// Отмечаем донат как просмотренный
if (currentDonat.order_id) {
@ -250,7 +246,6 @@ async function widgetView() {
}
}
// Ждем оставшееся время до конца показа доната
const elapsedMs = Date.now() - iterationStart;
const durationMs = (currentDonat.duration || 5) * 1000;
const remainingTimeMs = Math.max(0, durationMs - elapsedMs);
@ -259,7 +254,6 @@ async function widgetView() {
await new Promise(r => setTimeout(r, remainingTimeMs));
}
// Гарантированно очищаем контейнер
clearContainer(contentDiv);
}
}

View File

@ -33,6 +33,7 @@ func (repoDonat *RepoDonat) CreateDonat(
amount int,
text string,
donatUser string,
mediaUrl *string,
status string,
) error {
args := pgx.NamedArgs{
@ -44,6 +45,7 @@ func (repoDonat *RepoDonat) CreateDonat(
"amount": amount,
"status": status,
"donat_user": donatUser,
"media_url": mediaUrl,
"is_test": false,
"paid_time": nil,
}
@ -858,6 +860,7 @@ func (repoDonat *RepoDonat) GetPlayingDonat(
&donatForPlaying.PlayContent,
&donatForPlaying.ShowName,
&donatForPlaying.ShowText,
&donatForPlaying.VolumePercent,
)
if err != nil {

View File

@ -37,17 +37,19 @@ func (widgetRepo *RepoWidget) CreateWidget(
audio string,
name string,
isActive bool,
volumePercent int,
) (model.WidgetID, error) {
args := pgx.NamedArgs{
"streamer_id": streamerID,
"template_id": templateID,
"duration": duration,
"min_amount": minAmount,
"max_amount": maxAmount,
"image": image,
"audio": audio,
"name": name,
"is_active": isActive,
"streamer_id": streamerID,
"template_id": templateID,
"duration": duration,
"min_amount": minAmount,
"max_amount": maxAmount,
"image": image,
"audio": audio,
"name": name,
"is_active": isActive,
"volume_percent": volumePercent,
}
widgetID, err := widgetRepo.db.Insert(ctx, sql.CreateWidget, args)
if err != nil {
@ -141,6 +143,7 @@ func (widgetRepo *RepoWidget) UpdateWidget(
image *uuid.UUID,
audio *uuid.UUID,
name *string,
volumePercent *int,
) error {
args := pgx.NamedArgs{
"id": widgetID,
@ -167,6 +170,9 @@ func (widgetRepo *RepoWidget) UpdateWidget(
if isActive != nil {
args["is_active"] = *isActive
}
if volumePercent != nil {
args["volume_percent"] = *volumePercent
}
err := widgetRepo.db.Update(ctx, sql.UpdateWidget, args)
if err != nil {

View File

@ -95,6 +95,7 @@ func (donatService *ServiceDonat) CreateDonat(
donatUser string,
targetID *int,
amount int,
mediaUrl *string,
) (model.CreateDonatResponse, error) {
donatePage, err := donatService.donatRepo.GetDonatPageByLogin(ctx, streamerLogin)
@ -128,6 +129,7 @@ func (donatService *ServiceDonat) CreateDonat(
amount,
text,
donatUser,
mediaUrl,
"pending",
)
if err != nil {
@ -153,13 +155,26 @@ func (donatService *ServiceDonat) CreateTestDonat(
text string,
donatUser string,
targetID *int,
mediaUrl *string,
amount int,
) error {
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,
streamerID,
targetID,
@ -168,6 +183,7 @@ func (donatService *ServiceDonat) CreateTestDonat(
amount,
text,
donatUser,
mediaUrl,
"test_donat",
)
if err != nil {
@ -808,16 +824,17 @@ func (donatService *ServiceDonat) GetPlayingDonat(
}
response := model.PlayingDonatResponse{
Duration: playingDonat.Duration,
Image: playingDonat.Image,
Audio: playingDonat.Audio,
Text: playingDonat.Text,
Amount: playingDonat.Amount,
OrderID: playingDonat.OrderID,
DonatUser: playingDonat.DonatUser,
PlayContent: playingDonat.PlayContent,
ShowName: playingDonat.ShowName,
ShowText: playingDonat.ShowText,
Duration: playingDonat.Duration,
Image: playingDonat.Image,
Audio: playingDonat.Audio,
Text: playingDonat.Text,
Amount: playingDonat.Amount,
OrderID: playingDonat.OrderID,
DonatUser: playingDonat.DonatUser,
PlayContent: playingDonat.PlayContent,
ShowName: playingDonat.ShowName,
ShowText: playingDonat.ShowText,
VolumePercent: playingDonat.VolumePercent,
}
filteredSettings, err := donatService.GetFiltersSettings(ctx, streamerID)
@ -839,7 +856,7 @@ func (donatService *ServiceDonat) GetPlayingDonat(
if !filteredSettings.EnableLinks {
response.Text = donatService.replaceLinks(response.Text)
}
fmt.Println(response.Text)
response.VoiceSpeed = voiceSettings.VoiceSpeed
response.Scenery = voiceSettings.Scenery
response.VoiceSoundPercent = voiceSettings.VoiceSoundPercent

View File

@ -49,12 +49,12 @@ func (widgetService *ServiceWidget) CreateWidget(
audio string,
name string,
isActive bool,
volumePercent int,
) (model.GetWidgetDb, error) {
exists, err := widgetService.widgetRepo.CheckWidgetName(ctx, streamerID, name)
if err != nil {
return model.GetWidgetDb{}, err
}
fmt.Println(exists)
if exists == true {
slog.Error("GetWidgetDb 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,
name,
isActive,
volumePercent,
)
if err != nil {
slog.Error(err.Error())
@ -149,6 +150,7 @@ func (widgetService *ServiceWidget) UpdateWidget(
updateWidget.Image,
updateWidget.Audio,
updateWidget.Name,
updateWidget.VolumePercent,
)
if err != nil {
slog.Error(err.Error())

View File

@ -20,6 +20,7 @@ CREATE TABLE IF NOT EXISTS widgets (
name VARCHAR(50) NOT NULL,
image 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,
min_amount INTEGER NOT NULL,
max_amount INTEGER NOT NULL,