first commit

This commit is contained in:
2026-05-23 14:14:50 +03:30
commit 2a22bab127
48 changed files with 7554 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
export const JWT_SECRET = process.env.JWT_SECRET || "secret";
export const TOKEN_NAME = 'userToken'
export const requestType = ['software','hardware','his','rahkaran','taradod','general']

View File

@@ -0,0 +1,90 @@
import { CorsOptions } from "cors";
import dotenv from "dotenv";
import { NextFunction } from "express";
import { Options } from "express-rate-limit";
import { HelmetOptions } from "helmet";
import createHttpError from "http-errors";
import { Request } from "express";
dotenv.config();
const multer = require("multer");
const path = require("path");
const fs = require("fs");
export const config = {
port: process.env.PORT || 3500,
db: {
host: process.env.DB_HOST,
port: Number(process.env.DB_PORT),
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
},
jwtSecret: process.env.JWT_SECRET || "secret",
};
export const limiter_option: Partial<Options> = {
windowMs: 15 * 60 * 1000,
max: 4000,
standardHeaders: true,
legacyHeaders: false,
handler: (req: any, res: any, next: NextFunction) => {
next(
new createHttpError.TooManyRequests(
"تعداد درخواست شما بیشتر از حد مجاز است ، در زمان دیگری مجدد درخواست دهید",
),
);
},
// message: {error: "Too many requests, please try again later."},
};
export const helmet_option: HelmetOptions = {
contentSecurityPolicy: {
useDefaults: true,
directives: {
defaultSrc: ["'self'"],
// scriptSrc: ["'self'", "'unsafe-inline'", "https://trusted.cdn.com"],
// styleSrc: ["'self'", "'unsafe-inline'"],
// imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'"],
// fontSrc: ["'self'", "https://fonts.gstatic.com"],
objectSrc: ["'none'"],
upgradeInsecureRequests: [], // تبدیل اتومات http به https
},
},
crossOriginEmbedderPolicy: true,
crossOriginResourcePolicy: { policy: "same-origin" },
frameguard: { action: "deny" }, // جلوگیری از Clickjacking
referrerPolicy: { policy: "no-referrer" }, // جلوگیری از لو رفتن referrer
xssFilter: true, // فعال کردن فیلتر XSS
hsts: { maxAge: 63072000, includeSubDomains: true, preload: true }, // HSTS
};
export const cors_option: CorsOptions = {
origin: true,
credentials: true,
allowedHeaders: [
"Content-Type",
"Authorization",
"x-upload-token", // 👈 اینو اضافه کن
],
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"],
maxAge: 600,
// origin:["http://localhost:3000"]
};
async function createDirectoryRoute(req: Request<any>) {
const date = new Date();
const directory = path.join(__dirname, "..", "..", "..", "public", "images");
req.body.fileUploadPath = path.join(directory, "original");
fs.mkdirSync(directory, { recursive: true });
return directory;
}
const storage = multer.memoryStorage(); // Keep files in memory (instead of disk)
const uploadFile = multer({ storage: storage });
export { uploadFile };

View File

@@ -0,0 +1,8 @@
import autoBind from "auto-bind";
export class Controller {
constructor() {
autoBind(this);
}
}

View File

@@ -0,0 +1,6 @@
export const GlobalMessages = {
success: {},
errors: {
server: "خطايي رخ داده است",
},
};

View File

@@ -0,0 +1,29 @@
import { NextFunction } from "express";
import { ServerResponse } from "../types";
import { JWT_SECRET, TOKEN_NAME } from "../constants";
import createHttpError from "http-errors";
const jwt = require("jsonwebtoken");
const authMiddleware = (req:any, res:ServerResponse, next:NextFunction) => {
// ۱. خواندن توکن از کوکی‌ها
const token = req.cookies[TOKEN_NAME]; // TOKEN_NAME همان متغیری است که در زمان ست کردن کوکی داشتید
if (!token) {
throw new createHttpError.Unauthorized("لطفا وارد حساب كاربري خود شويد")
}
try {
// ۲. اعتبارسنجی توکن با استفاده از همان SECRET
const decoded = jwt.verify(token, JWT_SECRET);
// ۳. قرار دادن اطلاعات کاربر در آبجکت درخواست برای استفاده در مسیرهای بعدی
req.user = decoded;
next(); // ادامه به مسیر اصلی
} catch (error) {
throw new createHttpError.Forbidden("حساب كاربري شما نامعتبر است")
}
};
export default authMiddleware

View File

@@ -0,0 +1,17 @@
import router from "express";
import authRouter from "../../modules/auth/routes/auth.routes";
import departmentRouter from "../../modules/department/routes/department.routes";
import ticketRouter from "../../modules/ticket/routes/ticket.routes";
import userRouter from "../../modules/user/routes/user.router";
import reportRouter from "../../modules/report/routes/report.router";
import authMiddleware from "../middleware/auth.middleware";
const mainRouter = router.Router();
mainRouter.use("/auth", authRouter);
mainRouter.use("/department", authMiddleware, departmentRouter);
mainRouter.use("/ticket", authMiddleware, ticketRouter);
mainRouter.use("/user", authMiddleware, userRouter);
mainRouter.use("/report", authMiddleware, reportRouter);
export default mainRouter;

98
src/core/types/index.ts Normal file
View File

@@ -0,0 +1,98 @@
import { Response } from "express";
export interface ServerResponseObject {
status: number;
data: any;
message?: string;
}
export interface ServerErrorsObject {
status: number;
data?: any;
error: {
message: string;
description?: string;
};
}
export type ServerResponse = Response<
ServerResponseObject | ServerErrorsObject
>;
export enum TicketPriority {
LOW = "low",
MEDIUM = "medium",
HIGH = "high",
CRITICAL = "critical",
}
export enum TicketStatus {
OPEN = "open",
PENDING = "pending",
IN_PROGRESS = "in_progress",
RESOLVED = "resolved",
CLOSED = "closed",
}
export interface ticketRequestTypeDataType {
id: string;
ticketNumber: string;
createdBy: string;
departmentId: string;
internalPhone?: string;
requestType: string;
priority: TicketPriority;
description: string;
relatedSystem?: string;
location?: string;
helpdeskAction?: string;
assignedTo?: string;
status: TicketStatus;
resolvedAt?: Date;
finalNotes?: string;
createdAt?: Date;
updatedAt?: Date;
}
// reports
export interface TicketStats {
total: number
open: number
inProgress: number
resolved: number
closed: number
}
export interface DepartmentReport {
departmentId: string
departmentName: string
totalTickets: number
openTickets: number
resolvedTickets: number
}
export interface AgentReport {
userId: string
fullname: string
totalAssigned: number
resolved: number
open: number
}

View File

@@ -0,0 +1,33 @@
import { Op, WhereOptions } from "sequelize";
export interface ReportFilters {
startDate?: string;
endDate?: string;
priority?: string;
departmentId?: string;
agentId?: string;
}
export function buildTicketWhere(filters: ReportFilters): WhereOptions {
const where: WhereOptions = {};
if (filters.startDate && filters.endDate) {
where["createdAt"] = {
[Op.between]: [new Date(filters.startDate), new Date(filters.endDate)],
};
}
if (filters.priority) {
where["priority"] = filters.priority;
}
if (filters.departmentId) {
where["departmentId"] = filters.departmentId;
}
if (filters.agentId) {
where["assignedTo"] = filters.agentId;
}
return where;
}

View File

@@ -0,0 +1,12 @@
export const generateTicketNumber = (): string => {
const now = new Date();
const date =
now.getFullYear().toString() +
String(now.getMonth() + 1).padStart(2, "0") +
String(now.getDate()).padStart(2, "0");
const random = Math.floor(1000 + Math.random() * 9000);
return `TCK-${date}-${random}`;
};