import { generateUUID } from '@/lib/utils'; export interface Resource { id: string; tenantId: string; name: string; type: 'room' | 'vehicle' | 'equipment' | 'other'; location?: string; capacity?: number; description?: string; contactEmail?: string; isActive: boolean; metadata: Record; } export interface ResourceBooking { id: string; resourceId: string; eventId?: string; startTime: string; endTime: string; bookedBy: string; } interface ResourceRow { id: string; tenant_id: string; name: string; type: string; location: string | null; capacity: number | null; description: string | null; contact_email: string | null; is_active: boolean; metadata: Record; } interface BookingRow { id: string; resource_id: string; event_id: string | null; start_time: string; end_time: string; booked_by: string; } function rowToResource(row: ResourceRow): Resource { return { id: row.id, tenantId: row.tenant_id, name: row.name, type: row.type as Resource['type'], location: row.location ?? undefined, capacity: row.capacity ?? undefined, description: row.description ?? undefined, contactEmail: row.contact_email ?? undefined, isActive: row.is_active, metadata: row.metadata ?? {}, }; } function rowToBooking(row: BookingRow): ResourceBooking { return { id: row.id, resourceId: row.resource_id, eventId: row.event_id ?? undefined, startTime: row.start_time, endTime: row.end_time, bookedBy: row.booked_by, }; } let pool: { query: (text: string, params?: unknown[]) => Promise<{ rows: unknown[] }> } | null = null; async function getPool(): Promise<{ query: (text: string, params?: unknown[]) => Promise<{ rows: unknown[] }> } | null> { if (pool) return pool; const url = process.env.DATABASE_URL; if (url) { try { // @ts-expect-error - pg is an optional runtime dependency, not in package.json const pg = (await import('pg')) as unknown as { Pool?: new (cfg: { connectionString: string; max: number }) => unknown; default?: { Pool?: new (cfg: { connectionString: string; max: number }) => unknown } }; const PoolConstructor = (pg.Pool ?? pg.default?.Pool ?? null); if (PoolConstructor) { pool = new PoolConstructor({ connectionString: url, max: 10 }) as typeof pool; } console.log('[resources] PostgreSQL pool created'); return pool; } catch { console.warn('[resources] pg module not available, falling back to in-memory store'); } } console.warn('[resources] DATABASE_URL not set, using in-memory store'); return null; } const memoryResources: Map = new Map(); const memoryBookings: Map = new Map(); export async function listResources(tenantId: string, type?: string): Promise { const db = await getPool(); if (db) { let query = 'SELECT * FROM resources WHERE tenant_id = $1 AND is_active = true'; const params: string[] = [tenantId]; if (type) { query += ' AND type = $2'; params.push(type); } query += ' ORDER BY name ASC'; const result = await db.query(query, params); return (result.rows as ResourceRow[]).map(rowToResource); } let resources = Array.from(memoryResources.values()).filter(r => r.tenant_id === tenantId && r.is_active); if (type) { resources = resources.filter(r => r.type === type); } resources.sort((a, b) => a.name.localeCompare(b.name)); return resources.map(rowToResource); } export async function getResource(id: string): Promise { const db = await getPool(); if (db) { const result = await db.query('SELECT * FROM resources WHERE id = $1', [id]); if (result.rows.length === 0) return null; return rowToResource(result.rows[0] as ResourceRow); } const row = memoryResources.get(id); return row ? rowToResource(row) : null; } export async function createResource( tenantId: string, data: { name: string; type: Resource['type']; location?: string; capacity?: number; description?: string; contactEmail?: string; metadata?: Record } ): Promise { const db = await getPool(); if (db) { const result = await db.query( `INSERT INTO resources (id, tenant_id, name, type, location, capacity, description, contact_email, metadata) VALUES (gen_random_uuid(), $1, $2, $3, $4, $5, $6, $7, $8) RETURNING *`, [tenantId, data.name, data.type, data.location ?? null, data.capacity ?? null, data.description ?? null, data.contactEmail ?? null, JSON.stringify(data.metadata ?? {})] ); return rowToResource(result.rows[0] as ResourceRow); } const id = generateUUID(); const row: ResourceRow = { id, tenant_id: tenantId, name: data.name, type: data.type, location: data.location ?? null, capacity: data.capacity ?? null, description: data.description ?? null, contact_email: data.contactEmail ?? null, is_active: true, metadata: data.metadata ?? {}, }; memoryResources.set(id, row); return rowToResource(row); } export async function checkAvailability( resourceId: string, start: string, end: string, ): Promise<{ available: boolean; conflicts: ResourceBooking[] }> { const db = await getPool(); if (db) { const result = await db.query( `SELECT * FROM resources_bookings WHERE resource_id = $1 AND start_time < $3::timestamptz AND end_time > $2::timestamptz ORDER BY start_time ASC`, [resourceId, start, end], ); const conflicts = (result.rows as BookingRow[]).map(rowToBooking); return { available: conflicts.length === 0, conflicts }; } const conflicts = Array.from(memoryBookings.values()) .filter(b => b.resource_id === resourceId && b.start_time < end && b.end_time > start) .sort((a, b) => a.start_time.localeCompare(b.start_time)) .map(rowToBooking); return { available: conflicts.length === 0, conflicts }; } export async function bookResource( resourceId: string, start: string, end: string, bookedBy: string, eventId?: string, ): Promise { const db = await getPool(); if (db) { const result = await db.query( `INSERT INTO resources_bookings (id, resource_id, event_id, start_time, end_time, booked_by) VALUES (gen_random_uuid(), $1, $2, $3::timestamptz, $4::timestamptz, $5) RETURNING *`, [resourceId, eventId ?? null, start, end, bookedBy], ); return rowToBooking(result.rows[0] as BookingRow); } const id = generateUUID(); const row: BookingRow = { id, resource_id: resourceId, event_id: eventId ?? null, start_time: start, end_time: end, booked_by: bookedBy, }; memoryBookings.set(id, row); return rowToBooking(row); } export async function cancelBooking(bookingId: string): Promise { const db = await getPool(); if (db) { await db.query('DELETE FROM resources_bookings WHERE id = $1', [bookingId]); return; } memoryBookings.delete(bookingId); } export async function getBookingsForResource(resourceId: string): Promise { const db = await getPool(); if (db) { const result = await db.query( 'SELECT * FROM resources_bookings WHERE resource_id = $1 ORDER BY start_time ASC', [resourceId], ); return (result.rows as BookingRow[]).map(rowToBooking); } return Array.from(memoryBookings.values()) .filter(b => b.resource_id === resourceId) .sort((a, b) => a.start_time.localeCompare(b.start_time)) .map(rowToBooking); } export async function getBookingsForEvent(eventId: string): Promise { const db = await getPool(); if (db) { const result = await db.query( 'SELECT * FROM resources_bookings WHERE event_id = $1 ORDER BY start_time ASC', [eventId], ); return (result.rows as BookingRow[]).map(rowToBooking); } return Array.from(memoryBookings.values()) .filter(b => b.event_id === eventId) .sort((a, b) => a.start_time.localeCompare(b.start_time)) .map(rowToBooking); } export async function cancelBookingsForEvent(eventId: string): Promise { const db = await getPool(); if (db) { await db.query('DELETE FROM resources_bookings WHERE event_id = $1', [eventId]); return; } for (const [id, booking] of memoryBookings) { if (booking.event_id === eventId) { memoryBookings.delete(id); } } } export async function updateBookingEventId(bookingId: string, eventId: string): Promise { const db = await getPool(); if (db) { await db.query('UPDATE resources_bookings SET event_id = $2 WHERE id = $1', [bookingId, eventId]); return; } const row = memoryBookings.get(bookingId); if (row) { row.event_id = eventId; } }