From 47e1c7b493c16f2a206f29d12da9ea4c59915ca1 Mon Sep 17 00:00:00 2001 From: Arunavo Ray Date: Sat, 24 May 2025 18:33:59 +0530 Subject: [PATCH] feat: Implement automatic database cleanup feature with configuration options and API support --- .env.example | 6 + scripts/manage-db.ts | 39 +++- src/components/config/ConfigTabs.tsx | 157 +++++++++++--- .../config/DatabaseCleanupConfigForm.tsx | 146 +++++++++++++ src/components/config/ScheduleConfigForm.tsx | 84 ++++---- src/lib/cleanup-service.ts | 191 ++++++++++++++++++ src/lib/db/index.ts | 5 + src/lib/db/schema.ts | 6 + src/middleware.ts | 14 ++ src/pages/api/cleanup/auto.ts | 72 +++++++ src/pages/api/config/index.ts | 14 +- src/types/config.ts | 9 + 12 files changed, 667 insertions(+), 76 deletions(-) create mode 100644 src/components/config/DatabaseCleanupConfigForm.tsx create mode 100644 src/lib/cleanup-service.ts create mode 100644 src/pages/api/cleanup/auto.ts diff --git a/.env.example b/.env.example index 4d6d768..1622203 100644 --- a/.env.example +++ b/.env.example @@ -30,3 +30,9 @@ JWT_SECRET=change-this-to-a-secure-random-string-in-production # GITEA_ORGANIZATION=github-mirrors # GITEA_ORG_VISIBILITY=public # DELAY=3600 + +# Optional Database Cleanup Configuration (configured via web UI) +# These environment variables are optional and only used as defaults +# Users can configure cleanup settings through the web interface +# CLEANUP_ENABLED=false +# CLEANUP_RETENTION_DAYS=7 diff --git a/scripts/manage-db.ts b/scripts/manage-db.ts index eb2cff5..f7fa1a3 100644 --- a/scripts/manage-db.ts +++ b/scripts/manage-db.ts @@ -197,6 +197,33 @@ async function ensureTablesExist() { process.exit(1); } } + + // Migration: Add cleanup_config column to existing configs table + try { + const db = new Database(dbPath); + + // Check if cleanup_config column exists + const tableInfo = db.query(`PRAGMA table_info(configs)`).all(); + const hasCleanupConfig = tableInfo.some((column: any) => column.name === 'cleanup_config'); + + if (!hasCleanupConfig) { + console.log("Adding cleanup_config column to configs table..."); + + // Add the column with a default value + const defaultCleanupConfig = JSON.stringify({ + enabled: false, + retentionDays: 7, + lastRun: null, + nextRun: null, + }); + + db.exec(`ALTER TABLE configs ADD COLUMN cleanup_config TEXT NOT NULL DEFAULT '${defaultCleanupConfig}'`); + console.log("✅ cleanup_config column added successfully."); + } + } catch (error) { + console.error("❌ Error during cleanup_config migration:", error); + // Don't exit here as this is not critical for basic functionality + } } /** @@ -328,6 +355,7 @@ async function initializeDatabase() { include TEXT NOT NULL DEFAULT '["*"]', exclude TEXT NOT NULL DEFAULT '[]', schedule_config TEXT NOT NULL, + cleanup_config TEXT NOT NULL, created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')), updated_at INTEGER NOT NULL DEFAULT (strftime('%s','now')), FOREIGN KEY (user_id) REFERENCES users(id) @@ -459,10 +487,16 @@ async function initializeDatabase() { lastRun: null, nextRun: null, }); + const cleanupConfig = JSON.stringify({ + enabled: false, + retentionDays: 7, + lastRun: null, + nextRun: null, + }); const stmt = db.prepare(` - INSERT INTO configs (id, user_id, name, is_active, github_config, gitea_config, include, exclude, schedule_config, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + INSERT INTO configs (id, user_id, name, is_active, github_config, gitea_config, include, exclude, schedule_config, cleanup_config, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `); stmt.run( @@ -475,6 +509,7 @@ async function initializeDatabase() { include, exclude, scheduleConfig, + cleanupConfig, Date.now(), Date.now() ); diff --git a/src/components/config/ConfigTabs.tsx b/src/components/config/ConfigTabs.tsx index 7cb5902..fe42e2b 100644 --- a/src/components/config/ConfigTabs.tsx +++ b/src/components/config/ConfigTabs.tsx @@ -2,6 +2,7 @@ import { useEffect, useState, useCallback, useRef } from 'react'; import { GitHubConfigForm } from './GitHubConfigForm'; import { GiteaConfigForm } from './GiteaConfigForm'; import { ScheduleConfigForm } from './ScheduleConfigForm'; +import { DatabaseCleanupConfigForm } from './DatabaseCleanupConfigForm'; import type { ConfigApiResponse, GiteaConfig, @@ -9,6 +10,7 @@ import type { SaveConfigApiRequest, SaveConfigApiResponse, ScheduleConfig, + DatabaseCleanupConfig, } from '@/types/config'; import { Button } from '../ui/button'; import { useAuth } from '@/hooks/useAuth'; @@ -22,6 +24,7 @@ type ConfigState = { githubConfig: GitHubConfig; giteaConfig: GiteaConfig; scheduleConfig: ScheduleConfig; + cleanupConfig: DatabaseCleanupConfig; }; export function ConfigTabs() { @@ -48,13 +51,19 @@ export function ConfigTabs() { enabled: false, interval: 3600, }, + cleanupConfig: { + enabled: false, + retentionDays: 7, + }, }); const { user, refreshUser } = useAuth(); const [isLoading, setIsLoading] = useState(true); const [isSyncing, setIsSyncing] = useState(false); const [isConfigSaved, setIsConfigSaved] = useState(false); - const [isAutoSaving, setIsAutoSaving] = useState(false); - const autoSaveTimeoutRef = useRef(null); + const [isAutoSavingSchedule, setIsAutoSavingSchedule] = useState(false); + const [isAutoSavingCleanup, setIsAutoSavingCleanup] = useState(false); + const autoSaveScheduleTimeoutRef = useRef(null); + const autoSaveCleanupTimeoutRef = useRef(null); const isConfigFormValid = (): boolean => { const { githubConfig, giteaConfig } = config; @@ -107,6 +116,7 @@ export function ConfigTabs() { githubConfig: config.githubConfig, giteaConfig: config.giteaConfig, scheduleConfig: config.scheduleConfig, + cleanupConfig: config.cleanupConfig, }; try { const response = await fetch('/api/config', { @@ -142,19 +152,20 @@ export function ConfigTabs() { if (!user?.id || !isConfigSaved) return; // Only auto-save if config was previously saved // Clear any existing timeout - if (autoSaveTimeoutRef.current) { - clearTimeout(autoSaveTimeoutRef.current); + if (autoSaveScheduleTimeoutRef.current) { + clearTimeout(autoSaveScheduleTimeoutRef.current); } // Debounce the auto-save to prevent excessive API calls - autoSaveTimeoutRef.current = setTimeout(async () => { - setIsAutoSaving(true); + autoSaveScheduleTimeoutRef.current = setTimeout(async () => { + setIsAutoSavingSchedule(true); const reqPayload: SaveConfigApiRequest = { userId: user.id!, githubConfig: config.githubConfig, giteaConfig: config.giteaConfig, scheduleConfig: scheduleConfig, + cleanupConfig: config.cleanupConfig, }; try { @@ -184,16 +195,71 @@ export function ConfigTabs() { { duration: 3000 } ); } finally { - setIsAutoSaving(false); + setIsAutoSavingSchedule(false); } }, 500); // 500ms debounce - }, [user?.id, isConfigSaved, config.githubConfig, config.giteaConfig]); + }, [user?.id, isConfigSaved, config.githubConfig, config.giteaConfig, config.cleanupConfig]); - // Cleanup timeout on unmount + // Auto-save function specifically for cleanup config changes + const autoSaveCleanupConfig = useCallback(async (cleanupConfig: DatabaseCleanupConfig) => { + if (!user?.id || !isConfigSaved) return; // Only auto-save if config was previously saved + + // Clear any existing timeout + if (autoSaveCleanupTimeoutRef.current) { + clearTimeout(autoSaveCleanupTimeoutRef.current); + } + + // Debounce the auto-save to prevent excessive API calls + autoSaveCleanupTimeoutRef.current = setTimeout(async () => { + setIsAutoSavingCleanup(true); + + const reqPayload: SaveConfigApiRequest = { + userId: user.id!, + githubConfig: config.githubConfig, + giteaConfig: config.giteaConfig, + scheduleConfig: config.scheduleConfig, + cleanupConfig: cleanupConfig, + }; + + try { + const response = await fetch('/api/config', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(reqPayload), + }); + const result: SaveConfigApiResponse = await response.json(); + + if (result.success) { + // Silent success - no toast for auto-save + // Invalidate config cache so other components get fresh data + invalidateConfigCache(); + } else { + toast.error( + `Auto-save failed: ${result.message || 'Unknown error'}`, + { duration: 3000 } + ); + } + } catch (error) { + toast.error( + `Auto-save error: ${ + error instanceof Error ? error.message : String(error) + }`, + { duration: 3000 } + ); + } finally { + setIsAutoSavingCleanup(false); + } + }, 500); // 500ms debounce + }, [user?.id, isConfigSaved, config.githubConfig, config.giteaConfig, config.scheduleConfig]); + + // Cleanup timeouts on unmount useEffect(() => { return () => { - if (autoSaveTimeoutRef.current) { - clearTimeout(autoSaveTimeoutRef.current); + if (autoSaveScheduleTimeoutRef.current) { + clearTimeout(autoSaveScheduleTimeoutRef.current); + } + if (autoSaveCleanupTimeoutRef.current) { + clearTimeout(autoSaveCleanupTimeoutRef.current); } }; }, []); @@ -216,6 +282,8 @@ export function ConfigTabs() { response.giteaConfig || config.giteaConfig, scheduleConfig: response.scheduleConfig || config.scheduleConfig, + cleanupConfig: + response.cleanupConfig || config.cleanupConfig, }); if (response.id) setIsConfigSaved(true); } @@ -273,11 +341,20 @@ export function ConfigTabs() { -
-
- - - +
+
+
+ + + +
+
+
+
+ + + +
@@ -368,20 +445,40 @@ export function ConfigTabs() { } />
- - setConfig(prev => ({ - ...prev, - scheduleConfig: - typeof update === 'function' - ? update(prev.scheduleConfig) - : update, - })) - } - onAutoSave={autoSaveScheduleConfig} - isAutoSaving={isAutoSaving} - /> +
+
+ + setConfig(prev => ({ + ...prev, + scheduleConfig: + typeof update === 'function' + ? update(prev.scheduleConfig) + : update, + })) + } + onAutoSave={autoSaveScheduleConfig} + isAutoSaving={isAutoSavingSchedule} + /> +
+
+ + setConfig(prev => ({ + ...prev, + cleanupConfig: + typeof update === 'function' + ? update(prev.cleanupConfig) + : update, + })) + } + onAutoSave={autoSaveCleanupConfig} + isAutoSaving={isAutoSavingCleanup} + /> +
+
); diff --git a/src/components/config/DatabaseCleanupConfigForm.tsx b/src/components/config/DatabaseCleanupConfigForm.tsx new file mode 100644 index 0000000..1a87806 --- /dev/null +++ b/src/components/config/DatabaseCleanupConfigForm.tsx @@ -0,0 +1,146 @@ +import { Card, CardContent } from "@/components/ui/card"; +import { Checkbox } from "../ui/checkbox"; +import type { DatabaseCleanupConfig } from "@/types/config"; +import { formatDate } from "@/lib/utils"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "../ui/select"; +import { RefreshCw, Database } from "lucide-react"; + +interface DatabaseCleanupConfigFormProps { + config: DatabaseCleanupConfig; + setConfig: React.Dispatch>; + onAutoSave?: (config: DatabaseCleanupConfig) => void; + isAutoSaving?: boolean; +} + +export function DatabaseCleanupConfigForm({ + config, + setConfig, + onAutoSave, + isAutoSaving = false, +}: DatabaseCleanupConfigFormProps) { + const handleChange = ( + e: React.ChangeEvent + ) => { + const { name, value, type } = e.target; + const newConfig = { + ...config, + [name]: + type === "checkbox" ? (e.target as HTMLInputElement).checked : value, + }; + setConfig(newConfig); + + // Trigger auto-save for cleanup config changes + if (onAutoSave) { + onAutoSave(newConfig); + } + }; + + // Predefined retention periods + const retentionOptions: { value: number; label: string }[] = [ + { value: 1, label: "1 day" }, + { value: 3, label: "3 days" }, + { value: 7, label: "7 days" }, + { value: 14, label: "14 days" }, + { value: 30, label: "30 days" }, + { value: 60, label: "60 days" }, + { value: 90, label: "90 days" }, + ]; + + return ( + + + {isAutoSaving && ( +
+ + Auto-saving... +
+ )} +
+
+ + handleChange({ + target: { + name: "enabled", + type: "checkbox", + checked: Boolean(checked), + value: "", + }, + } as React.ChangeEvent) + } + /> + +
+ + {config.enabled && ( +
+ + + + +

+ Activities and events older than this period will be automatically deleted. +

+
+ )} + +
+ +
+ {config.lastRun ? formatDate(config.lastRun) : "Never"} +
+
+ + {config.nextRun && config.enabled && ( +
+ +
{formatDate(config.nextRun)}
+
+ )} +
+
+
+ ); +} diff --git a/src/components/config/ScheduleConfigForm.tsx b/src/components/config/ScheduleConfigForm.tsx index b06e9e9..3316866 100644 --- a/src/components/config/ScheduleConfigForm.tsx +++ b/src/components/config/ScheduleConfigForm.tsx @@ -90,51 +90,53 @@ export function ScheduleConfigForm({ -
- - - - -

- How often the mirroring process should run. -

-
- - {config.lastRun && ( + {config.enabled && (
- -
{formatDate(config.lastRun)}
+ + + + +

+ How often the mirroring process should run. +

)} +
+ +
+ {config.lastRun ? formatDate(config.lastRun) : "Never"} +
+
+ {config.nextRun && config.enabled && (
diff --git a/src/lib/cleanup-service.ts b/src/lib/cleanup-service.ts new file mode 100644 index 0000000..de75070 --- /dev/null +++ b/src/lib/cleanup-service.ts @@ -0,0 +1,191 @@ +/** + * Background cleanup service for automatic database maintenance + * This service runs periodically to clean up old events and mirror jobs + * based on user configuration settings + */ + +import { db, configs, events, mirrorJobs } from "@/lib/db"; +import { eq, lt, and } from "drizzle-orm"; + +interface CleanupResult { + userId: string; + eventsDeleted: number; + mirrorJobsDeleted: number; + error?: string; +} + +/** + * Clean up old events and mirror jobs for a specific user + */ +async function cleanupForUser(userId: string, retentionDays: number): Promise { + try { + console.log(`Running cleanup for user ${userId} with ${retentionDays} days retention`); + + // Calculate cutoff date + const cutoffDate = new Date(); + cutoffDate.setDate(cutoffDate.getDate() - retentionDays); + + let eventsDeleted = 0; + let mirrorJobsDeleted = 0; + + // Clean up old events + const eventsResult = await db + .delete(events) + .where( + and( + eq(events.userId, userId), + lt(events.createdAt, cutoffDate) + ) + ); + eventsDeleted = eventsResult.changes || 0; + + // Clean up old mirror jobs (only completed ones) + const jobsResult = await db + .delete(mirrorJobs) + .where( + and( + eq(mirrorJobs.userId, userId), + eq(mirrorJobs.inProgress, false), + lt(mirrorJobs.timestamp, cutoffDate) + ) + ); + mirrorJobsDeleted = jobsResult.changes || 0; + + console.log(`Cleanup completed for user ${userId}: ${eventsDeleted} events, ${mirrorJobsDeleted} jobs deleted`); + + return { + userId, + eventsDeleted, + mirrorJobsDeleted, + }; + } catch (error) { + console.error(`Error during cleanup for user ${userId}:`, error); + return { + userId, + eventsDeleted: 0, + mirrorJobsDeleted: 0, + error: error instanceof Error ? error.message : 'Unknown error', + }; + } +} + +/** + * Update the cleanup configuration with last run time and calculate next run + */ +async function updateCleanupConfig(userId: string, cleanupConfig: any) { + try { + const now = new Date(); + const nextRun = new Date(now.getTime() + 24 * 60 * 60 * 1000); // Next day + + const updatedConfig = { + ...cleanupConfig, + lastRun: now, + nextRun: nextRun, + }; + + await db + .update(configs) + .set({ + cleanupConfig: updatedConfig, + updatedAt: now, + }) + .where(eq(configs.userId, userId)); + + console.log(`Updated cleanup config for user ${userId}, next run: ${nextRun.toISOString()}`); + } catch (error) { + console.error(`Error updating cleanup config for user ${userId}:`, error); + } +} + +/** + * Run automatic cleanup for all users with cleanup enabled + */ +export async function runAutomaticCleanup(): Promise { + try { + console.log('Starting automatic cleanup service...'); + + // Get all users with cleanup enabled + const userConfigs = await db + .select() + .from(configs) + .where(eq(configs.isActive, true)); + + const results: CleanupResult[] = []; + const now = new Date(); + + for (const config of userConfigs) { + try { + const cleanupConfig = config.cleanupConfig; + + // Skip if cleanup is not enabled + if (!cleanupConfig?.enabled) { + continue; + } + + // Check if it's time to run cleanup + const nextRun = cleanupConfig.nextRun ? new Date(cleanupConfig.nextRun) : null; + + // If nextRun is null or in the past, run cleanup + if (!nextRun || now >= nextRun) { + const result = await cleanupForUser(config.userId, cleanupConfig.retentionDays || 7); + results.push(result); + + // Update the cleanup config with new run times + await updateCleanupConfig(config.userId, cleanupConfig); + } else { + console.log(`Skipping cleanup for user ${config.userId}, next run: ${nextRun.toISOString()}`); + } + } catch (error) { + console.error(`Error processing cleanup for user ${config.userId}:`, error); + results.push({ + userId: config.userId, + eventsDeleted: 0, + mirrorJobsDeleted: 0, + error: error instanceof Error ? error.message : 'Unknown error', + }); + } + } + + console.log(`Automatic cleanup completed. Processed ${results.length} users.`); + return results; + } catch (error) { + console.error('Error in automatic cleanup service:', error); + return []; + } +} + +/** + * Start the cleanup service with periodic execution + * This should be called when the application starts + */ +export function startCleanupService() { + console.log('Starting background cleanup service...'); + + // Run cleanup every hour + const CLEANUP_INTERVAL = 60 * 60 * 1000; // 1 hour in milliseconds + + // Run initial cleanup after 5 minutes to allow app to fully start + setTimeout(() => { + runAutomaticCleanup().catch(error => { + console.error('Error in initial cleanup run:', error); + }); + }, 5 * 60 * 1000); // 5 minutes + + // Set up periodic cleanup + setInterval(() => { + runAutomaticCleanup().catch(error => { + console.error('Error in periodic cleanup run:', error); + }); + }, CLEANUP_INTERVAL); + + console.log(`✅ Cleanup service started. Will run every ${CLEANUP_INTERVAL / 1000 / 60} minutes.`); +} + +/** + * Stop the cleanup service (for testing or shutdown) + */ +export function stopCleanupService() { + // Note: In a real implementation, you'd want to track the interval ID + // and clear it here. For now, this is a placeholder. + console.log('Cleanup service stop requested (not implemented)'); +} diff --git a/src/lib/db/index.ts b/src/lib/db/index.ts index d5929dc..c6f9380 100644 --- a/src/lib/db/index.ts +++ b/src/lib/db/index.ts @@ -81,6 +81,7 @@ export const events = sqliteTable("events", { const githubSchema = configSchema.shape.githubConfig; const giteaSchema = configSchema.shape.giteaConfig; const scheduleSchema = configSchema.shape.scheduleConfig; +const cleanupSchema = configSchema.shape.cleanupConfig; export const configs = sqliteTable("configs", { id: text("id").primaryKey(), @@ -112,6 +113,10 @@ export const configs = sqliteTable("configs", { .$type>() .notNull(), + cleanupConfig: text("cleanup_config", { mode: "json" }) + .$type>() + .notNull(), + createdAt: integer("created_at", { mode: "timestamp" }) .notNull() .default(new Date()), diff --git a/src/lib/db/schema.ts b/src/lib/db/schema.ts index 56b0fe7..8759046 100644 --- a/src/lib/db/schema.ts +++ b/src/lib/db/schema.ts @@ -52,6 +52,12 @@ export const configSchema = z.object({ lastRun: z.date().optional(), nextRun: z.date().optional(), }), + cleanupConfig: z.object({ + enabled: z.boolean().default(false), + retentionDays: z.number().min(1).default(7), // in days + lastRun: z.date().optional(), + nextRun: z.date().optional(), + }), createdAt: z.date().default(() => new Date()), updatedAt: z.date().default(() => new Date()), }); diff --git a/src/middleware.ts b/src/middleware.ts index f3f54e1..2b76cbe 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -1,9 +1,11 @@ import { defineMiddleware } from 'astro:middleware'; import { initializeRecovery, hasJobsNeedingRecovery, getRecoveryStatus } from './lib/recovery'; +import { startCleanupService } from './lib/cleanup-service'; // Flag to track if recovery has been initialized let recoveryInitialized = false; let recoveryAttempted = false; +let cleanupServiceStarted = false; export const onRequest = defineMiddleware(async (context, next) => { // Initialize recovery system only once when the server starts @@ -53,6 +55,18 @@ export const onRequest = defineMiddleware(async (context, next) => { } } + // Start cleanup service only once after recovery is complete + if (recoveryInitialized && !cleanupServiceStarted) { + try { + console.log('Starting automatic database cleanup service...'); + startCleanupService(); + cleanupServiceStarted = true; + } catch (error) { + console.error('Failed to start cleanup service:', error); + // Don't fail the request if cleanup service fails to start + } + } + // Continue with the request return next(); }); diff --git a/src/pages/api/cleanup/auto.ts b/src/pages/api/cleanup/auto.ts new file mode 100644 index 0000000..73e9f28 --- /dev/null +++ b/src/pages/api/cleanup/auto.ts @@ -0,0 +1,72 @@ +/** + * API endpoint to manually trigger automatic cleanup + * This is useful for testing and debugging the cleanup service + */ + +import type { APIRoute } from 'astro'; +import { runAutomaticCleanup } from '@/lib/cleanup-service'; + +export const POST: APIRoute = async ({ request }) => { + try { + console.log('Manual cleanup trigger requested'); + + // Run the automatic cleanup + const results = await runAutomaticCleanup(); + + // Calculate totals + const totalEventsDeleted = results.reduce((sum, result) => sum + result.eventsDeleted, 0); + const totalJobsDeleted = results.reduce((sum, result) => sum + result.mirrorJobsDeleted, 0); + const errors = results.filter(result => result.error); + + return new Response( + JSON.stringify({ + success: true, + message: 'Automatic cleanup completed', + results: { + usersProcessed: results.length, + totalEventsDeleted, + totalJobsDeleted, + errors: errors.length, + details: results, + }, + }), + { + status: 200, + headers: { + 'Content-Type': 'application/json', + }, + } + ); + } catch (error) { + console.error('Error in manual cleanup trigger:', error); + + return new Response( + JSON.stringify({ + success: false, + message: 'Failed to run automatic cleanup', + error: error instanceof Error ? error.message : 'Unknown error', + }), + { + status: 500, + headers: { + 'Content-Type': 'application/json', + }, + } + ); + } +}; + +export const GET: APIRoute = async () => { + return new Response( + JSON.stringify({ + success: false, + message: 'Use POST method to trigger cleanup', + }), + { + status: 405, + headers: { + 'Content-Type': 'application/json', + }, + } + ); +}; diff --git a/src/pages/api/config/index.ts b/src/pages/api/config/index.ts index 3c4b231..3b245fd 100644 --- a/src/pages/api/config/index.ts +++ b/src/pages/api/config/index.ts @@ -6,14 +6,14 @@ import { eq } from "drizzle-orm"; export const POST: APIRoute = async ({ request }) => { try { const body = await request.json(); - const { userId, githubConfig, giteaConfig, scheduleConfig } = body; + const { userId, githubConfig, giteaConfig, scheduleConfig, cleanupConfig } = body; - if (!userId || !githubConfig || !giteaConfig || !scheduleConfig) { + if (!userId || !githubConfig || !giteaConfig || !scheduleConfig || !cleanupConfig) { return new Response( JSON.stringify({ success: false, message: - "userId, githubConfig, giteaConfig, and scheduleConfig are required.", + "userId, githubConfig, giteaConfig, scheduleConfig, and cleanupConfig are required.", }), { status: 400, @@ -64,6 +64,7 @@ export const POST: APIRoute = async ({ request }) => { githubConfig, giteaConfig, scheduleConfig, + cleanupConfig, updatedAt: new Date(), }) .where(eq(configs.id, existingConfig.id)); @@ -113,6 +114,7 @@ export const POST: APIRoute = async ({ request }) => { include: [], exclude: [], scheduleConfig, + cleanupConfig, createdAt: new Date(), updatedAt: new Date(), }); @@ -197,6 +199,12 @@ export const GET: APIRoute = async ({ request }) => { lastRun: null, nextRun: null, }, + cleanupConfig: { + enabled: false, + retentionDays: 7, + lastRun: null, + nextRun: null, + }, }), { status: 200, diff --git a/src/types/config.ts b/src/types/config.ts index 8d35247..4bca4da 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -18,6 +18,13 @@ export interface ScheduleConfig { nextRun?: Date; } +export interface DatabaseCleanupConfig { + enabled: boolean; + retentionDays: number; + lastRun?: Date; + nextRun?: Date; +} + export interface GitHubConfig { username: string; token: string; @@ -34,6 +41,7 @@ export interface SaveConfigApiRequest { githubConfig: GitHubConfig; giteaConfig: GiteaConfig; scheduleConfig: ScheduleConfig; + cleanupConfig: DatabaseCleanupConfig; } export interface SaveConfigApiResponse { @@ -55,6 +63,7 @@ export interface ConfigApiResponse { githubConfig: GitHubConfig; giteaConfig: GiteaConfig; scheduleConfig: ScheduleConfig; + cleanupConfig: DatabaseCleanupConfig; include: string[]; exclude: string[]; createdAt: Date;