templates2.Saas.Nuxt/server/utils/org.ts
w4c-templates d447340474
Some checks failed
CI / quality (push) Has been cancelled
chore(template): publish Saas.Nuxt
2026-10-11 23:25:14 +03:00

115 lines
5.4 KiB
TypeScript

import { z } from 'zod';
import { db } from './db';
import { canManageMembers, canManageBilling, slugifyOrg, withinSeats, type OrgRole } from '#shared/org';
export const createOrgSchema = z.object({ name: z.string().min(2).max(80) });
export const addMemberSchema = z.object({ email: z.string().email(), role: z.enum(['admin', 'member']) });
export async function getActiveOrg(userId: string) {
const membership = await db.membership.findFirst({
where: { userId },
orderBy: { createdAt: 'asc' },
include: { org: { include: { subscription: true } } },
});
if (!membership) return null;
return { org: membership.org, role: membership.role as OrgRole, membershipId: membership.id };
}
export function getUserOrgs(userId: string) {
return db.membership.findMany({ where: { userId }, include: { org: true }, orderBy: { createdAt: 'asc' } });
}
export async function createOrgForUser(userId: string, name: string) {
const base = slugifyOrg(name) || 'org';
let slug = base;
for (let i = 2; await db.organization.findUnique({ where: { slug } }); i++) slug = `${base}-${i}`;
return db.organization.create({
data: {
slug,
name,
ownerId: userId,
memberships: { create: { userId, role: 'owner' } },
subscription: { create: { plan: 'free', status: 'active', provider: 'mock' } },
},
include: { subscription: true },
});
}
export function listMembers(orgId: string) {
return db.membership.findMany({ where: { orgId }, include: { user: true }, orderBy: { createdAt: 'asc' } });
}
export async function getMembership(userId: string, orgId: string) {
const membership = await db.membership.findUnique({
where: { orgId_userId: { orgId, userId } },
include: { org: { include: { subscription: true } } },
});
if (!membership) return null;
return { role: membership.role as OrgRole, org: membership.org, membershipId: membership.id };
}
export async function addMember(orgId: string, actorRole: OrgRole, email: string, role: 'admin' | 'member') {
if (!canManageMembers(actorRole)) return { ok: false as const, error: 'You do not have permission to manage members.' };
const user = await db.user.findUnique({ where: { email: email.toLowerCase() } });
if (!user) return { ok: false as const, error: 'No user with that email. Ask them to sign up first.' };
if (await db.membership.findUnique({ where: { orgId_userId: { orgId, userId: user.id } } })) {
return { ok: false as const, error: 'That user is already a member.' };
}
const [sub, count] = await Promise.all([
db.orgSubscription.findUnique({ where: { orgId } }),
db.membership.count({ where: { orgId } }),
]);
if (!withinSeats(sub?.plan ?? 'free', count)) {
return { ok: false as const, error: `Your plan allows ${count} members. Upgrade to add more.` };
}
const membership = await db.membership.create({ data: { orgId, userId: user.id, role }, include: { user: true } });
return { ok: true as const, membership };
}
export async function updateMemberRole(orgId: string, actorRole: OrgRole, membershipId: string, role: 'admin' | 'member') {
if (!canManageMembers(actorRole)) return { ok: false as const, error: 'Forbidden' };
const target = await db.membership.findUnique({ where: { id: membershipId } });
if (!target || target.orgId !== orgId) return { ok: false as const, error: 'Not found' };
if (target.role === 'owner') return { ok: false as const, error: 'The owner role cannot be changed.' };
const membership = await db.membership.update({ where: { id: membershipId }, data: { role } });
return { ok: true as const, membership };
}
export async function removeMember(orgId: string, actorRole: OrgRole, membershipId: string) {
if (!canManageMembers(actorRole)) return { ok: false as const, error: 'Forbidden' };
const target = await db.membership.findUnique({ where: { id: membershipId } });
if (!target || target.orgId !== orgId) return { ok: false as const, error: 'Not found' };
if (target.role === 'owner') return { ok: false as const, error: 'The owner cannot be removed.' };
await db.membership.delete({ where: { id: membershipId } });
return { ok: true as const };
}
export function getSubscription(orgId: string) {
return db.orgSubscription.findUnique({ where: { orgId } });
}
export function listInvoices(orgId: string) {
return db.invoice.findMany({ where: { orgId }, orderBy: { createdAt: 'desc' }, take: 24 });
}
export async function subscribeOrg(orgId: string, actorRole: OrgRole, plan: 'free' | 'pro', provider: { name: string }) {
if (!canManageBilling(actorRole)) return { ok: false as const, error: 'Only the owner can change the plan.' };
const currentPeriodEnd = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000);
const subscription = await db.orgSubscription.upsert({
where: { orgId },
update: { plan, status: 'active', provider: provider.name, currentPeriodEnd },
create: { orgId, plan, status: 'active', provider: provider.name, currentPeriodEnd },
});
if (plan === 'pro') {
await db.invoice.create({ data: { orgId, amount: 2900, status: provider.name === 'mock' ? 'paid' : 'open' } });
}
return { ok: true as const, subscription };
}
export async function cancelOrg(orgId: string, actorRole: OrgRole) {
if (!canManageBilling(actorRole)) return { ok: false as const, error: 'Only the owner can cancel.' };
const subscription = await db.orgSubscription.update({ where: { orgId }, data: { plan: 'free', status: 'canceled' } });
return { ok: true as const, subscription };
}