feat: add profile status service and integrate it into the gateway API context and router
This commit is contained in:
@@ -5,6 +5,7 @@ import type { KakaoOAuthClient } from './auth/kakaoClient.js';
|
||||
import type { OAuthSessionStore } from './auth/oauthSessionStore.js';
|
||||
import type { GatewayProfileRepository } from './orchestrator/profileRepository.js';
|
||||
import type { GatewayOrchestratorHandle } from './orchestrator/gatewayOrchestrator.js';
|
||||
import type { GatewayProfileStatusService } from './lobby/profileStatusService.js';
|
||||
|
||||
export interface GatewayApiContext {
|
||||
users: UserRepository;
|
||||
@@ -17,6 +18,7 @@ export interface GatewayApiContext {
|
||||
publicBaseUrl: string;
|
||||
profiles: GatewayProfileRepository;
|
||||
orchestrator: GatewayOrchestratorHandle;
|
||||
profileStatus: GatewayProfileStatusService;
|
||||
adminToken?: string;
|
||||
requestHeaders: Record<string, string | string[] | undefined>;
|
||||
}
|
||||
@@ -32,6 +34,7 @@ export const createGatewayApiContext = (options: {
|
||||
publicBaseUrl: string;
|
||||
profiles: GatewayProfileRepository;
|
||||
orchestrator: GatewayOrchestratorHandle;
|
||||
profileStatus: GatewayProfileStatusService;
|
||||
adminToken?: string;
|
||||
requestHeaders?: Record<string, string | string[] | undefined>;
|
||||
}): GatewayApiContext => ({
|
||||
@@ -45,6 +48,7 @@ export const createGatewayApiContext = (options: {
|
||||
publicBaseUrl: options.publicBaseUrl,
|
||||
profiles: options.profiles,
|
||||
orchestrator: options.orchestrator,
|
||||
profileStatus: options.profileStatus,
|
||||
adminToken: options.adminToken,
|
||||
requestHeaders: options.requestHeaders ?? {},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
import type { GatewayOrchestratorHandle } from '../orchestrator/gatewayOrchestrator.js';
|
||||
import type {
|
||||
GatewayProfileRecord,
|
||||
GatewayProfileRepository,
|
||||
GatewayProfileStatus,
|
||||
} from '../orchestrator/profileRepository.js';
|
||||
|
||||
export type LobbyMapSnapshot = {
|
||||
updatedAt: string | null;
|
||||
summary?: string | null;
|
||||
};
|
||||
|
||||
export type LobbyGeneralStatus = {
|
||||
exists: boolean;
|
||||
cityId: number | null;
|
||||
cityName: string | null;
|
||||
updatedAt: string | null;
|
||||
};
|
||||
|
||||
export type LobbyProfileStatus = {
|
||||
profileName: string;
|
||||
profile: string;
|
||||
scenario: string;
|
||||
status: GatewayProfileStatus;
|
||||
runtime: {
|
||||
apiRunning: boolean;
|
||||
daemonRunning: boolean;
|
||||
};
|
||||
map: LobbyMapSnapshot;
|
||||
myGeneral: LobbyGeneralStatus;
|
||||
};
|
||||
|
||||
export interface GatewayProfileStatusService {
|
||||
listLobbyProfiles(input: { userId?: string } | undefined): Promise<LobbyProfileStatus[]>;
|
||||
}
|
||||
|
||||
// 로비에서 사용할 프로필 상태를 메모리에서 반환하는 구현체.
|
||||
export class InMemoryProfileStatusService implements GatewayProfileStatusService {
|
||||
private profiles: LobbyProfileStatus[];
|
||||
|
||||
constructor(initial: LobbyProfileStatus[] = []) {
|
||||
this.profiles = [...initial];
|
||||
}
|
||||
|
||||
async listLobbyProfiles(): Promise<LobbyProfileStatus[]> {
|
||||
return this.profiles;
|
||||
}
|
||||
|
||||
setProfiles(profiles: LobbyProfileStatus[]): void {
|
||||
this.profiles = [...profiles];
|
||||
}
|
||||
}
|
||||
|
||||
// 프로필 저장소/오케스트레이터를 이용해 로비 상태를 구성하는 기본 구현체.
|
||||
export class RepositoryProfileStatusService implements GatewayProfileStatusService {
|
||||
constructor(
|
||||
private readonly profiles: GatewayProfileRepository,
|
||||
private readonly orchestrator: GatewayOrchestratorHandle
|
||||
) {}
|
||||
|
||||
async listLobbyProfiles(): Promise<LobbyProfileStatus[]> {
|
||||
const rows = await this.profiles.listProfiles();
|
||||
const runtimeStates = await this.orchestrator.listRuntimeStates(
|
||||
rows.map((profile) => profile.profileName)
|
||||
);
|
||||
const runtimeMap = new Map(
|
||||
runtimeStates.map((state) => [state.profileName, state])
|
||||
);
|
||||
return rows.map((row) => this.mapProfile(row, runtimeMap));
|
||||
}
|
||||
|
||||
private mapProfile(
|
||||
row: GatewayProfileRecord,
|
||||
runtimeMap: Map<string, { apiRunning: boolean; daemonRunning: boolean }>
|
||||
): LobbyProfileStatus {
|
||||
return {
|
||||
profileName: row.profileName,
|
||||
profile: row.profile,
|
||||
scenario: row.scenario,
|
||||
status: row.status,
|
||||
runtime: runtimeMap.get(row.profileName) ?? {
|
||||
apiRunning: false,
|
||||
daemonRunning: false,
|
||||
},
|
||||
map: {
|
||||
updatedAt: null,
|
||||
summary: null,
|
||||
},
|
||||
myGeneral: {
|
||||
exists: false,
|
||||
cityId: null,
|
||||
cityName: null,
|
||||
updatedAt: null,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,23 @@ export const appRouter = router({
|
||||
now: new Date().toISOString(),
|
||||
})),
|
||||
}),
|
||||
lobby: router({
|
||||
profiles: procedure
|
||||
.input(
|
||||
z.object({
|
||||
sessionToken: z.string().min(1).optional(),
|
||||
}).optional()
|
||||
)
|
||||
.query(async ({ ctx, input }) => {
|
||||
const sessionToken = input?.sessionToken;
|
||||
const session = sessionToken
|
||||
? await ctx.sessions.getSession(sessionToken)
|
||||
: null;
|
||||
return ctx.profileStatus.listLobbyProfiles({
|
||||
userId: session?.userId,
|
||||
});
|
||||
}),
|
||||
}),
|
||||
admin: adminRouter,
|
||||
auth: router({
|
||||
kakaoStart: procedure
|
||||
|
||||
@@ -18,6 +18,7 @@ import { createPostgresUserRepository } from './auth/postgresUserRepository.js';
|
||||
import { RedisGatewaySessionService } from './auth/redisSessionService.js';
|
||||
import { createGatewayOrchestrator } from './orchestrator/orchestratorFactory.js';
|
||||
import { appRouter } from './router.js';
|
||||
import { RepositoryProfileStatusService } from './lobby/profileStatusService.js';
|
||||
|
||||
export const createGatewayApiServer = async () => {
|
||||
const config = resolveGatewayApiConfigFromEnv();
|
||||
@@ -51,6 +52,7 @@ export const createGatewayApiServer = async () => {
|
||||
config,
|
||||
process.env
|
||||
);
|
||||
const profileStatus = new RepositoryProfileStatusService(profiles, orchestrator);
|
||||
|
||||
const app = fastify({
|
||||
logger: true,
|
||||
@@ -77,6 +79,7 @@ export const createGatewayApiServer = async () => {
|
||||
publicBaseUrl: config.publicBaseUrl,
|
||||
profiles,
|
||||
orchestrator,
|
||||
profileStatus,
|
||||
adminToken: config.adminToken,
|
||||
requestHeaders: req.headers,
|
||||
}),
|
||||
|
||||
@@ -4,6 +4,7 @@ import { InMemoryGatewaySessionService } from '../src/auth/inMemorySessionServic
|
||||
import { createInMemoryUserRepository } from '../src/auth/inMemoryUserRepository.js';
|
||||
import { InMemoryOAuthSessionStore } from '../src/auth/oauthSessionStore.js';
|
||||
import { createGatewayApiContext } from '../src/context.js';
|
||||
import { InMemoryProfileStatusService } from '../src/lobby/profileStatusService.js';
|
||||
import { appRouter } from '../src/router.js';
|
||||
|
||||
const buildCaller = () => {
|
||||
@@ -62,6 +63,7 @@ const buildCaller = () => {
|
||||
}),
|
||||
listRuntimeStates: async () => [],
|
||||
};
|
||||
const profileStatus = new InMemoryProfileStatusService();
|
||||
const caller = appRouter.createCaller(
|
||||
createGatewayApiContext({
|
||||
users,
|
||||
@@ -74,6 +76,7 @@ const buildCaller = () => {
|
||||
publicBaseUrl: 'http://localhost',
|
||||
profiles,
|
||||
orchestrator,
|
||||
profileStatus,
|
||||
requestHeaders: {},
|
||||
})
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user