feat: aplicar fallback de DB: parámetro → .dbInstance → getDb()

Co-authored-by: aider (openrouter/openai/gpt-5) <aider@aider.chat>
main
brobert 1 month ago
parent b0e33385b4
commit cd834552cc

@ -6,6 +6,7 @@ import { normalizeWhatsAppId, isGroupId } from '../utils/whatsapp';
import { Metrics } from './metrics';
import { TaskService } from '../tasks/service';
import { codeId, formatDDMM } from '../utils/formatting';
import { getDb } from '../db/locator';
type AdminContext = {
sender: string; // normalized user id (digits only)
@ -60,8 +61,9 @@ export class AdminService {
return [{ recipient: sender, message: '🚫 No estás autorizado para usar /admin.' }];
}
const instanceDb = ((this as any).dbInstance ?? getDb()) as Database;
// Asegurar acceso a la misma DB para AllowedGroups
try { AllowedGroups.dbInstance = this.dbInstance; } catch {}
try { AllowedGroups.dbInstance = instanceDb; } catch {}
const raw = String(ctx.message || '').trim();
const lower = raw.toLowerCase();
@ -109,18 +111,18 @@ export class AdminService {
if (!isGroupId(ctx.groupId)) {
return [{ recipient: sender, message: ' Este comando se debe usar dentro de un grupo.' }];
}
this.dbInstance.transaction(() => {
this.dbInstance.prepare(`
instanceDb.transaction(() => {
instanceDb.prepare(`
UPDATE groups
SET active = 0, archived = 1, last_verified = strftime('%Y-%m-%d %H:%M:%f','now')
WHERE id = ?
`).run(ctx.groupId);
this.dbInstance.prepare(`
instanceDb.prepare(`
UPDATE calendar_tokens
SET revoked_at = strftime('%Y-%m-%d %H:%M:%f','now')
WHERE group_id = ? AND revoked_at IS NULL
`).run(ctx.groupId);
this.dbInstance.prepare(`
instanceDb.prepare(`
UPDATE group_members
SET is_active = 0
WHERE group_id = ? AND is_active = 1
@ -136,18 +138,18 @@ export class AdminService {
if (!isGroupId(arg)) {
return [{ recipient: sender, message: '⚠️ Debes indicar un group_id válido terminado en @g.us' }];
}
this.dbInstance.transaction(() => {
this.dbInstance.prepare(`
instanceDb.transaction(() => {
instanceDb.prepare(`
UPDATE groups
SET active = 0, archived = 1, last_verified = strftime('%Y-%m-%d %H:%M:%f','now')
WHERE id = ?
`).run(arg);
this.dbInstance.prepare(`
instanceDb.prepare(`
UPDATE calendar_tokens
SET revoked_at = strftime('%Y-%m-%d %H:%M:%f','now')
WHERE group_id = ? AND revoked_at IS NULL
`).run(arg);
this.dbInstance.prepare(`
instanceDb.prepare(`
UPDATE group_members
SET is_active = 0
WHERE group_id = ? AND is_active = 1
@ -162,10 +164,10 @@ export class AdminService {
if (!isGroupId(ctx.groupId)) {
return [{ recipient: sender, message: ' Este comando se debe usar dentro de un grupo.' }];
}
this.dbInstance.transaction(() => {
this.dbInstance.prepare(`DELETE FROM tasks WHERE group_id = ?`).run(ctx.groupId);
this.dbInstance.prepare(`DELETE FROM groups WHERE id = ?`).run(ctx.groupId);
try { this.dbInstance.prepare(`DELETE FROM allowed_groups WHERE group_id = ?`).run(ctx.groupId); } catch {}
instanceDb.transaction(() => {
instanceDb.prepare(`DELETE FROM tasks WHERE group_id = ?`).run(ctx.groupId);
instanceDb.prepare(`DELETE FROM groups WHERE id = ?`).run(ctx.groupId);
try { instanceDb.prepare(`DELETE FROM allowed_groups WHERE group_id = ?`).run(ctx.groupId); } catch {}
})();
return [{ recipient: sender, message: `🗑️ Grupo borrado y datos asociados eliminados: ${ctx.groupId}` }];
}
@ -176,10 +178,10 @@ export class AdminService {
if (!isGroupId(arg)) {
return [{ recipient: sender, message: '⚠️ Debes indicar un group_id válido terminado en @g.us' }];
}
this.dbInstance.transaction(() => {
this.dbInstance.prepare(`DELETE FROM tasks WHERE group_id = ?`).run(arg);
this.dbInstance.prepare(`DELETE FROM groups WHERE id = ?`).run(arg);
try { this.dbInstance.prepare(`DELETE FROM allowed_groups WHERE group_id = ?`).run(arg); } catch {}
instanceDb.transaction(() => {
instanceDb.prepare(`DELETE FROM tasks WHERE group_id = ?`).run(arg);
instanceDb.prepare(`DELETE FROM groups WHERE id = ?`).run(arg);
try { instanceDb.prepare(`DELETE FROM allowed_groups WHERE group_id = ?`).run(arg); } catch {}
})();
return [{ recipient: sender, message: `🗑️ Grupo borrado y datos asociados eliminados: ${arg}` }];
}
@ -229,7 +231,7 @@ export class AdminService {
// /admin sync-grupos
if (rest === 'sync-grupos' || rest === 'group-sync' || rest === 'syncgroups') {
try { (GroupSyncService as any).dbInstance = this.dbInstance; } catch {}
try { (GroupSyncService as any).dbInstance = instanceDb; } catch {}
try {
const r = await GroupSyncService.syncGroups(true);
return [{ recipient: sender, message: `✅ Sync de grupos ejecutado: ${r.added} añadidos, ${r.updated} actualizados.` }];
@ -251,7 +253,7 @@ export class AdminService {
rest.startsWith('list-all ')
) {
// Asegurar acceso a la misma DB para TaskService
try { TaskService.dbInstance = this.dbInstance; } catch {}
try { TaskService.dbInstance = instanceDb; } catch {}
const DEFAULT_LIMIT = 50;
let limit = DEFAULT_LIMIT;

@ -5,6 +5,7 @@ import { AllowedGroups } from './allowed-groups';
import { Metrics } from './metrics';
import { route as routeCommand } from './commands';
import { ACTION_ALIASES } from './commands/shared';
import { getDb } from '../db/locator';
type CommandContext = {
sender: string; // normalized user id (digits only), but accept raw too
@ -41,6 +42,7 @@ export class CommandService {
static async handleWithOutcome(context: CommandContext): Promise<CommandOutcome> {
const msg = (context.message || '').trim();
const instanceDb = ((this as any).dbInstance ?? getDb()) as Database;
if (!/^\/(tarea|t)\b/i.test(msg)) {
return { responses: [], ok: true };
}
@ -49,14 +51,14 @@ export class CommandService {
try {
let usersTableExists = false;
try {
const row = this.dbInstance.query(`SELECT name FROM sqlite_master WHERE type='table' AND name='users'`).get() as { name?: string } | undefined;
const row = instanceDb.query(`SELECT name FROM sqlite_master WHERE type='table' AND name='users'`).get() as { name?: string } | undefined;
usersTableExists = !!row;
} catch {}
if (usersTableExists) {
const ensured = ensureUserExists(context.sender, this.dbInstance);
const ensured = ensureUserExists(context.sender, instanceDb);
if (ensured) {
try {
this.dbInstance.prepare(`UPDATE users SET last_command_at = strftime('%Y-%m-%d %H:%M:%f','now') WHERE id = ?`).run(ensured);
instanceDb.prepare(`UPDATE users SET last_command_at = strftime('%Y-%m-%d %H:%M:%f','now') WHERE id = ?`).run(ensured);
} catch {}
}
}
@ -64,7 +66,7 @@ export class CommandService {
// Gating de grupos en modo 'enforce' (cuando CommandService se invoca directamente)
if (isGroupId(context.groupId)) {
try { AllowedGroups.dbInstance = this.dbInstance; } catch { }
try { AllowedGroups.dbInstance = instanceDb; } catch { }
const mode = String(process.env.GROUP_GATING_MODE || 'off').toLowerCase();
if (mode === 'enforce') {
try {
@ -79,7 +81,7 @@ export class CommandService {
}
try {
const routed = await routeCommand(context, { db: this.dbInstance });
const routed = await routeCommand(context, { db: instanceDb });
const responses = routed ?? [];
// Clasificación explícita del outcome (evita lógica en server)

@ -1,5 +1,6 @@
import type { Database } from 'bun:sqlite';
import { db, ensureUserExists } from '../db';
import { ensureUserExists } from '../db';
import { getDb as getGlobalDb } from '../db/locator';
import { normalizeWhatsAppId } from '../utils/whatsapp';
import { Metrics } from './metrics';
import { IdentityService } from './identity';
@ -43,8 +44,14 @@ type EvolutionGroup = {
};
export class GroupSyncService {
// Static property for DB instance injection (defaults to global db)
static dbInstance: Database = db;
// Static property for DB instance injection (with fallback to global locator)
private static _dbInstance: Database | null = null;
static get dbInstance(): Database {
return (this._dbInstance as Database) ?? getGlobalDb();
}
static set dbInstance(value: Database) {
this._dbInstance = value;
}
// In-memory cache for active groups (made public for tests)
public static readonly activeGroupsCache = new Map<string, string>(); // groupId -> groupName

@ -1,5 +1,5 @@
import type { Database } from 'bun:sqlite';
import { db } from '../db';
import { getDb } from '../db/locator';
import { toIsoSqlUTC } from '../utils/datetime';
export class MaintenanceService {
@ -64,9 +64,10 @@ export class MaintenanceService {
}
}
static async cleanupInactiveMembersOnce(instance: Database = db, retentionDays: number = this.retentionDays): Promise<number> {
static async cleanupInactiveMembersOnce(instance?: Database, retentionDays: number = this.retentionDays): Promise<number> {
if (retentionDays <= 0) return 0;
const threshold = toIsoSqlUTC(new Date(Date.now() - retentionDays * 24 * 60 * 60 * 1000));
instance = (instance ?? (this as any).dbInstance ?? getDb()) as Database;
const res = instance.prepare(`
DELETE FROM group_members
WHERE is_active = 0
@ -81,8 +82,9 @@ export class MaintenanceService {
* en todas las tablas relevantes, basándose en user_aliases.
* Devuelve el número de alias procesados.
*/
static async reconcileAliasUsersOnce(instance: Database = db): Promise<number> {
static async reconcileAliasUsersOnce(instance?: Database): Promise<number> {
try {
instance = (instance ?? (this as any).dbInstance ?? getDb()) as Database;
const rows = instance.prepare(`SELECT alias, user_id FROM user_aliases WHERE alias != user_id`).all() as any[];
let merged = 0;

@ -8,6 +8,7 @@ import { ICONS } from '../utils/icons';
import { codeId, formatDDMM, bold, italic } from '../utils/formatting';
import { AllowedGroups } from './allowed-groups';
import { Metrics } from './metrics';
import { getDb } from '../db/locator';
type UserPreference = {
user_id: string;
@ -80,13 +81,14 @@ export class RemindersService {
}
static async runOnce(now: Date = new Date()): Promise<void> {
const instanceDb = ((this as any).dbInstance ?? getDb()) as Database;
const todayYMD = this.ymdInTZ(now);
const nowHM = this.hmInTZ(now);
const weekday = this.weekdayShortInTZ(now); // 'Mon'..'Sun'
const graceRaw = Number(process.env.REMINDERS_GRACE_MINUTES);
const GRACE_MIN = Number.isFinite(graceRaw) && graceRaw >= 0 ? Math.min(Math.floor(graceRaw), 180) : 60;
const rows = this.dbInstance.prepare(`
const rows = instanceDb.prepare(`
SELECT user_id, reminder_freq, reminder_time, last_reminded_on
FROM user_preferences
WHERE reminder_freq IN ('daily', 'weekly', 'weekdays')
@ -96,7 +98,7 @@ export class RemindersService {
const enforce = String(process.env.GROUP_GATING_MODE || 'off').toLowerCase() === 'enforce';
if (enforce) {
try {
(AllowedGroups as any).dbInstance = this.dbInstance;
(AllowedGroups as any).dbInstance = instanceDb;
// Evitar falsos positivos por caché obsoleta entre operaciones previas del test
AllowedGroups.clearCache?.();
} catch {}
@ -228,7 +230,7 @@ export class RemindersService {
}]);
// Marcar como enviado hoy
this.dbInstance.prepare(`
instanceDb.prepare(`
INSERT INTO user_preferences (user_id, reminder_freq, reminder_time, last_reminded_on, updated_at)
VALUES (?, COALESCE((SELECT reminder_freq FROM user_preferences WHERE user_id = ?), 'daily'),
COALESCE((SELECT reminder_time FROM user_preferences WHERE user_id = ?), '08:30'),

Loading…
Cancel
Save