import crypto from 'node:crypto'; import { stripTrailingSlash } from '@openpanel/common'; import { hashPassword } from '@openpanel/common/server'; import type { IServiceUser, ProjectType } from '@openpanel/db'; import { db, getId, getOrganizationById, getUserById } from '@openpanel/db'; import { zOnboardingProject } from '@openpanel/validation'; import { addDays } from 'date-fns'; import type { z } from 'zod'; import { createTRPCRouter, protectedProcedure, publicProcedure } from '../trpc'; async function createOrGetOrganization( input: z.infer, user: IServiceUser ) { if (input.organizationId) { return await getOrganizationById(input.organizationId); } const TRIAL_DURATION_IN_DAYS = 30; if (input.organization) { const organization = await db.organization.create({ data: { id: await getId('organization', input.organization), name: input.organization, createdByUserId: user.id, subscriptionEndsAt: addDays(new Date(), TRIAL_DURATION_IN_DAYS), subscriptionStatus: 'trialing', timezone: input.timezone, onboarding: '', }, }); return organization; } return null; } export const onboardingRouter = createTRPCRouter({ skipOnboardingCheck: publicProcedure.query(async ({ ctx }) => { if (!ctx.session.userId) { return { canSkip: false, url: null }; } const members = await db.member.findMany({ where: { userId: ctx.session.userId, }, }); if (members.length > 0) { return { canSkip: true, }; } const projectAccess = await db.projectAccess.findMany({ where: { userId: ctx.session.userId, }, }); if (projectAccess.length > 0) { return { canSkip: true, }; } return { canSkip: false }; }), project: protectedProcedure .input(zOnboardingProject) .mutation(async ({ input, ctx }) => { const types: ProjectType[] = []; if (input.website) { types.push('website'); } if (input.app) { types.push('app'); } if (input.backend) { types.push('backend'); } const user = await getUserById(ctx.session.userId); const organization = await createOrGetOrganization(input, user); if (!organization?.id) { throw new Error('Organization slug is missing'); } if (input.organization) { await db.member.create({ data: { email: user.email, organizationId: organization.id, role: 'org:admin', userId: user.id, }, }); } if (input.cors.length === 0 && input.website) { input.cors.push('*'); } const project = await db.project.create({ data: { id: await getId('project', input.project), name: input.project, organizationId: organization.id, types, domain: input.domain ? stripTrailingSlash(input.domain) : null, cors: input.cors.map((c) => stripTrailingSlash(c)), }, }); const secret = `sec_${crypto.randomBytes(10).toString('hex')}`; const client = await db.client.create({ data: { name: `${project.name} Client`, organizationId: organization.id, projectId: project.id, type: 'write', secret: await hashPassword(secret), }, }); return { ...client, secret, }; }), });