Files
Eric Allam 0e919f56f2 Alerts v1 (#1065)
* Alerts v1

* Encrypt alert webhook secrets and allow them to be generated by the server

* Alert v1 UI

* Remove unnecessary emails

* Move to using `@react-email/components`

* WIP slack alerts

* More slack alerts WIP

* Update pnpm lock after rebase

* Finish implementing Slack alerts

* Use a more error like emoji

* New secondary variant for the segmented control

* Added a simple checkbox style variant to storybook

* Style tweak to the segmented control

* UI improvements to the alert modal

* Use searchable Select for alerts. Changed default variant for SegmentedControl

* Secondary button now using secondary colour

* segmented control style tweak

* Improved the channel column in the alerts table

* Updated logo-mono.png

* Updated email styles

* Email templates updated to new styles

* Don’t log the decrypted secret

* await enqueing the deployment alert when an index fails

* await enqueing the timeout alert

---------

Co-authored-by: James Ritchie <james@jamesritchie.co.uk>
Co-authored-by: Matt Aitken <matt@mattaitken.com>
2024-05-09 12:12:00 +01:00

125 lines
3.2 KiB
TypeScript

import { nanoid, customAlphabet } from "nanoid";
import slug from "slug";
import { prisma } from "~/db.server";
import type { Project } from "@trigger.dev/database";
import { Organization, createEnvironment } from "./organization.server";
import { env } from "~/env.server";
export type { Project } from "@trigger.dev/database";
const externalRefGenerator = customAlphabet("abcdefghijklmnopqrstuvwxyz", 20);
export async function createProject(
{
organizationSlug,
name,
userId,
version,
}: { organizationSlug: string; name: string; userId: string; version: "v2" | "v3" },
attemptCount = 0
): Promise<Project & { organization: Organization }> {
//check the user has permissions to do this
const organization = await prisma.organization.findFirst({
where: {
slug: organizationSlug,
members: { some: { userId } },
},
});
if (!organization) {
throw new Error(
`User ${userId} does not have permission to create a project in organization ${organizationSlug}`
);
}
if (version === "v3") {
if (!organization.v3Enabled) {
throw new Error(`Organization can't create v3 projects.`);
}
if (!env.V3_ENABLED) {
throw new Error(`v3 is not available yet.`);
}
}
//ensure the slug is globally unique
const uniqueProjectSlug = `${slug(name)}-${nanoid(4)}`;
const projectWithSameSlug = await prisma.project.findFirst({
where: { slug: uniqueProjectSlug },
});
if (attemptCount > 100) {
throw new Error(`Unable to create project with slug ${uniqueProjectSlug} after 100 attempts`);
}
if (projectWithSameSlug) {
return createProject(
{
organizationSlug,
name,
userId,
version,
},
attemptCount + 1
);
}
const project = await prisma.project.create({
data: {
name,
slug: uniqueProjectSlug,
organization: {
connect: {
slug: organizationSlug,
},
},
externalRef: `proj_${externalRefGenerator()}`,
version: version === "v3" ? "V3" : "V2",
},
include: {
organization: {
include: {
members: true,
},
},
},
});
// Create the dev and prod environments
await createEnvironment(organization, project, "PRODUCTION");
if (version === "v2") {
await createEnvironment(organization, project, "STAGING");
}
for (const member of project.organization.members) {
await createEnvironment(organization, project, "DEVELOPMENT", member);
}
return project;
}
export async function findProjectBySlug(orgSlug: string, projectSlug: string, userId: string) {
// Find the project scoped to the organization, making sure the user belongs to that org
return await prisma.project.findFirst({
where: {
slug: projectSlug,
organization: {
slug: orgSlug,
members: { some: { userId } },
},
},
});
}
export async function findProjectByRef(externalRef: string, userId: string) {
// Find the project scoped to the organization, making sure the user belongs to that org
return await prisma.project.findFirst({
where: {
externalRef,
organization: {
members: { some: { userId } },
},
},
});
}