From 1cd11e8f9afeddb953f9482f1f372c96f9c94aa3 Mon Sep 17 00:00:00 2001 From: shni Date: Sun, 28 Sep 2025 01:09:26 -0500 Subject: [PATCH] feat: add reminders schema management for Appwrite integration --- src/core/api/reminders.ts | 13 ++++++++ src/core/api/remindersSchema.ts | 56 +++++++++++++++++++++++++++++++++ src/main.ts | 4 +++ 3 files changed, 73 insertions(+) create mode 100644 src/core/api/remindersSchema.ts diff --git a/src/core/api/reminders.ts b/src/core/api/reminders.ts index 6dbe289..a0ecc10 100644 --- a/src/core/api/reminders.ts +++ b/src/core/api/reminders.ts @@ -2,6 +2,7 @@ const sdk: any = require('node-appwrite'); import type Amayo from '../client'; import { getDatabases, isAppwriteConfigured, APPWRITE_COLLECTION_REMINDERS_ID, APPWRITE_DATABASE_ID } from './appwrite'; +import { ensureRemindersSchema } from './remindersSchema'; export type ReminderDoc = { $id?: string; @@ -23,9 +24,20 @@ export type ReminderRow = ReminderDoc & { $databaseId?: string; }; +let schemaEnsured = false; +async function ensureSchemaOnce() { + if (schemaEnsured) return; + try { + await ensureRemindersSchema(); + } finally { + schemaEnsured = true; + } +} + export async function scheduleReminder(doc: ReminderDoc): Promise { const db = getDatabases(); if (!db || !isAppwriteConfigured()) throw new Error('Appwrite no está configurado'); + await ensureSchemaOnce(); const data = { userId: doc.userId, guildId: doc.guildId ?? null, @@ -41,6 +53,7 @@ export async function scheduleReminder(doc: ReminderDoc): Promise { async function fetchDueReminders(limit = 25): Promise { const db = getDatabases(); if (!db || !isAppwriteConfigured()) return []; + try { await ensureSchemaOnce(); } catch {} const nowIso = new Date().toISOString(); try { const list = await db.listDocuments(APPWRITE_DATABASE_ID, APPWRITE_COLLECTION_REMINDERS_ID, [ diff --git a/src/core/api/remindersSchema.ts b/src/core/api/remindersSchema.ts new file mode 100644 index 0000000..8092c11 --- /dev/null +++ b/src/core/api/remindersSchema.ts @@ -0,0 +1,56 @@ +// eslint-disable-next-line @typescript-eslint/no-var-requires +const sdk: any = require('node-appwrite'); +import { getDatabases, isAppwriteConfigured, APPWRITE_COLLECTION_REMINDERS_ID, APPWRITE_DATABASE_ID } from './appwrite'; + +export async function ensureRemindersSchema() { + if (!isAppwriteConfigured()) return; + const db = getDatabases(); + if (!db) return; + + const databaseId = APPWRITE_DATABASE_ID; + const collectionId = APPWRITE_COLLECTION_REMINDERS_ID; + + // 1) Asegurar colección + try { + await db.getCollection(databaseId, collectionId); + } catch { + try { + await db.createCollection(databaseId, collectionId, collectionId, [ + // Permisos por defecto: accesible solo por server via API Key + ]); + // Nota: No añadimos permisos de lectura pública para evitar fuga de datos + } catch (e) { + console.warn('No se pudo crear la colección de recordatorios (puede existir ya):', e); + } + } + + // 2) Asegurar atributos requeridos + const createIfMissing = async (fn: () => Promise) => { + try { await fn(); } catch (e: any) { + const msg = String(e?.message || e); + if (!/already exists|attribute_already_exists/i.test(msg)) { + // Otros errores se muestran + console.warn('No se pudo crear atributo:', msg); + } + } + }; + + await createIfMissing(() => db.createStringAttribute(databaseId, collectionId, 'userId', 64, true)); + await createIfMissing(() => db.createStringAttribute(databaseId, collectionId, 'guildId', 64, false)); + await createIfMissing(() => db.createStringAttribute(databaseId, collectionId, 'channelId', 64, false)); + await createIfMissing(() => db.createStringAttribute(databaseId, collectionId, 'message', 2048, true)); + await createIfMissing(() => db.createDatetimeAttribute(databaseId, collectionId, 'executeAt', true)); + await createIfMissing(() => db.createDatetimeAttribute(databaseId, collectionId, 'createdAt', true)); + + // 3) Índice por executeAt para consultas por vencimiento + try { + //@ts-ignore + await db.createIndex(databaseId, collectionId, 'idx_executeAt_asc', 'key', ['executeAt'], ['ASC']); + } catch (e: any) { + const msg = String(e?.message || e); + if (!/already exists|index_already_exists/i.test(msg)) { + console.warn('No se pudo crear índice executeAt:', msg); + } + } +} + diff --git a/src/main.ts b/src/main.ts index 0b3e87b..03b75e1 100644 --- a/src/main.ts +++ b/src/main.ts @@ -7,6 +7,7 @@ import {loadComponents} from "./core/lib/components"; import { startMemoryMonitor } from "./core/memory/memoryMonitor"; import {memoryOptimizer} from "./core/memory/memoryOptimizer"; import { startReminderPoller } from "./core/api/reminders"; +import { ensureRemindersSchema } from "./core/api/remindersSchema"; // Activar monitor de memoria si se define la variable const __memInt = parseInt(process.env.MEMORY_LOG_INTERVAL_SECONDS || '0', 10); @@ -179,6 +180,9 @@ async function bootstrap() { } }); + // Asegurar esquema de Appwrite para recordatorios (colección + atributos + índice) + try { await ensureRemindersSchema(); } catch (e) { console.warn('No se pudo asegurar el esquema de recordatorios:', e); } + // Iniciar poller de recordatorios si Appwrite está configurado startReminderPoller(bot);