"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AdminService = void 0; const common_1 = require("@nestjs/common"); const config_1 = require("@nestjs/config"); const crypto_1 = require("crypto"); const prisma_service_1 = require("../prisma/prisma.service"); const mail_service_1 = require("../mail/mail.service"); const memory_mail_service_1 = require("../mail/memory-mail.service"); const password_1 = require("../auth/password"); const tokens_1 = require("../common/tokens"); const pagination_1 = require("../common/pagination"); const WEEK_MS = 7 * 24 * 60 * 60 * 1000; let AdminService = class AdminService { constructor(prisma, cfg, mail) { this.prisma = prisma; this.cfg = cfg; this.mail = mail; } async inviteStaff(email) { const exists = await this.prisma.account.findUnique({ where: { email } }); if (exists) throw new common_1.BadRequestException('Email уже зарегистрирован'); const tempHash = await (0, password_1.hashPassword)((0, crypto_1.randomBytes)(24).toString('hex')); const pepper = this.cfg.get('TOKEN_PEPPER') ?? 'TOKPEPPER'; const raw = (0, tokens_1.newToken)(); const acc = await this.prisma.account.create({ data: { email, passwordHash: tempHash, role: 'staff_dms', status: 'active', emailVerified: true }, }); await this.prisma.accountToken.create({ data: { accountId: acc.id, purpose: 'password_reset', tokenHash: (0, tokens_1.hashToken)(raw, pepper), expiresAt: new Date(Date.now() + WEEK_MS) }, }); const base = this.cfg.get('FRONTEND_URL') ?? 'https://international.stankin.ru'; await this.mail.send({ to: email, subject: 'Приглашение в систему — МГТУ «СТАНКИН»', text: `Вас пригласили как сотрудника управления международных связей. Задайте пароль для входа:\n${base}/set-password?token=${raw}\nСсылка действует 7 дней.`, }); return { accountId: acc.id }; } async setBlocked(accountId, blocked) { return this.prisma.account.update({ where: { id: accountId }, data: { status: blocked ? 'blocked' : 'active' }, select: { id: true, status: true }, }); } async listAccounts(adminId, q) { return this.prisma.withContext({ accountId: adminId, role: 'admin' }, async (tx) => { const where = {}; if (q.role) where.role = q.role; if (q.status) where.status = q.status; const [items, total] = await Promise.all([ tx.account.findMany({ where, orderBy: { createdAt: 'desc' }, skip: q.offset, take: q.limit, select: { id: true, email: true, role: true, status: true, createdAt: true, person: { select: { surname: true, givenName: true } }, }, }), tx.account.count({ where }), ]); return (0, pagination_1.paginated)(items, total, q.limit, q.offset); }); } mailOutbox(limit = 50) { if (!(this.mail instanceof memory_mail_service_1.MemoryMailService)) return { enabled: false, items: [] }; const items = this.mail.outbox.slice(-limit).reverse().map((m) => ({ to: m.to, subject: m.subject, sentAt: m.sentAt, link: (m.text.match(/https?:\/\/\S+/) || [])[0] ?? null, })); return { enabled: true, items }; } async exportAccountsCsv(adminId) { const rows = await this.prisma.withContext({ accountId: adminId, role: 'admin' }, (tx) => tx.account.findMany({ orderBy: { createdAt: 'asc' }, select: { email: true, role: true, status: true, emailVerified: true, createdAt: true, person: { select: { surname: true, givenName: true, patronymic: true, birthDate: true, applications: { orderBy: { submittedAt: 'desc' }, take: 1, select: { level: true, status: true, programText: true, program: { select: { titleRu: true } } } }, }, }, }, })); const cell = (v) => { const s = v == null ? '' : v instanceof Date ? v.toISOString().slice(0, 19).replace('T', ' ') : String(v); return /[";\n\r]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s; }; const head = ['Email', 'Роль', 'Статус', 'Email подтверждён', 'Создан (UTC)', 'Фамилия', 'Имя', 'Отчество', 'Дата рождения', 'Уровень', 'Программа', 'Статус заявки']; const lines = rows.map((r) => { const p = r.person, a = p?.applications[0]; return [r.email, r.role, r.status, r.emailVerified ? 'да' : 'нет', r.createdAt, p?.surname, p?.givenName, p?.patronymic, p?.birthDate ? p.birthDate.toISOString().slice(0, 10) : '', a?.level, a?.program?.titleRu ?? a?.programText, a?.status].map(cell).join(';'); }); return '\uFEFF' + [head.join(';'), ...lines].join('\r\n') + '\r\n'; } async stats(adminId) { return this.prisma.withContext({ accountId: adminId, role: 'admin' }, async (tx) => { const [apps, dorms, tickets, accounts, appsTotal, dormsTotal, ticketsTotal, accountsTotal] = await Promise.all([ tx.application.groupBy({ by: ['status'], _count: { _all: true } }), tx.dormApplication.groupBy({ by: ['status'], _count: { _all: true } }), tx.ticket.groupBy({ by: ['status'], _count: { _all: true } }), tx.account.groupBy({ by: ['status'], _count: { _all: true } }), tx.application.count(), tx.dormApplication.count(), tx.ticket.count(), tx.account.count(), ]); const toMap = (rows) => rows.reduce((a, r) => { a[r.status] = r._count._all; return a; }, {}); const accByRole = await tx.account.groupBy({ by: ['role'], _count: { _all: true } }); return { applications: { total: appsTotal, byStatus: toMap(apps) }, dorms: { total: dormsTotal, byStatus: toMap(dorms) }, tickets: { total: ticketsTotal, byStatus: toMap(tickets) }, accounts: { total: accountsTotal, byStatus: toMap(accounts), byRole: toMap(accByRole.map((r) => ({ status: r.role, _count: r._count }))) }, }; }); } }; exports.AdminService = AdminService; exports.AdminService = AdminService = __decorate([ (0, common_1.Injectable)(), __metadata("design:paramtypes", [prisma_service_1.PrismaService, config_1.ConfigService, mail_service_1.MailService]) ], AdminService); //# sourceMappingURL=admin.service.js.map