2025-01-21 22:50:16 +00:00
|
|
|
'use server';
|
|
|
|
|
|
2025-01-23 18:26:41 +00:00
|
|
|
import Ajv from "ajv";
|
2025-01-28 18:39:59 +00:00
|
|
|
import { auth, getCurrentUserOrg } from "./auth";
|
2025-01-30 20:49:04 +00:00
|
|
|
import { notAuthenticated, notFound, ServiceError, unexpectedError } from "@/lib/serviceError";
|
2025-01-21 22:50:16 +00:00
|
|
|
import { prisma } from "@/prisma";
|
2025-01-23 18:26:41 +00:00
|
|
|
import { StatusCodes } from "http-status-codes";
|
2025-01-28 18:39:59 +00:00
|
|
|
import { ErrorCode } from "@/lib/errorCodes";
|
|
|
|
|
import { isServiceError } from "@/lib/utils";
|
2025-01-24 18:51:49 +00:00
|
|
|
import { githubSchema } from "@sourcebot/schemas/v3/github.schema";
|
2025-01-27 22:07:07 +00:00
|
|
|
import { encrypt } from "@sourcebot/crypto"
|
2025-01-21 22:50:16 +00:00
|
|
|
|
2025-01-23 18:26:41 +00:00
|
|
|
const ajv = new Ajv({
|
|
|
|
|
validateFormats: false,
|
|
|
|
|
});
|
2025-01-21 22:50:16 +00:00
|
|
|
|
2025-01-27 22:07:07 +00:00
|
|
|
export const createSecret = async (key: string, value: string): Promise<{ success: boolean } | ServiceError> => {
|
2025-01-28 18:39:59 +00:00
|
|
|
const orgId = await getCurrentUserOrg();
|
|
|
|
|
if (isServiceError(orgId)) {
|
|
|
|
|
return orgId;
|
2025-01-27 22:07:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const encrypted = encrypt(value);
|
|
|
|
|
await prisma.secret.create({
|
|
|
|
|
data: {
|
|
|
|
|
orgId,
|
|
|
|
|
key,
|
|
|
|
|
encryptedValue: encrypted.encryptedData,
|
|
|
|
|
iv: encrypted.iv,
|
|
|
|
|
}
|
|
|
|
|
});
|
2025-01-30 18:23:47 +00:00
|
|
|
} catch {
|
2025-01-27 22:07:07 +00:00
|
|
|
return unexpectedError(`Failed to create secret`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
success: true,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const getSecrets = async (): Promise<{ createdAt: Date; key: string; }[] | ServiceError> => {
|
2025-01-28 18:39:59 +00:00
|
|
|
const orgId = await getCurrentUserOrg();
|
|
|
|
|
if (isServiceError(orgId)) {
|
|
|
|
|
return orgId;
|
2025-01-27 22:07:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const secrets = await prisma.secret.findMany({
|
|
|
|
|
where: {
|
|
|
|
|
orgId,
|
|
|
|
|
},
|
|
|
|
|
select: {
|
|
|
|
|
key: true,
|
|
|
|
|
createdAt: true
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return secrets.map((secret) => ({
|
|
|
|
|
key: secret.key,
|
|
|
|
|
createdAt: secret.createdAt,
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const deleteSecret = async (key: string): Promise<{ success: boolean } | ServiceError> => {
|
2025-01-28 18:39:59 +00:00
|
|
|
const orgId = await getCurrentUserOrg();
|
|
|
|
|
if (isServiceError(orgId)) {
|
|
|
|
|
return orgId;
|
2025-01-27 22:07:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await prisma.secret.delete({
|
|
|
|
|
where: {
|
|
|
|
|
orgId_key: {
|
|
|
|
|
orgId,
|
|
|
|
|
key,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
success: true,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-01-23 18:26:41 +00:00
|
|
|
export const createOrg = async (name: string): Promise<{ id: number } | ServiceError> => {
|
2025-01-21 22:50:16 +00:00
|
|
|
const session = await auth();
|
|
|
|
|
if (!session) {
|
|
|
|
|
return notAuthenticated();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create the org
|
|
|
|
|
const org = await prisma.org.create({
|
|
|
|
|
data: {
|
|
|
|
|
name,
|
|
|
|
|
members: {
|
|
|
|
|
create: {
|
|
|
|
|
userId: session.user.id,
|
|
|
|
|
role: "OWNER",
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
id: org.id,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-23 18:26:41 +00:00
|
|
|
export const switchActiveOrg = async (orgId: number): Promise<{ id: number } | ServiceError> => {
|
2025-01-21 22:50:16 +00:00
|
|
|
const session = await auth();
|
|
|
|
|
if (!session) {
|
|
|
|
|
return notAuthenticated();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check to see if the user is a member of the org
|
2025-01-23 18:26:41 +00:00
|
|
|
// @todo: refactor this into a shared function
|
2025-01-21 22:50:16 +00:00
|
|
|
const membership = await prisma.userToOrg.findUnique({
|
|
|
|
|
where: {
|
|
|
|
|
orgId_userId: {
|
|
|
|
|
userId: session.user.id,
|
|
|
|
|
orgId,
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
if (!membership) {
|
|
|
|
|
return notFound();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update the user's active org
|
|
|
|
|
await prisma.user.update({
|
|
|
|
|
where: {
|
|
|
|
|
id: session.user.id,
|
|
|
|
|
},
|
|
|
|
|
data: {
|
|
|
|
|
activeOrgId: orgId,
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
id: orgId,
|
|
|
|
|
}
|
2025-01-23 18:26:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const createConnection = async (config: string): Promise<{ id: number } | ServiceError> => {
|
2025-01-28 18:39:59 +00:00
|
|
|
const orgId = await getCurrentUserOrg();
|
|
|
|
|
if (isServiceError(orgId)) {
|
|
|
|
|
return orgId;
|
2025-01-23 18:26:41 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let parsedConfig;
|
|
|
|
|
try {
|
|
|
|
|
parsedConfig = JSON.parse(config);
|
2025-01-30 21:31:59 +00:00
|
|
|
} catch {
|
2025-01-23 18:26:41 +00:00
|
|
|
return {
|
|
|
|
|
statusCode: StatusCodes.BAD_REQUEST,
|
|
|
|
|
errorCode: ErrorCode.INVALID_REQUEST_BODY,
|
|
|
|
|
message: "config must be a valid JSON object."
|
|
|
|
|
} satisfies ServiceError;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// @todo: we will need to validate the config against different schemas based on the type of connection.
|
|
|
|
|
const isValidConfig = ajv.validate(githubSchema, parsedConfig);
|
|
|
|
|
if (!isValidConfig) {
|
|
|
|
|
return {
|
|
|
|
|
statusCode: StatusCodes.BAD_REQUEST,
|
|
|
|
|
errorCode: ErrorCode.INVALID_REQUEST_BODY,
|
|
|
|
|
message: `config schema validation failed with errors: ${ajv.errorsText(ajv.errors)}`,
|
|
|
|
|
} satisfies ServiceError;
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-24 21:16:08 +00:00
|
|
|
const connection = await prisma.connection.create({
|
2025-01-23 18:26:41 +00:00
|
|
|
data: {
|
|
|
|
|
orgId: orgId,
|
2025-01-24 21:16:08 +00:00
|
|
|
config: parsedConfig,
|
2025-01-23 18:26:41 +00:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
id: connection.id,
|
|
|
|
|
}
|
2025-01-21 22:50:16 +00:00
|
|
|
}
|