sourcebot/packages/web/src/actions.ts

267 lines
6.4 KiB
TypeScript
Raw Normal View History

'use server';
2025-01-23 18:26:41 +00:00
import Ajv from "ajv";
import { getUser } from "./data/user";
import { auth } from "./auth";
2025-01-23 18:26:41 +00:00
import { notAuthenticated, notFound, ServiceError, unexpectedError } from "./lib/serviceError";
import { prisma } from "@/prisma";
2025-01-23 18:26:41 +00:00
import { StatusCodes } from "http-status-codes";
import { ErrorCode } from "./lib/errorCodes";
import { githubSchema } from "@sourcebot/schemas/v3/github.schema";
import { encrypt } from "@sourcebot/crypto"
2025-01-23 18:26:41 +00:00
const ajv = new Ajv({
validateFormats: false,
});
export const createSecret = async (key: string, value: string): Promise<{ success: boolean } | ServiceError> => {
const session = await auth();
if (!session) {
return notAuthenticated();
}
const user = await getUser(session.user.id);
if (!user) {
return unexpectedError("User not found");
}
const orgId = user.activeOrgId;
if (!orgId) {
return unexpectedError("User has no active org");
}
// @todo: refactor this into a shared function
const membership = await prisma.userToOrg.findUnique({
where: {
orgId_userId: {
userId: session.user.id,
orgId,
}
},
});
if (!membership) {
return notFound();
}
try {
const encrypted = encrypt(value);
await prisma.secret.create({
data: {
orgId,
key,
encryptedValue: encrypted.encryptedData,
iv: encrypted.iv,
}
});
} catch (e) {
return unexpectedError(`Failed to create secret`);
}
return {
success: true,
}
}
export const getSecrets = async (): Promise<{ createdAt: Date; key: string; }[] | ServiceError> => {
const session = await auth();
if (!session) {
return notAuthenticated();
}
const user = await getUser(session.user.id);
if (!user) {
return unexpectedError("User not found");
}
const orgId = user.activeOrgId;
if (!orgId) {
return unexpectedError("User has no active org");
}
const membership = await prisma.userToOrg.findUnique({
where: {
orgId_userId: {
userId: session.user.id,
orgId,
}
},
});
if (!membership) {
return notFound();
}
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> => {
const session = await auth();
if (!session) {
return notAuthenticated();
}
const user = await getUser(session.user.id);
if (!user) {
return unexpectedError("User not found");
}
const orgId = user.activeOrgId;
if (!orgId) {
return unexpectedError("User has no active org");
}
const membership = await prisma.userToOrg.findUnique({
where: {
orgId_userId: {
userId: session.user.id,
orgId,
}
},
});
if (!membership) {
return notFound();
}
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> => {
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> => {
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
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> => {
const session = await auth();
if (!session) {
return notAuthenticated();
}
const user = await getUser(session.user.id);
if (!user) {
return unexpectedError("User not found");
}
const orgId = user.activeOrgId;
if (!orgId) {
return unexpectedError("User has no active org");
}
// @todo: refactor this into a shared function
const membership = await prisma.userToOrg.findUnique({
where: {
orgId_userId: {
userId: session.user.id,
orgId,
}
},
});
if (!membership) {
return notFound();
}
let parsedConfig;
try {
parsedConfig = JSON.parse(config);
} catch (e) {
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,
}
}