Files
web-app-launcher/plans/phase-4-7-full-expansion/phase-5-functional-backend.md
T
alexei.dolgolyov 1c0a7cb850 feat: Phases 4-7 — Full Feature Expansion (26 features)
Phase 4 — New Widget Types:
- Clock/Weather, System Stats, RSS/Feed, Calendar, Markdown,
  Metric/Counter, Link Group, Camera/Stream widgets
- Backend services with caching for each data source
- Full creation form with dynamic config fields per type

Phase 5 — Visual & Styling Enhancements:
- Glassmorphism card style (solid/glass/outline)
- Board-level themes with per-board hue/saturation
- Animated SVG status rings replacing static dots
- Card size options (compact/medium/large)
- Custom CSS injection (admin + per-board, sanitized)
- Wallpaper backgrounds with blur/overlay/parallax

Phase 6 — Functional Features:
- Favorites bar with drag-and-drop reordering
- Recent apps tracking with privacy toggle
- Uptime dashboard page (/status, guest-accessible)
- Notifications system (Discord/Slack/Telegram/HTTP webhooks)
- App tags with filtering in board view
- Multi-URL app cards with expandable sub-links
- Personal API tokens with scoped permissions
- Audit log with retention and admin viewer

Phase 7 — Quality of Life:
- Onboarding wizard (5-step first-launch setup)
- App URL health preview with favicon/title detection
- Board templates (4 built-in + custom import/export)
- Keyboard shortcut overlay (j/k nav, 1-9 boards, ? help)

212 files changed, 15641 insertions, 980 deletions.
Build, lint, type check, and 222 tests all pass.
2026-03-25 14:18:10 +03:00

190 lines
11 KiB
Markdown

# 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.