fix vibe engine audit findings: pg.Pool, plan replan, dead exclusions, legacy engine removal
Backend: - app.ts: switch shared pg.Client to pg.Pool with per-transaction clients (#205) - v2.routes.ts: replace plan instead of appending on replan, fixing self-duplication (#206) - session-director: populate recentExclusions, per-candidate ranking, batch repetition checks (#209/#211/#213/#215 + minor) - db.service.ts: claim-fusion watermark, legacy recommendation_batch engine removed (#216/#219/#232) - app.ts: drop test enqueue-job endpoint (#234) Frontend: - AudioEngine/Vibe/usePlaybackStore: dedupe completed feedback, gate feedback to vibe sessions, End Vibe stops playback, Keep toast, shuffle played-set (#207/#236/#237/#238/#239/#240) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+15
-28
@@ -1,6 +1,6 @@
|
||||
import Fastify from 'fastify';
|
||||
import cors from '@fastify/cors';
|
||||
import { Client as PgClient } from 'pg';
|
||||
import { Pool } from 'pg';
|
||||
import { createClient as createRedisClient } from 'redis';
|
||||
import { DbService } from './services/db.service.js';
|
||||
import { JobService } from './services/job.service.js';
|
||||
@@ -8,7 +8,6 @@ import { SearchService } from './services/search.service.js';
|
||||
import libraryRoutes from './routes/library.routes.js';
|
||||
import searchRoutes from './routes/search.routes.js';
|
||||
import adminRoutes from './routes/admin.routes.js';
|
||||
import vibeRoutes from './routes/vibe.routes.js';
|
||||
import historyRoutes from './routes/history.routes.js';
|
||||
import streamRoutes from './routes/stream.routes.js';
|
||||
import quarantineRoutes from './routes/quarantine.routes.js';
|
||||
@@ -33,10 +32,14 @@ export async function buildApp(config: AppConfig) {
|
||||
origin: process.env.CORS_ORIGIN?.split(',') || ['http://localhost:5173', 'http://localhost:5174'],
|
||||
});
|
||||
|
||||
const pgClient = new PgClient({
|
||||
// A Pool (not a single Client) so concurrent requests get independent
|
||||
// connections. Critical for correctness: recordPlay/recordSkip run
|
||||
// BEGIN...COMMIT transactions, and other requests' queries must not be
|
||||
// able to land inside another request's open transaction on a shared
|
||||
// connection (see db.service.ts withTransaction()).
|
||||
const pgPool = new Pool({
|
||||
connectionString: process.env.DATABASE_URL,
|
||||
});
|
||||
await pgClient.connect();
|
||||
|
||||
const redisClient = createRedisClient({
|
||||
url: process.env.REDIS_URL,
|
||||
@@ -50,7 +53,7 @@ export async function buildApp(config: AppConfig) {
|
||||
protocol: 'http',
|
||||
apiKey: config.searchApiKey,
|
||||
});
|
||||
const dbService = new DbService(pgClient, searchService);
|
||||
const dbService = new DbService(pgPool, searchService);
|
||||
|
||||
// Apply the idempotent schema on boot so tables added after the initial DB
|
||||
// volume was created (e.g. play_history, feedback) exist. The init-time
|
||||
@@ -118,7 +121,7 @@ export async function buildApp(config: AppConfig) {
|
||||
};
|
||||
|
||||
try {
|
||||
await pgClient.query('SELECT 1');
|
||||
await pgPool.query('SELECT 1');
|
||||
status.postgres = 'ok';
|
||||
} catch (err) {
|
||||
status.postgres = 'error';
|
||||
@@ -136,7 +139,7 @@ export async function buildApp(config: AppConfig) {
|
||||
}
|
||||
|
||||
const isHealthy = status.postgres === 'ok' && status.redis === 'ok';
|
||||
|
||||
|
||||
if (isHealthy) {
|
||||
return reply.code(200).send(status);
|
||||
} else {
|
||||
@@ -148,7 +151,6 @@ export async function buildApp(config: AppConfig) {
|
||||
fastify.register(libraryRoutes, { prefix: '/api', dbService });
|
||||
fastify.register(searchRoutes, { prefix: '/api', dbService });
|
||||
fastify.register(adminRoutes, { prefix: '/api/admin', jobService, dbService });
|
||||
fastify.register(vibeRoutes, { prefix: '/api/vibe', dbService });
|
||||
fastify.register(historyRoutes, { prefix: '/api', dbService });
|
||||
fastify.register(streamRoutes, { prefix: '/api', dbService });
|
||||
fastify.register(quarantineRoutes, { prefix: '/api', dbService });
|
||||
@@ -159,24 +161,9 @@ export async function buildApp(config: AppConfig) {
|
||||
|
||||
fastify.register(v2Routes, { prefix: '/api', dbService, sessionDirector });
|
||||
fastify.register(discoveryRoutes, { prefix: '/api', dbService });
|
||||
fastify.post('/api/test/enqueue-job', async (request, reply) => {
|
||||
const { jobType, trackId, payload } = request.body as any;
|
||||
try {
|
||||
if (jobType === 'metadataRefresh') {
|
||||
await jobService.enqueueMetadataRefresh(trackId, payload.type);
|
||||
} else if (jobType === 'audioAnalysis') {
|
||||
await jobService.enqueueAudioAnalysis(trackId, payload.features);
|
||||
} else if (jobType === 'cleanup') {
|
||||
await jobService.enqueueCleanup(payload.reason, payload.targetFiles);
|
||||
} else {
|
||||
return reply.code(400).send({ error: 'Unknown job type' });
|
||||
}
|
||||
await reply.send({ message: 'Job enqueued' });
|
||||
} catch (error) {
|
||||
request.log.error(error);
|
||||
await reply.status(500).send({ error: 'Internal server error' });
|
||||
}
|
||||
});
|
||||
// ponytail: /api/test/enqueue-job (manual job-enqueue test endpoint) removed —
|
||||
// nothing in the deployed app or its tests called it, and deployment never
|
||||
// sets NODE_ENV so an env gate would've stayed live in prod anyway.
|
||||
|
||||
// Register hooks to close connections on shutdown
|
||||
fastify.addHook('onClose', async () => {
|
||||
@@ -188,7 +175,7 @@ export async function buildApp(config: AppConfig) {
|
||||
fastify.log.error(err);
|
||||
}
|
||||
try {
|
||||
await pgClient.end();
|
||||
await pgPool.end();
|
||||
} catch (err) {
|
||||
fastify.log.error(err);
|
||||
}
|
||||
@@ -204,5 +191,5 @@ export async function buildApp(config: AppConfig) {
|
||||
}
|
||||
});
|
||||
|
||||
return { fastify, pgClient, redisClient };
|
||||
return { fastify, pgPool, redisClient };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user