mirror of
https://github.com/RayLabsHQ/gitea-mirror.git
synced 2026-10-12 10:21:51 +02:00
An admin who runs this alone wants to show a team what is being mirrored without handing out a login (#428). GET /api/repositories/export and GET /api/organizations/export return the tracked rows as a CSV file, and an Export CSV button on both pages downloads them. The file is the table without the internal fields: the ids, the metadata sync state and the mirror option overrides. Rows are scoped to the signed in user and ordered by full name and by name. No filters in this pass, the export is always the whole list. toCsv in src/lib/utils/csv.ts does the RFC 4180 work: a header row, CRLF line endings, quoting for commas, quotes and newlines, ISO dates, true and false for booleans, and empty for null.
60 lines
2.2 KiB
TypeScript
60 lines
2.2 KiB
TypeScript
import type { APIRoute } from "astro";
|
|
import { eq, sql } from "drizzle-orm";
|
|
import { db, organizations } from "@/lib/db";
|
|
import { createSecureErrorResponse } from "@/lib/utils";
|
|
import { requireAuthenticatedUserId } from "@/lib/auth-guards";
|
|
import { toCsv } from "@/lib/utils/csv";
|
|
|
|
/**
|
|
* The exported organization columns, in the order they appear in the file. It
|
|
* is the organizations table without the internal fields: the ids, the avatar
|
|
* URL and the mirror option overrides (#428).
|
|
*/
|
|
const organizationExportSelection = {
|
|
name: organizations.name,
|
|
membershipRole: organizations.membershipRole,
|
|
isIncluded: organizations.isIncluded,
|
|
destinationOrg: organizations.destinationOrg,
|
|
status: organizations.status,
|
|
repositoryCount: organizations.repositoryCount,
|
|
publicRepositoryCount: organizations.publicRepositoryCount,
|
|
privateRepositoryCount: organizations.privateRepositoryCount,
|
|
forkRepositoryCount: organizations.forkRepositoryCount,
|
|
lastMirrored: organizations.lastMirrored,
|
|
errorMessage: organizations.errorMessage,
|
|
createdAt: organizations.createdAt,
|
|
updatedAt: organizations.updatedAt,
|
|
};
|
|
|
|
export const ORGANIZATION_EXPORT_COLUMNS = Object.keys(
|
|
organizationExportSelection
|
|
) as (keyof typeof organizationExportSelection)[];
|
|
|
|
export function organizationExportFilename(now: Date): string {
|
|
return `gitea-mirror-organizations-${now.toISOString().slice(0, 10)}.csv`;
|
|
}
|
|
|
|
export const GET: APIRoute = async ({ request, locals }) => {
|
|
try {
|
|
const authResult = await requireAuthenticatedUserId({ request, locals });
|
|
if ("response" in authResult) return authResult.response;
|
|
|
|
const rows = await db
|
|
.select(organizationExportSelection)
|
|
.from(organizations)
|
|
.where(eq(organizations.userId, authResult.userId))
|
|
.orderBy(sql`${organizations.name} COLLATE NOCASE`);
|
|
|
|
return new Response(toCsv(rows, ORGANIZATION_EXPORT_COLUMNS), {
|
|
status: 200,
|
|
headers: {
|
|
"Content-Type": "text/csv; charset=utf-8",
|
|
"Content-Disposition": `attachment; filename="${organizationExportFilename(new Date())}"`,
|
|
"Cache-Control": "no-store",
|
|
},
|
|
});
|
|
} catch (error) {
|
|
return createSecureErrorResponse(error, "organizations CSV export", 500);
|
|
}
|
|
};
|