evOo-local/apps/api/src/agent-secrets.ts
cynarAI 6c7eed7580 Initial clean evOo-local from f00577b
Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline
(Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/*
packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
2026-09-20 17:15:16 +02:00

120 lines
3.8 KiB
TypeScript

import { ORPCError } from "@orpc/server";
import type { EncryptedSecretStore } from "@rakazo/adapters";
import type { Actor } from "@rakazo/contracts";
import { Prisma, type PrismaClient, withTransactionRetry } from "@rakazo/db";
type AgentSecretDeps = {
prisma: PrismaClient;
secrets: Pick<EncryptedSecretStore, "put">;
};
function agentSecretDto(row: { id: string; name: string; createdAt: Date; updatedAt: Date }) {
return {
id: row.id,
name: row.name,
createdAt: row.createdAt.toISOString(),
updatedAt: row.updatedAt.toISOString(),
};
}
async function requireSpaceOwner(prisma: PrismaClient, actor: Actor): Promise<void> {
const membership = await prisma.spaceMember.findUnique({
where: { spaceId_userId: { spaceId: actor.spaceId, userId: actor.userId } },
select: { role: true },
});
const roles = membership?.role.split(",").map((role) => role.trim());
if (!roles?.includes("owner")) throw new ORPCError("FORBIDDEN");
}
export async function listAgentSecrets(deps: AgentSecretDeps, actor: Actor) {
await requireSpaceOwner(deps.prisma, actor);
const rows = await deps.prisma.agentSecret.findMany({
where: { spaceId: actor.spaceId },
orderBy: [{ name: "asc" }],
select: { id: true, name: true, createdAt: true, updatedAt: true },
});
return rows.map(agentSecretDto);
}
export async function putAgentSecret(
deps: AgentSecretDeps,
actor: Actor,
input: { name: string; value: string },
signal = new AbortController().signal,
) {
await requireSpaceOwner(deps.prisma, actor);
const stored = await deps.secrets.put(input.value, {
operationId: `agent-secret:${input.name}`,
traceId: `agent-secret:${input.name}`,
spaceId: actor.spaceId,
userId: actor.userId,
signal,
});
const row = await withTransactionRetry(() =>
deps.prisma.$transaction(
async (tx) => {
const existing = await tx.agentSecret.findUnique({
where: { spaceId_name: { spaceId: actor.spaceId, name: input.name } },
select: { secretId: true },
});
await tx.secret.create({
data: {
id: stored.id,
userId: actor.userId,
spaceId: actor.spaceId,
kind: "agent-environment",
ciphertext: stored.ciphertext,
},
});
const updated = await tx.agentSecret.upsert({
where: { spaceId_name: { spaceId: actor.spaceId, name: input.name } },
create: {
spaceId: actor.spaceId,
createdByUserId: actor.userId,
name: input.name,
secretId: stored.id,
},
update: {
createdByUserId: actor.userId,
secretId: stored.id,
},
});
if (existing && existing.secretId !== stored.id) {
await tx.secret.deleteMany({
where: { id: existing.secretId, spaceId: actor.spaceId },
});
}
return updated;
},
{ isolationLevel: Prisma.TransactionIsolationLevel.Serializable },
),
);
return agentSecretDto(row);
}
export async function deleteAgentSecret(
deps: AgentSecretDeps,
actor: Actor,
id: string,
): Promise<{ ok: true }> {
await requireSpaceOwner(deps.prisma, actor);
await withTransactionRetry(() =>
deps.prisma.$transaction(
async (tx) => {
const existing = await tx.agentSecret.findFirst({
where: { id, spaceId: actor.spaceId },
select: { id: true, secretId: true },
});
if (!existing) throw new ORPCError("NOT_FOUND");
await tx.agentSecret.delete({ where: { id: existing.id } });
await tx.secret.deleteMany({
where: { id: existing.secretId, spaceId: actor.spaceId },
});
},
{ isolationLevel: Prisma.TransactionIsolationLevel.Serializable },
),
);
return { ok: true };
}