From e9c12bb9ffd408daa98dcc6ff118466b247ca0f4 Mon Sep 17 00:00:00 2001 From: Arunavo Ray Date: Sun, 15 Jun 2025 13:31:06 +0530 Subject: [PATCH] feat: enhance API config handling by adding mapping functions for UI and database structures --- src/lib/utils/config-mapper.ts | 134 +++++++++++++++++++++++++++++++++ src/pages/api/config/index.ts | 89 ++++++++++++++-------- 2 files changed, 190 insertions(+), 33 deletions(-) create mode 100644 src/lib/utils/config-mapper.ts diff --git a/src/lib/utils/config-mapper.ts b/src/lib/utils/config-mapper.ts new file mode 100644 index 0000000..7cd644f --- /dev/null +++ b/src/lib/utils/config-mapper.ts @@ -0,0 +1,134 @@ +/** + * Maps between UI config structure and database schema structure + */ + +import type { + GitHubConfig, + GiteaConfig, + MirrorOptions, + AdvancedOptions, + SaveConfigApiRequest +} from "@/types/config"; + +interface DbGitHubConfig { + username: string; + token?: string; + skipForks: boolean; + privateRepositories: boolean; + mirrorIssues: boolean; + mirrorWiki: boolean; + mirrorStarred: boolean; + useSpecificUser: boolean; + singleRepo?: string; + includeOrgs: string[]; + excludeOrgs: string[]; + mirrorPublicOrgs: boolean; + publicOrgs: string[]; + skipStarredIssues: boolean; +} + +interface DbGiteaConfig { + username: string; + url: string; + token: string; + organization?: string; + visibility: "public" | "private" | "limited"; + starredReposOrg: string; + preserveOrgStructure: boolean; + mirrorStrategy?: "preserve" | "single-org" | "flat-user"; +} + +/** + * 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 with fields from mirrorOptions and advancedOptions + const dbGithubConfig: DbGitHubConfig = { + username: githubConfig.username, + token: githubConfig.token, + privateRepositories: githubConfig.privateRepositories, + mirrorStarred: githubConfig.mirrorStarred, + + // From mirrorOptions + mirrorIssues: mirrorOptions.mirrorMetadata && mirrorOptions.metadataComponents.issues, + mirrorWiki: mirrorOptions.mirrorMetadata && mirrorOptions.metadataComponents.wiki, + + // From advancedOptions + skipForks: advancedOptions.skipForks, + skipStarredIssues: advancedOptions.skipStarredIssues, + + // Default values for fields not in UI + useSpecificUser: false, + includeOrgs: [], + excludeOrgs: [], + mirrorPublicOrgs: false, + publicOrgs: [], + }; + + // Gitea config remains mostly the same + const dbGiteaConfig: DbGiteaConfig = { + ...giteaConfig, + }; + + 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; +} { + const githubConfig: GitHubConfig = { + username: dbConfig.githubConfig?.username || "", + token: dbConfig.githubConfig?.token || "", + privateRepositories: dbConfig.githubConfig?.privateRepositories || false, + mirrorStarred: dbConfig.githubConfig?.mirrorStarred || false, + }; + + const giteaConfig: GiteaConfig = { + url: dbConfig.giteaConfig?.url || "", + username: dbConfig.giteaConfig?.username || "", + token: dbConfig.giteaConfig?.token || "", + organization: dbConfig.giteaConfig?.organization || "github-mirrors", + visibility: dbConfig.giteaConfig?.visibility || "public", + starredReposOrg: dbConfig.giteaConfig?.starredReposOrg || "github", + preserveOrgStructure: dbConfig.giteaConfig?.preserveOrgStructure || false, + mirrorStrategy: dbConfig.giteaConfig?.mirrorStrategy, + }; + + const mirrorOptions: MirrorOptions = { + mirrorReleases: false, // Not stored in DB yet + mirrorMetadata: dbConfig.githubConfig?.mirrorIssues || dbConfig.githubConfig?.mirrorWiki || false, + metadataComponents: { + issues: dbConfig.githubConfig?.mirrorIssues || false, + pullRequests: false, // Not stored in DB yet + labels: false, // Not stored in DB yet + milestones: false, // Not stored in DB yet + wiki: dbConfig.githubConfig?.mirrorWiki || false, + }, + }; + + const advancedOptions: AdvancedOptions = { + skipForks: dbConfig.githubConfig?.skipForks || false, + skipStarredIssues: dbConfig.githubConfig?.skipStarredIssues || false, + }; + + return { + githubConfig, + giteaConfig, + mirrorOptions, + advancedOptions, + }; +} \ No newline at end of file diff --git a/src/pages/api/config/index.ts b/src/pages/api/config/index.ts index 205641f..a7e7590 100644 --- a/src/pages/api/config/index.ts +++ b/src/pages/api/config/index.ts @@ -4,18 +4,19 @@ import { v4 as uuidv4 } from "uuid"; import { eq } from "drizzle-orm"; import { calculateCleanupInterval } from "@/lib/cleanup-service"; import { createSecureErrorResponse } from "@/lib/utils"; +import { mapUiToDbConfig, mapDbToUiConfig } from "@/lib/utils/config-mapper"; export const POST: APIRoute = async ({ request }) => { try { const body = await request.json(); - const { userId, githubConfig, giteaConfig, scheduleConfig, cleanupConfig } = body; + const { userId, githubConfig, giteaConfig, scheduleConfig, cleanupConfig, mirrorOptions, advancedOptions } = body; - if (!userId || !githubConfig || !giteaConfig || !scheduleConfig || !cleanupConfig) { + if (!userId || !githubConfig || !giteaConfig || !scheduleConfig || !cleanupConfig || !mirrorOptions || !advancedOptions) { return new Response( JSON.stringify({ success: false, message: - "userId, githubConfig, giteaConfig, scheduleConfig, and cleanupConfig are required.", + "userId, githubConfig, giteaConfig, scheduleConfig, cleanupConfig, mirrorOptions, and advancedOptions are required.", }), { status: 400, @@ -33,6 +34,14 @@ export const POST: APIRoute = async ({ request }) => { const existingConfig = existingConfigResult[0]; + // Map UI structure to database schema structure first + const { githubConfig: mappedGithubConfig, giteaConfig: mappedGiteaConfig } = mapUiToDbConfig( + githubConfig, + giteaConfig, + mirrorOptions, + advancedOptions + ); + // Preserve tokens if fields are empty if (existingConfig) { try { @@ -46,12 +55,12 @@ export const POST: APIRoute = async ({ request }) => { ? JSON.parse(existingConfig.giteaConfig) : existingConfig.giteaConfig; - if (!githubConfig.token && existingGithub.token) { - githubConfig.token = existingGithub.token; + if (!mappedGithubConfig.token && existingGithub.token) { + mappedGithubConfig.token = existingGithub.token; } - if (!giteaConfig.token && existingGitea.token) { - giteaConfig.token = existingGitea.token; + if (!mappedGiteaConfig.token && existingGitea.token) { + mappedGiteaConfig.token = existingGitea.token; } } catch (tokenError) { console.error("Failed to preserve tokens:", tokenError); @@ -120,8 +129,8 @@ export const POST: APIRoute = async ({ request }) => { await db .update(configs) .set({ - githubConfig, - giteaConfig, + githubConfig: mappedGithubConfig, + giteaConfig: mappedGiteaConfig, scheduleConfig: processedScheduleConfig, cleanupConfig: processedCleanupConfig, updatedAt: new Date(), @@ -168,8 +177,8 @@ export const POST: APIRoute = async ({ request }) => { userId, name: "Default Configuration", isActive: true, - githubConfig, - giteaConfig, + githubConfig: mappedGithubConfig, + giteaConfig: mappedGiteaConfig, include: [], exclude: [], scheduleConfig: processedScheduleConfig, @@ -214,33 +223,40 @@ export const GET: APIRoute = async ({ request }) => { .limit(1); if (config.length === 0) { - // Return a default empty configuration instead of a 404 error + // Return a default empty configuration with UI structure + const defaultDbConfig = { + githubConfig: { + username: "", + token: "", + skipForks: false, + privateRepositories: false, + mirrorIssues: false, + mirrorWiki: false, + mirrorStarred: false, + useSpecificUser: false, + preserveOrgStructure: false, + skipStarredIssues: false, + }, + giteaConfig: { + url: "", + token: "", + username: "", + organization: "github-mirrors", + visibility: "public", + starredReposOrg: "github", + preserveOrgStructure: false, + }, + }; + + const uiConfig = mapDbToUiConfig(defaultDbConfig); + return new Response( JSON.stringify({ id: null, userId: userId, name: "Default Configuration", isActive: true, - githubConfig: { - username: "", - token: "", - skipForks: false, - privateRepositories: false, - mirrorIssues: false, - mirrorWiki: false, - mirrorStarred: true, - useSpecificUser: false, - preserveOrgStructure: true, - skipStarredIssues: false, - }, - giteaConfig: { - url: "", - token: "", - username: "", - organization: "github-mirrors", - visibility: "public", - starredReposOrg: "github", - }, + ...uiConfig, scheduleConfig: { enabled: false, interval: 3600, @@ -261,7 +277,14 @@ export const GET: APIRoute = async ({ request }) => { ); } - return new Response(JSON.stringify(config[0]), { + // Map database structure to UI structure + const dbConfig = config[0]; + const uiConfig = mapDbToUiConfig(dbConfig); + + return new Response(JSON.stringify({ + ...dbConfig, + ...uiConfig, + }), { status: 200, headers: { "Content-Type": "application/json" }, });