Skip to main content
Glama
templates.ts8.61 kB
import { v4 as uuidv4 } from 'uuid'; import { logger } from '../config/logging.js'; import { getPromisifiedDb } from './db.js'; import fs from 'fs/promises'; import path from 'path'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); export interface Template { id: string; name: string; description?: string; content: string; tags?: string[]; version: number; createdAt: Date; updatedAt: Date; } // Database row interface to handle type conversions interface TemplateRow { id: string; name: string; description: string | null; content: string; tags: string | null; version: number; created_at: string; updated_at: string; } /** * Get a template by name or ID */ export async function getTemplate(nameOrId: string): Promise<Template> { const db = getPromisifiedDb(); // Try to find by ID first, then by name const template = await db.get<TemplateRow>( `SELECT * FROM templates WHERE id = ? OR name = ? LIMIT 1`, [nameOrId, nameOrId] ); if (!template) { logger.error(`Template not found: ${nameOrId}`); throw new Error(`Template not found: ${nameOrId}`); } // Parse tags if they exist const tags = template.tags ? JSON.parse(template.tags) : []; return { id: template.id, name: template.name, description: template.description || undefined, content: template.content, tags, version: template.version, createdAt: new Date(template.created_at), updatedAt: new Date(template.updated_at) }; } /** * Save a new template */ export async function saveTemplate(template: Omit<Template, 'id' | 'createdAt' | 'updatedAt' | 'version'>): Promise<Template> { const db = getPromisifiedDb(); const id = uuidv4(); const now = new Date().toISOString(); const version = 1; // Stringify tags if they exist const tags = template.tags ? JSON.stringify(template.tags) : null; await db.run( `INSERT INTO templates (id, name, description, content, tags, version, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [id, template.name, template.description, template.content, tags, version, now, now] ); logger.info(`Created new template: ${template.name} (${id})`); return { ...template, id, version, createdAt: new Date(now), updatedAt: new Date(now) }; } /** * Update an existing template */ export async function updateTemplate(id: string, updates: Partial<Omit<Template, 'id' | 'createdAt' | 'updatedAt'>>): Promise<Template> { const db = getPromisifiedDb(); // Get current template const current = await getTemplate(id); // Update the template const now = new Date().toISOString(); const newVersion = current.version + 1; // Store previous version await db.run( `INSERT INTO template_versions (id, template_id, version, content, created_at) VALUES (?, ?, ?, ?, ?)`, [uuidv4(), current.id, current.version, current.content, now] ); // Update fields that were provided const name = updates.name || current.name; const description = updates.description || current.description; const content = updates.content || current.content; const tags = updates.tags ? JSON.stringify(updates.tags) : current.tags ? JSON.stringify(current.tags) : null; // Update the template await db.run( `UPDATE templates SET name = ?, description = ?, content = ?, tags = ?, version = ?, updated_at = ? WHERE id = ?`, [name, description, content, tags, newVersion, now, id] ); logger.info(`Updated template: ${name} (${id}) to version ${newVersion}`); return { ...current, name, description, content, tags: tags ? JSON.parse(tags) : undefined, version: newVersion, updatedAt: new Date(now) }; } /** * List all templates (without content) */ export async function listTemplates(): Promise<Omit<Template, 'content'>[]> { const db = getPromisifiedDb(); interface TemplateListRow { id: string; name: string; description: string | null; tags: string | null; version: number; created_at: string; updated_at: string; } const templates = await db.all<TemplateListRow>( `SELECT id, name, description, tags, version, created_at, updated_at FROM templates WHERE deleted = 0 ORDER BY name` ); return templates.map(t => ({ id: t.id, name: t.name, description: t.description || undefined, tags: t.tags ? JSON.parse(t.tags) : [], version: t.version, createdAt: new Date(t.created_at), updatedAt: new Date(t.updated_at) })); } /** * Initialize with default templates */ export async function initializeDefaultTemplates() { logger.info('Initializing default templates'); // Check if we already have templates const db = getPromisifiedDb(); const result = await db.get<{ count: number }>('SELECT COUNT(*) as count FROM templates'); const count = result?.count || 0; if (count > 0) { logger.info(`Found ${count} existing templates, skipping initialization`); return; } // Add default templates const templatesDir = path.join(__dirname, '../../src/templates'); try { const files = await fs.readdir(templatesDir); for (const file of files) { if (file.endsWith('.md')) { const content = await fs.readFile(path.join(templatesDir, file), 'utf-8'); const name = file.replace('.md', ''); await saveTemplate({ name, description: `Default template: ${name}`, content, tags: ['default'] }); logger.info(`Added default template: ${name}`); } } } catch (error) { logger.warn(`Error initializing default templates: ${error instanceof Error ? error.message : String(error)}`); logger.info('Creating standard template as fallback'); // Create a standard template as fallback await saveTemplate({ name: 'standard', description: 'Standard PRD template', content: `# {{PRODUCT_NAME}} - Product Requirements Document ## Introduction ### Product Overview {{PRODUCT_DESCRIPTION}} ### Target Audience {{TARGET_AUDIENCE}} ## Core Features {{CORE_FEATURES}} ## Constraints and Limitations {{CONSTRAINTS}} ## User Stories *To be added by the product team* ## Acceptance Criteria *To be added for each feature* ## Timeline *To be determined* --- Generated on {{DATE}}`, tags: ['default'] }); } } // Add a soft-delete function to mark templates as deleted export async function deleteTemplate(id: string): Promise<void> { const db = getPromisifiedDb(); await db.run( `UPDATE templates SET deleted = 1, updated_at = CURRENT_TIMESTAMP WHERE id = ?`, [id] ); } /** * Export all non-deleted templates to a JSON file * @param filePath Path to write the JSON export */ export async function exportTemplates(filePath: string): Promise<void> { const db = getPromisifiedDb(); // Get all non-deleted template rows const rows = await db.all<TemplateRow>( `SELECT id, name, description, content, tags, version, created_at, updated_at FROM templates WHERE deleted = 0` ); // Convert rows to exportable objects const exports = rows.map(row => ({ id: row.id, name: row.name, description: row.description || undefined, content: row.content, tags: row.tags ? JSON.parse(row.tags) : [], version: row.version, createdAt: new Date(row.created_at).toISOString(), updatedAt: new Date(row.updated_at).toISOString(), })); await fs.writeFile(filePath, JSON.stringify(exports, null, 2), 'utf-8'); } /** * Import templates from a JSON file. Upserts based on name or id. * @param filePath Path to read the JSON import */ export async function importTemplates(filePath: string): Promise<void> { const db = getPromisifiedDb(); const data = await fs.readFile(filePath, 'utf-8'); const templates: Array<{ name: string; description?: string; content: string; tags?: string[] }> = JSON.parse(data); for (const t of templates) { try { // Try to find existing by name const existing = await getTemplate(t.name); // Update existing template (does versioning) await updateTemplate(existing.id, { name: t.name, description: t.description, content: t.content, tags: t.tags }); } catch { // Not found, create new await saveTemplate({ name: t.name, description: t.description, content: t.content, tags: t.tags }); } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Saml1211/PRD-MCP-Server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server