mcp-daily-tasks
Version:
MCP para manejar tareas y resúmenes con integración ClickUp.
207 lines (206 loc) • 8.38 kB
JavaScript
import Database from 'better-sqlite3';
import * as path from 'path';
import * as os from 'os';
import * as fs from 'fs';
const REQUIRED_KEYS = ["clickup_list_id", "project_title", "listId"];
export class SqliteStorageProvider {
constructor(dbPath) {
// Opción 1: Usar directorio home del usuario
const defaultPath = path.join(os.homedir(), '.mcp-activities', 'activities.db');
// Opción 2: Usar directorio de datos de la aplicación
// const defaultPath = path.join(os.homedir(), 'AppData', 'Local', 'mcp-activities', 'activities.db');
const finalPath = dbPath || defaultPath;
// Crear el directorio si no existe
const dir = path.dirname(finalPath);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
console.log(`Using database at: ${finalPath}`);
this.db = new Database(finalPath);
this.createTable();
}
createTable() {
this.db.prepare(`
CREATE TABLE IF NOT EXISTS activities (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user TEXT NOT NULL,
date TEXT NOT NULL,
activity TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`).run();
this.db.prepare(`
CREATE TABLE IF NOT EXISTS project_metadata (
id INTEGER PRIMARY KEY AUTOINCREMENT,
project TEXT NOT NULL,
key TEXT NOT NULL,
value TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE(project, key)
)
`).run();
// Crear índices para mejor rendimiento
this.db.prepare(`
CREATE INDEX IF NOT EXISTS idx_activities_user_date ON activities(user, date)
`).run();
this.db.prepare(`
CREATE INDEX IF NOT EXISTS idx_activities_date ON activities(date)
`).run();
this.db.prepare(`
CREATE INDEX IF NOT EXISTS idx_project_metadata_project ON project_metadata(project)
`).run();
}
// Método para verificar la conexión y ubicación de la DB
getDatabasePath() {
return this.db.name;
}
// Método para hacer backup de la base de datos
backup(backupPath) {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const defaultBackupPath = path.join(path.dirname(this.db.name), `activities_backup_${timestamp}.db`);
const finalBackupPath = backupPath || defaultBackupPath;
fs.copyFileSync(this.db.name, finalBackupPath);
return finalBackupPath;
}
addActivity(activity) {
const result = this.db.prepare('INSERT INTO activities (user, date, activity) VALUES (?, ?, ?)').run(activity.user, activity.date, JSON.stringify(activity.activity));
return result.lastInsertRowid;
}
getActivitiesByDate(date) {
return this.db.prepare('SELECT * FROM activities WHERE date = ? ORDER BY created_at DESC').all(date).map((a) => ({
...a,
activity: (() => {
try {
const parsed = JSON.parse(a.activity);
if (typeof parsed === "object" && parsed !== null && "status" in parsed && "value" in parsed) {
return parsed;
}
return { status: "complete", value: parsed };
}
catch {
return { status: "complete", value: a.activity };
}
})()
}));
}
getActivitiesByUserAndDate(user, date) {
return this.db.prepare('SELECT * FROM activities WHERE user = ? AND date = ? ORDER BY created_at DESC').all(user, date).map((a) => ({
...a,
activity: (() => {
try {
const parsed = JSON.parse(a.activity);
if (typeof parsed === "object" && parsed !== null && "status" in parsed && "value" in parsed) {
return parsed;
}
return { status: "complete", value: parsed };
}
catch {
return { status: "complete", value: a.activity };
}
})()
}));
}
getAllActivities() {
return this.db.prepare('SELECT * FROM activities ORDER BY date DESC, created_at DESC').all().map((a) => ({
...a,
activity: (() => {
try {
const parsed = JSON.parse(a.activity);
if (typeof parsed === "object" && parsed !== null && "status" in parsed && "value" in parsed) {
return parsed;
}
return { status: "complete", value: parsed };
}
catch {
return { status: "complete", value: a.activity };
}
})()
}));
}
getActivitiesByRange(startDate, endDate, name) {
let query = 'SELECT * FROM activities WHERE date >= ? AND date <= ?';
const params = [startDate, endDate];
if (name) {
query += ' AND LOWER(user) LIKE ?';
params.push(`%${name.toLowerCase()}%`);
}
query += ' ORDER BY date DESC, created_at DESC';
return this.db.prepare(query).all(...params).map((a) => ({
...a,
activity: (() => {
try {
const parsed = JSON.parse(a.activity);
if (typeof parsed === "object" && parsed !== null && "status" in parsed && "value" in parsed) {
return parsed;
}
return { status: "complete", value: parsed };
}
catch {
return { status: "complete", value: a.activity };
}
})()
}));
}
// CRUD para metadatos de proyecto con timestamps
setProjectMetadata(project, key, value) {
this.db.prepare(`
INSERT OR REPLACE INTO project_metadata (project, key, value, updated_at)
VALUES (?, ?, ?, CURRENT_TIMESTAMP)
`).run(project, key, value);
}
getProjectMetadata(project, key) {
const row = this.db.prepare('SELECT value FROM project_metadata WHERE project = ? AND key = ?').get(project, key);
return row?.value;
}
getAllProjectMetadata(project) {
const rows = this.db.prepare('SELECT key, value FROM project_metadata WHERE project = ? ORDER BY key').all(project);
const result = {};
for (const row of rows) {
result[row.key] = row.value;
}
return result;
}
deleteProjectMetadata(project, key) {
this.db.prepare('DELETE FROM project_metadata WHERE project = ? AND key = ?').run(project, key);
}
// Método para limpiar datos antiguos (opcional)
cleanupOldActivities(daysToKeep = 365) {
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);
const cutoffDateStr = cutoffDate.toISOString().split('T')[0];
const result = this.db.prepare('DELETE FROM activities WHERE date < ?').run(cutoffDateStr);
return result.changes;
}
// Cerrar la conexión de manera segura
close() {
this.db.close();
}
updateActivityById(id, fields) {
const sets = [];
const params = [];
if (fields.user !== undefined) {
sets.push('user = ?');
params.push(fields.user);
}
if (fields.date !== undefined) {
sets.push('date = ?');
params.push(fields.date);
}
if (fields.activity !== undefined) {
sets.push('activity = ?');
params.push(JSON.stringify(fields.activity));
}
if (sets.length === 0)
return;
params.push(id);
this.db.prepare(`UPDATE activities SET ${sets.join(', ')} WHERE id = ?`).run(...params);
}
deleteActivityById(id) {
this.db.prepare('DELETE FROM activities WHERE id = ?').run(id);
}
getProjectsByRange(startDate, endDate) {
const rows = this.db.prepare('SELECT DISTINCT user FROM activities WHERE date >= ? AND date <= ? ORDER BY user ASC').all(startDate, endDate);
return rows.map(r => r.user);
}
}