feat(theme-4): sync inbox/announcements/presence/deposit from main

This commit is contained in:
2026-06-18 10:08:03 +08:00
parent d3ca8498fb
commit 5c5aa7e55a
87 changed files with 6911 additions and 1026 deletions

View File

@@ -3,6 +3,7 @@ import {
Get,
Post,
Patch,
Delete,
Body,
Param,
Query,
@@ -30,6 +31,8 @@ import { BetsService } from '../../domains/betting/bets.service';
import { ContentService } from '../../domains/operations/content/content.service';
import { CashbackService } from '../../domains/operations/cashback/cashback.service';
import { DepositService } from '../../domains/deposit/deposit.service';
import { PlayerMessagesService } from '../../domains/player-messages/player-messages.service';
import { PresenceService } from '../../domains/presence/presence.service';
import { isInLocalTodayMatchWindow } from '@thebet365/shared';
import { IsString, IsNumber, IsArray, ValidateNested, Min, IsOptional } from 'class-validator';
import { Type } from 'class-transformer';
@@ -120,8 +123,16 @@ export class PlayerController {
private cashback: CashbackService,
private systemConfig: SystemConfigService,
private deposit: DepositService,
private playerMessages: PlayerMessagesService,
private presence: PresenceService,
) {}
@Post('presence/ping')
async presencePing(@CurrentUser('id') userId: bigint) {
await this.presence.touch(userId);
return jsonResponse({ ok: true });
}
private async formatPlayerProfile(user: NonNullable<Awaited<ReturnType<UsersService['findById']>>>) {
const accountSettings = await this.systemConfig.getPlayerAccountSettings();
const prefs = user.preferences;
@@ -175,10 +186,12 @@ export class PlayerController {
@Headers('x-time-zone') headerTimeZone?: string,
) {
const locale = userLocale || headerLocale || 'zh-CN';
const [banners, announcements, allMatches] = await Promise.all([
const [banners, announcements, allMatches, upcomingMatches, inboxEnabled] = await Promise.all([
this.content.listActive('BANNER', locale),
this.content.listActiveAnnouncements(locale),
this.matches.listPublished(locale, undefined, { includeMarkets: false }),
this.matches.listUpcomingPublished(locale),
this.systemConfig.getInboxFeatureEnabled(),
]);
const timeZone = safeTimeZone(headerTimeZone);
const now = new Date();
@@ -198,6 +211,8 @@ export class PlayerController {
notices: announcements,
hotMatches,
todayMatches,
upcomingMatches,
inboxEnabled,
});
}
@@ -245,6 +260,20 @@ export class PlayerController {
return jsonResponse(match);
}
@Public()
@Get('selections/odds')
async selectionOdds(@Query('ids') ids?: string) {
if (!ids?.trim()) throw appBadRequest('SELECTION_NOT_FOUND');
const parsed = ids
.split(',')
.map((s) => s.trim())
.filter(Boolean)
.map((s) => BigInt(s));
if (!parsed.length || parsed.length > 20) throw appBadRequest('PARLAY_LEG_COUNT_INVALID');
const items = await this.matches.getSelectionsOdds(parsed);
return jsonResponse({ items });
}
@Post('bets/single')
async singleBet(@CurrentUser('id') userId: bigint, @CurrentUser('parentId') parentId: bigint, @Body() dto: SingleBetDto) {
const bet = await this.bets.placeSingleBet(
@@ -449,4 +478,56 @@ export class PlayerController {
createdAt: order!.createdAt,
});
}
// ============ Messages / Inbox ============
@Get('messages')
async listMessages(
@CurrentUser('id') userId: bigint,
@Query('page') page?: string,
@Query('pageSize') pageSize?: string,
) {
const result = await this.playerMessages.listForPlayer(
userId,
page ? parseInt(page, 10) : 1,
pageSize ? parseInt(pageSize, 10) : 20,
);
return jsonResponse(result);
}
@Get('messages/unread-count')
async messageUnreadCount(@CurrentUser('id') userId: bigint) {
const result = await this.playerMessages.getUnreadCount(userId);
return jsonResponse(result);
}
@Get('messages/:id')
async messageDetail(@CurrentUser('id') userId: bigint, @Param('id') id: string) {
const message = await this.playerMessages.getForPlayer(userId, BigInt(id));
return jsonResponse(message);
}
@Patch('messages/read-all')
async markAllMessagesRead(@CurrentUser('id') userId: bigint) {
const result = await this.playerMessages.markAllRead(userId);
return jsonResponse(result);
}
@Patch('messages/:id/read')
async markMessageRead(@CurrentUser('id') userId: bigint, @Param('id') id: string) {
const message = await this.playerMessages.markRead(userId, BigInt(id));
return jsonResponse(message);
}
@Delete('messages')
async deleteAllMessages(@CurrentUser('id') userId: bigint) {
const result = await this.playerMessages.deleteAllForPlayer(userId);
return jsonResponse(result);
}
@Delete('messages/:id')
async deleteMessage(@CurrentUser('id') userId: bigint, @Param('id') id: string) {
const result = await this.playerMessages.deleteForPlayer(userId, BigInt(id));
return jsonResponse(result);
}
}