import { db, id, now, visibleClause } from '../db.js'; import { applicationSchema } from '../forms/schemas.js'; import { evaluateUpdateState } from '../services/appVersionService.js'; function camelApplication(row) { if (!row) return null; const base = { id: row.id, name: row.name, vendor: row.vendor || '', publisher: row.publisher || '', currentVersion: row.current_version || '', latestKnownVersion: row.latest_known_version || '', versionSource: row.version_source || 'manual', versionSourceRef: row.version_source_ref || '', graphConnectionId: row.graph_connection_id || null, currentGraphAppId: row.current_graph_app_id || '', autoCheck: Boolean(row.auto_check), lastCheckedAt: row.last_checked_at || '', lastCheckMessage: row.last_check_message || '', notes: row.notes || '', visibility: row.visibility, ownerUserId: row.owner_user_id, groupId: row.group_id, groupName: row.group_name || null, deploymentCount: row.deployment_count ?? 0, createdAt: row.created_at, updatedAt: row.updated_at }; // Surface the computed update state so callers don't re-derive it. base.updateState = evaluateUpdateState(base); return base; } function scopedGroupId(payload) { return payload.visibility === 'group' ? payload.groupId || null : null; } export function listApplications(userId) { return db.prepare(` SELECT a.*, g.name AS group_name, (SELECT COUNT(*) FROM intune_deployments d WHERE d.application_id = a.id) AS deployment_count FROM applications a LEFT JOIN groups g ON g.id = a.group_id WHERE ${visibleClause('a')} ORDER BY a.name `).all(userId, userId).map(camelApplication); } export function loadApplication(applicationId, userId) { const row = db.prepare(` SELECT a.*, g.name AS group_name, (SELECT COUNT(*) FROM intune_deployments d WHERE d.application_id = a.id) AS deployment_count FROM applications a LEFT JOIN groups g ON g.id = a.group_id WHERE a.id = ? AND ${visibleClause('a')} `).get(applicationId, userId, userId); return camelApplication(row); } export function createApplication(body, userId) { const payload = applicationSchema.parse(body); const applicationId = id('app'); db.prepare(` INSERT INTO applications ( id, name, vendor, publisher, current_version, latest_known_version, version_source, version_source_ref, graph_connection_id, current_graph_app_id, auto_check, notes, visibility, owner_user_id, group_id, created_by, created_at, updated_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `).run( applicationId, payload.name, payload.vendor, payload.publisher, payload.currentVersion, payload.latestKnownVersion, payload.versionSource, payload.versionSourceRef, payload.graphConnectionId || null, payload.currentGraphAppId, payload.autoCheck ? 1 : 0, payload.notes, payload.visibility, userId, scopedGroupId(payload), userId, now(), now() ); return loadApplication(applicationId, userId); } export function updateApplication(applicationId, body, userId) { const existing = loadApplication(applicationId, userId); if (!existing) return null; const payload = applicationSchema.parse({ ...existing, ...body }); db.prepare(` UPDATE applications SET name = ?, vendor = ?, publisher = ?, current_version = ?, latest_known_version = ?, version_source = ?, version_source_ref = ?, graph_connection_id = ?, current_graph_app_id = ?, auto_check = ?, notes = ?, visibility = ?, group_id = ?, updated_at = ? WHERE id = ? `).run( payload.name, payload.vendor, payload.publisher, payload.currentVersion, payload.latestKnownVersion, payload.versionSource, payload.versionSourceRef, payload.graphConnectionId || null, payload.currentGraphAppId, payload.autoCheck ? 1 : 0, payload.notes, payload.visibility, scopedGroupId(payload), now(), applicationId ); return loadApplication(applicationId, userId); } export function deleteApplication(applicationId, userId) { db.prepare(`DELETE FROM applications WHERE id = ? AND ${visibleClause()}`).run(applicationId, userId, userId); } // Record the outcome of an upstream version check on the catalog entry. export function recordVersionCheck(applicationId, userId, { latestKnownVersion, message }) { const existing = loadApplication(applicationId, userId); if (!existing) return null; applyVersionCheck(applicationId, { latestKnownVersion: latestKnownVersion ?? existing.latestKnownVersion, message }); return loadApplication(applicationId, userId); } // System-level write used by the background worker (no visibility scope). export function applyVersionCheck(applicationId, { latestKnownVersion, message }) { db.prepare(` UPDATE applications SET latest_known_version = COALESCE(?, latest_known_version), last_checked_at = ?, last_check_message = ?, updated_at = ? WHERE id = ? `).run(latestKnownVersion ?? null, now(), message || '', now(), applicationId); } // Applications opted into automatic upstream version checks. System context. export function listAutoCheckApplications() { return db.prepare('SELECT * FROM applications WHERE auto_check = 1').all().map(camelApplication); }