add routers and logic for online donat
This commit is contained in:
parent
a4cbb43ccd
commit
ae5301a222
@ -713,6 +713,40 @@ func GetDonatForPlaying(donatService model.DonatService) echo.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateStreamerOnline godoc
|
||||||
|
// @Summary Update streamer online status
|
||||||
|
// @Description Marks streamer as online in the system
|
||||||
|
// @Tags Donate
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Param streamer-id path string true "Streamer ID (numeric)"
|
||||||
|
// @Success 200 {object} map[string]interface{} "Success status"
|
||||||
|
// @Failure 400 {object} echo.HTTPError "Invalid streamer ID"
|
||||||
|
// @Failure 500 {object} echo.HTTPError "Internal server error"
|
||||||
|
// @Router /update-streamer-online/{streamer-id} [put]
|
||||||
|
func UpdateStreamerOnline(donatService model.DonatService) echo.HandlerFunc {
|
||||||
|
return func(c echo.Context) error {
|
||||||
|
ctx := context.Background()
|
||||||
|
streamerIDParam := c.Param("streamer-id")
|
||||||
|
|
||||||
|
streamerID, err := strconv.Atoi(streamerIDParam)
|
||||||
|
if err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusBadRequest, "Invalid streamer ID format")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = donatService.UpdateStreamerOnline(ctx, streamerID)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to update streamer online status", "error", err)
|
||||||
|
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to update online status")
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, map[string]interface{}{
|
||||||
|
"status": "success",
|
||||||
|
"message": "streamer online status updated",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateLoginDonatePage godoc
|
// UpdateLoginDonatePage godoc
|
||||||
// @Summary Update streamer login
|
// @Summary Update streamer login
|
||||||
// @Description Updates the streamer login associated with the donate page
|
// @Description Updates the streamer login associated with the donate page
|
||||||
|
@ -107,6 +107,7 @@ func IncludeDonatHandlers(
|
|||||||
server.GET(PREFIX+"/get-donat-for-playing/:streamer-id", GetDonatForPlaying(donatService))
|
server.GET(PREFIX+"/get-donat-for-playing/:streamer-id", GetDonatForPlaying(donatService))
|
||||||
server.GET(PREFIX+"/text-after-donat/:order-id", GetMessageAfterDonat(donatService))
|
server.GET(PREFIX+"/text-after-donat/:order-id", GetMessageAfterDonat(donatService))
|
||||||
server.POST(PREFIX+"/update-login-donate", UpdateLoginDonatePage(donatService))
|
server.POST(PREFIX+"/update-login-donate", UpdateLoginDonatePage(donatService))
|
||||||
|
server.PUT(PREFIX+"/update-streamer-online/:streamer-id", UpdateStreamerOnline(donatService))
|
||||||
}
|
}
|
||||||
|
|
||||||
func IncludeWidgetHandlers(
|
func IncludeWidgetHandlers(
|
||||||
|
@ -1201,6 +1201,51 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/update-streamer-online/{streamer-id}": {
|
||||||
|
"put": {
|
||||||
|
"description": "Marks streamer as online in the system",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Donate"
|
||||||
|
],
|
||||||
|
"summary": "Update streamer online status",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Streamer ID (numeric)",
|
||||||
|
"name": "streamer-id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Success status",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Invalid streamer ID",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/echo.HTTPError"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal server error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/echo.HTTPError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/voice-settings": {
|
"/voice-settings": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
|
@ -1194,6 +1194,51 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/update-streamer-online/{streamer-id}": {
|
||||||
|
"put": {
|
||||||
|
"description": "Marks streamer as online in the system",
|
||||||
|
"consumes": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"tags": [
|
||||||
|
"Donate"
|
||||||
|
],
|
||||||
|
"summary": "Update streamer online status",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Streamer ID (numeric)",
|
||||||
|
"name": "streamer-id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Success status",
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Invalid streamer ID",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/echo.HTTPError"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal server error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/echo.HTTPError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/voice-settings": {
|
"/voice-settings": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
|
@ -1328,6 +1328,36 @@ paths:
|
|||||||
summary: Update streamer login
|
summary: Update streamer login
|
||||||
tags:
|
tags:
|
||||||
- Donate
|
- Donate
|
||||||
|
/update-streamer-online/{streamer-id}:
|
||||||
|
put:
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
description: Marks streamer as online in the system
|
||||||
|
parameters:
|
||||||
|
- description: Streamer ID (numeric)
|
||||||
|
in: path
|
||||||
|
name: streamer-id
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: Success status
|
||||||
|
schema:
|
||||||
|
additionalProperties: true
|
||||||
|
type: object
|
||||||
|
"400":
|
||||||
|
description: Invalid streamer ID
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/echo.HTTPError'
|
||||||
|
"500":
|
||||||
|
description: Internal server error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/echo.HTTPError'
|
||||||
|
summary: Update streamer online status
|
||||||
|
tags:
|
||||||
|
- Donate
|
||||||
/voice-settings:
|
/voice-settings:
|
||||||
get:
|
get:
|
||||||
consumes:
|
consumes:
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"donat-widget/internal/model/api"
|
"donat-widget/internal/model/api"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -108,6 +109,7 @@ type DonatService interface {
|
|||||||
UpdateStreamerLogin(ctx context.Context, streamerLogin string, streamerID int) error
|
UpdateStreamerLogin(ctx context.Context, streamerLogin string, streamerID int) error
|
||||||
UpdateAvatarStreamer(token, avatarId string) error
|
UpdateAvatarStreamer(token, avatarId string) error
|
||||||
GetTextAfterDonatByOrder(ctx context.Context, orderID uuid.UUID) (string, error)
|
GetTextAfterDonatByOrder(ctx context.Context, orderID uuid.UUID) (string, error)
|
||||||
|
UpdateStreamerOnline(ctx context.Context, streamerID int) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type DonatRepo interface {
|
type DonatRepo interface {
|
||||||
@ -178,6 +180,8 @@ type DonatRepo interface {
|
|||||||
|
|
||||||
GetPlayingDonat(ctx context.Context, streamerID int) (PlayingDonat, error)
|
GetPlayingDonat(ctx context.Context, streamerID int) (PlayingDonat, error)
|
||||||
UpdateStreamerLogin(ctx context.Context, streamerLogin string, streamerID int) error
|
UpdateStreamerLogin(ctx context.Context, streamerLogin string, streamerID int) error
|
||||||
|
CreateStreamerOnline(ctx context.Context, streamerID int) error
|
||||||
|
GetLastStreamerOnline(ctx context.Context, streamerID int) (time.Time, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type TargetService interface {
|
type TargetService interface {
|
||||||
|
@ -130,6 +130,12 @@ CREATE TABLE IF NOT EXISTS streamers_widgets_pages (
|
|||||||
id UUID PRIMARY KEY NOT NULL DEFAULT uuid_generate_v4(),
|
id UUID PRIMARY KEY NOT NULL DEFAULT uuid_generate_v4(),
|
||||||
streamer_id INTEGER NOT NULL
|
streamer_id INTEGER NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS streamers_online (
|
||||||
|
id UUID NOT NULL PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||||
|
streamer_id INTEGER NOT NULL,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION update_updated_at()
|
CREATE OR REPLACE FUNCTION update_updated_at()
|
||||||
|
@ -589,3 +589,9 @@ SELECT streamer_id FROM streamers_widgets_pages WHERE id = @widget_page_id`
|
|||||||
var UpdateStreamerLogin = `
|
var UpdateStreamerLogin = `
|
||||||
UPDATE donate_pages SET streamer_login = @streamer_login WHERE streamer_id = @streamer_id
|
UPDATE donate_pages SET streamer_login = @streamer_login WHERE streamer_id = @streamer_id
|
||||||
RETURNING streamer_login;`
|
RETURNING streamer_login;`
|
||||||
|
|
||||||
|
var InsertStreamerOnline = `
|
||||||
|
INSERT INTO streamers_online (streamer_id) VALUES (@streamer_id)`
|
||||||
|
|
||||||
|
var GetLastStreamerOnline = `
|
||||||
|
SELECT created_at FROM streamers_online WHERE streamer_id = @streamer_id`
|
||||||
|
@ -64,6 +64,20 @@ func GetTemplate1(streamerID int, donatHost, ttsHost string) string {
|
|||||||
let widgetUrl = 'https://%s/api';
|
let widgetUrl = 'https://%s/api';
|
||||||
let ttsUrl = 'https://%s/api/tts';
|
let ttsUrl = 'https://%s/api/tts';
|
||||||
|
|
||||||
|
// Функция для периодических запросов онлайн-статуса
|
||||||
|
function startHeartbeat(streamerID) {
|
||||||
|
setInterval(async () => {
|
||||||
|
try {
|
||||||
|
await fetch(widgetUrl + '/update-streamer-online/' + streamerID, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {'Content-Type': 'application/json'}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Heartbeat error:', error);
|
||||||
|
}
|
||||||
|
}, 10000); // 10 секунд
|
||||||
|
}
|
||||||
|
|
||||||
function createTextWithAmount(text, amount) {
|
function createTextWithAmount(text, amount) {
|
||||||
const container = document.createElement('div');
|
const container = document.createElement('div');
|
||||||
container.className = 'text-container';
|
container.className = 'text-container';
|
||||||
@ -120,7 +134,7 @@ function playAudio(url, volume, signal) {
|
|||||||
if (resolved) return;
|
if (resolved) return;
|
||||||
resolved = true;
|
resolved = true;
|
||||||
audio.pause();
|
audio.pause();
|
||||||
audio.src = ""; // Release resources
|
audio.src = "";
|
||||||
audio.removeEventListener('ended', onEnded);
|
audio.removeEventListener('ended', onEnded);
|
||||||
audio.removeEventListener('error', onError);
|
audio.removeEventListener('error', onError);
|
||||||
reject(new DOMException('Aborted', 'AbortError'));
|
reject(new DOMException('Aborted', 'AbortError'));
|
||||||
@ -148,16 +162,15 @@ function playAudio(url, volume, signal) {
|
|||||||
audio.addEventListener('error', onError, { once: true });
|
audio.addEventListener('error', onError, { once: true });
|
||||||
|
|
||||||
audio.play().catch(err => {
|
audio.play().catch(err => {
|
||||||
// play() can reject if interrupted or no user gesture
|
|
||||||
if (resolved) return;
|
if (resolved) return;
|
||||||
resolved = true;
|
resolved = true;
|
||||||
signal?.removeEventListener('abort', onAbort);
|
signal?.removeEventListener('abort', onAbort);
|
||||||
audio.removeEventListener('ended', onEnded);
|
audio.removeEventListener('ended', onEnded);
|
||||||
audio.removeEventListener('error', onError);
|
audio.removeEventListener('error', onError);
|
||||||
if (err.name === 'AbortError' && signal?.aborted) {
|
if (err.name === 'AbortError' && signal?.aborted) {
|
||||||
reject(new DOMException('Aborted', 'AbortError')); // Propagate abort
|
reject(new DOMException('Aborted', 'AbortError'));
|
||||||
} else {
|
} else {
|
||||||
reject(err); // Other play() error
|
reject(err);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -208,7 +221,7 @@ function playSpeech(text, voiceSettings, signal) {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: JSON.stringify(requestBody),
|
body: JSON.stringify(requestBody),
|
||||||
signal: signal // Pass signal to fetch
|
signal: signal
|
||||||
})
|
})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (signal?.aborted) throw new DOMException('Aborted', 'AbortError');
|
if (signal?.aborted) throw new DOMException('Aborted', 'AbortError');
|
||||||
@ -241,10 +254,9 @@ function playSpeech(text, voiceSettings, signal) {
|
|||||||
return audio.play();
|
return audio.play();
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
// This catches errors from fetch, blob processing, or audio.play()
|
if (resolved) return;
|
||||||
if (resolved) return; // Already handled
|
signal?.removeEventListener('abort', onAbort);
|
||||||
signal?.removeEventListener('abort', onAbort); // Ensure listener removed
|
cleanupAndReject(error);
|
||||||
cleanupAndReject(error); // Pass the original error
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -273,26 +285,24 @@ async function playMedia(donat, voiceSettings) {
|
|||||||
ttsAudio = await playSpeech(donat.text, voiceSettings, controller.signal);
|
ttsAudio = await playSpeech(donat.text, voiceSettings, controller.signal);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error.name !== 'AbortError') { // Only re-throw non-abort errors
|
if (error.name !== 'AbortError') {
|
||||||
console.error('Error during media playback:', error);
|
console.error('Error during media playback:', error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
// If AbortError, it's handled by the timeout logic or external abort.
|
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const timeoutPromise = new Promise((resolve, reject) => {
|
const timeoutPromise = new Promise((resolve, reject) => {
|
||||||
timeoutId = setTimeout(() => {
|
timeoutId = setTimeout(() => {
|
||||||
controller.abort(); // Signal all operations to stop
|
controller.abort();
|
||||||
resolve('timeout'); // Resolve to indicate timeout completion
|
resolve('timeout');
|
||||||
}, (donat.duration || 0) * 1000); // Use donat.duration, default to 0 if undefined
|
}, (donat.duration || 0) * 1000);
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await Promise.race([mediaOperation, timeoutPromise]);
|
await Promise.race([mediaOperation, timeoutPromise]);
|
||||||
} finally {
|
} finally {
|
||||||
clearTimeout(timeoutId); // Crucial: clear timeout if mediaOperation finished first or errored
|
clearTimeout(timeoutId);
|
||||||
// Ensure audio elements are stopped and reset if they were initiated and not properly cleaned up by abort
|
|
||||||
if (audioElement && !audioElement.paused) {
|
if (audioElement && !audioElement.paused) {
|
||||||
audioElement.pause();
|
audioElement.pause();
|
||||||
audioElement.currentTime = 0;
|
audioElement.currentTime = 0;
|
||||||
@ -312,6 +322,8 @@ function clearContainer(container) {
|
|||||||
|
|
||||||
async function widgetView() {
|
async function widgetView() {
|
||||||
const streamerID = '%v';
|
const streamerID = '%v';
|
||||||
|
startHeartbeat(streamerID); // Запускаем heartbeat
|
||||||
|
|
||||||
const contentDiv = document.getElementById('content');
|
const contentDiv = document.getElementById('content');
|
||||||
|
|
||||||
if (!contentDiv) {
|
if (!contentDiv) {
|
||||||
@ -321,15 +333,15 @@ async function widgetView() {
|
|||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const iterationStart = Date.now();
|
const iterationStart = Date.now();
|
||||||
let currentDonat = null; // Store current donat for duration calculation
|
let currentDonat = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const donatData = await getDonatInfo(streamerID);
|
const donatData = await getDonatInfo(streamerID);
|
||||||
if (!donatData || Object.keys(donatData).length === 0) {
|
if (!donatData || Object.keys(donatData).length === 0) {
|
||||||
await new Promise(r => setTimeout(r, 5000)); // Wait before retrying
|
await new Promise(r => setTimeout(r, 5000));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
currentDonat = donatData; // Assign to currentDonat
|
currentDonat = donatData;
|
||||||
|
|
||||||
clearContainer(contentDiv);
|
clearContainer(contentDiv);
|
||||||
|
|
||||||
@ -363,7 +375,6 @@ async function widgetView() {
|
|||||||
voice_enabled: currentDonat.voice_enabled
|
voice_enabled: currentDonat.voice_enabled
|
||||||
};
|
};
|
||||||
|
|
||||||
// playMedia will ensure audio plays for at most currentDonat.duration
|
|
||||||
await playMedia(currentDonat, voiceSettings);
|
await playMedia(currentDonat, voiceSettings);
|
||||||
|
|
||||||
if (currentDonat.order_id) {
|
if (currentDonat.order_id) {
|
||||||
@ -379,18 +390,14 @@ async function widgetView() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Log errors from getDonatInfo, playMedia, or other processing
|
|
||||||
console.error('Ошибка обработки доната в цикле:', error);
|
console.error('Ошибка обработки доната в цикле:', error);
|
||||||
// If error, maybe wait a bit before next iteration to avoid spamming
|
if (!currentDonat) {
|
||||||
if (!currentDonat) { // e.g. error in getDonatInfo
|
|
||||||
await new Promise(r => setTimeout(r, 5000));
|
await new Promise(r => setTimeout(r, 5000));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const elapsedMs = Date.now() - iterationStart;
|
const elapsedMs = Date.now() - iterationStart;
|
||||||
// Total display time for the donation (visuals + audio)
|
|
||||||
// Use currentDonat.duration if available, otherwise default (e.g., 5 seconds)
|
|
||||||
const targetDisplayTimeMs = (currentDonat?.duration || 5) * 1000;
|
const targetDisplayTimeMs = (currentDonat?.duration || 5) * 1000;
|
||||||
|
|
||||||
const remainingTimeMs = Math.max(0, targetDisplayTimeMs - elapsedMs);
|
const remainingTimeMs = Math.max(0, targetDisplayTimeMs - elapsedMs);
|
||||||
@ -398,7 +405,6 @@ async function widgetView() {
|
|||||||
if (remainingTimeMs > 0) {
|
if (remainingTimeMs > 0) {
|
||||||
await new Promise(r => setTimeout(r, remainingTimeMs));
|
await new Promise(r => setTimeout(r, remainingTimeMs));
|
||||||
}
|
}
|
||||||
// If elapsedMs already exceeds targetDisplayTimeMs, loop will continue immediately
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -881,3 +881,36 @@ func (repoDonat *RepoDonat) UpdateStreamerLogin(
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (repoDonat *RepoDonat) CreateStreamerOnline(
|
||||||
|
ctx context.Context,
|
||||||
|
streamerID int,
|
||||||
|
) error {
|
||||||
|
args := pgx.NamedArgs{
|
||||||
|
"streamer_id": streamerID,
|
||||||
|
}
|
||||||
|
err := repoDonat.db.Exec(ctx, sql.InsertStreamerOnline, args)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repoDonat *RepoDonat) GetLastStreamerOnline(
|
||||||
|
ctx context.Context,
|
||||||
|
streamerID int,
|
||||||
|
) (time.Time, error) {
|
||||||
|
args := pgx.NamedArgs{
|
||||||
|
"streamer_id": streamerID,
|
||||||
|
}
|
||||||
|
row := repoDonat.db.SelectOne(ctx, sql.GetLastStreamerOnline, args)
|
||||||
|
|
||||||
|
var StreamerOnline time.Time
|
||||||
|
|
||||||
|
err := row.Scan(&StreamerOnline)
|
||||||
|
if err != nil {
|
||||||
|
return StreamerOnline, err
|
||||||
|
}
|
||||||
|
return StreamerOnline, nil
|
||||||
|
}
|
||||||
|
@ -14,6 +14,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ServiceDonat struct {
|
type ServiceDonat struct {
|
||||||
@ -343,10 +344,18 @@ func (donatService *ServiceDonat) GetOuterDonatPage(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = donatService.getLastStreamerOnline(ctx, donatePage.StreamerID)
|
||||||
|
var online string
|
||||||
|
if err != nil {
|
||||||
|
online = "offline"
|
||||||
|
} else {
|
||||||
|
online = "online"
|
||||||
|
}
|
||||||
|
|
||||||
var outerDonatePageResponse = model.OuterDonatePageResponse{
|
var outerDonatePageResponse = model.OuterDonatePageResponse{
|
||||||
Description: donatePage.Description,
|
Description: donatePage.Description,
|
||||||
Login: donatePage.StreamerLogin,
|
Login: donatePage.StreamerLogin,
|
||||||
OnLine: "online",
|
OnLine: online,
|
||||||
BackgroundImg: donatService.storage.DownloadLink(donatePage.BackgroundImgFileId),
|
BackgroundImg: donatService.storage.DownloadLink(donatePage.BackgroundImgFileId),
|
||||||
HeadImg: donatService.storage.DownloadLink(donatePage.HeadImgFileId),
|
HeadImg: donatService.storage.DownloadLink(donatePage.HeadImgFileId),
|
||||||
AvatarImg: donatService.storage.DownloadLink(avatarFileId),
|
AvatarImg: donatService.storage.DownloadLink(avatarFileId),
|
||||||
@ -856,6 +865,46 @@ func (donatService *ServiceDonat) replaceFilteredWords(text string, words []stri
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (donatService *ServiceDonat) UpdateStreamerOnline(
|
||||||
|
ctx context.Context,
|
||||||
|
streamerID int,
|
||||||
|
) error {
|
||||||
|
err := donatService.donatRepo.CreateStreamerOnline(
|
||||||
|
ctx,
|
||||||
|
streamerID,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (donatService *ServiceDonat) getLastStreamerOnline(
|
||||||
|
ctx context.Context,
|
||||||
|
streamerID int,
|
||||||
|
) error {
|
||||||
|
onlineTime, err := donatService.donatRepo.GetLastStreamerOnline(
|
||||||
|
ctx,
|
||||||
|
streamerID,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
currentTime := time.Now().UTC()
|
||||||
|
diff := currentTime.Sub(onlineTime)
|
||||||
|
|
||||||
|
if diff < 0 {
|
||||||
|
diff = -diff
|
||||||
|
}
|
||||||
|
|
||||||
|
if diff > time.Minute {
|
||||||
|
return fmt.Errorf("last online time is more than a minute old")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (donatService *ServiceDonat) replaceLinks(text string) string {
|
func (donatService *ServiceDonat) replaceLinks(text string) string {
|
||||||
re := regexp.MustCompile(`(?i)\bhttps?://\S+\b`)
|
re := regexp.MustCompile(`(?i)\bhttps?://\S+\b`)
|
||||||
return re.ReplaceAllStringFunc(text, func(match string) string {
|
return re.ReplaceAllStringFunc(text, func(match string) string {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user