archetecture security fix
This commit is contained in:
+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
|
||||
Reference in New Issue
Block a user