feat: enhance API config handling by adding mapping functions for UI and database structures

This commit is contained in:
Arunavo Ray
2025-06-15 13:31:06 +05:30
parent 42314ab0e3
commit e9c12bb9ff
2 changed files with 190 additions and 33 deletions

View File

@@ -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,
};
}

View File

@@ -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" },
});