archetecture security fix

This commit is contained in:
root
2026-06-11 03:22:12 -04:00
parent 6def9993da
commit 9483750161
3126 changed files with 177194 additions and 37211 deletions
+69
View File
@@ -0,0 +1,69 @@
export declare function listBillingAccounts(query: {
q?: string;
status?: string;
plan?: string;
page: number;
pageSize: number;
}): Promise<{
data: any[];
total: any;
stats: {
billingAccountCount: any;
openInvoiceCount: any;
pastDueInvoiceCount: any;
paidInvoiceCount: any;
accountsReceivableTotal: any;
recognizedRevenueTotal: any;
};
}>;
export declare function getBillingAccountDetail(companyId: string): Promise<any>;
export declare function updateBillingAccount(billingAccountId: string, data: {
legalName?: string;
billingEmail?: string;
billingAddress?: any;
taxId?: string | null;
taxExempt?: boolean;
invoiceTerms?: string;
netTermsDays?: number;
}, adminId: string, ip?: string): Promise<any>;
export declare function setDunningPaused(billingAccountId: string, paused: boolean, adminId: string, ip?: string): Promise<any>;
export declare function createDraftInvoice(billingAccountId: string, data: {
subscriptionId?: string | null;
invoiceType: string;
currency?: string;
dueAt?: string | null;
isSubscriptionBlocking?: boolean;
adminReason?: string | null;
lineItems: Array<{
type: string;
description: string;
quantity: number;
unitAmount: number;
periodStart?: string | null;
periodEnd?: string | null;
}>;
}, adminId: string, ip?: string): Promise<any>;
export declare function finalizeInvoice(invoiceId: string, adminId: string, ip?: string): Promise<any>;
export declare function payInvoice(invoiceId: string, data: {
amount?: number;
paymentMethodId?: string | null;
providerPaymentId?: string | null;
}, adminId: string, ip?: string): Promise<any>;
export declare function retryInvoicePayment(invoiceId: string, data: {
paymentMethodId?: string | null;
}, adminId: string, ip?: string): Promise<any>;
export declare function voidInvoice(invoiceId: string, reason: string, adminId: string, ip?: string): Promise<any>;
export declare function markInvoiceUncollectible(invoiceId: string, reason: string, adminId: string, ip?: string): Promise<any>;
export declare function issueCreditNote(invoiceId: string, data: {
amount: number;
reason: string;
}, adminId: string, ip?: string): Promise<any>;
export declare function issueRefund(invoiceId: string, data: {
amount: number;
reason: string;
}, adminId: string, ip?: string): Promise<any>;
export declare function getInvoicePdf(invoiceId: string): Promise<{
pdfBuffer: Buffer<ArrayBufferLike>;
invoiceNumber: any;
}>;
//# sourceMappingURL=admin.billing.service.d.ts.map
@@ -0,0 +1 @@
{"version":3,"file":"admin.billing.service.d.ts","sourceRoot":"","sources":["../../../src/modules/admin/admin.billing.service.ts"],"names":[],"mappings":"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"}
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+13
View File
@@ -0,0 +1,13 @@
export declare function presentAdminUser<T extends Record<string, any>>(admin: T): Omit<T, "passwordHash" | "totpSecret">;
export declare function presentAdminSession<T extends Record<string, any>>(admin: T, token: string): {
token: string;
admin: Omit<T, "passwordHash" | "totpSecret">;
};
export declare function presentPaginated<T>(data: T[], total: number, page: number, pageSize: number, extra?: Record<string, unknown>): {
data: T[];
total: number;
page: number;
pageSize: number;
totalPages: number;
};
//# sourceMappingURL=admin.presenter.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"admin.presenter.d.ts","sourceRoot":"","sources":["../../../src/modules/admin/admin.presenter.ts"],"names":[],"mappings":"AAAA,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC,0CAGvE;AAED,wBAAgB,mBAAmB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM;;;EAKzF;AAED,wBAAgB,gBAAgB,CAAC,CAAC,EAChC,IAAI,EAAE,CAAC,EAAE,EACT,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,KAAK,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM;;;;;;EAUpC"}
+26
View File
@@ -0,0 +1,26 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.presentAdminUser = presentAdminUser;
exports.presentAdminSession = presentAdminSession;
exports.presentPaginated = presentPaginated;
function presentAdminUser(admin) {
const { passwordHash, totpSecret, ...safe } = admin;
return safe;
}
function presentAdminSession(admin, token) {
return {
token,
admin: presentAdminUser(admin),
};
}
function presentPaginated(data, total, page, pageSize, extra = {}) {
return {
data,
total,
page,
pageSize,
totalPages: Math.ceil(total / pageSize),
...extra,
};
}
//# sourceMappingURL=admin.presenter.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"admin.presenter.js","sourceRoot":"","sources":["../../../src/modules/admin/admin.presenter.ts"],"names":[],"mappings":";;AAAA,4CAGC;AAED,kDAKC;AAED,4CAeC;AA3BD,SAAgB,gBAAgB,CAAgC,KAAQ;IACtE,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,GAAG,IAAI,EAAE,GAAG,KAAK,CAAA;IACnD,OAAO,IAAI,CAAA;AACb,CAAC;AAED,SAAgB,mBAAmB,CAAgC,KAAQ,EAAE,KAAa;IACxF,OAAO;QACL,KAAK;QACL,KAAK,EAAE,gBAAgB,CAAC,KAAK,CAAC;KAC/B,CAAA;AACH,CAAC;AAED,SAAgB,gBAAgB,CAC9B,IAAS,EACT,KAAa,EACb,IAAY,EACZ,QAAgB,EAChB,QAAiC,EAAE;IAEnC,OAAO;QACL,IAAI;QACJ,KAAK;QACL,IAAI;QACJ,QAAQ;QACR,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,QAAQ,CAAC;QACvC,GAAG,KAAK;KACT,CAAA;AACH,CAAC"}
+161
View File
@@ -0,0 +1,161 @@
export declare function findAdminByEmail(email: string): any;
export declare function findAdminByIdOrThrow(id: string): any;
export declare function updateAdminLastLogin(id: string): any;
export declare function updateAdminTotpSecret(id: string, secret: string): any;
export declare function enableAdminTotp(id: string): any;
export declare function replaceAdminRecoveryCodes(adminUserId: string, codeHashes: string[]): Promise<any>;
export declare function listUnusedAdminRecoveryCodes(adminUserId: string): any;
export declare function markAdminRecoveryCodeUsed(id: string): any;
export declare function setAdminPasswordReset(id: string, token: string, expiresAt: Date): any;
export declare function findAdminByResetToken(token: string): any;
export declare function updateAdminPassword(id: string, passwordHash: string): any;
export declare function createAuditLog(data: Record<string, unknown>): any;
export declare function listCompaniesPage(query: {
q?: string;
status?: string;
plan?: string;
page: number;
pageSize: number;
}): Promise<{
data: any;
total: any;
}>;
export declare function getCompanyDetail(id: string): any;
export declare function getCompanyUpdateSnapshot(id: string): any;
export declare function applyCompanyUpdate(id: string, body: any, current: {
name: string;
slug: string;
address?: unknown;
brand?: {
paymentMethodsEnabled?: any[] | null;
} | null;
}): Promise<any>;
export declare function updateCompanyStatus(id: string, status: string): any;
export declare function deleteCompany(id: string): any;
export declare function getCompanyForImpersonation(id: string): any;
export declare function listRentersPage(query: {
q?: string;
blocked?: string;
page: number;
pageSize: number;
}): Promise<{
data: any;
total: any;
}>;
export declare function updateRenterActive(id: string, isActive: boolean): any;
export declare function getPlatformMetricCounts(): Promise<{
totalCompanies: any;
activeCompanies: any;
trialingCompanies: any;
suspendedCompanies: any;
totalRenters: any;
totalReservations: any;
}>;
export declare function listAuditLogsPage(query: {
adminId?: string;
action?: string;
companyId?: string;
entityId?: string;
page: number;
pageSize: number;
}): Promise<{
data: any;
total: any;
}>;
export declare function listAdmins(): any;
export declare function createAdmin(data: {
email: string;
firstName: string;
lastName: string;
role: string;
passwordHash: string;
permissions?: any[];
}): any;
export declare function updateAdminRole(id: string, role: string): any;
export declare function updateAdmin(id: string, data: {
email?: string;
firstName?: string;
lastName?: string;
role?: string;
passwordHash?: string;
isActive?: boolean;
}): any;
export declare function replaceAdminPermissions(id: string, permissions: any[]): Promise<any>;
export declare function listBillingPage(query: {
status?: string;
plan?: string;
page: number;
pageSize: number;
}): Promise<{
data: any;
total: any;
}>;
export declare function listActiveSubscriptionsForMrr(): any;
export declare function getBillingStatusCounts(): Promise<{
activeCount: any;
trialingCount: any;
pastDueCount: any;
cancelledCount: any;
}>;
export declare function listCompanyInvoicesPage(companyId: string, query: {
page: number;
pageSize: number;
}): Promise<{
data: any;
total: any;
}>;
export declare function getInvoicePdfRecord(invoiceId: string): any;
export declare function listPricingConfigs(): any;
export declare function upsertPricingConfig(plan: string, billingPeriod: string, amount: number, updatedBy?: string): any;
export declare function listPlanFeatures(): any;
export declare function createPlanFeature(data: {
plan: 'STARTER' | 'GROWTH' | 'PRO';
label: string;
sortOrder?: number;
}): any;
export declare function updatePlanFeature(id: string, data: Partial<{
plan: 'STARTER' | 'GROWTH' | 'PRO';
label: string;
sortOrder: number;
}>): any;
export declare function deletePlanFeature(id: string): any;
export declare function listPromotions(): any;
export declare function createPromotion(data: {
code: string;
name: string;
description?: string | null;
discountType: string;
discountValue: number;
plans: string[];
periods: string[];
maxUses?: number | null;
validFrom: string;
validUntil?: string | null;
isActive: boolean;
createdBy?: string | null;
}): any;
export declare function updatePromotion(id: string, data: Partial<{
code: string;
name: string;
description: string | null;
discountType: string;
discountValue: number;
plans: string[];
periods: string[];
maxUses: number | null;
validFrom: string;
validUntil: string | null;
isActive: boolean;
}>): any;
export declare function deletePromotion(id: string): any;
export declare function listNotificationsPage(query: {
channel?: string;
status?: string;
companyId?: string;
page: number;
pageSize: number;
}): Promise<{
data: any;
total: any;
}>;
//# sourceMappingURL=admin.repo.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"admin.repo.d.ts","sourceRoot":"","sources":["../../../src/modules/admin/admin.repo.ts"],"names":[],"mappings":"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"}
+523
View File
@@ -0,0 +1,523 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.findAdminByEmail = findAdminByEmail;
exports.findAdminByIdOrThrow = findAdminByIdOrThrow;
exports.updateAdminLastLogin = updateAdminLastLogin;
exports.updateAdminTotpSecret = updateAdminTotpSecret;
exports.enableAdminTotp = enableAdminTotp;
exports.replaceAdminRecoveryCodes = replaceAdminRecoveryCodes;
exports.listUnusedAdminRecoveryCodes = listUnusedAdminRecoveryCodes;
exports.markAdminRecoveryCodeUsed = markAdminRecoveryCodeUsed;
exports.setAdminPasswordReset = setAdminPasswordReset;
exports.findAdminByResetToken = findAdminByResetToken;
exports.updateAdminPassword = updateAdminPassword;
exports.createAuditLog = createAuditLog;
exports.listCompaniesPage = listCompaniesPage;
exports.getCompanyDetail = getCompanyDetail;
exports.getCompanyUpdateSnapshot = getCompanyUpdateSnapshot;
exports.applyCompanyUpdate = applyCompanyUpdate;
exports.updateCompanyStatus = updateCompanyStatus;
exports.deleteCompany = deleteCompany;
exports.getCompanyForImpersonation = getCompanyForImpersonation;
exports.listRentersPage = listRentersPage;
exports.updateRenterActive = updateRenterActive;
exports.getPlatformMetricCounts = getPlatformMetricCounts;
exports.listAuditLogsPage = listAuditLogsPage;
exports.listAdmins = listAdmins;
exports.createAdmin = createAdmin;
exports.updateAdminRole = updateAdminRole;
exports.updateAdmin = updateAdmin;
exports.replaceAdminPermissions = replaceAdminPermissions;
exports.listBillingPage = listBillingPage;
exports.listActiveSubscriptionsForMrr = listActiveSubscriptionsForMrr;
exports.getBillingStatusCounts = getBillingStatusCounts;
exports.listCompanyInvoicesPage = listCompanyInvoicesPage;
exports.getInvoicePdfRecord = getInvoicePdfRecord;
exports.listPricingConfigs = listPricingConfigs;
exports.upsertPricingConfig = upsertPricingConfig;
exports.listPlanFeatures = listPlanFeatures;
exports.createPlanFeature = createPlanFeature;
exports.updatePlanFeature = updatePlanFeature;
exports.deletePlanFeature = deletePlanFeature;
exports.listPromotions = listPromotions;
exports.createPromotion = createPromotion;
exports.updatePromotion = updatePromotion;
exports.deletePromotion = deletePromotion;
exports.listNotificationsPage = listNotificationsPage;
const prisma_1 = require("../../lib/prisma");
const companyListInclude = {
brand: { select: { displayName: true, logoUrl: true, subdomain: true } },
contractSettings: { select: { legalName: true } },
subscription: { select: { plan: true, status: true } },
_count: { select: { employees: true, vehicles: true } },
};
const companyDetailInclude = {
brand: true,
contractSettings: true,
accountingSettings: true,
subscription: { include: { invoices: { orderBy: { createdAt: 'desc' }, take: 10 } } },
employees: true,
_count: { select: { employees: true, vehicles: true, customers: true, reservations: true } },
};
const billingInclude = {
company: { select: { id: true, name: true, email: true, slug: true, status: true } },
invoices: {
select: { id: true, amount: true, currency: true, status: true, paidAt: true, createdAt: true },
orderBy: { createdAt: 'desc' },
take: 5,
},
_count: { select: { invoices: true } },
};
function parseOptionalDate(value) {
if (!value)
return null;
return new Date(value);
}
function findAdminByEmail(email) {
return prisma_1.prisma.adminUser.findFirst({
where: {
email: {
equals: email,
mode: 'insensitive',
},
},
});
}
function findAdminByIdOrThrow(id) {
return prisma_1.prisma.adminUser.findUniqueOrThrow({ where: { id } });
}
function updateAdminLastLogin(id) {
return prisma_1.prisma.adminUser.update({ where: { id }, data: { lastLoginAt: new Date() } });
}
function updateAdminTotpSecret(id, secret) {
return prisma_1.prisma.adminUser.update({ where: { id }, data: { totpSecret: secret } });
}
function enableAdminTotp(id) {
return prisma_1.prisma.adminUser.update({ where: { id }, data: { totpEnabled: true } });
}
async function replaceAdminRecoveryCodes(adminUserId, codeHashes) {
return prisma_1.prisma.$transaction(async (tx) => {
await tx.adminRecoveryCode.deleteMany({ where: { adminUserId } });
await tx.adminRecoveryCode.createMany({
data: codeHashes.map((codeHash) => ({ adminUserId, codeHash })),
});
});
}
function listUnusedAdminRecoveryCodes(adminUserId) {
return prisma_1.prisma.adminRecoveryCode.findMany({
where: { adminUserId, usedAt: null },
select: { id: true, codeHash: true },
orderBy: { createdAt: 'asc' },
});
}
function markAdminRecoveryCodeUsed(id) {
return prisma_1.prisma.adminRecoveryCode.update({ where: { id }, data: { usedAt: new Date() } });
}
function setAdminPasswordReset(id, token, expiresAt) {
return prisma_1.prisma.adminUser.update({
where: { id },
data: { passwordResetToken: token, passwordResetExpiresAt: expiresAt },
});
}
function findAdminByResetToken(token) {
return prisma_1.prisma.adminUser.findFirst({
where: {
passwordResetToken: token,
passwordResetExpiresAt: { gt: new Date() },
},
});
}
function updateAdminPassword(id, passwordHash) {
return prisma_1.prisma.adminUser.update({
where: { id },
data: {
passwordHash,
passwordResetToken: null,
passwordResetExpiresAt: null,
},
});
}
function createAuditLog(data) {
return prisma_1.prisma.auditLog.create({ data: data });
}
async function listCompaniesPage(query) {
const where = {};
if (query.status)
where.status = query.status;
if (query.q) {
where.OR = [
{ name: { contains: query.q, mode: 'insensitive' } },
{ email: { contains: query.q, mode: 'insensitive' } },
{ slug: { contains: query.q, mode: 'insensitive' } },
];
}
if (query.plan)
where.subscription = { plan: query.plan };
const [data, total] = await Promise.all([
prisma_1.prisma.company.findMany({
where,
include: companyListInclude,
skip: (query.page - 1) * query.pageSize,
take: query.pageSize,
orderBy: { createdAt: 'desc' },
}),
prisma_1.prisma.company.count({ where }),
]);
return { data, total };
}
function getCompanyDetail(id) {
return prisma_1.prisma.company.findUniqueOrThrow({ where: { id }, include: companyDetailInclude });
}
function getCompanyUpdateSnapshot(id) {
return prisma_1.prisma.company.findUniqueOrThrow({
where: { id },
include: { brand: true, contractSettings: true, accountingSettings: true, subscription: true },
});
}
async function applyCompanyUpdate(id, body, current) {
return prisma_1.prisma.$transaction(async (tx) => {
if (body.company) {
const companyData = { ...body.company };
if (companyData.address && typeof companyData.address === 'object' && !Array.isArray(companyData.address)) {
const baseAddress = current.address && typeof current.address === 'object' && !Array.isArray(current.address)
? current.address
: {};
companyData.address = { ...baseAddress, ...companyData.address };
}
await tx.company.update({ where: { id }, data: companyData });
}
if (body.subscription) {
const sub = body.subscription;
await tx.subscription.upsert({
where: { companyId: id },
update: {
...sub,
trialStartAt: parseOptionalDate(sub.trialStartAt),
trialEndAt: parseOptionalDate(sub.trialEndAt),
currentPeriodStart: parseOptionalDate(sub.currentPeriodStart),
currentPeriodEnd: parseOptionalDate(sub.currentPeriodEnd),
cancelledAt: parseOptionalDate(sub.cancelledAt),
},
create: {
companyId: id,
plan: sub.plan ?? 'STARTER',
billingPeriod: sub.billingPeriod ?? 'MONTHLY',
status: sub.status ?? 'TRIALING',
currency: sub.currency ?? 'MAD',
trialStartAt: parseOptionalDate(sub.trialStartAt),
trialEndAt: parseOptionalDate(sub.trialEndAt),
currentPeriodStart: parseOptionalDate(sub.currentPeriodStart),
currentPeriodEnd: parseOptionalDate(sub.currentPeriodEnd),
cancelledAt: parseOptionalDate(sub.cancelledAt),
cancelAtPeriodEnd: sub.cancelAtPeriodEnd ?? false,
},
});
}
if (body.brand) {
await tx.brandSettings.upsert({
where: { companyId: id },
update: body.brand,
create: {
companyId: id,
displayName: body.brand.displayName ?? current.name,
subdomain: body.brand.subdomain ?? current.slug,
paymentMethodsEnabled: current.brand?.paymentMethodsEnabled ?? [],
...body.brand,
},
});
}
if (body.contractSettings) {
await tx.contractSettings.upsert({
where: { companyId: id },
update: body.contractSettings,
create: { companyId: id, ...body.contractSettings },
});
}
if (body.accountingSettings) {
await tx.accountingSettings.upsert({
where: { companyId: id },
update: body.accountingSettings,
create: { companyId: id, ...body.accountingSettings },
});
}
return tx.company.findUniqueOrThrow({ where: { id }, include: companyDetailInclude });
});
}
function updateCompanyStatus(id, status) {
return prisma_1.prisma.company.update({ where: { id }, data: { status: status } });
}
function deleteCompany(id) {
return prisma_1.prisma.company.delete({ where: { id } });
}
function getCompanyForImpersonation(id) {
return prisma_1.prisma.company.findUniqueOrThrow({
where: { id },
include: { employees: { where: { role: 'OWNER' } } },
});
}
async function listRentersPage(query) {
const where = {};
if (query.blocked !== undefined)
where.isActive = query.blocked === 'false';
if (query.q) {
where.OR = [
{ firstName: { contains: query.q, mode: 'insensitive' } },
{ email: { contains: query.q, mode: 'insensitive' } },
];
}
const [data, total] = await Promise.all([
prisma_1.prisma.renter.findMany({
where,
select: {
id: true,
firstName: true,
lastName: true,
email: true,
phone: true,
isActive: true,
createdAt: true,
_count: { select: { reservations: true } },
},
skip: (query.page - 1) * query.pageSize,
take: query.pageSize,
orderBy: { createdAt: 'desc' },
}),
prisma_1.prisma.renter.count({ where }),
]);
return { data, total };
}
function updateRenterActive(id, isActive) {
return prisma_1.prisma.renter.update({ where: { id }, data: { isActive } });
}
async function getPlatformMetricCounts() {
const [totalCompanies, activeCompanies, trialingCompanies, suspendedCompanies, totalRenters, totalReservations,] = await Promise.all([
prisma_1.prisma.company.count(),
prisma_1.prisma.company.count({ where: { status: 'ACTIVE' } }),
prisma_1.prisma.company.count({ where: { status: 'TRIALING' } }),
prisma_1.prisma.company.count({ where: { status: 'SUSPENDED' } }),
prisma_1.prisma.renter.count(),
prisma_1.prisma.reservation.count(),
]);
return {
totalCompanies,
activeCompanies,
trialingCompanies,
suspendedCompanies,
totalRenters,
totalReservations,
};
}
async function listAuditLogsPage(query) {
const where = {};
if (query.adminId)
where.adminUserId = query.adminId;
if (query.action)
where.action = { contains: query.action };
if (query.companyId)
where.companyId = query.companyId;
if (query.entityId)
where.resourceId = query.entityId;
const [data, total] = await Promise.all([
prisma_1.prisma.auditLog.findMany({
where,
include: { adminUser: { select: { firstName: true, lastName: true, email: true } } },
skip: (query.page - 1) * query.pageSize,
take: query.pageSize,
orderBy: { createdAt: 'desc' },
}),
prisma_1.prisma.auditLog.count({ where }),
]);
return { data, total };
}
function listAdmins() {
return prisma_1.prisma.adminUser.findMany({
include: { permissions: true },
});
}
function createAdmin(data) {
return prisma_1.prisma.adminUser.create({
data: {
email: data.email,
firstName: data.firstName,
lastName: data.lastName,
role: data.role,
passwordHash: data.passwordHash,
permissions: data.permissions ? { create: data.permissions } : undefined,
},
include: { permissions: true },
});
}
function updateAdminRole(id, role) {
return prisma_1.prisma.adminUser.update({ where: { id }, data: { role: role } });
}
function updateAdmin(id, data) {
return prisma_1.prisma.adminUser.update({
where: { id },
data: {
...(data.email !== undefined ? { email: data.email } : {}),
...(data.firstName !== undefined ? { firstName: data.firstName } : {}),
...(data.lastName !== undefined ? { lastName: data.lastName } : {}),
...(data.role !== undefined ? { role: data.role } : {}),
...(data.passwordHash !== undefined ? { passwordHash: data.passwordHash } : {}),
...(data.isActive !== undefined ? { isActive: data.isActive } : {}),
},
include: { permissions: true },
});
}
async function replaceAdminPermissions(id, permissions) {
await prisma_1.prisma.adminUser.findUniqueOrThrow({ where: { id } });
await prisma_1.prisma.$transaction([
prisma_1.prisma.adminPermission.deleteMany({ where: { adminUserId: id } }),
prisma_1.prisma.adminPermission.createMany({
data: permissions.map((permission) => ({
adminUserId: id,
resource: permission.resource,
actions: permission.actions,
})),
}),
]);
return prisma_1.prisma.adminUser.findUniqueOrThrow({ where: { id }, include: { permissions: true } });
}
async function listBillingPage(query) {
const where = {};
if (query.status)
where.status = query.status;
if (query.plan)
where.plan = query.plan;
const [data, total] = await Promise.all([
prisma_1.prisma.subscription.findMany({
where,
include: billingInclude,
skip: (query.page - 1) * query.pageSize,
take: query.pageSize,
orderBy: { createdAt: 'desc' },
}),
prisma_1.prisma.subscription.count({ where }),
]);
return { data, total };
}
function listActiveSubscriptionsForMrr() {
return prisma_1.prisma.subscription.findMany({
where: { status: { in: ['ACTIVE', 'TRIALING'] } },
select: { plan: true, billingPeriod: true },
});
}
async function getBillingStatusCounts() {
const [activeCount, trialingCount, pastDueCount, cancelledCount] = await Promise.all([
prisma_1.prisma.subscription.count({ where: { status: 'ACTIVE' } }),
prisma_1.prisma.subscription.count({ where: { status: 'TRIALING' } }),
prisma_1.prisma.subscription.count({ where: { status: 'PAST_DUE' } }),
prisma_1.prisma.subscription.count({ where: { status: 'CANCELLED' } }),
]);
return { activeCount, trialingCount, pastDueCount, cancelledCount };
}
async function listCompanyInvoicesPage(companyId, query) {
const [data, total] = await Promise.all([
prisma_1.prisma.subscriptionInvoice.findMany({
where: { companyId },
orderBy: { createdAt: 'desc' },
skip: (query.page - 1) * query.pageSize,
take: query.pageSize,
}),
prisma_1.prisma.subscriptionInvoice.count({ where: { companyId } }),
]);
return { data, total };
}
function getInvoicePdfRecord(invoiceId) {
return prisma_1.prisma.subscriptionInvoice.findUniqueOrThrow({
where: { id: invoiceId },
include: {
company: { select: { name: true, email: true, phone: true, address: true } },
subscription: {
select: {
plan: true,
billingPeriod: true,
currency: true,
currentPeriodStart: true,
currentPeriodEnd: true,
},
},
},
});
}
function listPricingConfigs() {
return prisma_1.prisma.pricingConfig.findMany({ orderBy: [{ plan: 'asc' }, { billingPeriod: 'asc' }] });
}
function upsertPricingConfig(plan, billingPeriod, amount, updatedBy) {
return prisma_1.prisma.pricingConfig.upsert({
where: { plan_billingPeriod: { plan, billingPeriod } },
update: { amount, updatedBy },
create: { id: `prc_${plan.toLowerCase()}_${billingPeriod.toLowerCase()}`, plan, billingPeriod, amount, updatedBy },
});
}
function listPlanFeatures() {
return prisma_1.prisma.planFeature.findMany({
orderBy: [{ plan: 'asc' }, { sortOrder: 'asc' }, { createdAt: 'asc' }],
});
}
function createPlanFeature(data) {
return prisma_1.prisma.planFeature.create({
data: {
plan: data.plan,
label: data.label,
sortOrder: data.sortOrder ?? 0,
},
});
}
function updatePlanFeature(id, data) {
return prisma_1.prisma.planFeature.update({
where: { id },
data,
});
}
function deletePlanFeature(id) {
return prisma_1.prisma.planFeature.delete({ where: { id } });
}
// ─── Pricing promotions ────────────────────────────────────────────────────
function listPromotions() {
return prisma_1.prisma.pricingPromotion.findMany({ orderBy: { createdAt: 'desc' } });
}
function createPromotion(data) {
return prisma_1.prisma.pricingPromotion.create({
data: {
...data,
validFrom: new Date(data.validFrom),
validUntil: data.validUntil ? new Date(data.validUntil) : null,
},
});
}
function updatePromotion(id, data) {
const { validFrom, validUntil, ...rest } = data;
return prisma_1.prisma.pricingPromotion.update({
where: { id },
data: {
...rest,
...(validFrom ? { validFrom: new Date(validFrom) } : {}),
...(validUntil !== undefined ? { validUntil: validUntil ? new Date(validUntil) : null } : {}),
},
});
}
function deletePromotion(id) {
return prisma_1.prisma.pricingPromotion.delete({ where: { id } });
}
async function listNotificationsPage(query) {
const where = {};
if (query.channel)
where.channel = query.channel;
if (query.status)
where.status = query.status;
if (query.companyId)
where.companyId = query.companyId;
const [data, total] = await Promise.all([
prisma_1.prisma.notification.findMany({
where,
orderBy: { createdAt: 'desc' },
skip: (query.page - 1) * query.pageSize,
take: query.pageSize,
include: { company: { select: { name: true } } },
}),
prisma_1.prisma.notification.count({ where }),
]);
return { data, total };
}
//# sourceMappingURL=admin.repo.js.map
File diff suppressed because one or more lines are too long
+3
View File
@@ -0,0 +1,3 @@
declare const router: import("express-serve-static-core").Router;
export default router;
//# sourceMappingURL=admin.routes.d.ts.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"admin.routes.d.ts","sourceRoot":"","sources":["../../../src/modules/admin/admin.routes.ts"],"names":[],"mappings":"AAsCA,QAAA,MAAM,MAAM,4CAAW,CAAA;AAwhBvB,eAAe,MAAM,CAAA"}
+720
View File
@@ -0,0 +1,720 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const express_1 = require("express");
const requireAdminAuth_1 = require("../../middleware/requireAdminAuth");
const validate_1 = require("../../http/validate");
const respond_1 = require("../../http/respond");
const sessionCookies_1 = require("../../security/sessionCookies");
const service = __importStar(require("./admin.service"));
const subService = __importStar(require("../subscriptions/subscription.service"));
const menuService = __importStar(require("../menu/menu.service"));
const admin_presenter_1 = require("./admin.presenter");
const admin_schemas_1 = require("./admin.schemas");
const zod_1 = require("zod");
const adminSubOverrideSchema = zod_1.z.object({
reason: zod_1.z.string().min(1).max(500),
});
const adminExtendTrialSchema = zod_1.z.object({
extraDays: zod_1.z.number().int().positive(),
reason: zod_1.z.string().min(1).max(500),
});
const adminImpersonationSchema = zod_1.z.object({
reason: zod_1.z.string().min(5).max(500),
durationMinutes: zod_1.z.number().int().min(1).max(30).default(15),
});
const subIdParamSchema = zod_1.z.object({ subscriptionId: zod_1.z.string() });
const router = (0, express_1.Router)();
// ─── Auth (public) ─────────────────────────────────────────────
router.post('/auth/login', async (req, res, next) => {
try {
const { email, password, totpCode, recoveryCode } = (0, validate_1.parseBody)(admin_schemas_1.loginSchema, req);
const result = await service.login(email, password, totpCode, recoveryCode);
if (!result)
return res.status(401).json({ error: 'invalid_credentials', message: 'Invalid email or password', statusCode: 401 });
if ('totpRequired' in result)
return res.status(401).json({ error: 'totp_required', message: '2FA code required', statusCode: 401 });
if ('invalidTotp' in result)
return res.status(401).json({ error: 'invalid_totp', message: 'Invalid 2FA code', statusCode: 401 });
(0, sessionCookies_1.setSessionCookie)(res, 'admin', result.token, 8 * 60 * 60 * 1000);
(0, respond_1.ok)(res, result);
}
catch (err) {
next(err);
}
});
router.post('/auth/logout', (_req, res) => {
(0, sessionCookies_1.clearSessionCookie)(res, 'admin');
(0, respond_1.ok)(res, { success: true });
});
router.post('/auth/forgot-password', async (req, res, next) => {
try {
const { email } = (0, validate_1.parseBody)(admin_schemas_1.forgotPasswordSchema, req);
await service.forgotPassword(email);
(0, respond_1.ok)(res, { message: 'If that email is registered, a reset link has been sent.' });
}
catch (err) {
next(err);
}
});
router.post('/auth/reset-password', async (req, res, next) => {
try {
const { token, password } = (0, validate_1.parseBody)(admin_schemas_1.resetPasswordSchema, req);
const ok2 = await service.resetPassword(token, password);
if (!ok2)
return res.status(400).json({ error: 'invalid_token', message: 'Reset link is invalid or has expired.', statusCode: 400 });
(0, respond_1.ok)(res, { message: 'Password updated successfully. You can now sign in.' });
}
catch (err) {
next(err);
}
});
// ─── Auth (protected) ──────────────────────────────────────────
router.get('/auth/me', requireAdminAuth_1.requireAdminAuth, (req, res) => {
(0, respond_1.ok)(res, (0, admin_presenter_1.presentAdminUser)(req.admin));
});
router.post('/auth/2fa/setup', requireAdminAuth_1.requireAdminAuth, async (req, res, next) => {
try {
(0, respond_1.ok)(res, await service.setupTotp(req.admin.id, req.admin.email));
}
catch (err) {
next(err);
}
});
router.post('/auth/2fa/verify', requireAdminAuth_1.requireAdminAuth, async (req, res, next) => {
try {
const { code } = (0, validate_1.parseBody)(admin_schemas_1.totpVerifySchema, req);
const result = await service.verifyTotp(req.admin.id, code);
if (!result)
return res.status(400).json({ error: 'invalid_code', message: 'Invalid 2FA code', statusCode: 400 });
(0, sessionCookies_1.setSessionCookie)(res, 'admin', result.token, 8 * 60 * 60 * 1000);
(0, respond_1.ok)(res, { success: true, admin: result.admin, recoveryCodes: result.recoveryCodes });
}
catch (err) {
next(err);
}
});
router.post('/auth/2fa/recovery-codes/regenerate', requireAdminAuth_1.requireAdminAuth, requireAdminAuth_1.requireFreshAdmin2FA, async (req, res, next) => {
try {
(0, respond_1.ok)(res, await service.regenerateRecoveryCodes(req.admin.id));
}
catch (err) {
next(err);
}
});
// ─── Companies ─────────────────────────────────────────────────
router.get('/companies', requireAdminAuth_1.requireAdminAuth, async (req, res, next) => {
try {
(0, respond_1.ok)(res, await service.listCompanies((0, validate_1.parseQuery)(admin_schemas_1.companiesQuerySchema, req)));
}
catch (err) {
next(err);
}
});
router.get('/companies/:id', requireAdminAuth_1.requireAdminAuth, async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(admin_schemas_1.idParamSchema, req);
(0, respond_1.ok)(res, await service.getCompany(id));
}
catch (err) {
next(err);
}
});
router.patch('/companies/:id', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('SUPPORT'), async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(admin_schemas_1.idParamSchema, req);
const body = (0, validate_1.parseBody)(admin_schemas_1.adminCompanyUpdateSchema, req);
(0, respond_1.ok)(res, await service.updateCompany(id, body, req.admin.id, req.ip));
}
catch (err) {
next(err);
}
});
router.patch('/companies/:id/status', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('SUPPORT'), async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(admin_schemas_1.idParamSchema, req);
const { status, reason } = (0, validate_1.parseBody)(admin_schemas_1.companyStatusSchema, req);
(0, respond_1.ok)(res, await service.setCompanyStatus(id, status, reason, req.admin.id, req.ip));
}
catch (err) {
next(err);
}
});
router.delete('/companies/:id', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('ADMIN'), requireAdminAuth_1.requireFreshAdmin2FA, async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(admin_schemas_1.idParamSchema, req);
await service.deleteCompany(id, req.admin.id, req.ip);
(0, respond_1.ok)(res, { success: true });
}
catch (err) {
next(err);
}
});
router.post('/companies/:id/impersonate', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('SUPER_ADMIN'), requireAdminAuth_1.requireFreshAdmin2FA, async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(admin_schemas_1.idParamSchema, req);
const { reason, durationMinutes } = (0, validate_1.parseBody)(adminImpersonationSchema, req);
(0, respond_1.ok)(res, await service.impersonateCompany(id, req.admin.id, req.ip, reason, durationMinutes));
}
catch (err) {
next(err);
}
});
// ─── Menu management ──────────────────────────────────────────
router.get('/menu-items', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('SUPPORT'), async (_req, res, next) => {
try {
(0, respond_1.ok)(res, await menuService.listMenuItems());
}
catch (err) {
next(err);
}
});
router.post('/menu-items', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('ADMIN'), async (req, res, next) => {
try {
(0, respond_1.created)(res, { data: await menuService.createMenuItem((0, validate_1.parseBody)(admin_schemas_1.menuItemSchema, req), req.admin) });
}
catch (err) {
next(err);
}
});
router.get('/menu-items/:id', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('SUPPORT'), async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(admin_schemas_1.idParamSchema, req);
(0, respond_1.ok)(res, await menuService.getMenuItem(id));
}
catch (err) {
next(err);
}
});
router.put('/menu-items/:id', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('ADMIN'), async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(admin_schemas_1.idParamSchema, req);
(0, respond_1.ok)(res, await menuService.updateMenuItem(id, (0, validate_1.parseBody)(admin_schemas_1.menuItemSchema, req), req.admin));
}
catch (err) {
next(err);
}
});
router.patch('/menu-items/:id/status', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('ADMIN'), async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(admin_schemas_1.idParamSchema, req);
const { isActive } = (0, validate_1.parseBody)(admin_schemas_1.menuItemStatusSchema, req);
(0, respond_1.ok)(res, await menuService.setMenuItemStatus(id, isActive, req.admin));
}
catch (err) {
next(err);
}
});
router.delete('/menu-items/:id', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('ADMIN'), async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(admin_schemas_1.idParamSchema, req);
await menuService.deleteMenuItem(id, req.admin);
(0, respond_1.ok)(res, { success: true });
}
catch (err) {
next(err);
}
});
router.post('/menu-items/:id/subscriptions', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('ADMIN'), async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(admin_schemas_1.idParamSchema, req);
const { assignments } = (0, validate_1.parseBody)(admin_schemas_1.menuPlanAssignmentsSchema, req);
(0, respond_1.ok)(res, await menuService.assignMenuItemToPlans(id, assignments, req.admin));
}
catch (err) {
next(err);
}
});
router.delete('/menu-items/:id/subscriptions/:plan', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('ADMIN'), async (req, res, next) => {
try {
const { id, plan } = (0, validate_1.parseParams)(admin_schemas_1.menuPlanParamSchema, req);
(0, respond_1.ok)(res, await menuService.removeMenuItemFromPlan(id, plan, req.admin));
}
catch (err) {
next(err);
}
});
router.post('/menu-items/:id/companies', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('ADMIN'), async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(admin_schemas_1.idParamSchema, req);
const { assignments } = (0, validate_1.parseBody)(admin_schemas_1.menuCompanyAssignmentsSchema, req);
(0, respond_1.ok)(res, await menuService.assignMenuItemToCompanies(id, assignments, req.admin));
}
catch (err) {
next(err);
}
});
router.delete('/menu-items/:id/companies/:companyId', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('ADMIN'), async (req, res, next) => {
try {
const { id, companyId } = (0, validate_1.parseParams)(admin_schemas_1.menuCompanyParamSchema, req);
(0, respond_1.ok)(res, await menuService.removeMenuItemFromCompany(id, companyId, req.admin));
}
catch (err) {
next(err);
}
});
router.post('/menu-preview', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('SUPPORT'), async (req, res, next) => {
try {
(0, respond_1.ok)(res, await menuService.previewCompanyMenu((0, validate_1.parseBody)(admin_schemas_1.menuPreviewSchema, req)));
}
catch (err) {
next(err);
}
});
router.get('/menu-audit-logs', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('SUPPORT'), async (req, res, next) => {
try {
(0, respond_1.ok)(res, await menuService.listMenuAuditLogs((0, validate_1.parseQuery)(admin_schemas_1.menuAuditLogQuerySchema, req)));
}
catch (err) {
next(err);
}
});
// ─── Renters ───────────────────────────────────────────────────
router.get('/renters', requireAdminAuth_1.requireAdminAuth, async (req, res, next) => {
try {
(0, respond_1.ok)(res, await service.listRenters((0, validate_1.parseQuery)(admin_schemas_1.rentersQuerySchema, req)));
}
catch (err) {
next(err);
}
});
router.post('/renters/:id/block', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('SUPPORT'), async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(admin_schemas_1.idParamSchema, req);
await service.setRenterActive(id, false);
(0, respond_1.ok)(res, { success: true });
}
catch (err) {
next(err);
}
});
router.post('/renters/:id/unblock', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('SUPPORT'), async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(admin_schemas_1.idParamSchema, req);
await service.setRenterActive(id, true);
(0, respond_1.ok)(res, { success: true });
}
catch (err) {
next(err);
}
});
// ─── Metrics ───────────────────────────────────────────────────
router.get('/metrics', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('FINANCE'), async (req, res, next) => {
try {
(0, respond_1.ok)(res, await service.getPlatformMetrics());
}
catch (err) {
next(err);
}
});
// ─── Notifications ─────────────────────────────────────────────
router.get('/notifications', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('SUPPORT'), async (req, res, next) => {
try {
(0, respond_1.ok)(res, await service.listNotifications((0, validate_1.parseQuery)(admin_schemas_1.notificationsQuerySchema, req)));
}
catch (err) {
next(err);
}
});
// ─── Audit logs ────────────────────────────────────────────────
router.get('/audit-logs', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('ADMIN'), async (req, res, next) => {
try {
(0, respond_1.ok)(res, await service.getAuditLogs((0, validate_1.parseQuery)(admin_schemas_1.auditLogQuerySchema, req)));
}
catch (err) {
next(err);
}
});
// ─── Admin users ───────────────────────────────────────────────
router.get('/admins', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('SUPER_ADMIN'), async (req, res, next) => {
try {
(0, respond_1.ok)(res, await service.listAdmins());
}
catch (err) {
next(err);
}
});
router.post('/admins', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('SUPER_ADMIN'), requireAdminAuth_1.requireFreshAdmin2FA, async (req, res, next) => {
try {
(0, respond_1.created)(res, { data: await service.createAdmin((0, validate_1.parseBody)(admin_schemas_1.createAdminSchema, req)) });
}
catch (err) {
next(err);
}
});
router.patch('/admins/:id', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('SUPER_ADMIN'), requireAdminAuth_1.requireFreshAdmin2FA, async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(admin_schemas_1.idParamSchema, req);
(0, respond_1.ok)(res, await service.updateAdmin(id, (0, validate_1.parseBody)(admin_schemas_1.updateAdminSchema, req)));
}
catch (err) {
next(err);
}
});
router.patch('/admins/:id/role', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('SUPER_ADMIN'), requireAdminAuth_1.requireFreshAdmin2FA, async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(admin_schemas_1.idParamSchema, req);
const { role } = (0, validate_1.parseBody)(admin_schemas_1.adminRoleSchema, req);
await service.updateAdminRole(id, role);
(0, respond_1.ok)(res, { success: true });
}
catch (err) {
next(err);
}
});
router.patch('/admins/:id/permissions', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('SUPER_ADMIN'), requireAdminAuth_1.requireFreshAdmin2FA, async (req, res, next) => {
try {
const { id } = (0, validate_1.parseParams)(admin_schemas_1.idParamSchema, req);
const { permissions } = (0, validate_1.parseBody)(admin_schemas_1.adminPermissionsSchema, req);
(0, respond_1.ok)(res, await service.updateAdminPermissions(id, permissions));
}
catch (err) {
next(err);
}
});
// ─── Billing ───────────────────────────────────────────────────
router.get('/billing', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('FINANCE'), async (req, res, next) => {
try {
(0, respond_1.ok)(res, await service.getBilling((0, validate_1.parseQuery)(admin_schemas_1.billingQuerySchema, req)));
}
catch (err) {
next(err);
}
});
router.get('/billing/invoices/:invoiceId/pdf', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('FINANCE'), async (req, res, next) => {
try {
const { invoiceId } = (0, validate_1.parseParams)(admin_schemas_1.invoiceIdParamSchema, req);
const { pdfBuffer, invoiceNumber } = await service.getInvoicePdf(invoiceId);
res.setHeader('Content-Type', 'application/pdf');
res.setHeader('Content-Disposition', `attachment; filename="${invoiceNumber}.pdf"`);
res.setHeader('Content-Length', pdfBuffer.length);
res.end(pdfBuffer);
}
catch (err) {
next(err);
}
});
router.get('/billing/:companyId', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('FINANCE'), async (req, res, next) => {
try {
const { companyId } = (0, validate_1.parseParams)(admin_schemas_1.companyIdParamSchema, req);
(0, respond_1.ok)(res, await service.getBillingAccountDetail(companyId));
}
catch (err) {
next(err);
}
});
router.get('/billing/:companyId/invoices', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('FINANCE'), async (req, res, next) => {
try {
const { companyId } = (0, validate_1.parseParams)(admin_schemas_1.companyIdParamSchema, req);
(0, respond_1.ok)(res, await service.getCompanyInvoices(companyId, (0, validate_1.parseQuery)(admin_schemas_1.invoicesQuerySchema, req)));
}
catch (err) {
next(err);
}
});
router.patch('/billing/accounts/:billingAccountId', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('FINANCE'), async (req, res, next) => {
try {
const { billingAccountId } = (0, validate_1.parseParams)(admin_schemas_1.billingAccountIdParamSchema, req);
(0, respond_1.ok)(res, await service.updateBillingAccount(billingAccountId, (0, validate_1.parseBody)(admin_schemas_1.billingAccountUpdateSchema, req), req.admin.id, req.ip));
}
catch (err) {
next(err);
}
});
router.post('/billing/accounts/:billingAccountId/pause-dunning', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('FINANCE'), async (req, res, next) => {
try {
const { billingAccountId } = (0, validate_1.parseParams)(admin_schemas_1.billingAccountIdParamSchema, req);
(0, respond_1.ok)(res, await service.setDunningPaused(billingAccountId, true, req.admin.id, req.ip));
}
catch (err) {
next(err);
}
});
router.post('/billing/accounts/:billingAccountId/resume-dunning', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('FINANCE'), async (req, res, next) => {
try {
const { billingAccountId } = (0, validate_1.parseParams)(admin_schemas_1.billingAccountIdParamSchema, req);
(0, respond_1.ok)(res, await service.setDunningPaused(billingAccountId, false, req.admin.id, req.ip));
}
catch (err) {
next(err);
}
});
router.post('/billing/accounts/:billingAccountId/invoices', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('FINANCE'), async (req, res, next) => {
try {
const { billingAccountId } = (0, validate_1.parseParams)(admin_schemas_1.billingAccountIdParamSchema, req);
(0, respond_1.created)(res, await service.createBillingInvoice(billingAccountId, (0, validate_1.parseBody)(admin_schemas_1.createBillingInvoiceSchema, req), req.admin.id, req.ip));
}
catch (err) {
next(err);
}
});
router.post('/billing/invoices/:invoiceId/finalize', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('FINANCE'), async (req, res, next) => {
try {
const { invoiceId } = (0, validate_1.parseParams)(admin_schemas_1.invoiceIdParamSchema, req);
(0, respond_1.ok)(res, await service.finalizeBillingInvoice(invoiceId, req.admin.id, req.ip));
}
catch (err) {
next(err);
}
});
router.post('/billing/invoices/:invoiceId/pay', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('FINANCE'), requireAdminAuth_1.requireFreshAdmin2FA, async (req, res, next) => {
try {
const { invoiceId } = (0, validate_1.parseParams)(admin_schemas_1.invoiceIdParamSchema, req);
(0, respond_1.ok)(res, await service.payBillingInvoice(invoiceId, (0, validate_1.parseBody)(admin_schemas_1.payBillingInvoiceSchema, req), req.admin.id, req.ip));
}
catch (err) {
next(err);
}
});
router.post('/billing/invoices/:invoiceId/retry-payment', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('FINANCE'), async (req, res, next) => {
try {
const { invoiceId } = (0, validate_1.parseParams)(admin_schemas_1.invoiceIdParamSchema, req);
(0, respond_1.ok)(res, await service.retryBillingInvoicePayment(invoiceId, (0, validate_1.parseBody)(admin_schemas_1.retryBillingInvoiceSchema, req), req.admin.id, req.ip));
}
catch (err) {
next(err);
}
});
router.post('/billing/invoices/:invoiceId/void', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('FINANCE'), async (req, res, next) => {
try {
const { invoiceId } = (0, validate_1.parseParams)(admin_schemas_1.invoiceIdParamSchema, req);
const { reason } = (0, validate_1.parseBody)(admin_schemas_1.billingReasonSchema, req);
(0, respond_1.ok)(res, await service.voidBillingInvoice(invoiceId, reason, req.admin.id, req.ip));
}
catch (err) {
next(err);
}
});
router.post('/billing/invoices/:invoiceId/uncollectible', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('FINANCE'), async (req, res, next) => {
try {
const { invoiceId } = (0, validate_1.parseParams)(admin_schemas_1.invoiceIdParamSchema, req);
const { reason } = (0, validate_1.parseBody)(admin_schemas_1.billingReasonSchema, req);
(0, respond_1.ok)(res, await service.markBillingInvoiceUncollectible(invoiceId, reason, req.admin.id, req.ip));
}
catch (err) {
next(err);
}
});
router.post('/billing/invoices/:invoiceId/credit-notes', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('FINANCE'), async (req, res, next) => {
try {
const { invoiceId } = (0, validate_1.parseParams)(admin_schemas_1.invoiceIdParamSchema, req);
(0, respond_1.ok)(res, await service.issueBillingCreditNote(invoiceId, (0, validate_1.parseBody)(admin_schemas_1.billingCreditNoteSchema, req), req.admin.id, req.ip));
}
catch (err) {
next(err);
}
});
router.post('/billing/invoices/:invoiceId/refunds', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('FINANCE'), requireAdminAuth_1.requireFreshAdmin2FA, async (req, res, next) => {
try {
const { invoiceId } = (0, validate_1.parseParams)(admin_schemas_1.invoiceIdParamSchema, req);
(0, respond_1.ok)(res, await service.issueBillingRefund(invoiceId, (0, validate_1.parseBody)(admin_schemas_1.billingRefundSchema, req), req.admin.id, req.ip));
}
catch (err) {
next(err);
}
});
// ─── Pricing config ────────────────────────────────────────────
router.get('/pricing', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('FINANCE'), async (req, res, next) => {
try {
(0, respond_1.ok)(res, await service.getPricingConfigs());
}
catch (err) {
next(err);
}
});
router.patch('/pricing', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('FINANCE'), requireAdminAuth_1.requireFreshAdmin2FA, async (req, res, next) => {
try {
const { entries } = (0, validate_1.parseBody)(admin_schemas_1.pricingUpdateSchema, req);
(0, respond_1.ok)(res, await service.updatePricingConfigs(entries, req.admin.id, req.ip));
}
catch (err) {
next(err);
}
});
router.get('/pricing/features', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('FINANCE'), async (_req, res, next) => {
try {
(0, respond_1.ok)(res, await service.listPlanFeatures());
}
catch (err) {
next(err);
}
});
router.post('/pricing/features', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('FINANCE'), requireAdminAuth_1.requireFreshAdmin2FA, async (req, res, next) => {
try {
const data = (0, validate_1.parseBody)(admin_schemas_1.planFeatureCreateSchema, req);
(0, respond_1.created)(res, await service.createPlanFeature(data, req.admin.id, req.ip));
}
catch (err) {
next(err);
}
});
router.patch('/pricing/features/:featureId', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('FINANCE'), requireAdminAuth_1.requireFreshAdmin2FA, async (req, res, next) => {
try {
const { featureId } = (0, validate_1.parseParams)(admin_schemas_1.planFeatureIdParamSchema, req);
const data = (0, validate_1.parseBody)(admin_schemas_1.planFeatureUpdateSchema, req);
(0, respond_1.ok)(res, await service.updatePlanFeature(featureId, data, req.admin.id, req.ip));
}
catch (err) {
next(err);
}
});
router.delete('/pricing/features/:featureId', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('FINANCE'), requireAdminAuth_1.requireFreshAdmin2FA, async (req, res, next) => {
try {
const { featureId } = (0, validate_1.parseParams)(admin_schemas_1.planFeatureIdParamSchema, req);
await service.deletePlanFeature(featureId, req.admin.id, req.ip);
(0, respond_1.ok)(res, { success: true });
}
catch (err) {
next(err);
}
});
// ─── Promotions ────────────────────────────────────────────────────────────
router.get('/pricing/promotions', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('FINANCE'), async (req, res, next) => {
try {
(0, respond_1.ok)(res, await service.listPromotions());
}
catch (err) {
next(err);
}
});
router.post('/pricing/promotions', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('FINANCE'), requireAdminAuth_1.requireFreshAdmin2FA, async (req, res, next) => {
try {
const data = (0, validate_1.parseBody)(admin_schemas_1.promotionCreateSchema, req);
(0, respond_1.created)(res, await service.createPromotion(data, req.admin.id, req.ip));
}
catch (err) {
next(err);
}
});
router.patch('/pricing/promotions/:promotionId', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('FINANCE'), requireAdminAuth_1.requireFreshAdmin2FA, async (req, res, next) => {
try {
const { promotionId } = (0, validate_1.parseParams)(admin_schemas_1.promotionIdParamSchema, req);
const data = (0, validate_1.parseBody)(admin_schemas_1.promotionUpdateSchema, req);
(0, respond_1.ok)(res, await service.updatePromotion(promotionId, data, req.admin.id, req.ip));
}
catch (err) {
next(err);
}
});
router.delete('/pricing/promotions/:promotionId', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('FINANCE'), requireAdminAuth_1.requireFreshAdmin2FA, async (req, res, next) => {
try {
const { promotionId } = (0, validate_1.parseParams)(admin_schemas_1.promotionIdParamSchema, req);
await service.deletePromotion(promotionId, req.admin.id, req.ip);
(0, respond_1.ok)(res, { success: true });
}
catch (err) {
next(err);
}
});
// ─── Subscription admin overrides ─────────────────────────────
router.get('/subscriptions/:subscriptionId/events', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('SUPPORT'), async (req, res, next) => {
try {
const { subscriptionId } = (0, validate_1.parseParams)(subIdParamSchema, req);
(0, respond_1.ok)(res, await subService.getEventsBySubscriptionId(subscriptionId));
}
catch (err) {
next(err);
}
});
router.post('/subscriptions/:subscriptionId/extend-trial', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('SUPPORT'), requireAdminAuth_1.requireFreshAdmin2FA, async (req, res, next) => {
try {
const { subscriptionId } = (0, validate_1.parseParams)(subIdParamSchema, req);
const { extraDays, reason } = (0, validate_1.parseBody)(adminExtendTrialSchema, req);
(0, respond_1.ok)(res, await subService.adminExtendTrial(subscriptionId, extraDays, req.admin.id, reason));
}
catch (err) {
next(err);
}
});
router.post('/subscriptions/:subscriptionId/extend-grace-period', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('SUPPORT'), requireAdminAuth_1.requireFreshAdmin2FA, async (req, res, next) => {
try {
const { subscriptionId } = (0, validate_1.parseParams)(subIdParamSchema, req);
const { reason } = (0, validate_1.parseBody)(adminSubOverrideSchema, req);
(0, respond_1.ok)(res, await subService.adminExtendGracePeriod(subscriptionId, 7, req.admin.id, reason));
}
catch (err) {
next(err);
}
});
router.post('/subscriptions/:subscriptionId/suspend', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('SUPPORT'), requireAdminAuth_1.requireFreshAdmin2FA, async (req, res, next) => {
try {
const { subscriptionId } = (0, validate_1.parseParams)(subIdParamSchema, req);
const { reason } = (0, validate_1.parseBody)(adminSubOverrideSchema, req);
(0, respond_1.ok)(res, await subService.adminSuspendSubscription(subscriptionId, req.admin.id, reason));
}
catch (err) {
next(err);
}
});
router.post('/subscriptions/:subscriptionId/reactivate', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('SUPPORT'), requireAdminAuth_1.requireFreshAdmin2FA, async (req, res, next) => {
try {
const { subscriptionId } = (0, validate_1.parseParams)(subIdParamSchema, req);
const { reason } = (0, validate_1.parseBody)(adminSubOverrideSchema, req);
(0, respond_1.ok)(res, await subService.adminReactivateSubscription(subscriptionId, req.admin.id, reason));
}
catch (err) {
next(err);
}
});
router.post('/subscriptions/:subscriptionId/cancel', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('SUPPORT'), requireAdminAuth_1.requireFreshAdmin2FA, async (req, res, next) => {
try {
const { subscriptionId } = (0, validate_1.parseParams)(subIdParamSchema, req);
const { reason } = (0, validate_1.parseBody)(adminSubOverrideSchema, req);
(0, respond_1.ok)(res, await subService.adminCancelSubscription(subscriptionId, req.admin.id, reason));
}
catch (err) {
next(err);
}
});
// ─── Site config ───────────────────────────────────────────────
router.get('/site-config/marketplace-homepage', requireAdminAuth_1.requireAdminAuth, async (req, res, next) => {
try {
(0, respond_1.ok)(res, await service.getMarketplaceHomepage());
}
catch (err) {
next(err);
}
});
router.patch('/site-config/marketplace-homepage', requireAdminAuth_1.requireAdminAuth, (0, requireAdminAuth_1.requireAdminRole)('SUPPORT'), async (req, res, next) => {
try {
const { homepage } = (0, validate_1.parseBody)(admin_schemas_1.homepageUpdateSchema, req);
(0, respond_1.ok)(res, await service.updateMarketplaceHomepage(homepage, req.admin.id, req.ip));
}
catch (err) {
next(err);
}
});
exports.default = router;
//# sourceMappingURL=admin.routes.js.map
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
+1
View File
@@ -0,0 +1 @@
{"version":3,"file":"admin.schemas.d.ts","sourceRoot":"","sources":["../../../src/modules/admin/admin.schemas.ts"],"names":[],"mappings":"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"}
+361
View File
@@ -0,0 +1,361 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.menuCompanyParamSchema = exports.menuPlanParamSchema = exports.menuItemIdParamSchema = exports.menuAuditLogQuerySchema = exports.menuPreviewSchema = exports.menuCompanyAssignmentsSchema = exports.menuPlanAssignmentsSchema = exports.menuItemStatusSchema = exports.menuItemSchema = exports.promotionIdParamSchema = exports.planFeatureIdParamSchema = exports.promotionUpdateSchema = exports.promotionCreateSchema = exports.planFeatureUpdateSchema = exports.planFeatureCreateSchema = exports.pricingUpdateSchema = exports.homepageUpdateSchema = exports.billingRefundSchema = exports.billingCreditNoteSchema = exports.billingReasonSchema = exports.retryBillingInvoiceSchema = exports.payBillingInvoiceSchema = exports.createBillingInvoiceSchema = exports.billingLineItemInputSchema = exports.billingAccountUpdateSchema = exports.invoiceIdParamSchema = exports.billingAccountIdParamSchema = exports.companyIdParamSchema = exports.idParamSchema = exports.marketplaceHomepageConfigSchema = exports.adminCompanyUpdateSchema = exports.companyStatusSchema = exports.adminPermissionsSchema = exports.adminRoleSchema = exports.updateAdminSchema = exports.createAdminSchema = exports.permissionSchema = exports.invoicesQuerySchema = exports.billingQuerySchema = exports.notificationsQuerySchema = exports.auditLogQuerySchema = exports.rentersQuerySchema = exports.companiesQuerySchema = exports.totpVerifySchema = exports.resetPasswordSchema = exports.forgotPasswordSchema = exports.loginSchema = void 0;
const zod_1 = require("zod");
exports.loginSchema = zod_1.z.object({
email: zod_1.z.string().email().max(255).trim().toLowerCase(),
password: zod_1.z.string().max(128),
totpCode: zod_1.z.string().length(6).optional(),
recoveryCode: zod_1.z.string().min(8).max(32).optional(),
});
exports.forgotPasswordSchema = zod_1.z.object({
email: zod_1.z.string().email().max(255).trim().toLowerCase(),
});
exports.resetPasswordSchema = zod_1.z.object({
token: zod_1.z.string().min(1),
password: zod_1.z.string().min(8).max(128),
});
exports.totpVerifySchema = zod_1.z.object({
code: zod_1.z.string().length(6),
});
exports.companiesQuerySchema = zod_1.z.object({
q: zod_1.z.string().optional(),
status: zod_1.z.string().optional(),
plan: zod_1.z.string().optional(),
page: zod_1.z.coerce.number().int().min(1).default(1),
pageSize: zod_1.z.coerce.number().int().min(1).max(100).default(20),
});
exports.rentersQuerySchema = zod_1.z.object({
q: zod_1.z.string().optional(),
blocked: zod_1.z.string().optional(),
page: zod_1.z.coerce.number().int().min(1).default(1),
pageSize: zod_1.z.coerce.number().int().min(1).max(100).default(20),
});
exports.auditLogQuerySchema = zod_1.z.object({
adminId: zod_1.z.string().optional(),
action: zod_1.z.string().optional(),
companyId: zod_1.z.string().optional(),
entityId: zod_1.z.string().optional(),
page: zod_1.z.coerce.number().int().min(1).default(1),
pageSize: zod_1.z.coerce.number().int().min(1).max(100).default(50),
});
exports.notificationsQuerySchema = zod_1.z.object({
channel: zod_1.z.string().optional(),
status: zod_1.z.string().optional(),
companyId: zod_1.z.string().optional(),
page: zod_1.z.coerce.number().int().min(1).default(1),
pageSize: zod_1.z.coerce.number().int().min(1).max(200).default(50),
});
exports.billingQuerySchema = zod_1.z.object({
q: zod_1.z.string().optional(),
status: zod_1.z.string().optional(),
plan: zod_1.z.string().optional(),
page: zod_1.z.coerce.number().int().min(1).default(1),
pageSize: zod_1.z.coerce.number().int().min(1).max(100).default(20),
});
exports.invoicesQuerySchema = zod_1.z.object({
page: zod_1.z.coerce.number().int().min(1).default(1),
pageSize: zod_1.z.coerce.number().int().min(1).max(100).default(20),
});
exports.permissionSchema = zod_1.z.object({
resource: zod_1.z.enum(['COMPANIES', 'COMPANY_EMPLOYEES', 'SUBSCRIPTIONS', 'PAYMENTS', 'OFFERS', 'RENTERS', 'ADMIN_USERS', 'AUDIT_LOGS', 'PLATFORM_METRICS']),
actions: zod_1.z.array(zod_1.z.enum(['READ', 'CREATE', 'UPDATE', 'DELETE', 'SUSPEND', 'IMPERSONATE'])).min(1),
});
exports.createAdminSchema = zod_1.z.object({
email: zod_1.z.string().email().trim().toLowerCase(),
firstName: zod_1.z.string().min(1).max(100).trim(),
lastName: zod_1.z.string().min(1).max(100).trim(),
role: zod_1.z.enum(['SUPER_ADMIN', 'ADMIN', 'SUPPORT', 'FINANCE', 'VIEWER']),
password: zod_1.z.string().min(8),
permissions: zod_1.z.array(exports.permissionSchema).optional(),
});
exports.updateAdminSchema = zod_1.z.object({
email: zod_1.z.string().email().trim().toLowerCase().optional(),
firstName: zod_1.z.string().min(1).max(100).trim().optional(),
lastName: zod_1.z.string().min(1).max(100).trim().optional(),
role: zod_1.z.enum(['SUPER_ADMIN', 'ADMIN', 'SUPPORT', 'FINANCE', 'VIEWER']).optional(),
password: zod_1.z.string().min(8).optional(),
isActive: zod_1.z.boolean().optional(),
});
exports.adminRoleSchema = zod_1.z.object({
role: zod_1.z.enum(['SUPER_ADMIN', 'ADMIN', 'SUPPORT', 'FINANCE', 'VIEWER']),
});
exports.adminPermissionsSchema = zod_1.z.object({
permissions: zod_1.z.array(exports.permissionSchema),
});
exports.companyStatusSchema = zod_1.z.object({
status: zod_1.z.enum(['ACTIVE', 'SUSPENDED', 'CANCELLED']),
reason: zod_1.z.string().optional(),
});
const nullableString = zod_1.z.union([zod_1.z.string(), zod_1.z.null()]).optional();
const nullableEmail = zod_1.z.union([zod_1.z.string().email(), zod_1.z.null()]).optional();
const nullableUrl = zod_1.z.union([zod_1.z.string().url(), zod_1.z.null()]).optional();
const nullableDate = zod_1.z.union([zod_1.z.string().datetime(), zod_1.z.string().regex(/^\d{4}-\d{2}-\d{2}$/), zod_1.z.null()]).optional();
exports.adminCompanyUpdateSchema = zod_1.z.object({
company: zod_1.z.object({
name: zod_1.z.string().min(1).optional(), slug: zod_1.z.string().min(1).optional(),
email: zod_1.z.string().email().optional(), phone: nullableString,
status: zod_1.z.enum(['PENDING', 'TRIALING', 'ACTIVE', 'PAST_DUE', 'SUSPENDED', 'CANCELLED']).optional(),
subscriptionPaymentRef: nullableString,
address: zod_1.z.object({
streetAddress: nullableString,
city: nullableString,
country: nullableString,
zipCode: nullableString,
legalName: nullableString,
legalForm: nullableString,
companyEmail: nullableEmail,
managerName: nullableString,
iceNumber: nullableString,
operatingLicenseNumber: nullableString,
operatingLicenseIssuedAt: nullableString,
operatingLicenseIssuedBy: nullableString,
fax: nullableString,
yearsActive: nullableString,
representativeName: nullableString,
representativeTitle: nullableString,
responsibleName: nullableString,
responsibleRole: nullableString,
responsibleIdentityNumber: nullableString,
responsibleQualification: nullableString,
responsiblePhone: nullableString,
responsibleEmail: nullableEmail,
}).optional(),
}).optional(),
subscription: zod_1.z.object({
plan: zod_1.z.enum(['STARTER', 'GROWTH', 'PRO']).optional(),
billingPeriod: zod_1.z.enum(['MONTHLY', 'ANNUAL']).optional(),
status: zod_1.z.enum(['TRIALING', 'ACTIVE', 'PAST_DUE', 'CANCELLED', 'UNPAID']).optional(),
currency: zod_1.z.literal('MAD').optional(),
trialStartAt: nullableDate, trialEndAt: nullableDate,
currentPeriodStart: nullableDate, currentPeriodEnd: nullableDate,
cancelledAt: nullableDate, cancelAtPeriodEnd: zod_1.z.boolean().optional(),
}).optional(),
brand: zod_1.z.object({
displayName: zod_1.z.string().min(1).optional(), tagline: nullableString,
subdomain: zod_1.z.string().min(1).optional(), customDomain: nullableString,
publicEmail: nullableEmail, publicPhone: nullableString, publicAddress: nullableString,
publicCity: nullableString, publicCountry: nullableString, websiteUrl: nullableUrl,
whatsappNumber: nullableString, defaultLocale: zod_1.z.string().min(2).optional(),
defaultCurrency: zod_1.z.literal('MAD').optional(),
isListedOnMarketplace: zod_1.z.boolean().optional(),
homePageConfig: zod_1.z.any().optional(),
menuConfig: zod_1.z.any().optional(),
}).optional(),
contractSettings: zod_1.z.object({
legalName: nullableString, registrationNumber: nullableString, taxId: nullableString,
terms: zod_1.z.string().optional(),
fuelPolicyType: zod_1.z.enum(['FULL_TO_FULL', 'FULL_TO_EMPTY', 'SAME_TO_SAME', 'PREPAID', 'FREE']).optional(),
lateFeePerHour: zod_1.z.number().int().nullable().optional(), taxRate: zod_1.z.number().nullable().optional(),
signatureRequired: zod_1.z.boolean().optional(), showTax: zod_1.z.boolean().optional(),
}).optional(),
accountingSettings: zod_1.z.object({
reportingPeriod: zod_1.z.enum(['WEEKLY', 'MONTHLY', 'QUARTERLY', 'ANNUAL']).optional(),
fiscalYearStart: zod_1.z.number().int().min(1).max(12).optional(),
currency: zod_1.z.literal('MAD').optional(),
accountantEmail: nullableEmail, accountantName: nullableString,
autoSendReport: zod_1.z.boolean().optional(),
reportFormat: zod_1.z.enum(['PDF', 'CSV', 'BOTH']).optional(),
}).optional(),
});
const marketplaceMetricSchema = zod_1.z.object({ value: zod_1.z.string().min(1), label: zod_1.z.string().min(1) });
const marketplacePillarSchema = zod_1.z.object({ title: zod_1.z.string().min(1), body: zod_1.z.string().min(1) });
const marketplaceStepSchema = zod_1.z.object({ step: zod_1.z.string().min(1), title: zod_1.z.string().min(1), body: zod_1.z.string().min(1) });
const marketplaceSectionSchema = zod_1.z.enum(['hero', 'surface', 'pillars', 'audiences', 'features', 'steps', 'closing']);
const marketplaceHomepageContentSchema = zod_1.z.object({
sections: zod_1.z.array(marketplaceSectionSchema).min(1),
heroKicker: zod_1.z.string().min(1), heroTitle: zod_1.z.string().min(1), heroBody: zod_1.z.string().min(1),
startTrial: zod_1.z.string().min(1), exploreVehicles: zod_1.z.string().min(1),
surfaceLabel: zod_1.z.string().min(1), surfaceTitle: zod_1.z.string().min(1), surfaceBody: zod_1.z.string().min(1),
liveLabel: zod_1.z.string().min(1), trustedFleets: zod_1.z.string().min(1), brandedFlows: zod_1.z.string().min(1), multiTenant: zod_1.z.string().min(1),
companyKicker: zod_1.z.string().min(1), companyTitle: zod_1.z.string().min(1), companyBody: zod_1.z.string().min(1),
renterKicker: zod_1.z.string().min(1), renterTitle: zod_1.z.string().min(1), renterBody: zod_1.z.string().min(1),
pillars: zod_1.z.array(marketplacePillarSchema).length(3),
metrics: zod_1.z.array(marketplaceMetricSchema).length(3),
featureLabel: zod_1.z.string().min(1), features: zod_1.z.array(zod_1.z.string().min(1)).min(1),
stepsTitle: zod_1.z.string().min(1), steps: zod_1.z.array(marketplaceStepSchema).length(3), stepLabel: zod_1.z.string().min(1),
readyKicker: zod_1.z.string().min(1), readyTitle: zod_1.z.string().min(1), readyBody: zod_1.z.string().min(1),
viewPricing: zod_1.z.string().min(1), createWorkspace: zod_1.z.string().min(1),
});
exports.marketplaceHomepageConfigSchema = zod_1.z.object({
en: marketplaceHomepageContentSchema,
fr: marketplaceHomepageContentSchema,
ar: marketplaceHomepageContentSchema,
});
exports.idParamSchema = zod_1.z.object({
id: zod_1.z.string().min(1),
});
exports.companyIdParamSchema = zod_1.z.object({
companyId: zod_1.z.string().min(1),
});
exports.billingAccountIdParamSchema = zod_1.z.object({
billingAccountId: zod_1.z.string().min(1),
});
exports.invoiceIdParamSchema = zod_1.z.object({
invoiceId: zod_1.z.string().min(1),
});
exports.billingAccountUpdateSchema = zod_1.z.object({
legalName: zod_1.z.string().min(1).max(255).optional(),
billingEmail: zod_1.z.string().email().optional(),
billingAddress: zod_1.z.any().optional(),
taxId: zod_1.z.union([zod_1.z.string().max(120), zod_1.z.null()]).optional(),
taxExempt: zod_1.z.boolean().optional(),
invoiceTerms: zod_1.z.enum(['DUE_ON_RECEIPT', 'NET_7', 'NET_15', 'NET_30', 'NET_45', 'NET_60']).optional(),
netTermsDays: zod_1.z.number().int().min(0).max(365).optional(),
});
exports.billingLineItemInputSchema = zod_1.z.object({
type: zod_1.z.enum([
'SUBSCRIPTION_FEE',
'SEAT_FEE',
'USAGE_FEE',
'SETUP_FEE',
'DISCOUNT',
'TAX',
'CREDIT',
'PRORATION',
'REFUND_ADJUSTMENT',
'MANUAL_ADJUSTMENT',
'PROFESSIONAL_SERVICES',
'CANCELLATION_FEE',
]),
description: zod_1.z.string().min(1).max(255),
quantity: zod_1.z.number().int().positive().max(100000),
unitAmount: zod_1.z.number().int(),
periodStart: nullableDate,
periodEnd: nullableDate,
});
exports.createBillingInvoiceSchema = zod_1.z.object({
subscriptionId: zod_1.z.union([zod_1.z.string(), zod_1.z.null()]).optional(),
invoiceType: zod_1.z.enum([
'SUBSCRIPTION_INITIAL',
'SUBSCRIPTION_RENEWAL',
'TRIAL_CONVERSION',
'SUBSCRIPTION_UPGRADE',
'SUBSCRIPTION_DOWNGRADE_CREDIT',
'USAGE_BASED',
'ONE_TIME',
'MANUAL',
'CORRECTION',
'CANCELLATION_FEE',
]),
currency: zod_1.z.string().min(3).max(8).optional(),
dueAt: nullableDate,
isSubscriptionBlocking: zod_1.z.boolean().optional(),
adminReason: zod_1.z.union([zod_1.z.string().max(500), zod_1.z.null()]).optional(),
lineItems: zod_1.z.array(exports.billingLineItemInputSchema).min(1),
});
exports.payBillingInvoiceSchema = zod_1.z.object({
amount: zod_1.z.number().int().positive().optional(),
paymentMethodId: zod_1.z.union([zod_1.z.string(), zod_1.z.null()]).optional(),
providerPaymentId: zod_1.z.union([zod_1.z.string(), zod_1.z.null()]).optional(),
});
exports.retryBillingInvoiceSchema = zod_1.z.object({
paymentMethodId: zod_1.z.union([zod_1.z.string(), zod_1.z.null()]).optional(),
});
exports.billingReasonSchema = zod_1.z.object({
reason: zod_1.z.string().min(1).max(500),
});
exports.billingCreditNoteSchema = zod_1.z.object({
amount: zod_1.z.number().int().positive(),
reason: zod_1.z.string().min(1).max(500),
});
exports.billingRefundSchema = zod_1.z.object({
amount: zod_1.z.number().int().positive(),
reason: zod_1.z.string().min(1).max(500),
});
exports.homepageUpdateSchema = zod_1.z.object({
homepage: exports.marketplaceHomepageConfigSchema,
});
exports.pricingUpdateSchema = zod_1.z.object({
entries: zod_1.z.array(zod_1.z.object({
plan: zod_1.z.enum(['STARTER', 'GROWTH', 'PRO']),
billingPeriod: zod_1.z.enum(['MONTHLY', 'ANNUAL']),
amount: zod_1.z.number().int().positive(),
})).min(1),
});
const planFeatureSchema = zod_1.z.object({
plan: zod_1.z.enum(['STARTER', 'GROWTH', 'PRO']),
label: zod_1.z.string().min(1).max(120),
sortOrder: zod_1.z.number().int().min(0).default(0),
});
exports.planFeatureCreateSchema = planFeatureSchema;
exports.planFeatureUpdateSchema = planFeatureSchema.partial();
exports.promotionCreateSchema = zod_1.z.object({
code: zod_1.z.string().min(2).max(30).regex(/^[A-Z0-9_-]+$/, 'Code must be uppercase A-Z, 0-9, _ or -'),
name: zod_1.z.string().min(1).max(100),
description: zod_1.z.string().max(500).optional(),
discountType: zod_1.z.enum(['PERCENTAGE', 'FIXED']),
discountValue: zod_1.z.number().int().positive(),
plans: zod_1.z.array(zod_1.z.enum(['STARTER', 'GROWTH', 'PRO'])),
periods: zod_1.z.array(zod_1.z.enum(['MONTHLY', 'ANNUAL'])),
maxUses: zod_1.z.number().int().positive().nullable().optional(),
validFrom: zod_1.z.string().datetime(),
validUntil: zod_1.z.string().datetime().nullable().optional(),
isActive: zod_1.z.boolean().default(true),
});
exports.promotionUpdateSchema = exports.promotionCreateSchema.partial();
exports.planFeatureIdParamSchema = zod_1.z.object({ featureId: zod_1.z.string().min(1) });
exports.promotionIdParamSchema = zod_1.z.object({ promotionId: zod_1.z.string().min(1) });
const employeeRoleSchema = zod_1.z.enum(['OWNER', 'MANAGER', 'AGENT']);
const planSchema = zod_1.z.enum(['STARTER', 'GROWTH', 'PRO']);
const menuItemTypeSchema = zod_1.z.enum(['INTERNAL_PAGE', 'EXTERNAL_LINK', 'PARENT_MENU', 'SECTION_LABEL', 'DIVIDER']);
exports.menuItemSchema = zod_1.z.object({
systemKey: zod_1.z.string().trim().min(1).max(120).optional().nullable(),
label: zod_1.z.string().trim().min(1).max(120),
itemType: menuItemTypeSchema,
routeOrUrl: zod_1.z.string().trim().max(400).optional().nullable(),
icon: zod_1.z.string().trim().max(120).optional().nullable(),
parentId: zod_1.z.string().trim().min(1).optional().nullable(),
displayOrder: zod_1.z.number().int().min(0).default(0),
openInNewTab: zod_1.z.boolean().default(false),
isRequired: zod_1.z.boolean().default(false),
isActive: zod_1.z.boolean().default(true),
roles: zod_1.z.array(employeeRoleSchema).default([]),
subscriptionPlans: zod_1.z.array(zod_1.z.object({
plan: planSchema,
displayOrder: zod_1.z.number().int().min(0).optional(),
isActive: zod_1.z.boolean().optional(),
})).default([]),
companyAssignments: zod_1.z.array(zod_1.z.object({
companyId: zod_1.z.string().trim().min(1),
displayOrder: zod_1.z.number().int().min(0).optional(),
isActive: zod_1.z.boolean().optional(),
})).default([]),
});
exports.menuItemStatusSchema = zod_1.z.object({
isActive: zod_1.z.boolean(),
});
exports.menuPlanAssignmentsSchema = zod_1.z.object({
assignments: zod_1.z.array(zod_1.z.object({
plan: planSchema,
displayOrder: zod_1.z.number().int().min(0).optional(),
isActive: zod_1.z.boolean().optional(),
})).min(1),
});
exports.menuCompanyAssignmentsSchema = zod_1.z.object({
assignments: zod_1.z.array(zod_1.z.object({
companyId: zod_1.z.string().trim().min(1),
displayOrder: zod_1.z.number().int().min(0).optional(),
isActive: zod_1.z.boolean().optional(),
})).min(1),
});
exports.menuPreviewSchema = zod_1.z.object({
companyId: zod_1.z.string().trim().min(1),
role: employeeRoleSchema,
});
exports.menuAuditLogQuerySchema = zod_1.z.object({
page: zod_1.z.coerce.number().int().min(1).default(1),
pageSize: zod_1.z.coerce.number().int().min(1).max(100).default(20),
});
exports.menuItemIdParamSchema = zod_1.z.object({
id: zod_1.z.string().min(1),
});
exports.menuPlanParamSchema = zod_1.z.object({
id: zod_1.z.string().min(1),
plan: planSchema,
});
exports.menuCompanyParamSchema = zod_1.z.object({
id: zod_1.z.string().min(1),
companyId: zod_1.z.string().min(1),
});
//# sourceMappingURL=admin.schemas.js.map
File diff suppressed because one or more lines are too long
+174
View File
@@ -0,0 +1,174 @@
import * as repo from './admin.repo';
import * as billingService from './admin.billing.service';
export declare function login(email: string, password: string, totpCode?: string, recoveryCode?: string): Promise<{
token: string;
admin: Omit<any, "passwordHash" | "totpSecret">;
} | {
readonly totpRequired: true;
readonly invalidTotp?: undefined;
} | {
readonly invalidTotp: true;
readonly totpRequired?: undefined;
} | null>;
export declare function setupTotp(adminId: string, email: string): Promise<{
secret: string;
qrCode: string;
}>;
export declare function verifyTotp(adminId: string, code: string): Promise<false | {
recoveryCodes: string[];
token: string;
admin: Omit<any, "passwordHash" | "totpSecret">;
}>;
export declare function regenerateRecoveryCodes(adminId: string): Promise<{
recoveryCodes: string[];
}>;
export declare function forgotPassword(email: string): Promise<void>;
export declare function resetPassword(token: string, password: string): Promise<boolean>;
export declare function listCompanies(query: {
q?: string;
status?: string;
plan?: string;
page: number;
pageSize: number;
}): Promise<{
data: unknown[];
total: number;
page: number;
pageSize: number;
totalPages: number;
}>;
export declare function getCompany(id: string): any;
export declare function updateCompany(id: string, body: any, adminId: string, ip?: string): Promise<any>;
export declare function setCompanyStatus(id: string, status: string, reason: string | undefined, adminId: string, ip?: string): Promise<any>;
export declare function deleteCompany(id: string, adminId: string, ip?: string): Promise<void>;
export declare function impersonateCompany(id: string, adminId: string, ip?: string, reason?: string, durationMinutes?: number): Promise<{
token: string;
expiresIn: number;
impersonation: {
companyId: string;
reason: string | undefined;
durationMinutes: number;
};
}>;
export declare function listRenters(query: {
q?: string;
blocked?: string;
page: number;
pageSize: number;
}): Promise<{
data: unknown[];
total: number;
page: number;
pageSize: number;
totalPages: number;
}>;
export declare function setRenterActive(id: string, isActive: boolean): any;
export declare function getPlatformMetrics(): Promise<{
totalCompanies: any;
activeCompanies: any;
trialingCompanies: any;
suspendedCompanies: any;
totalRenters: any;
totalReservations: any;
}>;
export declare function listNotifications(query: {
channel?: string;
status?: string;
companyId?: string;
page: number;
pageSize: number;
}): Promise<{
data: unknown[];
total: number;
page: number;
pageSize: number;
totalPages: number;
}>;
export declare function getAuditLogs(query: {
adminId?: string;
action?: string;
companyId?: string;
entityId?: string;
page: number;
pageSize: number;
}): Promise<{
data: unknown[];
total: number;
page: number;
pageSize: number;
totalPages: number;
}>;
export declare function listAdmins(): Promise<any>;
export declare function createAdmin(body: {
email: string;
firstName: string;
lastName: string;
role: string;
password: string;
permissions?: any[];
}): Promise<Omit<any, "passwordHash" | "totpSecret">>;
export declare function updateAdmin(id: string, body: {
email?: string;
firstName?: string;
lastName?: string;
role?: string;
password?: string;
isActive?: boolean;
}): Promise<Omit<any, "passwordHash" | "totpSecret">>;
export declare function updateAdminRole(id: string, role: string): any;
export declare function updateAdminPermissions(id: string, permissions: any[]): Promise<Omit<any, "passwordHash" | "totpSecret">>;
export declare function getBilling(query: {
q?: string;
status?: string;
plan?: string;
page: number;
pageSize: number;
}): Promise<{
data: any[];
total: number;
page: number;
pageSize: number;
totalPages: number;
}>;
export declare function getCompanyInvoices(companyId: string, query: {
page: number;
pageSize: number;
}): Promise<{
data: unknown[];
total: number;
page: number;
pageSize: number;
totalPages: number;
}>;
export declare function getInvoicePdf(invoiceId: string): Promise<{
pdfBuffer: Buffer<ArrayBufferLike>;
invoiceNumber: any;
}>;
export declare function getBillingAccountDetail(companyId: string): Promise<any>;
export declare function updateBillingAccount(billingAccountId: string, data: Parameters<typeof billingService.updateBillingAccount>[1], adminId: string, ip?: string): Promise<any>;
export declare function setDunningPaused(billingAccountId: string, paused: boolean, adminId: string, ip?: string): Promise<any>;
export declare function createBillingInvoice(billingAccountId: string, data: Parameters<typeof billingService.createDraftInvoice>[1], adminId: string, ip?: string): Promise<any>;
export declare function finalizeBillingInvoice(invoiceId: string, adminId: string, ip?: string): Promise<any>;
export declare function payBillingInvoice(invoiceId: string, data: Parameters<typeof billingService.payInvoice>[1], adminId: string, ip?: string): Promise<any>;
export declare function retryBillingInvoicePayment(invoiceId: string, data: Parameters<typeof billingService.retryInvoicePayment>[1], adminId: string, ip?: string): Promise<any>;
export declare function voidBillingInvoice(invoiceId: string, reason: string, adminId: string, ip?: string): Promise<any>;
export declare function markBillingInvoiceUncollectible(invoiceId: string, reason: string, adminId: string, ip?: string): Promise<any>;
export declare function issueBillingCreditNote(invoiceId: string, data: Parameters<typeof billingService.issueCreditNote>[1], adminId: string, ip?: string): Promise<any>;
export declare function issueBillingRefund(invoiceId: string, data: Parameters<typeof billingService.issueRefund>[1], adminId: string, ip?: string): Promise<any>;
export declare function getMarketplaceHomepage(): Promise<import("@rentaldrivego/types").MarketplaceHomepageConfig>;
export declare function updateMarketplaceHomepage(homepage: any, adminId: string, ip?: string): Promise<import("@rentaldrivego/types").MarketplaceHomepageConfig>;
export declare function getPricingConfigs(): any;
export declare function updatePricingConfigs(entries: {
plan: string;
billingPeriod: string;
amount: number;
}[], adminId: string, ip?: string): Promise<any>;
export declare function listPlanFeatures(): any;
export declare function createPlanFeature(data: Parameters<typeof repo.createPlanFeature>[0], adminId: string, ip?: string): Promise<any>;
export declare function updatePlanFeature(id: string, data: Parameters<typeof repo.updatePlanFeature>[1], adminId: string, ip?: string): Promise<any>;
export declare function deletePlanFeature(id: string, adminId: string, ip?: string): Promise<void>;
export declare function listPromotions(): any;
export declare function createPromotion(data: Parameters<typeof repo.createPromotion>[0], adminId: string, ip?: string): Promise<any>;
export declare function updatePromotion(id: string, data: Parameters<typeof repo.updatePromotion>[1], adminId: string, ip?: string): Promise<any>;
export declare function deletePromotion(id: string, adminId: string, ip?: string): Promise<void>;
//# sourceMappingURL=admin.service.d.ts.map
File diff suppressed because one or more lines are too long
+494
View File
@@ -0,0 +1,494 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.login = login;
exports.setupTotp = setupTotp;
exports.verifyTotp = verifyTotp;
exports.regenerateRecoveryCodes = regenerateRecoveryCodes;
exports.forgotPassword = forgotPassword;
exports.resetPassword = resetPassword;
exports.listCompanies = listCompanies;
exports.getCompany = getCompany;
exports.updateCompany = updateCompany;
exports.setCompanyStatus = setCompanyStatus;
exports.deleteCompany = deleteCompany;
exports.impersonateCompany = impersonateCompany;
exports.listRenters = listRenters;
exports.setRenterActive = setRenterActive;
exports.getPlatformMetrics = getPlatformMetrics;
exports.listNotifications = listNotifications;
exports.getAuditLogs = getAuditLogs;
exports.listAdmins = listAdmins;
exports.createAdmin = createAdmin;
exports.updateAdmin = updateAdmin;
exports.updateAdminRole = updateAdminRole;
exports.updateAdminPermissions = updateAdminPermissions;
exports.getBilling = getBilling;
exports.getCompanyInvoices = getCompanyInvoices;
exports.getInvoicePdf = getInvoicePdf;
exports.getBillingAccountDetail = getBillingAccountDetail;
exports.updateBillingAccount = updateBillingAccount;
exports.setDunningPaused = setDunningPaused;
exports.createBillingInvoice = createBillingInvoice;
exports.finalizeBillingInvoice = finalizeBillingInvoice;
exports.payBillingInvoice = payBillingInvoice;
exports.retryBillingInvoicePayment = retryBillingInvoicePayment;
exports.voidBillingInvoice = voidBillingInvoice;
exports.markBillingInvoiceUncollectible = markBillingInvoiceUncollectible;
exports.issueBillingCreditNote = issueBillingCreditNote;
exports.issueBillingRefund = issueBillingRefund;
exports.getMarketplaceHomepage = getMarketplaceHomepage;
exports.updateMarketplaceHomepage = updateMarketplaceHomepage;
exports.getPricingConfigs = getPricingConfigs;
exports.updatePricingConfigs = updatePricingConfigs;
exports.listPlanFeatures = listPlanFeatures;
exports.createPlanFeature = createPlanFeature;
exports.updatePlanFeature = updatePlanFeature;
exports.deletePlanFeature = deletePlanFeature;
exports.listPromotions = listPromotions;
exports.createPromotion = createPromotion;
exports.updatePromotion = updatePromotion;
exports.deletePromotion = deletePromotion;
const bcryptjs_1 = __importDefault(require("bcryptjs"));
const crypto_1 = __importDefault(require("crypto"));
const otplib_1 = require("otplib");
const tokens_1 = require("../../security/tokens");
const qrcode_1 = __importDefault(require("qrcode"));
const platformContentService_1 = require("../../services/platformContentService");
const notificationService_1 = require("../../services/notificationService");
const presenter = __importStar(require("./admin.presenter"));
const repo = __importStar(require("./admin.repo"));
const billingService = __importStar(require("./admin.billing.service"));
const ADMIN_RESET_TTL_MINUTES = 60;
const ADMIN_RECOVERY_CODE_COUNT = 10;
function generateRecoveryCode() {
const raw = crypto_1.default.randomBytes(9).toString('base64url').replace(/[^a-zA-Z0-9]/g, '').toUpperCase().slice(0, 12);
return `${raw.slice(0, 4)}-${raw.slice(4, 8)}-${raw.slice(8, 12)}`;
}
async function issueAdminRecoveryCodes(adminId) {
const codes = Array.from({ length: ADMIN_RECOVERY_CODE_COUNT }, generateRecoveryCode);
const hashes = await Promise.all(codes.map((code) => bcryptjs_1.default.hash(code, 12)));
await repo.replaceAdminRecoveryCodes(adminId, hashes);
await repo.createAuditLog({
adminUserId: adminId,
action: 'ADMIN_2FA_RECOVERY_CODES_ISSUED',
resource: 'AdminUser',
resourceId: adminId,
});
return codes;
}
async function consumeAdminRecoveryCode(adminId, code) {
const normalized = code.trim().toUpperCase();
if (!normalized)
return false;
const codes = await repo.listUnusedAdminRecoveryCodes(adminId);
for (const candidate of codes) {
if (await bcryptjs_1.default.compare(normalized, candidate.codeHash)) {
await repo.markAdminRecoveryCodeUsed(candidate.id);
await repo.createAuditLog({
adminUserId: adminId,
action: 'ADMIN_2FA_RECOVERY_CODE_USED',
resource: 'AdminUser',
resourceId: adminId,
});
return true;
}
}
return false;
}
function signAdminToken(adminId, last2faAt) {
return (0, tokens_1.signActorToken)(adminId, 'admin', { expiresIn: '8h', last2faAt });
}
function toAuditJson(value) {
return JSON.parse(JSON.stringify(value));
}
function ensureAdminBasePath(baseUrl) {
try {
const url = new URL(baseUrl);
const pathname = url.pathname.replace(/\/$/, '');
if (!pathname.endsWith('/admin'))
url.pathname = `${pathname}/admin`;
return url.toString().replace(/\/$/, '');
}
catch {
const trimmed = baseUrl.replace(/\/$/, '');
return trimmed.endsWith('/admin') ? trimmed : `${trimmed}/admin`;
}
}
async function login(email, password, totpCode, recoveryCode) {
const admin = await repo.findAdminByEmail(email);
if (!admin || !admin.isActive)
return null;
const valid = await bcryptjs_1.default.compare(password, admin.passwordHash);
if (!valid)
return null;
if (admin.totpEnabled) {
if (!totpCode && !recoveryCode)
return { totpRequired: true };
const validTotp = totpCode
? otplib_1.authenticator.verify({ token: totpCode, secret: admin.totpSecret })
: false;
const validRecoveryCode = !validTotp && recoveryCode
? await consumeAdminRecoveryCode(admin.id, recoveryCode)
: false;
if (!validTotp && !validRecoveryCode) {
return { invalidTotp: true };
}
}
await repo.updateAdminLastLogin(admin.id);
await repo.createAuditLog({
adminUserId: admin.id,
action: 'ADMIN_LOGIN',
resource: 'AdminUser',
resourceId: admin.id,
});
return presenter.presentAdminSession(admin, signAdminToken(admin.id, admin.totpEnabled ? Date.now() : undefined));
}
async function setupTotp(adminId, email) {
const secret = otplib_1.authenticator.generateSecret();
await repo.updateAdminTotpSecret(adminId, secret);
const otpauth = otplib_1.authenticator.keyuri(email, 'RentalDriveGo Admin', secret);
const qrCode = await qrcode_1.default.toDataURL(otpauth);
return { secret, qrCode };
}
async function verifyTotp(adminId, code) {
const admin = await repo.findAdminByIdOrThrow(adminId);
if (!admin.totpSecret)
return false;
const valid = otplib_1.authenticator.verify({ token: code, secret: admin.totpSecret });
if (!valid)
return false;
await repo.enableAdminTotp(adminId);
await repo.createAuditLog({
adminUserId: adminId,
action: 'ADMIN_2FA_VERIFIED',
resource: 'AdminUser',
resourceId: adminId,
});
const recoveryCodes = await issueAdminRecoveryCodes(adminId);
return {
...presenter.presentAdminSession({ ...admin, totpEnabled: true }, signAdminToken(adminId, Date.now())),
recoveryCodes,
};
}
async function regenerateRecoveryCodes(adminId) {
return { recoveryCodes: await issueAdminRecoveryCodes(adminId) };
}
async function forgotPassword(email) {
const admin = await repo.findAdminByEmail(email);
if (!admin || !admin.isActive)
return;
const rawToken = crypto_1.default.randomBytes(32).toString('hex');
const expiresAt = new Date(Date.now() + ADMIN_RESET_TTL_MINUTES * 60 * 1000);
await repo.setAdminPasswordReset(admin.id, rawToken, expiresAt);
const adminUrl = ensureAdminBasePath(process.env.ADMIN_URL ?? process.env.NEXT_PUBLIC_ADMIN_URL ?? 'http://localhost:3000/admin');
const resetUrl = `${adminUrl}/reset-password?token=${rawToken}`;
await (0, notificationService_1.sendTransactionalEmail)({
to: admin.email,
subject: 'Reset your RentalDriveGo admin password',
html: `<p>Hi ${admin.firstName},</p><p><a href="${resetUrl}">Reset password</a></p><p>Expires in ${ADMIN_RESET_TTL_MINUTES} minutes.</p>`,
text: `Hi ${admin.firstName},\n\nReset here: ${resetUrl}\n\nExpires in ${ADMIN_RESET_TTL_MINUTES} minutes.`,
}).catch((err) => console.error('[AdminForgotPassword]', err?.message));
}
async function resetPassword(token, password) {
const admin = await repo.findAdminByResetToken(token);
if (!admin)
return false;
await repo.updateAdminPassword(admin.id, await bcryptjs_1.default.hash(password, 12));
return true;
}
async function listCompanies(query) {
const { data, total } = await repo.listCompaniesPage(query);
return presenter.presentPaginated(data, total, query.page, query.pageSize);
}
function getCompany(id) {
return repo.getCompanyDetail(id);
}
async function updateCompany(id, body, adminId, ip) {
const before = await repo.getCompanyUpdateSnapshot(id);
const updated = await repo.applyCompanyUpdate(id, body, before);
await repo.createAuditLog({
adminUserId: adminId,
action: 'UPDATE_COMPANY',
resource: 'Company',
resourceId: id,
companyId: id,
before: toAuditJson(before),
after: toAuditJson(body),
ipAddress: ip,
});
return updated;
}
async function setCompanyStatus(id, status, reason, adminId, ip) {
const before = await repo.getCompanyUpdateSnapshot(id);
const updated = await repo.updateCompanyStatus(id, status);
await repo.createAuditLog({
adminUserId: adminId,
action: `SET_COMPANY_STATUS_${status}`,
resource: 'Company',
resourceId: id,
companyId: id,
before: { status: before.status },
after: { status },
note: reason,
ipAddress: ip,
});
return updated;
}
async function deleteCompany(id, adminId, ip) {
await repo.deleteCompany(id);
await repo.createAuditLog({
adminUserId: adminId,
action: 'DELETE_COMPANY',
resource: 'Company',
resourceId: id,
ipAddress: ip,
});
}
async function impersonateCompany(id, adminId, ip, reason, durationMinutes = 15) {
const company = await repo.getCompanyForImpersonation(id);
const ttlMinutes = Math.min(Math.max(durationMinutes, 1), 30);
const employeeId = company.employees[0]?.id;
if (!employeeId)
throw new Error('Company has no employee account to impersonate');
const token = (0, tokens_1.signActorToken)(employeeId, 'employee', { expiresIn: `${ttlMinutes}m` });
await repo.createAuditLog({
adminUserId: adminId,
action: 'IMPERSONATE_COMPANY',
resource: 'Company',
resourceId: id,
companyId: id,
note: reason,
before: { originalAdminId: adminId },
after: { targetCompanyId: id, durationMinutes: ttlMinutes },
ipAddress: ip,
});
return { token, expiresIn: ttlMinutes * 60, impersonation: { companyId: id, reason, durationMinutes: ttlMinutes } };
}
async function listRenters(query) {
const { data, total } = await repo.listRentersPage(query);
return presenter.presentPaginated(data, total, query.page, query.pageSize);
}
function setRenterActive(id, isActive) {
return repo.updateRenterActive(id, isActive);
}
function getPlatformMetrics() {
return repo.getPlatformMetricCounts();
}
async function listNotifications(query) {
const { data, total } = await repo.listNotificationsPage(query);
return presenter.presentPaginated(data, total, query.page, query.pageSize);
}
async function getAuditLogs(query) {
const { data, total } = await repo.listAuditLogsPage(query);
return presenter.presentPaginated(data, total, query.page, query.pageSize);
}
async function listAdmins() {
const admins = await repo.listAdmins();
return admins.map((admin) => presenter.presentAdminUser(admin));
}
async function createAdmin(body) {
const admin = await repo.createAdmin({
...body,
passwordHash: await bcryptjs_1.default.hash(body.password, 12),
});
return presenter.presentAdminUser(admin);
}
async function updateAdmin(id, body) {
const admin = await repo.updateAdmin(id, {
...(body.email !== undefined ? { email: body.email } : {}),
...(body.firstName !== undefined ? { firstName: body.firstName } : {}),
...(body.lastName !== undefined ? { lastName: body.lastName } : {}),
...(body.role !== undefined ? { role: body.role } : {}),
...(body.isActive !== undefined ? { isActive: body.isActive } : {}),
...(body.password ? { passwordHash: await bcryptjs_1.default.hash(body.password, 12) } : {}),
});
return presenter.presentAdminUser(admin);
}
function updateAdminRole(id, role) {
return repo.updateAdminRole(id, role);
}
async function updateAdminPermissions(id, permissions) {
const admin = await repo.replaceAdminPermissions(id, permissions);
return presenter.presentAdminUser(admin);
}
async function getBilling(query) {
const { data, total, stats } = await billingService.listBillingAccounts(query);
return presenter.presentPaginated(data, total, query.page, query.pageSize, { stats });
}
async function getCompanyInvoices(companyId, query) {
const detail = await billingService.getBillingAccountDetail(companyId);
const start = (query.page - 1) * query.pageSize;
const end = start + query.pageSize;
const data = detail.invoices.slice(start, end);
const total = detail.invoices.length;
return presenter.presentPaginated(data, total, query.page, query.pageSize);
}
async function getInvoicePdf(invoiceId) {
return billingService.getInvoicePdf(invoiceId);
}
function getBillingAccountDetail(companyId) {
return billingService.getBillingAccountDetail(companyId);
}
function updateBillingAccount(billingAccountId, data, adminId, ip) {
return billingService.updateBillingAccount(billingAccountId, data, adminId, ip);
}
function setDunningPaused(billingAccountId, paused, adminId, ip) {
return billingService.setDunningPaused(billingAccountId, paused, adminId, ip);
}
function createBillingInvoice(billingAccountId, data, adminId, ip) {
return billingService.createDraftInvoice(billingAccountId, data, adminId, ip);
}
function finalizeBillingInvoice(invoiceId, adminId, ip) {
return billingService.finalizeInvoice(invoiceId, adminId, ip);
}
function payBillingInvoice(invoiceId, data, adminId, ip) {
return billingService.payInvoice(invoiceId, data, adminId, ip);
}
function retryBillingInvoicePayment(invoiceId, data, adminId, ip) {
return billingService.retryInvoicePayment(invoiceId, data, adminId, ip);
}
function voidBillingInvoice(invoiceId, reason, adminId, ip) {
return billingService.voidInvoice(invoiceId, reason, adminId, ip);
}
function markBillingInvoiceUncollectible(invoiceId, reason, adminId, ip) {
return billingService.markInvoiceUncollectible(invoiceId, reason, adminId, ip);
}
function issueBillingCreditNote(invoiceId, data, adminId, ip) {
return billingService.issueCreditNote(invoiceId, data, adminId, ip);
}
function issueBillingRefund(invoiceId, data, adminId, ip) {
return billingService.issueRefund(invoiceId, data, adminId, ip);
}
function getMarketplaceHomepage() {
return (0, platformContentService_1.getMarketplaceHomepageContent)();
}
async function updateMarketplaceHomepage(homepage, adminId, ip) {
const saved = await (0, platformContentService_1.saveMarketplaceHomepageContent)(homepage);
await repo.createAuditLog({
adminUserId: adminId,
action: 'UPDATE',
resource: 'MarketplaceHomepage',
after: toAuditJson(saved),
ipAddress: ip,
userAgent: undefined,
});
return saved;
}
function getPricingConfigs() {
return repo.listPricingConfigs();
}
async function updatePricingConfigs(entries, adminId, ip) {
const results = await Promise.all(entries.map((e) => repo.upsertPricingConfig(e.plan, e.billingPeriod, e.amount, adminId)));
await repo.createAuditLog({
adminUserId: adminId,
action: 'UPDATE',
resource: 'PricingConfig',
after: toAuditJson(results),
ipAddress: ip,
userAgent: undefined,
});
return results;
}
function listPlanFeatures() {
return repo.listPlanFeatures();
}
async function createPlanFeature(data, adminId, ip) {
const feature = await repo.createPlanFeature(data);
await repo.createAuditLog({
adminUserId: adminId,
action: 'CREATE',
resource: 'PlanFeature',
after: toAuditJson(feature),
ipAddress: ip,
userAgent: undefined,
});
return feature;
}
async function updatePlanFeature(id, data, adminId, ip) {
const feature = await repo.updatePlanFeature(id, data);
await repo.createAuditLog({
adminUserId: adminId,
action: 'UPDATE',
resource: 'PlanFeature',
resourceId: id,
after: toAuditJson(feature),
ipAddress: ip,
userAgent: undefined,
});
return feature;
}
async function deletePlanFeature(id, adminId, ip) {
await repo.deletePlanFeature(id);
await repo.createAuditLog({
adminUserId: adminId,
action: 'DELETE',
resource: 'PlanFeature',
resourceId: id,
ipAddress: ip,
userAgent: undefined,
});
}
// ─── Promotions ────────────────────────────────────────────────────────────
function listPromotions() {
return repo.listPromotions();
}
async function createPromotion(data, adminId, ip) {
const promo = await repo.createPromotion({ ...data, createdBy: adminId });
await repo.createAuditLog({
adminUserId: adminId, action: 'CREATE', resource: 'PricingPromotion',
after: toAuditJson(promo), ipAddress: ip, userAgent: undefined,
});
return promo;
}
async function updatePromotion(id, data, adminId, ip) {
const promo = await repo.updatePromotion(id, data);
await repo.createAuditLog({
adminUserId: adminId, action: 'UPDATE', resource: 'PricingPromotion',
after: toAuditJson(promo), ipAddress: ip, userAgent: undefined,
});
return promo;
}
async function deletePromotion(id, adminId, ip) {
await repo.deletePromotion(id);
await repo.createAuditLog({
adminUserId: adminId, action: 'DELETE', resource: 'PricingPromotion',
entityId: id, ipAddress: ip, userAgent: undefined,
});
}
//# sourceMappingURL=admin.service.js.map
File diff suppressed because one or more lines are too long