237 lines
7.9 KiB
TypeScript
237 lines
7.9 KiB
TypeScript
import { pathToFileURL } from 'node:url';
|
|
|
|
import { createApp } from './app.js';
|
|
import { type AppConfig, loadConfig } from './config.js';
|
|
import type { AppContext } from './context.js';
|
|
import { createDatabase } from './db.js';
|
|
import { createLogger } from './logging.js';
|
|
import { AuthAuditLogRepository } from './repositories/authAuditLogRepository.js';
|
|
import { AuthIdentityRepository } from './repositories/authIdentityRepository.js';
|
|
import { AuthRiskBlockRepository } from './repositories/authRiskBlockRepository.js';
|
|
import { RpgAgentSessionRepository } from './repositories/RpgAgentSessionRepository.js';
|
|
import { RpgWorldProfileRepository } from './repositories/RpgWorldProfileRepository.js';
|
|
import { RpgSaveArchiveRepository } from './repositories/rpg-entry/RpgSaveArchiveRepository.js';
|
|
import { RpgWorldLibraryRepository } from './repositories/rpg-entry/RpgWorldLibraryRepository.js';
|
|
import { RpgBrowseHistoryRepository } from './repositories/rpg-profile/RpgBrowseHistoryRepository.js';
|
|
import { RpgProfileDashboardRepository } from './repositories/rpg-profile/RpgProfileDashboardRepository.js';
|
|
import { RpgRuntimeSnapshotRepository } from './repositories/rpg-runtime/RpgRuntimeSnapshotRepository.js';
|
|
import { RuntimeRepository } from './repositories/runtimeRepository.js';
|
|
import { SmsAuthEventRepository } from './repositories/smsAuthEventRepository.js';
|
|
import { UserRepository } from './repositories/userRepository.js';
|
|
import { UserSessionRepository } from './repositories/userSessionRepository.js';
|
|
import { CaptchaChallengeStore } from './services/captchaChallengeStore.js';
|
|
import { CustomWorldAgentAutoAssetService } from './services/customWorldAgentAutoAssetService.js';
|
|
import { CustomWorldAgentOrchestrator } from './services/customWorldAgentOrchestrator.js';
|
|
import { CustomWorldAgentSessionStore } from './services/customWorldAgentSessionStore.js';
|
|
import { UpstreamLlmClient } from './services/llmClient.js';
|
|
import { RpgWorldWorkSummaryService } from './services/RpgWorldWorkSummaryService.js';
|
|
import { createSmsVerificationService } from './services/smsVerificationService.js';
|
|
import { createWechatAuthService } from './services/wechatAuthService.js';
|
|
import { WechatAuthStateStore } from './services/wechatAuthStateStore.js';
|
|
|
|
function resolveListenTarget(serverAddr: string) {
|
|
const trimmed = serverAddr.trim();
|
|
if (!trimmed) {
|
|
return { host: '0.0.0.0', port: 8081 };
|
|
}
|
|
if (trimmed.startsWith(':')) {
|
|
return {
|
|
host: '0.0.0.0',
|
|
port: Number(trimmed.slice(1)),
|
|
};
|
|
}
|
|
if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) {
|
|
const url = new URL(trimmed);
|
|
return {
|
|
host: url.hostname,
|
|
port: Number(url.port || 80),
|
|
};
|
|
}
|
|
if (trimmed.includes(':')) {
|
|
const [host, portText] = trimmed.split(':');
|
|
return {
|
|
host: host || '0.0.0.0',
|
|
port: Number(portText),
|
|
};
|
|
}
|
|
return {
|
|
host: '0.0.0.0',
|
|
port: Number(trimmed),
|
|
};
|
|
}
|
|
|
|
function describeDatabase(databaseUrl: string) {
|
|
if (databaseUrl.startsWith('pg-mem://')) {
|
|
return {
|
|
database_engine: 'pg-mem',
|
|
database_name: databaseUrl.slice('pg-mem://'.length) || 'memory',
|
|
};
|
|
}
|
|
|
|
try {
|
|
const url = new URL(databaseUrl);
|
|
return {
|
|
database_engine: url.protocol.replace(/:$/u, ''),
|
|
database_host: url.hostname,
|
|
database_port: Number(url.port || 5432),
|
|
database_name: url.pathname.replace(/^\/+/u, '') || 'postgres',
|
|
};
|
|
} catch {
|
|
return {
|
|
database_engine: 'postgresql',
|
|
database_target: 'configured',
|
|
};
|
|
}
|
|
}
|
|
|
|
export async function createAppContext(config: AppConfig = loadConfig()) {
|
|
const logger = createLogger(config);
|
|
const db = await createDatabase(config);
|
|
const rpgAgentSessionRepository = new RpgAgentSessionRepository(db);
|
|
const rpgWorldProfileRepository = new RpgWorldProfileRepository(db);
|
|
const runtimeRepository = new RuntimeRepository(db);
|
|
const rpgProfileDashboardRepository = new RpgProfileDashboardRepository(
|
|
runtimeRepository,
|
|
);
|
|
const rpgBrowseHistoryRepository = new RpgBrowseHistoryRepository(
|
|
runtimeRepository,
|
|
);
|
|
const rpgSaveArchiveRepository = new RpgSaveArchiveRepository(
|
|
runtimeRepository,
|
|
);
|
|
const rpgWorldLibraryRepository = new RpgWorldLibraryRepository(
|
|
runtimeRepository,
|
|
);
|
|
const rpgRuntimeSnapshotRepository = new RpgRuntimeSnapshotRepository(
|
|
runtimeRepository,
|
|
);
|
|
const userRepository = new UserRepository(db);
|
|
const customWorldAgentSessions = new CustomWorldAgentSessionStore(
|
|
rpgAgentSessionRepository,
|
|
);
|
|
const rpgWorldWorkSummaryService = new RpgWorldWorkSummaryService(
|
|
rpgWorldProfileRepository,
|
|
customWorldAgentSessions,
|
|
);
|
|
const autoAssetService = new CustomWorldAgentAutoAssetService(
|
|
config,
|
|
config.dashScope.apiKey.trim()
|
|
? CustomWorldAgentAutoAssetService.createDashScopeCharacterVisualGenerator(
|
|
config,
|
|
)
|
|
: CustomWorldAgentAutoAssetService.createFallbackCharacterVisualGenerator(
|
|
config,
|
|
),
|
|
config.dashScope.apiKey.trim()
|
|
? CustomWorldAgentAutoAssetService.createDashScopeSceneActBackgroundGenerator(
|
|
config,
|
|
)
|
|
: CustomWorldAgentAutoAssetService.createFallbackSceneActBackgroundGenerator(
|
|
config,
|
|
),
|
|
);
|
|
const context: AppContext = {
|
|
config,
|
|
logger,
|
|
db,
|
|
userRepository,
|
|
authIdentityRepository: new AuthIdentityRepository(db),
|
|
authAuditLogRepository: new AuthAuditLogRepository(db),
|
|
authRiskBlockRepository: new AuthRiskBlockRepository(db),
|
|
smsAuthEventRepository: new SmsAuthEventRepository(db),
|
|
userSessionRepository: new UserSessionRepository(db),
|
|
rpgAgentSessionRepository,
|
|
rpgWorldProfileRepository,
|
|
rpgProfileDashboardRepository,
|
|
rpgBrowseHistoryRepository,
|
|
rpgSaveArchiveRepository,
|
|
rpgWorldLibraryRepository,
|
|
rpgRuntimeSnapshotRepository,
|
|
runtimeRepository,
|
|
llmClient: new UpstreamLlmClient(config, logger),
|
|
customWorldAgentSessions,
|
|
customWorldAgentOrchestrator: new CustomWorldAgentOrchestrator(
|
|
customWorldAgentSessions,
|
|
config.llm.apiKey.trim()
|
|
? new UpstreamLlmClient(config, logger)
|
|
: null,
|
|
{
|
|
autoAssetService,
|
|
rpgWorldProfileRepository,
|
|
userRepository,
|
|
},
|
|
),
|
|
rpgWorldWorkSummaryService,
|
|
smsVerificationService: createSmsVerificationService(config, logger),
|
|
wechatAuthService: createWechatAuthService(config, logger),
|
|
wechatAuthStates: new WechatAuthStateStore(),
|
|
captchaChallenges: new CaptchaChallengeStore(),
|
|
};
|
|
|
|
return context;
|
|
}
|
|
|
|
async function main() {
|
|
const context = await createAppContext();
|
|
const app = createApp(context);
|
|
const { host, port } = resolveListenTarget(context.config.serverAddr);
|
|
const server = app.listen(port, host, () => {
|
|
context.logger.info(
|
|
{
|
|
host,
|
|
port,
|
|
...describeDatabase(context.config.databaseUrl),
|
|
},
|
|
'server-node started',
|
|
);
|
|
});
|
|
|
|
let shuttingDown = false;
|
|
const shutdown = () => {
|
|
if (shuttingDown) {
|
|
return;
|
|
}
|
|
shuttingDown = true;
|
|
context.logger.info('server-node shutting down');
|
|
server.close(() => {
|
|
void context.db
|
|
.close()
|
|
.then(() => {
|
|
process.exit(0);
|
|
})
|
|
.catch((error) => {
|
|
context.logger.error({ err: error }, 'failed to close database');
|
|
process.exit(1);
|
|
});
|
|
});
|
|
};
|
|
|
|
process.on('SIGINT', shutdown);
|
|
process.on('SIGTERM', shutdown);
|
|
}
|
|
|
|
function isEntryModule() {
|
|
if (typeof process.argv[1] !== 'string') {
|
|
return false;
|
|
}
|
|
|
|
const entryHref = pathToFileURL(process.argv[1]).href;
|
|
if (typeof import.meta.url === 'string' && import.meta.url === entryHref) {
|
|
return true;
|
|
}
|
|
|
|
return (
|
|
typeof __filename === 'string' &&
|
|
pathToFileURL(__filename).href === entryHref
|
|
);
|
|
}
|
|
|
|
const isEntryPoint = isEntryModule();
|
|
|
|
if (isEntryPoint) {
|
|
void main().catch((error) => {
|
|
console.error(error);
|
|
process.exit(1);
|
|
});
|
|
}
|