# Phase 5: Functional Features — Backend **Status:** ✅ Complete **Parent plan:** [PLAN.md](./PLAN.md) **Domain:** backend ## Objective Implement all backend services, API routes, and background jobs for the 8 functional features: favorites, recent apps, uptime dashboard, notifications, tags, multi-URL apps, API tokens, and audit log. ## Tasks ### 5.1 Favorites Service & API - [x] Create `src/lib/server/services/favoriteService.ts` - `getUserFavorites(userId)` — ordered list of user's favorite apps - `addFavorite(userId, appId)` — add app to favorites (append to end) - `removeFavorite(userId, appId)` — remove from favorites - `reorderFavorites(userId, favoriteIds[])` — update order - [x] Create `src/routes/api/favorites/+server.ts` — GET (list), POST (add), DELETE (remove) - [x] Create `src/routes/api/favorites/reorder/+server.ts` — PATCH (reorder) ### 5.2 Recent Apps Service & API - [x] Create `src/lib/server/services/recentAppsService.ts` - `recordClick(userId, appId)` — add click record - `getRecentApps(userId, limit=10)` — get most recent unique apps - `clearHistory(userId)` — clear all click history for user - [x] Create `src/routes/api/recent-apps/+server.ts` — GET (list), POST (record click), DELETE (clear) ### 5.3 Uptime Dashboard Service & API - [x] Create `src/lib/server/services/uptimeService.ts` - `getUptimeStats(appId, timeRange: '24h'|'7d'|'30d')` — uptime percentage, avg response time - `getUptimeTimeline(appId, timeRange)` — status history with timestamps - `getAllAppsUptime(timeRange)` — aggregated uptime for all apps - `getIncidents(appId?, timeRange)` — list of down periods with duration - Queries AppStatus table, groups by time windows - [x] Create `src/routes/api/uptime/+server.ts` — GET all apps uptime summary - [x] Create `src/routes/api/uptime/[appId]/+server.ts` — GET single app uptime + timeline ### 5.4 Notifications Service & API - [x] Create `src/lib/server/services/notificationService.ts` - Channel management: create, update, delete, list channels for user - `sendNotification(userId, appId, event, message)` — create notification record + dispatch to channels - Dispatchers: `sendDiscord(webhookUrl, message)`, `sendSlack(webhookUrl, message)`, `sendTelegram(botToken, chatId, message)`, `sendHttp(url, payload)` - `getNotifications(userId, { unreadOnly?, limit?, offset? })` — paginated notification list - `markAsRead(notificationId)`, `markAllAsRead(userId)` - [x] Create `src/routes/api/notifications/+server.ts` — GET (list), PATCH (mark read) - [x] Create `src/routes/api/notifications/channels/+server.ts` — CRUD for notification channels - [x] Create `src/routes/api/notifications/channels/[id]/+server.ts` — single channel operations - [x] Create `src/routes/api/notifications/channels/[id]/test/+server.ts` — POST to send test notification - [x] Integrate with healthcheck scheduler: trigger notifications when app status changes (online->offline, offline->online) - Update `src/lib/server/jobs/healthcheckScheduler.ts` to call notificationService on status change ### 5.5 Tags Service & API - [x] Create `src/lib/server/services/tagService.ts` - CRUD for tags: create, update (name, color), delete, findAll - `addTagToApp(appId, tagId)`, `removeTagFromApp(appId, tagId)` - `getAppsByTag(tagId)` — apps with a specific tag - `getTagsForApp(appId)` — tags for a specific app - [x] Create `src/routes/api/tags/+server.ts` — GET (list), POST (create) - [x] Create `src/routes/api/tags/[id]/+server.ts` — PATCH (update), DELETE (delete) - [x] Create `src/routes/api/apps/[id]/tags/+server.ts` — GET (app's tags), POST (add tag), DELETE (remove tag) ### 5.6 Multi-URL Apps Service & API - [x] Extend `src/lib/server/services/appService.ts` - `addAppLink(appId, { label, url, icon, order })` — add secondary URL - `updateAppLink(linkId, { label?, url?, icon?, order? })` — update link - `removeAppLink(linkId)` — delete link - `reorderAppLinks(appId, linkIds[])` — update order - Include links in app queries (eager load) - [x] Create `src/routes/api/apps/[id]/links/+server.ts` — CRUD for app links - [x] Update existing app GET endpoints to include links in response ### 5.7 API Tokens Service & API - [x] Create `src/lib/server/services/apiTokenService.ts` - `generateToken(userId, name, scope, expiresAt?)` — generate random token, store hash - `revokeToken(tokenId, userId)` — delete token - `listTokens(userId)` — list tokens (without hash, with last used) - `validateToken(tokenString)` — hash and compare, check expiry, update lastUsedAt - [x] Create `src/routes/api/tokens/+server.ts` — GET (list), POST (generate) - [x] Create `src/routes/api/tokens/[id]/+server.ts` — DELETE (revoke) - [x] Update auth middleware to also check for Bearer token in Authorization header - `src/lib/server/middleware/authenticate.ts` — add API token validation path ### 5.8 Audit Log Service & API - [x] Create `src/lib/server/services/auditLogService.ts` - `logAction(userId, action, entityType, entityId, details?)` — record audit event - `getAuditLogs({ action?, entityType?, userId?, startDate?, endDate?, limit?, offset? })` — filtered, paginated query - `pruneOldLogs(retentionDays)` — delete logs older than retention period - [x] Create `src/routes/api/admin/audit-log/+server.ts` — GET (list, admin only) - [x] Add audit log calls to existing admin operations: - User CRUD, Board CRUD, App CRUD, Settings changes, Import/Export - Update relevant services/routes to call `auditLogService.logAction()` - [x] Add pruning cron job to healthcheck scheduler (or create separate job): - Run daily, prune based on SystemSettings retention config (default 90 days) ## Files to Modify/Create - `src/lib/server/services/favoriteService.ts` — new - `src/lib/server/services/recentAppsService.ts` — new - `src/lib/server/services/uptimeService.ts` — new - `src/lib/server/services/notificationService.ts` — new - `src/lib/server/services/tagService.ts` — new - `src/lib/server/services/apiTokenService.ts` — new - `src/lib/server/services/auditLogService.ts` — new - `src/lib/server/services/appService.ts` — modify (multi-URL links) - `src/lib/server/middleware/authenticate.ts` — modify (API token auth) - `src/lib/server/jobs/healthcheckScheduler.ts` — modify (notification triggers) - `src/routes/api/favorites/+server.ts` — new - `src/routes/api/favorites/reorder/+server.ts` — new - `src/routes/api/recent-apps/+server.ts` — new - `src/routes/api/uptime/+server.ts` — new - `src/routes/api/uptime/[appId]/+server.ts` — new - `src/routes/api/notifications/+server.ts` — new - `src/routes/api/notifications/channels/+server.ts` — new - `src/routes/api/notifications/channels/[id]/+server.ts` — new - `src/routes/api/notifications/channels/[id]/test/+server.ts` — new - `src/routes/api/tags/+server.ts` — new - `src/routes/api/tags/[id]/+server.ts` — new - `src/routes/api/apps/[id]/tags/+server.ts` — new - `src/routes/api/apps/[id]/links/+server.ts` — new - `src/routes/api/tokens/+server.ts` — new - `src/routes/api/tokens/[id]/+server.ts` — new - `src/routes/api/admin/audit-log/+server.ts` — new - Various existing route files — modify (add audit logging) ## Acceptance Criteria - All services handle errors gracefully - API token auth works alongside existing JWT auth (check both) - Notification dispatchers handle webhook failures without crashing - Audit logging doesn't slow down the operations it logs (fire-and-forget pattern) - Uptime calculations handle edge cases (no data, all unknown, timezone issues) - All new endpoints require appropriate auth (user-level or admin-level) - Favorites and recent apps are per-user, properly isolated ## Notes - For notification dispatchers, use simple `fetch()` calls — no need for a queue system at this scale - API token generation: use `crypto.randomBytes(32).toString('hex')` for the token, store bcrypt hash - Audit logging should be non-blocking: catch and log errors but don't fail the parent operation - Uptime calculation: group AppStatus records by time windows, calculate online/(online+offline) percentage - Tag colors: store as hex string (e.g., '#ef4444') ## Review Checklist - [x] All tasks completed - [x] Code follows project conventions - [ ] No unintended side effects - [ ] Build passes (Big Bang: code quality check only) - [ ] Tests pass (Big Bang: skipped for intermediate phase) ## Handoff to Next Phase ### What was done - Created 7 new backend services: favoriteService, recentAppsService, uptimeService, notificationService, tagService, apiTokenService, auditLogService - Extended appService with multi-URL link management (addAppLink, updateAppLink, removeAppLink, reorderAppLinks, getAppLinks) and eager-loaded links in findAll/findById - Created 16 new API route files across favorites, recent-apps, uptime, notifications (+ channels + test), tags, app tags, app links, tokens, admin audit-log - Updated authenticate.ts middleware with extractBearerToken helper - Updated hooks.server.ts to validate API tokens from Authorization header as fallback when no JWT session exists - Updated healthcheckScheduler.ts to track status transitions and broadcast notifications on status changes (online/offline/degraded), plus added daily audit log pruning cron job - Added audit logging calls to 8 existing route files: users CRUD, apps CRUD, boards CRUD, admin settings, admin import, admin export ### What the next phase needs to know - All new API endpoints require authentication via JWT cookie or Bearer API token - Favorites and recent apps are per-user, isolated by userId - Notification dispatchers are fire-and-forget — they catch errors and never throw - Audit logging is non-blocking (void return, catches errors internally) - API token validation iterates all tokens to bcrypt-compare; at scale this could be slow (consider indexing on a prefix for optimization) - The `broadcastNotification()` function sends to all users with enabled channels — used by healthcheck scheduler - Uptime stats return null for uptimePercentage/avgResponseTime when no data exists - Tags use the AppTag junction table (not the legacy comma-separated App.tags field) - App links are eager-loaded in appService.findAll() and findById() queries - Audit log pruning runs daily at midnight with 90-day default retention ### Potential concerns - API token validation scans all tokens in the database for bcrypt comparison. For large numbers of tokens, consider a two-step lookup (store a non-secret prefix for indexing, then bcrypt the full token). - The healthcheck scheduler tracks previous statuses in memory (Map). On server restart, the first check after restart will not detect transitions since the map is empty. - Notification channel configs are stored as JSON strings — the dispatcher trusts the shape after JSON.parse. Invalid configs are silently skipped.