skailan-core
Version:
Servicio de autenticación y multitenancy para Skailan.
176 lines (175 loc) • 6.88 kB
JavaScript
const JWT_SECRET = process.env.JWT_SECRET || "skailan-secret";
export class OrganizationService {
static async createOrganization(prisma, userId, name, slug, domain, subdomain) {
// Inicializar trial de 14 días
const now = new Date();
const trialEnd = new Date(now.getTime() + 14 * 24 * 60 * 60 * 1000);
const org = await prisma.organization.create({
data: {
name,
slug,
domain,
subdomain,
trialStart: now,
trialEnd: trialEnd,
isBlocked: false,
},
});
await prisma.membership.create({
data: { userId, organizationId: org.id, role: "OWNER", status: "ACTIVE" },
});
// Función auxiliar para provisionar el esquema del tenant
await this.provisionTenantSchema(prisma, org.slug);
return org;
}
// Función para provisionar el esquema del tenant
static async provisionTenantSchema(prisma, organizationSlug) {
const schemaName = `org_${organizationSlug}`;
const productTableSql = `
CREATE TABLE "${schemaName}"."products" (
"id" TEXT NOT NULL,
"name" TEXT NOT NULL,
"description" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "products_pkey" PRIMARY KEY ("id")
);
`;
try {
await prisma.$executeRawUnsafe(`CREATE SCHEMA IF NOT EXISTS "${schemaName}";`);
await prisma.$executeRawUnsafe(productTableSql);
console.log(`Esquema y tablas provisionadas para el tenant: ${schemaName}`);
}
catch (error) {
console.error(`Error provisionando el esquema para ${schemaName}:`, error);
throw error; // Re-lanzar el error para que la transacción de creación de la organización falle
}
}
static async listOrganizations(prisma, userId, page, limit) {
const skip = (page - 1) * limit;
const [memberships, total] = await Promise.all([
prisma.membership.findMany({
where: { userId },
include: { organization: true },
skip,
take: limit,
}),
prisma.membership.count({ where: { userId } }),
]);
const organizations = memberships
.map((m) => m.organization)
.filter(Boolean);
return {
data: organizations,
meta: {
total,
page,
limit,
totalPages: Math.ceil(total / limit),
},
};
}
static async getOrganization(prisma, userId, organizationId) {
const membership = await prisma.membership.findUnique({
where: { userId_organizationId: { userId, organizationId } },
include: { organization: true },
});
if (!membership) {
throw new Error("No tienes acceso a esta organización.");
}
return membership.organization;
}
static async updateOrganization(prisma, userId, organizationId, name, domain, subdomain) {
const membership = await prisma.membership.findUnique({
where: { userId_organizationId: { userId, organizationId } },
});
if (!membership || membership.role !== "OWNER") {
throw new Error("Solo el propietario puede editar.");
}
const org = await prisma.organization.update({
where: { id: organizationId },
data: { name, domain, subdomain },
});
return org;
}
static async deleteOrganization(prisma, userId, organizationId) {
const membership = await prisma.membership.findUnique({
where: { userId_organizationId: { userId, organizationId } },
});
if (!membership || membership.role !== "OWNER") {
throw new Error("Solo el propietario puede eliminar.");
}
const members = await prisma.membership.count({
where: { organizationId },
});
if (members > 1) {
throw new Error("No se puede eliminar una organización con más de un miembro.");
}
await prisma.organization.update({
where: { id: organizationId },
data: { deletedAt: new Date() },
});
}
static async getOrganizationSettings(prisma, userId, organizationId) {
const membership = await prisma.membership.findUnique({
where: { userId_organizationId: { userId, organizationId } },
});
if (!membership) {
throw new Error("No tienes acceso a esta organización.");
}
const org = await prisma.organization.findUnique({
where: { id: organizationId },
});
if (!org) {
throw new Error("Organización no encontrada.");
}
return { settings: org.settings || {} };
}
static async updateOrganizationSettings(prisma, userId, organizationId, settings) {
const membership = await prisma.membership.findUnique({
where: { userId_organizationId: { userId, organizationId } },
});
if (!membership || membership.role !== "OWNER") {
throw new Error("Solo el propietario puede modificar los settings.");
}
const org = await prisma.organization.update({
where: { id: organizationId },
data: { settings },
});
return { settings: org.settings };
}
static async getOrganizationStatus(prisma, userId, organizationId) {
const membership = await prisma.membership.findUnique({
where: { userId_organizationId: { userId, organizationId } },
});
if (!membership) {
throw new Error("No tienes acceso a esta organización.");
}
const org = await prisma.organization.findUnique({
where: { id: organizationId },
include: {
Subscription: { where: { status: "ACTIVE" }, include: { plan: true } },
},
});
if (!org) {
throw new Error("Organización no encontrada.");
}
let trialDaysLeft = null;
if (org.trialEnd) {
const now = new Date();
const diff = org.trialEnd.getTime() - now.getTime();
trialDaysLeft = Math.max(0, Math.ceil(diff / (1000 * 60 * 60 * 24)));
}
const activePlan = org.Subscription && org.Subscription.length > 0
? org.Subscription[0]?.plan || null
: null;
return {
trialStart: org.trialStart,
trialEnd: org.trialEnd,
trialDaysLeft,
isBlocked: org.isBlocked,
activePlan,
};
}
}
//# sourceMappingURL=OrganizationService.js.map