feat: 베팅 기능 추가 및 베팅 항목 저장 로직 구현
This commit is contained in:
@@ -116,4 +116,64 @@ export const tournamentRouter = router({
|
|||||||
await store.setBettingEntries(input);
|
await store.setBettingEntries(input);
|
||||||
return { ok: true, count: input.length };
|
return { ok: true, count: input.length };
|
||||||
}),
|
}),
|
||||||
|
placeBet: authedProcedure
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
targetId: z.number().int().positive(),
|
||||||
|
amount: z.number().int().positive(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.mutation(async ({ ctx, input }) => {
|
||||||
|
const userId = ctx.auth?.user.id;
|
||||||
|
if (!userId) {
|
||||||
|
throw new TRPCError({ code: 'UNAUTHORIZED' });
|
||||||
|
}
|
||||||
|
const store = new TournamentStore(ctx.redis, buildTournamentKeys(ctx.profile.name));
|
||||||
|
const state = await store.getState();
|
||||||
|
if (!state || state.stage !== 6) {
|
||||||
|
throw new TRPCError({ code: 'BAD_REQUEST', message: '베팅 기간이 아닙니다.' });
|
||||||
|
}
|
||||||
|
const closeAt = state.bettingCloseAt ? new Date(state.bettingCloseAt).getTime() : 0;
|
||||||
|
if (closeAt && closeAt <= Date.now()) {
|
||||||
|
throw new TRPCError({ code: 'BAD_REQUEST', message: '베팅이 마감되었습니다.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const matches = await store.getMatches();
|
||||||
|
const candidateIds = new Set<number>();
|
||||||
|
for (const match of matches) {
|
||||||
|
if (match.stage === 7) {
|
||||||
|
candidateIds.add(match.attackerId);
|
||||||
|
candidateIds.add(match.defenderId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!candidateIds.has(input.targetId)) {
|
||||||
|
throw new TRPCError({ code: 'BAD_REQUEST', message: '올바르지 않은 베팅 대상입니다.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const general = await ctx.db.general.findFirst({
|
||||||
|
where: { userId },
|
||||||
|
select: { id: true, gold: true },
|
||||||
|
});
|
||||||
|
if (!general) {
|
||||||
|
throw new TRPCError({ code: 'PRECONDITION_FAILED', message: '장수가 존재하지 않습니다.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const minRemainGold = 500;
|
||||||
|
if (general.gold - input.amount < minRemainGold) {
|
||||||
|
throw new TRPCError({ code: 'BAD_REQUEST', message: '소지금이 부족합니다.' });
|
||||||
|
}
|
||||||
|
|
||||||
|
await ctx.db.general.update({
|
||||||
|
where: { id: general.id },
|
||||||
|
data: { gold: general.gold - input.amount },
|
||||||
|
});
|
||||||
|
|
||||||
|
await store.appendBettingEntry({
|
||||||
|
generalId: general.id,
|
||||||
|
targetId: input.targetId,
|
||||||
|
amount: input.amount,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { ok: true };
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -51,4 +51,11 @@ export class TournamentStore {
|
|||||||
async setBettingEntries(entries: TournamentBetEntry[]): Promise<void> {
|
async setBettingEntries(entries: TournamentBetEntry[]): Promise<void> {
|
||||||
await this.redis.set(this.keys.bettingKey, JSON.stringify(entries));
|
await this.redis.set(this.keys.bettingKey, JSON.stringify(entries));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async appendBettingEntry(entry: TournamentBetEntry): Promise<TournamentBetEntry[]> {
|
||||||
|
const entries = await this.getBettingEntries();
|
||||||
|
entries.push(entry);
|
||||||
|
await this.setBettingEntries(entries);
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user