first commit
This commit is contained in:
4
src/core/constants/index.ts
Normal file
4
src/core/constants/index.ts
Normal 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']
|
||||
90
src/core/constants/server-configuration.ts
Normal file
90
src/core/constants/server-configuration.ts
Normal 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 };
|
||||
8
src/core/controller/main.controller.ts
Normal file
8
src/core/controller/main.controller.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import autoBind from "auto-bind";
|
||||
|
||||
export class Controller {
|
||||
constructor() {
|
||||
autoBind(this);
|
||||
}
|
||||
}
|
||||
|
||||
6
src/core/messages/index.ts
Normal file
6
src/core/messages/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export const GlobalMessages = {
|
||||
success: {},
|
||||
errors: {
|
||||
server: "خطايي رخ داده است",
|
||||
},
|
||||
};
|
||||
29
src/core/middleware/auth.middleware.ts
Normal file
29
src/core/middleware/auth.middleware.ts
Normal 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
|
||||
17
src/core/router/main.router.ts
Normal file
17
src/core/router/main.router.ts
Normal 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
98
src/core/types/index.ts
Normal 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
|
||||
}
|
||||
33
src/core/utils/functions.ts
Normal file
33
src/core/utils/functions.ts
Normal 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;
|
||||
}
|
||||
12
src/core/utils/generators.ts
Normal file
12
src/core/utils/generators.ts
Normal 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}`;
|
||||
};
|
||||
Reference in New Issue
Block a user