Files
gitea-mirror/src/pages/api/github/organizations.ts

143 lines
4.5 KiB
TypeScript

import type { APIRoute } from "astro";
import { db } from "@/lib/db";
import { organizations, repositories, configs } from "@/lib/db";
import { eq, sql, and, count } from "drizzle-orm";
import {
membershipRoleEnum,
type OrganizationsApiResponse,
} from "@/types/organizations";
import type { Organization } from "@/lib/db/schema";
import { repoStatusEnum } from "@/types/Repository";
import { jsonResponse, createSecureErrorResponse } from "@/lib/utils";
export const GET: APIRoute = async ({ request }) => {
const url = new URL(request.url);
const userId = url.searchParams.get("userId");
if (!userId) {
return jsonResponse({
data: {
success: false,
error: "Missing userId",
},
status: 400,
});
}
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`);
// 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: orgsWithBreakdown,
};
return jsonResponse({ data: resPayload, status: 200 });
} catch (error) {
return createSecureErrorResponse(error, "organizations fetch", 500);
}
};