Initial SMM panel implementation

This commit is contained in:
diyaa
2026-08-13 19:05:47 +02:00
commit 7717a674c7
64 changed files with 8626 additions and 0 deletions
+130
View File
@@ -0,0 +1,130 @@
const { getDb } = require('../config/db');
const { AppError } = require('../middleware/error-handler');
const { formatMoney, parseMoney } = require('../utils/money');
const { fetchProviderServices } = require('./provider.service');
function parseMarkupPercent(value = process.env.DEFAULT_MARKUP_PERCENT || '0') {
const raw = String(value).trim();
if (!/^\d+(?:\.\d{1,4})?$/.test(raw)) {
throw new AppError('Invalid markup percent', 400, 'INVALID_MARKUP_PERCENT');
}
return raw;
}
function calculateSellPrice(providerPrice, markupPercent = process.env.DEFAULT_MARKUP_PERCENT || '0') {
const price = parseMoney(String(providerPrice));
const markup = BigInt(Math.round(Number(markupPercent) * 10000));
const sellPrice = (price * (1000000n + markup) + 500000n) / 1000000n;
return formatMoney(sellPrice);
}
function normalizeProviderService(service, markupPercent) {
const providerServiceId = String(service.service ?? service.provider_service_id ?? '').trim();
const name = String(service.name ?? '').trim();
const category = String(service.category ?? 'Uncategorized').trim() || 'Uncategorized';
const providerPrice = String(service.rate ?? service.price ?? service.provider_price ?? '').trim();
const min = Number(service.min);
const max = Number(service.max);
if (!providerServiceId || !name || !providerPrice || !Number.isInteger(min) || !Number.isInteger(max)) {
throw new AppError('Provider service item is invalid', 502, 'INVALID_PROVIDER_SERVICE');
}
return {
provider_service_id: providerServiceId,
name,
category,
provider_price: formatMoney(parseMoney(providerPrice)),
markup_percent: markupPercent,
sell_price: calculateSellPrice(providerPrice, markupPercent),
min,
max,
is_active: true
};
}
async function syncServicesFromProvider(markupPercentInput) {
const markupPercent = parseMarkupPercent(markupPercentInput);
const providerServices = await fetchProviderServices();
const services = providerServices.map((service) => normalizeProviderService(service, markupPercent));
const db = getDb();
await db.transaction(async (trx) => {
for (const service of services) {
const existing = await trx('services')
.select('id')
.where({ provider_service_id: service.provider_service_id })
.first();
if (existing) {
await trx('services').where({ id: existing.id }).update(service);
} else {
await trx('services').insert(service);
}
}
});
return {
synced: services.length
};
}
async function listActiveServices() {
return getDb()('services')
.select('id', 'provider_service_id', 'name', 'category', 'sell_price', 'min', 'max', 'is_active', 'updated_at')
.where({ is_active: true })
.orderBy('category', 'asc')
.orderBy('name', 'asc');
}
async function listAdminServices() {
return getDb()('services')
.select('id', 'provider_service_id', 'name', 'category', 'provider_price', 'markup_percent', 'sell_price', 'min', 'max', 'is_active', 'updated_at')
.orderBy('category', 'asc')
.orderBy('name', 'asc');
}
async function updateService(serviceId, input) {
const db = getDb();
const service = await db('services').where({ id: serviceId }).first();
if (!service) {
throw new AppError('Service not found', 404, 'SERVICE_NOT_FOUND');
}
const update = {};
if (input.name !== undefined) update.name = input.name;
if (input.category !== undefined) update.category = input.category;
if (input.min !== undefined) update.min = input.min;
if (input.max !== undefined) update.max = input.max;
if (input.is_active !== undefined) update.is_active = input.is_active;
if (input.sell_price !== undefined) update.sell_price = formatMoney(parseMoney(input.sell_price));
if (input.markup_percent !== undefined) {
update.markup_percent = parseMarkupPercent(input.markup_percent);
update.sell_price = calculateSellPrice(service.provider_price, update.markup_percent);
}
if (Object.keys(update).length === 0) {
throw new AppError('No service changes provided', 400, 'NO_SERVICE_CHANGES');
}
await db('services').where({ id: serviceId }).update(update);
return db('services')
.select('id', 'provider_service_id', 'name', 'category', 'provider_price', 'markup_percent', 'sell_price', 'min', 'max', 'is_active', 'updated_at')
.where({ id: serviceId })
.first();
}
module.exports = {
calculateSellPrice,
listActiveServices,
listAdminServices,
syncServicesFromProvider,
updateService
};