Files
triggerdotdev--trigger.dev/apps/webapp/app/models/organization.server.ts
Matt Aitken bee59de3a0 Concurrency self serve (#2681)
* Don't use the organization max concurrency anymore

* Early draft of the concurrency page

* WIP adding a new stepper input component

* Move stepper to be alphabetical

* When max value is reached, disabled the + button

* Show placeholder if you delete all numbers

* Make all the html input values available to the component

* Adds size variants

* Move stepper into its own component

* Work on showing the extra concurrency

* The purchase form styling and functionality (minus actually purchasing)

* New style for outline input fields

* Concurrency purchasing working

* Purchasing concurrency and quota emails working

* Improvements to the modal

* Show cost breakdown in the modal

* Fix for allocated concurrency including DEV

* Improved types

* Allocating concurrency is working

* Live updates total env concurrency

* Implemented reset

* Fix for concurrency allocation editing across multiple projects

* Tabular numbers

* Added an error from allocating concurrency

* Fixes for allocating concurrency where it didn't calculate correctly

* "Increase limit" link to concurrency page

* Indent environments

* Added Preview limit when updating concurrency for an org

* Show error when changing plan fails

* Added maximumProjectCount column to Org

* Limit project count and display a rich error toast (with title and button now)

* Added title and button to toasts. Use it for new project error

* @trigger.dev/platform 1.0.20

* Allow submitting zero concurrency so you can downgrade back to nothing

* Use the server as the truth for omitted environments

* Updated the pricing panels

---------

Co-authored-by: James Ritchie <james@trigger.dev>
2025-11-19 11:22:23 +00:00

129 lines
3.1 KiB
TypeScript

import type {
Organization,
OrgMember,
Project,
RuntimeEnvironment,
User,
} from "@trigger.dev/database";
import { customAlphabet } from "nanoid";
import { generate } from "random-words";
import slug from "slug";
import { prisma, type PrismaClientOrTransaction } from "~/db.server";
import { env } from "~/env.server";
import { featuresForUrl } from "~/features.server";
import { createApiKeyForEnv, createPkApiKeyForEnv, envSlug } from "./api-key.server";
import { getDefaultEnvironmentConcurrencyLimit } from "~/services/platform.v3.server";
export type { Organization };
const nanoid = customAlphabet("1234567890abcdef", 4);
export async function createOrganization(
{
title,
userId,
companySize,
}: Pick<Organization, "title" | "companySize"> & {
userId: User["id"];
},
attemptCount = 0
): Promise<Organization> {
if (typeof process.env.BLOCKED_USERS === "string" && process.env.BLOCKED_USERS.includes(userId)) {
throw new Error("Organization could not be created.");
}
const uniqueOrgSlug = `${slug(title)}-${nanoid(4)}`;
const orgWithSameSlug = await prisma.organization.findFirst({
where: { slug: uniqueOrgSlug },
});
if (attemptCount > 100) {
throw new Error(`Unable to create organization with slug ${uniqueOrgSlug} after 100 attempts`);
}
if (orgWithSameSlug) {
return createOrganization(
{
title,
userId,
companySize,
},
attemptCount + 1
);
}
const features = featuresForUrl(new URL(env.APP_ORIGIN));
const organization = await prisma.organization.create({
data: {
title,
slug: uniqueOrgSlug,
companySize,
maximumConcurrencyLimit: env.DEFAULT_ORG_EXECUTION_CONCURRENCY_LIMIT,
members: {
create: {
userId: userId,
role: "ADMIN",
},
},
v3Enabled: true,
},
include: {
members: true,
},
});
return { ...organization };
}
export async function createEnvironment({
organization,
project,
type,
isBranchableEnvironment = false,
member,
prismaClient = prisma,
}: {
organization: Pick<Organization, "id" | "maximumConcurrencyLimit">;
project: Pick<Project, "id">;
type: RuntimeEnvironment["type"];
isBranchableEnvironment?: boolean;
member?: OrgMember;
prismaClient?: PrismaClientOrTransaction;
}) {
const slug = envSlug(type);
const apiKey = createApiKeyForEnv(type);
const pkApiKey = createPkApiKeyForEnv(type);
const shortcode = createShortcode().join("-");
const limit = await getDefaultEnvironmentConcurrencyLimit(organization.id, type);
return await prismaClient.runtimeEnvironment.create({
data: {
slug,
apiKey,
pkApiKey,
shortcode,
autoEnableInternalSources: type !== "DEVELOPMENT",
maximumConcurrencyLimit: limit,
organization: {
connect: {
id: organization.id,
},
},
project: {
connect: {
id: project.id,
},
},
orgMember: member ? { connect: { id: member.id } } : undefined,
type,
isBranchableEnvironment,
},
});
}
function createShortcode() {
return generate({ exactly: 2 });
}