first commit

This commit is contained in:
2026-05-26 16:00:09 +03:30
commit 83fd5c1a86
81 changed files with 6867 additions and 0 deletions

1
.env.development Normal file
View File

@@ -0,0 +1 @@
JWT_SECRET=a4f6c1f6b9b5c1c7c3a63cce9f9c5b7d6f8e3c7c2a0f7a1d9e0b4c8d2a1f5e6c3b2d9f0a6e7c4b1d8f3a2c5e6d9b7a0

1
.env.production Normal file
View File

@@ -0,0 +1 @@
JWT_SECRET=a4f6c1f6b9b5c1c7c3a63cce9f9c5b7d6f8e3c7c2a0f7a1d9e0b4c8d2a1f5e6c3b2d9f0a6e7c4b1d8f3a2c5e6d9b7a0

8
.sequelizerc Normal file
View File

@@ -0,0 +1,8 @@
const path = require("path");
module.exports = {
config: path.resolve("sequelize.config.js"),
"models-path": path.resolve("src/models"),
"seeders-path": path.resolve("seeders"),
"migrations-path": path.resolve("migrations")
};

2
index.ts Normal file
View File

@@ -0,0 +1,2 @@
import ServerApplication from "./src/app";
new ServerApplication();

View File

@@ -0,0 +1,22 @@
'use strict';
/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up (queryInterface, Sequelize) {
/**
* Add altering commands here.
*
* Example:
* await queryInterface.createTable('users', { id: Sequelize.INTEGER });
*/
},
async down (queryInterface, Sequelize) {
/**
* Add reverting commands here.
*
* Example:
* await queryInterface.dropTable('users');
*/
}
};

View File

@@ -0,0 +1,22 @@
'use strict';
/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up (queryInterface, Sequelize) {
/**
* Add altering commands here.
*
* Example:
* await queryInterface.createTable('users', { id: Sequelize.INTEGER });
*/
},
async down (queryInterface, Sequelize) {
/**
* Add reverting commands here.
*
* Example:
* await queryInterface.dropTable('users');
*/
}
};

3496
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

48
package.json Normal file
View File

@@ -0,0 +1,48 @@
{
"name": "backend",
"version": "1.0.0",
"description": "",
"main": "index.ts",
"scripts": {
"dev": "nodemon index.ts",
"build": "tsc",
"start": "node index.ts",
"type-check": "tsc --noEmit",
"clean": "rm -rf dist"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"dependencies": {
"auto-bind": "^4.0.0",
"bcryptjs": "^3.0.3",
"cookie-parser": "^1.4.7",
"cors": "^2.8.6",
"csurf": "^1.11.0",
"express": "^5.2.1",
"express-rate-limit": "^8.5.2",
"helmet": "^8.1.0",
"hpp": "^0.2.3",
"http-errors": "^2.0.1",
"jsonwebtoken": "^9.0.3",
"multer": "^2.1.1",
"pg": "^8.20.0",
"pg-hstore": "^2.3.4",
"sequelize": "^6.37.8"
},
"devDependencies": {
"@types/cookie-parser": "^1.4.10",
"@types/cors": "^2.8.19",
"@types/csurf": "^1.11.5",
"@types/express": "^5.0.6",
"@types/hpp": "^0.2.7",
"@types/jsonwebtoken": "^9.0.10",
"@types/node": "^25.9.0",
"dotenv": "^17.4.2",
"nodemon": "^3.1.14",
"sequelize-cli": "^6.6.5",
"ts-node": "^10.9.2",
"typescript": "^6.0.3"
}
}

135
seeders/rbac.seed.ts Normal file
View File

@@ -0,0 +1,135 @@
import { sequelize } from "../src/models";
import { Permission } from "../src/models/Permission";
import { Role } from "../src/models/Role";
export async function seedRBAC() {
const transaction = await sequelize.transaction();
try {
/**
* PERMISSIONS
*/
const permissionsList = [
"VIEW_APPLICANTS",
"CREATE_APPLICANT",
"EDIT_APPLICANT",
"DELETE_APPLICANT",
"EXPORT_APPLICANTS",
"VIEW_USERS",
"CREATE_USER",
"EDIT_USER",
"DELETE_USER",
"VIEW_ROLES",
"MANAGE_ROLES",
"SYSTEM_SETTINGS"
];
const permissions: any = {};
for (const perm of permissionsList) {
const [permission] = await Permission.findOrCreate({
where: { name: perm },
defaults: { name: perm },
transaction
});
permissions[perm] = permission;
}
/**
* ROLES
*/
const rolesData = [
{
name: "SUPER_ADMIN",
description: "دسترسی کامل به کل سیستم"
},
{
name: "ADMIN",
description: "مدیریت کاربران و رزومه‌ها"
},
{
name: "HR",
description: "کارشناس منابع انسانی"
},
{
name: "VIEWER",
description: "فقط مشاهده رزومه‌ها"
}
];
const roles: any = {};
for (const roleData of rolesData) {
const [role] = await Role.findOrCreate({
where: { name: roleData.name },
defaults: roleData,
transaction
});
roles[roleData.name] = role;
}
/**
* ROLE PERMISSIONS
*/
await roles.SUPER_ADMIN.setPermissions(Object.values(permissions), {
transaction
});
await roles.ADMIN.setPermissions(
[
permissions.VIEW_APPLICANTS,
permissions.CREATE_APPLICANT,
permissions.EDIT_APPLICANT,
permissions.DELETE_APPLICANT,
permissions.EXPORT_APPLICANTS,
permissions.VIEW_USERS,
permissions.CREATE_USER,
permissions.EDIT_USER,
permissions.VIEW_ROLES
],
{ transaction }
);
await roles.HR.setPermissions(
[
permissions.VIEW_APPLICANTS,
permissions.CREATE_APPLICANT,
permissions.EDIT_APPLICANT,
permissions.EXPORT_APPLICANTS
],
{ transaction }
);
await roles.VIEWER.setPermissions(
[
permissions.VIEW_APPLICANTS
],
{ transaction }
);
await transaction.commit();
console.log("✅ RBAC seed completed");
} catch (error) {
await transaction.rollback();
console.error("❌ RBAC seed failed:", error);
throw error;
}
}

18
sequelize.config.js Normal file
View File

@@ -0,0 +1,18 @@
require("ts-node/register");
module.exports = {
development: {
username: "postgres",
password: "root",
database: "employee_form",
host: "127.0.0.1",
dialect: "postgres",
},
production: {
username: "postgres",
password: "root",
database: "employee_form_prod",
host: "127.0.0.1",
dialect: "postgres",
},
};

96
src/app.ts Normal file
View File

@@ -0,0 +1,96 @@
import { NextFunction, Request, Response } from "express";
import path from "node:path";
import cookieParser from "cookie-parser";
import createHttpError from "http-errors";
// import { ServerErrorsObject, ServerResponse } from "./core/types";
// import { secureApp } from "./config/secure-app";
// import mainRouter from "./core/router/main.router";
import dotenv from "dotenv";
import { secureApp } from "./core/config/secure-app";
import initDB from "./core/config/db-connection";
import { ServerErrorsObject, ServerResponse } from "./core/types";
import { seedRBAC } from "../seeders/rbac.seed";
import mainRouter from "./core/router/main.router";
// import { seedDepartments } from "./seeders/department.seed";
// import { seedUsers } from "./seeders/user.seed";
// import { seedRoles } from "./seeders/role.seed";
const express = require("express") as typeof import("express");
dotenv.config();
export default class ServerApplication {
#PORT = 4000;
#APP = express();
constructor() {
this.serverConfiguration();
this.StartApplication();
this.InitClientSession();
this.RoutesConfiguration();
this.ErrorHandlingConfiguration();
}
async serverConfiguration() {
// this.#APP.use(secureApp);
this.#APP.use(express.json());
this.#APP.use(express.urlencoded({ extended: true }));
this.#APP.set("json spaces", 2);
this.#APP.use(
"/media/images",
express.static(path.join(__dirname, "..", "media", "images")),
);
this.#APP.use(
"/media/videos",
express.static(path.join(__dirname, "..", "media", "videos")),
);
}
async StartApplication() {
await initDB();
// await seedRBAC();
this.#APP.listen(this.#PORT, () => {
console.log(
`Server Running on PORT ${this.#PORT} url : ${"http://localhost:"}${
this.#PORT
}`,
);
});
}
InitClientSession() {
this.#APP.use(cookieParser(process.env.COOKIE_PARSER_SECRET_KEY));
}
RoutesConfiguration() {
// this.#APP.get("/", (req, res) => res.send(""));
this.#APP.use("/api/v1", mainRouter);
}
ErrorHandlingConfiguration() {
this.#APP.use((req: any, res: Response, next: NextFunction) => {
next(createHttpError.NotFound("این آدرس یافت نشد"));
});
this.#APP.use(
async (error: any, req: any, res: ServerResponse, next: NextFunction) => {
// await ErrorLog.create({
// message: error.message,
// stack: error.stack,
// severity: "HIGH",
// });
// console.log(error);
const serverError = createHttpError.InternalServerError();
const statusCode = error.status || serverError.status;
const message: string = error.message || serverError.message;
const errorObject: ServerErrorsObject = {
status: statusCode,
error: {
message,
},
};
return res.status(statusCode).json(errorObject);
},
);
}
}

16
src/config/database.ts Normal file
View File

@@ -0,0 +1,16 @@
// import { Sequelize } from "sequelize";
// const sequelize = new Sequelize(
// "employee_form",
// "postgres",
// "root",
// {
// host: "127.0.0.1",
// dialect: "postgres",
// logging: false
// }
// );
// export default sequelize;

View File

@@ -0,0 +1,15 @@
import { sequelize } from "../../models";
async function initDB(): Promise<void> {
try {
await sequelize.authenticate();
await sequelize.sync({ alter: true });
console.log("✅ Database synced successfully");
} catch (error) {
console.error("❌ Database sync failed:", error);
process.exit(1);
}
}
export default initDB;

View File

@@ -0,0 +1,29 @@
import helmet from "helmet";
import rateLimit from "express-rate-limit";
import cors from "cors";
import cookieParser from "cookie-parser";
import csurf from "csurf";
import hpp from "hpp";
import { cors_option, helmet_option, limiter_option } from "./server-configuration";
export const rateLimiter = rateLimit(limiter_option);
export const corsOptions = cors(cors_option);
export const securityHeaders = helmet(helmet_option);
export const csrfProtection = [
cookieParser(),
csurf({cookie: {httpOnly: true, secure: true, sameSite: "strict"}}),
];
export const sanitizeData = [
hpp(),
];
export const secureApp = [
corsOptions,
rateLimiter,
securityHeaders,
...sanitizeData,
];

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 @@
export const TOKEN_NAME = 'tid'

View File

@@ -0,0 +1,7 @@
export const Permissions = [
"VIEW_APPLICANTS",
"EDIT_APPLICANT",
"DELETE_APPLICANT",
"EXPORT_APPLICANTS",
"MANAGE_USERS",
];

View File

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

View File

View File

@@ -0,0 +1,6 @@
export const GlobalErrorMessages = Object.freeze({
server: {
internal: "متاسفانه خطايي رخ داده است",
},
notFound: "يافت نشد",
});

View File

View File

@@ -0,0 +1,47 @@
import { Request, Response, NextFunction } from "express";
import jwt from "jsonwebtoken";
import { User } from "../../models/User";
import { Role } from "../../models/Role";
import { Permission } from "../../models/Permission";
/**
* Middleware: استخراج اطلاعات کاربر از توکن JWT
* توکن باید در Header ارسال شود: Authorization: Bearer <token>
*/
export async function requireAuth(
req: Request,
res: Response,
next: NextFunction
) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith("Bearer ")) {
return res.status(401).json({ message: "توکن احراز هویت ارسال نشده است." });
}
const token = authHeader.split(" ")[1];
try {
// بررسی صحت و امضای JWT
const decoded: any = jwt.verify(token, process.env.JWT_SECRET!);
// پیدا کردن یوزر و نقش‌ها از دیتابیس
const user = await User.findByPk(decoded.userId, {
include: [
{
model: Role,
include: [Permission],
},
],
});
if (!user) {
return res.status(404).json({ message: "کاربر یافت نشد." });
}
// تزریق یوزر در request برای استفاده‌های بعدی
(req as any).user = user;
next();
} catch (err) {
return res.status(401).json({ message: "توکن نامعتبر یا منقضی است." });
}
}

View File

@@ -0,0 +1,24 @@
import { NextFunction } from "express";
import { ServerResponse } from "../types";
export function requirePermission(...permissions: string[]) {
return (req: Request, res: ServerResponse, next: NextFunction) => {
const user = (req as any).user;
if (!user)
return res
.status(401)
.json({ status: 401, data: {}, message: "احراز هویت انجام نشده است." });
const userPermissions =
user.Role?.Permissions?.map((p: any) => p.name) || [];
const hasPerm = permissions.every((p) => userPermissions.includes(p));
if (!hasPerm) {
return res
.status(403)
.json({ status: 403, data: {}, message: "دسترسی شما کافی نیست." });
}
next();
};
}

View File

@@ -0,0 +1,25 @@
import { NextFunction } from "express";
import { ServerResponse } from "../types";
export function requireRole(...roles: string[]) {
return (req: Request, res: ServerResponse, next: NextFunction) => {
const user = (req as any).user;
if (!user)
return res
.status(401)
.json({ status: 401, data: {}, message: "احراز هویت انجام نشده است." });
const userRole = user.Role?.name;
if (!userRole || !roles.includes(userRole)) {
return res
.status(403)
.json({
status: 403,
data: {},
message: "شما دسترسی لازم برای این عملیات را ندارید.",
});
}
next();
};
}

View File

@@ -0,0 +1,12 @@
import { Router } from 'express';
import AuthRouter from '../../modules/auth/router/auth.routes';
const mainRouter = Router();
// mainRouter.use('/user',userRouter)
// mainRouter.use('/applicant',applicantRouter)
mainRouter.use('/auth',AuthRouter)
// mainRouter.use('/user',userRouter)
export default mainRouter;

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

@@ -0,0 +1,20 @@
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
>;

39
src/models/Applicant.ts Normal file
View File

@@ -0,0 +1,39 @@
import { Model, DataTypes, Sequelize, Optional } from "sequelize";
export interface ApplicantAttributes {
id: string;
createdAt?: Date;
updatedAt?: Date;
}
export interface ApplicantCreationAttributes
extends Optional<ApplicantAttributes, "id" | "createdAt" | "updatedAt"> {}
export class Applicant
extends Model<ApplicantAttributes, ApplicantCreationAttributes>
implements ApplicantAttributes
{
public id!: string;
public readonly createdAt!: Date;
public readonly updatedAt!: Date;
static initModel(sequelize: Sequelize): typeof Applicant {
Applicant.init(
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
allowNull: false
}
},
{
sequelize,
tableName: "applicants",
timestamps: true
}
);
return Applicant;
}
}

40
src/models/Center.ts Normal file
View File

@@ -0,0 +1,40 @@
import { DataTypes, Model, Sequelize } from "sequelize";
export class Center extends Model {
public id!: string;
public name!: string;
public address!: string;
public isUrgent!: boolean;
static initModel(sequelize: Sequelize) {
Center.init(
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
address: {
type: DataTypes.TEXT,
allowNull: true,
},
isUrgent: {
type: DataTypes.BOOLEAN,
defaultValue: false,
allowNull: false,
},
},
{
sequelize,
tableName: "centers",
timestamps: true,
},
);
return Center;
}
}

View File

@@ -0,0 +1,46 @@
import { DataTypes, Model, Sequelize } from "sequelize";
const skillLevels = [
"NONE",
"VERY_WEAK",
"WEAK",
"AVERAGE",
"GOOD",
"VERY_GOOD",
"EXCELLENT",
];
export class ComputerSkill extends Model {
static initModel(sequelize: Sequelize) {
ComputerSkill.init(
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
applicantId: { type: DataTypes.UUID, allowNull: false },
pcUsage: { type: DataTypes.ENUM(...skillLevels), defaultValue: "NONE" },
word: { type: DataTypes.ENUM(...skillLevels), defaultValue: "NONE" },
excel: { type: DataTypes.ENUM(...skillLevels), defaultValue: "NONE" },
powerPoint: {
type: DataTypes.ENUM(...skillLevels),
defaultValue: "NONE",
},
rahkaran: {
type: DataTypes.ENUM(...skillLevels),
defaultValue: "NONE",
},
kasra: { type: DataTypes.ENUM(...skillLevels), defaultValue: "NONE" },
didgah: { type: DataTypes.ENUM(...skillLevels), defaultValue: "NONE" },
his: { type: DataTypes.ENUM(...skillLevels), defaultValue: "NONE" },
otherSoftware: { type: DataTypes.TEXT }, // توضیحات سایر نرم‌افزارها
},
{ sequelize, tableName: "computer_skills", timestamps: true },
);
return ComputerSkill;
}
}

26
src/models/Course.ts Normal file
View File

@@ -0,0 +1,26 @@
import { Model, DataTypes, Sequelize, Optional } from "sequelize";
export interface CourseAttributes {
id: string;
applicantId: string;
title: string;
institution: string;
year: number;
duration: string;
description?: string;
}
export class Course extends Model<CourseAttributes, Optional<CourseAttributes, "id">> {
static initModel(sequelize: Sequelize) {
Course.init({
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
applicantId: { type: DataTypes.UUID, allowNull: false },
title: { type: DataTypes.STRING, allowNull: false },
institution: { type: DataTypes.STRING },
year: { type: DataTypes.INTEGER },
duration: { type: DataTypes.STRING },
description: { type: DataTypes.TEXT }
}, { sequelize, tableName: "courses", timestamps: true });
return Course;
}
}

114
src/models/Education.ts Normal file
View File

@@ -0,0 +1,114 @@
import { Model, DataTypes, Sequelize, Optional } from "sequelize";
export interface EducationAttributes {
id: string;
applicantId: string;
degree: string; // مقطع تحصیلی
field: string; // رشته تحصیلی
university: string; // دانشگاه یا موسسه
startYear?: number;
endYear?: number;
gpa?: number; // معدل
description?: string;
certificateImageId?: string; // آیدی تصویر مدرک تحصیلی (FK به جدول فایل‌ها)
createdAt?: Date;
updatedAt?: Date;
}
export type EducationCreationAttributes = Optional<
EducationAttributes,
"id" | "createdAt" | "updatedAt" | "certificateImageId"
>;
export class Education
extends Model<EducationAttributes, EducationCreationAttributes>
implements EducationAttributes
{
public id!: string;
public applicantId!: string;
public degree!: string;
public field!: string;
public university!: string;
public startYear?: number;
public endYear?: number;
public gpa?: number;
public description?: string;
public certificateImageId?: string;
public readonly createdAt!: Date;
public readonly updatedAt!: Date;
static initModel(sequelize: Sequelize): typeof Education {
Education.init(
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
allowNull: false
},
applicantId: {
type: DataTypes.UUID,
allowNull: false
},
degree: {
type: DataTypes.STRING,
allowNull: false
},
field: {
type: DataTypes.STRING,
allowNull: false
},
university: {
type: DataTypes.STRING,
allowNull: false
},
startYear: {
type: DataTypes.INTEGER,
allowNull: true
},
endYear: {
type: DataTypes.INTEGER,
allowNull: true
},
gpa: {
type: DataTypes.FLOAT,
allowNull: true
},
description: {
type: DataTypes.TEXT,
allowNull: true
},
certificateImageId: {
type: DataTypes.UUID,
allowNull: true
}
},
{
sequelize,
tableName: "educations",
timestamps: true
}
);
return Education;
}
}

76
src/models/File.ts Normal file
View File

@@ -0,0 +1,76 @@
import { Model, DataTypes, Sequelize, Optional } from "sequelize";
export interface FileAttributes {
id: string;
fileName: string;
originalName: string;
mimeType: string;
path: string;
size: number;
createdAt?: Date;
updatedAt?: Date;
}
export interface FileCreationAttributes
extends Optional<FileAttributes, "id"> {}
export class File
extends Model<FileAttributes, FileCreationAttributes>
implements FileAttributes
{
public id!: string;
public fileName!: string;
public originalName!: string;
public mimeType!: string;
public path!: string;
public size!: number;
public readonly createdAt!: Date;
public readonly updatedAt!: Date;
static initModel(sequelize: Sequelize): typeof File {
File.init(
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
fileName: {
type: DataTypes.STRING,
allowNull: false
},
originalName: {
type: DataTypes.STRING
},
mimeType: {
type: DataTypes.STRING
},
path: {
type: DataTypes.STRING,
allowNull: false
},
size: {
type: DataTypes.INTEGER
}
},
{
sequelize,
tableName: "files",
timestamps: true
}
);
return File;
}
}

119
src/models/Identity.ts Normal file
View File

@@ -0,0 +1,119 @@
import { Model, DataTypes, Sequelize, Optional } from "sequelize";
export interface IdentityAttributes {
id: string;
applicantId: string;
firstName: string;
lastName: string;
fatherName: string;
nationalCode: string;
birthDate: Date;
birthPlace: string;
gender: string;
religion: string;
nationality: string;
profilePhotoId?: string;
createdAt?: Date;
updatedAt?: Date;
}
export interface IdentityCreationAttributes
extends Optional<IdentityAttributes, "id" | "profilePhotoId"> {}
export class Identity
extends Model<IdentityAttributes, IdentityCreationAttributes>
implements IdentityAttributes
{
public id!: string;
public applicantId!: string;
public firstName!: string;
public lastName!: string;
public fatherName!: string;
public nationalCode!: string;
public birthDate!: Date;
public birthPlace!: string;
public gender!: string;
public religion!: string;
public nationality!: string;
public profilePhotoId?: string;
public readonly createdAt!: Date;
public readonly updatedAt!: Date;
static initModel(sequelize: Sequelize): typeof Identity {
Identity.init(
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true
},
applicantId: {
type: DataTypes.UUID,
allowNull: false
},
firstName: {
type: DataTypes.STRING,
allowNull: false
},
lastName: {
type: DataTypes.STRING,
allowNull: false
},
fatherName: {
type: DataTypes.STRING
},
nationalCode: {
type: DataTypes.STRING(10),
allowNull: false
},
birthDate: {
type: DataTypes.DATEONLY
},
birthPlace: {
type: DataTypes.STRING
},
gender: {
type: DataTypes.STRING
},
religion: {
type: DataTypes.STRING
},
nationality: {
type: DataTypes.STRING
},
profilePhotoId: {
type: DataTypes.UUID,
allowNull: true
}
},
{
sequelize,
tableName: "identities",
timestamps: true
}
);
return Identity;
}
}

43
src/models/JobCategory.ts Normal file
View File

@@ -0,0 +1,43 @@
import { DataTypes, Model, Sequelize } from "sequelize";
export class JobCategory extends Model {
public id!: string;
public centerId!: string;
public name!: string;
public isUrgent!: boolean;
static initModel(sequelize: Sequelize) {
JobCategory.init(
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
centerId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: 'centers',
key: 'id'
}
},
name: {
type: DataTypes.STRING,
allowNull: false,
},
isUrgent: {
type: DataTypes.BOOLEAN,
defaultValue: false,
allowNull: false,
},
},
{
sequelize,
tableName: "job_categories",
timestamps: true
}
);
return JobCategory;
}
}

79
src/models/JobInfo.ts Normal file
View File

@@ -0,0 +1,79 @@
import { DataTypes, Model, Sequelize } from "sequelize";
export class JobInfo extends Model {
public id!: string;
public applicantId!: string;
public readyToWorkDate!: Date; // تاریخ آمادگی برای شروع
public isCurrentEmployee!: boolean; // از پرسنل حال حاضر هستم یا خیر
public hasPastCooperation!: boolean; // سابقه همکاری در گذشته
public isCurrentlyEmployed!: boolean; // در حال حاضر مشغول به کار هستم یا خیر
public dualJobInterest!: boolean; // تمایل به دو جا کار بودن
public retirementStatus!: "None" | "Retired" | "Redeemed"; // بازنشسته / بازخرید / هیچکدام
public isMilitary!: boolean; // نظامی بودن
public hasInsurance!: boolean; // سابقه بیمه دارد یا خیر
public insuranceType?: string; // نوع بیمه (تامین اجتماعی، خدمات درمانی و...)
public totalInsuranceYears!: number; // جمع سال‌های سابقه بیمه
static initModel(sequelize: Sequelize) {
JobInfo.init(
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
applicantId: {
type: DataTypes.UUID,
allowNull: false,
unique: true, // معمولاً برای هر متقاضی یک رکورد وضعیت کاری وجود دارد
},
readyToWorkDate: {
type: DataTypes.DATEONLY,
allowNull: false,
},
isCurrentEmployee: {
type: DataTypes.BOOLEAN,
defaultValue: false,
},
hasPastCooperation: {
type: DataTypes.BOOLEAN,
defaultValue: false,
},
isCurrentlyEmployed: {
type: DataTypes.BOOLEAN,
defaultValue: false,
},
dualJobInterest: {
type: DataTypes.BOOLEAN,
defaultValue: false,
},
retirementStatus: {
type: DataTypes.ENUM("None", "Retired", "Redeemed"),
defaultValue: "None",
},
isMilitary: {
type: DataTypes.BOOLEAN,
defaultValue: false,
},
hasInsurance: {
type: DataTypes.BOOLEAN,
defaultValue: false,
},
insuranceType: {
type: DataTypes.STRING,
allowNull: true,
},
totalInsuranceYears: {
type: DataTypes.DECIMAL(4, 1), // مثلاً 10.5 سال سابقه
defaultValue: 0,
},
},
{
sequelize,
tableName: "work_experiences",
timestamps: true,
},
);
return JobInfo;
}
}

83
src/models/JobRequest.ts Normal file
View File

@@ -0,0 +1,83 @@
import { DataTypes, Model, Sequelize } from "sequelize";
export class JobRequest extends Model {
// تعریف فیلدها برای شناسایی توسط TypeScript
public id!: string;
public applicantId!: string;
public centerId!: string;
public categoryId!: string;
public jobTitle!: string;
public description?: string;
public employmentType!: "Full-Time" | "Part-Time" | "Contract";
public requestedSalary!: number;
public extraDescription?: string;
// فیلدهای زمان‌بندی (اگر timestamps: true باشد)
public readonly createdAt!: Date;
public readonly updatedAt!: Date;
static initModel(sequelize: Sequelize) {
JobRequest.init(
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
applicantId: {
type: DataTypes.UUID,
allowNull: false,
},
centerId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: "centers", // نام جدول مراکز
key: "id",
},
},
categoryId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: "job_categories", // نام جدول رسته‌ها
key: "id",
},
},
jobTitle: {
type: DataTypes.STRING,
allowNull: false,
},
description: {
type: DataTypes.TEXT,
allowNull: true,
},
employmentType: {
// تعریف نوع همکاری به صورت ENUM
type: DataTypes.ENUM("Full-Time", "Part-Time", "Contract"),
allowNull: false,
},
requestedSalary: {
// استفاده از BIGINT چون مبالغ ریالی معمولاً طولانی هستند
type: DataTypes.BIGINT,
allowNull: false,
get() {
// تبدیل رشته خروجی از دیتابیس به عدد در جاوااسکریپت
const value = this.getDataValue("requestedSalary");
return value ? parseInt(value, 10) : 0;
},
},
extraDescription: {
type: DataTypes.TEXT,
allowNull: true,
},
},
{
sequelize,
tableName: "job_infos", // همان نامی که خودتان انتخاب کردید
timestamps: true,
},
);
return JobRequest;
}
}

View File

@@ -0,0 +1,16 @@
import { DataTypes, Model, Sequelize } from "sequelize";
export class LanguageSkill extends Model {
static initModel(sequelize: Sequelize) {
LanguageSkill.init({
id: { type: DataTypes.UUID, defaultValue: DataTypes.UUIDV4, primaryKey: true },
applicantId: { type: DataTypes.UUID, allowNull: false },
languageName: { type: DataTypes.STRING }, // انگلیسی، عربی، گویش خاص و ...
proficiency: { type: DataTypes.STRING }, // میزان تسلط
hasCertificate: { type: DataTypes.BOOLEAN, defaultValue: false },
certificateType: { type: DataTypes.STRING },
description: { type: DataTypes.TEXT }
}, { sequelize, tableName: "language_skills", timestamps: true });
return LanguageSkill;
}
}

51
src/models/Permission.ts Normal file
View File

@@ -0,0 +1,51 @@
// src/models/permission.model.ts
import {
Model,
DataTypes,
Optional,
Sequelize
} from "sequelize";
interface PermissionAttributes {
id: string;
name: string;
createdAt?: Date;
updatedAt?: Date;
}
type PermissionCreationAttributes = Optional<PermissionAttributes, "id">;
export class Permission extends Model<
PermissionAttributes,
PermissionCreationAttributes
> implements PermissionAttributes {
public id!: string;
public name!: string;
public readonly createdAt!: Date;
public readonly updatedAt!: Date;
static initModel(sequelize: Sequelize): typeof Permission {
Permission.init(
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
},
},
{
sequelize,
tableName: "permissions",
}
);
return Permission;
}
}

188
src/models/PersonalInfo.ts Normal file
View File

@@ -0,0 +1,188 @@
import { Model, DataTypes, Sequelize, Optional } from "sequelize";
export interface PersonalInfoAttributes {
id: string;
applicantId: string;
maritalStatus?: string;
militaryStatus?: string;
fatherEducation?: string;
fatherJob?: string;
motherEducation?: string;
motherJob?: string;
housingStatus?: string;
city?: string;
address?: string;
homePhone?: string;
mobilePhone?: string;
emergencyPhone?: string;
email?: string;
residenceDuration?: number;
isVeteran?: boolean;
hasCriminalRecord?: boolean;
criminalDescription?: string;
createdAt?: Date;
updatedAt?: Date;
}
export type PersonalInfoCreationAttributes = Optional<
PersonalInfoAttributes,
"id" | "createdAt" | "updatedAt"
>;
export class PersonalInfo
extends Model<PersonalInfoAttributes, PersonalInfoCreationAttributes>
implements PersonalInfoAttributes
{
public id!: string;
public applicantId!: string;
public maritalStatus?: string;
public militaryStatus?: string;
public fatherEducation?: string;
public fatherJob?: string;
public motherEducation?: string;
public motherJob?: string;
public housingStatus?: string;
public city?: string;
public address?: string;
public homePhone?: string;
public mobilePhone?: string;
public emergencyPhone?: string;
public email?: string;
public residenceDuration?: number;
public isVeteran?: boolean;
public hasCriminalRecord?: boolean;
public criminalDescription?: string;
public readonly createdAt!: Date;
public readonly updatedAt!: Date;
static initModel(sequelize: Sequelize): typeof PersonalInfo {
PersonalInfo.init(
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
allowNull: false
},
applicantId: {
type: DataTypes.UUID,
allowNull: false
},
maritalStatus: {
type: DataTypes.STRING,
allowNull: true
},
militaryStatus: {
type: DataTypes.STRING,
allowNull: true
},
fatherEducation: {
type: DataTypes.STRING,
allowNull: true
},
fatherJob: {
type: DataTypes.STRING,
allowNull: true
},
motherEducation: {
type: DataTypes.STRING,
allowNull: true
},
motherJob: {
type: DataTypes.STRING,
allowNull: true
},
housingStatus: {
type: DataTypes.STRING,
allowNull: true
},
city: {
type: DataTypes.STRING,
allowNull: true
},
address: {
type: DataTypes.TEXT,
allowNull: true
},
homePhone: {
type: DataTypes.STRING,
allowNull: true
},
mobilePhone: {
type: DataTypes.STRING,
allowNull: true
},
emergencyPhone: {
type: DataTypes.STRING,
allowNull: true
},
email: {
type: DataTypes.STRING,
allowNull: true,
validate: {
isEmail: true
}
},
residenceDuration: {
type: DataTypes.INTEGER,
allowNull: true
},
isVeteran: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
hasCriminalRecord: {
type: DataTypes.BOOLEAN,
defaultValue: false
},
criminalDescription: {
type: DataTypes.TEXT,
allowNull: true
}
},
{
sequelize,
tableName: "personal_infos",
timestamps: true
}
);
return PersonalInfo;
}
}

139
src/models/PhysicalInfo.ts Normal file
View File

@@ -0,0 +1,139 @@
import { Model, DataTypes, Sequelize, Optional } from "sequelize";
export interface PhysicalInfoAttributes {
id: string;
applicantId: string;
bloodType?: string;
height?: number; // به سانتی‌متر
weight?: number; // به کیلوگرم
bmi?: number;
hasDisability: boolean;
disabilityDescription?: string;
hasChronicDisease: boolean;
chronicDiseaseDescription?: string;
surgeryHistory?: string;
medications?: string;
specialMark?: string; // علامت مشخصه
createdAt?: Date;
updatedAt?: Date;
}
export type PhysicalInfoCreationAttributes = Optional<
PhysicalInfoAttributes,
"id" | "createdAt" | "updatedAt" | "hasDisability" | "hasChronicDisease"
>;
export class PhysicalInfo
extends Model<PhysicalInfoAttributes, PhysicalInfoCreationAttributes>
implements PhysicalInfoAttributes
{
public id!: string;
public applicantId!: string;
public bloodType?: string;
public height?: number;
public weight?: number;
public bmi?: number;
public hasDisability!: boolean;
public disabilityDescription?: string;
public hasChronicDisease!: boolean;
public chronicDiseaseDescription?: string;
public surgeryHistory?: string;
public medications?: string;
public specialMark?: string;
public readonly createdAt!: Date;
public readonly updatedAt!: Date;
static initModel(sequelize: Sequelize): typeof PhysicalInfo {
PhysicalInfo.init(
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
allowNull: false
},
applicantId: {
type: DataTypes.UUID,
allowNull: false
},
bloodType: {
type: DataTypes.STRING(10),
allowNull: true
},
height: {
type: DataTypes.FLOAT,
allowNull: true
},
weight: {
type: DataTypes.FLOAT,
allowNull: true
},
bmi: {
type: DataTypes.FLOAT,
allowNull: true
},
hasDisability: {
type: DataTypes.BOOLEAN,
defaultValue: false,
allowNull: false
},
disabilityDescription: {
type: DataTypes.TEXT,
allowNull: true
},
hasChronicDisease: {
type: DataTypes.BOOLEAN,
defaultValue: false,
allowNull: false
},
chronicDiseaseDescription: {
type: DataTypes.TEXT,
allowNull: true
},
surgeryHistory: {
type: DataTypes.TEXT,
allowNull: true
},
medications: {
type: DataTypes.TEXT,
allowNull: true
},
specialMark: {
type: DataTypes.STRING,
allowNull: true
}
},
{
sequelize,
tableName: "physical_infos",
timestamps: true
}
);
return PhysicalInfo;
}
}

73
src/models/Referral.ts Normal file
View File

@@ -0,0 +1,73 @@
import { DataTypes, Model, Sequelize } from "sequelize";
export class Referral extends Model {
public id!: string;
public applicantId!: string;
public firstName!: string;
public lastName!: string;
public relationship!: string; // نسبت
public acquaintanceDuration!: string; // مدت زمان آشنایی (مثلاً: ۵ سال)
public acquaintanceType!: "Direct" | "Indirect"; // نوع آشنایی: مستقیم / غیرمستقیم
public jobTitle!: string; // شغل معرف
public workplaceName!: string; // نام محل کار معرف
public phoneNumber!: string; // تلفن تماس
static initModel(sequelize: Sequelize) {
Referral.init(
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
applicantId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: "applicants", // یا هر نامی که برای جدول متقاضی دارید
key: "id",
},
},
firstName: {
type: DataTypes.STRING,
allowNull: false,
},
lastName: {
type: DataTypes.STRING,
allowNull: false,
},
relationship: {
type: DataTypes.STRING,
allowNull: false,
},
acquaintanceDuration: {
type: DataTypes.STRING,
allowNull: true,
},
acquaintanceType: {
type: DataTypes.ENUM("Direct", "Indirect"),
allowNull: false,
defaultValue: "Direct",
},
jobTitle: {
type: DataTypes.STRING,
allowNull: true,
},
workplaceName: {
type: DataTypes.STRING,
allowNull: true,
},
phoneNumber: {
type: DataTypes.STRING(15),
allowNull: false,
},
},
{
sequelize,
tableName: "referrals",
timestamps: true,
},
);
return Referral;
}
}

53
src/models/Relation.ts Normal file
View File

@@ -0,0 +1,53 @@
import { DataTypes, Model, Sequelize } from "sequelize";
export class Relation extends Model {
static initModel(sequelize: Sequelize) {
Relation.init(
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
applicantId: {
type: DataTypes.UUID,
allowNull: false,
references: {
model: "applicants",
key: "id",
},
},
firstName: {
type: DataTypes.STRING,
allowNull: false,
},
lastName: {
type: DataTypes.STRING,
allowNull: false,
},
relationship: {
type: DataTypes.STRING,
allowNull: false,
},
jobTitle: {
type: DataTypes.STRING,
allowNull: true,
},
workplaceName: {
type: DataTypes.STRING,
allowNull: true,
},
phoneNumber: {
type: DataTypes.STRING(15),
allowNull: false,
},
order: {
type: DataTypes.INTEGER,
allowNull: false, // عدد 1 برای آشنای اول و 2 برای آشنای دوم
},
},
{ sequelize, tableName: "relations", timestamps: true },
);
return Relation;
}
}

56
src/models/Role.ts Normal file
View File

@@ -0,0 +1,56 @@
// src/models/role.model.ts
import {
Model,
DataTypes,
Optional,
Sequelize
} from "sequelize";
interface RoleAttributes {
id: string;
name: string;
description?: string;
createdAt?: Date;
updatedAt?: Date;
}
type RoleCreationAttributes = Optional<RoleAttributes, "id">;
export class Role extends Model<RoleAttributes, RoleCreationAttributes>
implements RoleAttributes {
public id!: string;
public name!: string;
public description?: string;
public readonly createdAt!: Date;
public readonly updatedAt!: Date;
static initModel(sequelize: Sequelize): typeof Role {
Role.init(
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
name: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
},
description: {
type: DataTypes.STRING,
allowNull: true,
},
},
{
sequelize,
tableName: "roles",
timestamps:true,
}
);
return Role;
}
}

72
src/models/User.ts Normal file
View File

@@ -0,0 +1,72 @@
// src/models/user.model.ts
import { Model, DataTypes, Optional, Sequelize } from "sequelize";
import { Role } from "./Role";
interface UserAttributes {
id: string;
fullname: string;
email: string;
password: string;
roleId: string;
isActive: boolean;
createdAt?: Date;
updatedAt?: Date;
}
type UserCreationAttributes = Optional<UserAttributes, "id" | "isActive">;
export class User
extends Model<UserAttributes, UserCreationAttributes>
implements UserAttributes
{
public id!: string;
public fullname!: string;
public email!: string;
public password!: string;
public roleId!: string;
public isActive!: boolean;
public role?: Role;
public readonly createdAt!: Date;
public readonly updatedAt!: Date;
static initModel(sequelize: Sequelize): typeof User {
User.init(
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
fullname: {
type: DataTypes.STRING,
allowNull: false,
},
email: {
type: DataTypes.STRING,
allowNull: false,
unique: true,
},
password: {
type: DataTypes.STRING,
allowNull: false,
},
roleId: {
type: DataTypes.UUID,
allowNull: false,
},
isActive: {
type: DataTypes.BOOLEAN,
defaultValue: true,
},
},
{
sequelize,
tableName: "users",
timestamps: true,
},
);
return User;
}
}

View File

@@ -0,0 +1,25 @@
import { DataTypes, Model, Sequelize } from "sequelize";
export class WorkExperience extends Model {
static initModel(sequelize: Sequelize) {
WorkExperience.init(
{
id: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
primaryKey: true,
},
applicantId: { type: DataTypes.UUID, allowNull: false },
hasNoExperience: { type: DataTypes.BOOLEAN, defaultValue: false },
companyName: { type: DataTypes.STRING },
lastPosition: { type: DataTypes.STRING },
startYear: { type: DataTypes.INTEGER },
endYear: { type: DataTypes.INTEGER },
leavingReason: { type: DataTypes.STRING },
description: { type: DataTypes.TEXT },
},
{ sequelize, tableName: "work_experiences", timestamps: true },
);
return WorkExperience;
}
}

228
src/models/index.ts Normal file
View File

@@ -0,0 +1,228 @@
import { Sequelize } from "sequelize";
import { Applicant } from "./Applicant";
import { Identity } from "./Identity";
import { PersonalInfo } from "./PersonalInfo";
import { PhysicalInfo } from "./PhysicalInfo";
import { Education } from "./Education";
import { Course } from "./Course";
import { ComputerSkill } from "./ComputerSkill";
import { LanguageSkill } from "./LanguageSkill";
import { WorkExperience } from "./WorkExperience";
import { Relation } from "./Relation";
import { File } from "./File";
import { Role } from "./Role";
import { User } from "./User";
import { Permission } from "./Permission";
import { Referral } from "./Referral";
import { JobInfo } from "./JobInfo";
import { JobRequest } from "./JobRequest";
import { Center } from "./Center";
import { JobCategory } from "./JobCategory";
// تنظیمات اتصال به دیتابیس
const sequelize = new Sequelize("employee_form", "postgres", "root", {
host: "127.0.0.1",
dialect: "postgres",
logging: false,
});
// ۱. مقداردهی اولیه مدل‌ها (Initialization)
const models = {
// استخدامی
Applicant: Applicant.initModel(sequelize),
Center: Center.initModel(sequelize),
Identity: Identity.initModel(sequelize),
PersonalInfo: PersonalInfo.initModel(sequelize),
PhysicalInfo: PhysicalInfo.initModel(sequelize),
Education: Education.initModel(sequelize),
Course: Course.initModel(sequelize),
ComputerSkill: ComputerSkill.initModel(sequelize),
LanguageSkill: LanguageSkill.initModel(sequelize),
WorkExperience: WorkExperience.initModel(sequelize),
JobInfo: JobInfo.initModel(sequelize),
Relation: Relation.initModel(sequelize),
File: File.initModel(sequelize),
User: User.initModel(sequelize),
Role: Role.initModel(sequelize),
Permission: Permission.initModel(sequelize),
Referral: Referral.initModel(sequelize),
JobCategory: JobCategory.initModel(sequelize),
JobRequest: JobRequest.initModel(sequelize),
};
// Role.hasMany(User, { foreignKey: "roleId" });
// User.belongsTo(Role, { foreignKey: "roleId" });
// Role.belongsToMany(Permission, {
// through: "role_permissions",
// foreignKey: "roleId",
// });
// Permission.belongsToMany(Role, {
// through: "role_permissions",
// foreignKey: "permissionId",
// });
User.belongsTo(Role, {
foreignKey: "roleId",
as: "role",
});
Role.hasMany(User, {
foreignKey: "roleId",
as: "users",
});
Role.belongsToMany(Permission, {
through: "role_permissions",
foreignKey: "roleId",
otherKey: "permissionId",
as: "permissions",
});
Permission.belongsToMany(Role, {
through: "role_permissions",
foreignKey: "permissionId",
otherKey: "roleId",
as: "roles",
});
Applicant.hasOne(Identity, {
foreignKey: "applicantId",
as: "identity",
onDelete: "CASCADE",
});
Identity.belongsTo(Applicant, {
foreignKey: "applicantId",
as: "applicant",
});
Applicant.hasOne(PersonalInfo, {
foreignKey: "applicantId",
as: "personalInfo",
onDelete: "CASCADE",
});
PersonalInfo.belongsTo(Applicant, {
foreignKey: "applicantId",
as: "applicant",
});
Applicant.hasOne(PhysicalInfo, {
foreignKey: "applicantId",
as: "physicalInfo",
onDelete: "CASCADE",
});
PhysicalInfo.belongsTo(Applicant, {
foreignKey: "applicantId",
as: "applicant",
});
Applicant.hasMany(Education, {
foreignKey: "applicantId",
as: "educations",
onDelete: "CASCADE",
});
Education.belongsTo(Applicant, {
foreignKey: "applicantId",
as: "applicant",
});
Applicant.hasMany(Course, {
foreignKey: "applicantId",
as: "courses",
onDelete: "CASCADE",
});
Course.belongsTo(Applicant, {
foreignKey: "applicantId",
as: "applicant",
});
Applicant.hasMany(ComputerSkill, {
foreignKey: "applicantId",
as: "computerSkills",
onDelete: "CASCADE",
});
ComputerSkill.belongsTo(Applicant, {
foreignKey: "applicantId",
as: "applicant",
});
Applicant.hasMany(LanguageSkill, {
foreignKey: "applicantId",
as: "languageSkills",
onDelete: "CASCADE",
});
LanguageSkill.belongsTo(Applicant, {
foreignKey: "applicantId",
as: "applicant",
});
Applicant.hasMany(WorkExperience, {
foreignKey: "applicantId",
as: "workExperiences",
onDelete: "CASCADE",
});
WorkExperience.belongsTo(Applicant, {
foreignKey: "applicantId",
as: "applicant",
});
Applicant.hasOne(JobInfo, {
foreignKey: "applicantId",
as: "jobInfo",
onDelete: "CASCADE",
});
JobInfo.belongsTo(Applicant, {
foreignKey: "applicantId",
as: "applicant",
});
Applicant.hasMany(Relation, {
foreignKey: "applicantId",
as: "relations",
onDelete: "CASCADE",
});
Relation.belongsTo(Applicant, {
foreignKey: "applicantId",
as: "applicant",
});
Applicant.hasMany(File, {
foreignKey: "applicantId",
as: "files",
onDelete: "CASCADE",
});
File.belongsTo(Applicant, {
foreignKey: "applicantId",
as: "applicant",
});
Applicant.hasMany(Referral, { foreignKey: "applicantId", as: "referrals" });
Referral.belongsTo(Applicant, { foreignKey: "applicantId" });
Applicant.hasOne(JobInfo, {
foreignKey: "applicantId",
as: "workStatus",
});
JobInfo.belongsTo(Applicant, { foreignKey: "applicantId" });
// مثال رابطه در فایل اصلی مدل‌ها
JobRequest.belongsTo(JobCategory, { foreignKey: "categoryId", as: "category" });
// در فایل تعریف روابط:
Center.hasMany(JobCategory, { foreignKey: "centerId", as: "categories" });
JobCategory.belongsTo(Center, { foreignKey: "centerId" });
JobCategory.hasMany(JobRequest, { foreignKey: "categoryId" });
Applicant.hasMany(JobRequest, { foreignKey: "applicantId", as: "jobRequests" });
JobRequest.belongsTo(Applicant, { foreignKey: "applicantId", as: "applicant" });
Center.hasMany(JobRequest, { foreignKey: "centerId", as: "applications" });
JobRequest.belongsTo(Center, { foreignKey: "centerId", as: "center" });
// ۲. برقراری روابط (Associations)
// این حلقه متد associate را در هر کلاسی که تعریف شده باشد فراخوانی می‌کند
Object.values(models).forEach((model: any) => {
if (typeof model.associate === "function") {
model.associate(models);
}
});
export { sequelize };
export default models;

View File

@@ -0,0 +1,74 @@
import { NextFunction } from "express";
import { Controller } from "../../../core/controller/main.controller";
import { ServerResponse } from "../../../core/types";
import AuthService from "../service/auth.service";
import { TOKEN_NAME } from "../../../core/constant";
import { GlobalErrorMessages } from "../../../core/messages/errors";
class AuthControllerClass extends Controller {
#service;
constructor() {
super();
this.#service = AuthService;
}
// login function for users that controlling this form
async userLogin(req: any, res: ServerResponse, next: NextFunction) {
try {
const data = await this.#service.usersLogin(
req?.body?.email,
req?.body?.password,
);
res.cookie(TOKEN_NAME, data.token, {
httpOnly: true,
secure: false,
sameSite: "lax",
maxAge: 24 * 60 * 60 * 1000,
});
return res.status(200).json({
status: 200,
data,
message: "با موفقيت وارد شديد",
});
} catch (error) {
next(error);
}
}
async applicantLogin(req: any, res: ServerResponse, next: NextFunction) {
try {
const data = await this.#service.applicantLogin(
req?.body?.nationalCode,
);
res.cookie(TOKEN_NAME, data.token, {
httpOnly: true,
secure: false,
sameSite: "lax",
maxAge: 24 * 60 * 60 * 1000,
});
return res.status(200).json({
status: 200,
data,
message: "با موفقيت وارد شديد",
});
} catch (error) {
next(error);
}
}
async userLogout(req: any, res: ServerResponse, next: NextFunction) {
try {
res.clearCookie(TOKEN_NAME);
return res.status(200).json({
status: 200,
data: {},
message: "Ok",
});
} catch (error) {
next(GlobalErrorMessages.server.internal);
}
}
}
const AuthController = new AuthControllerClass();
export default AuthController;

View File

@@ -0,0 +1,14 @@
export const authErrorMessages = Object.freeze({
notFound:{
user:"كاربر يافت نشد",
applicant:"متقاضي يافت نشد"
},
dosentMatch :{
email:"ايميل اشتباه است",
password:"پسورد اشتباه است",
},
login:{
invalidData:'ايميل و يا رمز عبور اشتباه است'
},
logout:'خروج از حساب با خطا مواجه شده است'
})

View File

@@ -0,0 +1,13 @@
import { Router } from "express";
import AuthController from "../controller/auth.controller";
const AuthRouter = Router();
// authentication applicants
AuthRouter.post("/applicant/login", AuthController.applicantLogin);
// AuthRouter.post('/applicant/logout',AuthController.applicantLogout)
// authentication users
AuthRouter.post("/user/login", AuthController.userLogin);
AuthRouter.post("/user/logout", AuthController.userLogout);
export default AuthRouter;

View File

@@ -0,0 +1,84 @@
import createHttpError from "http-errors";
import { Controller } from "../../../core/controller/main.controller";
import { User } from "../../../models/User";
import bcrypt from "bcryptjs";
import jwt from "jsonwebtoken";
import { Role } from "../../../models/Role";
import { Applicant } from "../../../models/Applicant";
import { Identity } from "../../../models/Identity";
import { authErrorMessages } from "../messages/auth.messages";
class AuthServiceClass extends Controller {
async usersLogin(email: string, password: string) {
try {
// ۱. پیدا کردن کاربر به همراه نقش
const user = await User.findOne({
where: { email },
include: [
{
model: Role,
as: "role",
},
],
});
if (!user)
throw new createHttpError.Unauthorized("ایمیل یا رمز عبور اشتباه است.");
// ۲. چک کردن پسورد
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch)
throw new createHttpError.Unauthorized("ایمیل یا رمز عبور اشتباه است.");
// ۳. صدور توکن
const token = jwt.sign(
{ userId: user.id },
process.env.JWT_SECRET || "secret",
{ expiresIn: "24h" }, // طول عمر توکن
);
return {
token,
user: { id: user.id, fullname: user.fullname, role: user },
};
} catch (err) {
throw new createHttpError.InternalServerError("خطای سرور");
}
}
async applicantLogin(nationalCode: string) {
try {
const identity = await Identity.findOne({
where: { nationalCode },
include: [
{
model: Applicant,
as: "applicant",
},
],
});
if (!identity?.applicantId) {
throw new createHttpError.NotFound(
authErrorMessages.notFound.applicant,
);
}
const token = jwt.sign(
{ userId: identity.applicantId },
process.env.JWT_SECRET || "secret",
{ expiresIn: "24h" }, // طول عمر توکن
);
return {
token,
applicant: { id: identity.applicantId, fullname: `${identity.firstName} ${identity.lastName}`, role: identity },
};
} catch (error) {
throw new createHttpError.InternalServerError("خطای سرور");
}
}
}
const AuthService = new AuthServiceClass();
export default AuthService;

View File

View File

@@ -0,0 +1,81 @@
import { NextFunction } from "express";
import { Controller } from "../../../../core/controller/main.controller";
import { ServerResponse } from "../../../../core/types";
import CenterService from "../service/center.service";
class CenterControllerClass extends Controller {
#service;
constructor() {
super();
this.#service = CenterService;
}
async getAll(req: any, res: ServerResponse, next: NextFunction) {
try {
const centers = await this.#service.getAll();
return res.status(200).json({
status: 200,
data: centers,
message: "Ok",
});
} catch (error) {
next(error);
}
}
async getById(req: any, res: ServerResponse, next: NextFunction) {
try {
const id = req?.params?.id;
const centers = await this.#service.getById(id);
return res.status(200).json({
status: 200,
data: centers,
message: "Ok",
});
} catch (error) {
next(error);
}
}
async create(req: any, res: ServerResponse, next: NextFunction) {
try {
await this.#service.create(req.body);
return res.status(200).json({
status: 200,
data: {},
message: "ساخته شد",
});
} catch (error) {
next(error);
}
}
async update(req: any, res: ServerResponse, next: NextFunction) {
try {
const id = req?.params?.id;
await this.#service.update(id, req.body);
return res.status(200).json({
status: 200,
data: {},
message: "ويرايش شد",
});
} catch (error) {
next(error);
}
}
async delete(req: any, res: ServerResponse, next: NextFunction) {
try {
const id = req?.params?.id;
await this.#service.delete(id);
return res.status(200).json({
status: 200,
data: {},
message: "حذف شد",
});
} catch (error) {
next(error);
}
}
}
const CenterController = new CenterControllerClass();
export default CenterController;

View File

@@ -0,0 +1,12 @@
import { Router } from "express";
import CenterController from "../controller/center.controller";
const CenterRouter = Router();
CenterRouter.post("/", CenterController.create); // ایجاد
CenterRouter.get("/", CenterController.getAll); // لیست همه
CenterRouter.get("/:id", CenterController.getById); // مشاهده یکی
CenterRouter.put("/:id", CenterController.update); // ویرایش
CenterRouter.delete("/:id", CenterController.delete); // حذف
export default CenterRouter;

View File

@@ -0,0 +1,51 @@
import createHttpError from "http-errors";
import { Controller } from "../../../../core/controller/main.controller";
import { GlobalErrorMessages } from "../../../../core/messages/errors";
import { Center } from "../../../../models/Center";
import { JobCategory } from "../../../../models/JobCategory";
class CenterServiceClass extends Controller {
// ایجاد مرکز جدید
async create(data: any) {
return await Center.create(data);
}
// دریافت لیست تمام مراکز (همراه با رسته‌های شغلی مرتبط)
async getAll() {
return await Center.findAll({
include: [{ model: JobCategory, as: "categories" }],
order: [["createdAt", "DESC"]],
});
}
// دریافت یک مرکز خاص با ID
async getById(id: string) {
const center = await Center.findByPk(id, {
include: [{ model: JobCategory, as: "categories" }],
});
if (!center) throw new Error("مرکز مورد نظر یافت نشد.");
return center;
}
// بروزرسانی اطلاعات مرکز
async update(id: string, data: any) {
const center = await this.getById(id);
return await center.update(data);
}
// حذف مرکز
async delete(id: string) {
const center = await this.getById(id);
await center.destroy();
return { message: "مرکز با موفقیت حذف شد." };
}
// متد کمکی برای گرفتن مراکز فوری (Urgent)
async getUrgentCenters() {
return await Center.findAll({ where: { isUrgent: true } });
}
}
const CenterService = new CenterServiceClass();
export default CenterService;

View File

@@ -0,0 +1,61 @@
import { Controller } from "../../../../core/controller/main.controller";
import { ServerResponse } from "../../../../core/types";
import { NextFunction } from "express";
import CourseInfoService from "../service/courseForm.service";
class CourseInfoControllerClass extends Controller {
#service;
constructor() {
super();
this.#service = CourseInfoService;
}
async create(req: any, res: ServerResponse, next: NextFunction) {
try {
const course = await this.#service.create(req.body);
res.status(201).json({ status: 200, data: course });
} catch (err) {
next(err);
}
}
async getById(req: any, res: ServerResponse, next: NextFunction) {
try {
const course = await this.#service.getById(req.params.id);
res.json({ status: 200, data: course });
} catch (err) {
next(err);
}
}
async list(req: any, res: ServerResponse, next: NextFunction) {
try {
const { applicantId } = req.query as { applicantId?: string };
const courses = await this.#service.list(applicantId);
res.json({ status: 200, data: courses });
} catch (err) {
next(err);
}
}
async update(req: any, res: ServerResponse, next: NextFunction) {
try {
const updated = await this.#service.update(req.params.id, req.body);
res.json({ status: 200, data: updated });
} catch (err) {
next(err);
}
}
async delete(req: any, res: ServerResponse, next: NextFunction) {
try {
const result = await this.#service.delete(req.params.id);
res.json({ status: 200, data: result });
} catch (err) {
next(err);
}
}
}
const CourseInfoController = new CourseInfoControllerClass();
export default CourseInfoController;

View File

@@ -0,0 +1,11 @@
import { Router } from "express";
import CourseInfoController from "./controller/courseForm.controller";
const CourseInfoRouter = Router();
CourseInfoRouter.post("/create", CourseInfoController.create);
// CourseInfoRouter.get("/all", CourseInfoController.getAll);
CourseInfoRouter.get("/get/:id", CourseInfoController.getById);
CourseInfoRouter.put("/update/:id", CourseInfoController.update);
CourseInfoRouter.delete("/remove/:id", CourseInfoController.delete);
export default CourseInfoRouter;

View File

@@ -0,0 +1,49 @@
import createHttpError from "http-errors";
import { Controller } from "../../../../core/controller/main.controller";
import { Course } from "../../../../models/Course";
export interface CoursePayload {
applicantId: string;
degree: string;
field: string;
university: string;
startYear?: number | null;
endYear?: number | null;
gpa?: number | null;
description?: string | null;
certificateImageId?: string | null;
}
class CourseServiceClass extends Controller {
async create(data: any) {
return await Course.create(data);
}
async getById(id: string) {
const course = await Course.findByPk(id);
if (!course) throw new createHttpError.NotFound("دوره یافت نشد");
return course;
}
async list(applicantId?: string) {
const where = applicantId ? { applicantId } : {};
return await Course.findAll({ where });
}
async update(id: string, data: any) {
const [affectedRows] = await Course.update(data, { where: { id } });
if (affectedRows === 0)
throw new createHttpError.NotFound("دوره برای ویرایش یافت نشد");
return await Course.findByPk(id);
}
async delete(id: string) {
const deleted = await Course.destroy({ where: { id } });
if (!deleted) throw new createHttpError.NotFound("دوره برای حذف یافت نشد");
return { message: "دوره با موفقیت حذف شد" };
}
}
const CourseService = new CourseServiceClass();
export default CourseService;

View File

@@ -0,0 +1,71 @@
import { Controller } from "../../../../core/controller/main.controller";
import { ServerResponse } from "../../../../core/types";
import { NextFunction } from "express";
import EducationInfoService from "../service/educationForm.service";
class EducationInfoControllerClass extends Controller {
#service;
constructor() {
super();
this.#service = EducationInfoService;
}
// POST /educations
async create(req: any, res: ServerResponse, next: NextFunction) {
try {
const result = await this.#service.create(req.body);
res.status(201).json({ status: 201, data: result });
} catch (err) {
next(err);
}
}
// GET /educations/:id
async getById(req: any, res: ServerResponse, next: NextFunction) {
try {
const { id } = req.params;
const result = await this.#service.getById(id);
res.json({ status: 201, data: result });
} catch (err) {
next(err);
}
}
// GET /educations?applicantId=...
async list(req: any, res: ServerResponse, next: NextFunction) {
try {
const { applicantId } = req.query;
const result = await this.#service.list({
applicantId: applicantId as string | undefined,
});
res.json({ status: 201, data: result });
} catch (err) {
next(err);
}
}
// PUT /educations/:id
async update(req: any, res: ServerResponse, next: NextFunction) {
try {
const { id } = req.params;
const result = await this.#service.update(id, req.body);
res.json({ status: 201, data: result });
} catch (err) {
next(err);
}
}
// DELETE /educations/:id
async delete(req: any, res: ServerResponse, next: NextFunction) {
try {
const { id } = req.params;
const result = await this.#service.delete(id);
res.json({ status: 201, data: result });
} catch (err) {
next(err);
}
}
}
const EducationInfoController = new EducationInfoControllerClass();
export default EducationInfoController;

View File

@@ -0,0 +1,11 @@
import { Router } from "express";
import EducationInfoController from "./controller/educationForm.controller";
const EducationInfoRouter = Router();
EducationInfoRouter.post("/create", EducationInfoController.create);
// EducationInfoRouter.get("/all", EducationInfoController.getAll);
EducationInfoRouter.get("/get/:id", EducationInfoController.getById);
EducationInfoRouter.put("/update/:id", EducationInfoController.update);
EducationInfoRouter.delete("/remove/:id", EducationInfoController.delete);
export default EducationInfoRouter;

View File

@@ -0,0 +1,120 @@
import createHttpError from "http-errors";
import { Controller } from "../../../../core/controller/main.controller";
import { Education } from "../../../../models/Education";
export interface EducationPayload {
applicantId: string;
degree: string;
field: string;
university: string;
startYear?: number | null;
endYear?: number | null;
gpa?: number | null;
description?: string | null;
certificateImageId?: string | null;
}
class EducationServiceClass extends Controller {
// ایجاد رکورد جدید
async create(data: EducationPayload) {
try {
const education = await Education.create({
applicantId: data.applicantId,
degree: data.degree,
field: data.field,
university: data.university,
startYear: data.startYear ?? undefined,
endYear: data.endYear ?? undefined,
gpa: data.gpa ?? undefined,
description: data.description ?? "",
certificateImageId: data.certificateImageId ?? undefined,
});
return education;
} catch (err) {
throw new createHttpError.InternalServerError(
"خطا در ایجاد سابقه تحصیلی",
);
}
}
// گرفتن یک رکورد با id
async getById(id: string) {
const education = await Education.findByPk(id);
if (!education) {
throw new createHttpError.NotFound("سابقه تحصیلی پیدا نشد");
}
return education;
}
// لیست سوابق، مثلا بر اساس applicantId
async list(filter?: { applicantId?: string }) {
const where: any = {};
if (filter?.applicantId) {
where.applicantId = filter.applicantId;
}
const list = await Education.findAll({ where });
return list;
}
// بروزرسانی
async update(id: string, data: Partial<EducationPayload>) {
const education = await Education.findByPk(id);
if (!education) {
throw new createHttpError.NotFound("سابقه تحصیلی پیدا نشد");
}
try {
await education.update({
applicantId: data.applicantId ?? education.applicantId,
degree: data.degree ?? education.degree,
field: data.field ?? education.field,
university: data.university ?? education.university,
startYear:
typeof data.startYear === "number"
? data.startYear
: education.startYear,
endYear:
typeof data.endYear === "number" ? data.endYear : education.endYear,
gpa: typeof data.gpa === "number" ? data.gpa : education.gpa,
description: data.description ?? education.description,
certificateImageId:
data.certificateImageId ?? education.certificateImageId,
});
return education;
} catch (err) {
throw new createHttpError.InternalServerError(
"خطا در بروزرسانی سابقه تحصیلی",
);
}
}
// حذف
async delete(id: string) {
const education = await Education.findByPk(id);
if (!education) {
throw new createHttpError.NotFound("سابقه تحصیلی پیدا نشد");
}
try {
await education.destroy();
return { success: true };
} catch (err) {
throw new createHttpError.InternalServerError(
"خطا در حذف سابقه تحصیلی",
);
}
}
}
const EducationService = new EducationServiceClass();
export default EducationService;

View File

@@ -0,0 +1,61 @@
import { NextFunction } from "express";
import { Controller } from "../../../../core/controller/main.controller";
import { ServerResponse } from "../../../../core/types";
import IdentityFormService from "../service/identityForm.service";
class IdentityFormControllerClass extends Controller {
#service;
constructor() {
super();
this.#service = IdentityFormService;
}
async getById(req: any, res: ServerResponse, next: NextFunction) {
try {
const id = req?.params?.id;
const data = await this.#service.getById(id);
return res.status(200).json({
status: 200,
data,
message: "Ok",
});
} catch (error) {
next(error);
}
}
// create and import identity form (step 1 in system)
async create(req: any, res: ServerResponse, next: NextFunction) {
try {
await this.#service.create(req.body);
return res.status(200).json({
status: 200,
data: {},
message: "ثبت شد",
});
} catch (error) {
next(error);
}
}
// update identity form
async update(req: any, res: ServerResponse, next: NextFunction) {
try {
const id = req?.params?.id;
await this.#service.update(id, req.body);
return res.status(200).json({
status: 200,
data: {},
message: "ويرايش شد",
});
} catch (error) {
next(error);
}
}
}
const IdentityFormController = new IdentityFormControllerClass();
export default IdentityFormController;

View File

@@ -0,0 +1,11 @@
import { Router } from "express";
import IdentityFormController from "./controller/identityForm.controller";
const identityRouter = Router();
identityRouter.post("/create", IdentityFormController.create);
// identityRouter.get("/all", IdentityFormController.getAll);
identityRouter.get("/get/:id", IdentityFormController.getById);
identityRouter.put("/update/:id", IdentityFormController.update);
// identityRouter.delete("/remove/:id", IdentityFormController.remove);
export default identityRouter;

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,86 @@
import createHttpError from "http-errors";
import { Controller } from "../../../../core/controller/main.controller";
import { GlobalErrorMessages } from "../../../../core/messages/errors";
import { Identity } from "../../../../models/Identity";
import { Applicant } from "../../../../models/Applicant";
import { UUIDV4 } from "sequelize";
import { Center } from "../../../../models/Center";
class IdentityFormServiceClass extends Controller {
async getById(id: string) {
try {
const data = await Identity.findOne({ where: { id } });
return data;
} catch (error) {
throw new createHttpError.InternalServerError(
GlobalErrorMessages.server.internal,
);
}
}
async create(data: any) {
let applicantId = null;
try {
applicantId = await Applicant.create({
id: UUIDV4().toString({}),
});
} catch (error) {
throw new createHttpError.InternalServerError(
GlobalErrorMessages.server.internal,
);
}
try {
await Identity.create({
applicantId: applicantId.id,
birthDate: data.birthDate,
birthPlace: data.birthPlace,
fatherName: data.fatherName,
firstName: data.firstName,
nationalCode: data.nationalCode,
gender: data.gender,
lastName: data.lastName,
nationality: data.nationality,
religion: data.religion,
profilePhotoId: data.profilePhotoId,
});
} catch (error) {
throw new createHttpError.InternalServerError(
GlobalErrorMessages.server.internal,
);
}
}
async update(formID: string, data: any) {
const [affectedRows] = await Identity.update(
{
birthDate: data.birthDate,
birthPlace: data.birthPlace,
fatherName: data.fatherName,
firstName: data.firstName,
nationalCode: data.nationalCode,
gender: data.gender,
lastName: data.lastName,
nationality: data.nationality,
religion: data.religion,
profilePhotoId: data.profilePhotoId,
},
{
where: {
id: formID, // اعمال شرط برای آپدیت فقط همین فرم خاص
},
},
);
// بررسی اینکه آیا ردیفی پیدا و آپدیت شده است یا خیر
if (affectedRows === 0) {
throw new createHttpError.NotFound("فرم پیدا نشد");
}
return true;
}
}
const IdentityFormService = new IdentityFormServiceClass();
export default IdentityFormService;

View File

@@ -0,0 +1,16 @@
import { Router } from "express";
import PersonalInfoRouter from "./personalInfo/index.routes";
import identityRouter from "./identity/index.routes";
import PhysicalInfoRouter from "./physicalInfo/index.routes";
import EducationInfoRouter from "./education/index.routes";
import CourseInfoRouter from "./course/index.routes";
const formRouter = Router();
formRouter.use("/identity", identityRouter);
formRouter.use("/personal-info", PersonalInfoRouter);
formRouter.use("/physical-info", PhysicalInfoRouter);
formRouter.use("/education", EducationInfoRouter);
formRouter.use("/course", CourseInfoRouter);
export default formRouter;

View File

@@ -0,0 +1,79 @@
import { Controller } from "../../../../core/controller/main.controller";
import { ServerResponse } from "../../../../core/types";
import { NextFunction } from "express";
import PersonalInfoService from "../service/personalInfoForm.service";
class PersonalInfoControllerClass extends Controller {
#service;
constructor() {
super();
this.#service = PersonalInfoService;
}
// POST /personal-infos
async create(req: any, res: ServerResponse, next: NextFunction) {
try {
const result = await this.#service.create(req.body);
res.status(201).json({ status: 201, data: result });
} catch (error) {
next(error);
}
}
// GET /personal-infos/:id
async getById(req: any, res: ServerResponse, next: NextFunction) {
try {
const { id } = req.params;
const result = await this.#service.findById(id);
res.status(200).json({ status: 200, data: result });
} catch (error) {
next(error);
}
}
// GET /personal-infos
async getAll(req: any, res: ServerResponse, next: NextFunction) {
try {
const result = await this.#service.findAll();
res.status(200).json({ status: 200, data: result });
} catch (error) {
next(error);
}
}
// GET /personal-infos/applicant/:applicantId
async getByApplicantId(req: any, res: ServerResponse, next: NextFunction) {
try {
const { applicantId } = req.params;
const result = await this.#service.findByApplicantId(applicantId);
res.status(200).json({ status: 200, data: result });
} catch (error) {
next(error);
}
}
// PUT /personal-infos/:id
async update(req: any, res: ServerResponse, next: NextFunction) {
try {
const { id } = req.params;
const result = await this.#service.update(id, req.body);
res.status(200).json({ status: 200, data: result });
} catch (error) {
next(error);
}
}
// DELETE /personal-infos/:id
async remove(req: any, res: ServerResponse, next: NextFunction) {
try {
const { id } = req.params;
const result = await this.#service.delete(id);
res.status(200).json({ status: 200, data: result });
} catch (error) {
next(error);
}
}
}
const PersonalInfoController = new PersonalInfoControllerClass();
export default PersonalInfoController;

View File

@@ -0,0 +1,11 @@
import { Router } from "express";
import PersonalInfoController from "./controller/personalInfoForm.controller";
const PersonalInfoRouter = Router();
PersonalInfoRouter.post("/create", PersonalInfoController.create);
PersonalInfoRouter.get("/all", PersonalInfoController.getAll);
PersonalInfoRouter.get("/get/:id", PersonalInfoController.getById);
PersonalInfoRouter.put("/update/:id", PersonalInfoController.update);
PersonalInfoRouter.delete("/remove/:id", PersonalInfoController.remove);
export default PersonalInfoRouter;

View File

@@ -0,0 +1,119 @@
import createHttpError from "http-errors";
import { Controller } from "../../../../core/controller/main.controller";
import { PersonalInfo } from "../../../../models/PersonalInfo";
class PersonalInfoServiceClass extends Controller {
async create(data: any) {
try {
const record = await PersonalInfo.create({
applicantId: data.applicantId,
maritalStatus: data.maritalStatus,
militaryStatus: data.militaryStatus,
fatherEducation: data.fatherEducation,
fatherJob: data.fatherJob,
motherEducation: data.motherEducation,
motherJob: data.motherJob,
housingStatus: data.housingStatus,
city: data.city,
address: data.address,
homePhone: data.homePhone,
mobilePhone: data.mobilePhone,
emergencyPhone: data.emergencyPhone,
email: data.email,
residenceDuration: data.residenceDuration,
isVeteran: data.isVeteran,
hasCriminalRecord: data.hasCriminalRecord,
criminalDescription: data.criminalDescription,
});
return record;
} catch (error: any) {
throw new createHttpError.InternalServerError(
error.message || "خطا در ایجاد اطلاعات شخصی",
);
}
}
async findById(id: string) {
const record = await PersonalInfo.findByPk(id);
if (!record) {
throw new createHttpError.NotFound("اطلاعات شخصی پیدا نشد");
}
return record;
}
// اگر لازم داری همه رکوردها رو بگیری
async findAll() {
const records = await PersonalInfo.findAll();
return records;
}
// مثلا گرفتن بر اساس applicantId
async findByApplicantId(applicantId: string) {
const record = await PersonalInfo.findOne({
where: { applicantId },
});
if (!record) {
throw new createHttpError.NotFound(
"اطلاعات شخصی برای این متقاضی پیدا نشد",
);
}
return record;
}
async update(id: string, data: any) {
const [affectedRows] = await PersonalInfo.update(
{
applicantId: data.applicantId,
maritalStatus: data.maritalStatus,
militaryStatus: data.militaryStatus,
fatherEducation: data.fatherEducation,
fatherJob: data.fatherJob,
motherEducation: data.motherEducation,
motherJob: data.motherJob,
housingStatus: data.housingStatus,
city: data.city,
address: data.address,
homePhone: data.homePhone,
mobilePhone: data.mobilePhone,
emergencyPhone: data.emergencyPhone,
email: data.email,
residenceDuration: data.residenceDuration,
isVeteran: data.isVeteran,
hasCriminalRecord: data.hasCriminalRecord,
criminalDescription: data.criminalDescription,
},
{
where: { id },
},
);
if (affectedRows === 0) {
throw new createHttpError.NotFound("اطلاعات شخصی برای ویرایش پیدا نشد");
}
// برگردوندن رکورد آپدیت‌شده
const updated = await PersonalInfo.findByPk(id);
return updated;
}
async delete(id: string) {
const deletedCount = await PersonalInfo.destroy({
where: { id },
});
if (deletedCount === 0) {
throw new createHttpError.NotFound("اطلاعات شخصی برای حذف پیدا نشد");
}
return { success: true };
}
}
const PersonalInfoService = new PersonalInfoServiceClass();
export default PersonalInfoService;

View File

@@ -0,0 +1,51 @@
import { Controller } from "../../../../core/controller/main.controller";
import { ServerResponse } from "../../../../core/types";
import { NextFunction } from "express";
import PhysicalInfoService from "../service/physicalInfoForm.service";
class PhysicalInfoControllerClass extends Controller {
#service;
constructor() {
super();
this.#service = PhysicalInfoService;
}
async create(req: any, res: ServerResponse, next: NextFunction) {
try {
const info = await this.#service.create(req.body);
res.status(201).json({ status: 201, data: info });
} catch (error) {
next(error);
}
}
async getById(req: any, res: ServerResponse, next: NextFunction) {
try {
const info = await this.#service.getById(req.params.id);
res.json({ status: 200, data: info });
} catch (error) {
next(error);
}
}
async update(req: any, res: ServerResponse, next: NextFunction) {
try {
const updatedInfo = await this.#service.update(req.params.id, req.body);
res.json({ status: 200, data: updatedInfo });
} catch (error) {
next(error);
}
}
async delete(req: any, res: ServerResponse, next: NextFunction) {
try {
const result = await this.#service.delete(req.params.id);
res.json({ status: 200, data: result });
} catch (error) {
next(error);
}
}
}
const PhysicalInfoController = new PhysicalInfoControllerClass();
export default PhysicalInfoController;

View File

@@ -0,0 +1,11 @@
import { Router } from "express";
import PhysicalInfoController from "./controller/physicalInfoForm.controller";
const PhysicalInfoRouter = Router();
PhysicalInfoRouter.post("/create", PhysicalInfoController.create);
// PhysicalInfoRouter.get("/all", PhysicalInfoController.getAll);
PhysicalInfoRouter.get("/get/:id", PhysicalInfoController.getById);
PhysicalInfoRouter.put("/update/:id", PhysicalInfoController.update);
PhysicalInfoRouter.delete("/remove/:id", PhysicalInfoController.delete);
export default PhysicalInfoRouter;

View File

@@ -0,0 +1,43 @@
import createHttpError from "http-errors";
import { Controller } from "../../../../core/controller/main.controller";
import { PhysicalInfo } from "../../../../models/PhysicalInfo";
class PhysicalInfoServiceClass extends Controller {
async create(data: any) {
return await PhysicalInfo.create(data);
}
async getById(id: string) {
const info = await PhysicalInfo.findByPk(id);
if (!info) throw new createHttpError.NotFound("اطلاعات جسمانی یافت نشد");
return info;
}
async update(id: string, data: any) {
const [affectedRows] = await PhysicalInfo.update(data, {
where: { id },
});
if (affectedRows === 0) {
throw new createHttpError.NotFound("اطلاعات جسمانی برای ویرایش یافت نشد");
}
return await PhysicalInfo.findByPk(id);
}
async delete(id: string) {
const deleted = await PhysicalInfo.destroy({
where: { id },
});
if (!deleted) {
throw new createHttpError.NotFound("اطلاعات جسمانی برای حذف یافت نشد");
}
return { message: "با موفقیت حذف شد" };
}
}
const PhysicalInfoService = new PhysicalInfoServiceClass();
export default PhysicalInfoService;

11
tsconfig.json Normal file
View File

@@ -0,0 +1,11 @@
{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"outDir": "dist"
}
}