diff --git a/package.json b/package.json index 2e4ec47..3f285e1 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ }, "scripts": { "setup": "bun install && bun run manage-db init", - "dev": "bunx --bun astro dev --port 4567", + "dev": "bunx --bun astro dev --port 9876", "dev:clean": "bun run cleanup-db && bun run manage-db init && bunx --bun astro dev", "build": "bunx --bun astro build", "cleanup-db": "rm -f gitea-mirror.db data/gitea-mirror.db", diff --git a/src/lib/db/schema.ts b/src/lib/db/schema.ts index c061ce8..050462e 100644 --- a/src/lib/db/schema.ts +++ b/src/lib/db/schema.ts @@ -24,7 +24,7 @@ export const githubConfigSchema = z.object({ includePublic: z.boolean().default(true), includeOrganizations: z.array(z.string()).default([]), starredReposOrg: z.string().optional(), - mirrorStrategy: z.enum(["preserve", "single-org", "flat-user"]).default("preserve"), + mirrorStrategy: z.enum(["preserve", "single-org", "flat-user", "mixed"]).default("preserve"), defaultOrg: z.string().optional(), }); diff --git a/src/lib/gitea.test.ts b/src/lib/gitea.test.ts index f2dfae1..07e32d6 100644 --- a/src/lib/gitea.test.ts +++ b/src/lib/gitea.test.ts @@ -312,13 +312,13 @@ describe("getGiteaRepoOwner - Organization Override Tests", () => { skipStarredIssues: false }, giteaConfig: { - username: "giteauser", + defaultOwner: "giteauser", url: "https://gitea.example.com", token: "gitea-token", - organization: "github-mirrors", + defaultOrg: "github-mirrors", visibility: "public", starredReposOrg: "starred", - preserveOrgStructure: false, + preserveVisibility: false, mirrorStrategy: "preserve" } }; @@ -354,18 +354,7 @@ describe("getGiteaRepoOwner - Organization Override Tests", () => { expect(result).toBe("starred"); }); - test("preserve strategy: personal repos use personalReposOrg override", () => { - const configWithOverride = { - ...baseConfig, - giteaConfig: { - ...baseConfig.giteaConfig!, - personalReposOrg: "my-personal-mirrors" - } - }; - const repo = { ...baseRepo, organization: undefined }; - const result = getGiteaRepoOwner({ config: configWithOverride, repository: repo }); - expect(result).toBe("my-personal-mirrors"); - }); + // Removed test for personalReposOrg as this field no longer exists test("preserve strategy: personal repos fallback to username when no override", () => { const repo = { ...baseRepo, organization: undefined }; @@ -379,7 +368,7 @@ describe("getGiteaRepoOwner - Organization Override Tests", () => { expect(result).toBe("myorg"); }); - test("mixed strategy: personal repos go to organization", () => { + test("single-org strategy: personal repos go to defaultOrg", () => { const configWithMixed = { ...baseConfig, giteaConfig: { @@ -393,7 +382,7 @@ describe("getGiteaRepoOwner - Organization Override Tests", () => { expect(result).toBe("github-mirrors"); }); - test("mixed strategy: org repos preserve their structure", () => { + test("single-org strategy: org repos also go to defaultOrg", () => { const configWithMixed = { ...baseConfig, giteaConfig: { @@ -407,18 +396,16 @@ describe("getGiteaRepoOwner - Organization Override Tests", () => { expect(result).toBe("myorg"); }); - test("mixed strategy: fallback to username if no org configs", () => { - const configWithMixed = { + test("flat-user strategy: all repos go to defaultOwner", () => { + const configWithFlatUser = { ...baseConfig, giteaConfig: { ...baseConfig.giteaConfig!, - mirrorStrategy: "mixed" as const, - organization: undefined, - personalReposOrg: undefined + mirrorStrategy: "flat-user" as const } }; - const repo = { ...baseRepo, organization: undefined }; - const result = getGiteaRepoOwner({ config: configWithMixed, repository: repo }); + const repo = { ...baseRepo, organization: "myorg" }; + const result = getGiteaRepoOwner({ config: configWithFlatUser, repository: repo }); expect(result).toBe("giteauser"); }); }); diff --git a/src/lib/gitea.ts b/src/lib/gitea.ts index e2e5920..4e42fc0 100644 --- a/src/lib/gitea.ts +++ b/src/lib/gitea.ts @@ -64,7 +64,7 @@ export const getGiteaRepoOwnerAsync = async ({ throw new Error("GitHub or Gitea config is required."); } - if (!config.giteaConfig.username) { + if (!config.giteaConfig.defaultOwner) { throw new Error("Gitea username is required."); } @@ -96,11 +96,7 @@ export const getGiteaRepoOwnerAsync = async ({ } } - // Check for personal repos override (when it's user's repo, not an organization) - if (!repository.organization && config.giteaConfig.personalReposOrg) { - console.log(`Using personal repos override: ${config.giteaConfig.personalReposOrg}`); - return config.giteaConfig.personalReposOrg; - } + // For personal repos (not organization repos), fall back to the default strategy // Fall back to existing strategy logic return getGiteaRepoOwner({ config, repository }); @@ -117,7 +113,7 @@ export const getGiteaRepoOwner = ({ throw new Error("GitHub or Gitea config is required."); } - if (!config.giteaConfig.username) { + if (!config.giteaConfig.defaultOwner) { throw new Error("Gitea username is required."); } @@ -137,7 +133,7 @@ export const getGiteaRepoOwner = ({ return repository.organization; } // Use personal repos override if configured, otherwise use username - return config.giteaConfig.personalReposOrg || config.giteaConfig.username; + return config.giteaConfig.defaultOwner; case "single-org": // All non-starred repos go to the destination organization @@ -145,11 +141,11 @@ export const getGiteaRepoOwner = ({ return config.giteaConfig.organization; } // Fallback to username if no organization specified - return config.giteaConfig.username; + return config.giteaConfig.defaultOwner; case "flat-user": // All non-starred repos go under the user account - return config.giteaConfig.username; + return config.giteaConfig.defaultOwner; case "mixed": // Mixed mode: personal repos to single org, organization repos preserve structure @@ -162,11 +158,11 @@ export const getGiteaRepoOwner = ({ return config.giteaConfig.organization; } // Fallback to username if no organization specified - return config.giteaConfig.username; + return config.giteaConfig.defaultOwner; default: // Default fallback - return config.giteaConfig.username; + return config.giteaConfig.defaultOwner; } }; @@ -268,7 +264,7 @@ export const mirrorGithubRepoToGitea = async ({ throw new Error("github config and gitea config are required."); } - if (!config.giteaConfig.username) { + if (!config.giteaConfig.defaultOwner) { throw new Error("Gitea username is required."); } @@ -357,7 +353,7 @@ export const mirrorGithubRepoToGitea = async ({ const apiUrl = `${config.giteaConfig.url}/api/v1/repos/migrate`; // Handle organization creation if needed for single-org or preserve strategies - if (repoOwner !== config.giteaConfig.username && !repository.isStarred) { + if (repoOwner !== config.giteaConfig.defaultOwner && !repository.isStarred) { // Need to create the organization if it doesn't exist await getOrCreateGiteaOrg({ orgName: repoOwner, @@ -383,11 +379,13 @@ export const mirrorGithubRepoToGitea = async ({ ); //mirror releases - await mirrorGitHubReleasesToGitea({ - config, - octokit, - repository, - }); + if (config.githubConfig?.mirrorReleases) { + await mirrorGitHubReleasesToGitea({ + config, + octokit, + repository, + }); + } // clone issues // Skip issues for starred repos if skipStarredIssues is enabled @@ -738,11 +736,13 @@ export async function mirrorGitHubRepoToGiteaOrg({ ); //mirror releases - await mirrorGitHubReleasesToGitea({ - config, - octokit, - repository, - }); + if (config.githubConfig?.mirrorReleases) { + await mirrorGitHubReleasesToGitea({ + config, + octokit, + repository, + }); + } // Clone issues // Skip issues for starred repos if skipStarredIssues is enabled @@ -906,7 +906,7 @@ export async function mirrorGitHubOrgToGitea({ // Determine the target organization based on strategy if (mirrorStrategy === "single-org" && config.giteaConfig?.organization) { // For single-org strategy, use the configured destination organization - targetOrgName = config.giteaConfig.organization; + targetOrgName = config.giteaConfig.defaultOrg || config.giteaConfig.defaultOwner; giteaOrgId = await getOrCreateGiteaOrg({ orgId: organization.id, orgName: targetOrgName, @@ -925,7 +925,7 @@ export async function mirrorGitHubOrgToGitea({ // For flat-user strategy, we shouldn't create organizations at all // Skip organization creation and let individual repos be handled by getGiteaRepoOwner console.log(`Using flat-user strategy: repos will be placed under user account`); - targetOrgName = config.giteaConfig?.username || ""; + targetOrgName = config.giteaConfig?.defaultOwner || ""; } //query the db with the org name and get the repos @@ -1082,7 +1082,7 @@ export const syncGiteaRepo = async ({ !config.userId || !config.giteaConfig?.url || !config.giteaConfig?.token || - !config.giteaConfig?.username + !config.giteaConfig?.defaultOwner ) { throw new Error("Gitea config is required."); } @@ -1405,7 +1405,7 @@ export async function mirrorGitHubReleasesToGitea({ config: Partial; }) { if ( - !config.giteaConfig?.username || + !config.giteaConfig?.defaultOwner || !config.giteaConfig?.token || !config.giteaConfig?.url ) { diff --git a/src/pages/api/github/organizations.ts b/src/pages/api/github/organizations.ts index 15b458d..5310fd8 100644 --- a/src/pages/api/github/organizations.ts +++ b/src/pages/api/github/organizations.ts @@ -66,30 +66,17 @@ export const GET: APIRoute = async ({ request }) => { baseConditions.push(eq(repositories.isStarred, false)); } - // Get total count with all user config filters applied - const totalConditions = [...baseConditions]; - if (githubConfig.skipForks) { - totalConditions.push(eq(repositories.isForked, false)); - } - if (!githubConfig.privateRepositories) { - totalConditions.push(eq(repositories.isPrivate, false)); - } - + // Get actual total count (without user config filters) const [totalCount] = await db .select({ count: count() }) .from(repositories) - .where(and(...totalConditions)); - - // Get public count - const publicConditions = [...baseConditions, eq(repositories.isPrivate, false)]; - if (githubConfig.skipForks) { - publicConditions.push(eq(repositories.isForked, false)); - } + .where(and(...baseConditions)); + // Get public count (actual count, not filtered) const [publicCount] = await db .select({ count: count() }) .from(repositories) - .where(and(...publicConditions)); + .where(and(...baseConditions, eq(repositories.isPrivate, false))); // Get private count (always show actual count regardless of config) const [privateCount] = await db diff --git a/src/pages/api/job/mirror-repo.ts b/src/pages/api/job/mirror-repo.ts index 60bd3af..92e59f1 100644 --- a/src/pages/api/job/mirror-repo.ts +++ b/src/pages/api/job/mirror-repo.ts @@ -113,7 +113,7 @@ export const POST: APIRoute = async ({ request }) => { (config.githubConfig?.preserveOrgStructure ? "preserve" : "flat-user"); const shouldUseOrgMirror = - owner !== config.giteaConfig?.username || // Different owner means org + owner !== config.giteaConfig?.defaultOwner || // Different owner means org mirrorStrategy === "single-org" || // Single-org strategy always uses org repoData.isStarred; // Starred repos always go to org diff --git a/src/pages/api/job/retry-repo.ts b/src/pages/api/job/retry-repo.ts index 560295c..23506e2 100644 --- a/src/pages/api/job/retry-repo.ts +++ b/src/pages/api/job/retry-repo.ts @@ -147,7 +147,7 @@ export const POST: APIRoute = async ({ request }) => { (config.githubConfig?.preserveOrgStructure ? "preserve" : "flat-user"); const shouldUseOrgMirror = - owner !== config.giteaConfig?.username || // Different owner means org + owner !== config.giteaConfig?.defaultOwner || // Different owner means org mirrorStrategy === "single-org" || // Single-org strategy always uses org repoData.isStarred; // Starred repos always go to org