From 99a37601908ff73a0530a948ac1c2e662f9d34cb Mon Sep 17 00:00:00 2001 From: AlkenD Date: Tue, 11 Nov 2025 19:55:08 +0530 Subject: [PATCH 01/21] feat(api): add real-time logs streaming with WebSocket and REST endpoints --- apps/api/package.json | 3 + apps/api/src/domains/index.ts | 1 + apps/api/src/domains/logs/index.ts | 2 + apps/api/src/domains/logs/logs.controller.ts | 51 ++++++++ apps/api/src/domains/logs/logs.routes.ts | 110 ++++++++++++++++ apps/api/src/domains/logs/logs.services.ts | 119 ++++++++++++++++++ apps/api/src/lib/config/swagger.ts | 4 + apps/api/src/lib/utils/logger.ts | 3 + apps/api/src/lib/utils/websocket-transport.ts | 49 ++++++++ apps/api/src/lib/websocket/index.ts | 30 ++++- apps/api/src/routes/v1/index.ts | 4 + pnpm-lock.yaml | 3 + 12 files changed, 376 insertions(+), 3 deletions(-) create mode 100644 apps/api/src/domains/logs/index.ts create mode 100644 apps/api/src/domains/logs/logs.controller.ts create mode 100644 apps/api/src/domains/logs/logs.routes.ts create mode 100644 apps/api/src/domains/logs/logs.services.ts create mode 100644 apps/api/src/lib/utils/websocket-transport.ts diff --git a/apps/api/package.json b/apps/api/package.json index 79b9ad4..f2ea174 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -7,6 +7,8 @@ "dev": "tsx watch src/index.ts", "build": "tsc && tsc-alias", "start": "node dist/index.js", + "kill-port": "(lsof -ti:3001 | xargs kill -9 2>/dev/null && sleep 1 && lsof -ti:3001 | xargs kill -9 2>/dev/null) || echo 'Port cleared or not in use'", + "dev:clean": "pnpm kill-port && pnpm dev", "db:generate": "prisma generate", "db:push": "prisma db push", "db:push:force": "prisma db push --accept-data-loss", @@ -40,6 +42,7 @@ "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.1", "winston": "^3.18.3", + "winston-transport": "^4.9.0", "ws": "^8.18.3", "zod": "^4.1.12" }, diff --git a/apps/api/src/domains/index.ts b/apps/api/src/domains/index.ts index 88747f8..3aa7a35 100644 --- a/apps/api/src/domains/index.ts +++ b/apps/api/src/domains/index.ts @@ -5,4 +5,5 @@ export { moviesRoutes } from "./movies"; export { tvshowsRoutes } from "./tvshows"; export { streamRoutes } from "./stream"; export { settingsRoutes } from "./settings"; +export { logsRoutes } from "./logs"; export { default as searchRoutes } from "./search/search.routes"; diff --git a/apps/api/src/domains/logs/index.ts b/apps/api/src/domains/logs/index.ts new file mode 100644 index 0000000..0d2e37f --- /dev/null +++ b/apps/api/src/domains/logs/index.ts @@ -0,0 +1,2 @@ +export { default as logsRoutes } from "./logs.routes"; + diff --git a/apps/api/src/domains/logs/logs.controller.ts b/apps/api/src/domains/logs/logs.controller.ts new file mode 100644 index 0000000..4550ad9 --- /dev/null +++ b/apps/api/src/domains/logs/logs.controller.ts @@ -0,0 +1,51 @@ +import { Request, Response } from "express"; +import { logsServices } from "./logs.services"; +import { logger } from "@/lib/utils"; + +/** + * Get recent logs + * @route GET /api/v1/logs + */ +export const getLogs = async (req: Request, res: Response) => { + try { + const limit = req.query.limit ? parseInt(req.query.limit as string) : 100; + const level = req.query.level as string | undefined; + + const logs = await logsServices.getRecentLogs(limit, level); + + res.status(200).json({ + success: true, + data: logs, + }); + } catch (error) { + logger.error("Error fetching logs:", error); + res.status(500).json({ + success: false, + message: "Failed to fetch logs", + error: error instanceof Error ? error.message : "Unknown error", + }); + } +}; + +/** + * Clear logs + * @route DELETE /api/v1/logs + */ +export const clearLogs = async (req: Request, res: Response) => { + try { + await logsServices.clearLogs(); + + res.status(200).json({ + success: true, + message: "Logs cleared successfully", + }); + } catch (error) { + logger.error("Error clearing logs:", error); + res.status(500).json({ + success: false, + message: "Failed to clear logs", + error: error instanceof Error ? error.message : "Unknown error", + }); + } +}; + diff --git a/apps/api/src/domains/logs/logs.routes.ts b/apps/api/src/domains/logs/logs.routes.ts new file mode 100644 index 0000000..5ec51e8 --- /dev/null +++ b/apps/api/src/domains/logs/logs.routes.ts @@ -0,0 +1,110 @@ +import express, { Router } from "express"; +import { getLogs, clearLogs } from "./logs.controller"; + +const router: Router = express.Router(); + +/** + * @swagger + * /api/v1/logs: + * get: + * summary: Get recent API logs + * description: Fetches recent log entries from the server with optional filtering + * tags: [Logs] + * parameters: + * - in: query + * name: limit + * schema: + * type: number + * default: 100 + * description: Number of logs to retrieve + * - in: query + * name: level + * schema: + * type: string + * enum: [error, warn, info, http, debug] + * description: Filter by log level + * responses: + * 200: + * description: Successfully retrieved logs + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * example: true + * data: + * type: array + * items: + * type: object + * properties: + * timestamp: + * type: string + * example: "2025-11-11 18:36:11" + * level: + * type: string + * example: "info" + * message: + * type: string + * example: "Server started successfully" + * meta: + * type: object + * nullable: true + * 500: + * description: Internal server error + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * example: false + * message: + * type: string + * error: + * type: string + */ +router.get("/", getLogs); + +/** + * @swagger + * /api/v1/logs: + * delete: + * summary: Clear all logs + * description: Clears all log entries from the server + * tags: [Logs] + * responses: + * 200: + * description: Logs cleared successfully + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * example: true + * message: + * type: string + * example: "Logs cleared successfully" + * 500: + * description: Internal server error + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * example: false + * message: + * type: string + * error: + * type: string + */ +router.delete("/", clearLogs); + +export default router; + diff --git a/apps/api/src/domains/logs/logs.services.ts b/apps/api/src/domains/logs/logs.services.ts new file mode 100644 index 0000000..0ff3cb6 --- /dev/null +++ b/apps/api/src/domains/logs/logs.services.ts @@ -0,0 +1,119 @@ +import fs from "fs/promises"; +import path from "path"; +import { logger } from "@/lib/utils"; + +interface LogEntry { + timestamp: string; + level: string; + message: string; + meta?: Record; +} + +const LOG_FILE_PATH = path.join(process.cwd(), "logs", "combined.log"); +const MAX_LOGS_TO_READ = 500; // Maximum number of logs to read from file + +/** + * Strip ANSI color codes from a string + */ +function stripAnsiColors(str: string): string { + // eslint-disable-next-line no-control-regex + return str.replace(/\x1b\[[0-9;]*m/g, ""); +} + +/** + * Parse a log line from the combined.log file + */ +function parseLogLine(line: string): LogEntry | null { + try { + // Strip ANSI color codes first + const cleanLine = stripAnsiColors(line); + + // Log format: "YYYY-MM-DD HH:mm:ss [level]: message meta" + const match = cleanLine.match( + /^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) \[(\w+)\]: (.+)$/ + ); + + if (!match) return null; + + const [, timestamp, level, rest] = match; + + if (!timestamp || !level || !rest) return null; + + // Try to extract metadata JSON if present + let message = rest; + let meta: Record | undefined; + + // Check if there's JSON at the end + const jsonMatch = rest.match(/^(.+?)(\{.+\})$/); + if (jsonMatch && jsonMatch[1] && jsonMatch[2]) { + try { + message = jsonMatch[1].trim(); + meta = JSON.parse(jsonMatch[2]); + } catch { + // If JSON parsing fails, keep original message + message = rest; + } + } + + return { + timestamp, + level: level.toLowerCase(), + message, + meta, + }; + } catch { + return null; + } +} + +/** + * Get recent logs from the log file + */ +export async function getRecentLogs( + limit: number = 100, + levelFilter?: string +): Promise { + try { + // Read the log file + const content = await fs.readFile(LOG_FILE_PATH, "utf-8"); + const lines = content.split("\n").filter((line) => line.trim()); + + // Parse log lines + const logs = lines + .slice(-MAX_LOGS_TO_READ) // Get last N lines + .map(parseLogLine) + .filter((log): log is LogEntry => log !== null); + + // Filter by level if specified + let filteredLogs = logs; + if (levelFilter) { + const normalizedLevel = levelFilter.toLowerCase(); + filteredLogs = logs.filter((log) => log.level === normalizedLevel); + } + + // Return most recent logs (up to limit) + return filteredLogs.slice(-limit).reverse(); + } catch { + logger.error("Error reading log file"); + return []; + } +} + +/** + * Clear the log file + */ +export async function clearLogs(): Promise { + try { + await fs.writeFile(LOG_FILE_PATH, ""); + logger.info("Logs cleared"); + } catch (error) { + logger.error("Error clearing logs"); + throw error; + } +} + +export const logsServices = { + getRecentLogs, + clearLogs, +}; + diff --git a/apps/api/src/lib/config/swagger.ts b/apps/api/src/lib/config/swagger.ts index 5c0901f..51b6677 100644 --- a/apps/api/src/lib/config/swagger.ts +++ b/apps/api/src/lib/config/swagger.ts @@ -79,6 +79,10 @@ const options = { name: "Settings", description: "Application settings and configuration", }, + { + name: "Logs", + description: "API logs and debugging endpoints", + }, ], components: { schemas: { diff --git a/apps/api/src/lib/utils/logger.ts b/apps/api/src/lib/utils/logger.ts index ed97da0..6d3f109 100644 --- a/apps/api/src/lib/utils/logger.ts +++ b/apps/api/src/lib/utils/logger.ts @@ -1,4 +1,5 @@ import winston from "winston"; +import { WebSocketTransport } from "./websocket-transport"; // Define log levels const levels = { @@ -49,6 +50,8 @@ const transports = [ }), // File transport for all logs new winston.transports.File({ filename: "logs/combined.log" }), + // WebSocket transport for real-time log streaming + new WebSocketTransport(), ]; // Create the logger diff --git a/apps/api/src/lib/utils/websocket-transport.ts b/apps/api/src/lib/utils/websocket-transport.ts new file mode 100644 index 0000000..51fe925 --- /dev/null +++ b/apps/api/src/lib/utils/websocket-transport.ts @@ -0,0 +1,49 @@ +import winston from "winston"; +import TransportStream from "winston-transport"; +import { sendLogMessage } from "../websocket"; + +/** + * Custom Winston transport that broadcasts log messages via WebSocket + */ +export class WebSocketTransport extends TransportStream { + constructor(opts?: TransportStream.TransportStreamOptions) { + super(opts); + } + + log( + info: { timestamp: string; level: string; message: string; [key: string]: unknown }, + callback: () => void + ) { + setImmediate(() => { + this.emit("logged", info); + }); + + // Extract relevant log information + const { timestamp, level, message, ...meta } = info; + + // Remove winston metadata from meta + const cleanMeta: Record = { ...meta }; + const levelSymbol = Symbol.for("level") as unknown as string; + const messageSymbol = Symbol.for("message") as unknown as string; + const splatSymbol = Symbol.for("splat") as unknown as string; + delete cleanMeta[levelSymbol]; + delete cleanMeta[messageSymbol]; + delete cleanMeta[splatSymbol]; + + // Broadcast log message to all connected WebSocket clients + try { + sendLogMessage({ + level: level as "error" | "warn" | "info" | "http" | "debug", + message: message as string, + timestamp: timestamp as string, + meta: Object.keys(cleanMeta).length > 0 ? cleanMeta : undefined, + }); + } catch { + // Silently fail if WebSocket is not available or broadcasting fails + // This prevents logger errors from cascading + } + + callback(); + } +} + diff --git a/apps/api/src/lib/websocket/index.ts b/apps/api/src/lib/websocket/index.ts index b3902ba..0a49ca8 100644 --- a/apps/api/src/lib/websocket/index.ts +++ b/apps/api/src/lib/websocket/index.ts @@ -4,12 +4,18 @@ import { logger } from "@/lib/utils"; interface ScanProgress { type: "scan:progress"; - phase: "scanning" | "fetching-metadata" | "fetching-episodes" | "saving"; + phase: "scanning" | "fetching-metadata" | "fetching-episodes" | "saving" | "discovering" | "batching" | "batch-complete"; progress: number; // 0-100 current: number; total: number; message: string; libraryId?: string; + scanJobId?: string; + batchItemComplete?: { + folderName: string; + itemsSaved: number; + totalItems: number; + }; } interface ScanComplete { @@ -17,15 +23,25 @@ interface ScanComplete { libraryId: string; totalItems: number; message: string; + scanJobId?: string; } interface ScanError { type: "scan:error"; libraryId?: string; + scanJobId?: string; error: string; } -type WebSocketMessage = ScanProgress | ScanComplete | ScanError; +interface LogMessage { + type: "log:message"; + level: "error" | "warn" | "info" | "http" | "debug"; + message: string; + timestamp: string; + meta?: Record; +} + +type WebSocketMessage = ScanProgress | ScanComplete | ScanError | LogMessage; // Module-level state let wss: WebSocketServer | null = null; @@ -107,6 +123,13 @@ export function sendScanError(data: Omit) { }); } +export function sendLogMessage(data: Omit) { + broadcast({ + type: "log:message", + ...data, + }); +} + export function getClientCount(): number { return clients.size; } @@ -128,7 +151,8 @@ export const wsManager = { sendScanProgress, sendScanComplete, sendScanError, + sendLogMessage, getClientCount, close: closeWebSocket, }; -export type { ScanProgress, ScanComplete, ScanError, WebSocketMessage }; +export type { ScanProgress, ScanComplete, ScanError, LogMessage, WebSocketMessage }; diff --git a/apps/api/src/routes/v1/index.ts b/apps/api/src/routes/v1/index.ts index 5496da1..877367e 100644 --- a/apps/api/src/routes/v1/index.ts +++ b/apps/api/src/routes/v1/index.ts @@ -6,6 +6,7 @@ import tvshowsRoutes from "../../domains/tvshows/tvshows.routes"; import streamRoutes from "../../domains/stream/stream.routes"; import settingsRoutes from "../../domains/settings/settings.routes"; import searchRoutes from "../../domains/search/search.routes"; +import logsRoutes from "../../domains/logs/logs.routes"; const router: Router = express.Router(); @@ -30,4 +31,7 @@ router.use("/stream", streamRoutes); // Settings routes router.use("/settings", settingsRoutes); +// Logs routes +router.use("/logs", logsRoutes); + export default router; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cc02405..b63a543 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -86,6 +86,9 @@ importers: winston: specifier: ^3.18.3 version: 3.18.3 + winston-transport: + specifier: ^4.9.0 + version: 4.9.0 ws: specifier: ^8.18.3 version: 8.18.3(bufferutil@4.0.9) From aa41a15361c6095c982516ae70407e2effaebc8f Mon Sep 17 00:00:00 2001 From: AlkenD Date: Tue, 11 Nov 2025 19:56:39 +0530 Subject: [PATCH 02/21] feat(scan): add batch scanning with job tracking and resume capability --- .../migration.sql | 38 ++ .../migration.sql | 3 + .../migration.sql | 2 + apps/api/prisma/schema.prisma | 50 +++ .../api/src/domains/movies/movies.services.ts | 6 + .../scan/helpers/batch-scanner.helper.ts | 394 ++++++++++++++++++ apps/api/src/domains/scan/helpers/index.ts | 4 + .../helpers/media-type-detector.helper.ts | 154 +++++++ .../scan/helpers/rate-limiter.helper.ts | 4 +- .../scan/helpers/scan-job-cleanup.helper.ts | 133 ++++++ .../domains/scan/helpers/timeout-helper.ts | 110 +++++ apps/api/src/domains/scan/scan.controller.ts | 196 ++++++++- apps/api/src/domains/scan/scan.routes.ts | 98 +++++ apps/api/src/domains/scan/scan.schema.ts | 6 + apps/api/src/domains/scan/scan.services.ts | 373 +++++++++++++++++ .../src/domains/stream/stream.controller.ts | 32 +- .../src/domains/tvshows/tvshows.services.ts | 6 + apps/api/src/index.ts | 114 ++++- apps/api/src/lib/database/index.ts | 2 +- .../src/lib/middleware/setup.middleware.ts | 18 + 20 files changed, 1715 insertions(+), 28 deletions(-) create mode 100644 apps/api/prisma/migrations/20251110191932_add_scan_job_tracking/migration.sql create mode 100644 apps/api/prisma/migrations/20251110194320_add_batch_tracking/migration.sql create mode 100644 apps/api/prisma/migrations/20251110220936_add_total_items_saved/migration.sql create mode 100644 apps/api/src/domains/scan/helpers/batch-scanner.helper.ts create mode 100644 apps/api/src/domains/scan/helpers/media-type-detector.helper.ts create mode 100644 apps/api/src/domains/scan/helpers/scan-job-cleanup.helper.ts create mode 100644 apps/api/src/domains/scan/helpers/timeout-helper.ts diff --git a/apps/api/prisma/migrations/20251110191932_add_scan_job_tracking/migration.sql b/apps/api/prisma/migrations/20251110191932_add_scan_job_tracking/migration.sql new file mode 100644 index 0000000..d4fe230 --- /dev/null +++ b/apps/api/prisma/migrations/20251110191932_add_scan_job_tracking/migration.sql @@ -0,0 +1,38 @@ +-- CreateEnum +CREATE TYPE "ScanJobStatus" AS ENUM ('PENDING', 'IN_PROGRESS', 'COMPLETED', 'FAILED', 'PAUSED'); + +-- CreateTable +CREATE TABLE "ScanJob" ( + "id" TEXT NOT NULL, + "libraryId" TEXT NOT NULL, + "scanPath" TEXT NOT NULL, + "mediaType" "MediaType" NOT NULL, + "status" "ScanJobStatus" NOT NULL DEFAULT 'PENDING', + "batchSize" INTEGER NOT NULL, + "totalFolders" INTEGER NOT NULL DEFAULT 0, + "processedCount" INTEGER NOT NULL DEFAULT 0, + "failedCount" INTEGER NOT NULL DEFAULT 0, + "processedFolders" TEXT NOT NULL DEFAULT '[]', + "failedFolders" TEXT NOT NULL DEFAULT '[]', + "pendingFolders" TEXT NOT NULL DEFAULT '[]', + "errorMessage" TEXT, + "startedAt" TIMESTAMP(3), + "completedAt" TIMESTAMP(3), + "lastBatchAt" TIMESTAMP(3), + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "ScanJob_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE INDEX "ScanJob_libraryId_idx" ON "ScanJob"("libraryId"); + +-- CreateIndex +CREATE INDEX "ScanJob_status_idx" ON "ScanJob"("status"); + +-- CreateIndex +CREATE INDEX "ScanJob_scanPath_idx" ON "ScanJob"("scanPath"); + +-- AddForeignKey +ALTER TABLE "ScanJob" ADD CONSTRAINT "ScanJob_libraryId_fkey" FOREIGN KEY ("libraryId") REFERENCES "Library"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/apps/api/prisma/migrations/20251110194320_add_batch_tracking/migration.sql b/apps/api/prisma/migrations/20251110194320_add_batch_tracking/migration.sql new file mode 100644 index 0000000..b28a5f4 --- /dev/null +++ b/apps/api/prisma/migrations/20251110194320_add_batch_tracking/migration.sql @@ -0,0 +1,3 @@ +-- AlterTable +ALTER TABLE "ScanJob" ADD COLUMN "currentBatch" INTEGER NOT NULL DEFAULT 0, +ADD COLUMN "totalBatches" INTEGER NOT NULL DEFAULT 0; diff --git a/apps/api/prisma/migrations/20251110220936_add_total_items_saved/migration.sql b/apps/api/prisma/migrations/20251110220936_add_total_items_saved/migration.sql new file mode 100644 index 0000000..f29633a --- /dev/null +++ b/apps/api/prisma/migrations/20251110220936_add_total_items_saved/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "ScanJob" ADD COLUMN "totalItemsSaved" INTEGER NOT NULL DEFAULT 0; diff --git a/apps/api/prisma/schema.prisma b/apps/api/prisma/schema.prisma index d64a053..2c3c8f1 100644 --- a/apps/api/prisma/schema.prisma +++ b/apps/api/prisma/schema.prisma @@ -256,6 +256,7 @@ model Library { children Library[] @relation("LibraryHierarchy") media MediaLibrary[] + scanJobs ScanJob[] @@index([slug]) @@index([parentId]) @@ -314,6 +315,55 @@ model ExternalId { @@index([externalId]) } +// ──────────────────────────── +// SCAN JOBS +// ──────────────────────────── + +enum ScanJobStatus { + PENDING + IN_PROGRESS + COMPLETED + FAILED + PAUSED +} + +model ScanJob { + id String @id @default(cuid()) + libraryId String + scanPath String + mediaType MediaType + status ScanJobStatus @default(PENDING) + batchSize Int // Number of folders per batch + + // Progress tracking + totalFolders Int @default(0) + totalBatches Int @default(0) // Total number of batches (calculated) + currentBatch Int @default(0) // Current batch number + processedCount Int @default(0) // Number of folders processed + failedCount Int @default(0) // Number of folders failed + totalItemsSaved Int @default(0) // Number of media items actually saved to DB + + // Folder tracking (JSON arrays) + processedFolders String @default("[]") // JSON array of processed folder paths + failedFolders String @default("[]") // JSON array of failed folder paths + pendingFolders String @default("[]") // JSON array of remaining folder paths + + errorMessage String? + + startedAt DateTime? + completedAt DateTime? + lastBatchAt DateTime? // Last batch completion time + + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + library Library @relation(fields: [libraryId], references: [id], onDelete: Cascade) + + @@index([libraryId]) + @@index([status]) + @@index([scanPath]) +} + // ──────────────────────────── // SETTINGS // ──────────────────────────── diff --git a/apps/api/src/domains/movies/movies.services.ts b/apps/api/src/domains/movies/movies.services.ts index 20b9769..509a1e4 100644 --- a/apps/api/src/domains/movies/movies.services.ts +++ b/apps/api/src/domains/movies/movies.services.ts @@ -8,6 +8,12 @@ export const moviesServices = { include: { media: true, }, + orderBy: { + media: { + createdAt: 'desc', // Most recent first + }, + }, + take: 10, // Limit to 10 most recent }); return serializeBigInt(movies) as MoviesListResponse; }, diff --git a/apps/api/src/domains/scan/helpers/batch-scanner.helper.ts b/apps/api/src/domains/scan/helpers/batch-scanner.helper.ts new file mode 100644 index 0000000..1fd7208 --- /dev/null +++ b/apps/api/src/domains/scan/helpers/batch-scanner.helper.ts @@ -0,0 +1,394 @@ +/** + * Batch scanning utilities + * Handles scanning large directories in manageable batches + */ + +import { readdir } from "fs/promises"; +import { join } from "path"; +import { logger } from "@/lib/utils"; +import { MediaType, ScanJobStatus } from "@/lib/database"; +import prisma from "@/lib/database/prisma"; +import { collectMediaEntries } from "./file-scanner.helper"; +import { + fetchExistingMetadata, + fetchMetadataForEntries, + fetchSeasonMetadata, + saveMediaToDatabase, + createRateLimiter, + withTimeoutAndRetry, +} from "./index"; +import { wsManager } from "@/lib/websocket"; +import type { TmdbMetadata } from "../scan.types"; +import type { TmdbSeasonMetadata } from "@/lib/providers/tmdb/tmdb.types"; + +/** + * Discover top-level folders to batch process + * For TV shows: Returns show folders + * For movies: Returns movie folders or files depending on structure + */ +export async function discoverFoldersToScan( + rootPath: string, + mediaType: "movie" | "tv" +): Promise { + return withTimeoutAndRetry( + async () => { + logger.info(`🔍 Listing directory: ${rootPath} (this may take a while on slow mounts)...`); + const entries = await readdir(rootPath, { withFileTypes: true }); + const folders: string[] = []; + + for (const entry of entries) { + // Skip hidden files and system files + if (entry.name.startsWith(".") || entry.name.startsWith("@")) { + continue; + } + + if (entry.isDirectory()) { + folders.push(entry.name); + } + } + + logger.info( + `📂 Discovered ${folders.length} ${mediaType === "tv" ? "show" : "movie"} folders to scan` + ); + return folders; + }, + { + timeoutMs: 600000, // 10 minutes timeout for very slow FTP mounts (initial folder discovery can be slow) + maxRetries: 2, // Only retry twice (each retry could take 10 min) + operationName: `Discover folders in ${rootPath}`, + } + ); +} + +/** + * Create or get existing scan job + */ +export async function createScanJob( + libraryId: string, + scanPath: string, + mediaType: MediaType, + folders: string[] +): Promise { + // Determine batch size based on media type + const batchSize = mediaType === MediaType.TV_SHOW ? 5 : 25; + + // Calculate total number of batches + const totalBatches = Math.ceil(folders.length / batchSize); + + const scanJob = await prisma.scanJob.create({ + data: { + libraryId, + scanPath, + mediaType, + status: ScanJobStatus.PENDING, + batchSize, + totalFolders: folders.length, + totalBatches, + currentBatch: 0, + pendingFolders: JSON.stringify(folders), + startedAt: new Date(), + }, + }); + + logger.info( + `📝 Created scan job ${scanJob.id} for ${folders.length} folders (${totalBatches} batches of ${batchSize})` + ); + return scanJob.id; +} + +/** + * Get the next batch of folders to process + */ +export async function getNextBatch(scanJobId: string): Promise { + const scanJob = await prisma.scanJob.findUnique({ + where: { id: scanJobId }, + }); + + if (!scanJob) { + throw new Error(`Scan job ${scanJobId} not found`); + } + + if (scanJob.status === ScanJobStatus.COMPLETED) { + return null; + } + + const pendingFolders: string[] = JSON.parse(scanJob.pendingFolders); + + if (pendingFolders.length === 0) { + return null; + } + + const batch = pendingFolders.slice(0, scanJob.batchSize); + return batch; +} + +/** + * Mark a batch as processed + */ +export async function markBatchProcessed( + scanJobId: string, + processedFolderNames: string[], + failedFolderNames: string[] = [], + itemsSaved: number = 0 +): Promise { + const scanJob = await prisma.scanJob.findUnique({ + where: { id: scanJobId }, + }); + + if (!scanJob) { + throw new Error(`Scan job ${scanJobId} not found`); + } + + const pendingFolders: string[] = JSON.parse(scanJob.pendingFolders); + const processedFolders: string[] = JSON.parse(scanJob.processedFolders); + const failedFolders: string[] = JSON.parse(scanJob.failedFolders); + + // Remove processed folders from pending + const newPendingFolders = pendingFolders.filter( + (f) => !processedFolderNames.includes(f) && !failedFolderNames.includes(f) + ); + + // Add to processed/failed lists + processedFolders.push(...processedFolderNames); + failedFolders.push(...failedFolderNames); + + const totalProcessed = processedFolders.length + failedFolders.length; + const isComplete = newPendingFolders.length === 0; + + // Increment current batch number + const newCurrentBatch = scanJob.currentBatch + 1; + + const newTotalItemsSaved = scanJob.totalItemsSaved + itemsSaved; + + await prisma.scanJob.update({ + where: { id: scanJobId }, + data: { + pendingFolders: JSON.stringify(newPendingFolders), + processedFolders: JSON.stringify(processedFolders), + failedFolders: JSON.stringify(failedFolders), + processedCount: processedFolders.length, + failedCount: failedFolders.length, + totalItemsSaved: newTotalItemsSaved, + currentBatch: newCurrentBatch, + lastBatchAt: new Date(), + status: isComplete ? ScanJobStatus.COMPLETED : ScanJobStatus.IN_PROGRESS, + completedAt: isComplete ? new Date() : undefined, + }, + }); + + logger.info( + `✅ Batch ${newCurrentBatch}/${scanJob.totalBatches} processed: ${processedFolderNames.length} success, ${failedFolderNames.length} failed (${totalProcessed}/${scanJob.totalFolders} folders, ${newTotalItemsSaved} items saved)` + ); +} + +/** + * Process a single folder batch + */ +export async function processFolderBatch( + scanJobId: string, + folderNames: string[], + options: { + rootPath: string; + mediaType: "movie" | "tv"; + tmdbApiKey: string; + libraryId: string; + maxDepth: number; + fileExtensions: string[]; + rescan?: boolean; + originalPath?: string; + } +): Promise<{ + processedFolders: string[]; + failedFolders: string[]; + totalSaved: number; +}> { + const { + rootPath, + mediaType, + tmdbApiKey, + libraryId, + maxDepth, + fileExtensions, + rescan = false, + originalPath, + } = options; + + const rateLimiter = createRateLimiter(); + const metadataCache = new Map(); + const episodeMetadataCache = new Map(); + + const processedFolders: string[] = []; + const failedFolders: string[] = []; + let totalSaved = 0; + + // Get scan job for total folder count + const scanJob = await prisma.scanJob.findUnique({ + where: { id: scanJobId }, + }); + + const totalFolders = scanJob?.totalFolders || folderNames.length; + const currentOffset = scanJob ? (scanJob.processedCount + scanJob.failedCount) : 0; + + for (const folderName of folderNames) { + const folderPath = join(rootPath, folderName); + + try { + logger.info(`\n📁 Processing: ${folderName}`); + + // Calculate overall progress (not just batch progress) + const overallCurrent = currentOffset + processedFolders.length + failedFolders.length; + const overallProgress = Math.floor((overallCurrent / totalFolders) * 100); + + wsManager.sendScanProgress({ + phase: "scanning", + progress: overallProgress, + current: overallCurrent, + total: totalFolders, + message: `Scanning: ${folderName}`, + libraryId, + scanJobId, + }); + + // Step 1: Collect media entries for this folder (with timeout and retry for slow mounts) + const mediaEntries = await withTimeoutAndRetry( + () => + collectMediaEntries(folderPath, { + maxDepth, + mediaType, + fileExtensions, + }), + { + timeoutMs: 300000, // 5 minutes timeout per folder for very slow mounts + maxRetries: 2, // Retry twice if it fails + operationName: `Scan folder: ${folderName}`, + } + ); + + if (mediaEntries.length === 0) { + logger.warn(`⚠️ No media found in ${folderName}, skipping`); + processedFolders.push(folderName); + continue; + } + + logger.info(`Found ${mediaEntries.length} media items in ${folderName}`); + + // Step 2: Fetch existing metadata if not rescanning + let existingMetadataMap = new Map(); + if (!rescan) { + const tmdbIdsToCheck = mediaEntries + .filter((e) => e.extractedIds.tmdbId) + .map((e) => e.extractedIds.tmdbId!); + + existingMetadataMap = await fetchExistingMetadata(tmdbIdsToCheck, libraryId); + existingMetadataMap.forEach((metadata, tmdbId) => { + metadataCache.set(tmdbId, metadata); + }); + } + + // Step 3: Fetch metadata from TMDB + const metadataProgress = Math.floor(((currentOffset + processedFolders.length) / totalFolders) * 100); + wsManager.sendScanProgress({ + phase: "fetching-metadata", + progress: metadataProgress, + current: currentOffset + processedFolders.length, + total: totalFolders, + message: `Fetching metadata: ${folderName}`, + libraryId, + scanJobId, + }); + + await fetchMetadataForEntries(mediaEntries, { + mediaType, + tmdbApiKey, + rateLimiter, + metadataCache, + existingMetadataMap, + libraryId, + }); + + // Step 4: Fetch season metadata for TV shows + if (mediaType === "tv") { + await fetchSeasonMetadata(mediaEntries, { + tmdbApiKey, + rateLimiter, + episodeMetadataCache, + libraryId, + }); + } + + // Step 5: Save to database + const savingProgress = Math.floor(((currentOffset + processedFolders.length) / totalFolders) * 100); + wsManager.sendScanProgress({ + phase: "saving", + progress: savingProgress, + current: currentOffset + processedFolders.length, + total: totalFolders, + message: `Saving: ${folderName}`, + libraryId, + scanJobId, + }); + + let savedCount = 0; + for (const mediaEntry of mediaEntries) { + if (!mediaEntry.isDirectory) { + try { + await saveMediaToDatabase( + mediaEntry, + mediaType, + tmdbApiKey, + episodeMetadataCache, + libraryId, + originalPath + ); + savedCount++; + } catch (error) { + logger.error( + `Failed to save ${mediaEntry.name}: ${error instanceof Error ? error.message : error}` + ); + } + } + } + + totalSaved += savedCount; + processedFolders.push(folderName); + + logger.info(`✅ ${folderName}: Saved ${savedCount}/${mediaEntries.length} items`); + + // Send batch item completion + const completionCurrent = currentOffset + processedFolders.length; + const completionProgress = Math.floor((completionCurrent / totalFolders) * 100); + + wsManager.sendScanProgress({ + phase: "batch-complete", + progress: completionProgress, + current: completionCurrent, + total: totalFolders, + message: `Completed: ${folderName} (${savedCount} items)`, + libraryId, + scanJobId, + batchItemComplete: { + folderName, + itemsSaved: savedCount, + totalItems: mediaEntries.length, + }, + }); + } catch (error) { + logger.error( + `❌ Failed to process ${folderName}: ${error instanceof Error ? error.message : error}` + ); + failedFolders.push(folderName); + + wsManager.sendScanError({ + error: `Failed to process ${folderName}: ${error instanceof Error ? error.message : String(error)}`, + scanJobId, + }); + } + } + + return { + processedFolders, + failedFolders, + totalSaved, + }; +} + diff --git a/apps/api/src/domains/scan/helpers/index.ts b/apps/api/src/domains/scan/helpers/index.ts index 45918eb..d910c87 100644 --- a/apps/api/src/domains/scan/helpers/index.ts +++ b/apps/api/src/domains/scan/helpers/index.ts @@ -9,4 +9,8 @@ export * from "./file-scanner.helper"; export * from "./metadata-fetcher.helper"; export * from "./database.helper"; export * from "./path-validator.helper"; +export * from "./batch-scanner.helper"; +export * from "./timeout-helper"; +export * from "./scan-job-cleanup.helper"; +export * from "./media-type-detector.helper"; diff --git a/apps/api/src/domains/scan/helpers/media-type-detector.helper.ts b/apps/api/src/domains/scan/helpers/media-type-detector.helper.ts new file mode 100644 index 0000000..725b9e1 --- /dev/null +++ b/apps/api/src/domains/scan/helpers/media-type-detector.helper.ts @@ -0,0 +1,154 @@ +/** + * Detects if a directory structure matches the expected media type + * Helps prevent accidentally scanning movies as TV shows and vice versa + */ + +import { readdirSync } from "fs"; +import { join } from "path"; +import { logger } from "@/lib/utils"; + +interface MediaTypeHints { + hasSeasonFolders: number; // Count of folders with "Season" pattern + hasEpisodeFiles: number; // Count of files with S##E## pattern + hasMovieFiles: number; // Count of files with year pattern (2020) + avgDepth: number; // Average nesting depth + sampleFiles: string[]; // Sample filenames for logging +} + +/** + * Analyze directory structure to detect media type hints + */ +export function analyzeDirectoryStructure( + rootPath: string, + maxSamples: number = 20 +): MediaTypeHints { + const hints: MediaTypeHints = { + hasSeasonFolders: 0, + hasEpisodeFiles: 0, + hasMovieFiles: 0, + avgDepth: 0, + sampleFiles: [], + }; + + const depths: number[] = []; + const seasonPattern = /season\s*\d+/i; + const episodePattern = /S\d{1,2}E\d{1,2}/i; + const yearPattern = /\(?\d{4}\)?/; // (2020) or 2020 + + function scan(currentPath: string, depth: number = 0): void { + // Limit depth to prevent long scans + if (depth > 4) return; + + // Limit total samples + if (hints.sampleFiles.length >= maxSamples) return; + + try { + const entries = readdirSync(currentPath, { withFileTypes: true }); + + for (const entry of entries) { + if (hints.sampleFiles.length >= maxSamples) break; + + const fullPath = join(currentPath, entry.name); + + if (entry.isDirectory()) { + // Check for season folders + if (seasonPattern.test(entry.name)) { + hints.hasSeasonFolders++; + } + scan(fullPath, depth + 1); + } else { + // Check file patterns + const filename = entry.name.toLowerCase(); + + // Video extensions only + if (!/\.(mkv|mp4|avi|mov)$/i.test(filename)) continue; + + depths.push(depth); + hints.sampleFiles.push(entry.name); + + if (episodePattern.test(entry.name)) { + hints.hasEpisodeFiles++; + } + if (yearPattern.test(entry.name)) { + hints.hasMovieFiles++; + } + } + } + } catch (error) { + // Silently skip inaccessible directories + } + } + + scan(rootPath); + + // Calculate average depth + if (depths.length > 0) { + hints.avgDepth = depths.reduce((a, b) => a + b, 0) / depths.length; + } + + return hints; +} + +/** + * Detect if specified media type matches directory structure + * Returns warning if mismatch detected + */ +export function detectMediaTypeMismatch( + rootPath: string, + specifiedType: "movie" | "tv" +): { mismatch: boolean; warning?: string; confidence: number } { + logger.info(`🔍 Analyzing directory structure for media type validation...`); + + const hints = analyzeDirectoryStructure(rootPath); + + logger.info(` Season folders: ${hints.hasSeasonFolders}`); + logger.info(` Episode-pattern files: ${hints.hasEpisodeFiles}`); + logger.info(` Movie-pattern files: ${hints.hasMovieFiles}`); + logger.info(` Average depth: ${hints.avgDepth.toFixed(1)}`); + + if (hints.sampleFiles.length > 0) { + logger.info(` Sample files: ${hints.sampleFiles.slice(0, 3).join(", ")}`); + } + + // Calculate confidence scores (0-100) + const tvScore = + (hints.hasSeasonFolders > 0 ? 40 : 0) + + (hints.hasEpisodeFiles > 5 ? 30 : hints.hasEpisodeFiles * 6) + + (hints.avgDepth >= 3 ? 30 : 0); + + const movieScore = + (hints.hasMovieFiles > 5 ? 40 : hints.hasMovieFiles * 8) + + (hints.avgDepth <= 2 ? 30 : 0) + + (hints.hasSeasonFolders === 0 ? 30 : 0); + + // Determine mismatch + if (specifiedType === "tv") { + // Specified TV, but looks like movies + if (movieScore > tvScore + 20) { + return { + mismatch: true, + warning: `⚠️ WARNING: You specified "TV Shows" but this directory looks like MOVIES!\n` + + ` Detected: ${hints.hasMovieFiles} movie-pattern files, avg depth ${hints.avgDepth.toFixed(1)}, ${hints.hasSeasonFolders} season folders\n` + + ` This may result in incorrect metadata matching. Consider scanning as "movie" type instead.`, + confidence: movieScore, + }; + } + } else if (specifiedType === "movie") { + // Specified movie, but looks like TV + if (tvScore > movieScore + 20) { + return { + mismatch: true, + warning: `⚠️ WARNING: You specified "Movies" but this directory looks like TV SHOWS!\n` + + ` Detected: ${hints.hasSeasonFolders} season folders, ${hints.hasEpisodeFiles} episode files, avg depth ${hints.avgDepth.toFixed(1)}\n` + + ` This may result in incorrect metadata matching. Consider scanning as "tv" type instead.`, + confidence: tvScore, + }; + } + } + + return { + mismatch: false, + confidence: specifiedType === "tv" ? tvScore : movieScore, + }; +} + diff --git a/apps/api/src/domains/scan/helpers/rate-limiter.helper.ts b/apps/api/src/domains/scan/helpers/rate-limiter.helper.ts index 5b4cfb9..251f331 100644 --- a/apps/api/src/domains/scan/helpers/rate-limiter.helper.ts +++ b/apps/api/src/domains/scan/helpers/rate-limiter.helper.ts @@ -15,8 +15,8 @@ export interface RateLimiter { * @returns RateLimiter instance */ export function createRateLimiter( - maxRequestsPer10Sec: number = 38, - concurrency: number = 10 + maxRequestsPer10Sec: number = 30, // Reduced from 38 to be more conservative + concurrency: number = 8 // Reduced from 10 to avoid overwhelming TMDB ): RateLimiter { const queue: Array<() => Promise> = []; let processing = false; diff --git a/apps/api/src/domains/scan/helpers/scan-job-cleanup.helper.ts b/apps/api/src/domains/scan/helpers/scan-job-cleanup.helper.ts new file mode 100644 index 0000000..5100943 --- /dev/null +++ b/apps/api/src/domains/scan/helpers/scan-job-cleanup.helper.ts @@ -0,0 +1,133 @@ +/** + * Scan job cleanup utilities + * Handles zombie/stale scan jobs that may be stuck after crashes + */ + +import { logger } from "@/lib/utils"; +import prisma from "@/lib/database/prisma"; +import { ScanJobStatus } from "@/lib/database"; + +/** + * Mark stale scan jobs as FAILED + * A scan job is considered stale if it's been IN_PROGRESS for more than the specified timeout + */ +export async function cleanupStaleJobs( + staleTimeoutMs: number = 6 * 60 * 60 * 1000 // 6 hours default +): Promise { + try { + const staleThreshold = new Date(Date.now() - staleTimeoutMs); + + // Find jobs that have been in progress for too long + const staleJobs = await prisma.scanJob.findMany({ + where: { + status: ScanJobStatus.IN_PROGRESS, + OR: [ + // Job started but no batch completed recently + { + lastBatchAt: { + lt: staleThreshold, + }, + }, + // Job started but never completed a batch + { + lastBatchAt: null, + startedAt: { + lt: staleThreshold, + }, + }, + ], + }, + include: { + library: { + select: { + name: true, + }, + }, + }, + }); + + if (staleJobs.length === 0) { + logger.debug("No stale scan jobs found"); + return 0; + } + + // Mark them as FAILED + const updatePromises = staleJobs.map((job) => + prisma.scanJob.update({ + where: { id: job.id }, + data: { + status: ScanJobStatus.FAILED, + errorMessage: `Scan job marked as failed due to inactivity (timeout: ${staleTimeoutMs / 1000 / 60} minutes). Last activity: ${job.lastBatchAt?.toISOString() || job.startedAt?.toISOString() || "unknown"}`, + completedAt: new Date(), + }, + }) + ); + + await Promise.all(updatePromises); + + logger.warn( + `🧹 Cleaned up ${staleJobs.length} stale scan job(s):` + ); + staleJobs.forEach((job) => { + logger.warn( + ` - ${job.library.name} (${job.currentBatch}/${job.totalBatches} batches, ${job.processedCount}/${job.totalFolders} folders)` + ); + }); + + return staleJobs.length; + } catch (error) { + logger.error( + `Failed to cleanup stale jobs: ${error instanceof Error ? error.message : error}` + ); + return 0; + } +} + +/** + * Get scan job status with metadata + */ +export async function getScanJobStatus(scanJobId: string) { + const job = await prisma.scanJob.findUnique({ + where: { id: scanJobId }, + include: { + library: { + select: { + id: true, + name: true, + }, + }, + }, + }); + + if (!job) { + return null; + } + + const progressPercent = + job.totalFolders > 0 + ? Math.floor( + ((job.processedCount + job.failedCount) / job.totalFolders) * 100 + ) + : 0; + + return { + id: job.id, + status: job.status, + library: job.library, + progress: { + currentBatch: job.currentBatch, + totalBatches: job.totalBatches, + processedFolders: job.processedCount, + failedFolders: job.failedCount, + totalFolders: job.totalFolders, + percentComplete: progressPercent, + }, + timestamps: { + startedAt: job.startedAt, + lastBatchAt: job.lastBatchAt, + completedAt: job.completedAt, + }, + error: job.errorMessage, + }; +} + diff --git a/apps/api/src/domains/scan/helpers/timeout-helper.ts b/apps/api/src/domains/scan/helpers/timeout-helper.ts new file mode 100644 index 0000000..b43914f --- /dev/null +++ b/apps/api/src/domains/scan/helpers/timeout-helper.ts @@ -0,0 +1,110 @@ +/** + * Timeout utilities for handling slow/unresponsive mounted drives + * Works with any mount type: FTP, SMB, NFS, slow USB, network drives, etc. + */ + +import { logger } from "@/lib/utils"; + +/** + * Execute a promise with a timeout + * Useful for operations on slow or unresponsive mounted drives + */ +export async function withTimeout( + promise: Promise, + timeoutMs: number, + operationName: string +): Promise { + let timeoutHandle: NodeJS.Timeout; + + const timeoutPromise = new Promise((_, reject) => { + timeoutHandle = setTimeout(() => { + reject( + new Error( + `Operation "${operationName}" timed out after ${timeoutMs}ms. This may indicate a slow or unresponsive mounted drive.` + ) + ); + }, timeoutMs); + }); + + try { + const result = await Promise.race([promise, timeoutPromise]); + clearTimeout(timeoutHandle!); + return result; + } catch (error) { + clearTimeout(timeoutHandle!); + throw error; + } +} + +/** + * Retry an operation with exponential backoff + * Useful for transient failures on mounted drives + */ +export async function withRetry( + operation: () => Promise, + options: { + maxRetries?: number; + initialDelayMs?: number; + maxDelayMs?: number; + operationName: string; + } +): Promise { + const { + maxRetries = 3, + initialDelayMs = 1000, + maxDelayMs = 10000, + operationName, + } = options; + + let lastError: Error | unknown; + + for (let attempt = 0; attempt <= maxRetries; attempt++) { + try { + return await operation(); + } catch (error) { + lastError = error; + + if (attempt === maxRetries) { + logger.error( + `Operation "${operationName}" failed after ${maxRetries + 1} attempts` + ); + throw error; + } + + // Calculate delay with exponential backoff + const delay = Math.min(initialDelayMs * Math.pow(2, attempt), maxDelayMs); + + logger.warn( + `Operation "${operationName}" failed (attempt ${attempt + 1}/${maxRetries + 1}), retrying in ${delay}ms... Error: ${error instanceof Error ? error.message : String(error)}` + ); + + await new Promise((resolve) => setTimeout(resolve, delay)); + } + } + + throw lastError; +} + +/** + * Execute operation with both timeout and retry logic + * Best for operations on potentially slow/unreliable mounted drives + */ +export async function withTimeoutAndRetry( + operation: () => Promise, + options: { + timeoutMs?: number; + maxRetries?: number; + operationName: string; + } +): Promise { + const { timeoutMs = 60000, maxRetries = 3, operationName } = options; + + return withRetry( + () => withTimeout(operation(), timeoutMs, operationName), + { + maxRetries, + operationName, + } + ); +} + diff --git a/apps/api/src/domains/scan/scan.controller.ts b/apps/api/src/domains/scan/scan.controller.ts index 0a1dc1d..9bbda40 100644 --- a/apps/api/src/domains/scan/scan.controller.ts +++ b/apps/api/src/domains/scan/scan.controller.ts @@ -6,16 +6,36 @@ import { z } from "zod"; import { logger, mapHostToContainerPath, - sendSuccess, asyncHandler, ValidationError, + sendSuccess, } from "@/lib/utils"; import { wsManager } from "@/lib/websocket"; -import { isDangerousRootPath, isMediaRootPath } from "./helpers/path-validator.helper"; +import { isDangerousRootPath, isMediaRootPath, detectMediaTypeMismatch } from "./helpers"; import { existsSync, statSync } from "fs"; type ScanPathRequest = z.infer; +// Scan queue to prevent overwhelming slow mounts +let activeScan: Promise | null = null; +const scanQueue: Array<() => Promise> = []; + +async function processQueue() { + if (activeScan) { + logger.info("📋 Scan queued - another scan is in progress"); + return; + } + + if (scanQueue.length === 0) return; + + const nextScan = scanQueue.shift()!; + activeScan = nextScan() + .finally(() => { + activeScan = null; + processQueue(); // Process next in queue + }); +} + export const scanControllers = { /** * Scan a path for media files @@ -98,33 +118,175 @@ export const scanControllers = { logger.info(`Scanning path: ${mappedPath} (original: ${path})`); - // Start the scan in the background (don't await) - // This allows us to return immediately while the scan progresses - scanServices.post(mappedPath, finalOptions) + // Detect media type mismatch (warn if directory structure doesn't match specified type) + const effectiveMediaType = mediaType || 'movie'; + const mismatchDetection = detectMediaTypeMismatch(mappedPath, effectiveMediaType); + if (mismatchDetection.mismatch) { + logger.warn(mismatchDetection.warning); + // Don't throw error, just log warning - user might know what they're doing + } else { + logger.info(`✓ Media type validation passed (confidence: ${mismatchDetection.confidence}%)`); + } + + // Determine if we should use batch scanning + // Use batch scanning if: + // 1. Explicitly requested via options.batchScan = true + // 2. OR it's a TV show library (5 per batch) + // 3. OR it's a movie library (25 per batch - better for large/slow storage) + // Only disable if explicitly set to false + const useBatchScan = options?.batchScan !== false; + + if (useBatchScan) { + logger.info(`🔄 Using batch scanning mode (${mediaType === 'tv' ? '5' : '25'} folders per batch)`); + } else { + logger.info(`📁 Using full directory scanning mode`); + } + + // Queue the scan to prevent overwhelming slow mounts + const scanTask = async () => { + const scanPromise = useBatchScan + ? scanServices.postBatched(mappedPath, finalOptions) + : scanServices.post(mappedPath, finalOptions); + + return scanPromise + .then((result) => { + if ('totalFiles' in result) { + logger.info( + `✅ Scan completed: ${result.libraryName} (${result.totalSaved}/${result.totalFiles} items)` + ); + } else { + logger.info( + `✅ Batch scan completed: ${result.libraryName}` + ); + logger.info( + ` 📁 Folders: ${result.foldersProcessed}/${result.totalFolders} processed, ${result.foldersFailed} failed` + ); + logger.info( + ` 🎬 Media Items: ${result.totalItemsSaved} saved to database` + ); + } + }) + .catch((error) => { + // Send error via WebSocket + const errorMessage = + error instanceof Error ? error.message : "Failed to scan path"; + logger.error(`❌ Scan failed: ${errorMessage}`); + wsManager.sendScanError({ + error: errorMessage, + }); + }); + }; + + // Add to queue or start immediately + if (activeScan) { + scanQueue.push(scanTask); + logger.info(`📋 Scan queued (${scanQueue.length} in queue)`); + return sendSuccess( + res, + { + path: path, + mediaType: options?.mediaType, + queued: true, + queuePosition: scanQueue.length, + }, + 202, + `Scan queued. ${scanQueue.length} scan(s) ahead in queue. Progress will be sent via WebSocket when started.` + ); + } else { + activeScan = scanTask(); + processQueue(); // Start processing queue + + return sendSuccess( + res, + { + path: path, + mediaType: options?.mediaType, + queued: false, + }, + 202, + "Scan started successfully. Progress will be sent via WebSocket." + ); + } + }), + + /** + * Resume a failed or paused scan job + */ + resumeScan: asyncHandler(async (req: Request, res: Response) => { + const { scanJobId } = req.params; + + if (!scanJobId) { + throw new ValidationError("Scan job ID is required"); + } + + // Get TMDB API key from database settings + const tmdbApiKey = await getTmdbApiKey(); + if (!tmdbApiKey) { + throw new ValidationError("TMDB API key is required. Please configure it in settings."); + } + + logger.info(`Resuming scan job: ${scanJobId}`); + + // Start the resume in the background (don't await) + scanServices.resumeScanJob(scanJobId, tmdbApiKey) .then((result) => { logger.info( - `✅ Scan completed: ${result.libraryName} (${result.totalSaved}/${result.totalFiles} items)` + `✅ Resumed scan completed: ${result.libraryName}` + ); + logger.info( + ` 📁 Folders: ${result.foldersProcessed}/${result.totalFolders} processed, ${result.foldersFailed} failed` + ); + logger.info( + ` 🎬 Media Items: ${result.totalItemsSaved} total in database` ); }) .catch((error) => { // Send error via WebSocket const errorMessage = - error instanceof Error ? error.message : "Failed to scan path"; - logger.error(`❌ Scan failed: ${errorMessage}`); + error instanceof Error ? error.message : "Failed to resume scan"; + logger.error(`❌ Resume scan failed: ${errorMessage}`); wsManager.sendScanError({ error: errorMessage, + scanJobId, }); }); // Return immediately with 202 Accepted - // Client will receive progress updates via WebSocket - return res.status(202).json({ - success: true, - message: "Scan started successfully. Progress will be sent via WebSocket.", - data: { - path: path, - mediaType: options?.mediaType, - }, - }); + return sendSuccess( + res, + { scanJobId }, + 202, + "Scan resumed successfully. Progress will be sent via WebSocket." + ); + }), + + /** + * Get scan job status + */ + getJobStatus: asyncHandler(async (req: Request, res: Response) => { + const { scanJobId } = req.params; + + if (!scanJobId) { + throw new ValidationError("Scan job ID is required"); + } + + const status = await scanServices.getJobStatus(scanJobId); + + if (!status) { + throw new ValidationError(`Scan job ${scanJobId} not found`); + } + + return sendSuccess(res, status); + }), + + /** + * Cleanup stale scan jobs + */ + cleanupStaleJobs: asyncHandler(async (req: Request, res: Response) => { + logger.info("Manual cleanup of stale scan jobs requested"); + + const result = await scanServices.cleanupStaleJobs(); + + return sendSuccess(res, result, 200, "Stale scan jobs cleaned up successfully"); }), }; diff --git a/apps/api/src/domains/scan/scan.routes.ts b/apps/api/src/domains/scan/scan.routes.ts index 3b268b2..a4af214 100644 --- a/apps/api/src/domains/scan/scan.routes.ts +++ b/apps/api/src/domains/scan/scan.routes.ts @@ -148,4 +148,102 @@ const router: Router = express.Router(); */ router.post("/path", validateBody(scanPathSchema), scanControllers.post); +/** + * @swagger + * /api/v1/scan/resume/{scanJobId}: + * post: + * summary: Resume a failed or paused scan job + * description: | + * Resumes a scan job that was previously paused or failed. + * - Continues processing from where it left off + * - Only processes remaining unscanned folders + * - Sends progress updates via WebSocket + * tags: [Scan] + * parameters: + * - in: path + * name: scanJobId + * required: true + * schema: + * type: string + * description: The ID of the scan job to resume + * example: "clxxxx1234567890abcdefgh" + * responses: + * 202: + * description: Scan resumed successfully + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * example: true + * message: + * type: string + * example: "Scan resumed successfully. Progress will be sent via WebSocket." + * data: + * type: object + * properties: + * scanJobId: + * type: string + * example: "clxxxx1234567890abcdefgh" + * 400: + * description: Bad request - Invalid scan job ID or cannot resume + * 404: + * description: Scan job not found + * 500: + * description: Internal server error + */ +router.post("/resume/:scanJobId", scanControllers.resumeScan); + +/** + * @swagger + * /api/v1/scan/job/{scanJobId}: + * get: + * summary: Get scan job status + * description: Get detailed status information about a scan job + * tags: [Scan] + * parameters: + * - in: path + * name: scanJobId + * required: true + * schema: + * type: string + * responses: + * 200: + * description: Scan job status retrieved successfully + * 404: + * description: Scan job not found + */ +router.get("/job/:scanJobId", scanControllers.getJobStatus); + +/** + * @swagger + * /api/v1/scan/cleanup: + * post: + * summary: Cleanup stale scan jobs + * description: | + * Manually trigger cleanup of scan jobs that have been stuck in IN_PROGRESS state. + * Useful after API crashes or unexpected shutdowns. + * tags: [Scan] + * responses: + * 200: + * description: Cleanup completed + * content: + * application/json: + * schema: + * type: object + * properties: + * success: + * type: boolean + * data: + * type: object + * properties: + * cleanedCount: + * type: number + * message: + * type: string + */ +router.post("/cleanup", scanControllers.cleanupStaleJobs); + export default router; diff --git a/apps/api/src/domains/scan/scan.schema.ts b/apps/api/src/domains/scan/scan.schema.ts index 04284b6..d409687 100644 --- a/apps/api/src/domains/scan/scan.schema.ts +++ b/apps/api/src/domains/scan/scan.schema.ts @@ -53,6 +53,12 @@ export const scanPathSchema = z.object({ fileExtensions: z.array(sanitizedStringSchema).max(20).optional(), libraryName: z.string().min(1).max(100).optional(), rescan: z.boolean().optional(), + batchScan: z + .boolean() + .optional() + .describe( + "Enable batch scanning mode for large libraries. Automatically enabled for TV shows. Batches: 5 shows or 25 movies per batch." + ), }) .optional(), }); diff --git a/apps/api/src/domains/scan/scan.services.ts b/apps/api/src/domains/scan/scan.services.ts index cc7e825..eb818a5 100644 --- a/apps/api/src/domains/scan/scan.services.ts +++ b/apps/api/src/domains/scan/scan.services.ts @@ -12,6 +12,13 @@ import { fetchMetadataForEntries, fetchSeasonMetadata, saveMediaToDatabase, + discoverFoldersToScan, + createScanJob, + getNextBatch, + markBatchProcessed, + processFolderBatch, + cleanupStaleJobs, + getScanJobStatus, } from "./helpers"; export const scanServices = { @@ -265,4 +272,370 @@ export const scanServices = { }, }; }, + + /** + * Batch scanning service - processes large libraries in manageable batches + * Ideal for remote/slow storage (FTP, SMB, etc.) + */ + postBatched: async ( + rootPath: string, + options: { + maxDepth?: number; + tmdbApiKey: string; + mediaType?: "movie" | "tv"; + fileExtensions?: string[]; + libraryName?: string; + rescan?: boolean; + originalPath?: string; + } + ) => { + const { + maxDepth, + tmdbApiKey, + mediaType = "movie", + fileExtensions, + libraryName, + rescan = false, + originalPath, + } = options; + + // Set reasonable default maxDepth based on media type if not provided + const defaultMaxDepth = mediaType === "tv" ? 4 : 2; + const effectiveMaxDepth = maxDepth ?? defaultMaxDepth; + + if (!tmdbApiKey) { + throw new Error("TMDB API key is required"); + } + + // Use default extensions if none provided or if empty array + const finalFileExtensions = + fileExtensions && fileExtensions.length > 0 + ? fileExtensions + : getDefaultVideoExtensions(); + + // Use original path for library name and database storage + const displayPath = originalPath || rootPath; + const finalLibraryName = libraryName || `Library - ${displayPath}`; + const librarySlug = finalLibraryName + .toLowerCase() + .replace(/[^a-z0-9]+/g, "-") + .replace(/(^-|-$)/g, ""); + + logger.info(`📚 Creating/getting library: ${finalLibraryName}`); + + const library = await prisma.library.upsert({ + where: { slug: librarySlug }, + update: { + name: finalLibraryName, + libraryPath: displayPath, + libraryType: mediaType === "tv" ? MediaType.TV_SHOW : MediaType.MOVIE, + isLibrary: true, + }, + create: { + name: finalLibraryName, + slug: librarySlug, + libraryPath: displayPath, + libraryType: mediaType === "tv" ? MediaType.TV_SHOW : MediaType.MOVIE, + isLibrary: true, + }, + }); + + logger.info(`✓ Library ready: ${library.name} (ID: ${library.id})\n`); + + // Step 1: Discover folders to scan + logger.info("🔍 Discovering folders to scan..."); + wsManager.sendScanProgress({ + phase: "discovering", + progress: 0, + current: 0, + total: 0, + message: "Discovering folders...", + libraryId: library.id, + }); + + const folders = await discoverFoldersToScan(rootPath, mediaType); + + if (folders.length === 0) { + logger.info("⚠️ No folders found to scan."); + wsManager.sendScanComplete({ + libraryId: library.id, + totalItems: 0, + message: `No ${mediaType === "tv" ? "shows" : "movies"} found in "${library.name}"`, + }); + + return { + libraryId: library.id, + libraryName: library.name, + totalFolders: 0, + totalSaved: 0, + scanJobId: null, + }; + } + + // Step 2: Create scan job + const scanJobId = await createScanJob( + library.id, + displayPath, + mediaType === "tv" ? MediaType.TV_SHOW : MediaType.MOVIE, + folders + ); + + wsManager.sendScanProgress({ + phase: "batching", + progress: 5, + current: 0, + total: folders.length, + message: `Starting batch scan (${folders.length} folders)`, + libraryId: library.id, + scanJobId, + }); + + // Step 3: Process batches + let totalSaved = 0; + let batchNumber = 0; + + while (true) { + const batch = await getNextBatch(scanJobId); + + if (!batch || batch.length === 0) { + break; + } + + batchNumber++; + logger.info( + `\n📦 Processing batch ${batchNumber} (${batch.length} folders)` + ); + + const result = await processFolderBatch(scanJobId, batch, { + rootPath, + mediaType, + tmdbApiKey, + libraryId: library.id, + maxDepth: effectiveMaxDepth, + fileExtensions: finalFileExtensions, + rescan, + originalPath, + }); + + totalSaved += result.totalSaved; + + // Mark batch as processed + await markBatchProcessed( + scanJobId, + result.processedFolders, + result.failedFolders, + result.totalSaved + ); + + // Send batch completion update + const scanJob = await prisma.scanJob.findUnique({ + where: { id: scanJobId }, + }); + + if (scanJob) { + const progressPercent = Math.floor( + ((scanJob.processedCount + scanJob.failedCount) / + scanJob.totalFolders) * + 100 + ); + + wsManager.sendScanProgress({ + phase: "batching", + progress: progressPercent, + current: scanJob.processedCount + scanJob.failedCount, + total: scanJob.totalFolders, + message: `Batch ${scanJob.currentBatch}/${scanJob.totalBatches} complete: ${result.processedFolders.length} success, ${result.failedFolders.length} failed (${scanJob.processedCount + scanJob.failedCount}/${scanJob.totalFolders} folders)`, + libraryId: library.id, + scanJobId, + }); + } + } + + logger.info("\n✅ Batch scan complete!\n"); + + // Send final completion message + wsManager.sendScanComplete({ + libraryId: library.id, + totalItems: totalSaved, + message: `Batch scan complete! Saved ${totalSaved} items to library "${library.name}"`, + scanJobId, + }); + + // Get final scan job stats + const finalScanJob = await prisma.scanJob.findUnique({ + where: { id: scanJobId }, + }); + + return { + libraryId: library.id, + libraryName: library.name, + totalFolders: folders.length, + foldersProcessed: finalScanJob?.processedCount || 0, + foldersFailed: finalScanJob?.failedCount || 0, + totalItemsSaved: finalScanJob?.totalItemsSaved || 0, + scanJobId, + }; + }, + + /** + * Resume a failed or paused scan job + */ + resumeScanJob: async (scanJobId: string, tmdbApiKey: string) => { + // Get the scan job + const scanJob = await prisma.scanJob.findUnique({ + where: { id: scanJobId }, + include: { library: true }, + }); + + if (!scanJob) { + throw new Error(`Scan job ${scanJobId} not found`); + } + + if (scanJob.status === "COMPLETED") { + throw new Error("Cannot resume a completed scan job"); + } + + if (scanJob.status === "IN_PROGRESS") { + throw new Error("Scan job is already in progress"); + } + + logger.info( + `🔄 Resuming scan job ${scanJobId} for library: ${scanJob.library.name}` + ); + + // Update status to in progress + await prisma.scanJob.update({ + where: { id: scanJobId }, + data: { + status: "IN_PROGRESS", + startedAt: scanJob.startedAt || new Date(), + }, + }); + + const mediaType = scanJob.mediaType === MediaType.TV_SHOW ? "tv" : "movie"; + const rootPath = scanJob.scanPath; + + // Get default file extensions + const finalFileExtensions = getDefaultVideoExtensions(); + + // Set maxDepth based on media type + const effectiveMaxDepth = mediaType === "tv" ? 4 : 2; + + // Process remaining batches + let totalSaved = 0; + let batchNumber = 0; + + wsManager.sendScanProgress({ + phase: "batching", + progress: Math.floor( + ((scanJob.processedCount + scanJob.failedCount) / scanJob.totalFolders) * + 100 + ), + current: scanJob.processedCount + scanJob.failedCount, + total: scanJob.totalFolders, + message: `Resuming scan job...`, + libraryId: scanJob.libraryId, + scanJobId, + }); + + while (true) { + const batch = await getNextBatch(scanJobId); + + if (!batch || batch.length === 0) { + break; + } + + batchNumber++; + logger.info( + `\n📦 Processing batch ${batchNumber} (${batch.length} folders)` + ); + + const result = await processFolderBatch(scanJobId, batch, { + rootPath, + mediaType, + tmdbApiKey, + libraryId: scanJob.libraryId, + maxDepth: effectiveMaxDepth, + fileExtensions: finalFileExtensions, + rescan: false, + }); + + totalSaved += result.totalSaved; + + // Mark batch as processed + await markBatchProcessed( + scanJobId, + result.processedFolders, + result.failedFolders, + result.totalSaved + ); + + // Send batch completion update + const updatedScanJob = await prisma.scanJob.findUnique({ + where: { id: scanJobId }, + }); + + if (updatedScanJob) { + const progressPercent = Math.floor( + ((updatedScanJob.processedCount + updatedScanJob.failedCount) / + updatedScanJob.totalFolders) * + 100 + ); + + wsManager.sendScanProgress({ + phase: "batching", + progress: progressPercent, + current: + updatedScanJob.processedCount + updatedScanJob.failedCount, + total: updatedScanJob.totalFolders, + message: `Batch ${updatedScanJob.currentBatch}/${updatedScanJob.totalBatches} complete: ${result.processedFolders.length} success, ${result.failedFolders.length} failed (${updatedScanJob.processedCount + updatedScanJob.failedCount}/${updatedScanJob.totalFolders} folders)`, + libraryId: scanJob.libraryId, + scanJobId, + }); + } + } + + logger.info("\n✅ Resumed scan complete!\n"); + + // Get final scan job stats + const finalScanJob = await prisma.scanJob.findUnique({ + where: { id: scanJobId }, + }); + + // Send final completion message + wsManager.sendScanComplete({ + libraryId: scanJob.libraryId, + totalItems: finalScanJob?.totalItemsSaved || 0, + message: `Resumed scan complete! Total: ${finalScanJob?.totalItemsSaved || 0} items in library "${scanJob.library.name}"`, + scanJobId, + }); + + return { + libraryId: scanJob.libraryId, + libraryName: scanJob.library.name, + totalFolders: finalScanJob?.totalFolders || 0, + foldersProcessed: finalScanJob?.processedCount || 0, + foldersFailed: finalScanJob?.failedCount || 0, + totalItemsSaved: finalScanJob?.totalItemsSaved || 0, + scanJobId, + }; + }, + + /** + * Get scan job status + */ + getJobStatus: async (scanJobId: string) => { + return getScanJobStatus(scanJobId); + }, + + /** + * Manually cleanup stale jobs + */ + cleanupStaleJobs: async (staleTimeoutMs?: number) => { + const cleanedCount = await cleanupStaleJobs(staleTimeoutMs); + return { + cleanedCount, + message: `Cleaned up ${cleanedCount} stale scan job(s)`, + }; + }, }; diff --git a/apps/api/src/domains/stream/stream.controller.ts b/apps/api/src/domains/stream/stream.controller.ts index f959a22..e124db6 100644 --- a/apps/api/src/domains/stream/stream.controller.ts +++ b/apps/api/src/domains/stream/stream.controller.ts @@ -83,7 +83,23 @@ export const streamControllers = { if (!range) { // No range requested, send entire file res.status(200); - const fileStream = createReadStream(filePath); + const fileStream = createReadStream(filePath, { + highWaterMark: 1024 * 1024, // 1MB chunks for better buffering on slow mounts + }); + + // Handle stream errors + fileStream.on("error", (error) => { + logger.error(`Stream error for ${filePath}:`, error); + if (!res.headersSent) { + res.status(500).end(); + } + }); + + // Keep connection alive + res.on("close", () => { + fileStream.destroy(); + }); + return fileStream.pipe(res); } @@ -109,8 +125,12 @@ export const streamControllers = { res.setHeader("Content-Range", `bytes ${start}-${end}/${fileSize}`); res.setHeader("Content-Length", chunkSize); - // Create stream for the requested range - const fileStream = createReadStream(filePath, { start, end }); + // Create stream for the requested range with optimized settings for remote mounts + const fileStream = createReadStream(filePath, { + start, + end, + highWaterMark: 1024 * 1024, // 1MB chunks for better buffering on slow mounts + }); fileStream.on("error", (error) => { logger.error(`Stream error for ${filePath}:`, error); @@ -123,6 +143,12 @@ export const streamControllers = { } }); + // Keep connection alive and cleanup on close + res.on("close", () => { + logger.debug(`Client disconnected, destroying stream for ${filePath}`); + fileStream.destroy(); + }); + return fileStream.pipe(res); }), }; diff --git a/apps/api/src/domains/tvshows/tvshows.services.ts b/apps/api/src/domains/tvshows/tvshows.services.ts index d0faffe..64ba683 100644 --- a/apps/api/src/domains/tvshows/tvshows.services.ts +++ b/apps/api/src/domains/tvshows/tvshows.services.ts @@ -8,6 +8,12 @@ export const tvshowsServices = { include: { media: true, }, + orderBy: { + media: { + createdAt: 'desc', // Most recent first + }, + }, + take: 10, // Limit to 10 most recent }); return serializeBigInt(tvshows) as TVShowsListResponse; }, diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index bb8581f..9d09e82 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -14,6 +14,14 @@ import { settingsManager } from "./core/config/settings"; const app = express(); const httpServer = createServer(app); +// Enable SO_REUSEADDR to allow port reuse immediately after restart +httpServer.on('listening', () => { + const address = httpServer.address(); + if (address && typeof address !== 'string') { + // Socket is successfully bound + } +}); + setupMiddleware(app); setupRoutes(app); setupErrorHandling(app); @@ -26,6 +34,19 @@ const startServer = async () => { const isFirstRun = await settingsManager.isFirstRun(); const tmdbApiKey = await settingsManager.getTmdbApiKey(); + // Handle port already in use error + httpServer.on('error', (error: NodeJS.ErrnoException) => { + if (error.code === 'EADDRINUSE') { + logger.error(`❌ Port ${config.port} is already in use!`); + logger.error(`💡 To kill the process using this port, run:`); + logger.error(` lsof -ti:${config.port} | xargs kill -9`); + process.exit(1); + } else { + logger.error("Server error:", error); + process.exit(1); + } + }); + httpServer.listen(config.port, "0.0.0.0", async () => { logger.info(`🚀 Server running on port ${config.port}`); logger.info(`📊 Health check: http://localhost:${config.port}/health`); @@ -43,6 +64,66 @@ const startServer = async () => { } else { logger.info("⚠️ TMDB API key not configured - add it in settings"); } + + // Auto-resume interrupted scan jobs from previous session + logger.info("🔍 Checking for interrupted scan jobs from previous session..."); + const interruptedJobs = await prisma.scanJob.findMany({ + where: { + status: { + in: ['IN_PROGRESS', 'PENDING'], + }, + }, + include: { + library: { + select: { name: true }, + }, + }, + }); + + if (interruptedJobs.length > 0) { + logger.info(`⏸️ Found ${interruptedJobs.length} interrupted scan job(s) - auto-resuming...`); + + // Import scanServices dynamically to avoid circular deps + const { scanServices } = await import("./domains/scan/scan.services.js"); + + for (const job of interruptedJobs) { + logger.info( + `🔄 Resuming: ${job.library.name} (${job.processedCount}/${job.totalFolders} folders, Batch ${job.currentBatch}/${job.totalBatches})` + ); + + // Get TMDB API key + const tmdbApiKey = await settingsManager.getTmdbApiKey(); + if (!tmdbApiKey) { + logger.warn(` ⚠️ Skipping ${job.library.name} - TMDB API key not configured`); + continue; + } + + // First, mark as FAILED so resumeScanJob can pick it up + await prisma.scanJob.update({ + where: { id: job.id }, + data: { status: 'FAILED' }, + }); + + // Resume in background (small delay to ensure DB update propagates) + setTimeout(() => { + scanServices.resumeScanJob(job.id, tmdbApiKey) + .then((result) => { + logger.info( + `✅ Auto-resumed scan completed: ${result.libraryName} (${result.totalItemsSaved} additional items)` + ); + }) + .catch((error: unknown) => { + logger.error( + `❌ Auto-resume failed for ${job.library.name}: ${error instanceof Error ? error.message : error}` + ); + }); + }, 100); + } + + logger.info(`✅ Started auto-resume for ${interruptedJobs.length} scan job(s)`); + } else { + logger.info("✅ No interrupted scans found"); + } }); } catch (error) { logger.error("Failed to start server:", error); @@ -50,14 +131,37 @@ const startServer = async () => { } }; -const gracefulShutdown = async () => { - logger.info("Shutting down gracefully..."); +const gracefulShutdown = async (signal: string) => { + logger.info(`Received ${signal}, shutting down gracefully...`); + + // Close HTTP server first + httpServer.close(() => { + logger.info("HTTP server closed"); + }); + + // Close WebSocket connections wsManager.close(); + + // Disconnect from database await prisma.$disconnect(); + + logger.info("Shutdown complete"); process.exit(0); }; -process.on("SIGTERM", gracefulShutdown); -process.on("SIGINT", gracefulShutdown); +process.on("SIGTERM", () => gracefulShutdown("SIGTERM")); +process.on("SIGINT", () => gracefulShutdown("SIGINT")); +process.on("SIGUSR2", () => gracefulShutdown("SIGUSR2")); // tsx/nodemon sends this + +// Handle uncaught exceptions to prevent zombie processes +process.on("uncaughtException", (error) => { + logger.error("Uncaught Exception:", error); + gracefulShutdown("uncaughtException"); +}); + +process.on("unhandledRejection", (reason) => { + logger.error("Unhandled Rejection:", reason); + gracefulShutdown("unhandledRejection"); +}); -startServer(); +startServer(); \ No newline at end of file diff --git a/apps/api/src/lib/database/index.ts b/apps/api/src/lib/database/index.ts index 3a765e9..68dcd75 100644 --- a/apps/api/src/lib/database/index.ts +++ b/apps/api/src/lib/database/index.ts @@ -1,2 +1,2 @@ export { default as prisma } from "./prisma"; -export { MediaType } from "@prisma/client"; +export { MediaType, ScanJobStatus } from "@prisma/client"; diff --git a/apps/api/src/lib/middleware/setup.middleware.ts b/apps/api/src/lib/middleware/setup.middleware.ts index 9c7a7f2..8d94108 100644 --- a/apps/api/src/lib/middleware/setup.middleware.ts +++ b/apps/api/src/lib/middleware/setup.middleware.ts @@ -11,6 +11,24 @@ const limiter = rateLimit({ windowMs: config.rateLimitWindowMs, max: config.rateLimitMax, message: "Too many requests from this IP, please try again later.", + // Skip rate limiting for localhost, scan routes, stream routes, and WebSocket + skip: (req) => { + // Skip for localhost/127.0.0.1 + const isLocalhost = req.ip === '127.0.0.1' || + req.ip === '::1' || + req.ip === 'localhost' || + req.hostname === 'localhost' || + req.hostname === '127.0.0.1'; + + if (isLocalhost) return true; + + // Skip for specific routes + return ( + req.path.startsWith('/api/v1/scan') || + req.path.startsWith('/api/v1/stream') || + req.path.startsWith('/ws') // WebSocket + ); + }, }); // Get local machine IP address for LAN access From f22e7be5e43caed87d711f23edcd7ca4076a437a Mon Sep 17 00:00:00 2001 From: AlkenD Date: Sat, 15 Nov 2025 01:37:14 +0530 Subject: [PATCH 03/21] feat: add changelog management and version sync infrastructure --- CHANGELOG.md | 206 +++++++++++++++++ .../migration.sql | 2 + apps/api/src/lib/middleware/index.ts | 1 + .../src/lib/middleware/version.middleware.ts | 90 ++++++++ apps/docs/src/content/docs/changelog.md | 210 ++++++++++++++++++ package.json | 15 +- scripts/README.md | 9 + scripts/sync-changelog.js | 80 +++++++ scripts/sync-version.js | 210 ++++++++++++++++++ 9 files changed, 821 insertions(+), 2 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 apps/api/prisma/migrations/20251112211711_store_mesh_gradiant/migration.sql create mode 100644 apps/api/src/lib/middleware/version.middleware.ts create mode 100644 apps/docs/src/content/docs/changelog.md create mode 100644 scripts/README.md create mode 100755 scripts/sync-changelog.js create mode 100755 scripts/sync-version.js diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..1b6b931 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,206 @@ +# Changelog + +All notable changes to DesterLib will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +#### API Server + +- **Version Management** + - Version matching system between API and clients + - Automatic version validation middleware + - Client version compatibility checking with strict semantic versioning + - Version information in health endpoint + - API version exposed via response headers (`X-API-Version`) + - Version mismatch error handling (HTTP 426) + +- **Media Scanning** + - Batch scanning with job tracking and resume capability + - Scan job status tracking in database (PENDING, IN_PROGRESS, COMPLETED, FAILED, PAUSED) + - Progress tracking with batch processing + - Folder-level batch processing for large libraries + - Scan job cleanup and management + - Improved file detection with configurable regex patterns + - Media type detection (movies, TV shows, music, comics) + - Path validation and sanitization + - Rate limiting for TMDB API calls + - Timeout handling for long-running scans + +- **Color Extraction & Mesh Gradients** + - Automatic color extraction from media posters/backdrops + - Mesh gradient generation with 4-corner color mapping + - Color caching in database for performance + - On-demand color extraction middleware + - Background color extraction for non-blocking requests + - Darkened color variants for better UI contrast + +- **Logging & Monitoring** + - Real-time logs streaming with WebSocket support + - REST endpoints for log retrieval + - Log filtering by level (error, warn, info, debug) + - Log file parsing and structured log entries + - Log clearing functionality + +- **Search & Discovery** + - Search functionality for movies and TV shows + - Genre-based filtering + - Library search capabilities + +- **Database Schema** + - Mesh gradient colors stored in Media model + - Scan job tracking with progress fields + - Enhanced library management with hierarchical support + - External ID tracking (TMDB, IMDB, TVDB, etc.) + - Person and role tracking (actors, directors, writers) + - Genre management with slug support + +- **API Improvements** + - Unified response structure across all endpoints + - Enhanced Swagger/OpenAPI documentation + - Improved error handling and validation + - Input sanitization middleware + +#### CLI Tool (`@desterlib/cli`) + +- **Setup Wizard** + - Interactive setup wizard for Docker configuration + - Docker installation check and validation + - Docker Compose availability verification + - Installation directory management (`~/.desterlib`) + - Existing installation detection and reconfiguration options + +- **Configuration Generation** + - Automatic Docker Compose file generation + - Environment file (.env) generation with secure defaults + - README generation with management commands + - Custom port configuration + - Database credentials setup + - Media path configuration with validation + +- **Features** + - Update checker for CLI tool + - Banner display on startup + - Path validation and sanitization + - Container status checking + - Docker container management commands + +#### Flutter Client + +- **Core Features** + - Cross-platform support (Android, iOS, macOS, Linux, Windows) + - Media library browsing (movies and TV shows) + - Video streaming with smooth playback + - Watch progress tracking and resume functionality + - Search and filter capabilities + - Dark mode support + - Server configuration management + - Version compatibility checking + +- **Video Player** + - Full-featured video player with controls + - Playback speed adjustment + - Progress tracking and seeking + - Volume control + - Fullscreen support + - Gesture controls + - Video settings menu + - Track selection (audio/subtitle support) + +- **Library Management** + - Library browsing with grid/list views + - Genre-based filtering and color coding + - Media detail pages with metadata + - TV show seasons and episodes navigation + - Library search functionality + - Media search across libraries + +- **Settings & Configuration** + - Server connection management + - Library management (add, edit, delete) + - TMDB API key configuration + - Video player settings + - API connection testing + +- **Logs Viewer** + - Real-time log streaming + - Log filtering by level + - Log modal viewer + - Structured log display + +#### Documentation + +- **New Documentation Site** + - Astro-based documentation site with Starlight theme + - Responsive design with mobile/tablet/desktop mockups + - Interactive API documentation + - Getting started guides + - Platform-specific setup guides + - Deployment guides + - Development guides + +- **Documentation Sections** + - API overview and environment variables + - CLI tool documentation + - Client platform setup guides + - Deployment guides (Docker, security) + - Development guides (contributing, versioning, structure) + - User guides (TMDB setup, backup/restore, remote access, updating) + - Changelog page integrated into docs + +### Changed + +- Improved scanning performance with batch processing +- Enhanced error handling and logging throughout API +- Better structured API responses with consistent format +- Improved Prisma query logging +- Optimized media scanning regex patterns +- Modularized scan helpers for better maintainability +- Enhanced middleware setup and organization + +### Fixed + +- Improved Prisma client location handling +- Better error messages for version mismatches +- Fixed scanning regex for better file detection +- Improved path validation and sanitization + +## [0.1.0] - 2025-11-13 + +### Added + +- Initial release +- Express API with TypeScript +- Flutter client application +- Media library management +- Movie and TV show catalog +- Media scanning and metadata fetching +- TMDB integration +- Media streaming endpoints +- WebSocket support for real-time updates +- Docker support +- Documentation site + +### Features + +- Library creation and management +- Media scanning with metadata extraction +- Search functionality for movies and TV shows +- Stream media files +- Settings management +- Color extraction from media posters +- Mesh gradient generation + +### Security + +- Rate limiting +- CORS configuration +- Input sanitization +- Helmet security headers + +[Unreleased]: https://github.com/yourusername/desterlib/compare/v0.1.0...HEAD +[0.1.0]: https://github.com/yourusername/desterlib/releases/tag/v0.1.0 diff --git a/apps/api/prisma/migrations/20251112211711_store_mesh_gradiant/migration.sql b/apps/api/prisma/migrations/20251112211711_store_mesh_gradiant/migration.sql new file mode 100644 index 0000000..9e4249e --- /dev/null +++ b/apps/api/prisma/migrations/20251112211711_store_mesh_gradiant/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Media" ADD COLUMN "meshGradientColors" TEXT[]; diff --git a/apps/api/src/lib/middleware/index.ts b/apps/api/src/lib/middleware/index.ts index e1ab157..95f5b6f 100644 --- a/apps/api/src/lib/middleware/index.ts +++ b/apps/api/src/lib/middleware/index.ts @@ -11,4 +11,5 @@ export { validateParams, } from "./validation.middleware"; export { sanitizeInput } from "./sanitization.middleware"; +export { validateVersion, addVersionHeader } from "./version.middleware"; // Auth middleware exports go here diff --git a/apps/api/src/lib/middleware/version.middleware.ts b/apps/api/src/lib/middleware/version.middleware.ts new file mode 100644 index 0000000..2dda9ab --- /dev/null +++ b/apps/api/src/lib/middleware/version.middleware.ts @@ -0,0 +1,90 @@ +import { Request, Response, NextFunction } from "express"; +import { readFileSync } from "fs"; +import { join } from "path"; +import { logger } from "../utils"; + +// Read version from package.json +function getApiVersion(): string { + try { + const packageJson = JSON.parse( + readFileSync(join(__dirname, "../../../package.json"), "utf-8") + ); + return packageJson.version; + } catch (error) { + return "unknown"; + } +} + +// Parse semantic version string to compare +function parseVersion(version: string): { major: number; minor: number; patch: number } | null { + const match = version.match(/^(\d+)\.(\d+)\.(\d+)/); + if (!match || !match[1] || !match[2] || !match[3]) return null; + return { + major: parseInt(match[1], 10), + minor: parseInt(match[2], 10), + patch: parseInt(match[3], 10), + }; +} + +// Check if client version is compatible with API version +function isVersionCompatible(clientVersion: string, apiVersion: string): boolean { + const client = parseVersion(clientVersion); + const api = parseVersion(apiVersion); + + if (!client || !api) return false; + + // Major version must match + if (client.major !== api.major) return false; + + // Minor version must match (we enforce strict minor version matching) + if (client.minor !== api.minor) return false; + + // Patch version can differ (backwards compatible) + return true; +} + +/** + * Middleware to validate client version compatibility + * Expects a 'X-Client-Version' header from the client + */ +export function validateVersion(req: Request, res: Response, next: NextFunction): void { + const clientVersion = req.headers["x-client-version"] as string; + const apiVersion = getApiVersion(); + + // Skip validation if no client version is provided (for backwards compatibility) + if (!clientVersion) { + logger.warn("No client version provided in request headers"); + next(); + return; + } + + // Check version compatibility + if (!isVersionCompatible(clientVersion, apiVersion)) { + logger.warn( + `Version mismatch: Client version ${clientVersion} is not compatible with API version ${apiVersion}` + ); + res.status(426).json({ + success: false, + error: "Version mismatch", + message: `Client version ${clientVersion} is not compatible with API version ${apiVersion}. Please update your client.`, + data: { + clientVersion, + apiVersion, + upgradeRequired: true, + }, + }); + return; + } + + next(); +} + +/** + * Add API version to response headers + */ +export function addVersionHeader(req: Request, res: Response, next: NextFunction): void { + const apiVersion = getApiVersion(); + res.setHeader("X-API-Version", apiVersion); + next(); +} + diff --git a/apps/docs/src/content/docs/changelog.md b/apps/docs/src/content/docs/changelog.md new file mode 100644 index 0000000..a033f2e --- /dev/null +++ b/apps/docs/src/content/docs/changelog.md @@ -0,0 +1,210 @@ +--- +title: Changelog +description: All notable changes to DesterLib +--- + +All notable changes to DesterLib will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added + +#### API Server + +- **Version Management** + - Version matching system between API and clients + - Automatic version validation middleware + - Client version compatibility checking with strict semantic versioning + - Version information in health endpoint + - API version exposed via response headers (`X-API-Version`) + - Version mismatch error handling (HTTP 426) + +- **Media Scanning** + - Batch scanning with job tracking and resume capability + - Scan job status tracking in database (PENDING, IN_PROGRESS, COMPLETED, FAILED, PAUSED) + - Progress tracking with batch processing + - Folder-level batch processing for large libraries + - Scan job cleanup and management + - Improved file detection with configurable regex patterns + - Media type detection (movies, TV shows, music, comics) + - Path validation and sanitization + - Rate limiting for TMDB API calls + - Timeout handling for long-running scans + +- **Color Extraction & Mesh Gradients** + - Automatic color extraction from media posters/backdrops + - Mesh gradient generation with 4-corner color mapping + - Color caching in database for performance + - On-demand color extraction middleware + - Background color extraction for non-blocking requests + - Darkened color variants for better UI contrast + +- **Logging & Monitoring** + - Real-time logs streaming with WebSocket support + - REST endpoints for log retrieval + - Log filtering by level (error, warn, info, debug) + - Log file parsing and structured log entries + - Log clearing functionality + +- **Search & Discovery** + - Search functionality for movies and TV shows + - Genre-based filtering + - Library search capabilities + +- **Database Schema** + - Mesh gradient colors stored in Media model + - Scan job tracking with progress fields + - Enhanced library management with hierarchical support + - External ID tracking (TMDB, IMDB, TVDB, etc.) + - Person and role tracking (actors, directors, writers) + - Genre management with slug support + +- **API Improvements** + - Unified response structure across all endpoints + - Enhanced Swagger/OpenAPI documentation + - Improved error handling and validation + - Input sanitization middleware + +#### CLI Tool (`@desterlib/cli`) + +- **Setup Wizard** + - Interactive setup wizard for Docker configuration + - Docker installation check and validation + - Docker Compose availability verification + - Installation directory management (`~/.desterlib`) + - Existing installation detection and reconfiguration options + +- **Configuration Generation** + - Automatic Docker Compose file generation + - Environment file (.env) generation with secure defaults + - README generation with management commands + - Custom port configuration + - Database credentials setup + - Media path configuration with validation + +- **Features** + - Update checker for CLI tool + - Banner display on startup + - Path validation and sanitization + - Container status checking + - Docker container management commands + +#### Flutter Client + +- **Core Features** + - Cross-platform support (Android, iOS, macOS, Linux, Windows) + - Media library browsing (movies and TV shows) + - Video streaming with smooth playback + - Watch progress tracking and resume functionality + - Search and filter capabilities + - Dark mode support + - Server configuration management + - Version compatibility checking + +- **Video Player** + - Full-featured video player with controls + - Playback speed adjustment + - Progress tracking and seeking + - Volume control + - Fullscreen support + - Gesture controls + - Video settings menu + - Track selection (audio/subtitle support) + +- **Library Management** + - Library browsing with grid/list views + - Genre-based filtering and color coding + - Media detail pages with metadata + - TV show seasons and episodes navigation + - Library search functionality + - Media search across libraries + +- **Settings & Configuration** + - Server connection management + - Library management (add, edit, delete) + - TMDB API key configuration + - Video player settings + - API connection testing + +- **Logs Viewer** + - Real-time log streaming + - Log filtering by level + - Log modal viewer + - Structured log display + +#### Documentation + +- **New Documentation Site** + - Astro-based documentation site with Starlight theme + - Responsive design with mobile/tablet/desktop mockups + - Interactive API documentation + - Getting started guides + - Platform-specific setup guides + - Deployment guides + - Development guides + +- **Documentation Sections** + - API overview and environment variables + - CLI tool documentation + - Client platform setup guides + - Deployment guides (Docker, security) + - Development guides (contributing, versioning, structure) + - User guides (TMDB setup, backup/restore, remote access, updating) + - Changelog page integrated into docs + +### Changed + +- Improved scanning performance with batch processing +- Enhanced error handling and logging throughout API +- Better structured API responses with consistent format +- Improved Prisma query logging +- Optimized media scanning regex patterns +- Modularized scan helpers for better maintainability +- Enhanced middleware setup and organization + +### Fixed + +- Improved Prisma client location handling +- Better error messages for version mismatches +- Fixed scanning regex for better file detection +- Improved path validation and sanitization + +## [0.1.0] - 2025-11-13 + +### Added + +- Initial release +- Express API with TypeScript +- Flutter client application +- Media library management +- Movie and TV show catalog +- Media scanning and metadata fetching +- TMDB integration +- Media streaming endpoints +- WebSocket support for real-time updates +- Docker support +- Documentation site + +### Features + +- Library creation and management +- Media scanning with metadata extraction +- Search functionality for movies and TV shows +- Stream media files +- Settings management +- Color extraction from media posters +- Mesh gradient generation + +### Security + +- Rate limiting +- CORS configuration +- Input sanitization +- Helmet security headers + +[Unreleased]: https://github.com/yourusername/desterlib/compare/v0.1.0...HEAD +[0.1.0]: https://github.com/yourusername/desterlib/releases/tag/v0.1.0 + diff --git a/package.json b/package.json index bb8e637..13fa1a8 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "name": "desterlib", + "version": "0.1.0", "private": true, "scripts": { "build": "turbo run build", @@ -13,10 +14,14 @@ "changeset": "changeset", "changeset:add": "changeset add", "changeset:status": "changeset status", - "version": "changeset version && pnpm install --lockfile-only", + "version": "changeset version && pnpm install --lockfile-only && pnpm changelog:sync", + "version:sync": "node scripts/sync-version.js", + "version:check": "node scripts/sync-version.js", + "changelog:sync": "node scripts/sync-changelog.js", "release": "pnpm build && changeset publish", "pr:create": "bash scripts/create-pr.sh", - "pr:create:main": "bash scripts/create-pr.sh main" + "pr:create:main": "bash scripts/create-pr.sh main", + "setup:docker": "tsx packages/cli/src/index.ts" }, "devDependencies": { "@changesets/changelog-github": "^0.5.1", @@ -28,6 +33,7 @@ "cz-customizable": "^7.5.1", "husky": "^9.1.7", "prettier": "^3.6.2", + "tsx": "^4.20.6", "turbo": "^2.5.8", "typescript": "5.9.2" }, @@ -39,5 +45,10 @@ "packageManager": "pnpm@9.0.0", "engines": { "node": ">=18" + }, + "pnpm": { + "overrides": { + "lightningcss": "^1.30.2" + } } } diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000..33dc539 --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,9 @@ +# Scripts + +Utility scripts for the DesterLib monorepo. + +## Documentation + +📖 **[Full Documentation](https://desterlib.github.io/desterlib)** + +For script usage and version management, visit the [documentation site](https://desterlib.github.io/desterlib). diff --git a/scripts/sync-changelog.js b/scripts/sync-changelog.js new file mode 100755 index 0000000..4ce4c46 --- /dev/null +++ b/scripts/sync-changelog.js @@ -0,0 +1,80 @@ +#!/usr/bin/env node + +/** + * Changelog Sync Script + * + * This script syncs the root CHANGELOG.md to the docs site. + * It reads the CHANGELOG.md and updates the docs changelog page. + */ + +const fs = require("fs"); +const path = require("path"); + +// ANSI color codes for terminal output +const colors = { + reset: "\x1b[0m", + green: "\x1b[32m", + yellow: "\x1b[33m", + red: "\x1b[31m", + blue: "\x1b[34m", +}; + +function log(message, color = "reset") { + console.log(`${colors[color]}${message}${colors.reset}`); +} + +// Paths +const rootChangelogPath = path.join(__dirname, "../CHANGELOG.md"); +const docsChangelogPath = path.join( + __dirname, + "../apps/docs/src/content/docs/changelog.md" +); + +try { + // Read root CHANGELOG.md + if (!fs.existsSync(rootChangelogPath)) { + log(`❌ Error: CHANGELOG.md not found at ${rootChangelogPath}`, "red"); + process.exit(1); + } + + let changelogContent = fs.readFileSync(rootChangelogPath, "utf8"); + + // Remove the first heading (# Changelog) to avoid duplication with frontmatter title + // This handles both "# Changelog" and "# Changelog\n" patterns + changelogContent = changelogContent + .replace(/^# Changelog\s*\n?/i, "") + .trimStart(); + + // Create the docs changelog content + // The frontmatter is separated from the content + const docsChangelogContent = `--- +title: Changelog +description: All notable changes to DesterLib +--- + +${changelogContent} +`; + + // Ensure docs directory exists + const docsDir = path.dirname(docsChangelogPath); + if (!fs.existsSync(docsDir)) { + fs.mkdirSync(docsDir, { recursive: true }); + } + + // Write to docs + fs.writeFileSync(docsChangelogPath, docsChangelogContent); + + log("\n📝 Syncing CHANGELOG.md to docs", "blue"); + log("─".repeat(50), "blue"); + log(`✅ Updated ${docsChangelogPath}`, "green"); + log(""); + + // Verify the root changelog is readable + const lines = changelogContent.split("\n").length; + log(`✓ Root CHANGELOG.md has ${lines} lines`, "yellow"); + log(""); +} catch (error) { + log(`❌ Error syncing changelog: ${error.message}`, "red"); + log(error.stack, "red"); + process.exit(1); +} diff --git a/scripts/sync-version.js b/scripts/sync-version.js new file mode 100755 index 0000000..a23e074 --- /dev/null +++ b/scripts/sync-version.js @@ -0,0 +1,210 @@ +#!/usr/bin/env node + +/** + * Version Sync Script + * + * This script ensures all version numbers across the monorepo are in sync. + * It reads the version from the root package.json and updates: + * - apps/api/package.json + * - packages/cli/package.json + * - desterlib-flutter/pubspec.yaml + * - desterlib-flutter/lib/api/pubspec.yaml + * - desterlib-flutter/lib/core/config/api_config.dart + */ + +const fs = require("fs"); +const path = require("path"); + +// ANSI color codes for terminal output +const colors = { + reset: "\x1b[0m", + green: "\x1b[32m", + yellow: "\x1b[33m", + red: "\x1b[31m", + blue: "\x1b[34m", +}; + +function log(message, color = "reset") { + console.log(`${colors[color]}${message}${colors.reset}`); +} + +// Read root version +const rootPackagePath = path.join(__dirname, "../package.json"); +const rootPackage = JSON.parse(fs.readFileSync(rootPackagePath, "utf8")); +const version = rootPackage.version; + +if (!version) { + log("❌ Error: No version found in root package.json", "red"); + process.exit(1); +} + +log(`\n📦 Syncing version: ${version}`, "blue"); +log("─".repeat(50), "blue"); + +let errors = 0; +let updates = 0; + +// Update API package.json +try { + const apiPackagePath = path.join(__dirname, "../apps/api/package.json"); + const apiPackage = JSON.parse(fs.readFileSync(apiPackagePath, "utf8")); + + if (apiPackage.version !== version) { + apiPackage.version = version; + fs.writeFileSync( + apiPackagePath, + JSON.stringify(apiPackage, null, 2) + "\n" + ); + log(`✅ Updated apps/api/package.json: ${version}`, "green"); + updates++; + } else { + log(`✓ apps/api/package.json already at ${version}`, "yellow"); + } +} catch (error) { + log(`❌ Error updating apps/api/package.json: ${error.message}`, "red"); + errors++; +} + +// Update CLI package.json +try { + const cliPackagePath = path.join(__dirname, "../packages/cli/package.json"); + const cliPackage = JSON.parse(fs.readFileSync(cliPackagePath, "utf8")); + + if (cliPackage.version !== version) { + cliPackage.version = version; + fs.writeFileSync( + cliPackagePath, + JSON.stringify(cliPackage, null, 2) + "\n" + ); + log(`✅ Updated packages/cli/package.json: ${version}`, "green"); + updates++; + } else { + log(`✓ packages/cli/package.json already at ${version}`, "yellow"); + } +} catch (error) { + log(`❌ Error updating packages/cli/package.json: ${error.message}`, "red"); + errors++; +} + +// Update Flutter pubspec.yaml +try { + const flutterPubspecPath = path.join( + __dirname, + "../../desterlib-flutter/pubspec.yaml" + ); + let pubspecContent = fs.readFileSync(flutterPubspecPath, "utf8"); + + const versionRegex = /^version:\s*[\d.]+$/m; + if (versionRegex.test(pubspecContent)) { + const newContent = pubspecContent.replace( + versionRegex, + `version: ${version}` + ); + if (newContent !== pubspecContent) { + fs.writeFileSync(flutterPubspecPath, newContent); + log(`✅ Updated desterlib-flutter/pubspec.yaml: ${version}`, "green"); + updates++; + } else { + log(`✓ desterlib-flutter/pubspec.yaml already at ${version}`, "yellow"); + } + } else { + log(`❌ Could not find version in desterlib-flutter/pubspec.yaml`, "red"); + errors++; + } +} catch (error) { + log( + `❌ Error updating desterlib-flutter/pubspec.yaml: ${error.message}`, + "red" + ); + errors++; +} + +// Update Flutter API client pubspec.yaml +try { + const apiClientPubspecPath = path.join( + __dirname, + "../../desterlib-flutter/lib/api/pubspec.yaml" + ); + let pubspecContent = fs.readFileSync(apiClientPubspecPath, "utf8"); + + const versionRegex = /^version:\s*[\d.]+$/m; + if (versionRegex.test(pubspecContent)) { + const newContent = pubspecContent.replace( + versionRegex, + `version: ${version}` + ); + if (newContent !== pubspecContent) { + fs.writeFileSync(apiClientPubspecPath, newContent); + log( + `✅ Updated desterlib-flutter/lib/api/pubspec.yaml: ${version}`, + "green" + ); + updates++; + } else { + log( + `✓ desterlib-flutter/lib/api/pubspec.yaml already at ${version}`, + "yellow" + ); + } + } else { + log( + `❌ Could not find version in desterlib-flutter/lib/api/pubspec.yaml`, + "red" + ); + errors++; + } +} catch (error) { + log( + `❌ Error updating desterlib-flutter/lib/api/pubspec.yaml: ${error.message}`, + "red" + ); + errors++; +} + +// Update Flutter API config +try { + const apiConfigPath = path.join( + __dirname, + "../../desterlib-flutter/lib/core/config/api_config.dart" + ); + let configContent = fs.readFileSync(apiConfigPath, "utf8"); + + const versionRegex = /static const String clientVersion = '[^']+';/; + if (versionRegex.test(configContent)) { + const newContent = configContent.replace( + versionRegex, + `static const String clientVersion = '${version}'; // Synced from root package.json` + ); + if (newContent !== configContent) { + fs.writeFileSync(apiConfigPath, newContent); + log( + `✅ Updated desterlib-flutter/lib/core/config/api_config.dart: ${version}`, + "green" + ); + updates++; + } else { + log( + `✓ desterlib-flutter/lib/core/config/api_config.dart already at ${version}`, + "yellow" + ); + } + } else { + log(`❌ Could not find clientVersion in api_config.dart`, "red"); + errors++; + } +} catch (error) { + log(`❌ Error updating api_config.dart: ${error.message}`, "red"); + errors++; +} + +// Summary +log("\n" + "─".repeat(50), "blue"); +if (errors > 0) { + log(`❌ Completed with ${errors} error(s) and ${updates} update(s)`, "red"); + process.exit(1); +} else if (updates > 0) { + log(`✅ Successfully updated ${updates} file(s)`, "green"); +} else { + log(`✓ All files already at version ${version}`, "yellow"); +} +log(""); From 1e540d4df8ed33dd33367190f06e5ca9462e74b9 Mon Sep 17 00:00:00 2001 From: AlkenD Date: Sat, 15 Nov 2025 01:37:32 +0530 Subject: [PATCH 04/21] feat(api): add color extraction and mesh gradient generation --- apps/api/prisma/schema.prisma | 21 +-- .../api/src/domains/movies/movies.services.ts | 35 ++++- .../color-extraction-middleware.helper.ts | 120 ++++++++++++++++++ .../scan/helpers/color-extraction.helper.ts | 112 ++++++++++++++++ apps/api/src/domains/scan/helpers/index.ts | 2 + .../src/domains/tvshows/tvshows.services.ts | 35 ++++- 6 files changed, 309 insertions(+), 16 deletions(-) create mode 100644 apps/api/src/domains/scan/helpers/color-extraction-middleware.helper.ts create mode 100644 apps/api/src/domains/scan/helpers/color-extraction.helper.ts diff --git a/apps/api/prisma/schema.prisma b/apps/api/prisma/schema.prisma index 2c3c8f1..5f09a1a 100644 --- a/apps/api/prisma/schema.prisma +++ b/apps/api/prisma/schema.prisma @@ -15,16 +15,17 @@ enum MediaType { } model Media { - id String @id @default(cuid()) - title String - type MediaType - description String? - posterUrl String? - backdropUrl String? - releaseDate DateTime? - rating Float? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + id String @id @default(cuid()) + title String + type MediaType + description String? + posterUrl String? + backdropUrl String? + meshGradientColors String[] // Hex colors for mesh gradient (4 colors for corners) + releaseDate DateTime? + rating Float? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt // Relations - subtypes point to Media, not the other way around movie Movie? diff --git a/apps/api/src/domains/movies/movies.services.ts b/apps/api/src/domains/movies/movies.services.ts index 509a1e4..8fa3e30 100644 --- a/apps/api/src/domains/movies/movies.services.ts +++ b/apps/api/src/domains/movies/movies.services.ts @@ -1,9 +1,12 @@ import prisma from "@/lib/database/prisma"; import { MoviesListResponse, MovieResponse } from "./movies.types"; -import { serializeBigInt, NotFoundError } from "@/lib/utils"; +import { serializeBigInt, NotFoundError, logger } from "@/lib/utils"; +import { enrichMediaWithColors, enrichMediaArrayWithColors } from "../scan/helpers"; export const moviesServices = { getMovies: async (): Promise => { + logger.info("📽️ Fetching movies list..."); + const movies = await prisma.movie.findMany({ include: { media: true, @@ -15,12 +18,28 @@ export const moviesServices = { }, take: 10, // Limit to 10 most recent }); - return serializeBigInt(movies) as MoviesListResponse; + + logger.info(`Found ${movies.length} movies, enriching with colors...`); + + // Enrich with mesh gradient colors on-demand + const enrichedMovies = await enrichMediaArrayWithColors( + movies.map((m) => m.media) + ); + + // Map back to movie structure + const moviesWithColors = movies.map((movie, index) => ({ + ...movie, + media: enrichedMovies[index], + })); + + return serializeBigInt(moviesWithColors) as MoviesListResponse; }, getMovieById: async ( id: string ): Promise => { + logger.info(`📽️ Fetching movie by ID: ${id}`); + const movie = await prisma.movie.findUnique({ where: { id }, include: { @@ -30,7 +49,17 @@ export const moviesServices = { if (!movie) { throw new NotFoundError("Movie", id); } - const serialized = serializeBigInt(movie) as MovieResponse; + + logger.info(`Found movie: "${movie.media.title}", enriching with colors...`); + + // Enrich with mesh gradient colors on-demand + const enrichedMedia = await enrichMediaWithColors(movie.media); + const movieWithColors = { + ...movie, + media: enrichedMedia, + }; + + const serialized = serializeBigInt(movieWithColors) as MovieResponse; return { ...serialized, streamUrl: `/api/v1/stream/${id}`, diff --git a/apps/api/src/domains/scan/helpers/color-extraction-middleware.helper.ts b/apps/api/src/domains/scan/helpers/color-extraction-middleware.helper.ts new file mode 100644 index 0000000..b95ef86 --- /dev/null +++ b/apps/api/src/domains/scan/helpers/color-extraction-middleware.helper.ts @@ -0,0 +1,120 @@ +/** + * Middleware helper for on-demand color extraction + * Extracts and caches mesh gradient colors when media is requested + */ + +import prisma from "@/lib/database/prisma"; +import { logger } from "@/lib/utils"; +import { extractAndDarkenMeshColors } from "./color-extraction.helper"; + +/** + * Ensure media has mesh gradient colors extracted + * If colors don't exist, extract them from backdrop and cache in database + */ +export async function ensureMeshColors( + mediaId: string, + backdropUrl: string | null, + mediaTitle?: string +): Promise { + try { + // Check if media already has colors + const media = await prisma.media.findUnique({ + where: { id: mediaId }, + select: { meshGradientColors: true, title: true }, + }); + + const title = mediaTitle || media?.title || mediaId; + + // If colors already exist, return them + if ( + media?.meshGradientColors && + Array.isArray(media.meshGradientColors) && + media.meshGradientColors.length === 4 + ) { + logger.debug(`[${title}] Using cached mesh colors: ${media.meshGradientColors.join(", ")}`); + return media.meshGradientColors; + } + + // No colors yet - extract them + if (!backdropUrl) { + logger.debug(`[${title}] No backdrop URL, skipping color extraction`); + return []; + } + + logger.info(`[${title}] 🎨 Extracting mesh colors from backdrop...`); + const colors = await extractAndDarkenMeshColors(backdropUrl, 0.4); + + // Cache colors in database + await prisma.media.update({ + where: { id: mediaId }, + data: { meshGradientColors: colors }, + }); + + logger.info(`[${title}] ✓ Colors extracted and cached: ${colors.join(", ")}`); + return colors; + } catch (error) { + const title = mediaTitle || mediaId; + logger.warn( + `[${title}] Failed to extract/cache colors: ${error instanceof Error ? error.message : error}` + ); + return []; + } +} + +/** + * Enrich media object with mesh gradient colors + * Returns immediately - extracts colors in background if needed + */ +export async function enrichMediaWithColors( + media: T +): Promise { + const title = media.title || media.id; + + // If colors already exist, return them + if ( + media.meshGradientColors && + Array.isArray(media.meshGradientColors) && + media.meshGradientColors.length === 4 + ) { + logger.debug(`[${title}] ✓ Using cached mesh colors`); + return media; + } + + // No colors yet - trigger background extraction but don't wait for it + if (media.backdropUrl) { + logger.info(`[${title}] 🎨 Triggering background color extraction...`); + + // Extract in background - don't await! + ensureMeshColors(media.id, media.backdropUrl, media.title) + .then((colors) => { + logger.info(`[${title}] ✓ Background extraction complete: ${colors.join(", ")}`); + }) + .catch((err) => { + logger.warn(`[${title}] Background extraction failed: ${err.message}`); + }); + } + + // Return immediately without colors (will be available on next request) + return media; +} + +/** + * Enrich an array of media objects with mesh gradient colors + * Returns immediately - triggers background extraction for items without colors + */ +export async function enrichMediaArrayWithColors( + mediaArray: T[] +): Promise { + const withColors = mediaArray.filter((m) => m.meshGradientColors?.length === 4).length; + const withoutColors = mediaArray.length - withColors; + + logger.info(`📊 Media colors: ${withColors} cached, ${withoutColors} to extract in background`); + + // Process all media (returns immediately, extracts in background) + const enrichedMedia = await Promise.all( + mediaArray.map((media) => enrichMediaWithColors(media)) + ); + + return enrichedMedia; +} + diff --git a/apps/api/src/domains/scan/helpers/color-extraction.helper.ts b/apps/api/src/domains/scan/helpers/color-extraction.helper.ts new file mode 100644 index 0000000..6e8ea1d --- /dev/null +++ b/apps/api/src/domains/scan/helpers/color-extraction.helper.ts @@ -0,0 +1,112 @@ +/** + * Color extraction utilities for mesh gradients + * Extracts prominent colors from images for UI backgrounds + */ + +import { logger } from "@/lib/utils"; + +// Type definition for node-vibrant v4 Swatch +type VibrantSwatch = { + hex: string; + rgb: [number, number, number]; + population: number; +} | null; + +// Default fallback colors (vibrant purple-blue theme) +const DEFAULT_MESH_COLORS = [ + "#7C3AED", // Top-left: Vibrant purple + "#2563EB", // Top-right: Bright blue + "#EC4899", // Bottom-left: Hot pink + "#8B5CF6", // Bottom-right: Purple-blue +]; + +/** + * Extract mesh gradient colors from an image URL + * Returns 4 hex color strings for mesh gradient corners + */ +export async function extractMeshColors(imageUrl: string): Promise { + try { + // Only extract if we have an image URL + if (!imageUrl) { + return DEFAULT_MESH_COLORS; + } + + // Import node-vibrant v4 for Node.js environment + const { Vibrant } = await import("node-vibrant/node"); + + // Extract color palette from image + const vibrant = new Vibrant(imageUrl); + const palette: any = await vibrant.getPalette(); + + // Extract 4 colors for mesh gradient corners + // Prefer muted/dark tones for better background aesthetics + // In v4, swatch has direct 'hex' property + const color1 = (palette.DarkMuted?.hex || + palette.Muted?.hex || + palette.Vibrant?.hex || + DEFAULT_MESH_COLORS[0]) as string; + + const color2 = (palette.Muted?.hex || + palette.LightMuted?.hex || + palette.LightVibrant?.hex || + DEFAULT_MESH_COLORS[1]) as string; + + const color3 = (palette.DarkVibrant?.hex || + palette.Vibrant?.hex || + palette.DarkMuted?.hex || + DEFAULT_MESH_COLORS[2]) as string; + + const color4 = (palette.LightMuted?.hex || + palette.LightVibrant?.hex || + palette.Muted?.hex || + DEFAULT_MESH_COLORS[3]) as string; + + const colors: string[] = [color1, color2, color3, color4]; + + logger.debug(`Extracted mesh colors from ${imageUrl}: ${colors.join(", ")}`); + return colors; + } catch (error) { + logger.warn( + `Failed to extract colors from ${imageUrl}: ${error instanceof Error ? error.message : error}` + ); + return DEFAULT_MESH_COLORS; + } +} + +/** + * Darken a hex color for better background contrast + */ +export function darkenColor(hex: string, amount: number = 0.3): string { + try { + // Remove # if present + const cleanHex = hex.replace("#", ""); + + // Parse RGB + const r = parseInt(cleanHex.substring(0, 2), 16); + const g = parseInt(cleanHex.substring(2, 4), 16); + const b = parseInt(cleanHex.substring(4, 6), 16); + + // Darken + const newR = Math.floor(r * (1 - amount)); + const newG = Math.floor(g * (1 - amount)); + const newB = Math.floor(b * (1 - amount)); + + // Convert back to hex + const toHex = (n: number) => n.toString(16).padStart(2, "0"); + return `#${toHex(newR)}${toHex(newG)}${toHex(newB)}`; + } catch (error) { + return hex; // Return original on error + } +} + +/** + * Extract and darken colors for mesh gradient + */ +export async function extractAndDarkenMeshColors( + imageUrl: string, + darkenAmount: number = 0.3 +): Promise { + const colors = await extractMeshColors(imageUrl); + return colors.map((color) => darkenColor(color, darkenAmount)); +} + diff --git a/apps/api/src/domains/scan/helpers/index.ts b/apps/api/src/domains/scan/helpers/index.ts index d910c87..80b3520 100644 --- a/apps/api/src/domains/scan/helpers/index.ts +++ b/apps/api/src/domains/scan/helpers/index.ts @@ -13,4 +13,6 @@ export * from "./batch-scanner.helper"; export * from "./timeout-helper"; export * from "./scan-job-cleanup.helper"; export * from "./media-type-detector.helper"; +export * from "./color-extraction.helper"; +export * from "./color-extraction-middleware.helper"; diff --git a/apps/api/src/domains/tvshows/tvshows.services.ts b/apps/api/src/domains/tvshows/tvshows.services.ts index 64ba683..2737827 100644 --- a/apps/api/src/domains/tvshows/tvshows.services.ts +++ b/apps/api/src/domains/tvshows/tvshows.services.ts @@ -1,9 +1,12 @@ import prisma from "@/lib/database/prisma"; import { TVShowsListResponse, TVShowResponse } from "./tvshows.types"; -import { serializeBigInt, NotFoundError } from "@/lib/utils"; +import { serializeBigInt, NotFoundError, logger } from "@/lib/utils"; +import { enrichMediaWithColors, enrichMediaArrayWithColors } from "../scan/helpers"; export const tvshowsServices = { getTVShows: async (): Promise => { + logger.info("📺 Fetching TV shows list..."); + const tvshows = await prisma.tVShow.findMany({ include: { media: true, @@ -15,10 +18,26 @@ export const tvshowsServices = { }, take: 10, // Limit to 10 most recent }); - return serializeBigInt(tvshows) as TVShowsListResponse; + + logger.info(`Found ${tvshows.length} TV shows, enriching with colors...`); + + // Enrich with mesh gradient colors on-demand + const enrichedMedia = await enrichMediaArrayWithColors( + tvshows.map((tv) => tv.media) + ); + + // Map back to tvshow structure + const tvshowsWithColors = tvshows.map((tvshow, index) => ({ + ...tvshow, + media: enrichedMedia[index], + })); + + return serializeBigInt(tvshowsWithColors) as TVShowsListResponse; }, getTVShowById: async (id: string): Promise => { + logger.info(`📺 Fetching TV show by ID: ${id}`); + const tvshow = await prisma.tVShow.findUnique({ where: { id }, include: { @@ -33,7 +52,17 @@ export const tvshowsServices = { if (!tvshow) { throw new NotFoundError("TV Show", id); } - const serialized = serializeBigInt(tvshow) as any; + + logger.info(`Found TV show: "${tvshow.media.title}", enriching with colors...`); + + // Enrich with mesh gradient colors on-demand + const enrichedMedia = await enrichMediaWithColors(tvshow.media); + const tvshowWithColors = { + ...tvshow, + media: enrichedMedia, + }; + + const serialized = serializeBigInt(tvshowWithColors) as any; // Transform seasons and episodes to match API schema const seasonsWithTransformedData = serialized.seasons.map((season: any) => ({ From 80307da2873561a6a3d01a47e9631da6725e6d31 Mon Sep 17 00:00:00 2001 From: AlkenD Date: Sat, 15 Nov 2025 01:37:38 +0530 Subject: [PATCH 05/21] feat(api): improve services and routes with unified responses --- apps/api/src/domains/movies/movies.routes.ts | 12 +++++++++++ apps/api/src/domains/search/search.routes.ts | 12 +++++++++++ .../api/src/domains/search/search.services.ts | 2 ++ .../api/src/domains/tvshows/tvshows.routes.ts | 12 +++++++++++ .../src/lib/middleware/setup.middleware.ts | 20 +++++++++++++++++-- apps/api/src/routes/index.ts | 17 +++++++++++++++- 6 files changed, 72 insertions(+), 3 deletions(-) diff --git a/apps/api/src/domains/movies/movies.routes.ts b/apps/api/src/domains/movies/movies.routes.ts index 92ee381..2d052b7 100644 --- a/apps/api/src/domains/movies/movies.routes.ts +++ b/apps/api/src/domains/movies/movies.routes.ts @@ -77,6 +77,12 @@ const router: Router = express.Router(); * backdropUrl: * type: string * nullable: true + * meshGradientColors: + * type: array + * items: + * type: string + * description: Hex color strings for mesh gradient (4 corners) + * example: ["#7C3AED", "#2563EB", "#EC4899", "#8B5CF6"] * releaseDate: * type: string * format: date-time @@ -192,6 +198,12 @@ router.get("/", moviesControllers.getMovies); * backdropUrl: * type: string * nullable: true + * meshGradientColors: + * type: array + * items: + * type: string + * description: Hex color strings for mesh gradient (4 corners) + * example: ["#7C3AED", "#2563EB", "#EC4899", "#8B5CF6"] * releaseDate: * type: string * format: date-time diff --git a/apps/api/src/domains/search/search.routes.ts b/apps/api/src/domains/search/search.routes.ts index 0496a88..d0004b7 100644 --- a/apps/api/src/domains/search/search.routes.ts +++ b/apps/api/src/domains/search/search.routes.ts @@ -86,6 +86,12 @@ const router: Router = express.Router(); * backdropUrl: * type: string * nullable: true + * meshGradientColors: + * type: array + * items: + * type: string + * description: Hex color strings for mesh gradient (4 corners) + * example: ["#7C3AED", "#2563EB", "#EC4899", "#8B5CF6"] * releaseDate: * type: string * format: date-time @@ -137,6 +143,12 @@ const router: Router = express.Router(); * backdropUrl: * type: string * nullable: true + * meshGradientColors: + * type: array + * items: + * type: string + * description: Hex color strings for mesh gradient (4 corners) + * example: ["#7C3AED", "#2563EB", "#EC4899", "#8B5CF6"] * releaseDate: * type: string * format: date-time diff --git a/apps/api/src/domains/search/search.services.ts b/apps/api/src/domains/search/search.services.ts index d9a5a89..b41df5d 100644 --- a/apps/api/src/domains/search/search.services.ts +++ b/apps/api/src/domains/search/search.services.ts @@ -62,6 +62,7 @@ export const searchServices = { description: media.description, posterUrl: media.posterUrl, backdropUrl: media.backdropUrl, + meshGradientColors: media.meshGradientColors, releaseDate: media.releaseDate, rating: media.rating, createdAt: media.createdAt, @@ -80,6 +81,7 @@ export const searchServices = { description: media.description, posterUrl: media.posterUrl, backdropUrl: media.backdropUrl, + meshGradientColors: media.meshGradientColors, releaseDate: media.releaseDate, rating: media.rating, createdAt: media.createdAt, diff --git a/apps/api/src/domains/tvshows/tvshows.routes.ts b/apps/api/src/domains/tvshows/tvshows.routes.ts index b641f9f..e3199c4 100644 --- a/apps/api/src/domains/tvshows/tvshows.routes.ts +++ b/apps/api/src/domains/tvshows/tvshows.routes.ts @@ -65,6 +65,12 @@ const router: Router = express.Router(); * backdropUrl: * type: string * nullable: true + * meshGradientColors: + * type: array + * items: + * type: string + * description: Hex color strings for mesh gradient (4 corners) + * example: ["#7C3AED", "#2563EB", "#EC4899", "#8B5CF6"] * releaseDate: * type: string * format: date-time @@ -164,6 +170,12 @@ router.get("/", tvshowsControllers.getTVShows); * backdropUrl: * type: string * nullable: true + * meshGradientColors: + * type: array + * items: + * type: string + * description: Hex color strings for mesh gradient (4 corners) + * example: ["#7C3AED", "#2563EB", "#EC4899", "#8B5CF6"] * releaseDate: * type: string * format: date-time diff --git a/apps/api/src/lib/middleware/setup.middleware.ts b/apps/api/src/lib/middleware/setup.middleware.ts index 8d94108..43535e8 100644 --- a/apps/api/src/lib/middleware/setup.middleware.ts +++ b/apps/api/src/lib/middleware/setup.middleware.ts @@ -5,7 +5,8 @@ import compression from "compression"; import rateLimit from "express-rate-limit"; import os from "os"; import { config } from "../../core/config/env"; -import { sanitizeInput } from "."; +import { sanitizeInput, addVersionHeader, validateVersion } from "."; +import { logger } from "../utils"; const limiter = rateLimit({ windowMs: config.rateLimitWindowMs, @@ -50,6 +51,14 @@ function getLocalIpAddress(): string { const localIp = getLocalIpAddress(); export function setupMiddleware(app: express.Application) { + // HTTP Request logging (skip health checks and websocket) + app.use((req, res, next) => { + if (req.path !== "/health" && req.path !== "/ws" && !req.path.includes("/api/docs")) { + logger.debug(`${req.method} ${req.path}`); + } + next(); + }); + // Only trust loopback proxies to satisfy express-rate-limit validation app.set("trust proxy", "loopback"); app.use( @@ -105,10 +114,17 @@ export function setupMiddleware(app: express.Application) { credentials: true, optionsSuccessStatus: 200, methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"], - allowedHeaders: ["Content-Type", "Authorization", "X-Requested-With"], + allowedHeaders: ["Content-Type", "Authorization", "X-Requested-With", "X-Client-Version"], + exposedHeaders: ["X-API-Version"], }) ); + // Add API version to response headers + app.use(addVersionHeader); + + // Validate client version (only for /api/v1 routes) + app.use("/api/v1", validateVersion); + // JSON parsing middleware - skip for static assets and image/media files app.use((req, res, next) => { // Skip JSON parsing for static assets and media files diff --git a/apps/api/src/routes/index.ts b/apps/api/src/routes/index.ts index fd52221..6fc4abd 100644 --- a/apps/api/src/routes/index.ts +++ b/apps/api/src/routes/index.ts @@ -1,13 +1,27 @@ import express, { Router } from "express"; +import { readFileSync } from "fs"; +import { join } from "path"; const router: Router = express.Router(); +// Read version from package.json +const getApiVersion = (): string => { + try { + const packageJson = JSON.parse( + readFileSync(join(__dirname, "../../package.json"), "utf-8") + ); + return packageJson.version; + } catch (error) { + return "unknown"; + } +}; + /** * @swagger * /health: * get: * summary: Health check endpoint - * description: Returns the current status of the API + * description: Returns the current status of the API including version information * tags: [Health] * responses: * 200: @@ -20,6 +34,7 @@ const router: Router = express.Router(); router.get("/health", (req, res) => { res.status(200).json({ status: "OK", + version: getApiVersion(), timestamp: new Date().toISOString(), uptime: process.uptime(), }); From ee3732caf5a22af7146b2b42a292731df6223422 Mon Sep 17 00:00:00 2001 From: AlkenD Date: Sat, 15 Nov 2025 01:37:43 +0530 Subject: [PATCH 06/21] docs(api): improve Swagger documentation and API config --- apps/api/package.json | 3 ++- apps/api/src/lib/config/swagger.ts | 23 +++++++++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/apps/api/package.json b/apps/api/package.json index f2ea174..b483289 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -1,6 +1,6 @@ { "name": "api", - "version": "0.0.1", + "version": "0.1.0", "description": "Express API with TypeScript", "main": "dist/index.js", "scripts": { @@ -38,6 +38,7 @@ "express-rate-limit": "^7.4.1", "helmet": "^7.1.0", "jsonwebtoken": "^9.0.2", + "node-vibrant": "^4.0.2", "pg": "^8.16.3", "swagger-jsdoc": "^6.2.8", "swagger-ui-express": "^5.0.1", diff --git a/apps/api/src/lib/config/swagger.ts b/apps/api/src/lib/config/swagger.ts index 51b6677..018fa0f 100644 --- a/apps/api/src/lib/config/swagger.ts +++ b/apps/api/src/lib/config/swagger.ts @@ -4,6 +4,19 @@ import { config } from "../../core/config/env"; import { logger } from "../../lib/utils"; import path from "path"; import os from "os"; +import { readFileSync } from "fs"; + +// Read version from package.json +function getApiVersion(): string { + try { + const packageJson = JSON.parse( + readFileSync(path.join(__dirname, "../../../package.json"), "utf-8") + ); + return packageJson.version; + } catch (error) { + return "0.1.0"; // Fallback version + } +} /// Get local machine IP address for LAN access function getLocalIpAddress(): string { @@ -22,13 +35,14 @@ function getLocalIpAddress(): string { } const localIp = getLocalIpAddress(); +const apiVersion = getApiVersion(); const options = { definition: { openapi: "3.0.0", info: { title: "DesterLib API", - version: "1.0.0", + version: apiVersion, description: "API documentation for DesterLib - A comprehensive media library management system", contact: { @@ -93,6 +107,11 @@ const options = { type: "string", example: "OK", }, + version: { + type: "string", + example: "0.1.0", + description: "API version", + }, timestamp: { type: "string", format: "date-time", @@ -231,7 +250,7 @@ try { openapi: "3.0.0", info: { title: "DesterLib API", - version: "1.0.0", + version: apiVersion, description: "API documentation for DesterLib", }, paths: {}, From 5d33c6d97405d7040dc5aa6b11a9cc41ad004ce8 Mon Sep 17 00:00:00 2001 From: AlkenD Date: Sat, 15 Nov 2025 01:37:50 +0530 Subject: [PATCH 07/21] feat(cli): add interactive setup wizard for Docker configuration --- packages/cli/.gitignore | 5 + packages/cli/README.md | 9 + packages/cli/package.json | 54 +++++ packages/cli/src/commands/setup.ts | 256 +++++++++++++++++++++++ packages/cli/src/index.ts | 117 +++++++++++ packages/cli/src/utils/banner.ts | 16 ++ packages/cli/src/utils/docker.ts | 143 +++++++++++++ packages/cli/src/utils/env.ts | 46 ++++ packages/cli/src/utils/paths.ts | 85 ++++++++ packages/cli/src/utils/templates.ts | 178 ++++++++++++++++ packages/cli/src/utils/update-checker.ts | 148 +++++++++++++ packages/cli/tsconfig.json | 15 ++ 12 files changed, 1072 insertions(+) create mode 100644 packages/cli/.gitignore create mode 100644 packages/cli/README.md create mode 100644 packages/cli/package.json create mode 100644 packages/cli/src/commands/setup.ts create mode 100644 packages/cli/src/index.ts create mode 100644 packages/cli/src/utils/banner.ts create mode 100644 packages/cli/src/utils/docker.ts create mode 100644 packages/cli/src/utils/env.ts create mode 100644 packages/cli/src/utils/paths.ts create mode 100644 packages/cli/src/utils/templates.ts create mode 100644 packages/cli/src/utils/update-checker.ts create mode 100644 packages/cli/tsconfig.json diff --git a/packages/cli/.gitignore b/packages/cli/.gitignore new file mode 100644 index 0000000..431b515 --- /dev/null +++ b/packages/cli/.gitignore @@ -0,0 +1,5 @@ +node_modules +dist +*.log +.DS_Store + diff --git a/packages/cli/README.md b/packages/cli/README.md new file mode 100644 index 0000000..b88ad64 --- /dev/null +++ b/packages/cli/README.md @@ -0,0 +1,9 @@ +# @desterlib/cli + +Command-line tool for setting up DesterLib with Docker. + +## Documentation + +📖 **[Full CLI Documentation](https://desterlib.github.io/desterlib/cli/overview)** + +For installation, usage, and more, visit the [documentation site](https://desterlib.github.io/desterlib/cli/overview). diff --git a/packages/cli/package.json b/packages/cli/package.json new file mode 100644 index 0000000..25b38eb --- /dev/null +++ b/packages/cli/package.json @@ -0,0 +1,54 @@ +{ + "name": "@desterlib/cli", + "version": "0.0.1", + "description": "DesterLib setup CLI for easy Docker configuration", + "type": "module", + "bin": { + "desterlib": "./dist/index.js" + }, + "scripts": { + "dev": "tsx src/index.ts", + "build": "tsc", + "setup": "node dist/index.js" + }, + "keywords": [ + "desterlib", + "cli", + "setup", + "docker" + ], + "author": "DesterLib", + "license": "AGPL-3.0", + "repository": { + "type": "git", + "url": "https://github.com/DesterLib/desterlib.git", + "directory": "packages/cli" + }, + "homepage": "https://desterlib.github.io/desterlib/cli/overview", + "bugs": { + "url": "https://github.com/DesterLib/desterlib/issues" + }, + "publishConfig": { + "access": "public" + }, + "files": [ + "dist", + "README.md" + ], + "engines": { + "node": ">=18" + }, + "dependencies": { + "chalk": "^4.1.2", + "commander": "^11.1.0", + "inquirer": "^8.2.6", + "ora": "^5.4.1", + "execa": "^5.1.1" + }, + "devDependencies": { + "@types/inquirer": "^8.2.10", + "@types/node": "^20.14.10", + "tsx": "^4.15.7", + "typescript": "^5.5.3" + } +} diff --git a/packages/cli/src/commands/setup.ts b/packages/cli/src/commands/setup.ts new file mode 100644 index 0000000..caf5e08 --- /dev/null +++ b/packages/cli/src/commands/setup.ts @@ -0,0 +1,256 @@ +import inquirer from 'inquirer'; +import chalk from 'chalk'; +import path from 'path'; +import os from 'os'; +import { + createConfigFiles, + type EnvConfig, +} from '../utils/env.js'; +import { + startDockerContainers, + checkContainersStatus, + checkDockerCompose, +} from '../utils/docker.js'; +import { getInstallationDir, isInstalled, validatePath, removeDirectory, ensureDirectory } from '../utils/paths.js'; + +/** + * Main setup wizard + */ +export async function setupWizard(): Promise { + try { + const installDir = getInstallationDir(); + console.log(chalk.gray(`📁 Installation directory: ${installDir}\n`)); + + // Check if Docker Compose is available + const hasCompose = await checkDockerCompose(); + if (!hasCompose) { + console.log(chalk.red('❌ Docker Compose is not available')); + console.log(chalk.yellow('\nPlease install Docker Compose:')); + console.log(chalk.cyan(' https://docs.docker.com/compose/install/')); + process.exit(1); + } + + // Check if already installed + const alreadyInstalled = isInstalled(); + if (alreadyInstalled) { + const { action } = await inquirer.prompt([ + { + type: 'list', + name: 'action', + message: chalk.yellow('⚠️ DesterLib configuration already exists. What would you like to do?'), + choices: [ + { name: 'Reconfigure (update settings)', value: 'reconfigure' }, + { name: 'Remove and start fresh', value: 'reinstall' }, + { name: 'Cancel', value: 'cancel' }, + ], + }, + ]); + + if (action === 'cancel') { + console.log(chalk.yellow('\n👋 Setup cancelled.')); + return; + } + + if (action === 'reinstall') { + console.log(chalk.yellow('\n🗑️ Removing existing configuration...')); + const removed = await removeDirectory(installDir); + if (!removed) { + console.log(chalk.red('❌ Failed to remove existing configuration')); + process.exit(1); + } + } + // For 'reconfigure', we just overwrite the files + } + + // Ensure installation directory exists + await ensureDirectory(installDir); + + console.log(chalk.cyan.bold('📋 Configuration Setup\n')); + console.log(chalk.gray('Please provide the following information:\n')); + + // Gather configuration + const answers = await inquirer.prompt([ + { + type: 'input', + name: 'mediaPath', + message: '📚 Path to your media library:', + default: path.join(os.homedir(), 'Media'), + validate: (input: string) => { + const validation = validatePath(input); + if (!validation.valid) { + return validation.message || 'Invalid path'; + } + return true; + }, + }, + { + type: 'input', + name: 'port', + message: '🔌 API server port:', + default: '3001', + validate: (input: string) => { + const port = parseInt(input, 10); + if (isNaN(port) || port < 1024 || port > 65535) { + return 'Please enter a valid port number (1024-65535)'; + } + return true; + }, + }, + ]); + + console.log(chalk.cyan.bold('\n🔒 Database Configuration\n')); + console.log(chalk.gray('Configure PostgreSQL database credentials:\n')); + + const dbAnswers = await inquirer.prompt([ + { + type: 'input', + name: 'postgresUser', + message: '👤 Database username:', + default: 'desterlib', + }, + { + type: 'password', + name: 'postgresPassword', + message: '🔐 Database password:', + mask: '*', + validate: (input: string) => { + if (input.length < 8) { + return 'Password must be at least 8 characters'; + } + return true; + }, + }, + { + type: 'input', + name: 'postgresDb', + message: '🗄️ Database name:', + default: 'desterlib', + }, + ]); + + // Build configuration + const config: EnvConfig = { + mediaPath: answers.mediaPath, + port: parseInt(answers.port, 10), + postgresUser: dbAnswers.postgresUser, + postgresPassword: dbAnswers.postgresPassword, + postgresDb: dbAnswers.postgresDb, + databaseUrl: `postgresql://${dbAnswers.postgresUser}:${dbAnswers.postgresPassword}@postgres:5432/${dbAnswers.postgresDb}?schema=public`, + }; + + // Display configuration review + console.log(chalk.cyan.bold('\n📋 Configuration Review\n')); + console.log(chalk.gray('Please review your configuration:\n')); + console.log(chalk.white(' Media Path: ') + chalk.cyan(config.mediaPath)); + console.log(chalk.white(' API Port: ') + chalk.cyan(config.port.toString())); + console.log(chalk.white(' Database User: ') + chalk.cyan(config.postgresUser)); + console.log(chalk.white(' Database Name: ') + chalk.cyan(config.postgresDb)); + console.log(chalk.white(' Database Password:') + chalk.cyan('***hidden***')); + console.log(''); + console.log(chalk.gray('Note: TMDB API key and JWT secret are configured in-app, not here.')); + console.log(''); + + // Confirm before proceeding + const { confirmConfig } = await inquirer.prompt([ + { + type: 'confirm', + name: 'confirmConfig', + message: chalk.yellow('📝 Proceed with this configuration?'), + default: true, + }, + ]); + + if (!confirmConfig) { + console.log(chalk.yellow('\n👋 Setup cancelled. No changes were made.')); + return; + } + + console.log(chalk.cyan.bold('\n⚙️ Applying Configuration\n')); + + // Create all configuration files + const configCreated = await createConfigFiles(config, installDir); + if (!configCreated) { + console.log(chalk.red('\n❌ Failed to create configuration. Setup aborted.')); + process.exit(1); + } + + // Ask if user wants to start containers now + const { startNow } = await inquirer.prompt([ + { + type: 'confirm', + name: 'startNow', + message: chalk.cyan.bold('\n🚀 Start Docker containers now?'), + default: true, + }, + ]); + + if (startNow) { + console.log(''); + const started = await startDockerContainers(installDir); + + if (started) { + // Wait a moment for containers to initialize + await new Promise((resolve) => setTimeout(resolve, 3000)); + + // Check status + await checkContainersStatus(installDir); + + // Display success message + displaySuccessMessage(config.port, installDir); + } else { + console.log(chalk.red('\n❌ Failed to start containers.')); + console.log(chalk.yellow('\nYou can start them manually with:')); + console.log(chalk.cyan(` cd ${installDir}`)); + console.log(chalk.cyan(' docker-compose up -d')); + } + } else { + console.log(chalk.yellow('\n⏸️ Containers not started.')); + console.log(chalk.gray('\nTo start them later, run:')); + console.log(chalk.cyan(` cd ${installDir}`)); + console.log(chalk.cyan(' docker-compose up -d')); + console.log(''); + } + + } catch (error: any) { + if (error.isTtyError) { + console.error(chalk.red('\n❌ Interactive prompts not supported in this environment')); + } else if (error.message === 'User force closed the prompt') { + console.log(chalk.yellow('\n\n👋 Setup cancelled by user.')); + } else { + console.error(chalk.red('\n❌ An error occurred during setup:'), error.message); + } + process.exit(1); + } +} + +/** + * Display success message with next steps + */ +function displaySuccessMessage(port: number, installDir: string): void { + console.log(chalk.green.bold('\n✅ Setup Complete!\n')); + console.log(chalk.cyan('📚 Your DesterLib server is now running!\n')); + + console.log(chalk.bold('🔗 Quick Links:')); + console.log(chalk.gray(' ├─') + chalk.cyan(` API Server: http://localhost:${port}`)); + console.log(chalk.gray(' ├─') + chalk.cyan(` API Docs: http://localhost:${port}/api/docs`)); + console.log(chalk.gray(' ├─') + chalk.cyan(` Health Check: http://localhost:${port}/health`)); + console.log(chalk.gray(' └─') + chalk.cyan(` WebSocket: ws://localhost:${port}/ws`)); + + console.log(chalk.bold('\n📱 Next Steps:')); + console.log(chalk.gray(' 1.') + chalk.white(' Open the API docs and configure your media library')); + console.log(chalk.gray(' 2.') + chalk.white(' Download the DesterLib mobile/desktop app')); + console.log(chalk.gray(' 3.') + chalk.white(` Connect to: http://localhost:${port}`)); + + console.log(chalk.bold('\n📂 Installation Location:')); + console.log(chalk.gray(' ') + chalk.white(installDir)); + + console.log(chalk.bold('\n🛠️ Useful Commands:')); + console.log(chalk.gray(' ├─') + chalk.white(' Stop: ') + chalk.cyan(`cd ${installDir} && docker-compose down`)); + console.log(chalk.gray(' ├─') + chalk.white(' Restart: ') + chalk.cyan(`cd ${installDir} && docker-compose restart`)); + console.log(chalk.gray(' ├─') + chalk.white(' Logs: ') + chalk.cyan(`cd ${installDir} && docker-compose logs -f`)); + console.log(chalk.gray(' └─') + chalk.white(' Status: ') + chalk.cyan(`cd ${installDir} && docker-compose ps`)); + + console.log(chalk.gray('\n📖 Documentation: ') + chalk.cyan('https://desterlib.github.io/desterlib')); + console.log(''); +} + diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts new file mode 100644 index 0000000..fe4cca0 --- /dev/null +++ b/packages/cli/src/index.ts @@ -0,0 +1,117 @@ +#!/usr/bin/env node + +import { Command } from "commander"; +import chalk from "chalk"; +import { setupWizard } from "./commands/setup.js"; +import { checkDocker } from "./utils/docker.js"; +import { displayBanner } from "./utils/banner.js"; +import { + getCurrentVersion, + checkForUpdates, + displayUpdateNotification, +} from "./utils/update-checker.js"; + +const program = new Command(); + +program + .name("desterlib") + .description("🎬 DesterLib CLI - Configure your personal media server") + .version(getCurrentVersion()); + +program + .command("setup") + .description("Run the interactive setup wizard") + .option("--skip-docker-check", "Skip Docker installation check") + .option("--skip-update-check", "Skip checking for CLI updates") + .action(async (options) => { + displayBanner(); + + // Check for updates in the background (non-blocking) + if (!options.skipUpdateCheck) { + checkForUpdates() + .then((updateInfo) => { + displayUpdateNotification(updateInfo); + }) + .catch(() => { + // Silently fail - don't interrupt the user experience + }); + } + + // Check Docker installation unless skipped + if (!options.skipDockerCheck) { + const dockerInstalled = await checkDocker(); + if (!dockerInstalled) { + console.log(chalk.red("\n❌ Docker is not installed or not running.")); + console.log(chalk.yellow("\n📦 Please install Docker Desktop:")); + console.log( + chalk.cyan( + " • macOS/Windows: https://www.docker.com/products/docker-desktop" + ) + ); + console.log( + chalk.cyan(" • Linux: https://docs.docker.com/engine/install/") + ); + console.log( + chalk.yellow("\nAfter installing Docker, run this setup again.") + ); + process.exit(1); + } + } + + await setupWizard(); + }); + +program + .command("update-check") + .description("Check for CLI updates") + .action(async () => { + console.log(chalk.cyan("Checking for updates...\n")); + const updateInfo = await checkForUpdates(); + + if (updateInfo.isOutdated && updateInfo.latestVersion) { + displayUpdateNotification(updateInfo); + } else { + console.log( + chalk.green( + `✅ You're using the latest version (${updateInfo.currentVersion})` + ) + ); + } + }); + +// Default command is setup +program.action(async () => { + displayBanner(); + + // Check for updates in the background (non-blocking) + checkForUpdates() + .then((updateInfo) => { + displayUpdateNotification(updateInfo); + }) + .catch(() => { + // Silently fail - don't interrupt the user experience + }); + + const dockerInstalled = await checkDocker(); + + if (!dockerInstalled) { + console.log(chalk.red("\n❌ Docker is not installed or not running.")); + console.log(chalk.yellow("\n📦 Please install Docker Desktop:")); + console.log( + chalk.cyan( + " • macOS/Windows: https://www.docker.com/products/docker-desktop" + ) + ); + console.log( + chalk.cyan(" • Linux: https://docs.docker.com/engine/install/") + ); + console.log( + chalk.yellow("\nAfter installing Docker, run this setup again.") + ); + process.exit(1); + } + + await setupWizard(); +}); + +program.parse(); diff --git a/packages/cli/src/utils/banner.ts b/packages/cli/src/utils/banner.ts new file mode 100644 index 0000000..44494c3 --- /dev/null +++ b/packages/cli/src/utils/banner.ts @@ -0,0 +1,16 @@ +import chalk from 'chalk'; + +export function displayBanner(): void { + console.clear(); + console.log(chalk.cyan.bold(` +╔══════════════════════════════════════════════════════════════╗ +║ ║ +║ 🎬 DesterLib Setup ║ +║ ║ +║ Your Personal Media Server ║ +║ ║ +╚══════════════════════════════════════════════════════════════╝ + `)); + console.log(chalk.gray(' Welcome! Let\'s set up your DesterLib server.\n')); +} + diff --git a/packages/cli/src/utils/docker.ts b/packages/cli/src/utils/docker.ts new file mode 100644 index 0000000..740080d --- /dev/null +++ b/packages/cli/src/utils/docker.ts @@ -0,0 +1,143 @@ +import execa from 'execa'; +import chalk from 'chalk'; +import ora from 'ora'; + +/** + * Check if Docker is installed and running + */ +export async function checkDocker(): Promise { + const spinner = ora('Checking Docker installation...').start(); + + try { + // Check if docker command exists + await execa('docker', ['--version']); + + // Check if Docker daemon is running + await execa('docker', ['ps']); + + spinner.succeed(chalk.green('Docker is installed and running')); + return true; + } catch (error) { + spinner.fail(chalk.red('Docker check failed')); + return false; + } +} + +/** + * Check if Docker Compose is available + */ +export async function checkDockerCompose(): Promise { + try { + // Try docker compose (v2) + await execa('docker', ['compose', 'version']); + return true; + } catch { + try { + // Try docker-compose (v1) + await execa('docker-compose', ['--version']); + return true; + } catch { + return false; + } + } +} + +/** + * Get the appropriate docker compose command + */ +export async function getDockerComposeCommand(): Promise { + try { + await execa('docker', ['compose', 'version']); + return ['docker', 'compose']; + } catch { + return ['docker-compose']; + } +} + +/** + * Start Docker containers + */ +export async function startDockerContainers(installDir: string): Promise { + const spinner = ora('Starting Docker containers...').start(); + + try { + const composeCmd = await getDockerComposeCommand(); + const command = composeCmd[0]; + const baseArgs = composeCmd.slice(1); + + if (!command) { + throw new Error('No docker compose command found'); + } + + spinner.text = 'Building and starting containers (this may take a few minutes)...'; + + await execa(command, [...baseArgs, 'up', '-d', '--build'], { + cwd: installDir, + stdio: 'pipe' + }); + + spinner.succeed(chalk.green('Docker containers started successfully')); + return true; + } catch (error: any) { + spinner.fail(chalk.red('Failed to start Docker containers')); + console.error(chalk.red('\nError details:'), error.message); + return false; + } +} + +/** + * Stop Docker containers + */ +export async function stopDockerContainers(installDir: string): Promise { + const spinner = ora('Stopping Docker containers...').start(); + + try { + const composeCmd = await getDockerComposeCommand(); + const command = composeCmd[0]; + const baseArgs = composeCmd.slice(1); + + if (!command) { + throw new Error('No docker compose command found'); + } + + await execa(command, [...baseArgs, 'down'], { + cwd: installDir, + stdio: 'pipe' + }); + + spinner.succeed(chalk.green('Docker containers stopped')); + return true; + } catch (error: any) { + spinner.fail(chalk.red('Failed to stop Docker containers')); + console.error(chalk.red('\nError details:'), error.message); + return false; + } +} + +/** + * Check Docker containers status + */ +export async function checkContainersStatus(installDir: string): Promise { + const spinner = ora('Checking container status...').start(); + + try { + const composeCmd = await getDockerComposeCommand(); + const command = composeCmd[0]; + const baseArgs = composeCmd.slice(1); + + if (!command) { + throw new Error('No docker compose command found'); + } + + const { stdout } = await execa(command, [...baseArgs, 'ps'], { + cwd: installDir + }); + + spinner.stop(); + console.log(chalk.cyan('\n📊 Container Status:')); + console.log(stdout); + } catch (error) { + spinner.fail(chalk.red('Failed to check container status')); + } +} + diff --git a/packages/cli/src/utils/env.ts b/packages/cli/src/utils/env.ts new file mode 100644 index 0000000..df19e10 --- /dev/null +++ b/packages/cli/src/utils/env.ts @@ -0,0 +1,46 @@ +import fs from 'fs'; +import path from 'path'; +import chalk from 'chalk'; +import ora from 'ora'; +import { generateDockerCompose, generateEnvFile, generateReadme } from './templates.js'; + +export interface EnvConfig { + mediaPath: string; + port: number; + databaseUrl: string; + postgresUser: string; + postgresPassword: string; + postgresDb: string; +} + + +/** + * Create configuration files in the installation directory + */ +export async function createConfigFiles(config: EnvConfig, installDir: string): Promise { + const spinner = ora('Creating configuration files...').start(); + + try { + // Create docker-compose.yml + const composeContent = generateDockerCompose(config); + const composePath = path.join(installDir, 'docker-compose.yml'); + await fs.promises.writeFile(composePath, composeContent, 'utf-8'); + + // Create .env file + const envContent = generateEnvFile(config); + const envPath = path.join(installDir, '.env'); + await fs.promises.writeFile(envPath, envContent, 'utf-8'); + + // Create README + const readmeContent = generateReadme(config.port, installDir); + const readmePath = path.join(installDir, 'README.md'); + await fs.promises.writeFile(readmePath, readmeContent, 'utf-8'); + + spinner.succeed(chalk.green('Configuration files created successfully')); + return true; + } catch (error: any) { + spinner.fail(chalk.red('Failed to create configuration files')); + console.error(chalk.red('\nError details:'), error.message); + return false; + } +} diff --git a/packages/cli/src/utils/paths.ts b/packages/cli/src/utils/paths.ts new file mode 100644 index 0000000..fe9f339 --- /dev/null +++ b/packages/cli/src/utils/paths.ts @@ -0,0 +1,85 @@ +import path from 'path'; +import fs from 'fs'; +import os from 'os'; + +/** + * Get the installation directory where DesterLib will be installed + * Defaults to ~/.desterlib + */ +export function getInstallationDir(): string { + return path.join(os.homedir(), '.desterlib'); +} + +/** + * Check if DesterLib configuration exists + */ +export function isInstalled(): boolean { + const installDir = getInstallationDir(); + const dockerComposePath = path.join(installDir, 'docker-compose.yml'); + const envPath = path.join(installDir, '.env'); + return fs.existsSync(dockerComposePath) && fs.existsSync(envPath); +} + +/** + * Validate if a path exists and is accessible + */ +export function validatePath(inputPath: string): { valid: boolean; message?: string } { + if (!inputPath || inputPath.trim() === '') { + return { valid: false, message: 'Path cannot be empty' }; + } + + const expandedPath = inputPath.replace(/^~/, process.env.HOME || ''); + + try { + if (!fs.existsSync(expandedPath)) { + return { + valid: false, + message: `Path does not exist: ${expandedPath}. Please create it first or choose an existing directory.` + }; + } + + const stats = fs.statSync(expandedPath); + if (!stats.isDirectory()) { + return { valid: false, message: 'Path must be a directory, not a file' }; + } + + // Check if readable + fs.accessSync(expandedPath, fs.constants.R_OK); + + return { valid: true }; + } catch (error: any) { + return { + valid: false, + message: `Cannot access path: ${error.message}` + }; + } +} + +/** + * Ensure directory exists, create if it doesn't + */ +export async function ensureDirectory(dirPath: string): Promise { + try { + if (!fs.existsSync(dirPath)) { + await fs.promises.mkdir(dirPath, { recursive: true }); + } + return true; + } catch (error) { + return false; + } +} + +/** + * Remove directory and all its contents + */ +export async function removeDirectory(dirPath: string): Promise { + try { + if (fs.existsSync(dirPath)) { + await fs.promises.rm(dirPath, { recursive: true, force: true }); + } + return true; + } catch (error) { + return false; + } +} + diff --git a/packages/cli/src/utils/templates.ts b/packages/cli/src/utils/templates.ts new file mode 100644 index 0000000..f898817 --- /dev/null +++ b/packages/cli/src/utils/templates.ts @@ -0,0 +1,178 @@ +import { EnvConfig } from './env.js'; + +const DOCKER_IMAGE = 'desterlib/api:latest'; // TODO: Update with your actual Docker Hub image + +/** + * Generate docker-compose.yml content + */ +export function generateDockerCompose(config: EnvConfig): string { + return `# DesterLib Docker Compose Configuration +# Generated by DesterLib CLI + +services: + postgres: + image: postgres:15-alpine + container_name: desterlib-postgres + restart: unless-stopped + environment: + POSTGRES_USER: ${config.postgresUser} + POSTGRES_PASSWORD: ${config.postgresPassword} + POSTGRES_DB: ${config.postgresDb} + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ${config.postgresUser}"] + interval: 10s + timeout: 5s + retries: 5 + + api: + image: ${DOCKER_IMAGE} + container_name: desterlib-api + restart: unless-stopped + depends_on: + postgres: + condition: service_healthy + environment: + # Required environment variables + DATABASE_URL: ${config.databaseUrl} + NODE_ENV: production + PORT: ${config.port} + + # Rate limiting (optional - defaults shown) + RATE_LIMIT_WINDOW_MS: 900000 # 15 minutes + RATE_LIMIT_MAX: 100 # 100 requests per window + ports: + # Bind to all interfaces to allow mobile app connections + - "0.0.0.0:${config.port}:${config.port}" + volumes: + # Mount your media library (read-only for security) + - ${config.mediaPath}:/media:ro + healthcheck: + test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:${config.port}/health || exit 1"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + +volumes: + postgres_data: + driver: local +`; +} + +/** + * Generate .env content + */ +export function generateEnvFile(config: EnvConfig): string { + return `# DesterLib Configuration +# Generated by DesterLib CLI + +# Database Configuration +DATABASE_URL=${config.databaseUrl} + +# Server Configuration +NODE_ENV=production +PORT=${config.port} + +# Rate Limiting (optional) +RATE_LIMIT_WINDOW_MS=900000 # 15 minutes in milliseconds +RATE_LIMIT_MAX=100 # Maximum requests per window + +# Notes: +# - TMDB API key is configured in the app settings (not here) +# - JWT secret is stored in the database (not here) +# - Media path is configured in docker-compose.yml +`; +} + +/** + * Generate README for the installation directory + */ +export function generateReadme(port: number, installDir: string): string { + return `# DesterLib Installation + +This directory contains your DesterLib configuration. + +## Quick Start + +\`\`\`bash +# Start DesterLib +docker-compose up -d + +# View logs +docker-compose logs -f + +# Stop DesterLib +docker-compose down +\`\`\` + +## Access Points + +- **API Server**: http://localhost:${port} +- **API Documentation**: http://localhost:${port}/api/docs +- **Health Check**: http://localhost:${port}/health +- **WebSocket**: ws://localhost:${port}/ws + +## Configuration Files + +- \`docker-compose.yml\` - Docker services configuration +- \`.env\` - Environment variables and secrets + +## Updating + +To update to the latest version: + +\`\`\`bash +# Pull latest image +docker-compose pull + +# Restart with new image +docker-compose up -d +\`\`\` + +## Reconfiguring + +To change your configuration, run: + +\`\`\`bash +npx @desterlib/cli +\`\`\` + +## Useful Commands + +\`\`\`bash +# View running containers +docker-compose ps + +# Restart services +docker-compose restart + +# View API logs +docker-compose logs -f api + +# View database logs +docker-compose logs -f postgres + +# Stop and remove everything (keeps data) +docker-compose down + +# Stop and remove everything including data +docker-compose down -v +\`\`\` + +## Troubleshooting + +If you encounter issues: + +1. Check logs: \`docker-compose logs -f\` +2. Verify containers are running: \`docker-compose ps\` +3. Restart services: \`docker-compose restart\` +4. Check port availability: \`lsof -i :${port}\` + +For more help, visit: https://desterlib.github.io/desterlib +`; +} + diff --git a/packages/cli/src/utils/update-checker.ts b/packages/cli/src/utils/update-checker.ts new file mode 100644 index 0000000..b9840b3 --- /dev/null +++ b/packages/cli/src/utils/update-checker.ts @@ -0,0 +1,148 @@ +import { readFileSync } from "fs"; +import { fileURLToPath } from "url"; +import { dirname, join } from "path"; +import chalk from "chalk"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +interface UpdateInfo { + currentVersion: string; + latestVersion: string | null; + isOutdated: boolean; +} + +/** + * Get the current version from package.json + */ +export function getCurrentVersion(): string { + try { + const packagePath = join(__dirname, "../../package.json"); + const packageJson = JSON.parse(readFileSync(packagePath, "utf-8")); + return packageJson.version || "0.0.0"; + } catch (error) { + return "0.0.0"; + } +} + +/** + * Check for updates from npm registry + */ +export async function checkForUpdates(): Promise { + const currentVersion = getCurrentVersion(); + let latestVersion: string | null = null; + + try { + const response = await fetch( + "https://registry.npmjs.org/@desterlib/cli/latest", + { + headers: { + Accept: "application/vnd.npm.install-v1+json", + }, + } + ); + + if (response.ok) { + const data = (await response.json()) as { version?: string }; + latestVersion = data.version || null; + } + } catch (error) { + // Silently fail - network issues shouldn't block the CLI + // Only log in development/debug mode + } + + const isOutdated = latestVersion !== null && latestVersion !== currentVersion; + + return { + currentVersion, + latestVersion, + isOutdated, + }; +} + +/** + * Compare two semantic versions + * Returns: 1 if v1 > v2, -1 if v1 < v2, 0 if equal + */ +function compareVersions(v1: string, v2: string): number { + const parts1 = v1.split(".").map(Number); + const parts2 = v2.split(".").map(Number); + + for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) { + const part1 = parts1[i] || 0; + const part2 = parts2[i] || 0; + + if (part1 > part2) return 1; + if (part1 < part2) return -1; + } + + return 0; +} + +/** + * Display update notification if a new version is available + */ +export function displayUpdateNotification(updateInfo: UpdateInfo): void { + if (!updateInfo.isOutdated || !updateInfo.latestVersion) { + return; + } + + const isMajorUpdate = + compareVersions(updateInfo.latestVersion, updateInfo.currentVersion) > 0; + + console.log( + chalk.yellow( + "\n┌─────────────────────────────────────────────────────────┐" + ) + ); + console.log( + chalk.yellow("│") + + chalk.bold(" ⚠️ Update Available!") + + " ".repeat(35) + + chalk.yellow("│") + ); + console.log( + chalk.yellow("├─────────────────────────────────────────────────────────┤") + ); + console.log( + chalk.yellow("│") + + ` Current version: ${chalk.gray(updateInfo.currentVersion)}` + + " ".repeat(25 - updateInfo.currentVersion.length) + + chalk.yellow("│") + ); + console.log( + chalk.yellow("│") + + ` Latest version: ${chalk.green(updateInfo.latestVersion)}` + + " ".repeat(25 - updateInfo.latestVersion.length) + + chalk.yellow("│") + ); + console.log( + chalk.yellow("├─────────────────────────────────────────────────────────┤") + ); + console.log( + chalk.yellow("│") + + ` Run: ${chalk.cyan("npm install -g @desterlib/cli@latest")}` + + " ".repeat(12) + + chalk.yellow("│") + ); + console.log( + chalk.yellow( + "└─────────────────────────────────────────────────────────┘\n" + ) + ); +} + +/** + * Check for updates asynchronously (non-blocking) + * This will check in the background and display a notification if needed + */ +export async function checkForUpdatesAsync(): Promise { + // Run in background, don't await + checkForUpdates() + .then((updateInfo) => { + displayUpdateNotification(updateInfo); + }) + .catch(() => { + // Silently fail - don't interrupt the user experience + }); +} diff --git a/packages/cli/tsconfig.json b/packages/cli/tsconfig.json new file mode 100644 index 0000000..ff740f5 --- /dev/null +++ b/packages/cli/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../typescript-config/base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "module": "ES2022", + "moduleResolution": "node", + "target": "ES2022", + "lib": ["ES2022"], + "types": ["node"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} + From c0cd50cb19b947d3b50637046ebd44ed0934504a Mon Sep 17 00:00:00 2001 From: AlkenD Date: Sat, 15 Nov 2025 01:37:55 +0530 Subject: [PATCH 08/21] feat(docs): add new Astro-based documentation site --- apps/docs/.prettierrc | 11 + apps/docs/astro.config.mjs | 219 ++++++++++++++------ apps/docs/package.json | 20 +- apps/docs/src/assets/background.png | Bin 0 -> 2148436 bytes apps/docs/src/assets/laptop/f-macbook.png | Bin 0 -> 463038 bytes apps/docs/src/assets/laptop/s-home.png | Bin 0 -> 1077055 bytes apps/docs/src/assets/logo.svg | 10 + apps/docs/src/assets/mobile.png | Bin 0 -> 193959 bytes apps/docs/src/assets/mobile/f-iphone.png | Bin 0 -> 35839 bytes apps/docs/src/assets/mobile/s-home.png | Bin 0 -> 229125 bytes apps/docs/src/assets/tablet.png | Bin 0 -> 675115 bytes apps/docs/src/assets/tablet/f-ipad.png | Bin 0 -> 229474 bytes apps/docs/src/assets/tablet/s-home.png | Bin 0 -> 772496 bytes apps/docs/src/components/FeatureCards.astro | 138 ++++++++++++ apps/docs/src/components/Head.astro | 14 ++ apps/docs/src/components/Hero.astro | 98 +++++++++ apps/docs/src/components/PageFrame.astro | 170 +++++++++++++++ apps/docs/src/components/react/Laptop.tsx | 69 ++++++ apps/docs/src/components/react/Mobile.tsx | 67 ++++++ apps/docs/src/components/react/Mockups.tsx | 39 ++++ apps/docs/src/components/react/Tablet.tsx | 69 ++++++ apps/docs/src/content/docs/index.mdx | 86 +------- apps/docs/src/styles/global.css | 9 + apps/docs/tsconfig.json | 15 +- 24 files changed, 887 insertions(+), 147 deletions(-) create mode 100644 apps/docs/.prettierrc create mode 100644 apps/docs/src/assets/background.png create mode 100644 apps/docs/src/assets/laptop/f-macbook.png create mode 100644 apps/docs/src/assets/laptop/s-home.png create mode 100644 apps/docs/src/assets/logo.svg create mode 100644 apps/docs/src/assets/mobile.png create mode 100644 apps/docs/src/assets/mobile/f-iphone.png create mode 100644 apps/docs/src/assets/mobile/s-home.png create mode 100644 apps/docs/src/assets/tablet.png create mode 100644 apps/docs/src/assets/tablet/f-ipad.png create mode 100644 apps/docs/src/assets/tablet/s-home.png create mode 100644 apps/docs/src/components/FeatureCards.astro create mode 100644 apps/docs/src/components/Head.astro create mode 100644 apps/docs/src/components/Hero.astro create mode 100644 apps/docs/src/components/PageFrame.astro create mode 100644 apps/docs/src/components/react/Laptop.tsx create mode 100644 apps/docs/src/components/react/Mobile.tsx create mode 100644 apps/docs/src/components/react/Mockups.tsx create mode 100644 apps/docs/src/components/react/Tablet.tsx create mode 100644 apps/docs/src/styles/global.css diff --git a/apps/docs/.prettierrc b/apps/docs/.prettierrc new file mode 100644 index 0000000..d87faa5 --- /dev/null +++ b/apps/docs/.prettierrc @@ -0,0 +1,11 @@ +{ + "plugins": ["prettier-plugin-astro"], + "overrides": [ + { + "files": "*.astro", + "options": { + "parser": "astro" + } + } + ] +} diff --git a/apps/docs/astro.config.mjs b/apps/docs/astro.config.mjs index 11b06bf..2ae2f6b 100644 --- a/apps/docs/astro.config.mjs +++ b/apps/docs/astro.config.mjs @@ -1,67 +1,162 @@ // @ts-check -import { defineConfig } from 'astro/config'; -import starlight from '@astrojs/starlight'; +import { defineConfig } from "astro/config"; +import starlight from "@astrojs/starlight"; +import { readFileSync } from "fs"; +import { fileURLToPath } from "url"; +import { dirname, join } from "path"; + +import tailwindcss from "@tailwindcss/vite"; + +import react from "@astrojs/react"; + +// Read version from root package.json +const __dirname = dirname(fileURLToPath(import.meta.url)); +const rootPackagePath = join(__dirname, "../../package.json"); +const rootPackage = JSON.parse(readFileSync(rootPackagePath, "utf-8")); +const version = rootPackage.version; // https://astro.build/config export default defineConfig({ - site: 'https://docs.dester.in', - base: '/', - integrations: [ - starlight({ - title: 'DesterLib Docs', - description: 'Documentation for DesterLib - Your Personal Media Server', - logo: { - src: './src/assets/logo.png', - alt: 'DesterLib Logo', - }, - social: [ - { icon: 'github', label: 'GitHub', href: 'https://github.com/DesterLib/desterlib' }, - ], - sidebar: [ - { - label: 'Getting Started', - items: [ - { label: 'Introduction', slug: 'index' }, - { label: 'Quick Start', slug: 'getting-started/quick-start' }, - { label: 'Installation', slug: 'getting-started/installation' }, - ], - }, - { - label: 'Projects', - items: [ - { label: 'API Server', slug: 'api/overview' }, - { label: 'Client Applications', slug: 'clients/overview' }, - ], - }, - { - label: 'Client Platforms', - items: [ - { label: 'Platform Setup', slug: 'clients/flutter' }, - ], - }, - { - label: 'Development', - collapsed: false, - items: [ - { label: 'Contributing Guide', slug: 'development/contributing' }, - { label: 'Project Structure', slug: 'development/structure' }, - { label: 'Versioning Guide', slug: 'development/versioning' }, - { label: 'Quick Reference', slug: 'development/quick-reference' }, - { label: 'Commit Guidelines', slug: 'development/commit-guidelines' }, - ], - }, - { - label: 'API Reference', - items: [ - { label: 'Swagger Docs', link: 'http://localhost:3001/api/docs', attrs: { target: '_blank', rel: 'noopener noreferrer' } }, - ], - }, - // TODO: Add Deployment section when deployment guides are ready - // { - // label: 'Deployment', - // autogenerate: { directory: 'deployment' }, - // }, - ], - }), - ], + site: "https://docs.dester.in", + base: "/", + integrations: [ + starlight({ + title: "DesterLib Docs", + description: "Documentation for DesterLib - Your Personal Media Server", + logo: { + src: "./src/assets/logo.svg", + alt: "DesterLib Logo", + }, + social: [ + { + icon: "github", + label: "GitHub", + href: "https://github.com/DesterLib/desterlib", + }, + ], + customCss: ["./src/styles/global.css"], + expressiveCode: { + themes: ["dark-plus"], + }, + defaultLocale: "root", + locales: { + root: { + label: "English", + lang: "en", + }, + }, + components: { + Head: "./src/components/Head.astro", + PageFrame: "./src/components/PageFrame.astro", + Hero: "./src/components/Hero.astro", + }, + sidebar: [ + // GETTING STARTED + { + label: "Getting Started", + items: [ + { label: "Introduction", slug: "index" }, + { label: "Quick Start", slug: "getting-started/quick-start" }, + { label: "Installation", slug: "getting-started/installation" }, + ], + }, + + // PROJECTS & TOOLS + { + label: "Projects", + collapsed: false, + items: [ + { + label: "API Server", + collapsed: true, + items: [ + { label: "Overview", slug: "api/overview" }, + { + label: "Environment Variables", + slug: "api/environment-variables", + }, + ], + }, + { + label: "Client Apps", + collapsed: true, + items: [ + { label: "Overview", slug: "clients/overview" }, + { label: "Platform Setup", slug: "clients/flutter" }, + ], + }, + { + label: "CLI Tool", + collapsed: true, + items: [{ label: "Overview", slug: "cli/overview" }], + }, + ], + }, + + // GUIDES + { + label: "Guides", + collapsed: true, + items: [ + { label: "TMDB Setup", slug: "guides/tmdb-setup" }, + { label: "Managing Server", slug: "guides/managing-server" }, + { label: "Updating DesterLib", slug: "guides/updating" }, + { label: "Backup & Restore", slug: "guides/backup-restore" }, + { label: "Remote Access", slug: "guides/remote-access" }, + ], + }, + + // DEPLOYMENT + { + label: "Deployment", + collapsed: true, + items: [ + { label: "Docker Production", slug: "deployment/docker" }, + { label: "Security Guide", slug: "deployment/security" }, + ], + }, + + // DEVELOPMENT + { + label: "Development", + collapsed: true, + items: [ + { label: "Contributing Guide", slug: "development/contributing" }, + { label: "Project Structure", slug: "development/structure" }, + { + label: "Commit Guidelines", + slug: "development/commit-guidelines", + }, + { label: "Versioning Guide", slug: "development/versioning" }, + { label: "Quick Reference", slug: "development/quick-reference" }, + ], + }, + + // CHANGELOG + { + label: "Changelog", + items: [{ label: "Release History", slug: "changelog" }], + }, + + // EXTERNAL LINKS + { + label: "API Reference", + items: [ + { + label: "Interactive API Docs (Swagger)", + link: "http://localhost:3001/api/docs", + attrs: { target: "_blank", rel: "noopener noreferrer" }, + }, + ], + }, + ], + }), + react(), + ], + vite: { + plugins: [tailwindcss()], + define: { + "import.meta.env.VITE_APP_VERSION": JSON.stringify(version), + }, + }, }); diff --git a/apps/docs/package.json b/apps/docs/package.json index 1151252..a6f2213 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -6,18 +6,30 @@ "scripts": { "dev": "astro dev", "start": "astro dev", - "build": "astro check && astro build", + "build": "node ../../scripts/sync-changelog.js && astro check && astro build", "preview": "astro preview", "astro": "astro", "check-types": "astro check" }, "dependencies": { + "@astrojs/react": "^4.4.2", "@astrojs/starlight": "^0.36.1", + "@astrojs/starlight-tailwind": "^4.0.2", + "@tailwindcss/vite": "^4.1.17", + "@types/react": "^19.2.4", + "@types/react-dom": "^19.2.3", "astro": "^5.6.1", - "sharp": "^0.34.2" + "motion": "^12.23.24", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "sharp": "^0.34.2", + "tailwindcss": "^4.1.17" }, "devDependencies": { "@astrojs/check": "^0.9.0", - "typescript": "^5.9.2" + "prettier": "^3.6.2", + "prettier-plugin-astro": "0.14.1", + "typescript": "^5.9.2", + "vite": "^6.4.1" } -} \ No newline at end of file +} diff --git a/apps/docs/src/assets/background.png b/apps/docs/src/assets/background.png new file mode 100644 index 0000000000000000000000000000000000000000..36e5d9d988f689c78f84dcc90f933f10d01f3e23 GIT binary patch literal 2148436 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�obRu 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O*)X8_R!c?1d2A$rnJ7fU)j? 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 z zTdjZyBvd@M0dLDuhCNoXLW_gQ0L)$@Y>J&qvjxgGVrS0B|X14R#+Jr#sY zRw`>B0Poq?@Ew+4x2idAw`&C$kDnpL7Vn+r+2dW$0jx(*s;^e=L7_jnIwk-M*8!7^ z82-36SsFH0sqkEOIS+L{LDJXScz-yDtP1UI_O)#WA2w%GSozp8M$!hD&)EA>5F!gR z5FicS`u%eBaOPFOX+G(`z+`Uoq8=_%sJDZEag8szs(_YQ>I|>u;uE-m zT+XW~nR9Ii@VFTOs!_il{}(Hjge}HOXZiA%j!L;2Wa7sg6d$Bl{IHA3lz?gmjfz1< zf{Ou8ai3NK*C_aXoo)WInEJ&-9&_;cxmg>JN^XO2bZD0kUTBGXCNs3%_h5M(C@bje`&ZQETgwBRE|Rj244!12hg07yvW#FBT^FegMH4 z^J(v>tj8eHx-tC7wLVTE8u>HacbkKjI*OxIQrNA_OXkqCOO){?k;NqfB%oFcwf&TSVA^n9^LfpY{#kz zydLDhVXT&T#0Q|wJsc|)LUwsP=GtOs!!<0q6TYT~XM9xeUUS%HX|JsJCXrHoG4e-3 zpyfK6ecxy2Cq#g8aW!+SXmZU_nd5)QswP`ejVhl5nN!`aV?B12LCd;m~Ass7&PDxhK!)VQ{Q;{pA1JqJ6ak<&4`s_D3HX0GKP4?sTW0Os9MAs+%5^Ep)- zc?KNEY_9Q8*`s-1|I@$yXDfDwuQEW_U8jB?k^x>r1+)vDBG^>}5*S#Nt$~z&3;VR>;XXNv%AZyo+GmVKZL&0^2ExnO69&zJK8Yn&<$|wvAgpSj8u5d zRl#;0H1D4Y(2*NcO!|I{_x&Y5c?8>?F5FDfP6Z)t^9hixl!v7e$WrV`p_&Bxztt*B zg%`>X8}=TUF4)q;mIw!hm0I~iC?3(TXv4*-6aou6=o6G~Qaq~^nVz}})$5%QAZ-I+ zc`Yi9SnSO%Z)*9o<7Pi`c&-AuAGSw0ds4#o20#ie9E=Un!h`^LC4h?~I>$p1kjmgv z2ZhQP7R1Fy;FC5eQstKS`NG%VrgMF|JvM$ulX6+%#)k9@$Ijct4Yw>Itn}n~+V$Su zV0f*v>%eE-<_iNi?@jg{-xda^(ppIVoAQrW&R`{>u)Ck(R*t@Tw3U@0@Fga3As=(L zH5Ft1{@}R1l8UPE|CcAc#|P&I{MR?tAlwY#X5&txh(Os+Lg8e8`7kx<8KCP0J!2r^OzQFx=f&&D6%Zq$gP_VsY18NHT)_F%&7qN9vN%p-1 z!#YG)sQ(qRR_D==?|qIqdqJP=b?T0V-G^i~>0DA#ps{7EsJ5}a@c!$fwL`wq`VQJ$ zU{K0Bj&ln>qFKxd0>Z<5UqgzuSbG%PI^fUZiVfTqY+sX)OS?0SjHD!0^bWq$yln*ianz)`nGd3A9%3c{X#GgY>mm>eiz>t#63wL*8tLOAJQOPz`f7{rrGib zYs*ZFkV-PiLq_19eox=^y|vg5MUj^E65zCgJ~ys6_*5r}IK^>D&oK3Jchv z6Z(MT{Gk9|C^|uy*W{oyS?6&5cy6i^xsmaEPQgc0s8o1kn=Rjvgjv) zM%jd%=0kph{ptb0@fpa&g|Z+t6QmTKoInIp1Mk6jBPs7|IvIpTwGCCh0F-b5rrNX< z7H{vJ6#4YMwMuKP*;yUft1J^q1_H}`PU-pSKu`uhA|C?Z_e_cZBC98of(o}S-}~nO z!9$D>EW5_dto#W+&jC!EbV@Z+4KYP#6QIVrj`IC^QE+du->^YBRDR&%K|ru_w zfe2J2mvJb^O~M2FY=zc`!rDr#YYYa^5(*6#02rSEp#36M`N&(K63oB~g1bKqW_X_W z0~D!Mu7a@=!JOj**qIVk&uwXx{A*c9hIJv@z-o~7(n-!)ezD$OEe%`rnO4&URMiACGAL0xp zIL5*%tD0)-8u*L1(Rce8vuA`I>Y(1_Z@b9~sc8=d)jGguV^rs!n_Hn$>|r z85~ldy0jYI1aSH6Fvrb=3)iCfbt_1h)d84`P_YBtEh2}sw=S~82}t;@16Gp{kRYiT z5NV~??)fc}Se7rRv&yr#mr0lwn~Rxq9=$<@G1k#erg5$kY?^2tN6^7!L45O3CBj3w zpP%@S(rlSQP_jMw*^Ai7L0t*z3Y_ZRH@mDm%wsiyu zFU&e%RVo2oSluvex34v^9*J1s-L?X;8Hl1bAjdopRcJ}30l=2bJv^I0Y8?H2^Y>BN z8sOQ$EkXN$<%hMD*O9(4FgGkFtfC%v8b@n_%|7M%zEsmhDWA`5Qf!XrxJj>SMfvc+ zPn#D3=q>kPP-%G2M=k3%9D%e^i2_8&cycumec}U{x=N_oSGz{d8X(Rp`JsXj5E_MV z0OWj+75Ob*@kdLSfUvpu5F~kj0PXC>vIiAub^XPKA(0;hL z@$5nvX8-pDkPl-BKL4?^31Jia5bkkpF>?Uv$F=15`FDWiW52{2Ds5)_qH+Z7Q%&G; zpb9Lj+mB-_U}AnAG9mUw$9@lB?YR-YVtiIUBV#pVBR=){p2OT4RTPI%T=r?^7`}he zy3F5WEx_}^)=9&z#A`y!I7xe&T*w?9c>=litSRxaw_Q^_+B0Snc00V{O%D8`R8IxO zLCs#>pEctq1Z2ja!?H((S1L!+x1R4~|Cwt}-a@?i@n=i5Tq)x-@|uiC{49i46z3FK z_MhQ14+4NeB>7$jX__$*66aXKk!L{WSV)aLFYp~{yO4JI>|Be-Lv^LXn9KeS&PViD zG7Yl69oL?WK@$h{_;Y+tJ~wMf?xn?v*JK&m68(3~+;G*>ut387^`A|t3`*taJpJ$g z_MeMNzzLA{X-KbW%6%7wGV;L$fp_!o=@U*+k>Di5E&(so+qS_ff+2lSM#HE3tpl9> zv+Ilgs=LZTp}MG28Dpoo?({~)CY(SZHc|YH;y?nZ+FUKg_)d?{EQD_zl*b|U697lQ z($&^0I&>FVzv#VEh$0}ZDJ+$Py<`vIgjrG8+1Snd3@V=8nlN1EF(VgII z|6{@oAxcZ&i10YkL(}#(1qffz@aR6NQL--yGw;CFsAKR_HXnd z0gvH_5l^SvE9~`>>oHoAt?L&7w*P zt7I@%`Ua(sq_^IuuOcPw4GT_@mpechfOwrGpd68Y&ApALKCIxK17HZ>3uX7qe(o15 zGtqPKR_OpW7v;Ih%7*G5R0LE83sV6b5Ek&>669Vbui2zoeKXPA*kB378WX0bPtq#yWClfM_MKiLv|I1N18g;3QB>n+%X` zC?TItRC(e+yf)1}%oUM|1k4y%2~{ybLHr)X1}u!IHix3CA7+-S3dqfBV31F^InH_4 z+}{ZVN|~-;*eO;V2)t!w$|U8iO7{EjKA-@`nxQtVQ1G2>1A~>OlT8Ew>Cr0d4<4$Y z{4P8wX3iU21MHw+t$}rWGm%AgS>~Ic;?kyzK%hdo3Gf1-1(5jGfpUe&wx~;4sdF~* zD!YwM<=~vLl~yCS^M#fZWzuT^2wq8kC{T44@k9G4Rso?>psyd_IvMd;We7#jVdaYG zXC?Kpg-Sg>$R=7sNqy5=axTzgSo}$_0eCF7T{%%%AY-vjzQUktxhB9$Y-NCIA%S}m zD4B9_KKBIZ5{wbeS>*?>&sLQd?n^{Zqvggd-)mY-z@7vC*H_rBbzqGEQ@`&UN||N* z(ARY-w(ZNWwN4cR?Ik~5DAsAOHd`z(S@nRY;w06w>=K~-007*73(%1pg_n|1RRU4cE)|4{M=EwBzoZfe3ba??yD+YFtDBk=y!s`{T@;7z`2T< z$u+_yej+|zT<80ErQ-i&P&Qy8`W+wzDmrp~mt|J)XJP0zzf&4+@e8v9OL1ivmr=5T z`XlRdmWsZ$k&n?fe=e`z&yEwVcWg~3lp1qu+Wl>QkiHkus`GId=s^KO-{>f3-1e!1&F-AbFn&>)7X-v?3^V-J{Q*3Izxre|Y z^XxamP0w9yZI!;zj+`)wF1;So9rc4gRGa6Y-R@d>s$bXCsn|v zsuBpWuZp;5xG<~E+_wZ2Tf#6ohMztx12o0Fe#!EQ;}pb62buf4)gj~lxHnWnJOmWg zcdP24kj0^3b&2z$;8lY7IgssE5f#l1fbgTd{4{_0snS-*mJbxk_+T6_et`YW^AUjD zlWJc9x9b6j5hy6DBOfprJhVPv)pl97fKeH~ZFxwYq3V+ADp^GkSVR~w0#*~|yelBo z7B2mH18`!JPrAUOGZ}hVgxDwEOQ@$2qx7QsK}3{&tIKfYJ+HS5&sX>*=UWXygg-m&$*91$tq0C77#`>cf$3+Go;uJ!-H(OD zl*kEI5E_(Y^%K`k1&zz^9u*P*4|VWX?hA<-l;2yexF~mFfIn#e-o~s%*==Emd&apA zdn*uI7&itt24NGBz+;)${udc*J&N}H?YH09{Rv^jxR+P|SCs?qB$gpj)7JV`;-C2$>jx{l z2^bkbssZT$&I4exs&fEt)q#Bm!Ut$m>sJ`3hjR&GlE?ODd!m4*4tkt&uLhI{p<|$T z0Luw)%zVv#29U&`*sFw4Nc#j>L*M6j2Bl(=5JU5D51T#iAq0u zl;Y}?a!}@Sa7CSKkhHmNsJ6xR;kxm+Kfk$Rg7Yckljog-^-Ks|&r^X8g0;-xw8+IU3 z94A!QL=*eKc6`GkDRU5ekbFkwZN}%HfBwn#b=6Bf_|uQ;S`|(jOROo+`K%d7LdMlv z`P~R_b=w;yC*S%G%{QHpWiKz5FcCnyg1zuhO1rzl{5k zD;aOOzoRNX>z>JiVNZj#BG(xEnXE0Tw#d(1#WL)rHaZ?jzed{fB@1UkL``&{yFz-Z!ot+5$MaPc06#RW6%-Bc#OC zieOZLq_ky_2YI;)dL%#u|M}mhH-bJ;8Us+j!lDK@+Z4WLIt?tivibc z1eH(rs0L6;NjN>9Ie4HF=~n<`K%2h~P?9zgHTF0~fB?2P0nIJI(Pq~?% zlmk$k4mbq5=KdmWzZWU*&2PZ0T`(tDoSnVitGVwnm^i$ zeL<*3fHIY`6RQ?>>jeAYD~o{nNwyBSx))11`lhad=yq`lNna_Seg~$Gj zaISp3X%o_9?_!^Gg3Y)2LVAH}x=HHHsi-`kL2awNe~a+iCJPP%>^wAGp`xw?nFxXb z8bvk4Zij)N+uimO4ArD}J?29t2(cox6=A_`pGbSq=bHhbRN=`pn2;kjvIXGci@vqE zr^haU1Y;UtA^>v$aDBW~w6PaVkRtpX0mxH*HQ9c^xal^Z30zVoPx3?T2Qv&N1Mu^i zKIZ_4Fuv|Z0gLbF8*Qz)F-DkZ?{m!Ci%X0<$lN}y^aem%4valFJUT2@7lHBf5h2UD z((u0iKaA!it4cx+ zUZgT0u1|-$G8k*qwPRdCIgb?y;B+UEDk>CM0RdZzPGN$0X~oqSE0vw?^YtLu!3Ve1 zxRZ>nIRpR{(3c#CsYXzYV*qOgpb2W7D}#j_t6ESQ17dF9c>S=uAHU!04|!Jmn7gO*&Pv}0b6QN^F*+)$<@n zw?0l^BVdzuA)77c7Gyjuwm{Jy0r!att!9e$(dls1{uYFnlDEL(bj=bS3+njAZPbL#+vBVA2vYZK+qt7k? z0!h_(QuVPi^8Cl`ja3`B%ek$rvgu&z^~Uv&*Cf&wR(G_$ku5|&Oq@Ev&UFLpt~P88 zBX-?^Hk8~vw zys(eazX0|{D$E4J3F7u^y!o*A`MBHnnd)-{0$XSgFh^AreE#oJDJOpiQxHJ9f+q=~ z5jc$+gpPxTPy4Zf>N~NWOHjBo-vI=mR zqZ+)g6P+BQr3{7b{UD43^S-sT{l%65@JIiKvU~3bPcF4>3}kKwvzM|sS4Yalvu$Cf zYAX`i^Yw$wvPkuP(jkKV|YTv_^+k^=^x!9HB_ zS&2Pg?=PuVH>vs`K0w04C_%Aj2bJQ_yD9`mcxB3%zjdImkcjR3Ash^8Ru zVi}-|fF@Z20U*E9w!(g+lY;#2pZC}m5bQ$*6~;9|6RE}c zbL~6&{O@y*wj#5tZSdty80!F*R%U@n0r~ge|6Y#(AIQaG5DGtk^k>Aj?zDYL-T>QX zeKnJ{^fL#M6gwau>>XH4<^JQz3c=U&#Vm{!L_$@-BWzhZ*vb3@g5p*kUL5)daK`9i z{qlzg$SJSq%|JH5UV)|pKsWhjmnbmNNAa$HfBzP;ltBEg&tc{?5r8_0@H7Zf2rx_K z_xA0b-}6w2W4rb-12r|;>MqM3wmA>yr8t-wDl^$PtjP0`MO6E|e+NpEV~8N~c22f->f>s$_c+?>Fm zW9cB}Wr_7d)PlUefRq7J1$xNZAfR7Vkp`y%NVnKmEBT7^15YmZg!Uu{8@HQYdGvklDX=XiX_x(C2p07<`}nIZeGH5lZ~>fD4V z)X^s!MHH z-lHNQdzuh)21#4i1I)*eGqF255?cnW(^s7TzyJPw`SIfio~t4Ec#R3@G&#gE;W=Id zvYYrAuQ5Pht!Dql`X+n0v;*23zU%?gO~eH+QRUmAD!_J6Eml04*REKJ#n%X1KLF#^ zYw*Yca3u-bIu=Bk1F^*lk?66+@gB>g19_PFOiMFAhp9lf)!C?=%9@c153eiLbCvs= zR1f%h5!6uHds%5oFtVx#pY1Y*%A7ky$k+^m5>R*qoP00qCv#vJ#O!i6;#T{>ImP3cGUCzG1kR-{*V9D zzu5ne>dAYPDg?kUROtk-tu%#x0jTCHfs(5HmvU3!BmuS}0EYm}2Y~zOG{RS5MS{q&WCqor!xd@eQ>5sXP0vo1Cjdm1Z7Mhppf6R@y+xoH!@oZ}RDNy~0k=MM zSP?*feyVDU+OOF^X;)b(zJISSGZsi!#tK2ew^0#GWiLQM0;~pq`^z2<76n*MkKtM! zxGD)Ps|EmUH!;K;K^lZ_s$}-F|6EoG+R*Bl5C7_&21Ee*Y0^^_>Ity&=l0+MpTHYH zMJeVx;NO{F0A{_9(cYW<+?&9^mDeF6Mc&fSs5-6GjdGRYvQQxJvZtYv1=bt;i|Y5@ z*SxQv>jMZ|`E9uoT?6sMpkb6?(k~v>2>{wxTQ|tPbfO}i5x<=nzT)qtg3&u_-5&PYJW5-AThUapAY)FPXyj?lN7fAiwKf>&08tPfk*jHu$`t)mF+?m zFb=9DMEZL`1aNcK{~Lff=KmF;kxgv4=6eHJUHiNOcwGK`-MO^lzVZ2Qr!%$6P#_Cv zoN432;9026H*LTQNrQ#J4G&;N%6yD}%s={l7TS;y*vDG;Cm7iNoxeXnN%--w#4t_~ zC!EJS`bfQ)>H+lCS(OzG5Ejtcfs<(-0ehI2A|)34pzpNe!gvHx_j4t{Qoo<@aDhts z%=QG(aFqoNOEjRg+=_$$T9zqSsZdz9+fszytr(Au3i>MB4_q3ydGQ$sL4i4su@6#) z)dW!X>@?D$RTMzy01`ZWmtki_r5Y9alkAQPt4t6Ss=)1wRUmVn0&bJ6*sMT5RhR7V zY&WSO)@|4cE!>pRmd({!7Dg9Yg`tfZ>T62|d=_#>SYbji>r4h2vk(;nRQ3a`om80v z3myZBP4ke7WT|qh6yht}vXFGd|KsnAY;KAo0Wi)R#@FQWkH*7yeY;$XIjIsaY_j_- zm$OeZ#{5ajAE+WXk=3#w@#`;E5|S)5IS2oBme)7F-$c4BX+WspwfOndb~b2Vfa-7b z=l6=NF~sX*4h7qop??Lo=R3I?S+a1$FMFGMay|!{6;{VkL9HqtR4u@taG{k^{gm%K z+gO3d6Rn*VhL~#z-{BWtWVf~OI%GeSu>b%oc?J0Mb~)?2Cgon-X(vMu`YJ0P99CX@ zu1Sf>P(tQ(1n|af3*?r!@M^U>$&rM0LMb%M$_9bcciR};;P3x>4yz)n^r6Ujv66wH z<>M*8{pb6#U#gZ=E6LKvel770zPdSv^a3OP_1dTS)GRe=eOIHZ3ka+jO;r% z$CIoVCi%!i0m}mg_J>rIl8i(K0?9}G?iM}p8vt-G0mgi!9l`Op-p?Ng zZ&AvAOBt45u&Y6z;hJ#J^5M4FuTv^Nmr%#M9Rv$L9~A-@m*mzm0^4}#z?v_={u8e+ zYX&~U<3rUCn0MReiVlT%f*jfmPQtEbvNao?jm#6kq28bg`VM5yNdyG@1qx>TkG2|k z&dTqV2hJRVa5?a|W|^Qv`QYjW ztEAap?0gNzF2-Kdnhl#93}GH>`*of!r;%rHk`+k3t6V$5@v=xFp;>VK^242zy&>xo zDzf}s#_M2B!?jeP(eU8)VU7e4adTG?EwYCJcuLRqfS&C0%auOxEI*i1m3d^!&x0p#k_YWs;EjkZCxvJT)t z$U=dO8=pxa!046jH3mQif?q+x3bgoAwnk~+-$T!L%~e$g5Fo($136(_$#7IE$wsFBC?gQB+G<^32dv998|QXp~E z&b+S^_XuA;yaB^D+jJWI<65FM_F#f-_TGQv1R$`~|nu|&c9003r9iX{w5Q3JUFkt3p7?YSKw%~=GoGO1P{k04i` z`>ke{lIu%h_VoRCYE>)nNN8St|4RWAMK@F zoiZ#ya$m8G^1R}EqAKRz)nhrz{;W~B=A-=x`h7|Wya8LUE!+#_KC{jquQ~Q8wg)K* z_KoU3pe@EPUT>l=?HU#Hm^&f*vUU_bn^|FzbuYP+H~9qgbow(vdpJ>G}!!&o=T z!fOuU6XpIz7Im^KT)r|K@=Q%d0R99ZcZElSl*jc)d|*(U-xDGw!RiO#xw)^{wi$;Q zr^Wll0P3;KS#9N)VDQ+-c-}joWe(9bY>g;zalcr-J}i2&E_%#y{%0-8INHA|TT7}Q za6RaQyg&P5?}_+1`B^m?%xC{sux~N}qTFxkN00q6#tq^R5+iGZ$<&s4pK%_Gr;r7% zS{Ot`#+HfI64gJ?8bAJ@fB#=mqI(65yV>HP%Hx@S?FKOOkPsFiPzbrJ16n`pa?_zv2AdL4hY2s!9JzmKgo$R zEfY8p_kJyG(?v0>=UB1OSEN8i0EvOCb;tAl$wG4C-qDIk%!Rq&t+e z{qAWj(n^B6wBZ2I+~0@y;^#B%i*}GDjehx1tQ3WH5|AqjfW!Ax0)Z-zoT+|Z>46Mo z_?cyz1X&2W(rUyOHM|CU!^&%YWjX9RHL9Ig+GOzhYiuR3c?Oh3fDxp?a;2XnJ_G7y zjRdkSQXJx;1)`%a_6gq!{3~R&kGDyr$KoJc5WEI53&jDE>6iNV9`bx*VN}!&HBKo^4xEBWjg5Usr0LIsaV|0=tlI`?r$_M~p zdnTJHmD9CqJW#PYO)21{LY{|{s&Gn}3QR83eN8%u!dD2M&wS|OkljBU_YDwp5Mm-Y zqzb1ueX&K$MBjh3AAO#+qLzN7qEZS+1S%t;;zlGhRKxvs-v~OhGKOF!H+B6Ep*+TM zSGGZCk}-h9aqkFtgfeQYkzOsFC+@o<|iup6)=oS20ru}UASaLq4~fZ2b8U^ zw$NdzwZUxId*QYIcYDJAYr|(OB)l%OEp$9?9CK8w!X~fo z<`PaRzGWi?l7N;3CVy7@Zqt57N^bV|O3N96_YW!pD)Ti6CSh%dcXlxu2OEH<@%#oA zitNPLo&r_1LArGiaFt@8>du$_y74-!sV}eZB%GEDLDR~gow#1&b41`NTSR)?K$=pd zvfo!-`?arkAjqMz2jKR!njc6go|9L5{NuB5544PeeGp3H8IZUeF|Dj%;-(eggvU#O z(_P-@T&q(RRiv)R*pf29m(?`sfQxJ!^2u}6Y$I4l^r0f|LW2UiHZRKv%0 za*hD)BDe=T4BBebrYwRw3z$a*0l?^Q-Ygm>Vc6*&(1d*^(xX@z!|{S^rNx)*+ty8> zeeWCC8|S_Ryidraz07Utao$qIO zavNpDj8GlFK(r}%F8V}JC6r~^Ms&0ZLv=L6kz03bpJtg1X@+eLL%tyTsB7CVp! z`_9itsc>l*h5kPIHJBFw>{z(y0Qm2n@OVi@jd4b`+09{&=VcjcqH#z@RS9eiu-_LL zKWq`2^Kf|hvg!%<_pSiZ0geFBXJA@s2cOnQZzGj%N9l_1Wzg;P1RX<_O44MO?|V~q%FC|{K_RuE)+im^zkGW6ymXtA%*5Hx>P3Rj zvnnP74%}{U0}y%#m`-ueW^hP;yxa>!mFvoc!Kae7pO)#pRXm=L6`@z zH#k9Qh>FeEvm60CTo2dY`@La(6;N^x7{}K**i+|4D=eWf1!-|+rom+ZihRy_+~9pC zAqM~oKqf?902G6Z1UO>bV8wem+M(cC5+&wAW!<9cHwZO=@y+w7*q$MN%K?P!k4#d< zs^yyZ2V4#iWi|Lv##-IJ$Fkw^cnn3k|NH+pR5D;}>`MZ|2FMELfjg&*Ex;;r*XVmDsH08IC`g!)Zf4Urie0^{Qt2)$S%FOU7^!sn}8(V1kIG zf!BxJ2Z@sS?1yNva(>tkfpkiL>N)xtc1$5hV$GEI#bzo5MzS!R5fSxzIa)f<}(9T`Ke4u2yuvww0YLF zSQB}C4J)pUy{tbGtzyw|^)e;Z)UgihRL3a5EJ~)&DpfwV$5gKa_z(z0yMm`f z4HQHRi-Hq?IeJi^JNyP<6J^~WduN+r8AA(~e#mdJfZ#&jHtBe)i6S*+gDdLK{lSn+fFS@EpOrwB?O!Tw0$?T6w?_bO2+s2fUF73Uz_HRF zzrx$TPXnsZa3A=L{vIemsS3t_!}}es+$xZ4!!uc7xSM>30X-rNVVActZ8WLMUmBc{ zZT&$jmC4{R;E_q8gtYc~o6aB|D!_9Ba68M?si=fKAJojNRbE>&ataW!-{Y?A04j56 ztuSe?4W*z1@e~|KAX1@m06sggj37RGFE3J?!}f-jF(2(ofDZw>>c&v#YH`@YNP6W6LaqWOXL(7h-i5&ia}D*e)A z|1`0L9Ag$R%DGCZkIw}teQ^-;nV`PtvIJ$qw;$^eEHE%G*)}s3I~Z?V3Sr>`ptAQ5 z{ZldSp1fAsUhrToBBy;jU8p3a0*rPLYPYfpq(F5BKu)YPp!Yz!bRegLUN2egOx4$> z5M=~DKNV(%^@z$$R5dK~)M&ASaf5*4S?XdZ8FTHv_4WaXiRuw$O;nP3I4Yi5y&^BV z=PrO2|HHgE(*kI(t7|6kIcR$jrML#GiK#KXuTNT4EN)|b){Q}41aN3=g!Q+RfMoP= z7{AO4*jX!)RB2bUMF@L0pJfPzdQIR;C$mpoJPo;Ubf z?h47??Eih10*qE=WZLP&y$}q=zgY#bs5;=K*AtOj4OWwM0@${yR1E_eKjY>YIS@O8 zJb8|Qy8S#Wu$9X77BEa6#~AhaH$nEQ8~|(@h#a!ArH{vcp#Vldo_AITVX3DV|Gbc3 z#pmH#P$_}&uV7i`vruh|&*Ge6GhtQtVoLQ9~nLU=2JDjD>?+fN=gBZSiduv~5lf2PMU<|h=y zRpc)-0Hm&;HOE)7&{h2a;s)Wd3$78iJfsm3B8+*!Tm~pYR2jmyfjV+G(FLI6dRiE` z41(@YZz@^U0QINKd^eMFGt1*=dj4wN!m~^HCYu1w@DqAAQNax0ZY3!VF!_Q0GI%<( zZIGux)h9ix8Jd2QnwS*?_V$K2M=K(OLUkXz$}%g-QK{Nro7xg3r0_U|18>Eo)s_YMBX{o!2|)%YYXe)df`vsI`bo!gzDybd=eUTEW^=fgzHpkj3; zNB|H5c($LABZN(CpBFzr?*ybpJUj`W!b^GOdt0hinw0-*ov}CApte>nLKP4#Vh6br zGk0ITO{GS^-=DI=f^9~>54>*$$$ygV6bbqjp0E?uIJ9u01h=we3o339Vn4wBFO=cLjlSY$2UIl>M*#Q%7`cW>BFjn$}ic6+|C#JK0mBP$KpXBNkO+o zx7)}iV4UDPq4KA?A0Vz(Gf~jr9RBw$heuWNa9*Izp0aY{!R6}kLmt+=fzN^XT_I!x z7=YE)%RrMvHd@DUFa+tsgSx$I0WSXW=btSQ((OHzU_zg3+Eg&5oY?{H>jXNQ`k0yU?1A=W`OG4fmf)7Pd+G} zV9~MfUnGQy0)(>S-T)j=BB?sb^CCcaWk4>$=GMVysv@bz?bq`C`?m`Jb3T*$;JZPU z2_TRq8spvX5EKfb-c&RVoDZUj94-eL2jwC)9xfK%r1Wc&>prZ6d zJD2+T@!1dZ-)WWgN%i%O>(0}*S#q*Nc|5=5V`>G z-`?L;%|`%f3Q2@}7@o@T+V3O=y=-Yg#J{#m0gHSpJBRS`gb`|@fJz|*YG9KXlU4Uo5hSoxnplorq>AQBtF zW0lV~D_d=O5c?Ot26NWx@BF9*pbZdYwJG}(pipc*aP0y30@xjt`vJ?E0rYwRg2weV ztE4fuLKGPMtO4donhAJjzmN>GMH7A>pvemUWC;up`B-V8U0$+i;(fZ!n%8c&F@P=u zQSnh{wNd)QDrf?Uu9P@r7Yw4K9Kq5M2529=CgUk&1jedgSJRgm6997Y-FQAkP22&% zDeCY5gsub_)>Bqx+ivX=rN2mIkth5tSE~(cIFll=HX1?6hkOOjrQ8QT1J|s|lxe7s z1MazPV+E7B&@vvsNMzcP)PSS5L#Y9~n=45|8fD&jeAR&S25MvJ#CeMM<65K0PQ}Tp zz+`@I@(ytCc&)3}7>Jv`%RQx^bHA}ZI*>@srB=}g5tXwc;0{a#~**x zJU>GY<-2Sh)NDmGDgz8yCv$-PYEnWuLC$H&I5vRH*Yt5UY;)4)uD0U(72?Qapqh;2 zTJ!F`Sgy5{*eMF&PkWfyXi=A2iOuC}PQ7)rK4$jFw8D11l2?~K2f^B4klXRWn*pSE9{!+ZnS;~GC* zEr`CtGbd|n2*=|bjj&}={(~wT`}l~p4fg*b!c%>O{!Y~rKKCzw`OC1M!Wcrv0sieZ zc2vTmjXi#9K>Op`p8O7f2m9URLYvS3;eY791-I4470O>I?AD2gVjaK%kW444i2I4B{FpBUdA|WJ>>I?rZ+PDOh6+IW zw8#bmKzt$CGt&|1yiy``H_SUk8(@Wd387Lg0OZjf}wJuik1BeIujEafdbZ!bPZsh|d-TR9mC0VDP zAa;dP8j$vTrDB;@006c&+b_U)1duciqO5VC9W>p(Np76`USRD&YVRv%4s<}ReqYW! zkl?j+)ra!G$nP8KVECzb_>11spRgqYkR&@0Yjdk?WcnB3Gx3ceY$k;7;&24@xa@9d zb3hxV9g6ZRD-5s?q?a`m;)OsdLEhf~@QEi-4}Wi{{b|tzAayJ3dyEqRt}n?!E493{ z7+olDF=ya453g?Q1rI_YU!*jx45FdOeX!*d_k3gyW} zb$4Yo6MXsiuTVTFr&lY<3B;_hqiO_`xcTaUCTu{i^sC!wD~1F7_w$YFK&aQhcQ6p` zeWCTvY(=cXZR!6?4|xS-V@_WPprLvRWUVt6s7^m??Lvjiq(D)SdaMlUqxFx+XJ_V6 zakJ1)4cGOOtqQ?m)GS@_pvI<>4{L&^O-GzY&;7Uam1Xo;t3V!6?Y-L)0pkm5f(vb4 z=q(G<j=iBfCl<16WEc`eHBpyMH#>`D^sdf%A0tq^8I=P zC{B_JBnQBa#U<~si=sV+tghL{yc6p7-cmv^^g_%uryp zVD0@*!s3ZQ?enSFcbaO+pC6y>52^;y51T3jaJBTN$7T?Uk*10h*r4pLHexU&fO3rW z7r=Fq0|4Dwp=Wg?+U-v$)tl^{KJK42!Iql|bN21O|F zD1dANIB36v@JFsy_XkSH=Ndu-V+HHuhpl5auW!X*3Jxx~Cn$ZfIsfg)4~~o7wlsL@ zPXG-`>B0{P96?V%tN-jyo& z;mmO~7*-I{y)FL!`|qp>_ymB4wpm{ist^D&DS~Im=LhbOzzjh(x1nu*v_FO|L?pZ@w!435TkiZl^Xe4&NZC-=Ows%W?pU|&cu5$9{Jxf=o5 zmn&XaaUzwb)`NcFM+HO&o7(qIKmY*vA$2UX9q`?(%vq!Wec3(%;2gS)fSAb$hv5zX zX;xX3QAJQc{`grD%8T#*{NcgIJ_Rp;g!c zXxA@KYK|umw&)*wq!$MrmiP|u4negtw;G%#;3tAYL5TJ{=KbP@XyX8`M-S(W(I|W4 zt6XTUb&rQTufMnX0MwiNB;y=^zsb^Q07xX7{GDiD%(DZqS`EAsyhQ&&84A$G-qlC- zFadOoS$qbR-f0hKHdF)T=@SB!QMhC62k;XsA_G9N4&wKk>>GrGRmCojR%mCyH2lFe zMoDUmo&m%Hn2R@a0L`>nRL=o4v$qZCg=-Cg8JiLG;l4Xm#1{~W{d~B82FSxJxtQ(|{TAkxD z!`?xj((xWoM!0-Ulvt~uveXQ_N$i3#BEB3}*M%$oY z?a@B$Yw-P@8GPnnjrUBt?byPF=TtzYFfBG?1B({PXe(`9BDCt8A z;JKFyhj`y(Ol7X%-tpfC!$Um%Qf(SS1Y_RoU^`ZeV6B{#7n0!K>Ix*Tg@66)U(3J# z>%X#%aKDEz?dth8#1kPth?R&W;qUXz3Q3-O!i}as=JtR55C3(eBM* zRT=s>dax_}69-NJ!34dhGi_`DB*R}Dz|uY;*mR~RdsI!>c+om$mleYw&rjKh^iA7J z@6p%0af@|TQ1s45pQnkO5#Y!V#PNg&Q>5k^sQ_r};)ub+<42 zGC6p)+A5$(F%LyDl#I=ODB0|P*;7CrQ3)QOsw_yPG@gK}lPK9}{nM|l3fzXueYUb1 zAO(V7KWwZ+SMTvXy`*AO{=xOqC2KWz<3H{T-FBbD(d%HjO zC%^a4e!yF438T%Y(89u;fVIib6CfrTE`X%8|DjS+HVoQZbB_;|F97Uwb&51pORcgR z!WJUzca8bR&pkV+>HA4`C-Nb0&Jhq`bzxxVOyeVfS^yot_5LeTE1EEvI39lXgTW0x z+LNviZAt5qS#~+xq?Tfmh%|!^-BuaTsutii;C?ao7uw*=2>oTyAm#!9fWs7w8TR0vyp7TXo5&@8jnx z03N{pIi7b`Of#s{zr)!1%Som0tG|m6sruz{%nec>OC*D;#u3&8dT&!5uQ`fqM>WVc zm6X6&kP}eRSba;cZegoM&YLT{A23!N%vEfcoZ*0ziL_r@hKUTv-wqRtjj2F}w{2cLmLr*@p;L zG#GGDW{3d8hrx3yV4-XVVMXG#acu@61zQoQ*wNOs`f6H7u8RVl^Q;kI(m2mR!0rBy z;_G@UIE@c`+56~F24Z=ph0#yWxi|akp<>~W&ySY1i5V8OUQsDL#w-Kv`l%i&K7geF zw7iCQu;EnUM-E$&kEc|po#-IR#pfmLU>Ke(AO+(Mef`N?D_M6G2DATFD zFnQLb240n#9cQ~7Ha|W=1brNm#c0Luym@v2Xr!7G6x^Fja0>5_v+kV}$Mk&(zV+{BH zV#NzKl+1`=m5;`Y+!R1atwXtuH3lFfvtWF%2HEki{Q&jbP5UMEGf9DdLBHMJIUfJ$ zKssjqLc5PjyX25RcUZ8@h->y>g|T>Khctkz&n7O9g6m|1FI%xj-XxP zk6&4}BTE%L%NXSBwUGclo?lItPm9S!&knqPyIku#{ZaKySo|ErI{J8N?tOpn zhppGf5QJ~NjRSPjvJj-#O2CjiBYYN~GV~_j1sFe9kzdUfDfEZ$|L-RbINBD`pBcXQ z`zDJkgs1|T1%M5p>~Vj%9Tdj-vTac|-!JtoualN`0&&Md2cJm`2Pi6+sbT(JFV~^I zqenUpm}pnoba*}Wx^8=|rz|;WGsL!BW)d50)7Q(yf;{18ex5*T0G?3j7Xw=X6{u>* zb!qLapO3!ctn2i!9R;rqe*E@my?N8G=Nv&>5(ytHo3&!rge|yF06&J@gz`bn~sDb-@>xV5K z4he$x1FjUrBtg*ke}4S5MGS0CF5JhZ{CHhsD>1Q>hM~7m%75Oyn-{PcyO0j}Qmsv& z@!r?pe|#rE3?NxP{)N^lyT|)9$?x@PunldE(K^#UtI2PFl7Bs_au!yYk<6t9maJU* zynhJ*fhrr{&#EbaaXwD~M2AA3Bxzr}FfTrBjwRhrhwH$m&D9<=F|ffAeqtjjD6{yp0Djz+|q2ldh2<2SRa+z`N`1jcRnX6UXc2 z9JM`2PWB-{d9*25U{+c9dGgW67{<|s)*2ISX3&?MyHMDpDs++k%}QVTGE~f5BM3xY z;wwKqy)kz2nuIEY_~7`4s0@4bJ{HwB!}4TUM&TY70|eLWO=^3PQ`TUPU;f~A$I4#ZLxFRck4i2b-dy2A}1e}2=_6^ofyzVJ7 z;q1H`jFa5M$IlNwpU_r6|NP0!2mrJJAV~^?AYD}PAplTBw7|BN^C!TQtwBhZXpci% z3WJ=t09f%54-h(lHT;36f4qxvOG*PtTHL=IQ zce%oW_Xv^)VEMtyWQ>OZSOL7Uzs&PJ*BgtLygzMlu<~JB#t$6CKx{j3t=Uh+#v;Jx zr`M~@9sI%1o(_NhN8mO@OTfd`A;hsa2`C?c5AO%S*RPZN|ER$6H$qNh!IXP<+t&c7 z@v0B-92=B?>Rf;OJUM!Fhgiyd!`NUD_Lqlu?s0f&XIxp`KIVkMbP}E+g97N@T)B7j z0goaZis$1v%N#z=i!TE2K5};eXTWxhBdehK zSqF9hAl)+lvo@4c0`h}oBT!F)s@Dm8*DM>OdZj~MJc^EPW>q#n6q8vZkFXfs-++>bqZe=}8UO=jP;=i7L!#InTQN}g? zhjq@M$$5EPOEE`dB@}`y?UjBx-g9gXk>8Q^I)pOTQjZyu0I6WeK0NAp{CDhr(#E(x zS3&V}@CW^A0+*!fVf`PLJNOOE^8Hzx(Z2m>J5>Dh^dJAH{}%^cs9>Qw z+!!o)g53y_QEY7XYHyw*fAvPYfsN(*KjD=P>x=0`|70$XRGhA~sKDBJ=K**xKM+WW z4T3F0a1i>lJ&Vdor2y_gX8m!x(VPHphQ$H^TU^44s$murEBFn{{JJ-&xC_-OJCB#& z=S%zh>&^k(fm?dUN{u~>K%gIf<9%VHbDPdq>@}9z6D;S>RF)U3uLkd-H#ZNZ1aAqM zo}tR`ulccDTPFqT+awh*Egu|&M`z>;yNffc4x|q(WEC8WN9%@|;j%2h{~Ew(tW{v2 z127lwdtc;Re(wB*Pq~N7h3_2JAq41YK zRVl6+I;j9kZsPfHDovq|2*`rg~1BVfbw=>S(=r$QF#m5%X1 zzya;JTxf6dlBp0Y9vrj@;J6{27#`vnZ7A`5>wojG-{eR8$NC@>za(T5la^4CNEHm1 zH(ujPTOsr;{LE)t+3-QdU`-Mev^m09e`Cn%%*0XzD#IHc^XMhevH>()rIrI#)q(g; z-oW#z_Oa0VIN%-LW`%NY_Rlwl1vPjBSwTw{%}-hk;Knbil%X{h$q*>5JDBp9^9}t= zRX1!|zV&g3c0(WEr2uXS`^;FtT7ilT+B{hS&y@s8>HO|acwlXDl>`Z)zH$HHSgs@n zajom_rIGvti7*l7u1{LYC^)t-uo_@GeV%EdatASj zSIz?GdmGbU2X-N_ZsOsiKV*M@KJZzzD*-Tma=7dwRhM!os_e%806^5Q6W0>bOJTOm zu0Zugjb+69g%wN$W(nlZXNI@pegMA98@tiE?7j+I{&soe_fc6-rN7keJEfY_gnPX# zswP2&1lBVE+Wm7`{tXg})&eg+hqR_Ng{a{u+wZ%RZFl;_&bGbSr1SYLQJCW0%gp~i7{E*JaBa+3gsJW1VI_qV-B%tA?wbsKO0?3)je7ntLh-ewQl zLr1qZ-QhoMoe7vNBz~LVDIJkaPLx9vr zfGKpewv%zF>`6dIzs~DP^Z3cU6Kx_Drt9LS3j3}E`V?y0 zzkY+lilL%x7kHUt5qMFZ=cre2@Bp|A458050X=|o&zEvP?2+9NirdFA$rL;a|I)#C zv@^!fj}FfNe*fTW;uqfAu>JY?s8m;XKpsFo)0i!6)I^&P*%<()14sxF!Dqkb zlPd}E>{dm`g9O8BtBHk&kUobys0eWLPaHB|Pg<$$42zO73AQw}bb+D+mO1?b0sH_! zrIn5qDvzg>b^w}aJIA@&pLaNrQ8|7lXnecq&_i)X73d8s39Pxz7CiVTJ{MI0+_X;m z9olQ(OyI3AJ{PWijG%v|kgWohy{v-(UO=J0XxW(Su+nCV83fqZ^utUC?xYoSV<$67H{BbQ8AwnfJDmtRn)uI0cppvR`d_EOq0K$s{^qTge{f0TF zq#$~+4=%&clGRZ@44h#RLFE>!IyPFr5&Qy7V~~<6;|_$gPx1XA46sK2?eBkQX^PayuH=G{q67E)4UU?|H)?;mAucFL8=16gk<3N?*h_6 zB)p*nzh4Ug1+?R2)g(R#0FT3fX(h1x{?FITSOZX%dhX*I1PVcnT{a%^@&Drwl=>sI za2{3y0^?4;=a0OAKv^kyX|0PC6nzF40W*>co?*8ya- zHnst%90L>$LUF7?tx1xzlL1jR$G05oZwF}j{Is&Z9W4t2_=RMk&6C%^R0M>4h@v^c zn2Rmp@Y#4@!lnZ>(-y6dL98hOn{1^qK(=CAh^UC6ybb`Ab@K?eNA;a+v>Lrci5r3~ z02b!ImH*Z4h`wlP$0$@&nPBjhb1Pt4fVQ-Q+nV>)ziYyEkx&$P$Tcz+DEOSH=%b{K z(m3sr_C%;K##SmA@cNJj7+H|&01&LxOVNh(6{efIKi@qWj3Vt#-C zadk=gr+@mVWRsNqvGvZdN6P%jI0@-w>!D)x@yJXtQNYTTgvA2L%=0XuKZ#G<5Sggt z`is9wSQe61!ye>mVBZS;sGvy%I6H=IYAd5^l4AIl2Y62#9jk3pVexcD00@uFs{pzb z<~i5G%Kqjm$nk8O9K4EgX3_=0v;lD&K;yK5?O1+MdLR7ATe1+8(V_bZ`0c*r`iE

UgkJs8kjy&bFPWzUOC6C8ZaRAUg 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

-U} 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 z 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 z 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