build: include production build artifacts in repo
This commit is contained in:
Vendored
+69
@@ -0,0 +1,69 @@
|
||||
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);
|
||||
};
|
||||
import { Controller, Get, Module } from "@nestjs/common";
|
||||
import { APP_FILTER, APP_GUARD } from "@nestjs/core";
|
||||
import { ThrottlerGuard, ThrottlerModule } from "@nestjs/throttler";
|
||||
import { AuthModule } from "./auth/auth.controller.js";
|
||||
import { CatalogModule } from "./catalog/catalog.module.js";
|
||||
import { CommonModule } from "./common/common.module.js";
|
||||
import { PrismaExceptionFilter } from "./common/prisma-exception.filter.js";
|
||||
import { ContactModule } from "./contact/contact.module.js";
|
||||
import { DashboardModule } from "./dashboard/dashboard.module.js";
|
||||
import { MediaModule } from "./media/media.controller.js";
|
||||
import { PrismaModule, PrismaService } from "./prisma/prisma.service.js";
|
||||
import { PublicModule } from "./public/public.module.js";
|
||||
import { SectionsModule } from "./sections/sections.module.js";
|
||||
import { UsersModule } from "./users/users.controller.js";
|
||||
let HealthController = class HealthController {
|
||||
prisma;
|
||||
constructor(prisma) {
|
||||
this.prisma = prisma;
|
||||
}
|
||||
async health() {
|
||||
await this.prisma.$queryRaw `SELECT 1`;
|
||||
return { ok: true };
|
||||
}
|
||||
};
|
||||
__decorate([
|
||||
Get(),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", []),
|
||||
__metadata("design:returntype", Promise)
|
||||
], HealthController.prototype, "health", null);
|
||||
HealthController = __decorate([
|
||||
Controller("health"),
|
||||
__metadata("design:paramtypes", [PrismaService])
|
||||
], HealthController);
|
||||
let AppModule = class AppModule {
|
||||
};
|
||||
AppModule = __decorate([
|
||||
Module({
|
||||
imports: [
|
||||
ThrottlerModule.forRoot([{ ttl: 60_000, limit: 300 }]),
|
||||
PrismaModule,
|
||||
CommonModule,
|
||||
AuthModule,
|
||||
UsersModule,
|
||||
MediaModule,
|
||||
CatalogModule,
|
||||
SectionsModule,
|
||||
ContactModule,
|
||||
PublicModule,
|
||||
DashboardModule,
|
||||
],
|
||||
controllers: [HealthController],
|
||||
providers: [
|
||||
{ provide: APP_GUARD, useClass: ThrottlerGuard },
|
||||
{ provide: APP_FILTER, useClass: PrismaExceptionFilter },
|
||||
],
|
||||
})
|
||||
], AppModule);
|
||||
export { AppModule };
|
||||
//# sourceMappingURL=app.module.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"app.module.js","sourceRoot":"","sources":["../src/app.module.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACzD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,qCAAqC,CAAC;AAC5E,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAC;AAClE,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAC1D,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EAAE,cAAc,EAAE,MAAM,+BAA+B,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAE1D,IACM,gBAAgB,GADtB,MACM,gBAAgB;IACS;IAA7B,YAA6B,MAAqB;QAArB,WAAM,GAAN,MAAM,CAAe;IAAG,CAAC;IAGhD,AAAN,KAAK,CAAC,MAAM;QACV,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAA,UAAU,CAAC;QACtC,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IACtB,CAAC;CACF,CAAA;AAJO;IADL,GAAG,EAAE;;;;8CAIL;AAPG,gBAAgB;IADrB,UAAU,CAAC,QAAQ,CAAC;qCAEkB,aAAa;GAD9C,gBAAgB,CAQrB;AAuBM,IAAM,SAAS,GAAf,MAAM,SAAS;CAAG,CAAA;AAAZ,SAAS;IArBrB,MAAM,CAAC;QACN,OAAO,EAAE;YAEP,eAAe,CAAC,OAAO,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;YACtD,YAAY;YACZ,YAAY;YACZ,UAAU;YACV,WAAW;YACX,WAAW;YACX,aAAa;YACb,cAAc;YACd,aAAa;YACb,YAAY;YACZ,eAAe;SAChB;QACD,WAAW,EAAE,CAAC,gBAAgB,CAAC;QAC/B,SAAS,EAAE;YACT,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,cAAc,EAAE;YAChD,EAAE,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,qBAAqB,EAAE;SACzD;KACF,CAAC;GACW,SAAS,CAAG"}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
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);
|
||||
};
|
||||
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
||||
return function (target, key) { decorator(target, key, paramIndex); }
|
||||
};
|
||||
import { Body, Controller, Get, HttpCode, Module, Post, Req, Res } from "@nestjs/common";
|
||||
import { JwtModule } from "@nestjs/jwt";
|
||||
import { Throttle } from "@nestjs/throttler";
|
||||
import { changePasswordSchema, loginSchema, } from "@imen/shared";
|
||||
import { env, isProd } from "../config/env.js";
|
||||
import { AdminAuth, CurrentUser } from "../common/auth.js";
|
||||
import { ZodPipe } from "../common/zod.pipe.js";
|
||||
import { AuthService } from "./auth.service.js";
|
||||
const COOKIE = "imen_rt";
|
||||
const COOKIE_PATH = "/api/admin/auth";
|
||||
const ua = (req) => req.headers["user-agent"] ?? "";
|
||||
const readCookie = (req) => req.cookies?.[COOKIE];
|
||||
let AuthController = class AuthController {
|
||||
auth;
|
||||
constructor(auth) {
|
||||
this.auth = auth;
|
||||
}
|
||||
async login(body, req, res) {
|
||||
return this.respond(res, await this.auth.login(body, ua(req)));
|
||||
}
|
||||
async refresh(req, res) {
|
||||
try {
|
||||
return this.respond(res, await this.auth.refresh(readCookie(req), ua(req)));
|
||||
}
|
||||
catch (e) {
|
||||
res.clearCookie(COOKIE, { path: COOKIE_PATH });
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
async logout(req, res) {
|
||||
await this.auth.logout(readCookie(req));
|
||||
res.clearCookie(COOKIE, { path: COOKIE_PATH });
|
||||
}
|
||||
me(user) {
|
||||
return this.auth.me(user.id);
|
||||
}
|
||||
async changePassword(body, user) {
|
||||
await this.auth.changePassword(user.id, body);
|
||||
}
|
||||
respond(res, t) {
|
||||
res.cookie(COOKIE, t.refreshToken, {
|
||||
httpOnly: true,
|
||||
secure: isProd && env.COOKIE_SECURE,
|
||||
sameSite: "strict",
|
||||
path: COOKIE_PATH,
|
||||
expires: t.refreshExpiresAt,
|
||||
});
|
||||
return { accessToken: t.accessToken, user: t.user };
|
||||
}
|
||||
};
|
||||
__decorate([
|
||||
Post("login"),
|
||||
HttpCode(200),
|
||||
Throttle({ default: { limit: 8, ttl: 60_000 } }),
|
||||
__param(0, Body(new ZodPipe(loginSchema))),
|
||||
__param(1, Req()),
|
||||
__param(2, Res({ passthrough: true })),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object, Object, Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], AuthController.prototype, "login", null);
|
||||
__decorate([
|
||||
Post("refresh"),
|
||||
HttpCode(200),
|
||||
Throttle({ default: { limit: 30, ttl: 60_000 } }),
|
||||
__param(0, Req()),
|
||||
__param(1, Res({ passthrough: true })),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object, Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], AuthController.prototype, "refresh", null);
|
||||
__decorate([
|
||||
Post("logout"),
|
||||
HttpCode(204),
|
||||
__param(0, Req()),
|
||||
__param(1, Res({ passthrough: true })),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object, Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], AuthController.prototype, "logout", null);
|
||||
__decorate([
|
||||
Get("me"),
|
||||
AdminAuth(),
|
||||
__param(0, CurrentUser()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object]),
|
||||
__metadata("design:returntype", void 0)
|
||||
], AuthController.prototype, "me", null);
|
||||
__decorate([
|
||||
Post("change-password"),
|
||||
HttpCode(204),
|
||||
AdminAuth(),
|
||||
__param(0, Body(new ZodPipe(changePasswordSchema))),
|
||||
__param(1, CurrentUser()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object, Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], AuthController.prototype, "changePassword", null);
|
||||
AuthController = __decorate([
|
||||
Controller("admin/auth"),
|
||||
__metadata("design:paramtypes", [AuthService])
|
||||
], AuthController);
|
||||
export { AuthController };
|
||||
let AuthModule = class AuthModule {
|
||||
};
|
||||
AuthModule = __decorate([
|
||||
Module({
|
||||
imports: [
|
||||
JwtModule.register({
|
||||
global: true,
|
||||
secret: env.JWT_SECRET,
|
||||
signOptions: { expiresIn: env.JWT_ACCESS_TTL },
|
||||
}),
|
||||
],
|
||||
controllers: [AuthController],
|
||||
providers: [AuthService],
|
||||
exports: [AuthService],
|
||||
})
|
||||
], AuthModule);
|
||||
export { AuthModule };
|
||||
//# sourceMappingURL=auth.controller.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"auth.controller.js","sourceRoot":"","sources":["../../src/auth/auth.controller.ts"],"names":[],"mappings":"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"}
|
||||
Vendored
+127
@@ -0,0 +1,127 @@
|
||||
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);
|
||||
};
|
||||
import { createHash, randomBytes } from "node:crypto";
|
||||
import { BadRequestException, Injectable, UnauthorizedException } from "@nestjs/common";
|
||||
import { JwtService } from "@nestjs/jwt";
|
||||
import { hash, verify } from "@node-rs/argon2";
|
||||
import { env } from "../config/env.js";
|
||||
import { AuditService } from "../common/common.module.js";
|
||||
import { PrismaService } from "../prisma/prisma.service.js";
|
||||
export const hashPassword = (password) => hash(password);
|
||||
const sha256 = (v) => createHash("sha256").update(v).digest("hex");
|
||||
export const toUserDto = (u) => ({
|
||||
id: u.id,
|
||||
phone: u.phone,
|
||||
name: u.name,
|
||||
role: u.role,
|
||||
isActive: u.isActive,
|
||||
lastLoginAt: u.lastLoginAt?.toISOString() ?? null,
|
||||
createdAt: u.createdAt.toISOString(),
|
||||
});
|
||||
const INVALID_LOGIN = "شماره موبایل یا رمز عبور اشتباه است";
|
||||
const DUMMY_HASH = hashPassword(randomBytes(16).toString("hex"));
|
||||
let AuthService = class AuthService {
|
||||
prisma;
|
||||
jwt;
|
||||
audit;
|
||||
constructor(prisma, jwt, audit) {
|
||||
this.prisma = prisma;
|
||||
this.jwt = jwt;
|
||||
this.audit = audit;
|
||||
}
|
||||
async login(input, userAgent) {
|
||||
const user = await this.prisma.adminUser.findUnique({ where: { phone: input.phone } });
|
||||
const ok = await verify(user?.passwordHash ?? (await DUMMY_HASH), input.password).catch(() => false);
|
||||
if (!user || !ok || !user.isActive)
|
||||
throw new UnauthorizedException(INVALID_LOGIN);
|
||||
const updated = await this.prisma.adminUser.update({
|
||||
where: { id: user.id },
|
||||
data: { lastLoginAt: new Date() },
|
||||
});
|
||||
this.audit.log(user.id, "login", "user", user.id, user.phone);
|
||||
return this.issue(updated, userAgent);
|
||||
}
|
||||
async refresh(token, userAgent) {
|
||||
if (!token)
|
||||
throw new UnauthorizedException("نشست شما منقضی شده است");
|
||||
const stored = await this.prisma.refreshToken.findUnique({
|
||||
where: { tokenHash: sha256(token) },
|
||||
include: { user: true },
|
||||
});
|
||||
if (!stored)
|
||||
throw new UnauthorizedException("نشست شما منقضی شده است");
|
||||
if (stored.revokedAt) {
|
||||
await this.revokeAll(stored.userId);
|
||||
throw new UnauthorizedException("نشست شما منقضی شده است");
|
||||
}
|
||||
if (stored.expiresAt < new Date() || !stored.user.isActive) {
|
||||
throw new UnauthorizedException("نشست شما منقضی شده است");
|
||||
}
|
||||
await this.prisma.refreshToken.update({
|
||||
where: { id: stored.id },
|
||||
data: { revokedAt: new Date() },
|
||||
});
|
||||
return this.issue(stored.user, userAgent);
|
||||
}
|
||||
async logout(token) {
|
||||
if (!token)
|
||||
return;
|
||||
await this.prisma.refreshToken.updateMany({
|
||||
where: { tokenHash: sha256(token), revokedAt: null },
|
||||
data: { revokedAt: new Date() },
|
||||
});
|
||||
}
|
||||
async changePassword(userId, input) {
|
||||
const user = await this.prisma.adminUser.findUniqueOrThrow({ where: { id: userId } });
|
||||
if (!(await verify(user.passwordHash, input.currentPassword).catch(() => false))) {
|
||||
throw new BadRequestException("رمز عبور فعلی اشتباه است");
|
||||
}
|
||||
await this.prisma.adminUser.update({
|
||||
where: { id: userId },
|
||||
data: { passwordHash: await hashPassword(input.newPassword) },
|
||||
});
|
||||
this.audit.log(userId, "change-password", "user", userId, user.phone);
|
||||
}
|
||||
async me(userId) {
|
||||
return toUserDto(await this.prisma.adminUser.findUniqueOrThrow({ where: { id: userId } }));
|
||||
}
|
||||
revokeAll(userId) {
|
||||
return this.prisma.refreshToken.updateMany({
|
||||
where: { userId, revokedAt: null },
|
||||
data: { revokedAt: new Date() },
|
||||
});
|
||||
}
|
||||
async issue(user, userAgent) {
|
||||
const payload = { sub: user.id, role: user.role };
|
||||
const accessToken = await this.jwt.signAsync(payload);
|
||||
const refreshToken = randomBytes(48).toString("base64url");
|
||||
const refreshExpiresAt = new Date(Date.now() + env.REFRESH_TTL_DAYS * 86_400_000);
|
||||
await this.prisma.refreshToken.create({
|
||||
data: {
|
||||
userId: user.id,
|
||||
tokenHash: sha256(refreshToken),
|
||||
expiresAt: refreshExpiresAt,
|
||||
userAgent: userAgent.slice(0, 300),
|
||||
},
|
||||
});
|
||||
void this.prisma.refreshToken
|
||||
.deleteMany({ where: { expiresAt: { lt: new Date(Date.now() - 86_400_000) } } })
|
||||
.catch(() => undefined);
|
||||
return { accessToken, refreshToken, refreshExpiresAt, user: toUserDto(user) };
|
||||
}
|
||||
};
|
||||
AuthService = __decorate([
|
||||
Injectable(),
|
||||
__metadata("design:paramtypes", [PrismaService,
|
||||
JwtService,
|
||||
AuditService])
|
||||
], AuthService);
|
||||
export { AuthService };
|
||||
//# sourceMappingURL=auth.service.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+214
@@ -0,0 +1,214 @@
|
||||
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);
|
||||
};
|
||||
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
||||
return function (target, key) { decorator(target, key, paramIndex); }
|
||||
};
|
||||
import { Body, Controller, Delete, Get, HttpCode, Module, Param, Patch, Post, Put, Query, } from "@nestjs/common";
|
||||
import { categoryInputSchema, productInputSchema, productListQuerySchema, reorderSchema, } from "@imen/shared";
|
||||
import { z } from "zod";
|
||||
import { AdminAuth, CurrentUser, Roles } from "../common/auth.js";
|
||||
import { IdPipe, ZodPipe } from "../common/zod.pipe.js";
|
||||
import { MediaModule } from "../media/media.controller.js";
|
||||
import { CategoriesService } from "./categories.service.js";
|
||||
import { ProductsService } from "./products.service.js";
|
||||
const publishSchema = z.object({ isPublished: z.boolean() });
|
||||
let CategoriesController = class CategoriesController {
|
||||
categories;
|
||||
constructor(categories) {
|
||||
this.categories = categories;
|
||||
}
|
||||
list() {
|
||||
return this.categories.list();
|
||||
}
|
||||
get(id) {
|
||||
return this.categories.get(id);
|
||||
}
|
||||
create(body, user) {
|
||||
return this.categories.create(body, user.id);
|
||||
}
|
||||
async reorder(body, user) {
|
||||
await this.categories.reorder(body.ids, user.id);
|
||||
}
|
||||
update(id, body, user) {
|
||||
return this.categories.update(id, body, user.id);
|
||||
}
|
||||
async remove(id, user) {
|
||||
await this.categories.remove(id, user.id);
|
||||
}
|
||||
};
|
||||
__decorate([
|
||||
Get(),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", []),
|
||||
__metadata("design:returntype", void 0)
|
||||
], CategoriesController.prototype, "list", null);
|
||||
__decorate([
|
||||
Get(":id"),
|
||||
__param(0, Param("id", IdPipe)),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Number]),
|
||||
__metadata("design:returntype", void 0)
|
||||
], CategoriesController.prototype, "get", null);
|
||||
__decorate([
|
||||
Post(),
|
||||
__param(0, Body(new ZodPipe(categoryInputSchema))),
|
||||
__param(1, CurrentUser()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object, Object]),
|
||||
__metadata("design:returntype", void 0)
|
||||
], CategoriesController.prototype, "create", null);
|
||||
__decorate([
|
||||
Post("reorder"),
|
||||
HttpCode(204),
|
||||
__param(0, Body(new ZodPipe(reorderSchema))),
|
||||
__param(1, CurrentUser()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object, Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], CategoriesController.prototype, "reorder", null);
|
||||
__decorate([
|
||||
Put(":id"),
|
||||
__param(0, Param("id", IdPipe)),
|
||||
__param(1, Body(new ZodPipe(categoryInputSchema))),
|
||||
__param(2, CurrentUser()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Number, Object, Object]),
|
||||
__metadata("design:returntype", void 0)
|
||||
], CategoriesController.prototype, "update", null);
|
||||
__decorate([
|
||||
Roles("ADMIN"),
|
||||
Delete(":id"),
|
||||
HttpCode(204),
|
||||
__param(0, Param("id", IdPipe)),
|
||||
__param(1, CurrentUser()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Number, Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], CategoriesController.prototype, "remove", null);
|
||||
CategoriesController = __decorate([
|
||||
AdminAuth(),
|
||||
Controller("admin/categories"),
|
||||
__metadata("design:paramtypes", [CategoriesService])
|
||||
], CategoriesController);
|
||||
export { CategoriesController };
|
||||
let ProductsController = class ProductsController {
|
||||
products;
|
||||
constructor(products) {
|
||||
this.products = products;
|
||||
}
|
||||
list(q) {
|
||||
return this.products.list(q);
|
||||
}
|
||||
get(id) {
|
||||
return this.products.get(id);
|
||||
}
|
||||
create(body, user) {
|
||||
return this.products.create(body, user.id);
|
||||
}
|
||||
async reorder(body, user) {
|
||||
await this.products.reorder(body.ids, user.id);
|
||||
}
|
||||
duplicate(id, user) {
|
||||
return this.products.duplicate(id, user.id);
|
||||
}
|
||||
publish(id, body, user) {
|
||||
return this.products.setPublished(id, body.isPublished, user.id);
|
||||
}
|
||||
update(id, body, user) {
|
||||
return this.products.update(id, body, user.id);
|
||||
}
|
||||
async remove(id, user) {
|
||||
await this.products.remove(id, user.id);
|
||||
}
|
||||
};
|
||||
__decorate([
|
||||
Get(),
|
||||
__param(0, Query(new ZodPipe(productListQuerySchema))),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object]),
|
||||
__metadata("design:returntype", void 0)
|
||||
], ProductsController.prototype, "list", null);
|
||||
__decorate([
|
||||
Get(":id"),
|
||||
__param(0, Param("id", IdPipe)),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Number]),
|
||||
__metadata("design:returntype", void 0)
|
||||
], ProductsController.prototype, "get", null);
|
||||
__decorate([
|
||||
Post(),
|
||||
__param(0, Body(new ZodPipe(productInputSchema))),
|
||||
__param(1, CurrentUser()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object, Object]),
|
||||
__metadata("design:returntype", void 0)
|
||||
], ProductsController.prototype, "create", null);
|
||||
__decorate([
|
||||
Post("reorder"),
|
||||
HttpCode(204),
|
||||
__param(0, Body(new ZodPipe(reorderSchema))),
|
||||
__param(1, CurrentUser()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object, Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], ProductsController.prototype, "reorder", null);
|
||||
__decorate([
|
||||
Post(":id/duplicate"),
|
||||
__param(0, Param("id", IdPipe)),
|
||||
__param(1, CurrentUser()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Number, Object]),
|
||||
__metadata("design:returntype", void 0)
|
||||
], ProductsController.prototype, "duplicate", null);
|
||||
__decorate([
|
||||
Patch(":id/publish"),
|
||||
__param(0, Param("id", IdPipe)),
|
||||
__param(1, Body(new ZodPipe(publishSchema))),
|
||||
__param(2, CurrentUser()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Number, Object, Object]),
|
||||
__metadata("design:returntype", void 0)
|
||||
], ProductsController.prototype, "publish", null);
|
||||
__decorate([
|
||||
Put(":id"),
|
||||
__param(0, Param("id", IdPipe)),
|
||||
__param(1, Body(new ZodPipe(productInputSchema))),
|
||||
__param(2, CurrentUser()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Number, Object, Object]),
|
||||
__metadata("design:returntype", void 0)
|
||||
], ProductsController.prototype, "update", null);
|
||||
__decorate([
|
||||
Roles("ADMIN"),
|
||||
Delete(":id"),
|
||||
HttpCode(204),
|
||||
__param(0, Param("id", IdPipe)),
|
||||
__param(1, CurrentUser()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Number, Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], ProductsController.prototype, "remove", null);
|
||||
ProductsController = __decorate([
|
||||
AdminAuth(),
|
||||
Controller("admin/products"),
|
||||
__metadata("design:paramtypes", [ProductsService])
|
||||
], ProductsController);
|
||||
export { ProductsController };
|
||||
let CatalogModule = class CatalogModule {
|
||||
};
|
||||
CatalogModule = __decorate([
|
||||
Module({
|
||||
imports: [MediaModule],
|
||||
controllers: [CategoriesController, ProductsController],
|
||||
providers: [CategoriesService, ProductsService],
|
||||
})
|
||||
], CatalogModule);
|
||||
export { CatalogModule };
|
||||
//# sourceMappingURL=catalog.module.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"catalog.module.js","sourceRoot":"","sources":["../../src/catalog/catalog.module.ts"],"names":[],"mappings":"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"}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
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);
|
||||
};
|
||||
import { ConflictException, Injectable } from "@nestjs/common";
|
||||
import { AuditService, RevalidateService } from "../common/common.module.js";
|
||||
import { MediaService } from "../media/media.service.js";
|
||||
import { PrismaService } from "../prisma/prisma.service.js";
|
||||
const withCover = { coverImage: true };
|
||||
let CategoriesService = class CategoriesService {
|
||||
prisma;
|
||||
media;
|
||||
audit;
|
||||
revalidate;
|
||||
constructor(prisma, media, audit, revalidate) {
|
||||
this.prisma = prisma;
|
||||
this.media = media;
|
||||
this.audit = audit;
|
||||
this.revalidate = revalidate;
|
||||
}
|
||||
async list() {
|
||||
const rows = await this.prisma.category.findMany({
|
||||
orderBy: [{ sortOrder: "asc" }, { id: "asc" }],
|
||||
include: { ...withCover, _count: { select: { products: true, groups: true } } },
|
||||
});
|
||||
return rows.map(({ coverImage, _count, ...c }) => ({
|
||||
...c,
|
||||
coverImage: coverImage && this.media.toDto(coverImage),
|
||||
productCount: _count.products,
|
||||
groupCount: _count.groups,
|
||||
}));
|
||||
}
|
||||
async get(id) {
|
||||
const { coverImage, ...c } = await this.prisma.category.findUniqueOrThrow({
|
||||
where: { id },
|
||||
include: {
|
||||
...withCover,
|
||||
groups: {
|
||||
orderBy: { sortOrder: "asc" },
|
||||
include: { _count: { select: { products: true } } },
|
||||
},
|
||||
},
|
||||
});
|
||||
return {
|
||||
...c,
|
||||
coverImage: coverImage && this.media.toDto(coverImage),
|
||||
groups: c.groups.map(({ _count, ...g }) => ({ ...g, productCount: _count.products })),
|
||||
};
|
||||
}
|
||||
async create(input, userId) {
|
||||
this.assertUniqueGroupSlugs(input);
|
||||
const { groups, ...data } = input;
|
||||
const last = await this.prisma.category.aggregate({ _max: { sortOrder: true } });
|
||||
const category = await this.prisma.category.create({
|
||||
data: {
|
||||
...data,
|
||||
sortOrder: (last._max.sortOrder ?? -1) + 1,
|
||||
groups: {
|
||||
create: groups.map((g, i) => ({ slug: g.slug, name: g.name, desc: g.desc, sortOrder: i })),
|
||||
},
|
||||
},
|
||||
});
|
||||
this.audit.log(userId, "create", "category", category.id, category.name);
|
||||
this.revalidate.trigger();
|
||||
return this.get(category.id);
|
||||
}
|
||||
async update(id, input, userId) {
|
||||
this.assertUniqueGroupSlugs(input);
|
||||
const { groups, ...data } = input;
|
||||
await this.prisma.$transaction(async (tx) => {
|
||||
const category = await tx.category.update({ where: { id }, data });
|
||||
const keepIds = groups.flatMap((g) => (g.id ? [g.id] : []));
|
||||
await tx.productGroup.deleteMany({ where: { categoryId: id, id: { notIn: keepIds } } });
|
||||
for (const g of groups) {
|
||||
if (g.id) {
|
||||
await tx.productGroup.update({
|
||||
where: { id: g.id, categoryId: id },
|
||||
data: { slug: `__tmp-${g.id}` },
|
||||
});
|
||||
}
|
||||
}
|
||||
for (const [i, g] of groups.entries()) {
|
||||
const fields = { slug: g.slug, name: g.name, desc: g.desc, sortOrder: i };
|
||||
if (g.id) {
|
||||
await tx.productGroup.update({ where: { id: g.id, categoryId: id }, data: fields });
|
||||
}
|
||||
else {
|
||||
await tx.productGroup.create({ data: { ...fields, categoryId: id } });
|
||||
}
|
||||
}
|
||||
this.audit.log(userId, "update", "category", id, category.name);
|
||||
});
|
||||
this.revalidate.trigger();
|
||||
return this.get(id);
|
||||
}
|
||||
async remove(id, userId) {
|
||||
const category = await this.prisma.category.findUniqueOrThrow({
|
||||
where: { id },
|
||||
include: { _count: { select: { products: true } } },
|
||||
});
|
||||
if (category._count.products > 0) {
|
||||
throw new ConflictException(`این دسته ${category._count.products} محصول دارد؛ ابتدا محصولات را حذف یا جابهجا کنید`);
|
||||
}
|
||||
await this.prisma.category.delete({ where: { id } });
|
||||
this.audit.log(userId, "delete", "category", id, category.name);
|
||||
this.revalidate.trigger();
|
||||
}
|
||||
async reorder(ids, userId) {
|
||||
await this.prisma.$transaction(ids.map((id, i) => this.prisma.category.update({ where: { id }, data: { sortOrder: i } })));
|
||||
this.audit.log(userId, "reorder", "category", "", `${ids.length} دسته`);
|
||||
this.revalidate.trigger();
|
||||
}
|
||||
assertUniqueGroupSlugs(input) {
|
||||
const slugs = input.groups.map((g) => g.slug);
|
||||
if (new Set(slugs).size !== slugs.length) {
|
||||
throw new ConflictException("اسلاگ زیرگروهها باید یکتا باشد");
|
||||
}
|
||||
}
|
||||
};
|
||||
CategoriesService = __decorate([
|
||||
Injectable(),
|
||||
__metadata("design:paramtypes", [PrismaService,
|
||||
MediaService,
|
||||
AuditService,
|
||||
RevalidateService])
|
||||
], CategoriesService);
|
||||
export { CategoriesService };
|
||||
//# sourceMappingURL=categories.service.js.map
|
||||
File diff suppressed because one or more lines are too long
+182
@@ -0,0 +1,182 @@
|
||||
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);
|
||||
};
|
||||
import { BadRequestException, Injectable } from "@nestjs/common";
|
||||
import { AuditService, RevalidateService } from "../common/common.module.js";
|
||||
import { MediaService } from "../media/media.service.js";
|
||||
import { PrismaService } from "../prisma/prisma.service.js";
|
||||
let ProductsService = class ProductsService {
|
||||
prisma;
|
||||
media;
|
||||
audit;
|
||||
revalidate;
|
||||
constructor(prisma, media, audit, revalidate) {
|
||||
this.prisma = prisma;
|
||||
this.media = media;
|
||||
this.audit = audit;
|
||||
this.revalidate = revalidate;
|
||||
}
|
||||
async list(q) {
|
||||
const where = {
|
||||
...(q.categoryId && { categoryId: q.categoryId }),
|
||||
...(q.groupId && { groupId: q.groupId }),
|
||||
...(q.status && { isPublished: q.status === "published" }),
|
||||
...(q.q && {
|
||||
OR: [
|
||||
{ name: { contains: q.q, mode: "insensitive" } },
|
||||
{ nameEn: { contains: q.q, mode: "insensitive" } },
|
||||
{ code: { contains: q.q, mode: "insensitive" } },
|
||||
],
|
||||
}),
|
||||
};
|
||||
const [rows, total] = await this.prisma.$transaction([
|
||||
this.prisma.product.findMany({
|
||||
where,
|
||||
orderBy: [{ category: { sortOrder: "asc" } }, { sortOrder: "asc" }, { id: "asc" }],
|
||||
skip: (q.page - 1) * q.pageSize,
|
||||
take: q.pageSize,
|
||||
include: {
|
||||
image: true,
|
||||
category: { select: { id: true, name: true } },
|
||||
group: { select: { id: true, name: true } },
|
||||
},
|
||||
}),
|
||||
this.prisma.product.count({ where }),
|
||||
]);
|
||||
return {
|
||||
items: rows.map(({ image, ...p }) => ({ ...p, image: image && this.media.toDto(image) })),
|
||||
total,
|
||||
page: q.page,
|
||||
pageSize: q.pageSize,
|
||||
};
|
||||
}
|
||||
async get(id) {
|
||||
const { image, gallery, ...p } = await this.prisma.product.findUniqueOrThrow({
|
||||
where: { id },
|
||||
include: { image: true, gallery: { orderBy: { sortOrder: "asc" }, include: { media: true } } },
|
||||
});
|
||||
return {
|
||||
...p,
|
||||
image: image && this.media.toDto(image),
|
||||
gallery: gallery.map((g) => this.media.toDto(g.media)),
|
||||
};
|
||||
}
|
||||
async create(input, userId) {
|
||||
await this.assertGroup(input);
|
||||
const { galleryIds, ...data } = input;
|
||||
const last = await this.prisma.product.aggregate({
|
||||
where: { categoryId: input.categoryId },
|
||||
_max: { sortOrder: true },
|
||||
});
|
||||
const product = await this.prisma.product.create({
|
||||
data: {
|
||||
...data,
|
||||
sortOrder: (last._max.sortOrder ?? -1) + 1,
|
||||
gallery: { create: unique(galleryIds).map((mediaId, i) => ({ mediaId, sortOrder: i })) },
|
||||
},
|
||||
});
|
||||
this.audit.log(userId, "create", "product", product.id, product.name);
|
||||
this.revalidate.trigger();
|
||||
return this.get(product.id);
|
||||
}
|
||||
async update(id, input, userId) {
|
||||
await this.assertGroup(input);
|
||||
const { galleryIds, ...data } = input;
|
||||
const product = await this.prisma.product.update({
|
||||
where: { id },
|
||||
data: {
|
||||
...data,
|
||||
gallery: {
|
||||
deleteMany: {},
|
||||
create: unique(galleryIds).map((mediaId, i) => ({ mediaId, sortOrder: i })),
|
||||
},
|
||||
},
|
||||
});
|
||||
this.audit.log(userId, "update", "product", id, product.name);
|
||||
this.revalidate.trigger();
|
||||
return this.get(id);
|
||||
}
|
||||
async setPublished(id, isPublished, userId) {
|
||||
const product = await this.prisma.product.update({ where: { id }, data: { isPublished } });
|
||||
this.audit.log(userId, isPublished ? "publish" : "unpublish", "product", id, product.name);
|
||||
this.revalidate.trigger();
|
||||
return { id, isPublished };
|
||||
}
|
||||
async duplicate(id, userId) {
|
||||
const src = await this.prisma.product.findUniqueOrThrow({
|
||||
where: { id },
|
||||
include: { gallery: true },
|
||||
});
|
||||
let n = 1;
|
||||
const taken = async (code, slug) => (await this.prisma.product.count({ where: { OR: [{ code }, { slug }] } })) > 0;
|
||||
while (await taken(`${src.code}-${n}`, `${src.slug}-copy-${n}`))
|
||||
n++;
|
||||
const copy = await this.prisma.product.create({
|
||||
data: {
|
||||
categoryId: src.categoryId,
|
||||
groupId: src.groupId,
|
||||
code: `${src.code}-${n}`,
|
||||
slug: `${src.slug}-copy-${n}`,
|
||||
name: `${src.name} (کپی)`,
|
||||
nameEn: src.nameEn,
|
||||
desc: src.desc,
|
||||
specs: src.specs,
|
||||
variants: src.variants,
|
||||
details: src.details,
|
||||
brand: src.brand,
|
||||
origin: src.origin,
|
||||
sizes: src.sizes,
|
||||
standards: src.standards,
|
||||
features: src.features,
|
||||
applications: src.applications,
|
||||
attributes: (src.attributes ?? []),
|
||||
packaging: src.packaging,
|
||||
minOrder: src.minOrder,
|
||||
seoTitle: src.seoTitle,
|
||||
seoDescription: src.seoDescription,
|
||||
imageId: src.imageId,
|
||||
sortOrder: src.sortOrder + 1,
|
||||
isPublished: false,
|
||||
gallery: {
|
||||
create: src.gallery.map((g) => ({ mediaId: g.mediaId, sortOrder: g.sortOrder })),
|
||||
},
|
||||
},
|
||||
});
|
||||
this.audit.log(userId, "duplicate", "product", copy.id, copy.name);
|
||||
return this.get(copy.id);
|
||||
}
|
||||
async remove(id, userId) {
|
||||
const product = await this.prisma.product.delete({ where: { id } });
|
||||
this.audit.log(userId, "delete", "product", id, product.name);
|
||||
this.revalidate.trigger();
|
||||
}
|
||||
async reorder(ids, userId) {
|
||||
await this.prisma.$transaction(ids.map((id, i) => this.prisma.product.update({ where: { id }, data: { sortOrder: i } })));
|
||||
this.audit.log(userId, "reorder", "product", "", `${ids.length} محصول`);
|
||||
this.revalidate.trigger();
|
||||
}
|
||||
async assertGroup(input) {
|
||||
if (!input.groupId)
|
||||
return;
|
||||
const group = await this.prisma.productGroup.findUnique({ where: { id: input.groupId } });
|
||||
if (group?.categoryId !== input.categoryId) {
|
||||
throw new BadRequestException("زیرگروه انتخابشده متعلق به این دسته نیست");
|
||||
}
|
||||
}
|
||||
};
|
||||
ProductsService = __decorate([
|
||||
Injectable(),
|
||||
__metadata("design:paramtypes", [PrismaService,
|
||||
MediaService,
|
||||
AuditService,
|
||||
RevalidateService])
|
||||
], ProductsService);
|
||||
export { ProductsService };
|
||||
const unique = (ids) => [...new Set(ids)];
|
||||
//# sourceMappingURL=products.service.js.map
|
||||
File diff suppressed because one or more lines are too long
Vendored
+60
@@ -0,0 +1,60 @@
|
||||
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);
|
||||
};
|
||||
import { createParamDecorator, ForbiddenException, Injectable, SetMetadata, UnauthorizedException, UseGuards, applyDecorators, } from "@nestjs/common";
|
||||
import { Reflector } from "@nestjs/core";
|
||||
import { JwtService } from "@nestjs/jwt";
|
||||
import { PrismaService } from "../prisma/prisma.service.js";
|
||||
const ROLES_KEY = "roles";
|
||||
let JwtAuthGuard = class JwtAuthGuard {
|
||||
jwt;
|
||||
reflector;
|
||||
prisma;
|
||||
constructor(jwt, reflector, prisma) {
|
||||
this.jwt = jwt;
|
||||
this.reflector = reflector;
|
||||
this.prisma = prisma;
|
||||
}
|
||||
async canActivate(ctx) {
|
||||
const req = ctx.switchToHttp().getRequest();
|
||||
const [scheme, token] = (req.headers.authorization ?? "").split(" ");
|
||||
if (scheme !== "Bearer" || !token)
|
||||
throw new UnauthorizedException("ابتدا وارد شوید");
|
||||
let payload;
|
||||
try {
|
||||
payload = await this.jwt.verifyAsync(token);
|
||||
}
|
||||
catch {
|
||||
throw new UnauthorizedException("نشست شما منقضی شده است");
|
||||
}
|
||||
const user = await this.prisma.adminUser.findUnique({ where: { id: payload.sub } });
|
||||
if (!user?.isActive)
|
||||
throw new UnauthorizedException("حساب کاربری غیرفعال است");
|
||||
req.user = { id: user.id, phone: user.phone, name: user.name, role: user.role };
|
||||
const roles = this.reflector.getAllAndOverride(ROLES_KEY, [
|
||||
ctx.getHandler(),
|
||||
ctx.getClass(),
|
||||
]);
|
||||
if (roles?.length && !roles.includes(user.role)) {
|
||||
throw new ForbiddenException("برای این کار دسترسی ندارید");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
JwtAuthGuard = __decorate([
|
||||
Injectable(),
|
||||
__metadata("design:paramtypes", [JwtService,
|
||||
Reflector,
|
||||
PrismaService])
|
||||
], JwtAuthGuard);
|
||||
export { JwtAuthGuard };
|
||||
export const AdminAuth = (...roles) => applyDecorators(SetMetadata(ROLES_KEY, roles), UseGuards(JwtAuthGuard));
|
||||
export const Roles = (...roles) => SetMetadata(ROLES_KEY, roles);
|
||||
export const CurrentUser = createParamDecorator((_, ctx) => ctx.switchToHttp().getRequest().user);
|
||||
//# sourceMappingURL=auth.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/common/auth.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAEL,oBAAoB,EAEpB,kBAAkB,EAClB,UAAU,EACV,WAAW,EACX,qBAAqB,EACrB,SAAS,EACT,eAAe,GAChB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAgB5D,MAAM,SAAS,GAAG,OAAO,CAAC;AAGnB,IAAM,YAAY,GAAlB,MAAM,YAAY;IAEJ;IACA;IACA;IAHnB,YACmB,GAAe,EACf,SAAoB,EACpB,MAAqB;QAFrB,QAAG,GAAH,GAAG,CAAY;QACf,cAAS,GAAT,SAAS,CAAW;QACpB,WAAM,GAAN,MAAM,CAAe;IACrC,CAAC;IAEJ,KAAK,CAAC,WAAW,CAAC,GAAqB;QACrC,MAAM,GAAG,GAAG,GAAG,CAAC,YAAY,EAAE,CAAC,UAAU,EAAiB,CAAC;QAC3D,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,aAAa,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACrE,IAAI,MAAM,KAAK,QAAQ,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,qBAAqB,CAAC,iBAAiB,CAAC,CAAC;QAEtF,IAAI,OAAmB,CAAC;QACxB,IAAI,CAAC;YACH,OAAO,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,WAAW,CAAa,KAAK,CAAC,CAAC;QAC1D,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,qBAAqB,CAAC,wBAAwB,CAAC,CAAC;QAC5D,CAAC;QAGD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QACpF,IAAI,CAAC,IAAI,EAAE,QAAQ;YAAE,MAAM,IAAI,qBAAqB,CAAC,yBAAyB,CAAC,CAAC;QAChF,GAAG,CAAC,IAAI,GAAG,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;QAEhF,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,iBAAiB,CAAqB,SAAS,EAAE;YAC5E,GAAG,CAAC,UAAU,EAAE;YAChB,GAAG,CAAC,QAAQ,EAAE;SACf,CAAC,CAAC;QACH,IAAI,KAAK,EAAE,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YAChD,MAAM,IAAI,kBAAkB,CAAC,4BAA4B,CAAC,CAAC;QAC7D,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;CACF,CAAA;AAjCY,YAAY;IADxB,UAAU,EAAE;qCAGa,UAAU;QACJ,SAAS;QACZ,aAAa;GAJ7B,YAAY,CAiCxB;;AAGD,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,GAAG,KAAa,EAAE,EAAE,CAC5C,eAAe,CAAC,WAAW,CAAC,SAAS,EAAE,KAAK,CAAC,EAAE,SAAS,CAAC,YAAY,CAAC,CAAC,CAAC;AAG1E,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,GAAG,KAAa,EAAE,EAAE,CAAC,WAAW,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;AAEzE,MAAM,CAAC,MAAM,WAAW,GAAG,oBAAoB,CAC7C,CAAC,CAAU,EAAE,GAAqB,EAAY,EAAE,CAC9C,GAAG,CAAC,YAAY,EAAE,CAAC,UAAU,EAAiB,CAAC,IAAK,CACvD,CAAC"}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
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);
|
||||
};
|
||||
var AuditService_1, RevalidateService_1;
|
||||
import { Global, Injectable, Logger, Module } from "@nestjs/common";
|
||||
import { env } from "../config/env.js";
|
||||
import { PrismaService } from "../prisma/prisma.service.js";
|
||||
let AuditService = AuditService_1 = class AuditService {
|
||||
prisma;
|
||||
logger = new Logger(AuditService_1.name);
|
||||
constructor(prisma) {
|
||||
this.prisma = prisma;
|
||||
}
|
||||
log(userId, action, entity, entityId = "", summary = "") {
|
||||
this.prisma.auditLog
|
||||
.create({ data: { userId, action, entity, entityId: String(entityId), summary } })
|
||||
.catch((e) => this.logger.warn(`audit log failed: ${String(e)}`));
|
||||
}
|
||||
};
|
||||
AuditService = AuditService_1 = __decorate([
|
||||
Injectable(),
|
||||
__metadata("design:paramtypes", [PrismaService])
|
||||
], AuditService);
|
||||
export { AuditService };
|
||||
let RevalidateService = RevalidateService_1 = class RevalidateService {
|
||||
logger = new Logger(RevalidateService_1.name);
|
||||
timer;
|
||||
trigger() {
|
||||
if (!env.WEB_REVALIDATE_URL)
|
||||
return;
|
||||
clearTimeout(this.timer);
|
||||
this.timer = setTimeout(() => void this.run(), 1500);
|
||||
this.timer.unref();
|
||||
}
|
||||
async run() {
|
||||
try {
|
||||
const res = await fetch(env.WEB_REVALIDATE_URL, {
|
||||
method: "POST",
|
||||
headers: { "x-revalidate-secret": env.REVALIDATE_SECRET ?? "" },
|
||||
signal: AbortSignal.timeout(5000),
|
||||
});
|
||||
if (!res.ok)
|
||||
this.logger.warn(`site revalidation returned ${res.status}`);
|
||||
}
|
||||
catch (e) {
|
||||
this.logger.warn(`site revalidation failed: ${String(e)}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
RevalidateService = RevalidateService_1 = __decorate([
|
||||
Injectable()
|
||||
], RevalidateService);
|
||||
export { RevalidateService };
|
||||
let CommonModule = class CommonModule {
|
||||
};
|
||||
CommonModule = __decorate([
|
||||
Global(),
|
||||
Module({
|
||||
providers: [AuditService, RevalidateService],
|
||||
exports: [AuditService, RevalidateService],
|
||||
})
|
||||
], CommonModule);
|
||||
export { CommonModule };
|
||||
//# sourceMappingURL=common.module.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"common.module.js","sourceRoot":"","sources":["../../src/common/common.module.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAIrD,IAAM,YAAY,oBAAlB,MAAM,YAAY;IAGM;IAFZ,MAAM,GAAG,IAAI,MAAM,CAAC,cAAY,CAAC,IAAI,CAAC,CAAC;IAExD,YAA6B,MAAqB;QAArB,WAAM,GAAN,MAAM,CAAe;IAAG,CAAC;IAEtD,GAAG,CACD,MAAqB,EACrB,MAAc,EACd,MAAc,EACd,WAA4B,EAAE,EAC9B,OAAO,GAAG,EAAE;QAEZ,IAAI,CAAC,MAAM,CAAC,QAAQ;aACjB,MAAM,CAAC,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,CAAC,EAAE,OAAO,EAAE,EAAE,CAAC;aACjF,KAAK,CAAC,CAAC,CAAU,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,qBAAqB,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAC/E,CAAC;CACF,CAAA;AAhBY,YAAY;IADxB,UAAU,EAAE;qCAI0B,aAAa;GAHvC,YAAY,CAgBxB;;AAOM,IAAM,iBAAiB,yBAAvB,MAAM,iBAAiB;IACX,MAAM,GAAG,IAAI,MAAM,CAAC,mBAAiB,CAAC,IAAI,CAAC,CAAC;IACrD,KAAK,CAA6B;IAE1C,OAAO;QACL,IAAI,CAAC,GAAG,CAAC,kBAAkB;YAAE,OAAO;QACpC,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACzB,IAAI,CAAC,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC,GAAG,EAAE,EAAE,IAAI,CAAC,CAAC;QACrD,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;IAEO,KAAK,CAAC,GAAG;QACf,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,CAAC,kBAAmB,EAAE;gBAC/C,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAE,qBAAqB,EAAE,GAAG,CAAC,iBAAiB,IAAI,EAAE,EAAE;gBAC/D,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC;aAClC,CAAC,CAAC;YACH,IAAI,CAAC,GAAG,CAAC,EAAE;gBAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,8BAA8B,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;QAC5E,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,6BAA6B,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC7D,CAAC;IACH,CAAC;CACF,CAAA;AAvBY,iBAAiB;IAD7B,UAAU,EAAE;GACA,iBAAiB,CAuB7B;;AAOM,IAAM,YAAY,GAAlB,MAAM,YAAY;CAAG,CAAA;AAAf,YAAY;IALxB,MAAM,EAAE;IACR,MAAM,CAAC;QACN,SAAS,EAAE,CAAC,YAAY,EAAE,iBAAiB,CAAC;QAC5C,OAAO,EAAE,CAAC,YAAY,EAAE,iBAAiB,CAAC;KAC3C,CAAC;GACW,YAAY,CAAG"}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
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;
|
||||
};
|
||||
import { Catch, HttpStatus } from "@nestjs/common";
|
||||
import { Prisma } from "../generated/prisma/client.js";
|
||||
const FIELD_LABELS = {
|
||||
slug: "اسلاگ",
|
||||
code: "کد",
|
||||
email: "ایمیل",
|
||||
phone: "شماره موبایل",
|
||||
};
|
||||
function uniqueFields(meta) {
|
||||
if (meta?.target)
|
||||
return Array.isArray(meta.target) ? meta.target : [meta.target];
|
||||
const constraint = meta?.driverAdapterError?.cause?.constraint;
|
||||
if (constraint?.fields)
|
||||
return constraint.fields;
|
||||
const match = constraint?.index?.match(/^[^_]+_(.+)_key$/);
|
||||
return match ? match[1].split("_") : [];
|
||||
}
|
||||
let PrismaExceptionFilter = class PrismaExceptionFilter {
|
||||
catch(error, host) {
|
||||
const res = host.switchToHttp().getResponse();
|
||||
switch (error.code) {
|
||||
case "P2002": {
|
||||
const fields = uniqueFields(error.meta)
|
||||
.filter((f) => !f.endsWith("Id"))
|
||||
.map((f) => FIELD_LABELS[f] ?? f)
|
||||
.join("، ");
|
||||
return res.status(HttpStatus.CONFLICT).json({
|
||||
statusCode: HttpStatus.CONFLICT,
|
||||
message: fields ? `مقدار «${fields}» تکراری است` : "مقدار تکراری است",
|
||||
});
|
||||
}
|
||||
case "P2003":
|
||||
return res.status(HttpStatus.CONFLICT).json({
|
||||
statusCode: HttpStatus.CONFLICT,
|
||||
message: "این مورد به دادههای دیگری وابسته است",
|
||||
});
|
||||
case "P2025":
|
||||
return res
|
||||
.status(HttpStatus.NOT_FOUND)
|
||||
.json({ statusCode: HttpStatus.NOT_FOUND, message: "مورد پیدا نشد" });
|
||||
default:
|
||||
return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({
|
||||
statusCode: HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
message: "خطای پایگاه داده",
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
PrismaExceptionFilter = __decorate([
|
||||
Catch(Prisma.PrismaClientKnownRequestError)
|
||||
], PrismaExceptionFilter);
|
||||
export { PrismaExceptionFilter };
|
||||
//# sourceMappingURL=prisma-exception.filter.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"prisma-exception.filter.js","sourceRoot":"","sources":["../../src/common/prisma-exception.filter.ts"],"names":[],"mappings":";;;;;;AAAA,OAAO,EAAsB,KAAK,EAAwB,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE7F,OAAO,EAAE,MAAM,EAAE,MAAM,+BAA+B,CAAC;AAEvD,MAAM,YAAY,GAA2B;IAC3C,IAAI,EAAE,OAAO;IACb,IAAI,EAAE,IAAI;IACV,KAAK,EAAE,OAAO;IACd,KAAK,EAAE,cAAc;CACtB,CAAC;AAQF,SAAS,YAAY,CAAC,IAA4B;IAChD,IAAI,IAAI,EAAE,MAAM;QAAE,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAClF,MAAM,UAAU,GAAG,IAAI,EAAE,kBAAkB,EAAE,KAAK,EAAE,UAAU,CAAC;IAC/D,IAAI,UAAU,EAAE,MAAM;QAAE,OAAO,UAAU,CAAC,MAAM,CAAC;IAEjD,MAAM,KAAK,GAAG,UAAU,EAAE,KAAK,EAAE,KAAK,CAAC,kBAAkB,CAAC,CAAC;IAC3D,OAAO,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AAC3C,CAAC;AAIM,IAAM,qBAAqB,GAA3B,MAAM,qBAAqB;IAChC,KAAK,CAAC,KAA2C,EAAE,IAAmB;QACpE,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC,WAAW,EAAY,CAAC;QAExD,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;YACnB,KAAK,OAAO,CAAC,CAAC,CAAC;gBACb,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC,IAA8B,CAAC;qBAC9D,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;qBAChC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;qBAChC,IAAI,CAAC,IAAI,CAAC,CAAC;gBACd,OAAO,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC;oBAC1C,UAAU,EAAE,UAAU,CAAC,QAAQ;oBAC/B,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,UAAU,MAAM,cAAc,CAAC,CAAC,CAAC,kBAAkB;iBACtE,CAAC,CAAC;YACL,CAAC;YACD,KAAK,OAAO;gBACV,OAAO,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC;oBAC1C,UAAU,EAAE,UAAU,CAAC,QAAQ;oBAC/B,OAAO,EAAE,uCAAuC;iBACjD,CAAC,CAAC;YACL,KAAK,OAAO;gBACV,OAAO,GAAG;qBACP,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC;qBAC5B,IAAI,CAAC,EAAE,UAAU,EAAE,UAAU,CAAC,SAAS,EAAE,OAAO,EAAE,eAAe,EAAE,CAAC,CAAC;YAC1E;gBACE,OAAO,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,qBAAqB,CAAC,CAAC,IAAI,CAAC;oBACvD,UAAU,EAAE,UAAU,CAAC,qBAAqB;oBAC5C,OAAO,EAAE,kBAAkB;iBAC5B,CAAC,CAAC;QACP,CAAC;IACH,CAAC;CACF,CAAA;AA/BY,qBAAqB;IADjC,KAAK,CAAC,MAAM,CAAC,6BAA6B,CAAC;GAC/B,qBAAqB,CA+BjC"}
|
||||
Vendored
+23
@@ -0,0 +1,23 @@
|
||||
import { BadRequestException } from "@nestjs/common";
|
||||
import { toIssues } from "@imen/shared";
|
||||
import { z } from "zod";
|
||||
z.config(z.locales.fa());
|
||||
export class ZodPipe {
|
||||
schema;
|
||||
constructor(schema) {
|
||||
this.schema = schema;
|
||||
}
|
||||
transform(value) {
|
||||
const result = this.schema.safeParse(value);
|
||||
if (!result.success) {
|
||||
throw new BadRequestException({
|
||||
statusCode: 400,
|
||||
message: "اطلاعات واردشده معتبر نیست",
|
||||
issues: toIssues(result.error),
|
||||
});
|
||||
}
|
||||
return result.data;
|
||||
}
|
||||
}
|
||||
export const IdPipe = new ZodPipe(z.coerce.number().int().positive());
|
||||
//# sourceMappingURL=zod.pipe.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"zod.pipe.js","sourceRoot":"","sources":["../../src/common/zod.pipe.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAsB,MAAM,gBAAgB,CAAC;AACzE,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AACxC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAGxB,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,EAAE,CAAC,CAAC;AAGzB,MAAM,OAAO,OAAO;IACW;IAA7B,YAA6B,MAAS;QAAT,WAAM,GAAN,MAAM,CAAG;IAAG,CAAC;IAE1C,SAAS,CAAC,KAAc;QACtB,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAC5C,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,IAAI,mBAAmB,CAAC;gBAC5B,UAAU,EAAE,GAAG;gBACf,OAAO,EAAE,4BAA4B;gBACrC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC;aAC/B,CAAC,CAAC;QACL,CAAC;QACD,OAAO,MAAM,CAAC,IAAI,CAAC;IACrB,CAAC;CACF;AAGD,MAAM,CAAC,MAAM,MAAM,GAAG,IAAI,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,CAAC"}
|
||||
Vendored
+43
@@ -0,0 +1,43 @@
|
||||
import { existsSync } from "node:fs";
|
||||
import { z } from "zod";
|
||||
if (existsSync(".env"))
|
||||
process.loadEnvFile(".env");
|
||||
const bool = (fallback) => z
|
||||
.enum(["true", "false", "1", "0"])
|
||||
.optional()
|
||||
.transform((v) => (v === undefined ? fallback : v === "true" || v === "1"));
|
||||
const schema = z.object({
|
||||
NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
|
||||
PORT: z.coerce.number().int().default(4000),
|
||||
CORS_ORIGINS: z.string().default(""),
|
||||
TRUST_PROXY: z.coerce.number().int().min(0).default(1),
|
||||
DATABASE_URL: z.string().min(1),
|
||||
JWT_SECRET: z.string().min(32, "JWT_SECRET must be at least 32 characters"),
|
||||
JWT_ACCESS_TTL: z.string().default("15m"),
|
||||
REFRESH_TTL_DAYS: z.coerce.number().int().positive().default(30),
|
||||
COOKIE_SECURE: bool(true),
|
||||
S3_ENDPOINT: z.string().optional(),
|
||||
S3_REGION: z.string().default("us-east-1"),
|
||||
S3_BUCKET: z.string().default("imen-media"),
|
||||
S3_ACCESS_KEY: z.string().default(""),
|
||||
S3_SECRET_KEY: z.string().default(""),
|
||||
S3_FORCE_PATH_STYLE: bool(true),
|
||||
S3_ENSURE_BUCKET: bool(false),
|
||||
MEDIA_PUBLIC_URL: z
|
||||
.string()
|
||||
.default("/media")
|
||||
.transform((v) => v.replace(/\/+$/, "")),
|
||||
WEB_REVALIDATE_URL: z.string().optional(),
|
||||
REVALIDATE_SECRET: z.string().optional(),
|
||||
ADMIN_PHONE: z.string().optional(),
|
||||
ADMIN_PASSWORD: z.string().optional(),
|
||||
ADMIN_NAME: z.string().default("مدیر سایت"),
|
||||
});
|
||||
const parsed = schema.safeParse(process.env);
|
||||
if (!parsed.success) {
|
||||
console.error("Invalid environment variables:\n" + z.prettifyError(parsed.error));
|
||||
process.exit(1);
|
||||
}
|
||||
export const env = parsed.data;
|
||||
export const isProd = env.NODE_ENV === "production";
|
||||
//# sourceMappingURL=env.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/config/env.ts"],"names":[],"mappings":"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"}
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
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);
|
||||
};
|
||||
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
||||
return function (target, key) { decorator(target, key, paramIndex); }
|
||||
};
|
||||
import { Body, Controller, Delete, Get, HttpCode, Injectable, Module, Param, Patch, Post, Query, Req, } from "@nestjs/common";
|
||||
import { Throttle } from "@nestjs/throttler";
|
||||
import { contactInputSchema, MESSAGE_STATUSES, messageUpdateSchema, } from "@imen/shared";
|
||||
import { z } from "zod";
|
||||
import { AdminAuth, CurrentUser, Roles } from "../common/auth.js";
|
||||
import { AuditService } from "../common/common.module.js";
|
||||
import { IdPipe, ZodPipe } from "../common/zod.pipe.js";
|
||||
import { PrismaService } from "../prisma/prisma.service.js";
|
||||
const listQuery = z.object({
|
||||
status: z.enum(MESSAGE_STATUSES).optional(),
|
||||
page: z.coerce.number().int().min(1).default(1),
|
||||
pageSize: z.coerce.number().int().min(1).max(100).default(20),
|
||||
});
|
||||
export const toMessageDto = (m) => ({
|
||||
id: m.id,
|
||||
name: m.name,
|
||||
org: m.org,
|
||||
phone: m.phone,
|
||||
message: m.message,
|
||||
status: m.status,
|
||||
createdAt: m.createdAt.toISOString(),
|
||||
});
|
||||
let ContactService = class ContactService {
|
||||
prisma;
|
||||
audit;
|
||||
constructor(prisma, audit) {
|
||||
this.prisma = prisma;
|
||||
this.audit = audit;
|
||||
}
|
||||
async submit(input, ip, userAgent) {
|
||||
if (input.website)
|
||||
return;
|
||||
await this.prisma.contactMessage.create({
|
||||
data: {
|
||||
name: input.name,
|
||||
org: input.org,
|
||||
phone: input.phone,
|
||||
message: input.message,
|
||||
ip: ip.slice(0, 64),
|
||||
userAgent: userAgent.slice(0, 300),
|
||||
},
|
||||
});
|
||||
}
|
||||
async list(q) {
|
||||
const where = q.status ? { status: q.status } : {};
|
||||
const [items, total] = await this.prisma.$transaction([
|
||||
this.prisma.contactMessage.findMany({
|
||||
where,
|
||||
orderBy: { id: "desc" },
|
||||
skip: (q.page - 1) * q.pageSize,
|
||||
take: q.pageSize,
|
||||
}),
|
||||
this.prisma.contactMessage.count({ where }),
|
||||
]);
|
||||
return { items: items.map(toMessageDto), total, page: q.page, pageSize: q.pageSize };
|
||||
}
|
||||
async setStatus(id, status) {
|
||||
return toMessageDto(await this.prisma.contactMessage.update({ where: { id }, data: { status } }));
|
||||
}
|
||||
async remove(id, userId) {
|
||||
const m = await this.prisma.contactMessage.delete({ where: { id } });
|
||||
this.audit.log(userId, "delete", "message", id, m.name);
|
||||
}
|
||||
};
|
||||
ContactService = __decorate([
|
||||
Injectable(),
|
||||
__metadata("design:paramtypes", [PrismaService,
|
||||
AuditService])
|
||||
], ContactService);
|
||||
export { ContactService };
|
||||
let PublicContactController = class PublicContactController {
|
||||
contact;
|
||||
constructor(contact) {
|
||||
this.contact = contact;
|
||||
}
|
||||
async submit(body, req) {
|
||||
await this.contact.submit(body, req.ip ?? "", req.headers["user-agent"] ?? "");
|
||||
return { ok: true };
|
||||
}
|
||||
};
|
||||
__decorate([
|
||||
Post(),
|
||||
HttpCode(201),
|
||||
Throttle({ default: { limit: 5, ttl: 10 * 60_000 } }),
|
||||
__param(0, Body(new ZodPipe(contactInputSchema))),
|
||||
__param(1, Req()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object, Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], PublicContactController.prototype, "submit", null);
|
||||
PublicContactController = __decorate([
|
||||
Controller("public/contact"),
|
||||
__metadata("design:paramtypes", [ContactService])
|
||||
], PublicContactController);
|
||||
export { PublicContactController };
|
||||
let MessagesController = class MessagesController {
|
||||
contact;
|
||||
constructor(contact) {
|
||||
this.contact = contact;
|
||||
}
|
||||
list(q) {
|
||||
return this.contact.list(q);
|
||||
}
|
||||
update(id, body) {
|
||||
return this.contact.setStatus(id, body.status);
|
||||
}
|
||||
async remove(id, user) {
|
||||
await this.contact.remove(id, user.id);
|
||||
}
|
||||
};
|
||||
__decorate([
|
||||
Get(),
|
||||
__param(0, Query(new ZodPipe(listQuery))),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object]),
|
||||
__metadata("design:returntype", void 0)
|
||||
], MessagesController.prototype, "list", null);
|
||||
__decorate([
|
||||
Patch(":id"),
|
||||
__param(0, Param("id", IdPipe)),
|
||||
__param(1, Body(new ZodPipe(messageUpdateSchema))),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Number, Object]),
|
||||
__metadata("design:returntype", void 0)
|
||||
], MessagesController.prototype, "update", null);
|
||||
__decorate([
|
||||
Roles("ADMIN"),
|
||||
Delete(":id"),
|
||||
HttpCode(204),
|
||||
__param(0, Param("id", IdPipe)),
|
||||
__param(1, CurrentUser()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Number, Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], MessagesController.prototype, "remove", null);
|
||||
MessagesController = __decorate([
|
||||
AdminAuth(),
|
||||
Controller("admin/messages"),
|
||||
__metadata("design:paramtypes", [ContactService])
|
||||
], MessagesController);
|
||||
export { MessagesController };
|
||||
let ContactModule = class ContactModule {
|
||||
};
|
||||
ContactModule = __decorate([
|
||||
Module({
|
||||
controllers: [PublicContactController, MessagesController],
|
||||
providers: [ContactService],
|
||||
})
|
||||
], ContactModule);
|
||||
export { ContactModule };
|
||||
//# sourceMappingURL=contact.module.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"contact.module.js","sourceRoot":"","sources":["../../src/contact/contact.module.ts"],"names":[],"mappings":"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"}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
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);
|
||||
};
|
||||
import { Controller, Get, Module } from "@nestjs/common";
|
||||
import { SECTION_KEYS } from "@imen/shared";
|
||||
import { AdminAuth } from "../common/auth.js";
|
||||
import { toMessageDto } from "../contact/contact.module.js";
|
||||
import { PrismaService } from "../prisma/prisma.service.js";
|
||||
const DAY_NAMES = ["یکشنبه", "دوشنبه", "سهشنبه", "چهارشنبه", "پنجشنبه", "جمعه", "شنبه"];
|
||||
let DashboardController = class DashboardController {
|
||||
prisma;
|
||||
constructor(prisma) {
|
||||
this.prisma = prisma;
|
||||
}
|
||||
async get() {
|
||||
const [categories, products, draftProducts, newMessages, readMessages, archivedMessages, media, messages, activity, categoryRows, sectionRows,] = await Promise.all([
|
||||
this.prisma.category.count(),
|
||||
this.prisma.product.count(),
|
||||
this.prisma.product.count({ where: { isPublished: false } }),
|
||||
this.prisma.contactMessage.count({ where: { status: "NEW" } }),
|
||||
this.prisma.contactMessage.count({ where: { status: "READ" } }),
|
||||
this.prisma.contactMessage.count({ where: { status: "ARCHIVED" } }),
|
||||
this.prisma.media.count(),
|
||||
this.prisma.contactMessage.findMany({ orderBy: { id: "desc" }, take: 6 }),
|
||||
this.prisma.auditLog.findMany({
|
||||
orderBy: { id: "desc" },
|
||||
take: 10,
|
||||
include: { user: { select: { name: true } } },
|
||||
}),
|
||||
this.prisma.category.findMany({
|
||||
select: { id: true, name: true, _count: { select: { products: true } } },
|
||||
orderBy: { products: { _count: "desc" } },
|
||||
take: 7,
|
||||
}),
|
||||
this.prisma.siteSection.findMany(),
|
||||
]);
|
||||
let hiddenCount = 0;
|
||||
for (const s of sectionRows) {
|
||||
if (s.data && typeof s.data === "object" && s.data.enabled === false) {
|
||||
hiddenCount++;
|
||||
}
|
||||
}
|
||||
const now = new Date();
|
||||
const trend = [];
|
||||
for (let i = 6; i >= 0; i--) {
|
||||
const start = new Date(now.getFullYear(), now.getMonth(), now.getDate() - i, 0, 0, 0);
|
||||
const end = new Date(now.getFullYear(), now.getMonth(), now.getDate() - i, 23, 59, 59, 999);
|
||||
const count = await this.prisma.contactMessage.count({
|
||||
where: { createdAt: { gte: start, lte: end } },
|
||||
});
|
||||
trend.push({
|
||||
date: start.toISOString().slice(0, 10),
|
||||
dayName: DAY_NAMES[start.getDay()],
|
||||
messages: count,
|
||||
});
|
||||
}
|
||||
return {
|
||||
categories,
|
||||
products,
|
||||
draftProducts,
|
||||
newMessages,
|
||||
media,
|
||||
recentMessages: messages.map(toMessageDto),
|
||||
recentActivity: activity.map((a) => ({
|
||||
id: a.id,
|
||||
action: a.action,
|
||||
entity: a.entity,
|
||||
summary: a.summary,
|
||||
user: a.user?.name ?? null,
|
||||
createdAt: a.createdAt.toISOString(),
|
||||
})),
|
||||
categoryStats: categoryRows.map((c) => ({
|
||||
id: c.id,
|
||||
name: c.name,
|
||||
count: c._count.products,
|
||||
})),
|
||||
messageBreakdown: {
|
||||
new: newMessages,
|
||||
read: readMessages,
|
||||
archived: archivedMessages,
|
||||
total: newMessages + readMessages + archivedMessages,
|
||||
},
|
||||
sectionsSummary: {
|
||||
total: SECTION_KEYS.length,
|
||||
visible: SECTION_KEYS.length - hiddenCount,
|
||||
hidden: hiddenCount,
|
||||
},
|
||||
trend,
|
||||
};
|
||||
}
|
||||
};
|
||||
__decorate([
|
||||
Get(),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", []),
|
||||
__metadata("design:returntype", Promise)
|
||||
], DashboardController.prototype, "get", null);
|
||||
DashboardController = __decorate([
|
||||
AdminAuth(),
|
||||
Controller("admin/dashboard"),
|
||||
__metadata("design:paramtypes", [PrismaService])
|
||||
], DashboardController);
|
||||
export { DashboardController };
|
||||
let DashboardModule = class DashboardModule {
|
||||
};
|
||||
DashboardModule = __decorate([
|
||||
Module({ controllers: [DashboardController] })
|
||||
], DashboardModule);
|
||||
export { DashboardModule };
|
||||
//# sourceMappingURL=dashboard.module.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"dashboard.module.js","sourceRoot":"","sources":["../../src/dashboard/dashboard.module.ts"],"names":[],"mappings":"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"}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
import * as Prisma from './internal/prismaNamespaceBrowser.js';
|
||||
export { Prisma };
|
||||
export * as $Enums from './enums.js';
|
||||
export * from './enums.js';
|
||||
//# sourceMappingURL=browser.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"browser.js","sourceRoot":"","sources":["../../../src/generated/prisma/browser.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,MAAM,MAAM,sCAAsC,CAAA;AAC9D,OAAO,EAAE,MAAM,EAAE,CAAA;AACjB,OAAO,KAAK,MAAM,MAAM,YAAY,CAAA;AACpC,cAAc,YAAY,CAAC"}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import * as path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
globalThis['__dirname'] = path.dirname(fileURLToPath(import.meta.url));
|
||||
import * as $Class from "./internal/class.js";
|
||||
import * as Prisma from "./internal/prismaNamespace.js";
|
||||
export * as $Enums from './enums.js';
|
||||
export * from "./enums.js";
|
||||
export const PrismaClient = $Class.getPrismaClientClass();
|
||||
export { Prisma };
|
||||
//# sourceMappingURL=client.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/generated/prisma/client.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AACjC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,UAAU,CAAC,WAAW,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;AAItE,OAAO,KAAK,MAAM,MAAM,qBAAqB,CAAA;AAC7C,OAAO,KAAK,MAAM,MAAM,+BAA+B,CAAA;AAEvD,OAAO,KAAK,MAAM,MAAM,YAAY,CAAA;AACpC,cAAc,YAAY,CAAA;AAgB1B,MAAM,CAAC,MAAM,YAAY,GAAG,MAAM,CAAC,oBAAoB,EAAE,CAAA;AAEzD,OAAO,EAAE,MAAM,EAAE,CAAA"}
|
||||
@@ -0,0 +1,2 @@
|
||||
export {};
|
||||
//# sourceMappingURL=commonInputTypes.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"commonInputTypes.js","sourceRoot":"","sources":["../../../src/generated/prisma/commonInputTypes.ts"],"names":[],"mappings":""}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
export const Role = {
|
||||
ADMIN: 'ADMIN',
|
||||
EDITOR: 'EDITOR'
|
||||
};
|
||||
export const MessageStatus = {
|
||||
NEW: 'NEW',
|
||||
READ: 'READ',
|
||||
ARCHIVED: 'ARCHIVED'
|
||||
};
|
||||
//# sourceMappingURL=enums.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"enums.js","sourceRoot":"","sources":["../../../src/generated/prisma/enums.ts"],"names":[],"mappings":"AAWA,MAAM,CAAC,MAAM,IAAI,GAAG;IAClB,KAAK,EAAE,OAAO;IACd,MAAM,EAAE,QAAQ;CACR,CAAA;AAKV,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,GAAG,EAAE,KAAK;IACV,IAAI,EAAE,MAAM;IACZ,QAAQ,EAAE,UAAU;CACZ,CAAA"}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"class.js","sourceRoot":"","sources":["../../../../src/generated/prisma/internal/class.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,OAAO,MAAM,+BAA+B,CAAA;AAIxD,MAAM,MAAM,GAAkC;IAC5C,iBAAiB,EAAE,EAAE;IACrB,eAAe,EAAE,QAAQ;IACzB,eAAe,EAAE,0CAA0C;IAC3D,gBAAgB,EAAE,YAAY;IAC9B,cAAc,EAAE,qlMAAqlM;IACrmM,kBAAkB,EAAE;QAClB,QAAQ,EAAE,EAAE;QACZ,OAAO,EAAE,EAAE;QACX,OAAO,EAAE,EAAE;KACZ;IACD,wBAAwB,EAAE;QACxB,SAAS,EAAE,EAAE;QACb,OAAO,EAAE,EAAE;KACZ;CACF,CAAA;AAED,MAAM,CAAC,gBAAgB,GAAG,IAAI,CAAC,KAAK,CAAC,83QAA83Q,CAAC,CAAA;AACp6Q,MAAM,CAAC,sBAAsB,GAAG;IAC9B,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,w0LAAw0L,CAAC;IAC71L,KAAK,EAAE,0psCAA0psC;CAClqsC,CAAA;AAED,KAAK,UAAU,kBAAkB,CAAC,UAAkB;IAClD,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAA;IAC9C,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAA;IACnD,OAAO,IAAI,WAAW,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;AAC1C,CAAC;AAED,MAAM,CAAC,YAAY,GAAG;IACpB,UAAU,EAAE,KAAK,IAAI,EAAE,CAAC,MAAM,MAAM,CAAC,8DAA8D,CAAC;IAEpG,0BAA0B,EAAE,KAAK,IAAI,EAAE;QACrC,MAAM,EAAE,IAAI,EAAE,GAAG,MAAM,MAAM,CAAC,0EAA0E,CAAC,CAAA;QACzG,OAAO,MAAM,kBAAkB,CAAC,IAAI,CAAC,CAAA;IACvC,CAAC;IAED,UAAU,EAAE,6BAA6B;CAC1C,CAAA;AA4OD,MAAM,UAAU,oBAAoB;IAClC,OAAO,OAAO,CAAC,eAAe,CAAC,MAAM,CAAuC,CAAA;AAC9E,CAAC"}
|
||||
@@ -0,0 +1,181 @@
|
||||
import * as runtime from "@prisma/client/runtime/client";
|
||||
export const PrismaClientKnownRequestError = runtime.PrismaClientKnownRequestError;
|
||||
export const PrismaClientUnknownRequestError = runtime.PrismaClientUnknownRequestError;
|
||||
export const PrismaClientRustPanicError = runtime.PrismaClientRustPanicError;
|
||||
export const PrismaClientInitializationError = runtime.PrismaClientInitializationError;
|
||||
export const PrismaClientValidationError = runtime.PrismaClientValidationError;
|
||||
export const sql = runtime.sqltag;
|
||||
export const empty = runtime.empty;
|
||||
export const join = runtime.join;
|
||||
export const raw = runtime.raw;
|
||||
export const Sql = runtime.Sql;
|
||||
export const Decimal = runtime.Decimal;
|
||||
export const getExtensionContext = runtime.Extensions.getExtensionContext;
|
||||
export const prismaVersion = {
|
||||
client: "7.10.1",
|
||||
engine: "d1b3f377ca835d73fd50954a5e22128b9ebdd6b8"
|
||||
};
|
||||
export const NullTypes = {
|
||||
DbNull: runtime.NullTypes.DbNull,
|
||||
JsonNull: runtime.NullTypes.JsonNull,
|
||||
AnyNull: runtime.NullTypes.AnyNull,
|
||||
};
|
||||
export const DbNull = runtime.DbNull;
|
||||
export const JsonNull = runtime.JsonNull;
|
||||
export const AnyNull = runtime.AnyNull;
|
||||
export const ModelName = {
|
||||
AdminUser: 'AdminUser',
|
||||
RefreshToken: 'RefreshToken',
|
||||
Media: 'Media',
|
||||
Category: 'Category',
|
||||
ProductGroup: 'ProductGroup',
|
||||
Product: 'Product',
|
||||
ProductImage: 'ProductImage',
|
||||
SiteSection: 'SiteSection',
|
||||
ContactMessage: 'ContactMessage',
|
||||
AuditLog: 'AuditLog'
|
||||
};
|
||||
export const TransactionIsolationLevel = runtime.makeStrictEnum({
|
||||
ReadUncommitted: 'ReadUncommitted',
|
||||
ReadCommitted: 'ReadCommitted',
|
||||
RepeatableRead: 'RepeatableRead',
|
||||
Serializable: 'Serializable'
|
||||
});
|
||||
export const AdminUserScalarFieldEnum = {
|
||||
id: 'id',
|
||||
phone: 'phone',
|
||||
name: 'name',
|
||||
passwordHash: 'passwordHash',
|
||||
role: 'role',
|
||||
isActive: 'isActive',
|
||||
lastLoginAt: 'lastLoginAt',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
};
|
||||
export const RefreshTokenScalarFieldEnum = {
|
||||
id: 'id',
|
||||
userId: 'userId',
|
||||
tokenHash: 'tokenHash',
|
||||
expiresAt: 'expiresAt',
|
||||
revokedAt: 'revokedAt',
|
||||
userAgent: 'userAgent',
|
||||
createdAt: 'createdAt'
|
||||
};
|
||||
export const MediaScalarFieldEnum = {
|
||||
id: 'id',
|
||||
key: 'key',
|
||||
mime: 'mime',
|
||||
size: 'size',
|
||||
width: 'width',
|
||||
height: 'height',
|
||||
alt: 'alt',
|
||||
originalName: 'originalName',
|
||||
createdAt: 'createdAt'
|
||||
};
|
||||
export const CategoryScalarFieldEnum = {
|
||||
id: 'id',
|
||||
slug: 'slug',
|
||||
code: 'code',
|
||||
name: 'name',
|
||||
nameEn: 'nameEn',
|
||||
shortDesc: 'shortDesc',
|
||||
fullDesc: 'fullDesc',
|
||||
icon: 'icon',
|
||||
coverImageId: 'coverImageId',
|
||||
sortOrder: 'sortOrder',
|
||||
isPublished: 'isPublished',
|
||||
seoTitle: 'seoTitle',
|
||||
seoDescription: 'seoDescription',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
};
|
||||
export const ProductGroupScalarFieldEnum = {
|
||||
id: 'id',
|
||||
categoryId: 'categoryId',
|
||||
slug: 'slug',
|
||||
name: 'name',
|
||||
desc: 'desc',
|
||||
sortOrder: 'sortOrder'
|
||||
};
|
||||
export const ProductScalarFieldEnum = {
|
||||
id: 'id',
|
||||
categoryId: 'categoryId',
|
||||
groupId: 'groupId',
|
||||
code: 'code',
|
||||
slug: 'slug',
|
||||
name: 'name',
|
||||
nameEn: 'nameEn',
|
||||
desc: 'desc',
|
||||
specs: 'specs',
|
||||
variants: 'variants',
|
||||
details: 'details',
|
||||
brand: 'brand',
|
||||
origin: 'origin',
|
||||
sizes: 'sizes',
|
||||
standards: 'standards',
|
||||
features: 'features',
|
||||
applications: 'applications',
|
||||
attributes: 'attributes',
|
||||
packaging: 'packaging',
|
||||
minOrder: 'minOrder',
|
||||
seoTitle: 'seoTitle',
|
||||
seoDescription: 'seoDescription',
|
||||
imageId: 'imageId',
|
||||
sortOrder: 'sortOrder',
|
||||
isPublished: 'isPublished',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
};
|
||||
export const ProductImageScalarFieldEnum = {
|
||||
productId: 'productId',
|
||||
mediaId: 'mediaId',
|
||||
sortOrder: 'sortOrder'
|
||||
};
|
||||
export const SiteSectionScalarFieldEnum = {
|
||||
key: 'key',
|
||||
data: 'data',
|
||||
updatedById: 'updatedById',
|
||||
updatedAt: 'updatedAt'
|
||||
};
|
||||
export const ContactMessageScalarFieldEnum = {
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
org: 'org',
|
||||
phone: 'phone',
|
||||
message: 'message',
|
||||
status: 'status',
|
||||
ip: 'ip',
|
||||
userAgent: 'userAgent',
|
||||
createdAt: 'createdAt'
|
||||
};
|
||||
export const AuditLogScalarFieldEnum = {
|
||||
id: 'id',
|
||||
userId: 'userId',
|
||||
action: 'action',
|
||||
entity: 'entity',
|
||||
entityId: 'entityId',
|
||||
summary: 'summary',
|
||||
createdAt: 'createdAt'
|
||||
};
|
||||
export const SortOrder = {
|
||||
asc: 'asc',
|
||||
desc: 'desc'
|
||||
};
|
||||
export const JsonNullValueInput = {
|
||||
JsonNull: JsonNull
|
||||
};
|
||||
export const QueryMode = {
|
||||
default: 'default',
|
||||
insensitive: 'insensitive'
|
||||
};
|
||||
export const NullsOrder = {
|
||||
first: 'first',
|
||||
last: 'last'
|
||||
};
|
||||
export const JsonNullValueFilter = {
|
||||
DbNull: DbNull,
|
||||
JsonNull: JsonNull,
|
||||
AnyNull: AnyNull
|
||||
};
|
||||
export const defineExtension = runtime.Extensions.defineExtension;
|
||||
//# sourceMappingURL=prismaNamespace.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"prismaNamespace.js","sourceRoot":"","sources":["../../../../src/generated/prisma/internal/prismaNamespace.ts"],"names":[],"mappings":"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"}
|
||||
@@ -0,0 +1,165 @@
|
||||
import * as runtime from "@prisma/client/runtime/index-browser";
|
||||
export const Decimal = runtime.Decimal;
|
||||
export const NullTypes = {
|
||||
DbNull: runtime.NullTypes.DbNull,
|
||||
JsonNull: runtime.NullTypes.JsonNull,
|
||||
AnyNull: runtime.NullTypes.AnyNull,
|
||||
};
|
||||
export const DbNull = runtime.DbNull;
|
||||
export const JsonNull = runtime.JsonNull;
|
||||
export const AnyNull = runtime.AnyNull;
|
||||
export const ModelName = {
|
||||
AdminUser: 'AdminUser',
|
||||
RefreshToken: 'RefreshToken',
|
||||
Media: 'Media',
|
||||
Category: 'Category',
|
||||
ProductGroup: 'ProductGroup',
|
||||
Product: 'Product',
|
||||
ProductImage: 'ProductImage',
|
||||
SiteSection: 'SiteSection',
|
||||
ContactMessage: 'ContactMessage',
|
||||
AuditLog: 'AuditLog'
|
||||
};
|
||||
export const TransactionIsolationLevel = runtime.makeStrictEnum({
|
||||
ReadUncommitted: 'ReadUncommitted',
|
||||
ReadCommitted: 'ReadCommitted',
|
||||
RepeatableRead: 'RepeatableRead',
|
||||
Serializable: 'Serializable'
|
||||
});
|
||||
export const AdminUserScalarFieldEnum = {
|
||||
id: 'id',
|
||||
phone: 'phone',
|
||||
name: 'name',
|
||||
passwordHash: 'passwordHash',
|
||||
role: 'role',
|
||||
isActive: 'isActive',
|
||||
lastLoginAt: 'lastLoginAt',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
};
|
||||
export const RefreshTokenScalarFieldEnum = {
|
||||
id: 'id',
|
||||
userId: 'userId',
|
||||
tokenHash: 'tokenHash',
|
||||
expiresAt: 'expiresAt',
|
||||
revokedAt: 'revokedAt',
|
||||
userAgent: 'userAgent',
|
||||
createdAt: 'createdAt'
|
||||
};
|
||||
export const MediaScalarFieldEnum = {
|
||||
id: 'id',
|
||||
key: 'key',
|
||||
mime: 'mime',
|
||||
size: 'size',
|
||||
width: 'width',
|
||||
height: 'height',
|
||||
alt: 'alt',
|
||||
originalName: 'originalName',
|
||||
createdAt: 'createdAt'
|
||||
};
|
||||
export const CategoryScalarFieldEnum = {
|
||||
id: 'id',
|
||||
slug: 'slug',
|
||||
code: 'code',
|
||||
name: 'name',
|
||||
nameEn: 'nameEn',
|
||||
shortDesc: 'shortDesc',
|
||||
fullDesc: 'fullDesc',
|
||||
icon: 'icon',
|
||||
coverImageId: 'coverImageId',
|
||||
sortOrder: 'sortOrder',
|
||||
isPublished: 'isPublished',
|
||||
seoTitle: 'seoTitle',
|
||||
seoDescription: 'seoDescription',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
};
|
||||
export const ProductGroupScalarFieldEnum = {
|
||||
id: 'id',
|
||||
categoryId: 'categoryId',
|
||||
slug: 'slug',
|
||||
name: 'name',
|
||||
desc: 'desc',
|
||||
sortOrder: 'sortOrder'
|
||||
};
|
||||
export const ProductScalarFieldEnum = {
|
||||
id: 'id',
|
||||
categoryId: 'categoryId',
|
||||
groupId: 'groupId',
|
||||
code: 'code',
|
||||
slug: 'slug',
|
||||
name: 'name',
|
||||
nameEn: 'nameEn',
|
||||
desc: 'desc',
|
||||
specs: 'specs',
|
||||
variants: 'variants',
|
||||
details: 'details',
|
||||
brand: 'brand',
|
||||
origin: 'origin',
|
||||
sizes: 'sizes',
|
||||
standards: 'standards',
|
||||
features: 'features',
|
||||
applications: 'applications',
|
||||
attributes: 'attributes',
|
||||
packaging: 'packaging',
|
||||
minOrder: 'minOrder',
|
||||
seoTitle: 'seoTitle',
|
||||
seoDescription: 'seoDescription',
|
||||
imageId: 'imageId',
|
||||
sortOrder: 'sortOrder',
|
||||
isPublished: 'isPublished',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
};
|
||||
export const ProductImageScalarFieldEnum = {
|
||||
productId: 'productId',
|
||||
mediaId: 'mediaId',
|
||||
sortOrder: 'sortOrder'
|
||||
};
|
||||
export const SiteSectionScalarFieldEnum = {
|
||||
key: 'key',
|
||||
data: 'data',
|
||||
updatedById: 'updatedById',
|
||||
updatedAt: 'updatedAt'
|
||||
};
|
||||
export const ContactMessageScalarFieldEnum = {
|
||||
id: 'id',
|
||||
name: 'name',
|
||||
org: 'org',
|
||||
phone: 'phone',
|
||||
message: 'message',
|
||||
status: 'status',
|
||||
ip: 'ip',
|
||||
userAgent: 'userAgent',
|
||||
createdAt: 'createdAt'
|
||||
};
|
||||
export const AuditLogScalarFieldEnum = {
|
||||
id: 'id',
|
||||
userId: 'userId',
|
||||
action: 'action',
|
||||
entity: 'entity',
|
||||
entityId: 'entityId',
|
||||
summary: 'summary',
|
||||
createdAt: 'createdAt'
|
||||
};
|
||||
export const SortOrder = {
|
||||
asc: 'asc',
|
||||
desc: 'desc'
|
||||
};
|
||||
export const JsonNullValueInput = {
|
||||
JsonNull: JsonNull
|
||||
};
|
||||
export const QueryMode = {
|
||||
default: 'default',
|
||||
insensitive: 'insensitive'
|
||||
};
|
||||
export const NullsOrder = {
|
||||
first: 'first',
|
||||
last: 'last'
|
||||
};
|
||||
export const JsonNullValueFilter = {
|
||||
DbNull: DbNull,
|
||||
JsonNull: JsonNull,
|
||||
AnyNull: AnyNull
|
||||
};
|
||||
//# sourceMappingURL=prismaNamespaceBrowser.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"prismaNamespaceBrowser.js","sourceRoot":"","sources":["../../../../src/generated/prisma/internal/prismaNamespaceBrowser.ts"],"names":[],"mappings":"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"}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
export {};
|
||||
//# sourceMappingURL=models.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"models.js","sourceRoot":"","sources":["../../../src/generated/prisma/models.ts"],"names":[],"mappings":""}
|
||||
@@ -0,0 +1,2 @@
|
||||
export {};
|
||||
//# sourceMappingURL=AdminUser.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"AdminUser.js","sourceRoot":"","sources":["../../../../src/generated/prisma/models/AdminUser.ts"],"names":[],"mappings":""}
|
||||
@@ -0,0 +1,2 @@
|
||||
export {};
|
||||
//# sourceMappingURL=AuditLog.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"AuditLog.js","sourceRoot":"","sources":["../../../../src/generated/prisma/models/AuditLog.ts"],"names":[],"mappings":""}
|
||||
@@ -0,0 +1,2 @@
|
||||
export {};
|
||||
//# sourceMappingURL=Category.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"Category.js","sourceRoot":"","sources":["../../../../src/generated/prisma/models/Category.ts"],"names":[],"mappings":""}
|
||||
@@ -0,0 +1,2 @@
|
||||
export {};
|
||||
//# sourceMappingURL=ContactMessage.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ContactMessage.js","sourceRoot":"","sources":["../../../../src/generated/prisma/models/ContactMessage.ts"],"names":[],"mappings":""}
|
||||
@@ -0,0 +1,2 @@
|
||||
export {};
|
||||
//# sourceMappingURL=Media.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"Media.js","sourceRoot":"","sources":["../../../../src/generated/prisma/models/Media.ts"],"names":[],"mappings":""}
|
||||
@@ -0,0 +1,2 @@
|
||||
export {};
|
||||
//# sourceMappingURL=Product.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"Product.js","sourceRoot":"","sources":["../../../../src/generated/prisma/models/Product.ts"],"names":[],"mappings":""}
|
||||
@@ -0,0 +1,2 @@
|
||||
export {};
|
||||
//# sourceMappingURL=ProductGroup.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ProductGroup.js","sourceRoot":"","sources":["../../../../src/generated/prisma/models/ProductGroup.ts"],"names":[],"mappings":""}
|
||||
@@ -0,0 +1,2 @@
|
||||
export {};
|
||||
//# sourceMappingURL=ProductImage.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"ProductImage.js","sourceRoot":"","sources":["../../../../src/generated/prisma/models/ProductImage.ts"],"names":[],"mappings":""}
|
||||
@@ -0,0 +1,2 @@
|
||||
export {};
|
||||
//# sourceMappingURL=RefreshToken.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"RefreshToken.js","sourceRoot":"","sources":["../../../../src/generated/prisma/models/RefreshToken.ts"],"names":[],"mappings":""}
|
||||
@@ -0,0 +1,2 @@
|
||||
export {};
|
||||
//# sourceMappingURL=SiteSection.js.map
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"SiteSection.js","sourceRoot":"","sources":["../../../../src/generated/prisma/models/SiteSection.ts"],"names":[],"mappings":""}
|
||||
Vendored
+21
@@ -0,0 +1,21 @@
|
||||
import { env } from "./config/env.js";
|
||||
import { NestFactory } from "@nestjs/core";
|
||||
import cookieParser from "cookie-parser";
|
||||
import helmet from "helmet";
|
||||
import { AppModule } from "./app.module.js";
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
app.set("trust proxy", env.TRUST_PROXY);
|
||||
app.setGlobalPrefix("api");
|
||||
app.use(helmet());
|
||||
app.use(cookieParser());
|
||||
const origins = env.CORS_ORIGINS.split(",")
|
||||
.map((o) => o.trim())
|
||||
.filter(Boolean);
|
||||
if (origins.length)
|
||||
app.enableCors({ origin: origins, credentials: true });
|
||||
app.enableShutdownHooks();
|
||||
await app.listen(env.PORT);
|
||||
}
|
||||
await bootstrap();
|
||||
//# sourceMappingURL=main.js.map
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAE3C,OAAO,YAAY,MAAM,eAAe,CAAC;AACzC,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,KAAK,UAAU,SAAS;IACtB,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC,MAAM,CAAyB,SAAS,CAAC,CAAC;IAGxE,GAAG,CAAC,GAAG,CAAC,aAAa,EAAE,GAAG,CAAC,WAAW,CAAC,CAAC;IACxC,GAAG,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;IAC3B,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC;IAClB,GAAG,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC,CAAC;IAExB,MAAM,OAAO,GAAG,GAAG,CAAC,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC;SACxC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,OAAO,CAAC,CAAC;IACnB,IAAI,OAAO,CAAC,MAAM;QAAE,GAAG,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;IAE3E,GAAG,CAAC,mBAAmB,EAAE,CAAC;IAC1B,MAAM,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;AAC7B,CAAC;AAED,MAAM,SAAS,EAAE,CAAC"}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
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);
|
||||
};
|
||||
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
||||
return function (target, key) { decorator(target, key, paramIndex); }
|
||||
};
|
||||
import { Body, Controller, Delete, Get, HttpCode, Module, Param, Patch, Post, Query, UploadedFile, UseInterceptors, } from "@nestjs/common";
|
||||
import { FileInterceptor } from "@nestjs/platform-express";
|
||||
import { mediaUpdateSchema } from "@imen/shared";
|
||||
import { z } from "zod";
|
||||
import { AdminAuth, CurrentUser, Roles } from "../common/auth.js";
|
||||
import { IdPipe, ZodPipe } from "../common/zod.pipe.js";
|
||||
import { MediaService } from "./media.service.js";
|
||||
const listQuery = z.object({
|
||||
page: z.coerce.number().int().min(1).default(1),
|
||||
pageSize: z.coerce.number().int().min(1).max(100).default(40),
|
||||
q: z.string().trim().max(100).optional(),
|
||||
});
|
||||
let MediaController = class MediaController {
|
||||
media;
|
||||
constructor(media) {
|
||||
this.media = media;
|
||||
}
|
||||
list(q) {
|
||||
return this.media.list(q.page, q.pageSize, q.q);
|
||||
}
|
||||
upload(file, alt, user) {
|
||||
return this.media.upload(file, typeof alt === "string" ? alt : "", user.id);
|
||||
}
|
||||
update(id, body, user) {
|
||||
return this.media.updateAlt(id, body.alt, user.id);
|
||||
}
|
||||
async remove(id, user) {
|
||||
await this.media.remove(id, user.id);
|
||||
}
|
||||
};
|
||||
__decorate([
|
||||
Get(),
|
||||
__param(0, Query(new ZodPipe(listQuery))),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object]),
|
||||
__metadata("design:returntype", void 0)
|
||||
], MediaController.prototype, "list", null);
|
||||
__decorate([
|
||||
Post(),
|
||||
UseInterceptors(FileInterceptor("file", { limits: { fileSize: 15 * 1024 * 1024 } })),
|
||||
__param(0, UploadedFile()),
|
||||
__param(1, Body("alt")),
|
||||
__param(2, CurrentUser()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object, Object, Object]),
|
||||
__metadata("design:returntype", void 0)
|
||||
], MediaController.prototype, "upload", null);
|
||||
__decorate([
|
||||
Patch(":id"),
|
||||
__param(0, Param("id", IdPipe)),
|
||||
__param(1, Body(new ZodPipe(mediaUpdateSchema))),
|
||||
__param(2, CurrentUser()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Number, Object, Object]),
|
||||
__metadata("design:returntype", void 0)
|
||||
], MediaController.prototype, "update", null);
|
||||
__decorate([
|
||||
Roles("ADMIN"),
|
||||
Delete(":id"),
|
||||
HttpCode(204),
|
||||
__param(0, Param("id", IdPipe)),
|
||||
__param(1, CurrentUser()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Number, Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], MediaController.prototype, "remove", null);
|
||||
MediaController = __decorate([
|
||||
AdminAuth(),
|
||||
Controller("admin/media"),
|
||||
__metadata("design:paramtypes", [MediaService])
|
||||
], MediaController);
|
||||
export { MediaController };
|
||||
let MediaModule = class MediaModule {
|
||||
};
|
||||
MediaModule = __decorate([
|
||||
Module({
|
||||
controllers: [MediaController],
|
||||
providers: [MediaService],
|
||||
exports: [MediaService],
|
||||
})
|
||||
], MediaModule);
|
||||
export { MediaModule };
|
||||
//# sourceMappingURL=media.controller.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"media.controller.js","sourceRoot":"","sources":["../../src/media/media.controller.ts"],"names":[],"mappings":"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"}
|
||||
Vendored
+138
@@ -0,0 +1,138 @@
|
||||
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);
|
||||
};
|
||||
var MediaService_1;
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { BadRequestException, ConflictException, Injectable, Logger, NotFoundException, } from "@nestjs/common";
|
||||
import { env } from "../config/env.js";
|
||||
import { AuditService, RevalidateService } from "../common/common.module.js";
|
||||
import { PrismaService } from "../prisma/prisma.service.js";
|
||||
import { ACCEPTED_IMAGE_TYPES, S3Storage, toWebp } from "./s3-storage.js";
|
||||
let MediaService = MediaService_1 = class MediaService {
|
||||
prisma;
|
||||
audit;
|
||||
revalidate;
|
||||
logger = new Logger(MediaService_1.name);
|
||||
storage = new S3Storage();
|
||||
constructor(prisma, audit, revalidate) {
|
||||
this.prisma = prisma;
|
||||
this.audit = audit;
|
||||
this.revalidate = revalidate;
|
||||
}
|
||||
async onModuleInit() {
|
||||
if (!env.S3_ENSURE_BUCKET)
|
||||
return;
|
||||
try {
|
||||
await this.storage.ensureBucket();
|
||||
}
|
||||
catch (e) {
|
||||
this.logger.error(`could not prepare bucket "${env.S3_BUCKET}": ${String(e)}`);
|
||||
}
|
||||
}
|
||||
url(key) {
|
||||
return this.storage.publicUrl(key);
|
||||
}
|
||||
toDto(m) {
|
||||
return {
|
||||
id: m.id,
|
||||
url: this.url(m.key),
|
||||
alt: m.alt,
|
||||
mime: m.mime,
|
||||
size: m.size,
|
||||
width: m.width,
|
||||
height: m.height,
|
||||
originalName: m.originalName,
|
||||
createdAt: m.createdAt.toISOString(),
|
||||
};
|
||||
}
|
||||
async list(page, pageSize, q) {
|
||||
const where = q
|
||||
? {
|
||||
OR: [
|
||||
{ originalName: { contains: q, mode: "insensitive" } },
|
||||
{ alt: { contains: q, mode: "insensitive" } },
|
||||
],
|
||||
}
|
||||
: {};
|
||||
const [items, total] = await this.prisma.$transaction([
|
||||
this.prisma.media.findMany({
|
||||
where,
|
||||
orderBy: { id: "desc" },
|
||||
skip: (page - 1) * pageSize,
|
||||
take: pageSize,
|
||||
}),
|
||||
this.prisma.media.count({ where }),
|
||||
]);
|
||||
return { items: items.map((m) => this.toDto(m)), total, page, pageSize };
|
||||
}
|
||||
async upload(file, alt, userId) {
|
||||
if (!file)
|
||||
throw new BadRequestException("فایلی ارسال نشده است");
|
||||
if (!ACCEPTED_IMAGE_TYPES.includes(file.mimetype)) {
|
||||
throw new BadRequestException("فقط تصاویر JPG، PNG، WebP، AVIF و GIF پذیرفته میشوند");
|
||||
}
|
||||
let image;
|
||||
try {
|
||||
image = await toWebp(file.buffer);
|
||||
}
|
||||
catch {
|
||||
throw new BadRequestException("فایل تصویر قابل پردازش نیست");
|
||||
}
|
||||
const now = new Date();
|
||||
const month = String(now.getMonth() + 1).padStart(2, "0");
|
||||
const key = `uploads/${now.getFullYear()}/${month}/${randomUUID()}.webp`;
|
||||
await this.storage.put(key, image.data, "image/webp");
|
||||
const media = await this.prisma.media.create({
|
||||
data: {
|
||||
key,
|
||||
mime: "image/webp",
|
||||
size: image.size,
|
||||
width: image.width,
|
||||
height: image.height,
|
||||
alt: alt.trim().slice(0, 200),
|
||||
originalName: Buffer.from(file.originalname, "latin1").toString("utf8").slice(0, 200),
|
||||
},
|
||||
});
|
||||
this.audit.log(userId, "upload", "media", media.id, media.originalName);
|
||||
return this.toDto(media);
|
||||
}
|
||||
async updateAlt(id, alt, userId) {
|
||||
const media = await this.prisma.media.update({ where: { id }, data: { alt } });
|
||||
this.audit.log(userId, "update", "media", id, media.originalName);
|
||||
this.revalidate.trigger();
|
||||
return this.toDto(media);
|
||||
}
|
||||
async remove(id, userId) {
|
||||
const media = await this.prisma.media.findUnique({
|
||||
where: { id },
|
||||
include: { _count: { select: { categoryCovers: true, productImages: true, productGallery: true } } },
|
||||
});
|
||||
if (!media)
|
||||
throw new NotFoundException("فایل پیدا نشد");
|
||||
const { categoryCovers, productImages, productGallery } = media._count;
|
||||
const sections = await this.prisma.$queryRaw `
|
||||
SELECT key FROM "SiteSection" WHERE data::text LIKE ${"%" + media.key + "%"}`;
|
||||
if (categoryCovers + productImages + productGallery + sections.length > 0) {
|
||||
throw new ConflictException("این تصویر در محصولات، دستهها یا بخشهای سایت استفاده شده و قابل حذف نیست");
|
||||
}
|
||||
await this.prisma.media.delete({ where: { id } });
|
||||
await this.storage.remove(media.key).catch((e) => {
|
||||
this.logger.warn(`could not delete object ${media.key}: ${String(e)}`);
|
||||
});
|
||||
this.audit.log(userId, "delete", "media", id, media.originalName);
|
||||
}
|
||||
};
|
||||
MediaService = MediaService_1 = __decorate([
|
||||
Injectable(),
|
||||
__metadata("design:paramtypes", [PrismaService,
|
||||
AuditService,
|
||||
RevalidateService])
|
||||
], MediaService);
|
||||
export { MediaService };
|
||||
//# sourceMappingURL=media.service.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
Vendored
+69
@@ -0,0 +1,69 @@
|
||||
import { CreateBucketCommand, DeleteObjectCommand, HeadBucketCommand, PutBucketPolicyCommand, PutObjectCommand, S3Client, } from "@aws-sdk/client-s3";
|
||||
import sharp from "sharp";
|
||||
import { env } from "../config/env.js";
|
||||
export class S3Storage {
|
||||
client = new S3Client({
|
||||
endpoint: env.S3_ENDPOINT,
|
||||
region: env.S3_REGION,
|
||||
forcePathStyle: env.S3_FORCE_PATH_STYLE,
|
||||
credentials: { accessKeyId: env.S3_ACCESS_KEY, secretAccessKey: env.S3_SECRET_KEY },
|
||||
});
|
||||
bucket = env.S3_BUCKET;
|
||||
publicUrl(key) {
|
||||
return `${env.MEDIA_PUBLIC_URL}/${key}`;
|
||||
}
|
||||
async put(key, body, contentType) {
|
||||
await this.client.send(new PutObjectCommand({
|
||||
Bucket: this.bucket,
|
||||
Key: key,
|
||||
Body: body,
|
||||
ContentType: contentType,
|
||||
CacheControl: "public, max-age=31536000, immutable",
|
||||
}));
|
||||
}
|
||||
async remove(key) {
|
||||
await this.client.send(new DeleteObjectCommand({ Bucket: this.bucket, Key: key }));
|
||||
}
|
||||
async ensureBucket() {
|
||||
try {
|
||||
await this.client.send(new HeadBucketCommand({ Bucket: this.bucket }));
|
||||
}
|
||||
catch {
|
||||
await this.client.send(new CreateBucketCommand({ Bucket: this.bucket }));
|
||||
}
|
||||
try {
|
||||
await this.client.send(new PutBucketPolicyCommand({
|
||||
Bucket: this.bucket,
|
||||
Policy: JSON.stringify({
|
||||
Version: "2012-10-17",
|
||||
Statement: [
|
||||
{
|
||||
Effect: "Allow",
|
||||
Principal: { AWS: ["*"] },
|
||||
Action: ["s3:GetObject"],
|
||||
Resource: [`arn:aws:s3:::${this.bucket}/*`],
|
||||
},
|
||||
],
|
||||
}),
|
||||
}));
|
||||
}
|
||||
catch {
|
||||
}
|
||||
}
|
||||
}
|
||||
export const ACCEPTED_IMAGE_TYPES = [
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/webp",
|
||||
"image/avif",
|
||||
"image/gif",
|
||||
];
|
||||
export async function toWebp(input) {
|
||||
const { data, info } = await sharp(input)
|
||||
.rotate()
|
||||
.resize({ width: 2000, height: 2000, fit: "inside", withoutEnlargement: true })
|
||||
.webp({ quality: 82 })
|
||||
.toBuffer({ resolveWithObject: true });
|
||||
return { data, width: info.width, height: info.height, size: info.size };
|
||||
}
|
||||
//# sourceMappingURL=s3-storage.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"s3-storage.js","sourceRoot":"","sources":["../../src/media/s3-storage.ts"],"names":[],"mappings":"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"}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
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);
|
||||
};
|
||||
import { Global, Injectable, Module } from "@nestjs/common";
|
||||
import { PrismaPg } from "@prisma/adapter-pg";
|
||||
import { env } from "../config/env.js";
|
||||
import { PrismaClient } from "../generated/prisma/client.js";
|
||||
export const createPrismaClient = () => new PrismaClient({ adapter: new PrismaPg({ connectionString: env.DATABASE_URL }) });
|
||||
let PrismaService = class PrismaService extends PrismaClient {
|
||||
constructor() {
|
||||
super({ adapter: new PrismaPg({ connectionString: env.DATABASE_URL }) });
|
||||
}
|
||||
async onModuleDestroy() {
|
||||
await this.$disconnect();
|
||||
}
|
||||
};
|
||||
PrismaService = __decorate([
|
||||
Injectable(),
|
||||
__metadata("design:paramtypes", [])
|
||||
], PrismaService);
|
||||
export { PrismaService };
|
||||
let PrismaModule = class PrismaModule {
|
||||
};
|
||||
PrismaModule = __decorate([
|
||||
Global(),
|
||||
Module({ providers: [PrismaService], exports: [PrismaService] })
|
||||
], PrismaModule);
|
||||
export { PrismaModule };
|
||||
//# sourceMappingURL=prisma.service.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"prisma.service.js","sourceRoot":"","sources":["../../src/prisma/prisma.service.ts"],"names":[],"mappings":";;;;;;;;;AAAA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAwB,MAAM,gBAAgB,CAAC;AAClF,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,GAAG,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAE7D,MAAM,CAAC,MAAM,kBAAkB,GAAG,GAAG,EAAE,CACrC,IAAI,YAAY,CAAC,EAAE,OAAO,EAAE,IAAI,QAAQ,CAAC,EAAE,gBAAgB,EAAE,GAAG,CAAC,YAAY,EAAE,CAAC,EAAE,CAAC,CAAC;AAG/E,IAAM,aAAa,GAAnB,MAAM,aAAc,SAAQ,YAAY;IAC7C;QACE,KAAK,CAAC,EAAE,OAAO,EAAE,IAAI,QAAQ,CAAC,EAAE,gBAAgB,EAAE,GAAG,CAAC,YAAY,EAAE,CAAC,EAAE,CAAC,CAAC;IAC3E,CAAC;IAED,KAAK,CAAC,eAAe;QACnB,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC;IAC3B,CAAC;CACF,CAAA;AARY,aAAa;IADzB,UAAU,EAAE;;GACA,aAAa,CAQzB;;AAIM,IAAM,YAAY,GAAlB,MAAM,YAAY;CAAG,CAAA;AAAf,YAAY;IAFxB,MAAM,EAAE;IACR,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC,aAAa,CAAC,EAAE,OAAO,EAAE,CAAC,aAAa,CAAC,EAAE,CAAC;GACpD,YAAY,CAAG"}
|
||||
+244
@@ -0,0 +1,244 @@
|
||||
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);
|
||||
};
|
||||
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
||||
return function (target, key) { decorator(target, key, paramIndex); }
|
||||
};
|
||||
import { Controller, Get, Injectable, Module, NotFoundException, Param } from "@nestjs/common";
|
||||
import { MediaModule } from "../media/media.controller.js";
|
||||
import { MediaService } from "../media/media.service.js";
|
||||
import { PrismaService } from "../prisma/prisma.service.js";
|
||||
import { SectionsModule, SectionsService } from "../sections/sections.module.js";
|
||||
const visible = { isPublished: true, category: { isPublished: true } };
|
||||
const RELATED_LIMIT = 4;
|
||||
const isAttribute = (a) => !!a && typeof a === "object" && typeof a.name === "string" && typeof a.value === "string";
|
||||
let PublicService = class PublicService {
|
||||
prisma;
|
||||
media;
|
||||
sections;
|
||||
constructor(prisma, media, sections) {
|
||||
this.prisma = prisma;
|
||||
this.media = media;
|
||||
this.sections = sections;
|
||||
}
|
||||
async site() {
|
||||
const [sections, categories] = await Promise.all([this.sections.getAll(), this.categories()]);
|
||||
return { sections, categories };
|
||||
}
|
||||
async categories() {
|
||||
const rows = await this.prisma.category.findMany({
|
||||
where: { isPublished: true },
|
||||
orderBy: [{ sortOrder: "asc" }, { id: "asc" }],
|
||||
include: {
|
||||
coverImage: true,
|
||||
_count: { select: { products: { where: { isPublished: true } } } },
|
||||
},
|
||||
});
|
||||
return rows.map((c) => ({
|
||||
slug: c.slug,
|
||||
code: c.code,
|
||||
name: c.name,
|
||||
nameEn: c.nameEn,
|
||||
shortDesc: c.shortDesc,
|
||||
icon: c.icon,
|
||||
coverImage: c.coverImage && this.media.url(c.coverImage.key),
|
||||
productCount: c._count.products,
|
||||
}));
|
||||
}
|
||||
async category(slug) {
|
||||
const c = await this.prisma.category.findFirst({
|
||||
where: { slug, isPublished: true },
|
||||
include: {
|
||||
coverImage: true,
|
||||
groups: { orderBy: { sortOrder: "asc" } },
|
||||
products: {
|
||||
where: { isPublished: true },
|
||||
orderBy: [{ sortOrder: "asc" }, { id: "asc" }],
|
||||
include: { image: true, group: { select: { slug: true } } },
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!c)
|
||||
throw new NotFoundException("دستهبندی یافت نشد");
|
||||
const products = c.products.map((p) => this.toPublicProduct(p));
|
||||
const usedGroups = new Set(products.map((p) => p.group));
|
||||
return {
|
||||
slug: c.slug,
|
||||
code: c.code,
|
||||
name: c.name,
|
||||
nameEn: c.nameEn,
|
||||
shortDesc: c.shortDesc,
|
||||
fullDesc: c.fullDesc,
|
||||
icon: c.icon,
|
||||
seoTitle: c.seoTitle,
|
||||
seoDescription: c.seoDescription,
|
||||
coverImage: c.coverImage && this.media.url(c.coverImage.key),
|
||||
productCount: products.length,
|
||||
groups: c.groups
|
||||
.filter((g) => usedGroups.has(g.slug))
|
||||
.map((g) => ({ slug: g.slug, name: g.name, desc: g.desc })),
|
||||
products,
|
||||
};
|
||||
}
|
||||
async product(slug) {
|
||||
const p = await this.prisma.product.findFirst({
|
||||
where: { slug, ...visible },
|
||||
include: {
|
||||
image: true,
|
||||
gallery: { orderBy: { sortOrder: "asc" }, include: { media: true } },
|
||||
group: true,
|
||||
category: { include: { coverImage: true } },
|
||||
},
|
||||
});
|
||||
if (!p)
|
||||
throw new NotFoundException("محصول یافت نشد");
|
||||
const media = new Map();
|
||||
for (const m of [p.image, ...p.gallery.map((g) => g.media)])
|
||||
if (m)
|
||||
media.set(m.id, m);
|
||||
const siblings = await this.prisma.product.findMany({
|
||||
where: { categoryId: p.categoryId, isPublished: true, id: { not: p.id } },
|
||||
orderBy: [{ sortOrder: "asc" }, { id: "asc" }],
|
||||
include: { image: true, group: { select: { slug: true } } },
|
||||
});
|
||||
const sameGroup = (s) => p.groupId !== null && s.groupId === p.groupId;
|
||||
const related = [...siblings.filter(sameGroup), ...siblings.filter((s) => !sameGroup(s))]
|
||||
.slice(0, RELATED_LIMIT)
|
||||
.map((s) => this.toPublicProduct(s));
|
||||
return {
|
||||
...this.toPublicProduct(p),
|
||||
details: p.details,
|
||||
brand: p.brand,
|
||||
origin: p.origin,
|
||||
sizes: p.sizes,
|
||||
standards: p.standards,
|
||||
features: p.features,
|
||||
applications: p.applications,
|
||||
attributes: Array.isArray(p.attributes) ? p.attributes.filter(isAttribute) : [],
|
||||
packaging: p.packaging,
|
||||
minOrder: p.minOrder,
|
||||
seoTitle: p.seoTitle,
|
||||
seoDescription: p.seoDescription,
|
||||
images: [...media.values()].map((m) => ({
|
||||
url: this.media.url(m.key),
|
||||
alt: m.alt || p.name,
|
||||
width: m.width,
|
||||
height: m.height,
|
||||
})),
|
||||
groupName: p.group?.name ?? null,
|
||||
category: {
|
||||
slug: p.category.slug,
|
||||
code: p.category.code,
|
||||
name: p.category.name,
|
||||
nameEn: p.category.nameEn,
|
||||
fullDesc: p.category.fullDesc,
|
||||
coverImage: p.category.coverImage && this.media.url(p.category.coverImage.key),
|
||||
},
|
||||
related,
|
||||
updatedAt: p.updatedAt.toISOString(),
|
||||
};
|
||||
}
|
||||
async productRefs() {
|
||||
const rows = await this.prisma.product.findMany({
|
||||
where: visible,
|
||||
orderBy: [{ category: { sortOrder: "asc" } }, { sortOrder: "asc" }],
|
||||
select: { slug: true, updatedAt: true },
|
||||
});
|
||||
return rows.map((r) => ({ slug: r.slug, updatedAt: r.updatedAt.toISOString() }));
|
||||
}
|
||||
toPublicProduct(p) {
|
||||
return {
|
||||
code: p.code,
|
||||
slug: p.slug,
|
||||
name: p.name,
|
||||
nameEn: p.nameEn,
|
||||
desc: p.desc,
|
||||
specs: p.specs,
|
||||
variants: p.variants,
|
||||
image: p.image && this.media.url(p.image.key),
|
||||
group: p.group?.slug ?? null,
|
||||
};
|
||||
}
|
||||
};
|
||||
PublicService = __decorate([
|
||||
Injectable(),
|
||||
__metadata("design:paramtypes", [PrismaService,
|
||||
MediaService,
|
||||
SectionsService])
|
||||
], PublicService);
|
||||
export { PublicService };
|
||||
let PublicController = class PublicController {
|
||||
publicData;
|
||||
constructor(publicData) {
|
||||
this.publicData = publicData;
|
||||
}
|
||||
site() {
|
||||
return this.publicData.site();
|
||||
}
|
||||
categories() {
|
||||
return this.publicData.categories();
|
||||
}
|
||||
category(slug) {
|
||||
return this.publicData.category(slug);
|
||||
}
|
||||
products() {
|
||||
return this.publicData.productRefs();
|
||||
}
|
||||
product(slug) {
|
||||
return this.publicData.product(slug);
|
||||
}
|
||||
};
|
||||
__decorate([
|
||||
Get("site"),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", []),
|
||||
__metadata("design:returntype", void 0)
|
||||
], PublicController.prototype, "site", null);
|
||||
__decorate([
|
||||
Get("categories"),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", []),
|
||||
__metadata("design:returntype", void 0)
|
||||
], PublicController.prototype, "categories", null);
|
||||
__decorate([
|
||||
Get("categories/:slug"),
|
||||
__param(0, Param("slug")),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [String]),
|
||||
__metadata("design:returntype", void 0)
|
||||
], PublicController.prototype, "category", null);
|
||||
__decorate([
|
||||
Get("products"),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", []),
|
||||
__metadata("design:returntype", void 0)
|
||||
], PublicController.prototype, "products", null);
|
||||
__decorate([
|
||||
Get("products/:slug"),
|
||||
__param(0, Param("slug")),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [String]),
|
||||
__metadata("design:returntype", void 0)
|
||||
], PublicController.prototype, "product", null);
|
||||
PublicController = __decorate([
|
||||
Controller("public"),
|
||||
__metadata("design:paramtypes", [PublicService])
|
||||
], PublicController);
|
||||
export { PublicController };
|
||||
let PublicModule = class PublicModule {
|
||||
};
|
||||
PublicModule = __decorate([
|
||||
Module({
|
||||
imports: [MediaModule, SectionsModule],
|
||||
controllers: [PublicController],
|
||||
providers: [PublicService],
|
||||
})
|
||||
], PublicModule);
|
||||
export { PublicModule };
|
||||
//# sourceMappingURL=public.module.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
+191
@@ -0,0 +1,191 @@
|
||||
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);
|
||||
};
|
||||
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
||||
return function (target, key) { decorator(target, key, paramIndex); }
|
||||
};
|
||||
import { BadRequestException, Body, Controller, ForbiddenException, Get, Injectable, Module, NotFoundException, Param, Patch, Put, ServiceUnavailableException, } from "@nestjs/common";
|
||||
import { isSectionKey, SECTION_KEYS, sectionSchemas, toIssues, } from "@imen/shared";
|
||||
import { AdminAuth, CurrentUser } from "../common/auth.js";
|
||||
import { AuditService, RevalidateService } from "../common/common.module.js";
|
||||
import { PrismaService } from "../prisma/prisma.service.js";
|
||||
let SectionsService = class SectionsService {
|
||||
prisma;
|
||||
audit;
|
||||
revalidate;
|
||||
constructor(prisma, audit, revalidate) {
|
||||
this.prisma = prisma;
|
||||
this.audit = audit;
|
||||
this.revalidate = revalidate;
|
||||
}
|
||||
async getAll() {
|
||||
const rows = await this.prisma.siteSection.findMany();
|
||||
const byKey = new Map(rows.map((r) => [r.key, r.data]));
|
||||
const missing = SECTION_KEYS.filter((k) => !byKey.has(k));
|
||||
if (missing.length) {
|
||||
throw new ServiceUnavailableException(`محتوای سایت مقداردهی نشده است (${missing.join(", ")}) — pnpm db:seed را اجرا کنید`);
|
||||
}
|
||||
return Object.fromEntries(SECTION_KEYS.map((k) => [k, sectionSchemas[k].parse(byKey.get(k))]));
|
||||
}
|
||||
async get(key) {
|
||||
if (!isSectionKey(key))
|
||||
throw new NotFoundException("بخش پیدا نشد");
|
||||
const row = await this.prisma.siteSection.findUnique({ where: { key } });
|
||||
if (!row)
|
||||
throw new NotFoundException("بخش پیدا نشد");
|
||||
return { key, data: sectionSchemas[key].parse(row.data), updatedAt: row.updatedAt };
|
||||
}
|
||||
async update(key, body, user) {
|
||||
if (!isSectionKey(key))
|
||||
throw new NotFoundException("بخش پیدا نشد");
|
||||
if (key === "settings" && user.role !== "ADMIN") {
|
||||
throw new ForbiddenException("فقط مدیر کل میتواند تنظیمات سایت را تغییر دهد");
|
||||
}
|
||||
const parsed = sectionSchemas[key].safeParse(body);
|
||||
if (!parsed.success) {
|
||||
throw new BadRequestException({
|
||||
statusCode: 400,
|
||||
message: "اطلاعات واردشده معتبر نیست",
|
||||
issues: toIssues(parsed.error),
|
||||
});
|
||||
}
|
||||
const row = await this.prisma.siteSection.upsert({
|
||||
where: { key },
|
||||
create: { key, data: parsed.data, updatedById: user.id },
|
||||
update: { data: parsed.data, updatedById: user.id },
|
||||
});
|
||||
this.audit.log(user.id, "update", "section", key, key);
|
||||
this.revalidate.trigger();
|
||||
return { key, data: parsed.data, updatedAt: row.updatedAt };
|
||||
}
|
||||
async listAdmin() {
|
||||
const rows = await this.prisma.siteSection.findMany();
|
||||
const byKey = new Map(rows.map((r) => [r.key, r]));
|
||||
return SECTION_KEYS.map((key) => {
|
||||
const row = byKey.get(key);
|
||||
let data = null;
|
||||
if (row) {
|
||||
const parsed = sectionSchemas[key].safeParse(row.data);
|
||||
data = parsed.success ? parsed.data : row.data;
|
||||
}
|
||||
return {
|
||||
key,
|
||||
updatedAt: row?.updatedAt?.toISOString() ?? null,
|
||||
enabled: data && typeof data === "object" && "enabled" in data
|
||||
? data.enabled !== false
|
||||
: true,
|
||||
};
|
||||
});
|
||||
}
|
||||
async setVisibility(key, enabled, user) {
|
||||
if (!isSectionKey(key))
|
||||
throw new NotFoundException("بخش پیدا نشد");
|
||||
if (key === "settings") {
|
||||
throw new BadRequestException("تنظیمات سایت قابلیت تغییر وضعیت نمایش ندارد");
|
||||
}
|
||||
const row = await this.prisma.siteSection.findUnique({ where: { key } });
|
||||
if (!row)
|
||||
throw new NotFoundException("بخش پیدا نشد");
|
||||
const currentData = row.data && typeof row.data === "object" ? { ...row.data } : {};
|
||||
currentData.enabled = enabled;
|
||||
const parsed = sectionSchemas[key].safeParse(currentData);
|
||||
if (!parsed.success) {
|
||||
throw new BadRequestException({
|
||||
statusCode: 400,
|
||||
message: "اطلاعات بخش معتبر نیست",
|
||||
issues: toIssues(parsed.error),
|
||||
});
|
||||
}
|
||||
const updated = await this.prisma.siteSection.update({
|
||||
where: { key },
|
||||
data: { data: parsed.data, updatedById: user.id },
|
||||
});
|
||||
this.audit.log(user.id, "update", "section", key, `تغییر وضعیت نمایش به ${enabled ? "نمایش" : "عدم نمایش"}`);
|
||||
this.revalidate.trigger();
|
||||
return {
|
||||
key,
|
||||
enabled: parsed.data.enabled !== false,
|
||||
updatedAt: updated.updatedAt.toISOString(),
|
||||
};
|
||||
}
|
||||
};
|
||||
SectionsService = __decorate([
|
||||
Injectable(),
|
||||
__metadata("design:paramtypes", [PrismaService,
|
||||
AuditService,
|
||||
RevalidateService])
|
||||
], SectionsService);
|
||||
export { SectionsService };
|
||||
let SectionsController = class SectionsController {
|
||||
sections;
|
||||
constructor(sections) {
|
||||
this.sections = sections;
|
||||
}
|
||||
list() {
|
||||
return this.sections.listAdmin();
|
||||
}
|
||||
get(key) {
|
||||
return this.sections.get(key);
|
||||
}
|
||||
update(key, body, user) {
|
||||
return this.sections.update(key, body, user);
|
||||
}
|
||||
setVisibility(key, body, user) {
|
||||
const enabled = body && typeof body.enabled === "boolean" ? body.enabled : Boolean(body?.enabled);
|
||||
return this.sections.setVisibility(key, enabled, user);
|
||||
}
|
||||
};
|
||||
__decorate([
|
||||
Get(),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", []),
|
||||
__metadata("design:returntype", void 0)
|
||||
], SectionsController.prototype, "list", null);
|
||||
__decorate([
|
||||
Get(":key"),
|
||||
__param(0, Param("key")),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [String]),
|
||||
__metadata("design:returntype", void 0)
|
||||
], SectionsController.prototype, "get", null);
|
||||
__decorate([
|
||||
Put(":key"),
|
||||
__param(0, Param("key")),
|
||||
__param(1, Body()),
|
||||
__param(2, CurrentUser()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [String, Object, Object]),
|
||||
__metadata("design:returntype", void 0)
|
||||
], SectionsController.prototype, "update", null);
|
||||
__decorate([
|
||||
Patch(":key/visibility"),
|
||||
__param(0, Param("key")),
|
||||
__param(1, Body()),
|
||||
__param(2, CurrentUser()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [String, Object, Object]),
|
||||
__metadata("design:returntype", void 0)
|
||||
], SectionsController.prototype, "setVisibility", null);
|
||||
SectionsController = __decorate([
|
||||
AdminAuth(),
|
||||
Controller("admin/sections"),
|
||||
__metadata("design:paramtypes", [SectionsService])
|
||||
], SectionsController);
|
||||
export { SectionsController };
|
||||
let SectionsModule = class SectionsModule {
|
||||
};
|
||||
SectionsModule = __decorate([
|
||||
Module({
|
||||
controllers: [SectionsController],
|
||||
providers: [SectionsService],
|
||||
exports: [SectionsService],
|
||||
})
|
||||
], SectionsModule);
|
||||
export { SectionsModule };
|
||||
//# sourceMappingURL=sections.module.js.map
|
||||
File diff suppressed because one or more lines are too long
Vendored
+45
@@ -0,0 +1,45 @@
|
||||
import { normalizeIrMobile } from "@imen/shared";
|
||||
import { hashPassword } from "../auth/auth.service.js";
|
||||
import { createPrismaClient } from "../prisma/prisma.service.js";
|
||||
const prisma = createPrismaClient();
|
||||
async function main() {
|
||||
const phone = normalizeIrMobile("09128081392");
|
||||
const password = "09128081392";
|
||||
const name = "مدیر سایت";
|
||||
const role = "ADMIN";
|
||||
const passwordHash = await hashPassword(password);
|
||||
const existing = await prisma.adminUser.findUnique({ where: { phone } });
|
||||
if (existing) {
|
||||
const updated = await prisma.adminUser.update({
|
||||
where: { id: existing.id },
|
||||
data: {
|
||||
passwordHash,
|
||||
name,
|
||||
role,
|
||||
},
|
||||
});
|
||||
console.log(`User already existed, password updated for: ${updated.phone} (ID: ${updated.id})`);
|
||||
}
|
||||
else {
|
||||
const user = await prisma.adminUser.create({
|
||||
data: {
|
||||
phone,
|
||||
passwordHash,
|
||||
name,
|
||||
role,
|
||||
},
|
||||
});
|
||||
console.log(`User successfully created: ${user.phone} (ID: ${user.id}, Role: ${user.role})`);
|
||||
}
|
||||
}
|
||||
try {
|
||||
await main();
|
||||
}
|
||||
catch (e) {
|
||||
console.error("Error creating user:", e);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
//# sourceMappingURL=create-admin.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"create-admin.js","sourceRoot":"","sources":["../../src/seed/create-admin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AAEjE,MAAM,MAAM,GAAG,kBAAkB,EAAE,CAAC;AAEpC,KAAK,UAAU,IAAI;IACjB,MAAM,KAAK,GAAG,iBAAiB,CAAC,aAAa,CAAC,CAAC;IAC/C,MAAM,QAAQ,GAAG,aAAa,CAAC;IAC/B,MAAM,IAAI,GAAG,WAAW,CAAC;IACzB,MAAM,IAAI,GAAG,OAAO,CAAC;IAErB,MAAM,YAAY,GAAG,MAAM,YAAY,CAAC,QAAQ,CAAC,CAAC;IAElD,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;IACzE,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC;YAC5C,KAAK,EAAE,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE;YAC1B,IAAI,EAAE;gBACJ,YAAY;gBACZ,IAAI;gBACJ,IAAI;aACL;SACF,CAAC,CAAC;QACH,OAAO,CAAC,GAAG,CAAC,+CAA+C,OAAO,CAAC,KAAK,SAAS,OAAO,CAAC,EAAE,GAAG,CAAC,CAAC;IAClG,CAAC;SAAM,CAAC;QACN,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC;YACzC,IAAI,EAAE;gBACJ,KAAK;gBACL,YAAY;gBACZ,IAAI;gBACJ,IAAI;aACL;SACF,CAAC,CAAC;QACH,OAAO,CAAC,GAAG,CAAC,8BAA8B,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,EAAE,WAAW,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;IAC/F,CAAC;AACH,CAAC;AAED,IAAI,CAAC;IACH,MAAM,IAAI,EAAE,CAAC;AACf,CAAC;AAAC,OAAO,CAAC,EAAE,CAAC;IACX,OAAO,CAAC,KAAK,CAAC,sBAAsB,EAAE,CAAC,CAAC,CAAC;IACzC,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC;QAAS,CAAC;IACT,MAAM,MAAM,CAAC,WAAW,EAAE,CAAC;AAC7B,CAAC"}
|
||||
+904
@@ -0,0 +1,904 @@
|
||||
export const CATEGORIES = [
|
||||
{
|
||||
slug: "workwear",
|
||||
code: "IP-C01",
|
||||
name: "پوشاک کار و البسه صنعتی",
|
||||
en: "INDUSTRIAL WORKWEAR",
|
||||
shortDesc: "لباسکار صنعتی با پارچه فلامنت کجراه اعلا، دوخت سه سوزنه و آستر عرقگیر؛ مناسب محیطهای کارگاهی و صنعتی.",
|
||||
fullDesc: "مجموعه کامل پوشاک کار صنعتی شامل کاپشن و شلوار مهندسی، لباس جوشکاری، پیشبند دوبنده و تیشرت کار. تمامی مدلها با پارچه فلامنت کجراه اعلا و کتان مقاوم، دوخت صنعتی سه سوزنه پیوسته برای جلوگیری از شکافتگی درزها و آستر عرقگیر اختصاصی تولید میشوند. موجود در سایزهای M تا 4XL.",
|
||||
icon: "Shirt",
|
||||
products: [
|
||||
{
|
||||
code: "IP-W01",
|
||||
name: "لباسکار مدل آلفا",
|
||||
en: "ALPHA WORKWEAR",
|
||||
desc: "کاپشن و شلوار مهندسی با جیبهای پاکتی و زیپدار، نوار مغزی تزئینی و طراحی ارگونومیک برای تیمهای فنی و مهندسی.",
|
||||
specs: ["فلامنت کجراه اعلا", "دوخت سه سوزنه", "آستر عرقگیر", "سایز M تا 4XL"],
|
||||
variants: [
|
||||
"طوسی/آبی",
|
||||
"مشکی/قرمز",
|
||||
"مشکی/زرد",
|
||||
"کرم/سرمهای",
|
||||
],
|
||||
},
|
||||
{
|
||||
code: "IP-W02",
|
||||
name: "لباسکار مدل اورجینال",
|
||||
en: "ORIGINAL WORKWEAR",
|
||||
desc: "کاپشن و شلوار استاندارد با دوام بالا، مناسب کادر فنی کارخانجات و خطوط تولید با ترکیبهای صنعتی دورنگ.",
|
||||
specs: ["کتان مقاوم", "دوخت سه سوزنه", "آستر عرقگیر", "سایز M تا 4XL"],
|
||||
variants: ["ترکیبهای صنعتی دورنگ"],
|
||||
},
|
||||
{
|
||||
code: "IP-W03",
|
||||
name: "لباسکار مدل بوفالو",
|
||||
en: "BUFFALO WORKWEAR",
|
||||
desc: "طراحی اسپرت با برشهای تقویتی روی شانه و بازو، زیپ مخفی محافظدار؛ مناسب محیطهای صنعتی فعال.",
|
||||
specs: ["فلامنت کجراه اعلا", "زیپ مخفی محافظدار", "برش تقویتی شانه", "سایز M تا 4XL"],
|
||||
variants: [
|
||||
"سرمهای/زرد",
|
||||
"طوسی/قرمز",
|
||||
"طوسی/خردلی",
|
||||
"آبی/مشکی",
|
||||
],
|
||||
},
|
||||
{
|
||||
code: "IP-W04",
|
||||
name: "لباسکار ست ورک",
|
||||
en: "SET WORK",
|
||||
desc: "ست اداری/کارگاهی با انعطاف بالا جهت فعالیتهای فنی و تعمیراتی؛ قابل استفاده در محیطهای ستادی و عملیاتی.",
|
||||
specs: ["کتان مقاوم", "دوخت سه سوزنه", "طراحی منعطف", "سایز M تا 4XL"],
|
||||
variants: ["ترکیبهای چندگانه کارگاهی"],
|
||||
},
|
||||
{
|
||||
code: "IP-W05",
|
||||
name: "لباسکار جوشکاری",
|
||||
en: "WELDING WORKWEAR",
|
||||
desc: "متریال سنگین کتان یا چرم جهت مقاومت در برابر پاشش جرقههای مذاب؛ طراحیشده برای جوشکاران صنعتی.",
|
||||
specs: ["مقاوم به جرقه مذاب", "کتان سنگین / چرم", "دوخت صنعتی", "سایز M تا 4XL"],
|
||||
},
|
||||
{
|
||||
code: "IP-W06",
|
||||
name: "پیشبند دوبنده",
|
||||
en: "BIB OVERALLS",
|
||||
desc: "آزادی عمل کامل کتف و شانه با جیبهای چندمنظوره ابزار بر روی پیشسینه؛ ایدهآل برای کارهای تعمیراتی و مونتاژ.",
|
||||
specs: ["آزادی حرکت شانه", "جیب ابزار پیشسینه", "دوخت سه سوزنه", "سایز M تا 4XL"],
|
||||
variants: ["سرمهای", "خاکستری"],
|
||||
},
|
||||
{
|
||||
code: "IP-W07",
|
||||
name: "تیشرت صنعتی",
|
||||
en: "INDUSTRIAL T-SHIRT",
|
||||
desc: "تیشرت یقهدار جودون / پنبهای مناسب فصول گرم و زیرپوش کاپشن کارگاهی در سولههای سرپوشیده.",
|
||||
specs: ["پنبه / جودون", "یقهدار", "مناسب فصول گرم", "سایز M تا 4XL"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: "safety-footwear",
|
||||
code: "IP-C02",
|
||||
name: "پایپوش و کفش ایمنی",
|
||||
en: "SAFETY FOOTWEAR",
|
||||
shortDesc: "کفشهای اداری و کفشهای ایمنی کارگری؛ راحت و شکیل برای محیط اداری، مقاوم و ضد لغزش برای کارگاه.",
|
||||
fullDesc: "پایپوش ایمنی شامل کفشهای اداری برای محیطهای اداری و سبک صنعتی و کفشهای ایمنی کارگری برای کارگاهها و پروژههای ساختمانی؛ با زیره مقاوم و مواد مقاوم در برابر سایش و مواد شیمیایی سبک.",
|
||||
icon: "Footprints",
|
||||
groups: [
|
||||
{ slug: "office", name: "کفش اداری", desc: "مناسب محیطهای اداری و سبک صنعتی، راحت، سبک و شکیل با زیره مقاوم." },
|
||||
{ slug: "worker", name: "کفش ایمنی کارگری", desc: "مناسب کارگاهها و پروژههای ساختمانی، مقاوم در برابر سایش و مواد شیمیایی سبک." },
|
||||
],
|
||||
products: [
|
||||
{
|
||||
code: "IP-F01",
|
||||
group: "office",
|
||||
name: "کفش اداری مدل ۱۱۷۴",
|
||||
en: "OFFICE SHOE 1174",
|
||||
desc: "مناسب محیطهای اداری و سبک صنعتی، راحت، سبک و شکیل با زیره مقاوم.",
|
||||
specs: ["چرم نبوک", "بنددار", "زیره مقاوم"],
|
||||
image: "/images/products/shoes/office/1.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-F02",
|
||||
group: "office",
|
||||
name: "کفش اداری بیبند مدل ۶",
|
||||
en: "SLIP-ON OFFICE SHOE",
|
||||
desc: "مناسب محیطهای اداری و سبک صنعتی، راحت، سبک و شکیل با زیره مقاوم.",
|
||||
specs: ["چرم طبیعی", "بدون بند", "زیره مقاوم"],
|
||||
image: "/images/products/shoes/office/2.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-F03",
|
||||
group: "office",
|
||||
name: "کفش اداری بنددار مشکی",
|
||||
en: "LACE-UP OFFICE SHOE",
|
||||
desc: "مناسب محیطهای اداری و سبک صنعتی، راحت، سبک و شکیل با زیره مقاوم.",
|
||||
specs: ["چرم طبیعی", "بنددار", "زیره مقاوم"],
|
||||
image: "/images/products/shoes/office/3.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-F04",
|
||||
group: "office",
|
||||
name: "کفش اداری بیبند AAEA075",
|
||||
en: "SLIP-ON OFFICE SHOE AAEA075",
|
||||
desc: "مناسب محیطهای اداری و سبک صنعتی، راحت، سبک و شکیل با زیره مقاوم.",
|
||||
specs: ["چرم طبیعی", "بدون بند", "زیره مقاوم"],
|
||||
image: "/images/products/shoes/office/4.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-F05",
|
||||
group: "office",
|
||||
name: "کفش اداری شبرو",
|
||||
en: "SHEBRO OFFICE SHOE",
|
||||
desc: "مناسب محیطهای اداری و سبک صنعتی، راحت، سبک و شکیل با زیره مقاوم.",
|
||||
specs: ["چرم طبیعی", "بنددار", "زیره مقاوم"],
|
||||
image: "/images/products/shoes/office/5.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-F06",
|
||||
group: "office",
|
||||
name: "کفش اداری بیبند کلاسیک",
|
||||
en: "CLASSIC SLIP-ON OFFICE SHOE",
|
||||
desc: "مناسب محیطهای اداری و سبک صنعتی، راحت، سبک و شکیل با زیره مقاوم.",
|
||||
specs: ["چرم طبیعی", "بدون بند", "زیره مقاوم"],
|
||||
image: "/images/products/shoes/office/6.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-F07",
|
||||
group: "office",
|
||||
name: "کفش اداری کالج",
|
||||
en: "LOAFER OFFICE SHOE",
|
||||
desc: "مناسب محیطهای اداری و سبک صنعتی، راحت، سبک و شکیل با زیره مقاوم.",
|
||||
specs: ["چرم طبیعی", "کالج", "زیره مقاوم"],
|
||||
image: "/images/products/shoes/office/7.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-F08",
|
||||
group: "office",
|
||||
name: "کفش اداری دیپلمات",
|
||||
en: "DIPLOMAT OFFICE SHOE",
|
||||
desc: "مناسب محیطهای اداری و سبک صنعتی، راحت، سبک و شکیل با زیره مقاوم.",
|
||||
specs: ["چرم طبیعی", "بنددار", "طراحی رسمی"],
|
||||
image: "/images/products/shoes/office/8.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-F09",
|
||||
group: "worker",
|
||||
name: "کفش ایمنی کارگری مدل ۱",
|
||||
en: "WORKER SAFETY SHOE",
|
||||
desc: "مناسب کارگاهها و پروژههای ساختمانی، مقاوم در برابر سایش و مواد شیمیایی سبک.",
|
||||
specs: ["مقاوم به سایش", "زیره ضد لغزش"],
|
||||
image: "/images/products/shoes/worker/1.png",
|
||||
},
|
||||
{
|
||||
code: "IP-F10",
|
||||
group: "worker",
|
||||
name: "کفش ایمنی کارگری مدل ۲",
|
||||
en: "WORKER SAFETY SHOE",
|
||||
desc: "مناسب کارگاهها و پروژههای ساختمانی، مقاوم در برابر سایش و مواد شیمیایی سبک.",
|
||||
specs: ["مقاوم به سایش", "زیره ضد لغزش"],
|
||||
image: "/images/products/shoes/worker/2.png",
|
||||
},
|
||||
{
|
||||
code: "IP-F11",
|
||||
group: "worker",
|
||||
name: "کفش ایمنی کارگری مدل ۹۰",
|
||||
en: "WORKER SAFETY SHOE 90",
|
||||
desc: "مناسب کارگاهها و پروژههای ساختمانی، مقاوم در برابر سایش و مواد شیمیایی سبک.",
|
||||
specs: ["مقاوم به سایش", "زیره ضد لغزش"],
|
||||
image: "/images/products/shoes/worker/3.png",
|
||||
},
|
||||
{
|
||||
code: "IP-F12",
|
||||
group: "worker",
|
||||
name: "کفش ایمنی پاتن پدیده تبریز مدل رایکا S1017",
|
||||
en: "PATEN PADIDEH RAYKA S1017",
|
||||
desc: "مناسب کارگاهها و پروژههای ساختمانی، مقاوم در برابر سایش و مواد شیمیایی سبک.",
|
||||
specs: ["پاتن پدیده تبریز", "مقاوم به سایش", "زیره ضد لغزش"],
|
||||
image: "/images/products/shoes/worker/4.png",
|
||||
},
|
||||
{
|
||||
code: "IP-F13",
|
||||
group: "worker",
|
||||
name: "کفش ایمنی پیما مدل دنا",
|
||||
en: "PAYMA DENA SAFETY SHOE",
|
||||
desc: "مناسب کارگاهها و پروژههای ساختمانی، مقاوم در برابر سایش و مواد شیمیایی سبک.",
|
||||
specs: ["پیما", "مقاوم به سایش", "زیره ضد لغزش"],
|
||||
image: "/images/products/shoes/worker/5.png",
|
||||
},
|
||||
{
|
||||
code: "IP-F14",
|
||||
group: "worker",
|
||||
name: "کفش کار کارگری ساقدار",
|
||||
en: "WORKER SAFETY BOOT",
|
||||
desc: "مناسب کارگاهها و پروژههای ساختمانی، مقاوم در برابر سایش و مواد شیمیایی سبک.",
|
||||
specs: ["ساقدار", "مقاوم به سایش", "زیره ضد لغزش"],
|
||||
image: "/images/products/shoes/worker/6.png",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: "protective-gloves",
|
||||
code: "IP-C03",
|
||||
name: "دستکشهای حفاظتی",
|
||||
en: "PROTECTIVE GLOVES",
|
||||
shortDesc: "دستکشهای ضد برش، چرم، ضد اسید و ضد حلال برای کارهای سنگین صنعتی، جوشکاری و تماس با مواد شیمیایی.",
|
||||
fullDesc: "دستکشهای حفاظت فردی شامل مدلهای ضد برش، چرم، ضد اسید و ضد حلال؛ از برندهای استادکار، امریکن سیفتی، ماکسی درای و NM Safety. هر مدل متناسب با نوع خطر محیط کار انتخاب شده است.",
|
||||
icon: "Hand",
|
||||
groups: [
|
||||
{ slug: "cut", name: "دستکش ضد برش", desc: "ایمن در برابر تیغهها و فلزات تیز." },
|
||||
{ slug: "leather", name: "دستکش چرم", desc: "محافظ دست در برابر حرارت، جرقه و سایش در کارهای سنگین و جوشکاری." },
|
||||
{ slug: "acid", name: "دستکش ضد اسید", desc: "محافظت کامل در برابر اسیدها و بازها." },
|
||||
{ slug: "solvent", name: "دستکش ضد حلال", desc: "مقاومت بالا در برابر تماس با مواد شیمیایی." },
|
||||
],
|
||||
products: [
|
||||
{
|
||||
code: "IP-G01",
|
||||
group: "cut",
|
||||
name: "دستکش ضد برش لاتکسی",
|
||||
en: "LATEX CUT-RESISTANT GLOVE",
|
||||
desc: "ایمن در برابر تیغهها و فلزات تیز.",
|
||||
specs: ["ضد برش", "پوشش لاتکس"],
|
||||
image: "/images/products/gloves/cut/1.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-G02",
|
||||
group: "cut",
|
||||
name: "دستکش ضد برش مدل ۲",
|
||||
en: "CUT-RESISTANT GLOVE",
|
||||
desc: "ایمن در برابر تیغهها و فلزات تیز.",
|
||||
specs: ["ضد برش"],
|
||||
image: "/images/products/gloves/cut/2.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-G03",
|
||||
group: "cut",
|
||||
name: "دستکش ضد برش مدل ۳",
|
||||
en: "CUT-RESISTANT GLOVE",
|
||||
desc: "ایمن در برابر تیغهها و فلزات تیز.",
|
||||
specs: ["ضد برش"],
|
||||
image: "/images/products/gloves/cut/3.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-G04",
|
||||
group: "cut",
|
||||
name: "دستکش ضد برش استادکار ۱۲ جفتی",
|
||||
en: "OSTADKAR CUT-RESISTANT GLOVE",
|
||||
desc: "ایمن در برابر تیغهها و فلزات تیز.",
|
||||
specs: ["ضد برش", "استادکار", "بسته ۱۲ جفتی"],
|
||||
image: "/images/products/gloves/cut/4.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-G05",
|
||||
group: "cut",
|
||||
name: "دستکش ضد برش تانگ وانگ",
|
||||
en: "TANG WANG CUT-RESISTANT GLOVE",
|
||||
desc: "ایمن در برابر تیغهها و فلزات تیز.",
|
||||
specs: ["ضد برش", "تانگ وانگ"],
|
||||
image: "/images/products/gloves/cut/5.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-G06",
|
||||
group: "leather",
|
||||
name: "دستکش چرم مدل ۰۷",
|
||||
en: "LEATHER GLOVE",
|
||||
desc: "محافظ دست در برابر حرارت، جرقه و سایش در کارهای سنگین و جوشکاری.",
|
||||
specs: ["چرم"],
|
||||
image: "/images/products/gloves/leather/1.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-G07",
|
||||
group: "leather",
|
||||
name: "دستکش چرم BMW",
|
||||
en: "BMW LEATHER GLOVE",
|
||||
desc: "محافظ دست در برابر حرارت، جرقه و سایش در کارهای سنگین و جوشکاری.",
|
||||
specs: ["چرم", "ساق بلند"],
|
||||
image: "/images/products/gloves/leather/2.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-G08",
|
||||
group: "leather",
|
||||
name: "دستکش کف چرم دوبل ساق پارچهای",
|
||||
en: "DOUBLE-PALM LEATHER GLOVE, FABRIC CUFF",
|
||||
desc: "محافظ دست در برابر حرارت، جرقه و سایش در کارهای سنگین و جوشکاری.",
|
||||
specs: ["کف چرم دوبل", "ساق پارچهای"],
|
||||
image: "/images/products/gloves/leather/3.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-G09",
|
||||
group: "leather",
|
||||
name: "دستکش کف چرم دوبل ساق چرم",
|
||||
en: "DOUBLE-PALM LEATHER GLOVE, LEATHER CUFF",
|
||||
desc: "محافظ دست در برابر حرارت، جرقه و سایش در کارهای سنگین و جوشکاری.",
|
||||
specs: ["کف چرم دوبل", "ساق چرم"],
|
||||
image: "/images/products/gloves/leather/4.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-G10",
|
||||
group: "leather",
|
||||
name: "دستکش هوبارت امریکن سیفتی",
|
||||
en: "AMERICAN SAFETY HOBART GLOVE",
|
||||
desc: "محافظ دست در برابر حرارت، جرقه و سایش در کارهای سنگین و جوشکاری.",
|
||||
specs: ["چرم", "جوشکاری", "EN 388 / EN 407"],
|
||||
image: "/images/products/gloves/leather/5.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-G11",
|
||||
group: "leather",
|
||||
name: "دستکش هوبارت طرح فرانسه",
|
||||
en: "FRENCH-STYLE HOBART GLOVE",
|
||||
desc: "محافظ دست در برابر حرارت، جرقه و سایش در کارهای سنگین و جوشکاری.",
|
||||
specs: ["چرم", "جوشکاری"],
|
||||
image: "/images/products/gloves/leather/6.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-G12",
|
||||
group: "acid",
|
||||
name: "دستکش ضد اسید مدل ۱",
|
||||
en: "ACID-RESISTANT GLOVE",
|
||||
desc: "محافظت کامل در برابر اسیدها و بازها.",
|
||||
specs: ["ضد اسید"],
|
||||
image: "/images/products/gloves/acid/1.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-G13",
|
||||
group: "acid",
|
||||
name: "دستکش ضد اسید مدل ۲",
|
||||
en: "ACID-RESISTANT GLOVE",
|
||||
desc: "محافظت کامل در برابر اسیدها و بازها.",
|
||||
specs: ["ضد اسید"],
|
||||
image: "/images/products/gloves/acid/2.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-G14",
|
||||
group: "acid",
|
||||
name: "دستکش ضد اسید مدل ۳",
|
||||
en: "ACID-RESISTANT GLOVE",
|
||||
desc: "محافظت کامل در برابر اسیدها و بازها.",
|
||||
specs: ["ضد اسید"],
|
||||
image: "/images/products/gloves/acid/3.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-G15",
|
||||
group: "acid",
|
||||
name: "دستکش ضد اسید مقاوم شیمیایی",
|
||||
en: "CHEMICAL-RESISTANT GLOVE",
|
||||
desc: "محافظت کامل در برابر اسیدها و بازها.",
|
||||
specs: ["ضد اسید", "مقاوم شیمیایی", "ساق بلند"],
|
||||
image: "/images/products/gloves/acid/4.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-G16",
|
||||
group: "solvent",
|
||||
name: "دستکش ضد حلال مدل ۱۳",
|
||||
en: "SOLVENT-RESISTANT GLOVE",
|
||||
desc: "مقاومت بالا در برابر تماس با مواد شیمیایی.",
|
||||
specs: ["ضد حلال"],
|
||||
image: "/images/products/gloves/solvent/1.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-G17",
|
||||
group: "solvent",
|
||||
name: "دستکش ماکسی درای پلاس 56-530",
|
||||
en: "MAXIDRY PLUS 56-530",
|
||||
desc: "مقاومت بالا در برابر تماس با مواد شیمیایی.",
|
||||
specs: ["ضد حلال", "MaxiDry Plus"],
|
||||
image: "/images/products/gloves/solvent/2.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-G18",
|
||||
group: "solvent",
|
||||
name: "دستکش ضد حلال نیتریلی",
|
||||
en: "NITRILE SOLVENT-RESISTANT GLOVE",
|
||||
desc: "مقاومت بالا در برابر تماس با مواد شیمیایی.",
|
||||
specs: ["ضد حلال", "نیتریل"],
|
||||
image: "/images/products/gloves/solvent/3.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-G19",
|
||||
group: "solvent",
|
||||
name: "دستکش نیتریل آزمایشگاهی ضد حلال NM Safety",
|
||||
en: "NM SAFETY NITRILE LAB GLOVE",
|
||||
desc: "مقاومت بالا در برابر تماس با مواد شیمیایی.",
|
||||
specs: ["ضد حلال", "نیتریل", "آزمایشگاهی"],
|
||||
image: "/images/products/gloves/solvent/4.webp",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: "safety-helmets",
|
||||
code: "IP-C04",
|
||||
name: "کلاه ایمنی",
|
||||
en: "SAFETY HELMETS",
|
||||
shortDesc: "کلاههای ایمنی کارگری، مهندسی، کار در ارتفاع و کپ ایمنی؛ مقاوم در برابر ضربه و سقوط اجسام.",
|
||||
fullDesc: "انواع کلاه ایمنی صنعتی شامل مدلهای کارگری، مهندسی، کار در ارتفاع با بند چانه قابل تنظیم و کپ ایمنی سبک. با پوسته ضربهگیر مقاوم، رگلاژ داخلی قابل تنظیم و تنوع رنگی استاندارد HSE.",
|
||||
icon: "HardHat",
|
||||
groups: [
|
||||
{ slug: "worker", name: "کلاه کارگری", desc: "محافظت از سر در محیطهای صنعتی و کارگاهها، مقاوم در برابر ضربه و سقوط اجسام." },
|
||||
{ slug: "engineering", name: "کلاه مهندسی", desc: "مناسب محیطهای اداری و صنعتی سبک، راحت و شکیل با تهویه مناسب." },
|
||||
{ slug: "height", name: "کار در ارتفاع", desc: "دارای بند چانه قابل تنظیم برای ثبات بیشتر هنگام کار در ارتفاع." },
|
||||
{ slug: "cap", name: "کپ ایمنی", desc: "سبک و راحت، مناسب استفاده روزمره در محیطهای صنعتی و اداری." },
|
||||
],
|
||||
products: [
|
||||
{
|
||||
code: "IP-H01",
|
||||
group: "worker",
|
||||
name: "کلاه ایمنی کارگری آبی",
|
||||
en: "WORKER SAFETY HELMET",
|
||||
desc: "محافظت از سر در محیطهای صنعتی و کارگاهها، مقاوم در برابر ضربه و سقوط اجسام.",
|
||||
specs: ["پوسته ضربهگیر", "رگلاژ قابل تنظیم"],
|
||||
image: "/images/products/helmets/2.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-H02",
|
||||
group: "engineering",
|
||||
name: "کلاه ایمنی مهندسی پادکس",
|
||||
en: "PADEX ENGINEERING HELMET",
|
||||
desc: "مناسب محیطهای اداری و صنعتی سبک، راحت و شکیل با تهویه مناسب.",
|
||||
specs: ["پادکس", "تهویه مناسب", "رگلاژ قابل تنظیم"],
|
||||
image: "/images/products/helmets/11.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-H03",
|
||||
group: "engineering",
|
||||
name: "کلاه ایمنی مهندسی پادکس PADX",
|
||||
en: "PADEX PADX ENGINEERING HELMET",
|
||||
desc: "مناسب محیطهای اداری و صنعتی سبک، راحت و شکیل با تهویه مناسب.",
|
||||
specs: ["پادکس", "تهویه مناسب", "رگلاژ قابل تنظیم"],
|
||||
image: "/images/products/helmets/12.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-H04",
|
||||
group: "engineering",
|
||||
name: "کلاه ایمنی مهندس پادکس (نمای داخلی)",
|
||||
en: "PADEX HELMET HARNESS",
|
||||
desc: "مناسب محیطهای اداری و صنعتی سبک، راحت و شکیل با تهویه مناسب.",
|
||||
specs: ["پادکس", "یراق داخلی", "رگلاژ قابل تنظیم"],
|
||||
image: "/images/products/helmets/10.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-H05",
|
||||
group: "height",
|
||||
name: "کلاه ایمنی کار در ارتفاع با عینک",
|
||||
en: "HEIGHT HELMET WITH VISOR",
|
||||
desc: "دارای بند چانه قابل تنظیم برای ثبات بیشتر هنگام کار در ارتفاع.",
|
||||
specs: ["بند چانه", "عینک داخلی", "رگلاژ قابل تنظیم"],
|
||||
image: "/images/products/helmets/1.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-H06",
|
||||
group: "height",
|
||||
name: "کلاه ایمنی کار در ارتفاع uvex",
|
||||
en: "UVEX HEIGHT HELMET",
|
||||
desc: "دارای بند چانه قابل تنظیم برای ثبات بیشتر هنگام کار در ارتفاع.",
|
||||
specs: ["uvex", "بند چانه", "تهویه مناسب"],
|
||||
image: "/images/products/helmets/3.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-H07",
|
||||
group: "height",
|
||||
name: "کلاه ایمنی کار در ارتفاع سبز",
|
||||
en: "HEIGHT SAFETY HELMET",
|
||||
desc: "دارای بند چانه قابل تنظیم برای ثبات بیشتر هنگام کار در ارتفاع.",
|
||||
specs: ["بند چانه", "رگلاژ قابل تنظیم"],
|
||||
image: "/images/products/helmets/4.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-H08",
|
||||
group: "height",
|
||||
name: "کلاه ایمنی صنعتی MK8",
|
||||
en: "MK8 INDUSTRIAL HELMET",
|
||||
desc: "دارای بند چانه قابل تنظیم برای ثبات بیشتر هنگام کار در ارتفاع.",
|
||||
specs: ["MK8", "بند چانه", "تهویه مناسب"],
|
||||
image: "/images/products/helmets/9.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-H09",
|
||||
group: "cap",
|
||||
name: "کپ ایمنی ضربهگیر",
|
||||
en: "SAFETY BUMP CAP",
|
||||
desc: "سبک و راحت، مناسب استفاده روزمره در محیطهای صنعتی و اداری.",
|
||||
specs: ["نقابدار", "ضربهگیر داخلی", "سبکوزن"],
|
||||
image: "/images/products/helmets/5.webp",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: "eye-protection",
|
||||
code: "IP-C05",
|
||||
name: "عینک ایمنی و صنعتی",
|
||||
en: "EYE PROTECTION",
|
||||
shortDesc: "عینکهای شفاف و دودی و گاگلهای محافظتی با ویژگیهای ضد ضربه، ضد خش، ضد بخار و UV.",
|
||||
fullDesc: "عینکهای ایمنی صنعتی شامل عینکهای شفاف، دودی و UV و گاگلهای کشدار از برندهای uvex، توتاص، کاناسیف و تک پلاست. ضد ضربه، ضد خش، ضد بخار و قابل تنظیم برای انواع سایز صورت.",
|
||||
icon: "Glasses",
|
||||
groups: [
|
||||
{ slug: "clear", name: "عینک شفاف", desc: "محافظت از چشمها در برابر برخورد اجسام و ابزار؛ ضد خش با دید واضح." },
|
||||
{ slug: "tinted", name: "عینک دودی و UV", desc: "محافظت در برابر اشعه مضر خورشید و نور شدید." },
|
||||
{ slug: "goggle", name: "گاگل", desc: "ضد بخار و ضد ضربه؛ جلوگیری از ایجاد مه در محیطهای گرم یا مرطوب." },
|
||||
],
|
||||
products: [
|
||||
{
|
||||
code: "IP-E01",
|
||||
group: "clear",
|
||||
name: "عینک ایمنی شفاف دسته قابل تنظیم",
|
||||
en: "ADJUSTABLE CLEAR SAFETY GLASSES",
|
||||
desc: "محافظت از چشمها در برابر برخورد اجسام و ابزار؛ قابل تنظیم برای انواع سایز صورت.",
|
||||
specs: ["ضد ضربه", "قابل تنظیم"],
|
||||
image: "/images/products/eyewear/1.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-E02",
|
||||
group: "clear",
|
||||
name: "عینک ایمنی روطبی",
|
||||
en: "OVER-SPEC SAFETY GLASSES",
|
||||
desc: "محافظت از چشمها در برابر برخورد اجسام و ابزار؛ قابل استفاده روی عینک طبی.",
|
||||
specs: ["ضد ضربه", "روطبی"],
|
||||
image: "/images/products/eyewear/4.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-E03",
|
||||
group: "clear",
|
||||
name: "عینک ایمنی uvex i-5",
|
||||
en: "UVEX I-5 SAFETY GLASSES",
|
||||
desc: "محافظت از چشمها در برابر برخورد اجسام و ابزار؛ ضد خش با دید واضح.",
|
||||
specs: ["uvex", "ضد ضربه", "ضد خش"],
|
||||
image: "/images/products/eyewear/5.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-E04",
|
||||
group: "clear",
|
||||
name: "عینک شیشه سفید تک پلاست",
|
||||
en: "TAK PLAST CLEAR GLASSES",
|
||||
desc: "محافظت از چشمها در برابر برخورد اجسام و ابزار؛ دارای محافظ کناری.",
|
||||
specs: ["تک پلاست", "محافظ کناری"],
|
||||
image: "/images/products/eyewear/11.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-E05",
|
||||
group: "tinted",
|
||||
name: "عینک ایمنی uvex دودی",
|
||||
en: "UVEX TINTED SAFETY GLASSES",
|
||||
desc: "محافظت در برابر اشعه مضر خورشید و نور شدید.",
|
||||
specs: ["uvex", "شیشه UV", "ضد ضربه"],
|
||||
image: "/images/products/eyewear/6.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-E06",
|
||||
group: "tinted",
|
||||
name: "عینک ایمنی توتاص AT113",
|
||||
en: "TOTAS AT113",
|
||||
desc: "محافظت در برابر اشعه مضر خورشید و نور شدید.",
|
||||
specs: ["توتاص", "شیشه UV", "ضد ضربه"],
|
||||
image: "/images/products/eyewear/7.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-E07",
|
||||
group: "tinted",
|
||||
name: "عینک ایمنی کاناسیف 20105",
|
||||
en: "CANASAFE 20105",
|
||||
desc: "محافظت در برابر اشعه مضر و نور شدید جوشکاری.",
|
||||
specs: ["کاناسیف", "شیشه UV", "قابل تنظیم"],
|
||||
image: "/images/products/eyewear/9.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-E08",
|
||||
group: "tinted",
|
||||
name: "عینک توتاص AT116",
|
||||
en: "TOTAS AT116",
|
||||
desc: "محافظت در برابر اشعه مضر خورشید و نور شدید.",
|
||||
specs: ["توتاص", "شیشه UV", "روطبی"],
|
||||
image: "/images/products/eyewear/10.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-E09",
|
||||
group: "goggle",
|
||||
name: "گاگل uvex ultravision",
|
||||
en: "UVEX ULTRAVISION GOGGLE",
|
||||
desc: "ضد بخار و ضد ضربه؛ جلوگیری از ایجاد مه در محیطهای گرم یا مرطوب.",
|
||||
specs: ["uvex", "ضد بخار", "کشدار"],
|
||||
image: "/images/products/eyewear/2.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-E10",
|
||||
group: "goggle",
|
||||
name: "گاگل ایمنی کشدار",
|
||||
en: "SAFETY GOGGLE",
|
||||
desc: "ضد بخار و ضد ضربه؛ جلوگیری از ایجاد مه در محیطهای گرم یا مرطوب.",
|
||||
specs: ["ضد بخار", "کشدار"],
|
||||
image: "/images/products/eyewear/3.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-E11",
|
||||
group: "goggle",
|
||||
name: "گاگل ایمنی توتاص ATBM",
|
||||
en: "TOTAS ATBM GOGGLE",
|
||||
desc: "ضد بخار و ضد ضربه؛ جلوگیری از ایجاد مه در محیطهای گرم یا مرطوب.",
|
||||
specs: ["توتاص", "ضد بخار", "کشدار"],
|
||||
image: "/images/products/eyewear/8.webp",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: "hearing-protection",
|
||||
code: "IP-C06",
|
||||
name: "حفاظت شنوایی",
|
||||
en: "HEARING PROTECTION",
|
||||
shortDesc: "ایرماف روگوشی با کاهش ۲۸ دسیبل و ایرپلاگهای اسفنجی و سیلیکونی برای محیطهای پرسروصدا.",
|
||||
fullDesc: "تجهیزات حفاظت شنوایی شامل ایرماف (محافظ روگوشی) با کاهش تراز صدا تا ۲۸ دسیبل و ایرپلاگ (داخلگوشی) در مدلهای اسفنجی فرمگیر و پلهای سیلیکونی. مناسب محیطهای با آلودگی صوتی بالای ۸۵ دسیبل، کمپرسورخانهها و خطوط تولید سنگین.",
|
||||
icon: "Ear",
|
||||
groups: [
|
||||
{ slug: "earmuff", name: "ایرماف", desc: "کاهش صدا تا ۲۸ دسیبل، مناسب محیطهای صنعتی و کارگاهی، قابل تنظیم و راحت برای استفاده طولانی." },
|
||||
{ slug: "earplug", name: "ایرپلاگ", desc: "محافظت در برابر صداهای شدید و نویز محیط، سبک و راحت، مدلهای یکبار مصرف و چندبار مصرف." },
|
||||
],
|
||||
products: [
|
||||
{
|
||||
code: "IP-HP01",
|
||||
group: "earmuff",
|
||||
name: "ایرماف هلبرگ",
|
||||
en: "HELLBERG EARMUFF",
|
||||
desc: "کاهش صدا تا ۲۸ دسیبل، مناسب محیطهای صنعتی و کارگاهی، قابل تنظیم و راحت برای استفاده طولانی.",
|
||||
specs: ["Hellberg", "هدبند قابل تنظیم", "بالشتک نرم"],
|
||||
image: "/images/products/hearing/1.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-HP02",
|
||||
group: "earmuff",
|
||||
name: "ایرماف uvex K2",
|
||||
en: "UVEX K2 EARMUFF",
|
||||
desc: "کاهش صدا تا ۲۸ دسیبل، مناسب محیطهای صنعتی و کارگاهی، قابل تنظیم و راحت برای استفاده طولانی.",
|
||||
specs: ["uvex", "هدبند قابل تنظیم", "بالشتک نرم"],
|
||||
image: "/images/products/hearing/2.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-HP03",
|
||||
group: "earmuff",
|
||||
name: "ایرماف بوفالو 110",
|
||||
en: "BUFFALO 110 EARMUFF",
|
||||
desc: "کاهش صدا تا ۲۸ دسیبل، مناسب محیطهای صنعتی و کارگاهی، قابل تنظیم و راحت برای استفاده طولانی.",
|
||||
specs: ["بوفالو", "هدبند قابل تنظیم", "بالشتک نرم"],
|
||||
image: "/images/products/hearing/4.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-HP04",
|
||||
group: "earmuff",
|
||||
name: "گوشی صداگیر 3M",
|
||||
en: "3M EARMUFF",
|
||||
desc: "کاهش صدا تا ۲۸ دسیبل، مناسب محیطهای صنعتی و کارگاهی، قابل تنظیم و راحت برای استفاده طولانی.",
|
||||
specs: ["3M", "هدبند قابل تنظیم", "بالشتک نرم"],
|
||||
image: "/images/products/hearing/8.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-HP05",
|
||||
group: "earplug",
|
||||
name: "ایرپلاگ کاستا مکس فیت MAXFIT",
|
||||
en: "COSTA MAXFIT EARPLUG",
|
||||
desc: "محافظت در برابر صداهای شدید و نویز محیط، سبک و راحت.",
|
||||
specs: ["سیلیکونی پلهای", "بنددار", "چندبار مصرف"],
|
||||
image: "/images/products/hearing/3.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-HP06",
|
||||
group: "earplug",
|
||||
name: "ایرپلاگ اسفنجی Bilsom 303L",
|
||||
en: "BILSOM 303L FOAM EARPLUG",
|
||||
desc: "محافظت در برابر صداهای شدید و نویز محیط، سبک و راحت.",
|
||||
specs: ["اسفنجی", "بنددار / بدون بند", "یکبار مصرف"],
|
||||
image: "/images/products/hearing/5.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-HP07",
|
||||
group: "earplug",
|
||||
name: "ایرپلاگ اسفنجی ماگما",
|
||||
en: "MAGMA FOAM EARPLUG",
|
||||
desc: "محافظت در برابر صداهای شدید و نویز محیط، سبک و راحت.",
|
||||
specs: ["اسفنجی", "یکبار مصرف"],
|
||||
image: "/images/products/hearing/6.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-HP08",
|
||||
group: "earplug",
|
||||
name: "ایرپلاگ مکس فیت با قاب",
|
||||
en: "MAXFIT EARPLUG WITH CASE",
|
||||
desc: "محافظت در برابر صداهای شدید و نویز محیط، سبک و راحت.",
|
||||
specs: ["سیلیکونی پلهای", "بنددار", "قاب نگهداری"],
|
||||
image: "/images/products/hearing/7.webp",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: "respiratory-protection",
|
||||
code: "IP-C07",
|
||||
name: "حفاظت تنفسی",
|
||||
en: "RESPIRATORY PROTECTION",
|
||||
shortDesc: "ماسکهای نیمصورت، تمامصورت، شیمیایی، P2/P3 و فیلتردار برای محافظت در برابر گازها و ذرات.",
|
||||
fullDesc: "تجهیزات حفاظت تنفسی شامل ماسکهای نیمصورت، تمامصورت، شیمیایی با کارتریج تخصصی، ماسکهای فیلتردار P2/P3 برای ذرات میکرونی، ماسکهای با فیلتر تعویضی اقتصادی و ماسکهای سهلایه بهداشتی. مناسب کار با حلالهای فرار، غبارهای معدنی، رنگپاشی و خطوط شیمیایی.",
|
||||
icon: "ShieldPlus",
|
||||
groups: [
|
||||
{ slug: "half", name: "ماسک نیمصورت", desc: "پوششدهی بینی و دهان با فیلتر قابل تعویض؛ مناسب محیطهای صنعتی با آلودگی متوسط و بخارات شیمیایی." },
|
||||
{ slug: "full", name: "ماسک تمامصورت", desc: "محافظت کامل از صورت و چشمها، مناسب کار با مواد شیمیایی و محیطهای پرخطر." },
|
||||
{ slug: "particulate", name: "ماسک فیلتردار P2/P3", desc: "محافظت در برابر گرد و غبار و ذرات معلق در هوا." },
|
||||
{ slug: "medical", name: "ماسک سهلایه", desc: "ماسک سبک و راحت، مناسب استفاده روزمره و محیطهای با آلودگی کم." },
|
||||
],
|
||||
products: [
|
||||
{
|
||||
code: "IP-R01",
|
||||
group: "half",
|
||||
name: "ماسک نیمصورت با فیلتر تعویضی",
|
||||
en: "HALF-FACE RESPIRATOR WITH FILTERS",
|
||||
desc: "پوششدهی بینی و دهان با فیلتر قابل تعویض برای استفاده طولانیمدت.",
|
||||
specs: ["نیمصورت", "فیلتر تعویضی", "دو کارتریج"],
|
||||
image: "/images/products/respiratory/5.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-R02",
|
||||
group: "half",
|
||||
name: "ماسک نیمصورت نورث 5500",
|
||||
en: "NORTH 5500 HALF MASK",
|
||||
desc: "پوششدهی بینی و دهان، مناسب محیطهای صنعتی با آلودگی متوسط.",
|
||||
specs: ["North", "نیمصورت", "فیلتر تعویضی"],
|
||||
image: "/images/products/respiratory/6.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-R03",
|
||||
group: "half",
|
||||
name: "ماسک شیمیایی نیمصورت 3M",
|
||||
en: "3M HALF-FACE RESPIRATOR",
|
||||
desc: "محافظت در برابر بخارات و گازهای شیمیایی با کارتریج و فیلتر قابل تعویض.",
|
||||
specs: ["3M", "کارتریج شیمیایی", "فیلتر تعویضی"],
|
||||
image: "/images/products/respiratory/7.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-R04",
|
||||
group: "half",
|
||||
name: "ماسک نیمصورت نورث 7700",
|
||||
en: "NORTH 7700 HALF MASK",
|
||||
desc: "پوششدهی بینی و دهان با بدنه سیلیکونی، مناسب استفاده طولانیمدت.",
|
||||
specs: ["North", "سیلیکونی", "فیلتر تعویضی"],
|
||||
image: "/images/products/respiratory/11.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-R05",
|
||||
group: "half",
|
||||
name: "ماسک نیمصورت نورث 7700 (نمای جانبی)",
|
||||
en: "NORTH 7700 HALF MASK",
|
||||
desc: "پوششدهی بینی و دهان با بدنه سیلیکونی، مناسب استفاده طولانیمدت.",
|
||||
specs: ["North", "سیلیکونی", "فیلتر تعویضی"],
|
||||
image: "/images/products/respiratory/10.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-R06",
|
||||
group: "full",
|
||||
name: "ماسک تمامصورت",
|
||||
en: "FULL-FACE RESPIRATOR",
|
||||
desc: "محافظت کامل از صورت و چشمها، مناسب کار با مواد شیمیایی و محیطهای پرخطر.",
|
||||
specs: ["تمامصورت", "شیشه ضد ضربه", "فیلتر تعویضی"],
|
||||
image: "/images/products/respiratory/4.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-R07",
|
||||
group: "full",
|
||||
name: "ماسک تنفسی دراگر",
|
||||
en: "DRÄGER FULL-FACE RESPIRATOR",
|
||||
desc: "محافظت کامل از صورت و چشمها، مناسب کار با مواد شیمیایی و محیطهای پرخطر.",
|
||||
specs: ["Dräger", "تمامصورت", "فیلتر چندمنظوره"],
|
||||
image: "/images/products/respiratory/8.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-R08",
|
||||
group: "particulate",
|
||||
name: "ماسک فیلتردار تاشو",
|
||||
en: "FOLDABLE PARTICULATE MASK",
|
||||
desc: "محافظت در برابر گرد و غبار و ذرات معلق در هوا.",
|
||||
specs: ["P2", "تاشو", "یکبار مصرف"],
|
||||
image: "/images/products/respiratory/1.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-R09",
|
||||
group: "particulate",
|
||||
name: "ماسک فیلتردار سوپاپدار",
|
||||
en: "VALVED PARTICULATE MASK",
|
||||
desc: "محافظت در برابر گرد و غبار و ذرات معلق در هوا.",
|
||||
specs: ["P2 / P3", "سوپاپ بازدمی", "یکبار مصرف"],
|
||||
image: "/images/products/respiratory/2.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-R10",
|
||||
group: "particulate",
|
||||
name: "ماسک فیلتردار دراگر سوپاپدار",
|
||||
en: "DRÄGER VALVED PARTICULATE MASK",
|
||||
desc: "محافظت در برابر گرد و غبار و ذرات معلق در هوا.",
|
||||
specs: ["Dräger", "سوپاپ بازدمی", "P2 / P3"],
|
||||
image: "/images/products/respiratory/3.webp",
|
||||
},
|
||||
{
|
||||
code: "IP-R11",
|
||||
group: "medical",
|
||||
name: "ماسک سهلایه پزشکی",
|
||||
en: "THREE-LAYER MEDICAL MASK",
|
||||
desc: "ماسک سبک و راحت، مناسب استفاده روزمره و محیطهای با آلودگی کم.",
|
||||
specs: ["سهلایه", "یکبار مصرف", "بسته ۵۰ عددی"],
|
||||
image: "/images/products/respiratory/9.webp",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
slug: "traffic-equipment",
|
||||
code: "IP-C08",
|
||||
name: "تجهیزات ترافیکی و ایمنی محیطی",
|
||||
en: "TRAFFIC & SITE SAFETY",
|
||||
shortDesc: "مخروط، نیوجرسی، استوانه، سرعتگیر، آینه محدب و سایر تجهیزات ایمنسازی محوطه صنعتی.",
|
||||
fullDesc: "تجهیزات ترافیکی و ایمنی محیطی شامل مخروط ترافیکی، نیوجرسی پلاستیکی، استوانه و بولارد، چراغ و تابلوهای هشداردهنده، باتوم ترافیکی، نوارهای خطر، سرعتگیر لاستیکی، آینههای محدب، محافظ گوشه ستون و گلمیخ خورشیدی. مناسب ایمنسازی محوطه کارخانه، انبارها، رمپها و پارکینگها.",
|
||||
icon: "TriangleAlert",
|
||||
products: [
|
||||
{
|
||||
code: "IP-T01",
|
||||
name: "مخروط ترافیکی",
|
||||
en: "TRAFFIC CONE",
|
||||
desc: "PVC انعطافپذیر با شبرنگهای استاندارد روز و شب؛ مقاوم در برابر واژگونی و ضربه خودرو.",
|
||||
specs: ["PVC انعطافپذیر", "شبرنگ استاندارد", "مقاوم به واژگونی", "قابل رؤیت شبانه"],
|
||||
},
|
||||
{
|
||||
code: "IP-T02",
|
||||
name: "نیوجرسی پلاستیکی",
|
||||
en: "NEW JERSEY BARRIER",
|
||||
desc: "بدنه پلیاتیلنی مدولار قابل اتصال با قابلیت پرشوندگی با آب یا ماسه جهت تثبیت و ایجاد حائل.",
|
||||
specs: ["پلیاتیلن مدولار", "قابل اتصال", "پرشدنی با آب/ماسه", "مقاوم UV"],
|
||||
},
|
||||
{
|
||||
code: "IP-T03",
|
||||
name: "استوانه ترافیکی",
|
||||
en: "TRAFFIC BOLLARD",
|
||||
desc: "پلیاورتان ارتجاعی انعطافپذیر پس از ضربه؛ مجهز به حلقههای شبرنگ هشدار برای تفکیک مسیر.",
|
||||
specs: ["پلیاورتان ارتجاعی", "بازگشت پس از ضربه", "شبرنگ هشدار", "نصب آسان"],
|
||||
},
|
||||
{
|
||||
code: "IP-T04",
|
||||
name: "چراغ و تابلوی هشدار",
|
||||
en: "WARNING LIGHT & SIGN",
|
||||
desc: "چراغهای چشمکزن سولار، تابلوی دستی ایست/حرکت و تابلوهای علائم هشداردهنده ترافیکی.",
|
||||
specs: ["سولار / باتری", "LED چشمکزن", "تابلوی Stop/Go", "علائم استاندارد"],
|
||||
},
|
||||
{
|
||||
code: "IP-T05",
|
||||
name: "باتوم ترافیکی",
|
||||
en: "TRAFFIC BATON",
|
||||
desc: "باتوم LED دار دستی جهت هدایت ترافیک در شب و محیطهای کمنور؛ سبک و ارگونومیک.",
|
||||
specs: ["LED داخلی", "قابل رؤیت شبانه", "سبکوزن", "ارگونومیک"],
|
||||
},
|
||||
{
|
||||
code: "IP-T06",
|
||||
name: "نوار خطر",
|
||||
en: "CAUTION TAPE",
|
||||
desc: "نوار شطرنجی و خطکشی زرد/قرمز هشدار محوطه گودبرداری و مناطق خطرناک با رولهای بازتابنده نور.",
|
||||
specs: ["زرد / قرمز هشدار", "بازتابنده نور", "رول بلند", "مقاوم به پارگی"],
|
||||
},
|
||||
{
|
||||
code: "IP-T07",
|
||||
name: "سرعتگیر لاستیکی",
|
||||
en: "RUBBER SPEED BUMP",
|
||||
desc: "قطعات لاستیکی سنگین مدولار با مقاومت وزنی بالا و المانهای بازتابش شبانه؛ نصب آسان.",
|
||||
specs: ["لاستیکی مدولار", "مقاومت وزنی بالا", "بازتاب شبانه", "نصب پیچی"],
|
||||
},
|
||||
{
|
||||
code: "IP-T08",
|
||||
name: "آینه محدب ترافیکی",
|
||||
en: "CONVEX TRAFFIC MIRROR",
|
||||
desc: "آینه نشکن فریمدار و کلاهدار جهت پوشش نقاط کور پیچها، تقاطعها و درب انبار.",
|
||||
specs: ["نشکن", "فریمدار و کلاهدار", "پوشش نقاط کور", "مقاوم به آب و باد"],
|
||||
},
|
||||
{
|
||||
code: "IP-T09",
|
||||
name: "محافظ گوشه ستون",
|
||||
en: "CORNER GUARD",
|
||||
desc: "ضربهگیر لاستیکی شبرنگدار جهت نصب روی ستونها برای ممانعت از آسیب لیفتراک و خودروهای سنگین.",
|
||||
specs: ["لاستیکی ضربهگیر", "شبرنگدار", "نصب دیواری", "محافظ ستون"],
|
||||
},
|
||||
{
|
||||
code: "IP-T10",
|
||||
name: "گلمیخ ترافیکی خورشیدی",
|
||||
en: "SOLAR ROAD STUD",
|
||||
desc: "بدنه آلومینیومی مستحکم مجهز به پنل خورشیدی و LED جهت تعیین مسیر و خطکشی شبانه.",
|
||||
specs: ["آلومینیوم مستحکم", "پنل خورشیدی", "LED", "مقاوم به عبور خودرو"],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
//# sourceMappingURL=categories.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
Vendored
+268
@@ -0,0 +1,268 @@
|
||||
export const SECTIONS = {
|
||||
settings: {
|
||||
name: "ایمن پیشگامان",
|
||||
nameEn: "Imen Pishgaman Safety",
|
||||
title: "ایمن پیشگامان | تجهیزات ایمنی و حفاظت فردی صنعتی",
|
||||
description: "ایمن پیشگامان — تأمینکننده تخصصی تجهیزات حفاظت فردی (PPE)، بازرسی، آموزش و راهکارهای ایمنی صنعتی برای سازمانهایی که جان انسانها را جدی میگیرند.",
|
||||
phone: "+98-21-91008800",
|
||||
phoneDisplay: "۰۲۱ - ۹۱ ۰۰ ۸۸ ۰۰",
|
||||
email: "info@imenpishgaman.ir",
|
||||
address: {
|
||||
street: "خیابان کارگر شمالی، خیابان فر، کوچه دوم، پلاک ۱۲",
|
||||
locality: "تهران",
|
||||
country: "IR",
|
||||
},
|
||||
workingHours: "شنبه تا پنجشنبه — ۸:۰۰ تا ۱۷:۰۰",
|
||||
openingHours: {
|
||||
days: ["Saturday", "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday"],
|
||||
opens: "08:00",
|
||||
closes: "17:00",
|
||||
},
|
||||
foundingDate: "2007",
|
||||
socials: [
|
||||
{ type: "website", url: "/" },
|
||||
{ type: "email", url: "mailto:info@imenpishgaman.ir" },
|
||||
{ type: "phone", url: "tel:+982191008800" },
|
||||
],
|
||||
ogImage: {
|
||||
url: "asset:hero.jpg",
|
||||
alt: "کارگر صنعتی با تجهیزات ایمنی کامل",
|
||||
width: 1792,
|
||||
height: 1008,
|
||||
},
|
||||
},
|
||||
hero: {
|
||||
enabled: true,
|
||||
badge: "تأمینکننده رسمی تجهیزات حفاظت فردی (PPE)",
|
||||
title: "ایمنی یعنی",
|
||||
titleHighlight: "بازگشت به خانه",
|
||||
intro: "ایمن پیشگامان با بیش از ۱۸ سال تجربه، تأمینکننده تخصصی تجهیزات ایمنی و حفاظت فردی برای سازمانهایی است که جان انسانها را جدی میگیرند؛ از کلاه ایمنی تا هارنس نجات، از مشاوره تا بازرسی و آموزش.",
|
||||
primaryCta: { label: "درخواست مشاوره رایگان", href: "#contact" },
|
||||
secondaryCta: { label: "مشاهده محصولات", href: "#products" },
|
||||
chips: [
|
||||
{ icon: "ShieldCheck", label: "ISO 9001:2015" },
|
||||
{ icon: "BadgeCheck", label: "CE CERTIFIED" },
|
||||
{ icon: "Award", label: "EN STANDARDS" },
|
||||
],
|
||||
miniStats: [
|
||||
{ value: "۱۸+", label: "سال تجربه در صنعت ایمنی" },
|
||||
{ value: "۸۵۰+", label: "سازمان تحت پوشش" },
|
||||
{ value: "۹۸٪", label: "رضایت مشتریان" },
|
||||
],
|
||||
image: { url: "asset:hero.jpg", alt: "کارگر صنعتی با تجهیزات ایمنی کامل" },
|
||||
lastInspection: "۱۴۰۴/۰۸/۱۲",
|
||||
},
|
||||
tape: {
|
||||
enabled: true,
|
||||
items: [
|
||||
"ISO 9001:2015",
|
||||
"CE CERTIFIED",
|
||||
"گارانتی اصالت کالا",
|
||||
"تحویل سراسری کشوری",
|
||||
"مشاوره و بازرسی تخصصی رایگان",
|
||||
"EN STANDARDS",
|
||||
"پشتیبانی ۲۴/۷ قراردادهای سازمانی",
|
||||
"تست نمونه قبل از خرید",
|
||||
],
|
||||
},
|
||||
stats: {
|
||||
enabled: true,
|
||||
items: [
|
||||
{ end: 18, suffix: "+", label: "سال تجربه در صنعت ایمنی", note: "SINCE 2007" },
|
||||
{ end: 2400, suffix: "+", label: "کد کالای ایمنی", note: "PPE CATALOG" },
|
||||
{ end: 850, suffix: "+", label: "مشتری سازمانی فعال", note: "B2B CLIENTS" },
|
||||
{ end: 98, suffix: "٪", label: "رضایت مشتریان", note: "SATISFACTION" },
|
||||
],
|
||||
},
|
||||
productsIntro: {
|
||||
enabled: true,
|
||||
head: {
|
||||
tag: "۰۱ — محصولات",
|
||||
title: "خط اول دفاع،\nتجهیزات ماست",
|
||||
highlight: "",
|
||||
desc: "هر محصول پیش از عرضه، زیر ذرهبین کنترل کیفیت و تطبیق با استانداردهای جهانی قرار میگیرد.",
|
||||
},
|
||||
ctaLabel: "دریافت کاتالوگ کامل",
|
||||
trust: [
|
||||
{
|
||||
icon: "BadgeCheck",
|
||||
title: "گارانتی اصالت کالا",
|
||||
desc: "تمام محصولات با فاکتور رسمی و کد رهگیری اصالت",
|
||||
},
|
||||
{
|
||||
icon: "PackageCheck",
|
||||
title: "تست نمونه قبل از خرید",
|
||||
desc: "نمونههای نمایشی برای ارزیابی تیم HSE شما",
|
||||
},
|
||||
{
|
||||
icon: "ShieldCheck",
|
||||
title: "امکان قرارداد سالانه",
|
||||
desc: "قیمت ثابت و تأمین مستمر برای سازمانهای بزرگ",
|
||||
},
|
||||
],
|
||||
},
|
||||
about: {
|
||||
enabled: true,
|
||||
head: {
|
||||
tag: "۰۲ — درباره ما",
|
||||
title: "ایمنی برای ما شعار نیست؛",
|
||||
highlight: "قرارداد است.",
|
||||
desc: "",
|
||||
},
|
||||
body: "از سال ۱۳۸۶، زمانی که اولین کلاه ایمنی را به یک کارگاه کوچک در جنوب تهران فروختیم، باور ما همین یک جمله بود: پشت هر تجهیز ایمنی، یک خانواده هست که منتظر بازگشت عزیزش است. امروز با بیش از ۸۵۰ سازمان، از پالایشگاهها تا معادن و سایتهای ساختمانی، در کنار کسانی هستیم که ایمنی را یک خط قرمز میدانند.",
|
||||
image: { url: "asset:about.jpg", alt: "انبار تجهیزات ایمنی ایمن پیشگامان" },
|
||||
badge: { value: "۱۸+", label: "سال حفاظت از\nجان نیروی کار" },
|
||||
timeline: [
|
||||
{ year: "۱۳۸۶", text: "تأسیس ایمن پیشگامان در تهران با تمرکز بر تجهیزات حفاظت فردی" },
|
||||
{ year: "۱۳۹۲", text: "توسعه شبکه توزیع به تمام ۳۱ استان کشور" },
|
||||
{ year: "۱۳۹۸", text: "راهاندازی آزمایشگاه کنترل کیفیت و تست تجهیزات" },
|
||||
{ year: "۱۴۰۲", text: "افتتاح مرکز تخصصی آموزش و بازرسی HSE" },
|
||||
],
|
||||
features: [
|
||||
{ icon: "Globe", label: "نماینده رسمی ۴۰+ برند جهانی" },
|
||||
{ icon: "Users", label: "تیم مهندسی و بازرسی HSE" },
|
||||
{ icon: "Headset", label: "پشتیبانی ۲۴/۷ قراردادها" },
|
||||
],
|
||||
},
|
||||
services: {
|
||||
enabled: true,
|
||||
head: {
|
||||
tag: "۰۳ — خدمات",
|
||||
title: "فراتر از تأمین؛\nهمراهی کامل ایمنی",
|
||||
highlight: "",
|
||||
desc: "ما در ایمن پیشگامان، تجهیزات را تحویل میدهیم و کنار شما میمانیم؛ از نخستین ارزیابی ریسک تا آخرین تست دورهای.",
|
||||
},
|
||||
items: [
|
||||
{
|
||||
icon: "ClipboardCheck",
|
||||
title: "بازرسی و ارزیابی ریسک",
|
||||
desc: "تیم مهندسی ما با بازدید از سایت شما، خطرات را شناسایی، اولویتبندی و برای هر کدام راهکار اجرایی مشخص با برآورد هزینه ارائه میدهد.",
|
||||
items: [
|
||||
"ارزیابی HAZOP و HAZID",
|
||||
"شناسایی و اولویتبندی خطرات",
|
||||
"گزارش مدیریتی با راهکار اجرایی",
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: "GraduationCap",
|
||||
title: "آموزش تخصصی ایمنی",
|
||||
desc: "از دورههای عمومی تا تربیت مربی؛ آموزشهای عملی و میدانی که ایمنی را از دفتر کلانها به رفتار روزمره پرسنل تبدیل میکند.",
|
||||
items: [
|
||||
"دورههای عمومی و تخصصی HSE",
|
||||
"تربیت مربی ایمنی سایت",
|
||||
"مانور و شبیهسازی شرایط اضطراری",
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: "Gauge",
|
||||
title: "تست و کالیبراسیون تجهیزات",
|
||||
desc: "تجهیزات ایمنی فقط وقتی قابل اعتمادند که تست شده باشند. تست دورهای، سرویس و صدور گواهی معتبر را به تیم تخصصی ما بسپارید.",
|
||||
items: [
|
||||
"تست هارنس و لاین نجات",
|
||||
"کالیبراسیون آشکارسازهای گاز",
|
||||
"سرویس دورهای و صدور گواهی",
|
||||
],
|
||||
},
|
||||
{
|
||||
icon: "Truck",
|
||||
title: "تأمین و لجستیک سازمانی",
|
||||
desc: "با قرارداد سالانه، قیمتگذاری شفاف و انبار محلی در سایت شما، نگرانی کمبود تجهیزات را از فهرست دغدغههایتان حذف کنید.",
|
||||
items: [
|
||||
"قرارداد سالانه با قیمت ثابت",
|
||||
"انبار محلی در سایت شما",
|
||||
"تحویل سراسری در ۴۸ ساعت",
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
standards: {
|
||||
enabled: true,
|
||||
head: {
|
||||
tag: "۰۴ — استانداردها",
|
||||
title: "مهرتأیید جهانی\nروی هر محصول",
|
||||
highlight: "",
|
||||
desc: "ما فقط تجهیزاتی را عرضه میکنیم که تأییدیه معتبر بینالمللی و تست آزمایشگاهی داشته باشند؛ بدون استثنا.",
|
||||
},
|
||||
note: "شماره سریال تمام محصولات در پایگاه داده استانداردهای اروپا قابل استعلام است و گواهی تست هر بچ تحویل مشتری میشود.",
|
||||
ctaLabel: "درخواست گواهی تست محصولات",
|
||||
items: [
|
||||
{ code: "EN 397", label: "کلاه ایمنی صنعتی" },
|
||||
{ code: "EN 388", label: "دستکشهای مکانیکی" },
|
||||
{ code: "EN 166", label: "محافظ چشم و صورت" },
|
||||
{ code: "EN 361", label: "هارنس ضدسقوط" },
|
||||
{ code: "ISO 20345", label: "کفش ایمنی" },
|
||||
{ code: "ISO 9001", label: "سیستم مدیریت کیفیت" },
|
||||
],
|
||||
},
|
||||
industries: {
|
||||
enabled: true,
|
||||
head: {
|
||||
tag: "۰۵ — صنایع",
|
||||
title: "حضور در سختترین\nمیدانهای کاری",
|
||||
highlight: "",
|
||||
desc: "جایی که دمای محیط از ۵۰ درجه گذر میکند یا ارتفاع کار به دهها متر میرسد، تجهیزات ما باید بینقص عمل کنند — و عمل میکنند.",
|
||||
},
|
||||
items: [
|
||||
{ name: "نفت و گاز", nameEn: "OIL & GAS", count: "۱۴۰+", image: { url: "asset:industries/oil-gas.jpg", alt: "نفت و گاز" } },
|
||||
{ name: "ساختمان و عمران", nameEn: "CONSTRUCTION", count: "۲۱۰+", image: { url: "asset:industries/construction.jpg", alt: "ساختمان و عمران" } },
|
||||
{ name: "پتروشیمی", nameEn: "PETROCHEMICAL", count: "۹۰+", image: { url: "asset:industries/petrochemical.jpg", alt: "پتروشیمی" } },
|
||||
{ name: "صنایع معدنی", nameEn: "MINING", count: "۷۵+", image: { url: "asset:industries/mining.jpg", alt: "صنایع معدنی" } },
|
||||
{ name: "برق و انرژی", nameEn: "POWER & ENERGY", count: "۶۰+", image: { url: "asset:industries/power-energy.jpg", alt: "برق و انرژی" } },
|
||||
{ name: "حملونقل و لجستیک", nameEn: "TRANSPORT", count: "۴۵+", image: { url: "asset:industries/transport.jpg", alt: "حملونقل و لجستیک" } },
|
||||
],
|
||||
},
|
||||
testimonials: {
|
||||
enabled: true,
|
||||
head: { tag: "۰۶ — اعتماد", title: "آنچه مشتریان ما میگویند", highlight: "", desc: "" },
|
||||
items: [
|
||||
{
|
||||
text: "ایمن پیشگامان فقط تأمینکننده نیست؛ شریک ایمنی ماست. بازرسیهای دورهای و آموزشهای میدانیشان نرخ حوادث سایت را به کمترین رقم تاریخ مجموعه رساند.",
|
||||
name: "مهندس رضا کاظمی",
|
||||
role: "مدیر HSE — مجتمع پتروشیمی خلیج فارس",
|
||||
},
|
||||
{
|
||||
text: "کیفیت تجهیزات و سرعت پاسخگویی در سطحی بود که قرارداد سالانه را بدون هیچ تردیدی تمدید کردیم؛ از خط تولید تا دفتر مرکزی.",
|
||||
name: "سارا محمدی",
|
||||
role: "سرپرست تدارکات — هلدینگ ساختمانی آرکان",
|
||||
},
|
||||
{
|
||||
text: "۱۴۰ کیلومتر دورتر از سایت، در عرض ۲۴ ساعت تجهیزات حیاتی را تحویل گرفتیم. به این میگویند شریک واقعی کاری.",
|
||||
name: "امیرحسین راد",
|
||||
role: "مدیر پروژه — صنایع معدنی کاوه",
|
||||
},
|
||||
],
|
||||
},
|
||||
contact: {
|
||||
enabled: true,
|
||||
head: {
|
||||
tag: "۰۷ — تماس",
|
||||
title: "بیایید ایمنی را\nبا هم جدی بگیریم",
|
||||
highlight: "",
|
||||
desc: "فرم را پر کنید تا کارشناسان ما برای هماهنگی جلسه بازدید رایگان سایت شما تماس بگیرند؛ یا مستقیم با ما در ارتباط باشید.",
|
||||
},
|
||||
emergencyTitle: "پاسخگویی اضطراری ۲۴/۷",
|
||||
emergencyText: "برای مشتریان دارای قرارداد سازمانی، خط ویژه حوادث و تأمین فوری تجهیزات در تمام ساعات شبانهروز فعال است.",
|
||||
formTag: "// فرم درخواست مشاوره",
|
||||
formTitle: "در کمتر از ۲۴ ساعت\nبا شما تماس میگیریم",
|
||||
successTitle: "درخواست شما ثبت شد",
|
||||
successText: "کارشناسان ما تا ۲۴ ساعت آینده برای هماهنگی جلسه بازدید با شما تماس میگیرند.",
|
||||
},
|
||||
footer: {
|
||||
enabled: true,
|
||||
about: "تأمینکننده تخصصی تجهیزات حفاظت فردی و راهکارهای ایمنی صنعتی؛ همراه سازمانها از سال ۱۳۸۶ در مسیر صفر شدن حوادث کاری.",
|
||||
copyright: "© ۱۴۰۴ ایمن پیشگامان — تمامی حقوق محفوظ است.",
|
||||
certLine: "ISO 9001:2015 — CE CERTIFIED PPE SUPPLIER",
|
||||
emergencyNote: "پشتیبانی اضطراری: ۲۴/۷",
|
||||
},
|
||||
};
|
||||
export const CATEGORY_COVERS = {
|
||||
workwear: "products/vest.jpg",
|
||||
"safety-footwear": "products/shoes.jpg",
|
||||
"protective-gloves": "products/gloves.jpg",
|
||||
"safety-helmets": "products/helmet.jpg",
|
||||
"eye-protection": "products/goggles.jpg",
|
||||
"hearing-protection": "products/hearing.jpg",
|
||||
"respiratory-protection": "products/respiratory.jpg",
|
||||
"traffic-equipment": "products/traffic.jpg",
|
||||
};
|
||||
//# sourceMappingURL=sections.js.map
|
||||
+1
File diff suppressed because one or more lines are too long
Vendored
+270
@@ -0,0 +1,270 @@
|
||||
import { env } from "../config/env.js";
|
||||
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { normalizeIrMobile, SECTION_KEYS, sectionSchemas } from "@imen/shared";
|
||||
import { hashPassword } from "../auth/auth.service.js";
|
||||
import { S3Storage, toWebp } from "../media/s3-storage.js";
|
||||
import { createPrismaClient } from "../prisma/prisma.service.js";
|
||||
import { CATEGORIES } from "./data/categories.js";
|
||||
import { CATEGORY_COVERS, SECTIONS } from "./data/sections.js";
|
||||
const ASSETS_DIR = process.env.SEED_ASSETS_DIR ??
|
||||
path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../seed-assets");
|
||||
const OVERWRITE_SECTIONS = ["1", "true"].includes(process.env.SEED_OVERWRITE_SECTIONS ?? "");
|
||||
const prisma = createPrismaClient();
|
||||
const storage = new S3Storage();
|
||||
const uploaded = new Map();
|
||||
async function asset(rel, alt = "") {
|
||||
const cached = uploaded.get(rel);
|
||||
if (cached)
|
||||
return cached;
|
||||
const key = `seed/${rel.replace(/\.[^.]+$/, "")}.webp`;
|
||||
let media = await prisma.media.findUnique({ where: { key } });
|
||||
if (!media) {
|
||||
const image = await toWebp(await readFile(path.join(ASSETS_DIR, rel)));
|
||||
try {
|
||||
await storage.put(key, image.data, "image/webp");
|
||||
}
|
||||
catch {
|
||||
try {
|
||||
const targetPath = path.resolve(ASSETS_DIR, "../../.s3data/imen-media", key);
|
||||
await mkdir(path.dirname(targetPath), { recursive: true });
|
||||
await writeFile(targetPath, image.data);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
media = await prisma.media.create({
|
||||
data: {
|
||||
key,
|
||||
mime: "image/webp",
|
||||
size: image.size,
|
||||
width: image.width,
|
||||
height: image.height,
|
||||
alt,
|
||||
originalName: path.basename(rel),
|
||||
},
|
||||
});
|
||||
}
|
||||
const result = { id: media.id, url: storage.publicUrl(key) };
|
||||
uploaded.set(rel, result);
|
||||
return result;
|
||||
}
|
||||
async function resolveAssets(value) {
|
||||
if (Array.isArray(value))
|
||||
return Promise.all(value.map(resolveAssets));
|
||||
if (value && typeof value === "object") {
|
||||
const entries = await Promise.all(Object.entries(value).map(async ([k, v]) => {
|
||||
if (k === "url" && typeof v === "string" && v.startsWith("asset:")) {
|
||||
return [k, (await asset(v.slice("asset:".length))).url];
|
||||
}
|
||||
return [k, await resolveAssets(v)];
|
||||
}));
|
||||
return Object.fromEntries(entries);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
async function seedSections() {
|
||||
for (const key of SECTION_KEYS) {
|
||||
const exists = await prisma.siteSection.findUnique({ where: { key } });
|
||||
if (exists && !OVERWRITE_SECTIONS)
|
||||
continue;
|
||||
const data = sectionSchemas[key].parse(await resolveAssets(SECTIONS[key]));
|
||||
await prisma.siteSection.upsert({ where: { key }, create: { key, data }, update: { data } });
|
||||
console.log(` section ${key}`);
|
||||
}
|
||||
}
|
||||
async function seedCatalog() {
|
||||
for (const [ci, c] of CATEGORIES.entries()) {
|
||||
if (await prisma.category.findUnique({ where: { slug: c.slug } }))
|
||||
continue;
|
||||
const coverPath = CATEGORY_COVERS[c.slug];
|
||||
const cover = coverPath ? await asset(coverPath, c.name) : null;
|
||||
const category = await prisma.category.create({
|
||||
data: {
|
||||
slug: c.slug,
|
||||
code: c.code,
|
||||
name: c.name,
|
||||
nameEn: c.en,
|
||||
shortDesc: c.shortDesc,
|
||||
fullDesc: c.fullDesc,
|
||||
icon: c.icon,
|
||||
coverImageId: cover?.id ?? null,
|
||||
sortOrder: ci,
|
||||
groups: {
|
||||
create: (c.groups ?? []).map((g, i) => ({
|
||||
slug: g.slug,
|
||||
name: g.name,
|
||||
desc: g.desc,
|
||||
sortOrder: i,
|
||||
})),
|
||||
},
|
||||
},
|
||||
include: { groups: true },
|
||||
});
|
||||
const groupIds = new Map(category.groups.map((g) => [g.slug, g.id]));
|
||||
for (const [pi, p] of c.products.entries()) {
|
||||
const image = p.image ? await asset(p.image.replace(/^\/images\//, ""), p.name) : null;
|
||||
await prisma.product.create({
|
||||
data: {
|
||||
categoryId: category.id,
|
||||
groupId: p.group ? (groupIds.get(p.group) ?? null) : null,
|
||||
code: p.code,
|
||||
slug: p.code.toLowerCase(),
|
||||
name: p.name,
|
||||
nameEn: p.en,
|
||||
desc: p.desc,
|
||||
specs: [...p.specs],
|
||||
variants: [...(p.variants ?? [])],
|
||||
imageId: image?.id ?? null,
|
||||
sortOrder: pi,
|
||||
},
|
||||
});
|
||||
}
|
||||
console.log(` category ${c.slug} (${c.products.length} products)`);
|
||||
}
|
||||
}
|
||||
async function seedAdmin() {
|
||||
if (!env.ADMIN_PHONE || !env.ADMIN_PASSWORD) {
|
||||
console.log(" ADMIN_PHONE / ADMIN_PASSWORD not set — skipping admin user");
|
||||
return;
|
||||
}
|
||||
const phone = normalizeIrMobile(env.ADMIN_PHONE);
|
||||
if (!/^09\d{9}$/.test(phone)) {
|
||||
console.log(` ADMIN_PHONE invalid (${env.ADMIN_PHONE}) — skipping admin user`);
|
||||
return;
|
||||
}
|
||||
const existing = await prisma.adminUser.findUnique({ where: { phone } });
|
||||
if (existing)
|
||||
return;
|
||||
const legacy = await prisma.adminUser.findFirst({
|
||||
where: { role: "ADMIN", phone: { contains: "@" } },
|
||||
orderBy: { id: "asc" },
|
||||
});
|
||||
if (legacy) {
|
||||
await prisma.adminUser.update({
|
||||
where: { id: legacy.id },
|
||||
data: { phone, name: env.ADMIN_NAME, passwordHash: await hashPassword(env.ADMIN_PASSWORD) },
|
||||
});
|
||||
console.log(` admin user migrated to ${phone}`);
|
||||
return;
|
||||
}
|
||||
await prisma.adminUser.create({
|
||||
data: {
|
||||
phone,
|
||||
name: env.ADMIN_NAME,
|
||||
role: "ADMIN",
|
||||
passwordHash: await hashPassword(env.ADMIN_PASSWORD),
|
||||
},
|
||||
});
|
||||
console.log(` admin user ${phone}`);
|
||||
}
|
||||
async function seedMessages() {
|
||||
const count = await prisma.contactMessage.count();
|
||||
if (count > 0)
|
||||
return;
|
||||
const samples = [
|
||||
{
|
||||
name: "مهندس رضا کاظمی",
|
||||
org: "مجتمع پتروشیمی خلیج فارس",
|
||||
phone: "09121112233",
|
||||
message: "درخواست پیشفاکتور ۵۰۰ عدد هارنس ایمنی دو قلاب و کلاه ایمنی استاندارد EN 397 برای فاز ۲ پتروشیمی.",
|
||||
status: "NEW",
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 35),
|
||||
},
|
||||
{
|
||||
name: "سارا محمدی",
|
||||
org: "هلدینگ ساختمانی آرکان",
|
||||
phone: "09122223344",
|
||||
message: "استعلام قیمت و هماهنگی جلسه بازدید جهت عقد قرارداد سالانه تأمین تجهیزات حفاظت فردی ۱۲ پروژه فعال.",
|
||||
status: "NEW",
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 120),
|
||||
},
|
||||
{
|
||||
name: "امیرحسین راد",
|
||||
org: "صنایع معدنی کاوه",
|
||||
phone: "09123334455",
|
||||
message: "درخواست تأمین فوری ۲۰۰ جفت دستکش ایمنی ضدبرش و کفش ایمنی ضداسید و پنجه کامپوزیت برای سایت یزد.",
|
||||
status: "READ",
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 8),
|
||||
},
|
||||
{
|
||||
name: "مهندس علیرضا بهرامی",
|
||||
org: "شرکت نفت و گاز پارس",
|
||||
phone: "09124445566",
|
||||
message: "تقاضای ارسال کاتالوگ و نمونه عینک ایمنی استاندارد EN 166 و محافظ گوش ایرماف جهت آزمایش واحد HSE.",
|
||||
status: "NEW",
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 24),
|
||||
},
|
||||
{
|
||||
name: "مریم رضایی",
|
||||
org: "داروسازی رازک",
|
||||
phone: "09125556677",
|
||||
message: "درخواست مشاوره تخصصی در رابطه با ماسکهای فیلتردار ذرات شیمیایی و لباسهای یکبار مصرف آزمایشگاهی.",
|
||||
status: "READ",
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 36),
|
||||
},
|
||||
{
|
||||
name: "مهندس مهدی یوسفی",
|
||||
org: "فولاد مبارکه اصفهان",
|
||||
phone: "09126667788",
|
||||
message: "درخواست برگزاری جلسه فنی برای تمدید قرارداد تأمین لباس کار نسوز، شیلد محافظ صورت و پابند نسوز.",
|
||||
status: "ARCHIVED",
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 72),
|
||||
},
|
||||
{
|
||||
name: "پیمان نادری",
|
||||
org: "خطوط لوله و مخابرات نفت",
|
||||
phone: "09127778899",
|
||||
message: "استعلام موجودی تجهیزات نجات در فضاهای بسته، سهپایه نجات (Tripod) و وینچ همراه با گواهی کالیبراسیون.",
|
||||
status: "NEW",
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 96),
|
||||
},
|
||||
{
|
||||
name: "دکتر مجید اسدی",
|
||||
org: "سیمان تهران",
|
||||
phone: "09128889900",
|
||||
message: "درخواست برگزاری دوره آموزشی کار در ارتفاع برای پرسنل پیمانکار و بازرسی دورهای خطوط حیات (Lifeline).",
|
||||
status: "READ",
|
||||
createdAt: new Date(Date.now() - 1000 * 60 * 60 * 120),
|
||||
},
|
||||
];
|
||||
for (const msg of samples) {
|
||||
await prisma.contactMessage.create({ data: msg });
|
||||
}
|
||||
console.log(` seeded ${samples.length} sample contact messages`);
|
||||
}
|
||||
async function main() {
|
||||
if (env.S3_ENSURE_BUCKET) {
|
||||
try {
|
||||
await storage.ensureBucket();
|
||||
}
|
||||
catch {
|
||||
console.log(" ensureBucket skipped");
|
||||
}
|
||||
}
|
||||
console.log("seeding sections…");
|
||||
await seedSections();
|
||||
console.log("seeding catalog…");
|
||||
await seedCatalog();
|
||||
console.log("seeding admin…");
|
||||
await seedAdmin();
|
||||
console.log("seeding messages…");
|
||||
await seedMessages();
|
||||
const [categories, products, media] = await Promise.all([
|
||||
prisma.category.count(),
|
||||
prisma.product.count(),
|
||||
prisma.media.count(),
|
||||
]);
|
||||
console.log(`done: ${categories} categories, ${products} products, ${media} media files`);
|
||||
}
|
||||
try {
|
||||
await main();
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
finally {
|
||||
await prisma.$disconnect();
|
||||
}
|
||||
//# sourceMappingURL=seed.js.map
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
+144
@@ -0,0 +1,144 @@
|
||||
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);
|
||||
};
|
||||
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
||||
return function (target, key) { decorator(target, key, paramIndex); }
|
||||
};
|
||||
import { BadRequestException, Body, Controller, Delete, Get, HttpCode, Injectable, Module, Param, Post, Put, } from "@nestjs/common";
|
||||
import { userCreateSchema, userUpdateSchema, } from "@imen/shared";
|
||||
import { AdminAuth, CurrentUser } from "../common/auth.js";
|
||||
import { AuditService } from "../common/common.module.js";
|
||||
import { IdPipe, ZodPipe } from "../common/zod.pipe.js";
|
||||
import { AuthModule } from "../auth/auth.controller.js";
|
||||
import { AuthService, hashPassword, toUserDto } from "../auth/auth.service.js";
|
||||
import { PrismaService } from "../prisma/prisma.service.js";
|
||||
let UsersService = class UsersService {
|
||||
prisma;
|
||||
auth;
|
||||
audit;
|
||||
constructor(prisma, auth, audit) {
|
||||
this.prisma = prisma;
|
||||
this.auth = auth;
|
||||
this.audit = audit;
|
||||
}
|
||||
async list() {
|
||||
const users = await this.prisma.adminUser.findMany({ orderBy: { id: "asc" } });
|
||||
return users.map(toUserDto);
|
||||
}
|
||||
async create(input, actor) {
|
||||
const user = await this.prisma.adminUser.create({
|
||||
data: {
|
||||
phone: input.phone,
|
||||
name: input.name,
|
||||
role: input.role,
|
||||
passwordHash: await hashPassword(input.password),
|
||||
},
|
||||
});
|
||||
this.audit.log(actor.id, "create", "user", user.id, user.phone);
|
||||
return toUserDto(user);
|
||||
}
|
||||
async update(id, input, actor) {
|
||||
if (id === actor.id && (input.role !== "ADMIN" || !input.isActive)) {
|
||||
throw new BadRequestException("نمیتوانید نقش یا وضعیت حساب خودتان را تغییر دهید");
|
||||
}
|
||||
const user = await this.prisma.adminUser.update({
|
||||
where: { id },
|
||||
data: {
|
||||
name: input.name,
|
||||
role: input.role,
|
||||
isActive: input.isActive,
|
||||
...(input.password ? { passwordHash: await hashPassword(input.password) } : {}),
|
||||
},
|
||||
});
|
||||
if (input.password || !input.isActive)
|
||||
await this.auth.revokeAll(id);
|
||||
this.audit.log(actor.id, "update", "user", id, user.phone);
|
||||
return toUserDto(user);
|
||||
}
|
||||
async remove(id, actor) {
|
||||
if (id === actor.id)
|
||||
throw new BadRequestException("نمیتوانید حساب خودتان را حذف کنید");
|
||||
const user = await this.prisma.adminUser.delete({ where: { id } });
|
||||
this.audit.log(actor.id, "delete", "user", id, user.phone);
|
||||
}
|
||||
};
|
||||
UsersService = __decorate([
|
||||
Injectable(),
|
||||
__metadata("design:paramtypes", [PrismaService,
|
||||
AuthService,
|
||||
AuditService])
|
||||
], UsersService);
|
||||
export { UsersService };
|
||||
let UsersController = class UsersController {
|
||||
users;
|
||||
constructor(users) {
|
||||
this.users = users;
|
||||
}
|
||||
list() {
|
||||
return this.users.list();
|
||||
}
|
||||
create(body, actor) {
|
||||
return this.users.create(body, actor);
|
||||
}
|
||||
update(id, body, actor) {
|
||||
return this.users.update(id, body, actor);
|
||||
}
|
||||
async remove(id, actor) {
|
||||
await this.users.remove(id, actor);
|
||||
}
|
||||
};
|
||||
__decorate([
|
||||
Get(),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", []),
|
||||
__metadata("design:returntype", void 0)
|
||||
], UsersController.prototype, "list", null);
|
||||
__decorate([
|
||||
Post(),
|
||||
__param(0, Body(new ZodPipe(userCreateSchema))),
|
||||
__param(1, CurrentUser()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Object, Object]),
|
||||
__metadata("design:returntype", void 0)
|
||||
], UsersController.prototype, "create", null);
|
||||
__decorate([
|
||||
Put(":id"),
|
||||
__param(0, Param("id", IdPipe)),
|
||||
__param(1, Body(new ZodPipe(userUpdateSchema))),
|
||||
__param(2, CurrentUser()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Number, Object, Object]),
|
||||
__metadata("design:returntype", void 0)
|
||||
], UsersController.prototype, "update", null);
|
||||
__decorate([
|
||||
Delete(":id"),
|
||||
HttpCode(204),
|
||||
__param(0, Param("id", IdPipe)),
|
||||
__param(1, CurrentUser()),
|
||||
__metadata("design:type", Function),
|
||||
__metadata("design:paramtypes", [Number, Object]),
|
||||
__metadata("design:returntype", Promise)
|
||||
], UsersController.prototype, "remove", null);
|
||||
UsersController = __decorate([
|
||||
AdminAuth("ADMIN"),
|
||||
Controller("admin/users"),
|
||||
__metadata("design:paramtypes", [UsersService])
|
||||
], UsersController);
|
||||
export { UsersController };
|
||||
let UsersModule = class UsersModule {
|
||||
};
|
||||
UsersModule = __decorate([
|
||||
Module({
|
||||
imports: [AuthModule],
|
||||
controllers: [UsersController],
|
||||
providers: [UsersService],
|
||||
})
|
||||
], UsersModule);
|
||||
export { UsersModule };
|
||||
//# sourceMappingURL=users.controller.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"users.controller.js","sourceRoot":"","sources":["../../src/users/users.controller.ts"],"names":[],"mappings":"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"}
|
||||
Reference in New Issue
Block a user