refactor: tighten backend and logic boundaries

This commit is contained in:
2026-08-15 06:46:52 +00:00
parent 7b02ff155f
commit 35d8824b95
86 changed files with 820 additions and 1872 deletions
-7
View File
@@ -46,13 +46,6 @@ const decodeImage = (input: string): Buffer => {
return buffer;
};
const SEOUL_OFFSET_MS = 9 * 60 * 60 * 1000;
export const kstDayStart = (value: Date): Date => {
const shifted = new Date(value.getTime() + SEOUL_OFFSET_MS);
return new Date(Date.UTC(shifted.getUTCFullYear(), shifted.getUTCMonth(), shifted.getUTCDate()) - SEOUL_OFFSET_MS);
};
const assertIconChangeAvailable = (user: UserRecord, now: Date): void => {
if (
user.picture !== 'default.jpg' &&
@@ -1,6 +1,6 @@
import { randomUUID } from 'node:crypto';
import { createSimplePasswordHasher, type PasswordHasher } from './passwordHasher.js';
import { createPasswordHasher, type PasswordHasher } from './passwordHasher.js';
import type {
AdminUserListItem,
CreateUserInput,
@@ -24,7 +24,7 @@ const toAdminUserListItem = (user: UserRecord): AdminUserListItem => ({
});
// 유저 데이터 저장소를 메모리로 대체한 임시 구현.
export const createInMemoryUserRepository = (hasher: PasswordHasher = createSimplePasswordHasher()): UserRepository => {
export const createInMemoryUserRepository = (hasher: PasswordHasher = createPasswordHasher()): UserRepository => {
const usersByName = new Map<string, UserRecord>();
const usersByOauthId = new Map<string, UserRecord>();
const usersByEmail = new Map<string, UserRecord>();
@@ -6,10 +6,10 @@ import type { KakaoOAuthClient, KakaoOAuthToken, KakaoUserInfo } from './kakaoCl
import type { OAuthSessionStore } from './oauthSessionStore.js';
import type { UserOAuthInfo, UserRecord, UserRepository } from './userRepository.js';
export const KAKAO_LOGIN_SCOPES = ['account_email', 'talk_message'] as const;
export const KAKAO_OTP_TTL_SECONDS = 180;
export const KAKAO_OTP_ATTEMPTS = 3;
export const KAKAO_TALK_VERIFICATION_DAYS = 10;
const KAKAO_LOGIN_SCOPES = ['account_email', 'talk_message'] as const;
const KAKAO_OTP_TTL_SECONDS = 180;
const KAKAO_OTP_ATTEMPTS = 3;
const KAKAO_TALK_VERIFICATION_DAYS = 10;
export type KakaoVerificationErrorCode =
| 'EMAIL_REQUIRED'
@@ -97,6 +97,3 @@ export const createPasswordHasher = (options: { legacyGlobalSalt?: string } = {}
return { ok: false, needsUpgrade: false };
},
});
// 기존 import 지점을 깨지 않되 새 계정은 Argon2id를 사용한다.
export const createSimplePasswordHasher = createPasswordHasher;
@@ -1,6 +1,6 @@
import { GatewayPrisma, type GatewayPrismaClient } from '@sammo-ts/infra';
import { createSimplePasswordHasher, type PasswordHasher } from './passwordHasher.js';
import { createPasswordHasher, type PasswordHasher } from './passwordHasher.js';
import type {
AdminUserListItem,
CreateUserInput,
@@ -158,7 +158,7 @@ const mapSpecialAccessGrant = (row: {
export const createPostgresUserRepository = (
prisma: GatewayPrismaClient,
hasher: PasswordHasher = createSimplePasswordHasher()
hasher: PasswordHasher = createPasswordHasher()
): UserRepository => {
return {
async findById(id: string): Promise<UserRecord | null> {
@@ -406,7 +406,9 @@ export const createPostgresUserRepository = (
if (result.count !== 1) {
return null;
}
return mapSpecialAccessGrant(await prisma.specialAccountAccessGrant.findUniqueOrThrow({ where: { id: grantId } }));
return mapSpecialAccessGrant(
await prisma.specialAccountAccessGrant.findUniqueOrThrow({ where: { id: grantId } })
);
},
async updateIcon(userId: string, picture: string, imageServer: number, updatedAt: Date): Promise<void> {
await prisma.appUser.update({
@@ -21,7 +21,7 @@ const isWideCodePoint = (codePoint: number): boolean =>
(codePoint >= 0x1f900 && codePoint <= 0x1f9ff) ||
(codePoint >= 0x20000 && codePoint <= 0x3fffd));
export const legacyStringWidth = (value: string): number =>
const legacyStringWidth = (value: string): number =>
Array.from(value).reduce((width, character) => {
const codePoint = character.codePointAt(0) ?? 0;
return width + (isWideCodePoint(codePoint) ? 2 : 1);
+1 -2
View File
@@ -6,8 +6,7 @@ export * from './config.js';
export * from './context.js';
export * from './router.js';
export * from './server.js';
import { GatewayPrisma } from '@sammo-ts/infra';
export { GatewayPrisma };
import type { GatewayPrisma } from '@sammo-ts/infra';
export type JsonObject = GatewayPrisma.JsonObject;
export type JsonArray = GatewayPrisma.JsonArray;
export * from './orchestrator/profileRepository.js';
@@ -26,7 +26,7 @@ export interface BuildRunner {
}
export const MAX_BUILD_OUTPUT_CHARS = 64 * 1024;
export const DEFAULT_RELEASE_TURBO_CONCURRENCY = 1;
const DEFAULT_RELEASE_TURBO_CONCURRENCY = 1;
export const resolveReleaseTurboConcurrency = (env?: Record<string, string>): number => {
const configured = env?.RELEASE_TURBO_CONCURRENCY?.trim();
@@ -469,10 +469,10 @@ export const buildProcessDefinitions = (
const sanitizeArtifactName = (value: string): string => value.replace(/[^0-9A-Za-z._-]+/g, '_');
export const buildProfileFrontendOutDir = (workspaceRoot: string, profileName: string): string =>
const buildProfileFrontendOutDir = (workspaceRoot: string, profileName: string): string =>
path.join(workspaceRoot, '.release-dist', sanitizeArtifactName(profileName), 'game-frontend');
export const buildProfileFrontendCommands = (
const buildProfileFrontendCommands = (
workspaceRoot: string,
profile: Pick<GatewayProfileRecord, 'profileName' | 'profile' | 'apiPort'>,
env?: Record<string, string>
@@ -2,8 +2,7 @@ import type { GatewayPrisma, GatewayPrismaClient } from '@sammo-ts/infra';
import type { GatewayOperationStatus, GatewaySourceMode } from './profileRepository.js';
export const GATEWAY_RELEASE_OPERATION_TYPES = ['DEPLOY', 'ROLLBACK'] as const;
export type GatewayReleaseOperationType = (typeof GATEWAY_RELEASE_OPERATION_TYPES)[number];
export type GatewayReleaseOperationType = 'DEPLOY' | 'ROLLBACK';
export interface GatewayReleaseStateRecord {
id: string;
@@ -46,7 +45,7 @@ export interface GatewayReleaseOperationCreateInput {
requestedBy: string;
}
export const GATEWAY_RELEASE_LOG_LEVELS = ['INFO', 'OUTPUT', 'ERROR'] as const;
const GATEWAY_RELEASE_LOG_LEVELS = ['INFO', 'OUTPUT', 'ERROR'] as const;
export type GatewayReleaseLogLevel = (typeof GATEWAY_RELEASE_LOG_LEVELS)[number];
export interface GatewayReleaseLogRecord {
@@ -14,14 +14,11 @@ export type GatewayProfileStatus = (typeof GATEWAY_PROFILE_STATUSES)[number];
export const GATEWAY_BUILD_STATUSES = ['IDLE', 'QUEUED', 'RUNNING', 'FAILED', 'SUCCEEDED'] as const;
export type GatewayBuildStatus = (typeof GATEWAY_BUILD_STATUSES)[number];
export const GATEWAY_OPERATION_TYPES = ['RESET', 'DEPLOY', 'START', 'STOP'] as const;
export type GatewayOperationType = (typeof GATEWAY_OPERATION_TYPES)[number];
export type GatewayOperationType = 'RESET' | 'DEPLOY' | 'START' | 'STOP';
export const GATEWAY_OPERATION_STATUSES = ['QUEUED', 'RUNNING', 'SUCCEEDED', 'FAILED', 'CANCELLED'] as const;
export type GatewayOperationStatus = (typeof GATEWAY_OPERATION_STATUSES)[number];
export type GatewayOperationStatus = 'QUEUED' | 'RUNNING' | 'SUCCEEDED' | 'FAILED' | 'CANCELLED';
export const GATEWAY_SOURCE_MODES = ['BRANCH', 'COMMIT'] as const;
export type GatewaySourceMode = (typeof GATEWAY_SOURCE_MODES)[number];
export type GatewaySourceMode = 'BRANCH' | 'COMMIT';
export interface GatewayOperationRecord {
id: string;
@@ -57,7 +54,7 @@ export interface GatewayOperationCreateInput {
scheduledAt?: string;
}
export const GATEWAY_OPERATION_LOG_LEVELS = ['INFO', 'OUTPUT', 'ERROR'] as const;
const GATEWAY_OPERATION_LOG_LEVELS = ['INFO', 'OUTPUT', 'ERROR'] as const;
export type GatewayOperationLogLevel = (typeof GATEWAY_OPERATION_LOG_LEVELS)[number];
export interface GatewayOperationLogRecord {
+1 -1
View File
@@ -20,7 +20,7 @@ export const resolveGatewayProfileKoreanName = (profile: string, configuredName?
return gatewayProfileKoreanNames.get(profile) ?? profile;
};
export const compareGatewayProfiles = (
const compareGatewayProfiles = (
left: { profile: string; instanceKey: string },
right: { profile: string; instanceKey: string }
): number => {