feat: synchronize account icons across game profiles
This commit is contained in:
@@ -5,7 +5,8 @@ import { isAfter, isValid, parseISO } from 'date-fns';
|
||||
import { z } from 'zod';
|
||||
|
||||
import type { GameSessionTokenPayload } from '@sammo-ts/common/auth/gameToken';
|
||||
import { authedProcedure, procedure, router } from '../../trpc.js';
|
||||
import { authedProcedure, engineProcedure, router } from '../../trpc.js';
|
||||
import { enqueueProfileIconResetForUser } from '../../services/accountIconSync.js';
|
||||
|
||||
const parseDate = (value: string): Date | null => {
|
||||
const parsed = parseISO(value);
|
||||
@@ -21,11 +22,7 @@ const resolveTtlSeconds = (expiresAt: string): number => {
|
||||
return ttl > 0 ? ttl : 0;
|
||||
};
|
||||
|
||||
const verifyGatewayToken = (
|
||||
token: string,
|
||||
profileName: string,
|
||||
secret: string
|
||||
): GameSessionTokenPayload | null => {
|
||||
const verifyGatewayToken = (token: string, profileName: string, secret: string): GameSessionTokenPayload | null => {
|
||||
const payload = decryptGameSessionToken(token, secret);
|
||||
if (!payload) {
|
||||
return null;
|
||||
@@ -52,14 +49,10 @@ export const authRouter = router({
|
||||
}
|
||||
return { userId };
|
||||
}),
|
||||
exchangeGatewayToken: procedure
|
||||
exchangeGatewayToken: engineProcedure
|
||||
.input(z.object({ gatewayToken: z.string().min(1) }))
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const payload = verifyGatewayToken(
|
||||
input.gatewayToken,
|
||||
ctx.profile.name,
|
||||
ctx.gameTokenSecret
|
||||
);
|
||||
const payload = verifyGatewayToken(input.gatewayToken, ctx.profile.name, ctx.gameTokenSecret);
|
||||
if (!payload) {
|
||||
throw new TRPCError({
|
||||
code: 'UNAUTHORIZED',
|
||||
@@ -88,6 +81,12 @@ export const authRouter = router({
|
||||
});
|
||||
}
|
||||
|
||||
if (payload.user.profileIconResetAt && payload.user.profileIconResetAt === payload.user.iconUpdatedAt) {
|
||||
// 일반 계정 아이콘 변경은 Ref처럼 사용자가 고른 서버에만 적용한다.
|
||||
// 관리자 reset만 다음 인증 경계에서 durable하게 복구한다.
|
||||
await enqueueProfileIconResetForUser(ctx, payload.user.id, payload.user.profileIconResetAt);
|
||||
}
|
||||
|
||||
const used = await ctx.accessTokenStore.markGatewayTokenUsed(payload.sessionId, ttlSeconds);
|
||||
if (!used) {
|
||||
throw new TRPCError({
|
||||
|
||||
@@ -11,10 +11,12 @@ import {
|
||||
accessEngineAuthedProcedure,
|
||||
accessEngineAuthedInputProcedure,
|
||||
authedProcedure,
|
||||
engineAuthedProcedure,
|
||||
router,
|
||||
} from '../../trpc.js';
|
||||
import { ConflictingTurnDaemonCommandError } from '../../daemon/databaseTransport.js';
|
||||
import { resolveAccessWindows } from '../../services/generalAccess.js';
|
||||
import { adjustAccountIconForUser } from '../../services/accountIconSync.js';
|
||||
import { getMyGeneral } from '../shared/general.js';
|
||||
import { resolveNationNotice } from '../nation/shared.js';
|
||||
|
||||
@@ -169,6 +171,13 @@ const resolvePenalty = (penalty: unknown): Record<string, number> => {
|
||||
};
|
||||
|
||||
export const generalRouter = router({
|
||||
adjustIcon: engineAuthedProcedure.mutation(({ ctx }) => {
|
||||
const userId = ctx.auth?.user.id;
|
||||
if (!userId) {
|
||||
throw new TRPCError({ code: 'UNAUTHORIZED' });
|
||||
}
|
||||
return adjustAccountIconForUser(ctx, userId);
|
||||
}),
|
||||
me: authedProcedure.query(async ({ ctx }) => {
|
||||
const userId = ctx.auth?.user.id;
|
||||
if (!userId) {
|
||||
@@ -326,12 +335,15 @@ export const generalRouter = router({
|
||||
availableAt: result.availableAt ?? null,
|
||||
};
|
||||
}),
|
||||
dieOnPrestart: accessEngineAuthedInputProcedure(zImmediateActionInput)
|
||||
.mutation(({ ctx, input }) => requestImmediateAction(ctx, input, 'dieOnPrestart')),
|
||||
buildNationCandidate: accessEngineAuthedInputProcedure(zImmediateActionInput)
|
||||
.mutation(({ ctx, input }) => requestImmediateAction(ctx, input, 'buildNationCandidate')),
|
||||
instantRetreat: accessEngineAuthedInputProcedure(zImmediateActionInput)
|
||||
.mutation(({ ctx, input }) => requestImmediateAction(ctx, input, 'instantRetreat')),
|
||||
dieOnPrestart: accessEngineAuthedInputProcedure(zImmediateActionInput).mutation(({ ctx, input }) =>
|
||||
requestImmediateAction(ctx, input, 'dieOnPrestart')
|
||||
),
|
||||
buildNationCandidate: accessEngineAuthedInputProcedure(zImmediateActionInput).mutation(({ ctx, input }) =>
|
||||
requestImmediateAction(ctx, input, 'buildNationCandidate')
|
||||
),
|
||||
instantRetreat: accessEngineAuthedInputProcedure(zImmediateActionInput).mutation(({ ctx, input }) =>
|
||||
requestImmediateAction(ctx, input, 'instantRetreat')
|
||||
),
|
||||
vacation: authedProcedure.mutation(async ({ ctx }) => {
|
||||
const general = await getMyGeneral(ctx);
|
||||
const result = await ctx.turnDaemon.requestCommand({
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
WAR_TRAIT_KEYS,
|
||||
} from '@sammo-ts/logic';
|
||||
import { readInheritancePoint, resolveInheritConstants } from '../../services/inheritance.js';
|
||||
import { loadAuthoritativeAccountIcon } from '../../services/accountIconSync.js';
|
||||
import { getSelectionPoolStatus, reserveSelectionPool, resolveSelectionMaxGeneral } from '../../services/selectPool.js';
|
||||
import {
|
||||
ConflictingTurnDaemonCommandError,
|
||||
@@ -465,6 +466,7 @@ export const joinRouter = router({
|
||||
});
|
||||
}
|
||||
const userId = auth.user.id;
|
||||
const accountIcon = input.pic ? await loadAuthoritativeAccountIcon(ctx, userId) : null;
|
||||
const commandRequestId = resolveJoinCreateRequestId(ctx.requestId, userId, input.clientRequestId);
|
||||
const result = await requestJoinCreateCommand(ctx, {
|
||||
type: 'joinCreateGeneral',
|
||||
@@ -479,8 +481,13 @@ export const joinRouter = router({
|
||||
pic: input.pic,
|
||||
character: input.character,
|
||||
profileId: ctx.profile.id,
|
||||
...(auth.user.picture !== undefined ? { ownerPicture: auth.user.picture } : {}),
|
||||
...(auth.user.imageServer !== undefined ? { ownerImageServer: auth.user.imageServer } : {}),
|
||||
...(accountIcon
|
||||
? {
|
||||
ownerPicture: accountIcon.picture,
|
||||
ownerImageServer: accountIcon.imageServer,
|
||||
ownerIconRevision: accountIcon.revision,
|
||||
}
|
||||
: {}),
|
||||
...(auth.user.canUseGeneralPicture !== undefined
|
||||
? {
|
||||
ownerCanUsePicture: auth.user.canUseGeneralPicture,
|
||||
|
||||
Reference in New Issue
Block a user