Refactor test database setup and add migrations
- Updated test files to use a separate test database configuration. - Introduced a new helper module for managing test database operations. - Added a setup file to configure the environment for tests. - Created new database migration scripts to optimize message indexing. - Added a sample environment file for test database configuration.
This commit is contained in:
@@ -106,6 +106,18 @@ export const pgMessagesTable = pgTable(
|
||||
table.created_at,
|
||||
table.id,
|
||||
),
|
||||
guildCreatedDeletedIdx: pgIndex("idx_messages_guild_created_deleted").on(
|
||||
table.guild_id,
|
||||
table.created_at,
|
||||
table.deleted_at,
|
||||
table.id,
|
||||
),
|
||||
channelAiStatusCreatedIdx: pgIndex(
|
||||
"idx_messages_channel_ai_status_created",
|
||||
).on(table.channel_id, table.ai_status, table.created_at, table.id),
|
||||
threadAiStatusCreatedIdx: pgIndex(
|
||||
"idx_messages_thread_ai_status_created",
|
||||
).on(table.thread_id, table.ai_status, table.created_at, table.id),
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -56,12 +56,14 @@ export async function buildConversationContext(
|
||||
0,
|
||||
);
|
||||
|
||||
const contextLines = await Promise.all(
|
||||
contextBefore.map((msg) => formatMessageForPrompt(msg, "context")),
|
||||
);
|
||||
const selectedContextLines: string[] = [];
|
||||
|
||||
// Go backwards through context, taking most recent first
|
||||
for (let i = contextBefore.length - 1; i >= 0; i--) {
|
||||
const msg = contextBefore[i];
|
||||
const line = await formatMessageForPrompt(msg, "context");
|
||||
for (let i = contextLines.length - 1; i >= 0; i--) {
|
||||
const line = contextLines[i];
|
||||
const lineTokens = estimateTokens(line);
|
||||
|
||||
if (usedTokens + lineTokens <= maxTokens) {
|
||||
|
||||
@@ -178,14 +178,27 @@ export async function captureMessage(
|
||||
// Queue analysis after attachment uploads settle so AI uses stable tele URLs.
|
||||
if (!isBacklog) {
|
||||
if (attachmentUploadTasks.length > 0) {
|
||||
setTimeout(() => queueMessageAnalysis(message.id), 30000);
|
||||
let analysisQueued = false;
|
||||
let fallbackTimer: NodeJS.Timeout | null = null;
|
||||
const queueAnalysisOnce = () => {
|
||||
if (analysisQueued) return;
|
||||
analysisQueued = true;
|
||||
if (fallbackTimer) {
|
||||
clearTimeout(fallbackTimer);
|
||||
fallbackTimer = null;
|
||||
}
|
||||
queueMessageAnalysis(message.id);
|
||||
};
|
||||
|
||||
fallbackTimer = setTimeout(queueAnalysisOnce, 30000);
|
||||
Promise.allSettled(attachmentUploadTasks)
|
||||
.then(() => queueMessageAnalysis(message.id))
|
||||
.then(queueAnalysisOnce)
|
||||
.catch((err) => {
|
||||
logger.error(
|
||||
{ messageId: message.id, error: err },
|
||||
"Failed to queue message analysis after attachment upload",
|
||||
);
|
||||
queueAnalysisOnce();
|
||||
});
|
||||
} else {
|
||||
queueMessageAnalysis(message.id);
|
||||
|
||||
@@ -48,6 +48,7 @@ interface MessageDatabase {
|
||||
selectDistinct<T = unknown[]>(...args: unknown[]): QueryBuilder<T>;
|
||||
insert<T = unknown>(...args: unknown[]): QueryBuilder<T>;
|
||||
update(...args: unknown[]): QueryBuilder<unknown>;
|
||||
transaction<T>(callback: (tx: MessageDatabase) => Promise<T>): Promise<T>;
|
||||
}
|
||||
|
||||
function db(): MessageDatabase {
|
||||
@@ -457,28 +458,28 @@ export async function updateMessagesAIAnalysisBulk(
|
||||
): Promise<MessageRecord[]> {
|
||||
if (updates.length === 0) return [];
|
||||
try {
|
||||
// Use raw SQL batch UPDATE instead of Promise.all per-message queries
|
||||
// (P2: reduce N*2 queries → 2 queries total)
|
||||
const database = db();
|
||||
const now = Date.now();
|
||||
|
||||
for (const { messageId, result } of updates) {
|
||||
await database
|
||||
.update(messagesTable)
|
||||
.set({
|
||||
ai_status: result.status,
|
||||
ai_moderation_flags: result.flags ?? null,
|
||||
ai_moderation_score: result.score ?? null,
|
||||
ai_analysis: result.analysis ?? null,
|
||||
ai_categories: stringifyAIList(result.categories),
|
||||
ai_severity: result.severity ?? null,
|
||||
ai_confidence: result.confidence ?? result.score ?? null,
|
||||
ai_recommended_action: result.recommendedAction ?? null,
|
||||
ai_analyzed_at: result.analyzedAt ?? now,
|
||||
ai_error: result.error ?? null,
|
||||
})
|
||||
.where(eq(messagesTable.id, messageId));
|
||||
}
|
||||
await database.transaction(async (tx) => {
|
||||
for (const { messageId, result } of updates) {
|
||||
await tx
|
||||
.update(messagesTable)
|
||||
.set({
|
||||
ai_status: result.status,
|
||||
ai_moderation_flags: result.flags ?? null,
|
||||
ai_moderation_score: result.score ?? null,
|
||||
ai_analysis: result.analysis ?? null,
|
||||
ai_categories: stringifyAIList(result.categories),
|
||||
ai_severity: result.severity ?? null,
|
||||
ai_confidence: result.confidence ?? result.score ?? null,
|
||||
ai_recommended_action: result.recommendedAction ?? null,
|
||||
ai_analyzed_at: result.analyzedAt ?? now,
|
||||
ai_error: result.error ?? null,
|
||||
})
|
||||
.where(eq(messagesTable.id, messageId));
|
||||
}
|
||||
});
|
||||
|
||||
// Fetch all updated messages in a single query
|
||||
const ids = updates.map(({ messageId }) => messageId);
|
||||
|
||||
@@ -38,7 +38,7 @@ export async function uploadRecordingSegment(input: {
|
||||
|
||||
try {
|
||||
// 1. Get file size and insert initial pending state to DB
|
||||
const stats = fs.statSync(oggPath);
|
||||
const stats = await fs.promises.stat(oggPath);
|
||||
await insertVoiceRecording({
|
||||
id,
|
||||
user_id: userId,
|
||||
@@ -54,7 +54,7 @@ export async function uploadRecordingSegment(input: {
|
||||
});
|
||||
|
||||
// 2. Perform async upload with retry logic
|
||||
const fileBuffer = fs.readFileSync(oggPath);
|
||||
const fileBuffer = await fs.promises.readFile(oggPath);
|
||||
const uploadResult = await uploadToTele({
|
||||
buffer: fileBuffer,
|
||||
filename: fileName,
|
||||
|
||||
Reference in New Issue
Block a user