diff --git a/src/components/dashboard/RepositoryList.tsx b/src/components/dashboard/RepositoryList.tsx index 668f878..d2fc9ea 100644 --- a/src/components/dashboard/RepositoryList.tsx +++ b/src/components/dashboard/RepositoryList.tsx @@ -81,6 +81,11 @@ export function RepositoryList({ repositories }: RepositoryListProps) { Private )} + {repo.isForked && ( + + Fork + + )}
diff --git a/src/components/organizations/OrganizationsList.tsx b/src/components/organizations/OrganizationsList.tsx index 841b890..8b79156 100644 --- a/src/components/organizations/OrganizationsList.tsx +++ b/src/components/organizations/OrganizationsList.tsx @@ -118,10 +118,38 @@ export function OrganizationList({
-

- {org.repositoryCount}{" "} - {org.repositoryCount === 1 ? "repository" : "repositories"} -

+
+
+ + {org.repositoryCount}{" "} + {org.repositoryCount === 1 ? "repository" : "repositories"} + +
+ {(org.publicRepositoryCount !== undefined || + org.privateRepositoryCount !== undefined || + org.forkRepositoryCount !== undefined) && ( +
+ {org.publicRepositoryCount !== undefined && ( + +
+ {org.publicRepositoryCount} public + + )} + {org.privateRepositoryCount !== undefined && org.privateRepositoryCount > 0 && ( + +
+ {org.privateRepositoryCount} private + + )} + {org.forkRepositoryCount !== undefined && org.forkRepositoryCount > 0 && ( + +
+ {org.forkRepositoryCount} fork{org.forkRepositoryCount !== 1 ? 's' : ''} + + )} +
+ )} +
diff --git a/src/components/repositories/RepositoryTable.tsx b/src/components/repositories/RepositoryTable.tsx index 7fde87a..ca7aeec 100644 --- a/src/components/repositories/RepositoryTable.tsx +++ b/src/components/repositories/RepositoryTable.tsx @@ -249,6 +249,11 @@ export default function RepositoryTable({ Private )} + {repo.isForked && ( + + Fork + + )}
{/* Owner */} diff --git a/src/lib/db/schema.ts b/src/lib/db/schema.ts index cfc60e8..87eb622 100644 --- a/src/lib/db/schema.ts +++ b/src/lib/db/schema.ts @@ -152,6 +152,9 @@ export const organizationSchema = z.object({ errorMessage: z.string().optional(), repositoryCount: z.number().default(0), + publicRepositoryCount: z.number().optional(), + privateRepositoryCount: z.number().optional(), + forkRepositoryCount: z.number().optional(), createdAt: z.date().default(() => new Date()), updatedAt: z.date().default(() => new Date()), diff --git a/src/pages/api/github/organizations.ts b/src/pages/api/github/organizations.ts index 2bbe198..dbe42a4 100644 --- a/src/pages/api/github/organizations.ts +++ b/src/pages/api/github/organizations.ts @@ -1,7 +1,7 @@ import type { APIRoute } from "astro"; import { db } from "@/lib/db"; -import { organizations } from "@/lib/db"; -import { eq, sql } from "drizzle-orm"; +import { organizations, repositories, configs } from "@/lib/db"; +import { eq, sql, and, count } from "drizzle-orm"; import { membershipRoleEnum, type OrganizationsApiResponse, @@ -25,24 +25,114 @@ export const GET: APIRoute = async ({ request }) => { } try { + // Fetch the user's active configuration to respect filtering settings + const [config] = await db + .select() + .from(configs) + .where(and(eq(configs.userId, userId), eq(configs.isActive, true))); + + if (!config) { + return jsonResponse({ + data: { + success: false, + error: "No active configuration found for this user", + }, + status: 404, + }); + } + + const githubConfig = config.githubConfig as { + mirrorStarred: boolean; + skipForks: boolean; + privateRepositories: boolean; + }; + const rawOrgs = await db .select() .from(organizations) .where(eq(organizations.userId, userId)) .orderBy(sql`name COLLATE NOCASE`); - const orgsWithIds: Organization[] = rawOrgs.map((org) => ({ - ...org, - status: repoStatusEnum.parse(org.status), - membershipRole: membershipRoleEnum.parse(org.membershipRole), - lastMirrored: org.lastMirrored ?? undefined, - errorMessage: org.errorMessage ?? undefined, - })); + // Calculate repository breakdowns for each organization + const orgsWithBreakdown = await Promise.all( + rawOrgs.map(async (org) => { + // Build base conditions for this organization (without private/fork filters) + const baseConditions = [ + eq(repositories.userId, userId), + eq(repositories.organization, org.name) + ]; + + if (!githubConfig.mirrorStarred) { + 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)); + } + + 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)); + } + + const [publicCount] = await db + .select({ count: count() }) + .from(repositories) + .where(and(...publicConditions)); + + // Get private count (only if private repos are enabled in config) + const [privateCount] = githubConfig.privateRepositories ? await db + .select({ count: count() }) + .from(repositories) + .where( + and( + ...baseConditions, + eq(repositories.isPrivate, true), + ...(githubConfig.skipForks ? [eq(repositories.isForked, false)] : []) + ) + ) : [{ count: 0 }]; + + // Get fork count (only if forks are enabled in config) + const [forkCount] = !githubConfig.skipForks ? await db + .select({ count: count() }) + .from(repositories) + .where( + and( + ...baseConditions, + eq(repositories.isForked, true), + ...(!githubConfig.privateRepositories ? [eq(repositories.isPrivate, false)] : []) + ) + ) : [{ count: 0 }]; + + return { + ...org, + status: repoStatusEnum.parse(org.status), + membershipRole: membershipRoleEnum.parse(org.membershipRole), + lastMirrored: org.lastMirrored ?? undefined, + errorMessage: org.errorMessage ?? undefined, + repositoryCount: totalCount.count, + publicRepositoryCount: publicCount.count, + privateRepositoryCount: privateCount.count, + forkRepositoryCount: forkCount.count, + }; + }) + ); const resPayload: OrganizationsApiResponse = { success: true, message: "Organizations fetched successfully", - organizations: orgsWithIds, + organizations: orgsWithBreakdown, }; return jsonResponse({ data: resPayload, status: 200 }); diff --git a/src/types/organizations.ts b/src/types/organizations.ts index c829870..4b0927e 100644 --- a/src/types/organizations.ts +++ b/src/types/organizations.ts @@ -33,6 +33,9 @@ export interface GitOrg { isIncluded: boolean; status: RepoStatus; repositoryCount: number; + publicRepositoryCount?: number; + privateRepositoryCount?: number; + forkRepositoryCount?: number; createdAt: Date; updatedAt: Date; }