UNPKG

skailan-core

Version:

Servicio de autenticación y multitenancy para Skailan.

356 lines 11.7 kB
import Joi from "joi"; export class ProductService { static instance; prisma; constructor(prisma) { this.prisma = prisma; } static getInstance(prisma) { if (!ProductService.instance) { ProductService.instance = new ProductService(prisma); } return ProductService.instance; } /** * Crea un nuevo plan */ async createPlan(request) { await this.validateCreatePlanRequest(request); const plan = await this.prisma.plan.create({ data: { name: request.name, description: request.description, price: request.price, type: request.type, interval: request.interval, trialDays: request.trialDays, maxUsers: request.maxUsers, maxProjects: request.maxProjects, features: request.features, isActive: request.isActive ?? true, }, include: { planFeatures: { include: { feature: true, }, }, }, }); return plan; } /** * Actualiza un plan existente */ async updatePlan(planId, request) { await this.validateUpdatePlanRequest(request); const plan = await this.prisma.plan.update({ where: { id: planId }, data: { ...(request.name && { name: request.name }), ...(request.description && { description: request.description }), ...(request.price !== undefined && { price: request.price }), ...(request.type && { type: request.type }), ...(request.interval && { interval: request.interval }), ...(request.trialDays !== undefined && { trialDays: request.trialDays, }), ...(request.maxUsers !== undefined && { maxUsers: request.maxUsers }), ...(request.maxProjects !== undefined && { maxProjects: request.maxProjects, }), ...(request.features && { features: request.features }), ...(request.isActive !== undefined && { isActive: request.isActive }), }, include: { planFeatures: { include: { feature: true, }, }, }, }); return plan; } /** * Obtiene un plan por ID */ async getPlan(planId) { const plan = await this.prisma.plan.findUnique({ where: { id: planId }, include: { planFeatures: { include: { feature: true, }, }, }, }); return plan; } /** * Lista todos los planes activos */ async listActivePlans() { const plans = await this.prisma.plan.findMany({ where: { isActive: true }, include: { planFeatures: { include: { feature: true, }, }, }, orderBy: { price: "asc" }, }); return plans; } /** * Lista todos los planes (activos e inactivos) */ async listAllPlans() { const plans = await this.prisma.plan.findMany({ include: { planFeatures: { include: { feature: true, }, }, }, orderBy: { createdAt: "desc" }, }); return plans; } /** * Elimina un plan (soft delete) */ async deletePlan(planId) { // Verificar que no hay suscripciones activas con este plan const activeSubscriptions = await this.prisma.subscription.count({ where: { planId, status: "ACTIVE", }, }); if (activeSubscriptions > 0) { throw new Error("No se puede eliminar un plan con suscripciones activas"); } await this.prisma.plan.update({ where: { id: planId }, data: { isActive: false }, }); } /** * Crea una nueva característica */ async createFeature(request) { await this.validateFeatureRequest(request); const feature = await this.prisma.feature.create({ data: { name: request.name, description: request.description, code: request.code, isActive: request.isActive ?? true, }, }); return feature; } /** * Asigna características a un plan */ async assignFeaturesToPlan(planId, featureIds) { // Verificar que el plan existe const plan = await this.prisma.plan.findUnique({ where: { id: planId }, }); if (!plan) { throw new Error("Plan no encontrado"); } // Eliminar asignaciones existentes await this.prisma.planFeature.deleteMany({ where: { planId }, }); // Crear nuevas asignaciones if (featureIds.length > 0) { await this.prisma.planFeature.createMany({ data: featureIds.map((featureId) => ({ planId, featureId, })), }); } } /** * Obtiene las características de un plan */ async getPlanFeatures(planId) { const planFeatures = await this.prisma.planFeature.findMany({ where: { planId }, include: { feature: true, }, }); return planFeatures.map((pf) => pf.feature); } /** * Lista todas las características activas */ async listActiveFeatures() { return await this.prisma.feature.findMany({ where: { isActive: true }, orderBy: { name: "asc" }, }); } /** * Obtiene estadísticas de planes */ async getPlanStats() { const [totalPlans, activePlans, totalFeatures, activeFeatures] = await Promise.all([ this.prisma.plan.count(), this.prisma.plan.count({ where: { isActive: true } }), this.prisma.feature.count(), this.prisma.feature.count({ where: { isActive: true } }), ]); const planTypes = await this.prisma.plan.groupBy({ by: ["type"], _count: { type: true }, }); return { totalPlans, activePlans, totalFeatures, activeFeatures, planTypes, }; } /** * Verifica si un plan tiene una característica específica */ async planHasFeature(planId, featureCode) { const planFeature = await this.prisma.planFeature.findFirst({ where: { planId, feature: { code: featureCode, isActive: true, }, }, include: { feature: true, }, }); return !!planFeature; } /** * Obtiene el plan gratuito por defecto */ async getFreePlan() { const plan = await this.prisma.plan.findFirst({ where: { type: "FREE", isActive: true, }, include: { planFeatures: { include: { feature: true, }, }, }, }); return plan; } /** * Valida la solicitud de creación de plan */ async validateCreatePlanRequest(request) { const schema = Joi.object({ name: Joi.string().min(1).max(100).required(), description: Joi.string().min(1).max(500).required(), price: Joi.number().min(0).required(), type: Joi.string() .valid("FREE", "TRIAL", "BASIC", "PRO", "ENTERPRISE") .required(), interval: Joi.string() .valid("MONTHLY", "YEARLY", "WEEKLY", "DAILY") .required(), trialDays: Joi.number().min(0).max(365).required(), maxUsers: Joi.number().min(1).optional(), maxProjects: Joi.number().min(1).optional(), features: Joi.object().optional(), isActive: Joi.boolean().optional(), }); const { error } = schema.validate(request); if (error) { throw new Error(`Datos de plan inválidos: ${error.message}`); } // Verificar que no existe un plan con el mismo nombre const existingPlan = await this.prisma.plan.findFirst({ where: { name: request.name }, }); if (existingPlan) { throw new Error("Ya existe un plan con ese nombre"); } } /** * Valida la solicitud de actualización de plan */ async validateUpdatePlanRequest(request) { const schema = Joi.object({ name: Joi.string().min(1).max(100).optional(), description: Joi.string().min(1).max(500).optional(), price: Joi.number().min(0).optional(), type: Joi.string() .valid("FREE", "TRIAL", "BASIC", "PRO", "ENTERPRISE") .optional(), interval: Joi.string() .valid("MONTHLY", "YEARLY", "WEEKLY", "DAILY") .optional(), trialDays: Joi.number().min(0).max(365).optional(), maxUsers: Joi.number().min(1).optional(), maxProjects: Joi.number().min(1).optional(), features: Joi.object().optional(), isActive: Joi.boolean().optional(), }); const { error } = schema.validate(request); if (error) { throw new Error(`Datos de plan inválidos: ${error.message}`); } if (request.name) { // Verificar que no existe otro plan con el mismo nombre const existingPlan = await this.prisma.plan.findFirst({ where: { name: request.name, // Excluir el plan actual id: { not: request.name }, // Esto no es correcto, necesitaríamos el ID del plan }, }); if (existingPlan) { throw new Error("Ya existe un plan con ese nombre"); } } } /** * Valida la solicitud de característica */ async validateFeatureRequest(request) { const schema = Joi.object({ name: Joi.string().min(1).max(100).required(), description: Joi.string().min(1).max(500).required(), code: Joi.string().min(1).max(50).required(), isActive: Joi.boolean().optional(), }); const { error } = schema.validate(request); if (error) { throw new Error(`Datos de característica inválidos: ${error.message}`); } // Verificar que no existe una característica con el mismo código const existingFeature = await this.prisma.feature.findFirst({ where: { code: request.code }, }); if (existingFeature) { throw new Error("Ya existe una característica con ese código"); } } } //# sourceMappingURL=ProductService.js.map