UNPKG

sysrot-hub

Version:

CLI de nueva generación para proyectos Next.js 14+ con IA multi-modelo, Web3 integration, internacionalización completa y roadmap realista 2025-2026

440 lines (382 loc) 11.9 kB
import { NextApiRequest, NextApiResponse } from 'next'; import { PrismaClient } from '@prisma/client'; import { getSession } from 'next-auth/react'; import { z } from 'zod'; const prisma = new PrismaClient(); // Validation schemas const createOrganizationSchema = z.object({ name: z.string().min(1, 'Organization name is required'), slug: z.string().min(1, 'Slug is required').regex(/^[a-z0-9-]+$/, 'Slug can only contain lowercase letters, numbers, and hyphens'), description: z.string().optional(), domain: z.string().optional(), planId: z.string().optional() }); const updateOrganizationSchema = createOrganizationSchema.partial(); const inviteMemberSchema = z.object({ email: z.string().email('Invalid email address'), role: z.enum(['ADMIN', 'MEMBER', 'VIEWER']).default('MEMBER') }); async function getUserMembership(userId: string, organizationId: string) { return await prisma.organizationMember.findUnique({ where: { organizationId_userId: { organizationId, userId } }, include: { organization: true, user: true } }); } async function hasPermission(userId: string, organizationId: string, requiredRole: string[] = ['OWNER', 'ADMIN']) { const membership = await getUserMembership(userId, organizationId); return membership && requiredRole.includes(membership.role); } export default async function handler(req: NextApiRequest, res: NextApiResponse) { try { const session = await getSession({ req }); if (!session?.user) { return res.status(401).json({ error: 'Authentication required' }); } const { method, query } = req; const { id } = query; switch (method) { case 'GET': if (id) { return await getOrganization(req, res); } else { return await getUserOrganizations(req, res); } case 'POST': return await createOrganization(req, res); case 'PUT': return await updateOrganization(req, res); case 'DELETE': return await deleteOrganization(req, res); default: res.setHeader('Allow', ['GET', 'POST', 'PUT', 'DELETE']); return res.status(405).end('Method Not Allowed'); } } catch (error) { console.error('Organizations API error:', error); return res.status(500).json({ error: 'Internal server error' }); } finally { await prisma.$disconnect(); } } async function getUserOrganizations(req: NextApiRequest, res: NextApiResponse) { const session = await getSession({ req }); try { const memberships = await prisma.organizationMember.findMany({ where: { userId: session.user.id }, include: { organization: { include: { plan: true, subscription: true, _count: { select: { members: true, activities: true } } } } }, orderBy: { joinedAt: 'desc' } }); const organizations = memberships.map(membership => ({ ...membership.organization, role: membership.role, joinedAt: membership.joinedAt, memberCount: membership.organization._count.members, activityCount: membership.organization._count.activities })); res.status(200).json({ organizations }); } catch (error) { console.error('Error fetching user organizations:', error); res.status(500).json({ error: 'Failed to fetch organizations' }); } } async function getOrganization(req: NextApiRequest, res: NextApiResponse) { const session = await getSession({ req }); const { id } = req.query; if (!id || typeof id !== 'string') { return res.status(400).json({ error: 'Organization ID is required' }); } try { // Check if user has access to this organization const membership = await getUserMembership(session.user.id, id); if (!membership) { return res.status(403).json({ error: 'Access denied' }); } const organization = await prisma.organization.findUnique({ where: { id }, include: { plan: true, subscription: true, members: { include: { user: { select: { id: true, name: true, email: true, image: true } } }, orderBy: { joinedAt: 'desc' } }, invitations: { where: { status: 'PENDING' }, include: { inviter: { select: { name: true, email: true } } } }, usage: { orderBy: { createdAt: 'desc' }, take: 12 // Last 12 months }, activities: { include: { user: { select: { name: true, email: true } } }, orderBy: { createdAt: 'desc' }, take: 50 }, _count: { select: { members: true, invitations: true, activities: true } } } }); if (!organization) { return res.status(404).json({ error: 'Organization not found' }); } // Add user's role to response const response = { ...organization, userRole: membership.role, userPermissions: membership.permissions }; res.status(200).json({ organization: response }); } catch (error) { console.error('Error fetching organization:', error); res.status(500).json({ error: 'Failed to fetch organization' }); } } async function createOrganization(req: NextApiRequest, res: NextApiResponse) { const session = await getSession({ req }); try { const validatedData = createOrganizationSchema.parse(req.body); // Check if slug is already taken const existingOrg = await prisma.organization.findUnique({ where: { slug: validatedData.slug } }); if (existingOrg) { return res.status(400).json({ error: 'Slug is already taken' }); } // Get default plan (free tier) const defaultPlan = await prisma.plan.findFirst({ where: { slug: 'starter' }, orderBy: { price: 'asc' } }); // Create organization const organization = await prisma.organization.create({ data: { ...validatedData, planId: validatedData.planId || defaultPlan?.id, status: 'ACTIVE', trialEndsAt: new Date(Date.now() + 14 * 24 * 60 * 60 * 1000) // 14 days trial } }); // Add creator as owner await prisma.organizationMember.create({ data: { organizationId: organization.id, userId: session.user.id, role: 'OWNER', permissions: ['*'] // Full permissions } }); // Create initial usage record const now = new Date(); await prisma.usage.create({ data: { organizationId: organization.id, month: now.getMonth() + 1, year: now.getFullYear(), users: 1 } }); // Log activity await prisma.activity.create({ data: { organizationId: organization.id, userId: session.user.id, action: 'organization_created', metadata: { organizationName: organization.name } } }); res.status(201).json({ organization, message: 'Organization created successfully' }); } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ error: 'Validation failed', details: error.errors }); } console.error('Error creating organization:', error); res.status(500).json({ error: 'Failed to create organization' }); } } async function updateOrganization(req: NextApiRequest, res: NextApiResponse) { const session = await getSession({ req }); const { id } = req.query; if (!id || typeof id !== 'string') { return res.status(400).json({ error: 'Organization ID is required' }); } try { // Check permissions const hasAccess = await hasPermission(session.user.id, id, ['OWNER', 'ADMIN']); if (!hasAccess) { return res.status(403).json({ error: 'Access denied' }); } const validatedData = updateOrganizationSchema.parse(req.body); // If updating slug, check if it's available if (validatedData.slug) { const existingOrg = await prisma.organization.findFirst({ where: { slug: validatedData.slug, NOT: { id } } }); if (existingOrg) { return res.status(400).json({ error: 'Slug is already taken' }); } } const organization = await prisma.organization.update({ where: { id }, data: validatedData, include: { plan: true, subscription: true, _count: { select: { members: true } } } }); // Log activity await prisma.activity.create({ data: { organizationId: id, userId: session.user.id, action: 'organization_updated', metadata: { changes: validatedData } } }); res.status(200).json({ organization, message: 'Organization updated successfully' }); } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ error: 'Validation failed', details: error.errors }); } console.error('Error updating organization:', error); res.status(500).json({ error: 'Failed to update organization' }); } } async function deleteOrganization(req: NextApiRequest, res: NextApiResponse) { const session = await getSession({ req }); const { id } = req.query; if (!id || typeof id !== 'string') { return res.status(400).json({ error: 'Organization ID is required' }); } try { // Check if user is owner const hasAccess = await hasPermission(session.user.id, id, ['OWNER']); if (!hasAccess) { return res.status(403).json({ error: 'Only organization owners can delete organizations' }); } // Check if organization has active subscription const organization = await prisma.organization.findUnique({ where: { id }, include: { subscription: true } }); if (!organization) { return res.status(404).json({ error: 'Organization not found' }); } if (organization.subscription?.status === 'ACTIVE') { return res.status(400).json({ error: 'Cannot delete organization with active subscription. Please cancel subscription first.' }); } // Soft delete - mark as cancelled await prisma.organization.update({ where: { id }, data: { status: 'CANCELLED' } }); // Log activity await prisma.activity.create({ data: { organizationId: id, userId: session.user.id, action: 'organization_deleted', metadata: { organizationName: organization.name } } }); res.status(200).json({ message: 'Organization deleted successfully' }); } catch (error) { console.error('Error deleting organization:', error); res.status(500).json({ error: 'Failed to delete organization' }); } }