271 lines
7.2 KiB
TypeScript
271 lines
7.2 KiB
TypeScript
import type { RuntimeEnvironment, PrismaClient } from "@trigger.dev/database";
|
|
import { redirect } from "remix-typedjson";
|
|
import { prisma } from "~/db.server";
|
|
import { logger } from "~/services/logger.server";
|
|
import { type UserFromSession } from "~/services/session.server";
|
|
import { newOrganizationPath, newProjectPath } from "~/utils/pathBuilder";
|
|
import { SelectBestEnvironmentPresenter } from "./SelectBestEnvironmentPresenter.server";
|
|
import { sortEnvironments } from "~/utils/environmentSort";
|
|
import { defaultAvatar, parseAvatar } from "~/components/primitives/Avatar";
|
|
import { env } from "~/env.server";
|
|
import { flags } from "~/v3/featureFlags.server";
|
|
import { validatePartialFeatureFlags } from "~/v3/featureFlags";
|
|
import { hydrateEnvsWithActivity } from "./v3/BranchesPresenter.server";
|
|
|
|
export class OrganizationsPresenter {
|
|
#prismaClient: PrismaClient;
|
|
|
|
constructor(prismaClient: PrismaClient = prisma) {
|
|
this.#prismaClient = prismaClient;
|
|
}
|
|
|
|
public async call({
|
|
user,
|
|
organizationSlug,
|
|
projectSlug,
|
|
environmentSlug,
|
|
request,
|
|
}: {
|
|
user: UserFromSession;
|
|
organizationSlug: string;
|
|
projectSlug: string | undefined;
|
|
environmentSlug: string | undefined;
|
|
request: Request;
|
|
}) {
|
|
const organizations = await this.#getOrganizations(user.id);
|
|
if (organizations.length === 0) {
|
|
logger.info("No organizations", {
|
|
organizationSlug,
|
|
request,
|
|
});
|
|
throw redirect(newOrganizationPath());
|
|
}
|
|
|
|
const organization = organizations.find((o) => o.slug === organizationSlug);
|
|
if (!organization) {
|
|
logger.info("Not Found: organization", {
|
|
organizationSlug,
|
|
request,
|
|
organization,
|
|
});
|
|
throw new Response("Organization not Found", { status: 404 });
|
|
}
|
|
|
|
const selector = new SelectBestEnvironmentPresenter();
|
|
const bestProject = await selector.selectBestProjectFromProjects({
|
|
user,
|
|
projectSlug,
|
|
projects: organization.projects,
|
|
});
|
|
if (!bestProject) {
|
|
logger.info("Not Found: project", {
|
|
projectSlug,
|
|
request,
|
|
project: bestProject,
|
|
});
|
|
throw redirect(newProjectPath(organization));
|
|
}
|
|
|
|
const fullProject = await this.#prismaClient.project.findFirst({
|
|
where: {
|
|
id: bestProject.id,
|
|
},
|
|
include: {
|
|
environments: {
|
|
select: {
|
|
id: true,
|
|
type: true,
|
|
slug: true,
|
|
paused: true,
|
|
pauseSource: true,
|
|
isBranchableEnvironment: true,
|
|
branchName: true,
|
|
parentEnvironmentId: true,
|
|
archivedAt: true,
|
|
updatedAt: true,
|
|
orgMember: {
|
|
select: {
|
|
userId: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!fullProject) {
|
|
logger.info("Not Found: project", {
|
|
projectSlug,
|
|
request,
|
|
project: bestProject,
|
|
});
|
|
throw redirect(newProjectPath(organization));
|
|
}
|
|
|
|
const environments = fullProject.environments.filter(
|
|
(env) => env.type !== "DEVELOPMENT" || env.orgMember?.userId === user.id
|
|
);
|
|
|
|
const environmentsWithActivity = await hydrateEnvsWithActivity(
|
|
user.id,
|
|
fullProject.id,
|
|
environments
|
|
);
|
|
|
|
const environment = this.#getEnvironment({
|
|
user,
|
|
projectId: fullProject.id,
|
|
environments,
|
|
environmentSlug,
|
|
});
|
|
|
|
return {
|
|
organizations,
|
|
organization,
|
|
project: {
|
|
...fullProject,
|
|
createdAt: fullProject.createdAt,
|
|
environments: sortEnvironments(environmentsWithActivity),
|
|
},
|
|
environment,
|
|
};
|
|
}
|
|
|
|
async #getOrganizations(userId: string) {
|
|
const orgs = await this.#prismaClient.organization.findMany({
|
|
where: { members: { some: { userId } }, deletedAt: null },
|
|
orderBy: { createdAt: "desc" },
|
|
select: {
|
|
id: true,
|
|
slug: true,
|
|
title: true,
|
|
avatar: true,
|
|
featureFlags: true,
|
|
projects: {
|
|
where: { deletedAt: null, version: "V3" },
|
|
select: {
|
|
id: true,
|
|
slug: true,
|
|
name: true,
|
|
updatedAt: true,
|
|
externalRef: true,
|
|
},
|
|
orderBy: { name: "asc" },
|
|
},
|
|
_count: {
|
|
select: {
|
|
members: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
// Get global feature flags with env-var-based defaults
|
|
const globalFlags = await flags({
|
|
defaultValues: {
|
|
hasAiAccess: env.AI_FEATURES_ENABLED === "1",
|
|
hasDashboardAgentAccess: env.DASHBOARD_AGENT_ENABLED === "1",
|
|
hasPrivateConnections: env.PRIVATE_CONNECTIONS_ENABLED === "1",
|
|
},
|
|
});
|
|
|
|
return orgs.map((org) => {
|
|
const orgFlagsResult = org.featureFlags
|
|
? validatePartialFeatureFlags(org.featureFlags as Record<string, unknown>)
|
|
: ({ success: false } as const);
|
|
const orgFlags = orgFlagsResult.success ? orgFlagsResult.data : {};
|
|
|
|
// Combine global flags with org flags (org flags win)
|
|
const combinedFlags = { ...globalFlags, ...orgFlags };
|
|
|
|
return {
|
|
id: org.id,
|
|
slug: org.slug,
|
|
title: org.title,
|
|
avatar: parseAvatar(org.avatar, defaultAvatar),
|
|
featureFlags: combinedFlags,
|
|
projects: org.projects.map((project) => ({
|
|
id: project.id,
|
|
slug: project.slug,
|
|
name: project.name,
|
|
updatedAt: project.updatedAt,
|
|
externalRef: project.externalRef,
|
|
})),
|
|
membersCount: org._count.members,
|
|
};
|
|
});
|
|
}
|
|
|
|
#getEnvironment({
|
|
user,
|
|
projectId,
|
|
environmentSlug,
|
|
environments,
|
|
}: {
|
|
user: UserFromSession;
|
|
projectId: string;
|
|
environmentSlug: string | undefined;
|
|
environments: (Pick<
|
|
RuntimeEnvironment,
|
|
| "id"
|
|
| "slug"
|
|
| "type"
|
|
| "branchName"
|
|
| "paused"
|
|
| "pauseSource"
|
|
| "parentEnvironmentId"
|
|
| "isBranchableEnvironment"
|
|
| "archivedAt"
|
|
> & {
|
|
orgMember: null | {
|
|
userId: string | undefined;
|
|
};
|
|
})[];
|
|
}) {
|
|
if (environmentSlug) {
|
|
const env = environments.find(
|
|
(e) =>
|
|
e.slug === environmentSlug &&
|
|
(e.type !== "DEVELOPMENT" || e.orgMember?.userId === user.id)
|
|
);
|
|
if (env) {
|
|
return env;
|
|
}
|
|
|
|
if (!env) {
|
|
logger.info("Not Found: environment", {
|
|
environmentSlug,
|
|
environments,
|
|
});
|
|
}
|
|
}
|
|
|
|
const currentEnvironmentId: string | undefined =
|
|
user.dashboardPreferences.projects[projectId]?.currentEnvironment.id;
|
|
|
|
const environment = environments.find((e) => e.id === currentEnvironmentId);
|
|
if (environment) {
|
|
return environment;
|
|
}
|
|
|
|
//otherwise show their dev environment
|
|
const yourDevEnvironment = environments.find(
|
|
(env) =>
|
|
env.type === "DEVELOPMENT" &&
|
|
env.parentEnvironmentId === null &&
|
|
env.orgMember?.userId === user.id
|
|
);
|
|
if (yourDevEnvironment) {
|
|
return yourDevEnvironment;
|
|
}
|
|
|
|
//otherwise show their prod environment
|
|
const prodEnvironment = environments.find((env) => env.type === "PRODUCTION");
|
|
if (prodEnvironment) {
|
|
return prodEnvironment;
|
|
}
|
|
|
|
throw new Error("No environments found");
|
|
}
|
|
}
|