/** * Maps between UI config structure and database schema structure */ import type { GitHubConfig, GiteaConfig, MirrorOptions, AdvancedOptions, SaveConfigApiRequest } from "@/types/config"; import { z } from "zod"; import { githubConfigSchema, giteaConfigSchema, scheduleConfigSchema, cleanupConfigSchema } from "@/lib/db/schema"; import { parseInterval } from "@/lib/utils/duration-parser"; // Use the actual database schema types type DbGitHubConfig = z.infer; type DbGiteaConfig = z.infer; type DbScheduleConfig = z.infer; type DbCleanupConfig = z.infer; /** * Maps UI config structure to database schema structure */ export function mapUiToDbConfig( githubConfig: GitHubConfig, giteaConfig: GiteaConfig, mirrorOptions: MirrorOptions, advancedOptions: AdvancedOptions ): { githubConfig: DbGitHubConfig; giteaConfig: DbGiteaConfig } { // Map GitHub config to match database schema fields const dbGithubConfig: DbGitHubConfig = { // Map username to owner field owner: githubConfig.username, type: "personal", // Default to personal, could be made configurable token: githubConfig.token || "", // Map checkbox fields with proper names includeStarred: githubConfig.mirrorStarred, includePrivate: githubConfig.privateRepositories, includeForks: !advancedOptions.skipForks, // Note: UI has skipForks, DB has includeForks skipForks: advancedOptions.skipForks, // Add skipForks field includeArchived: false, // Not in UI yet, default to false includePublic: true, // Not in UI yet, default to true // Organization related fields includeOrganizations: [], // Not in UI yet // Starred repos organization starredReposOrg: giteaConfig.starredReposOrg, // Mirror strategy mirrorStrategy: giteaConfig.mirrorStrategy || "preserve", defaultOrg: giteaConfig.organization, // Advanced options starredCodeOnly: advancedOptions.starredCodeOnly, }; // Map Gitea config to match database schema const dbGiteaConfig: DbGiteaConfig = { url: giteaConfig.url, token: giteaConfig.token, defaultOwner: giteaConfig.username, // Map username to defaultOwner organization: giteaConfig.organization, // Add organization field preserveOrgStructure: giteaConfig.mirrorStrategy === "preserve" || giteaConfig.mirrorStrategy === "mixed", // Add preserveOrgStructure field // Mirror interval and options mirrorInterval: "8h", // Default value, could be made configurable lfs: mirrorOptions.mirrorLFS || false, // LFS mirroring option wiki: mirrorOptions.mirrorMetadata && mirrorOptions.metadataComponents.wiki, // Visibility settings visibility: giteaConfig.visibility || "default", preserveVisibility: false, // This should be a separate field, not the same as preserveOrgStructure // Organization creation createOrg: true, // Default to true // Template settings (not in UI yet) templateOwner: undefined, templateRepo: undefined, // Topics addTopics: true, // Default to true topicPrefix: undefined, // Fork strategy forkStrategy: advancedOptions.skipForks ? "skip" : "reference", // Mirror options from UI issueConcurrency: giteaConfig.issueConcurrency ?? 3, pullRequestConcurrency: giteaConfig.pullRequestConcurrency ?? 5, mirrorReleases: mirrorOptions.mirrorReleases, releaseLimit: mirrorOptions.releaseLimit || 10, mirrorMetadata: mirrorOptions.mirrorMetadata, mirrorIssues: mirrorOptions.mirrorMetadata && mirrorOptions.metadataComponents.issues, mirrorPullRequests: mirrorOptions.mirrorMetadata && mirrorOptions.metadataComponents.pullRequests, mirrorLabels: mirrorOptions.mirrorMetadata && mirrorOptions.metadataComponents.labels, mirrorMilestones: mirrorOptions.mirrorMetadata && mirrorOptions.metadataComponents.milestones, }; return { githubConfig: dbGithubConfig, giteaConfig: dbGiteaConfig, }; } /** * Maps database schema structure to UI config structure */ export function mapDbToUiConfig(dbConfig: any): { githubConfig: GitHubConfig; giteaConfig: GiteaConfig; mirrorOptions: MirrorOptions; advancedOptions: AdvancedOptions; } { // Map from database GitHub config to UI fields const githubConfig: GitHubConfig = { username: dbConfig.githubConfig?.owner || "", // Map owner to username token: dbConfig.githubConfig?.token || "", privateRepositories: dbConfig.githubConfig?.includePrivate || false, // Map includePrivate to privateRepositories mirrorStarred: dbConfig.githubConfig?.includeStarred || false, // Map includeStarred to mirrorStarred }; // Map from database Gitea config to UI fields const giteaConfig: GiteaConfig = { url: dbConfig.giteaConfig?.url || "", username: dbConfig.giteaConfig?.defaultOwner || "", // Map defaultOwner to username token: dbConfig.giteaConfig?.token || "", organization: dbConfig.githubConfig?.defaultOrg || "github-mirrors", // Get from GitHub config visibility: dbConfig.giteaConfig?.visibility === "default" ? "public" : dbConfig.giteaConfig?.visibility || "public", starredReposOrg: dbConfig.githubConfig?.starredReposOrg || "starred", // Get from GitHub config preserveOrgStructure: dbConfig.giteaConfig?.preserveVisibility || false, // Map preserveVisibility mirrorStrategy: dbConfig.githubConfig?.mirrorStrategy || "preserve", // Get from GitHub config personalReposOrg: undefined, // Not stored in current schema issueConcurrency: dbConfig.giteaConfig?.issueConcurrency ?? 3, pullRequestConcurrency: dbConfig.giteaConfig?.pullRequestConcurrency ?? 5, }; // Map mirror options from various database fields const mirrorOptions: MirrorOptions = { mirrorReleases: dbConfig.giteaConfig?.mirrorReleases || false, releaseLimit: dbConfig.giteaConfig?.releaseLimit || 10, mirrorLFS: dbConfig.giteaConfig?.lfs || false, mirrorMetadata: dbConfig.giteaConfig?.mirrorMetadata || false, metadataComponents: { issues: dbConfig.giteaConfig?.mirrorIssues || false, pullRequests: dbConfig.giteaConfig?.mirrorPullRequests || false, labels: dbConfig.giteaConfig?.mirrorLabels || false, milestones: dbConfig.giteaConfig?.mirrorMilestones || false, wiki: dbConfig.giteaConfig?.wiki || false, }, }; // Map advanced options const advancedOptions: AdvancedOptions = { skipForks: !(dbConfig.githubConfig?.includeForks ?? true), // Invert includeForks to get skipForks // Support both old (skipStarredIssues) and new (starredCodeOnly) field names for backward compatibility starredCodeOnly: dbConfig.githubConfig?.starredCodeOnly ?? (dbConfig.githubConfig as any)?.skipStarredIssues ?? false, }; return { githubConfig, giteaConfig, mirrorOptions, advancedOptions, }; } /** * Maps UI schedule config to database schema */ export function mapUiScheduleToDb(uiSchedule: any, existing?: DbScheduleConfig): DbScheduleConfig { // Preserve existing schedule config and only update fields controlled by the UI const base: DbScheduleConfig = existing ? { ...(existing as unknown as DbScheduleConfig) } : (scheduleConfigSchema.parse({}) as unknown as DbScheduleConfig); // Store interval as seconds string to avoid lossy cron conversion const intervalSeconds = typeof uiSchedule.interval === 'number' && uiSchedule.interval > 0 ? String(uiSchedule.interval) : (typeof base.interval === 'string' ? base.interval : String(86400)); return { ...base, enabled: !!uiSchedule.enabled, interval: intervalSeconds, } as DbScheduleConfig; } /** * Maps database schedule config to UI format */ export function mapDbScheduleToUi(dbSchedule: DbScheduleConfig): any { // Handle null/undefined schedule config if (!dbSchedule) { return { enabled: false, interval: 86400, // Default to daily (24 hours) lastRun: null, nextRun: null, }; } // Parse interval supporting numbers (seconds), duration strings, and cron let intervalSeconds = 86400; // Default to daily (24 hours) try { const ms = parseInterval( typeof dbSchedule.interval === 'number' ? dbSchedule.interval : (dbSchedule.interval as unknown as string) ); intervalSeconds = Math.max(1, Math.floor(ms / 1000)); } catch (_e) { // Fallback to default if unparsable intervalSeconds = 86400; } return { enabled: dbSchedule.enabled || false, interval: intervalSeconds, lastRun: dbSchedule.lastRun || null, nextRun: dbSchedule.nextRun || null, }; } /** * Maps UI cleanup config to database schema */ export function mapUiCleanupToDb(uiCleanup: any): DbCleanupConfig { const parsedRetention = typeof uiCleanup.retentionDays === "string" ? parseInt(uiCleanup.retentionDays, 10) : uiCleanup.retentionDays; const retentionSeconds = Number.isFinite(parsedRetention) ? parsedRetention : 604800; return { enabled: Boolean(uiCleanup.enabled), retentionDays: retentionSeconds, deleteFromGitea: uiCleanup.deleteFromGitea ?? false, deleteIfNotInGitHub: uiCleanup.deleteIfNotInGitHub ?? true, protectedRepos: uiCleanup.protectedRepos ?? [], dryRun: uiCleanup.dryRun ?? false, orphanedRepoAction: (uiCleanup.orphanedRepoAction as DbCleanupConfig["orphanedRepoAction"]) || "archive", batchSize: uiCleanup.batchSize ?? 10, pauseBetweenDeletes: uiCleanup.pauseBetweenDeletes ?? 2000, lastRun: uiCleanup.lastRun ?? null, nextRun: uiCleanup.nextRun ?? null, }; } /** * Maps database cleanup config to UI format */ export function mapDbCleanupToUi(dbCleanup: DbCleanupConfig): any { // Handle null/undefined cleanup config if (!dbCleanup) { return { enabled: false, retentionDays: 604800, // Default to 7 days in seconds lastRun: null, nextRun: null, }; } return { enabled: dbCleanup.enabled ?? false, retentionDays: dbCleanup.retentionDays ?? 604800, deleteFromGitea: dbCleanup.deleteFromGitea ?? false, deleteIfNotInGitHub: dbCleanup.deleteIfNotInGitHub ?? true, protectedRepos: dbCleanup.protectedRepos ?? [], dryRun: dbCleanup.dryRun ?? false, orphanedRepoAction: dbCleanup.orphanedRepoAction ?? "archive", batchSize: dbCleanup.batchSize ?? 10, pauseBetweenDeletes: dbCleanup.pauseBetweenDeletes ?? 2000, lastRun: dbCleanup.lastRun ?? null, nextRun: dbCleanup.nextRun ?? null, }; }