Support Gitea alongside GitHub/GitLab for repo search, authenticated cloning, and pull request creation via Gitea API. Tasks can specify gitProvider and gitBaseUrl in their spec (defaults to github for backwards compat). Auto-discovers GITEA_TOKEN from env on boot.
87 lines
3.0 KiB
TypeScript
87 lines
3.0 KiB
TypeScript
import { eq } from "drizzle-orm";
|
|
import { db } from "./db";
|
|
import { credentials as credentialsTable } from "@homelab/db";
|
|
|
|
export type Provider =
|
|
| "github" | "gitlab" | "gitea"
|
|
| "anthropic" | "openai" | "openrouter" | "google" | "opencode-zen" | "opencode-go";
|
|
|
|
export const GIT_PROVIDERS: Provider[] = ["github", "gitlab", "gitea"];
|
|
export const AI_PROVIDERS: Provider[] = ["anthropic", "openai", "openrouter", "google", "opencode-zen", "opencode-go"];
|
|
|
|
export interface Credential {
|
|
id: string;
|
|
provider: Provider;
|
|
label: string;
|
|
token: string;
|
|
baseUrl?: string;
|
|
}
|
|
|
|
function maskToken(token: string): string {
|
|
if (token.length <= 8) return "••••••••";
|
|
return token.slice(0, 4) + "••••" + token.slice(-4);
|
|
}
|
|
|
|
function rowToCredential(row: typeof credentialsTable.$inferSelect): Credential {
|
|
return {
|
|
id: row.id,
|
|
provider: row.provider as Provider,
|
|
label: row.label,
|
|
token: row.token,
|
|
baseUrl: row.baseUrl ?? undefined,
|
|
};
|
|
}
|
|
|
|
export async function getAllCredentials(): Promise<Credential[]> {
|
|
const rows = await db.select().from(credentialsTable);
|
|
return rows.map(r => ({ ...rowToCredential(r), token: maskToken(r.token) }));
|
|
}
|
|
|
|
export async function getCredentialsByKind(kind: "git" | "ai"): Promise<Credential[]> {
|
|
const providers = kind === "git" ? GIT_PROVIDERS : AI_PROVIDERS;
|
|
const rows = await db.select().from(credentialsTable);
|
|
return rows
|
|
.filter(r => providers.includes(r.provider as Provider))
|
|
.map(r => ({ ...rowToCredential(r), token: maskToken(r.token) }));
|
|
}
|
|
|
|
export async function getCredential(id: string): Promise<Credential | undefined> {
|
|
const [row] = await db.select().from(credentialsTable).where(eq(credentialsTable.id, id));
|
|
return row ? rowToCredential(row) : undefined;
|
|
}
|
|
|
|
export async function getCredentialsByProvider(provider: Provider): Promise<Credential[]> {
|
|
const rows = await db.select().from(credentialsTable).where(eq(credentialsTable.provider, provider));
|
|
return rows.map(r => ({ ...rowToCredential(r), token: maskToken(r.token) }));
|
|
}
|
|
|
|
export async function getRawCredentialsByProvider(provider: Provider): Promise<Credential[]> {
|
|
const rows = await db.select().from(credentialsTable).where(eq(credentialsTable.provider, provider));
|
|
return rows.map(rowToCredential);
|
|
}
|
|
|
|
export async function upsertCredential(cred: Credential): Promise<Credential> {
|
|
await db.insert(credentialsTable).values({
|
|
id: cred.id,
|
|
provider: cred.provider,
|
|
label: cred.label,
|
|
token: cred.token,
|
|
baseUrl: cred.baseUrl,
|
|
}).onConflictDoUpdate({
|
|
target: credentialsTable.id,
|
|
set: {
|
|
provider: cred.provider,
|
|
label: cred.label,
|
|
token: cred.token,
|
|
baseUrl: cred.baseUrl,
|
|
updatedAt: new Date(),
|
|
},
|
|
});
|
|
return { ...cred, token: maskToken(cred.token) };
|
|
}
|
|
|
|
export async function deleteCredential(id: string): Promise<boolean> {
|
|
const result = await db.delete(credentialsTable).where(eq(credentialsTable.id, id));
|
|
return (result as unknown as { rowCount: number }).rowCount > 0;
|
|
}
|