mirror of
https://github.com/RayLabsHQ/gitea-mirror.git
synced 2025-12-12 22:46:46 +03:00
🎉 Gitea Mirror: Added
This commit is contained in:
177
src/pages/api/sync/index.ts
Normal file
177
src/pages/api/sync/index.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
import type { APIRoute } from "astro";
|
||||
import { db, organizations, repositories, configs } from "@/lib/db";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { createMirrorJob } from "@/lib/helpers";
|
||||
import {
|
||||
createGitHubClient,
|
||||
getGithubOrganizations,
|
||||
getGithubRepositories,
|
||||
getGithubStarredRepositories,
|
||||
} from "@/lib/github";
|
||||
import { jsonResponse } from "@/lib/utils";
|
||||
|
||||
export const POST: APIRoute = async ({ request }) => {
|
||||
const url = new URL(request.url);
|
||||
const userId = url.searchParams.get("userId");
|
||||
|
||||
if (!userId) {
|
||||
return jsonResponse({ data: { error: "Missing userId" }, status: 400 });
|
||||
}
|
||||
|
||||
try {
|
||||
const [config] = await db
|
||||
.select()
|
||||
.from(configs)
|
||||
.where(eq(configs.userId, userId))
|
||||
.limit(1);
|
||||
|
||||
if (!config) {
|
||||
return jsonResponse({
|
||||
data: { error: "No configuration found for this user" },
|
||||
status: 404,
|
||||
});
|
||||
}
|
||||
|
||||
const token = config.githubConfig?.token;
|
||||
|
||||
if (!token) {
|
||||
return jsonResponse({
|
||||
data: { error: "GitHub token is missing in config" },
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
const octokit = createGitHubClient(token);
|
||||
|
||||
// Fetch GitHub data in parallel
|
||||
const [basicAndForkedRepos, starredRepos, gitOrgs] = await Promise.all([
|
||||
getGithubRepositories({ octokit, config }),
|
||||
config.githubConfig?.mirrorStarred
|
||||
? getGithubStarredRepositories({ octokit, config })
|
||||
: Promise.resolve([]),
|
||||
getGithubOrganizations({ octokit, config }),
|
||||
]);
|
||||
|
||||
const allGithubRepos = [...basicAndForkedRepos, ...starredRepos];
|
||||
|
||||
// Prepare full list of repos and orgs
|
||||
const newRepos = allGithubRepos.map((repo) => ({
|
||||
id: uuidv4(),
|
||||
userId,
|
||||
configId: config.id,
|
||||
name: repo.name,
|
||||
fullName: repo.fullName,
|
||||
url: repo.url,
|
||||
cloneUrl: repo.cloneUrl,
|
||||
owner: repo.owner,
|
||||
organization: repo.organization,
|
||||
isPrivate: repo.isPrivate,
|
||||
isForked: repo.isForked,
|
||||
forkedFrom: repo.forkedFrom,
|
||||
hasIssues: repo.hasIssues,
|
||||
isStarred: repo.isStarred,
|
||||
isArchived: repo.isArchived,
|
||||
size: repo.size,
|
||||
hasLFS: repo.hasLFS,
|
||||
hasSubmodules: repo.hasSubmodules,
|
||||
defaultBranch: repo.defaultBranch,
|
||||
visibility: repo.visibility,
|
||||
status: repo.status,
|
||||
lastMirrored: repo.lastMirrored,
|
||||
errorMessage: repo.errorMessage,
|
||||
createdAt: repo.createdAt,
|
||||
updatedAt: repo.updatedAt,
|
||||
}));
|
||||
|
||||
const newOrgs = gitOrgs.map((org) => ({
|
||||
id: uuidv4(),
|
||||
userId,
|
||||
configId: config.id,
|
||||
name: org.name,
|
||||
avatarUrl: org.avatarUrl,
|
||||
membershipRole: org.membershipRole,
|
||||
isIncluded: false,
|
||||
status: org.status,
|
||||
repositoryCount: org.repositoryCount,
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
}));
|
||||
|
||||
let insertedRepos: typeof newRepos = [];
|
||||
let insertedOrgs: typeof newOrgs = [];
|
||||
|
||||
// Transaction to insert only new items
|
||||
await db.transaction(async (tx) => {
|
||||
const [existingRepos, existingOrgs] = await Promise.all([
|
||||
tx
|
||||
.select({ fullName: repositories.fullName })
|
||||
.from(repositories)
|
||||
.where(eq(repositories.userId, userId)),
|
||||
tx
|
||||
.select({ name: organizations.name })
|
||||
.from(organizations)
|
||||
.where(eq(organizations.userId, userId)),
|
||||
]);
|
||||
|
||||
const existingRepoNames = new Set(existingRepos.map((r) => r.fullName));
|
||||
const existingOrgNames = new Set(existingOrgs.map((o) => o.name));
|
||||
|
||||
insertedRepos = newRepos.filter(
|
||||
(r) => !existingRepoNames.has(r.fullName)
|
||||
);
|
||||
insertedOrgs = newOrgs.filter((o) => !existingOrgNames.has(o.name));
|
||||
|
||||
if (insertedRepos.length > 0) {
|
||||
await tx.insert(repositories).values(insertedRepos);
|
||||
}
|
||||
|
||||
if (insertedOrgs.length > 0) {
|
||||
await tx.insert(organizations).values(insertedOrgs);
|
||||
}
|
||||
});
|
||||
|
||||
// Create mirror jobs only for newly inserted items
|
||||
const mirrorJobPromises = [
|
||||
...insertedRepos.map((repo) =>
|
||||
createMirrorJob({
|
||||
userId,
|
||||
repositoryId: repo.id,
|
||||
repositoryName: repo.name,
|
||||
status: "imported",
|
||||
message: `Repository ${repo.name} fetched successfully`,
|
||||
details: `Repository ${repo.name} was fetched from GitHub`,
|
||||
})
|
||||
),
|
||||
...insertedOrgs.map((org) =>
|
||||
createMirrorJob({
|
||||
userId,
|
||||
organizationId: org.id,
|
||||
organizationName: org.name,
|
||||
status: "imported",
|
||||
message: `Organization ${org.name} fetched successfully`,
|
||||
details: `Organization ${org.name} was fetched from GitHub`,
|
||||
})
|
||||
),
|
||||
];
|
||||
|
||||
await Promise.all(mirrorJobPromises);
|
||||
|
||||
return jsonResponse({
|
||||
data: {
|
||||
success: true,
|
||||
message: "Repositories and organizations synced successfully",
|
||||
newRepositories: insertedRepos.length,
|
||||
newOrganizations: insertedOrgs.length,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error syncing GitHub data for user:", userId, error);
|
||||
return jsonResponse({
|
||||
data: {
|
||||
error: error instanceof Error ? error.message : "Something went wrong",
|
||||
},
|
||||
status: 500,
|
||||
});
|
||||
}
|
||||
};
|
||||
136
src/pages/api/sync/organization.ts
Normal file
136
src/pages/api/sync/organization.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import type { APIRoute } from "astro";
|
||||
import { Octokit } from "@octokit/rest";
|
||||
import { configs, db, organizations, repositories } from "@/lib/db";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { jsonResponse } from "@/lib/utils";
|
||||
import type {
|
||||
AddOrganizationApiRequest,
|
||||
AddOrganizationApiResponse,
|
||||
} from "@/types/organizations";
|
||||
import type { RepositoryVisibility, RepoStatus } from "@/types/Repository";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
export const POST: APIRoute = async ({ request }) => {
|
||||
try {
|
||||
const body: AddOrganizationApiRequest = await request.json();
|
||||
const { role, org, userId } = body;
|
||||
|
||||
if (!org || !userId || !role) {
|
||||
return jsonResponse({
|
||||
data: { success: false, error: "Missing org, role or userId" },
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
// Check if org already exists
|
||||
const existingOrg = await db
|
||||
.select()
|
||||
.from(organizations)
|
||||
.where(
|
||||
and(eq(organizations.name, org), eq(organizations.userId, userId))
|
||||
);
|
||||
|
||||
if (existingOrg.length > 0) {
|
||||
return jsonResponse({
|
||||
data: {
|
||||
success: false,
|
||||
error: "Organization already exists for this user",
|
||||
},
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
// Get user's config
|
||||
const [config] = await db
|
||||
.select()
|
||||
.from(configs)
|
||||
.where(eq(configs.userId, userId))
|
||||
.limit(1);
|
||||
|
||||
if (!config) {
|
||||
return jsonResponse({
|
||||
data: { error: "No configuration found for this user" },
|
||||
status: 404,
|
||||
});
|
||||
}
|
||||
|
||||
const configId = config.id;
|
||||
|
||||
const octokit = new Octokit();
|
||||
|
||||
// Fetch org metadata
|
||||
const { data: orgData } = await octokit.orgs.get({ org });
|
||||
|
||||
// Fetch public repos using Octokit paginator
|
||||
const publicRepos = await octokit.paginate(octokit.repos.listForOrg, {
|
||||
org,
|
||||
type: "public",
|
||||
per_page: 100,
|
||||
});
|
||||
|
||||
// Insert repositories
|
||||
const repoRecords = publicRepos.map((repo) => ({
|
||||
id: uuidv4(),
|
||||
userId,
|
||||
configId,
|
||||
name: repo.name,
|
||||
fullName: repo.full_name,
|
||||
url: repo.html_url,
|
||||
cloneUrl: repo.clone_url ?? "",
|
||||
owner: repo.owner.login,
|
||||
organization:
|
||||
repo.owner.type === "Organization" ? repo.owner.login : null,
|
||||
isPrivate: repo.private,
|
||||
isForked: repo.fork,
|
||||
forkedFrom: undefined,
|
||||
hasIssues: repo.has_issues,
|
||||
isStarred: false,
|
||||
isArchived: repo.archived,
|
||||
size: repo.size,
|
||||
hasLFS: false,
|
||||
hasSubmodules: false,
|
||||
defaultBranch: repo.default_branch ?? "main",
|
||||
visibility: (repo.visibility ?? "public") as RepositoryVisibility,
|
||||
status: "imported" as RepoStatus,
|
||||
lastMirrored: undefined,
|
||||
errorMessage: undefined,
|
||||
createdAt: repo.created_at ? new Date(repo.created_at) : new Date(),
|
||||
updatedAt: repo.updated_at ? new Date(repo.updated_at) : new Date(),
|
||||
}));
|
||||
|
||||
await db.insert(repositories).values(repoRecords);
|
||||
|
||||
// Insert organization metadata
|
||||
const organizationRecord = {
|
||||
id: uuidv4(),
|
||||
userId,
|
||||
configId,
|
||||
name: orgData.login,
|
||||
avatarUrl: orgData.avatar_url,
|
||||
membershipRole: role,
|
||||
isIncluded: false,
|
||||
status: "imported" as RepoStatus,
|
||||
repositoryCount: publicRepos.length,
|
||||
createdAt: orgData.created_at ? new Date(orgData.created_at) : new Date(),
|
||||
updatedAt: orgData.updated_at ? new Date(orgData.updated_at) : new Date(),
|
||||
};
|
||||
|
||||
await db.insert(organizations).values(organizationRecord);
|
||||
|
||||
const resPayload: AddOrganizationApiResponse = {
|
||||
success: true,
|
||||
organization: organizationRecord,
|
||||
message: "Organization and repositories imported successfully",
|
||||
};
|
||||
|
||||
return jsonResponse({ data: resPayload, status: 200 });
|
||||
} catch (error) {
|
||||
console.error("Error inserting organization/repositories:", error);
|
||||
return jsonResponse({
|
||||
data: {
|
||||
error: error instanceof Error ? error.message : "Something went wrong",
|
||||
},
|
||||
status: 500,
|
||||
});
|
||||
}
|
||||
};
|
||||
137
src/pages/api/sync/repository.ts
Normal file
137
src/pages/api/sync/repository.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import type { APIRoute } from "astro";
|
||||
import { Octokit } from "@octokit/rest";
|
||||
import { configs, db, repositories } from "@/lib/db";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { type Repository } from "@/lib/db/schema";
|
||||
import { jsonResponse } from "@/lib/utils";
|
||||
import type {
|
||||
AddRepositoriesApiRequest,
|
||||
AddRepositoriesApiResponse,
|
||||
RepositoryVisibility,
|
||||
} from "@/types/Repository";
|
||||
import { createMirrorJob } from "@/lib/helpers";
|
||||
|
||||
export const POST: APIRoute = async ({ request }) => {
|
||||
try {
|
||||
const body: AddRepositoriesApiRequest = await request.json();
|
||||
const { owner, repo, userId } = body;
|
||||
|
||||
if (!owner || !repo || !userId) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
error: "Missing owner, repo, or userId",
|
||||
}),
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
// Check if repository with the same owner, name, and userId already exists
|
||||
const existingRepo = await db
|
||||
.select()
|
||||
.from(repositories)
|
||||
.where(
|
||||
and(
|
||||
eq(repositories.owner, owner),
|
||||
eq(repositories.name, repo),
|
||||
eq(repositories.userId, userId)
|
||||
)
|
||||
);
|
||||
|
||||
if (existingRepo.length > 0) {
|
||||
return jsonResponse({
|
||||
data: {
|
||||
success: false,
|
||||
error:
|
||||
"Repository with this name and owner already exists for this user",
|
||||
},
|
||||
status: 400,
|
||||
});
|
||||
}
|
||||
|
||||
// Get user's active config
|
||||
const [config] = await db
|
||||
.select()
|
||||
.from(configs)
|
||||
.where(eq(configs.userId, userId))
|
||||
.limit(1);
|
||||
|
||||
if (!config) {
|
||||
return jsonResponse({
|
||||
data: { error: "No configuration found for this user" },
|
||||
status: 404,
|
||||
});
|
||||
}
|
||||
|
||||
const configId = config.id;
|
||||
|
||||
const octokit = new Octokit(); // No auth for public repos
|
||||
|
||||
const { data: repoData } = await octokit.rest.repos.get({ owner, repo });
|
||||
|
||||
const metadata = {
|
||||
id: uuidv4(),
|
||||
userId,
|
||||
configId,
|
||||
name: repoData.name,
|
||||
fullName: repoData.full_name,
|
||||
url: repoData.html_url,
|
||||
cloneUrl: repoData.clone_url,
|
||||
owner: repoData.owner.login,
|
||||
organization:
|
||||
repoData.owner.type === "Organization"
|
||||
? repoData.owner.login
|
||||
: undefined,
|
||||
isPrivate: repoData.private,
|
||||
isForked: repoData.fork,
|
||||
forkedFrom: undefined,
|
||||
hasIssues: repoData.has_issues,
|
||||
isStarred: false,
|
||||
isArchived: repoData.archived,
|
||||
size: repoData.size,
|
||||
hasLFS: false,
|
||||
hasSubmodules: false,
|
||||
defaultBranch: repoData.default_branch,
|
||||
visibility: (repoData.visibility ?? "public") as RepositoryVisibility,
|
||||
status: "imported" as Repository["status"],
|
||||
lastMirrored: undefined,
|
||||
errorMessage: undefined,
|
||||
createdAt: repoData.created_at
|
||||
? new Date(repoData.created_at)
|
||||
: new Date(),
|
||||
updatedAt: repoData.updated_at
|
||||
? new Date(repoData.updated_at)
|
||||
: new Date(),
|
||||
};
|
||||
|
||||
await db.insert(repositories).values(metadata);
|
||||
|
||||
createMirrorJob({
|
||||
userId,
|
||||
organizationId: metadata.organization,
|
||||
organizationName: metadata.organization,
|
||||
repositoryId: metadata.id,
|
||||
repositoryName: metadata.name,
|
||||
status: "imported",
|
||||
message: `Repository ${metadata.name} fetched successfully`,
|
||||
details: `Repository ${metadata.name} was fetched from GitHub`,
|
||||
});
|
||||
|
||||
const resPayload: AddRepositoriesApiResponse = {
|
||||
success: true,
|
||||
repository: metadata,
|
||||
message: "Repository added successfully",
|
||||
};
|
||||
|
||||
return jsonResponse({ data: resPayload, status: 200 });
|
||||
} catch (error) {
|
||||
console.error("Error inserting repository:", error);
|
||||
return jsonResponse({
|
||||
data: {
|
||||
error: error instanceof Error ? error.message : "Something went wrong",
|
||||
},
|
||||
status: 500,
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user