archetecture security fix
This commit is contained in:
@@ -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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"}
|
||||
+1227
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
@@ -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
@@ -0,0 +1,3 @@
|
||||
declare const router: import("express-serve-static-core").Router;
|
||||
export default router;
|
||||
//# sourceMappingURL=admin.routes.d.ts.map
|
||||
@@ -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
@@ -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
+1260
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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
@@ -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
@@ -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
Reference in New Issue
Block a user