added modules & forms jobcaegory-jobrequest-referral-relation etc
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
node_modules
|
||||||
5
package-lock.json
generated
5
package-lock.json
generated
@@ -211,6 +211,7 @@
|
|||||||
"integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==",
|
"integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/body-parser": "*",
|
"@types/body-parser": "*",
|
||||||
"@types/express-serve-static-core": "^5.0.0",
|
"@types/express-serve-static-core": "^5.0.0",
|
||||||
@@ -269,6 +270,7 @@
|
|||||||
"resolved": "https://package-mirror.liara.ir/repository/npm/@types/node/-/node-25.9.0.tgz",
|
"resolved": "https://package-mirror.liara.ir/repository/npm/@types/node/-/node-25.9.0.tgz",
|
||||||
"integrity": "sha512-AOQwYUNolgy3VosiRqXrACUXTN8nJUtPl7FJXMqZVyxiiCLhQuG3jXKvCS1ALr+Y2OmZhzzLVlYPEqJaiqkaJQ==",
|
"integrity": "sha512-AOQwYUNolgy3VosiRqXrACUXTN8nJUtPl7FJXMqZVyxiiCLhQuG3jXKvCS1ALr+Y2OmZhzzLVlYPEqJaiqkaJQ==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": ">=7.24.0 <7.24.7"
|
"undici-types": ">=7.24.0 <7.24.7"
|
||||||
}
|
}
|
||||||
@@ -1172,6 +1174,7 @@
|
|||||||
"resolved": "https://package-mirror.liara.ir/repository/npm/express/-/express-5.2.1.tgz",
|
"resolved": "https://package-mirror.liara.ir/repository/npm/express/-/express-5.2.1.tgz",
|
||||||
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
|
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"accepts": "^2.0.0",
|
"accepts": "^2.0.0",
|
||||||
"body-parser": "^2.2.1",
|
"body-parser": "^2.2.1",
|
||||||
@@ -2245,6 +2248,7 @@
|
|||||||
"resolved": "https://package-mirror.liara.ir/repository/npm/pg/-/pg-8.20.0.tgz",
|
"resolved": "https://package-mirror.liara.ir/repository/npm/pg/-/pg-8.20.0.tgz",
|
||||||
"integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==",
|
"integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"pg-connection-string": "^2.12.0",
|
"pg-connection-string": "^2.12.0",
|
||||||
"pg-pool": "^3.13.0",
|
"pg-pool": "^3.13.0",
|
||||||
@@ -3148,6 +3152,7 @@
|
|||||||
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
|
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
"peer": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
"tsserver": "bin/tsserver"
|
"tsserver": "bin/tsserver"
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { Router } from 'express';
|
import { Router } from 'express';
|
||||||
import AuthRouter from '../../modules/auth/router/auth.routes';
|
import AuthRouter from '../../modules/auth/router/auth.routes';
|
||||||
|
import formRouter from '../../modules/forms/index.routes';
|
||||||
|
import userRouter from '../../modules/user/routes/user.routes';
|
||||||
|
|
||||||
const mainRouter = Router();
|
const mainRouter = Router();
|
||||||
|
|
||||||
|
|
||||||
// mainRouter.use('/user',userRouter)
|
|
||||||
// mainRouter.use('/applicant',applicantRouter)
|
|
||||||
mainRouter.use('/auth',AuthRouter)
|
mainRouter.use('/auth',AuthRouter)
|
||||||
// mainRouter.use('/user',userRouter)
|
mainRouter.use('/user',userRouter)
|
||||||
|
mainRouter.use('/form',formRouter)
|
||||||
export default mainRouter;
|
export default mainRouter;
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
// modules/.../computer-skill/controller/computerSkill.controller.ts
|
||||||
|
import { NextFunction } from "express";
|
||||||
|
import { Controller } from "../../../../core/controller/main.controller";
|
||||||
|
import { ServerResponse } from "../../../../core/types";
|
||||||
|
import ComputerSkillService from "../service/computerSkill.service";
|
||||||
|
|
||||||
|
class ComputerSkillControllerClass extends Controller {
|
||||||
|
#service;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.#service = ComputerSkillService;
|
||||||
|
}
|
||||||
|
|
||||||
|
// (اختیاری) برای ادمین
|
||||||
|
async getAll(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const data = await this.#service.getAll();
|
||||||
|
return res.status(200).json({ status: 200, data, message: "Ok" });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// دریافت رکورد مهارتهای کاربر جاری
|
||||||
|
async getMine(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const applicantId = req.user?.id;
|
||||||
|
const data = await this.#service.getMine(applicantId);
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
status: 200,
|
||||||
|
data,
|
||||||
|
message: "Ok",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ایجاد یا بروزرسانی رکورد مهارتهای کاربر جاری
|
||||||
|
async upsertMine(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const applicantId = req.user?.id;
|
||||||
|
|
||||||
|
const data = await this.#service.upsertMine(applicantId, req.body);
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
status: 200,
|
||||||
|
data,
|
||||||
|
message: "ذخیره شد",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// حذف رکورد مهارتهای کاربر جاری
|
||||||
|
async removeMine(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const applicantId = req.user?.id;
|
||||||
|
await this.#service.removeMine(applicantId);
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
status: 200,
|
||||||
|
data: {},
|
||||||
|
message: "حذف شد",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ComputerSkillController = new ComputerSkillControllerClass();
|
||||||
|
export default ComputerSkillController;
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// modules/.../computer-skill/routes/computerSkill.routes.ts
|
||||||
|
import { Router } from "express";
|
||||||
|
import ComputerSkillController from "../controller/computerSkill.controller";
|
||||||
|
|
||||||
|
const computerSkillRouter = Router();
|
||||||
|
|
||||||
|
// پیشنهاد: این روتها پشت authMiddleware باشند
|
||||||
|
computerSkillRouter.get("/me", ComputerSkillController.getMine.bind(ComputerSkillController));
|
||||||
|
computerSkillRouter.put("/me", ComputerSkillController.upsertMine.bind(ComputerSkillController));
|
||||||
|
computerSkillRouter.delete("/me", ComputerSkillController.removeMine.bind(ComputerSkillController));
|
||||||
|
|
||||||
|
// (اختیاری) فقط برای ادمین:
|
||||||
|
// computerSkillRouter.get("/", ComputerSkillController.getAll.bind(ComputerSkillController));
|
||||||
|
|
||||||
|
export default computerSkillRouter;
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
// modules/.../computer-skill/service/computerSkill.service.ts
|
||||||
|
|
||||||
|
import { ComputerSkill } from "../../../../models/ComputerSkill";
|
||||||
|
|
||||||
|
const SKILL_LEVELS = [
|
||||||
|
"NONE",
|
||||||
|
"VERY_WEAK",
|
||||||
|
"WEAK",
|
||||||
|
"AVERAGE",
|
||||||
|
"GOOD",
|
||||||
|
"VERY_GOOD",
|
||||||
|
"EXCELLENT",
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
type SkillLevel = (typeof SKILL_LEVELS)[number];
|
||||||
|
|
||||||
|
type UpsertComputerSkillDTO = Partial<{
|
||||||
|
pcUsage: SkillLevel;
|
||||||
|
word: SkillLevel;
|
||||||
|
excel: SkillLevel;
|
||||||
|
powerPoint: SkillLevel;
|
||||||
|
rahkaran: SkillLevel;
|
||||||
|
kasra: SkillLevel;
|
||||||
|
didgah: SkillLevel;
|
||||||
|
his: SkillLevel;
|
||||||
|
otherSoftware: string | null;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
class ComputerSkillServiceClass {
|
||||||
|
async getAll() {
|
||||||
|
// فقط برای ادمین/اپراتور (اگر نیاز داری)
|
||||||
|
return await ComputerSkill.findAll({order: [["createdAt", "DESC"]]});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getMine(applicantId: string) {
|
||||||
|
const record = await ComputerSkill.findOne({where: {applicantId}});
|
||||||
|
// میتونی null برگردونی یا خطا بدی؛ من null برمیگردونم تا فرانت بدونه هنوز پر نشده
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
async upsertMine(applicantId: string, data: UpsertComputerSkillDTO) {
|
||||||
|
const existing = await ComputerSkill.findOne({where: {applicantId}});
|
||||||
|
|
||||||
|
if (!existing) {
|
||||||
|
return await ComputerSkill.create({
|
||||||
|
applicantId,
|
||||||
|
...data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await existing.update(data);
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeMine(applicantId: string) {
|
||||||
|
const existing = await ComputerSkill.findOne({where: {applicantId}});
|
||||||
|
if (!existing) {
|
||||||
|
// اگر بخوای idempotent باشه، میتونی true برگردونی
|
||||||
|
throw new Error("رکورد مهارتهای کامپیوتری یافت نشد");
|
||||||
|
}
|
||||||
|
await existing.destroy();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const ComputerSkillService = new ComputerSkillServiceClass();
|
||||||
|
export default ComputerSkillService;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Controller } from "../../../../core/controller/main.controller";
|
import {Controller} from "../../../../core/controller/main.controller";
|
||||||
import { ServerResponse } from "../../../../core/types";
|
import {ServerResponse} from "../../../../core/types";
|
||||||
import { NextFunction } from "express";
|
import {NextFunction} from "express";
|
||||||
import CourseInfoService from "../service/courseForm.service";
|
import CourseInfoService from "../service/courseForm.service";
|
||||||
|
|
||||||
class CourseInfoControllerClass extends Controller {
|
class CourseInfoControllerClass extends Controller {
|
||||||
@@ -12,7 +12,7 @@ class CourseInfoControllerClass extends Controller {
|
|||||||
async create(req: any, res: ServerResponse, next: NextFunction) {
|
async create(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
try {
|
try {
|
||||||
const course = await this.#service.create(req.body);
|
const course = await this.#service.create(req.body);
|
||||||
res.status(201).json({ status: 200, data: course });
|
res.status(201).json({status: 200, data: course});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
next(err);
|
next(err);
|
||||||
}
|
}
|
||||||
@@ -21,7 +21,7 @@ class CourseInfoControllerClass extends Controller {
|
|||||||
async getById(req: any, res: ServerResponse, next: NextFunction) {
|
async getById(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
try {
|
try {
|
||||||
const course = await this.#service.getById(req.params.id);
|
const course = await this.#service.getById(req.params.id);
|
||||||
res.json({ status: 200, data: course });
|
res.json({status: 200, data: course});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
next(err);
|
next(err);
|
||||||
}
|
}
|
||||||
@@ -29,9 +29,9 @@ class CourseInfoControllerClass extends Controller {
|
|||||||
|
|
||||||
async list(req: any, res: ServerResponse, next: NextFunction) {
|
async list(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
try {
|
try {
|
||||||
const { applicantId } = req.query as { applicantId?: string };
|
const {applicantId} = req.query as {applicantId?: string};
|
||||||
const courses = await this.#service.list(applicantId);
|
const courses = await this.#service.list(applicantId);
|
||||||
res.json({ status: 200, data: courses });
|
res.json({status: 200, data: courses});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
next(err);
|
next(err);
|
||||||
}
|
}
|
||||||
@@ -40,7 +40,7 @@ class CourseInfoControllerClass extends Controller {
|
|||||||
async update(req: any, res: ServerResponse, next: NextFunction) {
|
async update(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
try {
|
try {
|
||||||
const updated = await this.#service.update(req.params.id, req.body);
|
const updated = await this.#service.update(req.params.id, req.body);
|
||||||
res.json({ status: 200, data: updated });
|
res.json({status: 200, data: updated});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
next(err);
|
next(err);
|
||||||
}
|
}
|
||||||
@@ -49,11 +49,35 @@ class CourseInfoControllerClass extends Controller {
|
|||||||
async delete(req: any, res: ServerResponse, next: NextFunction) {
|
async delete(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
try {
|
try {
|
||||||
const result = await this.#service.delete(req.params.id);
|
const result = await this.#service.delete(req.params.id);
|
||||||
res.json({ status: 200, data: result });
|
res.json({status: 200, data: result});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
next(err);
|
next(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async bulkSave(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const applicantId = req.user.id; // دریافت از میدلور احراز هویت
|
||||||
|
const {items} = req.body;
|
||||||
|
|
||||||
|
if (!Array.isArray(items)) {
|
||||||
|
return res.status(400).json({
|
||||||
|
status: 400,
|
||||||
|
data: {},
|
||||||
|
message: "فیلد items الزامی بوده و باید به صورت آرایه باشد.",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const courses = await this.#service.bulkSync(applicantId, items);
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
status: 200,
|
||||||
|
message: "دورههای آموزشی با موفقیت ذخیره شدند",
|
||||||
|
data: courses,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const CourseInfoController = new CourseInfoControllerClass();
|
const CourseInfoController = new CourseInfoControllerClass();
|
||||||
|
|||||||
@@ -1,17 +1,14 @@
|
|||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import { Controller } from "../../../../core/controller/main.controller";
|
import {Controller} from "../../../../core/controller/main.controller";
|
||||||
import { Course } from "../../../../models/Course";
|
import {Course} from "../../../../models/Course";
|
||||||
|
import { sequelize } from "../../../../models";
|
||||||
|
|
||||||
export interface CoursePayload {
|
export interface CoursePayload {
|
||||||
applicantId: string;
|
title: string;
|
||||||
degree: string;
|
institution: string;
|
||||||
field: string;
|
year: number;
|
||||||
university: string;
|
duration: string;
|
||||||
startYear?: number | null;
|
description?: string;
|
||||||
endYear?: number | null;
|
|
||||||
gpa?: number | null;
|
|
||||||
description?: string | null;
|
|
||||||
certificateImageId?: string | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class CourseServiceClass extends Controller {
|
class CourseServiceClass extends Controller {
|
||||||
@@ -26,21 +23,43 @@ class CourseServiceClass extends Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async list(applicantId?: string) {
|
async list(applicantId?: string) {
|
||||||
const where = applicantId ? { applicantId } : {};
|
const where = applicantId ? {applicantId} : {};
|
||||||
return await Course.findAll({ where });
|
return await Course.findAll({where});
|
||||||
}
|
}
|
||||||
|
|
||||||
async update(id: string, data: any) {
|
async update(id: string, data: any) {
|
||||||
const [affectedRows] = await Course.update(data, { where: { id } });
|
const [affectedRows] = await Course.update(data, {where: {id}});
|
||||||
if (affectedRows === 0)
|
if (affectedRows === 0)
|
||||||
throw new createHttpError.NotFound("دوره برای ویرایش یافت نشد");
|
throw new createHttpError.NotFound("دوره برای ویرایش یافت نشد");
|
||||||
return await Course.findByPk(id);
|
return await Course.findByPk(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(id: string) {
|
async delete(id: string) {
|
||||||
const deleted = await Course.destroy({ where: { id } });
|
const deleted = await Course.destroy({where: {id}});
|
||||||
if (!deleted) throw new createHttpError.NotFound("دوره برای حذف یافت نشد");
|
if (!deleted) throw new createHttpError.NotFound("دوره برای حذف یافت نشد");
|
||||||
return { message: "دوره با موفقیت حذف شد" };
|
return {message: "دوره با موفقیت حذف شد"};
|
||||||
|
}
|
||||||
|
async bulkSync(applicantId: string, items: CoursePayload[]) {
|
||||||
|
return await sequelize.transaction(async (t) => {
|
||||||
|
// ۱. حذف تمام سوابق قبلیِ این کاربر خاص
|
||||||
|
await Course.destroy({
|
||||||
|
where: {applicantId},
|
||||||
|
transaction: t,
|
||||||
|
});
|
||||||
|
|
||||||
|
// ۲. آمادهسازی دیتا برای ثبت (بدون id تا خودکار تولید شود)
|
||||||
|
const dataToInsert = items.map((item) => ({
|
||||||
|
applicantId: applicantId, // امنیت: تحمیل آیدی کاربر احراز هویت شده
|
||||||
|
title: item.title,
|
||||||
|
institution: item.institution,
|
||||||
|
year: item.year,
|
||||||
|
duration: item.duration,
|
||||||
|
description: item.description,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// ۳. ثبت دستهجمعی
|
||||||
|
return await Course.bulkCreate(dataToInsert, {transaction: t});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,27 @@ class EducationInfoControllerClass extends Controller {
|
|||||||
next(err);
|
next(err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async bulkSave(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const applicantId = req.user.id;
|
||||||
|
const { items } = req.body;
|
||||||
|
|
||||||
|
if (!Array.isArray(items)) {
|
||||||
|
throw new Error("دادههای ارسالی باید به صورت آرایه (items) باشند");
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await this.#service.bulkSync(applicantId, items);
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
status: 200,
|
||||||
|
data,
|
||||||
|
message: "اطلاعات تحصیلی با موفقیت بروزرسانی شد",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const EducationInfoController = new EducationInfoControllerClass();
|
const EducationInfoController = new EducationInfoControllerClass();
|
||||||
|
|||||||
@@ -1,22 +1,32 @@
|
|||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import { Controller } from "../../../../core/controller/main.controller";
|
import {Controller} from "../../../../core/controller/main.controller";
|
||||||
import { Education } from "../../../../models/Education";
|
import {Education, EducationAttributes} from "../../../../models/Education";
|
||||||
|
import {sequelize} from "../../../../models";
|
||||||
|
|
||||||
export interface EducationPayload {
|
export interface EducationPayload {
|
||||||
applicantId: string;
|
applicantId: string;
|
||||||
degree: string;
|
degree: string;
|
||||||
field: string;
|
field: string;
|
||||||
university: string;
|
university: string;
|
||||||
startYear?: number | null;
|
startYear?: number;
|
||||||
endYear?: number | null;
|
endYear?: number;
|
||||||
gpa?: number | null;
|
gpa?: number;
|
||||||
description?: string | null;
|
description?: string;
|
||||||
certificateImageId?: string | null;
|
certificateImageId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
class EducationServiceClass extends Controller {
|
class EducationServiceClass extends Controller {
|
||||||
// ایجاد رکورد جدید
|
private validate(data: EducationPayload) {
|
||||||
|
if (
|
||||||
|
typeof data.startYear === "number" &&
|
||||||
|
typeof data.endYear === "number" &&
|
||||||
|
data.endYear < data.startYear
|
||||||
|
) {
|
||||||
|
throw new Error("سال پایان تحصیل نمیتواند کمتر از سال شروع باشد");
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
// ایجاد رکورد جدید
|
||||||
async create(data: EducationPayload) {
|
async create(data: EducationPayload) {
|
||||||
try {
|
try {
|
||||||
const education = await Education.create({
|
const education = await Education.create({
|
||||||
@@ -51,14 +61,14 @@ class EducationServiceClass extends Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// لیست سوابق، مثلا بر اساس applicantId
|
// لیست سوابق، مثلا بر اساس applicantId
|
||||||
async list(filter?: { applicantId?: string }) {
|
async list(filter?: {applicantId?: string}) {
|
||||||
const where: any = {};
|
const where: any = {};
|
||||||
|
|
||||||
if (filter?.applicantId) {
|
if (filter?.applicantId) {
|
||||||
where.applicantId = filter.applicantId;
|
where.applicantId = filter.applicantId;
|
||||||
}
|
}
|
||||||
|
|
||||||
const list = await Education.findAll({ where });
|
const list = await Education.findAll({where});
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,13 +116,35 @@ class EducationServiceClass extends Controller {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await education.destroy();
|
await education.destroy();
|
||||||
return { success: true };
|
return {success: true};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new createHttpError.InternalServerError(
|
throw new createHttpError.InternalServerError("خطا در حذف سابقه تحصیلی");
|
||||||
"خطا در حذف سابقه تحصیلی",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
async bulkSync(applicantId: string, items: EducationPayload[]) {
|
||||||
|
return await sequelize.transaction(async (t) => {
|
||||||
|
// ۱. حذف رکوردهای قبلی
|
||||||
|
await Education.destroy({where: {applicantId}, transaction: t});
|
||||||
|
|
||||||
|
// ۲. آمادهسازی دیتای جدید (اجبار به استفاده از applicantId صحیح)
|
||||||
|
const dataToInsert = items.map((item) => ({
|
||||||
|
// استخراج تمام فیلدها به جز applicantId احتمالی در ورودی
|
||||||
|
degree: item.degree,
|
||||||
|
field: item.field,
|
||||||
|
university: item.university,
|
||||||
|
startYear: item.startYear,
|
||||||
|
endYear: item.endYear,
|
||||||
|
gpa: item.gpa,
|
||||||
|
description: item.description,
|
||||||
|
certificateImageId: item.certificateImageId,
|
||||||
|
// اعمال آیدی احراز هویت شده
|
||||||
|
applicantId: applicantId,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// ۳. ثبت گروهی
|
||||||
|
return await Education.bulkCreate(dataToInsert, {transaction: t});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const EducationService = new EducationServiceClass();
|
const EducationService = new EducationServiceClass();
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
import { Router } from "express";
|
import {Router} from "express";
|
||||||
import PersonalInfoRouter from "./personalInfo/index.routes";
|
import PersonalInfoRouter from "./personalInfo/index.routes";
|
||||||
import identityRouter from "./identity/index.routes";
|
import identityRouter from "./identity/index.routes";
|
||||||
import PhysicalInfoRouter from "./physicalInfo/index.routes";
|
import PhysicalInfoRouter from "./physicalInfo/index.routes";
|
||||||
import EducationInfoRouter from "./education/index.routes";
|
import EducationInfoRouter from "./education/index.routes";
|
||||||
import CourseInfoRouter from "./course/index.routes";
|
import CourseInfoRouter from "./course/index.routes";
|
||||||
|
import CenterRouter from "./center/routes/center.routes";
|
||||||
|
import referralRouter from "./referral/routes/referral.routes";
|
||||||
|
import JobRequestRouter from "./jobRequest/routes/jobRequest.routes";
|
||||||
|
import computerSkillRouter from "./computerSkill/routes/computerSkill.routes";
|
||||||
|
import LanguageSkillRouter from "./languageSkill/routes/languageSkill.routes";
|
||||||
|
import workExperienceRouter from "./workExperience/routes/workExperience.routes";
|
||||||
|
|
||||||
const formRouter = Router();
|
const formRouter = Router();
|
||||||
|
|
||||||
@@ -12,5 +18,11 @@ formRouter.use("/personal-info", PersonalInfoRouter);
|
|||||||
formRouter.use("/physical-info", PhysicalInfoRouter);
|
formRouter.use("/physical-info", PhysicalInfoRouter);
|
||||||
formRouter.use("/education", EducationInfoRouter);
|
formRouter.use("/education", EducationInfoRouter);
|
||||||
formRouter.use("/course", CourseInfoRouter);
|
formRouter.use("/course", CourseInfoRouter);
|
||||||
|
formRouter.use("/center", CenterRouter);
|
||||||
|
formRouter.use("/referral", referralRouter);
|
||||||
|
formRouter.use("/job-request", JobRequestRouter);
|
||||||
|
formRouter.use("/computer-skill", computerSkillRouter);
|
||||||
|
formRouter.use("/language-skill", LanguageSkillRouter);
|
||||||
|
formRouter.use("/work-experience", workExperienceRouter);
|
||||||
|
|
||||||
export default formRouter;
|
export default formRouter;
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
// controllers/jobRequest.controller.ts
|
||||||
|
import { NextFunction } from "express";
|
||||||
|
import { Controller } from "../../../../core/controller/main.controller";
|
||||||
|
import { ServerResponse } from "../../../../core/types";
|
||||||
|
import JobRequestService from "../service/jobRequest.service";
|
||||||
|
|
||||||
|
class JobRequestControllerClass extends Controller {
|
||||||
|
#service;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.#service = JobRequestService;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ثبت نهایی فرم انتخاب مراکز
|
||||||
|
async create(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const applicantId = req.user?.id; // شناسه از توکن
|
||||||
|
// فرض: فرانتند آرایهای از انتخابها را میفرستد: [{centerId, categoryId, jobTitle, ...}, ...]
|
||||||
|
const requests = Array.isArray(req.body) ? req.body : [req.body];
|
||||||
|
|
||||||
|
const data = await this.#service.createRequests(applicantId, requests);
|
||||||
|
|
||||||
|
return res.status(201).json({
|
||||||
|
status: 201,
|
||||||
|
data,
|
||||||
|
message: "درخواستهای شغلی با موفقیت ثبت شد",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// مشاهده انتخابهای من
|
||||||
|
async getMyRequests(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const data = await this.#service.getByApplicant(req.user?.id);
|
||||||
|
return res.status(200).json({
|
||||||
|
status: 200,
|
||||||
|
data,
|
||||||
|
message: "Ok",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const data = await this.#service.update(req.params.id, req.user?.id, req.body);
|
||||||
|
return res.status(200).json({
|
||||||
|
status: 200,
|
||||||
|
data,
|
||||||
|
message: "ویرایش شد",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
await this.#service.remove(req.params.id, req.user?.id);
|
||||||
|
return res.status(200).json({
|
||||||
|
status: 200,
|
||||||
|
data:{},
|
||||||
|
message: "حذف شد",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const JobRequestController = new JobRequestControllerClass();
|
||||||
|
export default JobRequestController;
|
||||||
16
src/modules/forms/jobRequest/routes/jobRequest.routes.ts
Normal file
16
src/modules/forms/jobRequest/routes/jobRequest.routes.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { Router } from "express";
|
||||||
|
import JobRequestController from "../controller/jobRequest.controller";
|
||||||
|
|
||||||
|
const JobRequestRouter = Router();
|
||||||
|
|
||||||
|
// ثبت انتخابها (مراکز و رستهها)
|
||||||
|
JobRequestRouter.post("/", JobRequestController.create.bind(JobRequestController));
|
||||||
|
|
||||||
|
// لیست انتخابهای متقاضی فعلی
|
||||||
|
JobRequestRouter.get("/my-selections", JobRequestController.getMyRequests.bind(JobRequestController));
|
||||||
|
|
||||||
|
// حذف یا ویرایش یک انتخاب خاص
|
||||||
|
JobRequestRouter.put("/:id", JobRequestController.update.bind(JobRequestController));
|
||||||
|
JobRequestRouter.delete("/:id", JobRequestController.remove.bind(JobRequestController));
|
||||||
|
|
||||||
|
export default JobRequestRouter;
|
||||||
53
src/modules/forms/jobRequest/service/jobRequest.service.ts
Normal file
53
src/modules/forms/jobRequest/service/jobRequest.service.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { JobRequest } from "../../../../models/JobRequest";
|
||||||
|
|
||||||
|
class JobRequestServiceClass {
|
||||||
|
// ثبت درخواستهای شغلی (پشتیبانی از ثبت همزمان چند مرکز/رسته)
|
||||||
|
async createRequests(applicantId: string, requests: any[]) {
|
||||||
|
// اضافه کردن applicantId به تمام آبجکتهای ارسالی از سمت فرانت
|
||||||
|
const preparedData = requests.map((req) => ({
|
||||||
|
...req,
|
||||||
|
applicantId,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return await JobRequest.bulkCreate(preparedData);
|
||||||
|
}
|
||||||
|
|
||||||
|
// دریافت لیست تمام درخواستهای یک متقاضی (با جزئیات)
|
||||||
|
async getByApplicant(applicantId: string) {
|
||||||
|
return await JobRequest.findAll({
|
||||||
|
where: {applicantId},
|
||||||
|
// پیشنهاد: اینجا میتوانید Include کنید تا نام مرکز و رسته را هم بگیرید
|
||||||
|
include: ["center", "category"],
|
||||||
|
order: [["createdAt", "DESC"]],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// دریافت تمام درخواستهای ثبت شده برای یک مرکز خاص (برای پنل مدیریت مرکز)
|
||||||
|
async getByCenter(centerId: string) {
|
||||||
|
return await JobRequest.findAll({
|
||||||
|
where: {centerId},
|
||||||
|
include: ["applicant"], // اطلاعات متقاضی را هم بیاورد
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ویرایش یک درخواست خاص
|
||||||
|
async update(id: string, applicantId: string, data: any) {
|
||||||
|
const request = await JobRequest.findOne({where: {id, applicantId}});
|
||||||
|
if (!request) throw new Error("درخواست یافت نشد.");
|
||||||
|
|
||||||
|
return await request.update(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// حذف درخواست
|
||||||
|
async remove(id: string, applicantId: string) {
|
||||||
|
const request = await JobRequest.findOne({where: {id, applicantId}});
|
||||||
|
if (!request) throw new Error("درخواست یافت نشد.");
|
||||||
|
|
||||||
|
await request.destroy();
|
||||||
|
return {message: "درخواست با موفقیت حذف شد."};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const JobRequestService = new JobRequestServiceClass();
|
||||||
|
|
||||||
|
export default JobRequestService;
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
// modules/.../language-skill/controller/languageSkill.controller.ts
|
||||||
|
import { NextFunction } from "express";
|
||||||
|
import { Controller } from "../../../../core/controller/main.controller";
|
||||||
|
import { ServerResponse } from "../../../../core/types";
|
||||||
|
import LanguageSkillService from "../service/langugeSkill.service";
|
||||||
|
|
||||||
|
class LanguageSkillControllerClass extends Controller {
|
||||||
|
#service;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.#service = LanguageSkillService;
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const applicantId = req.user?.id;
|
||||||
|
const data = await this.#service.create(applicantId, req.body);
|
||||||
|
|
||||||
|
return res.status(201).json({
|
||||||
|
status: 201,
|
||||||
|
data,
|
||||||
|
message: "مهارت زبان با موفقیت ثبت شد",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAll(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const applicantId = req.user?.id;
|
||||||
|
const data = await this.#service.getAllByApplicant(applicantId);
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
status: 200,
|
||||||
|
data,
|
||||||
|
message: "Ok",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getOne(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const applicantId = req.user?.id;
|
||||||
|
const { id } = req.params;
|
||||||
|
|
||||||
|
const data = await this.#service.getById(id, applicantId);
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
status: 200,
|
||||||
|
data,
|
||||||
|
message: "Ok",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const applicantId = req.user?.id;
|
||||||
|
const { id } = req.params;
|
||||||
|
|
||||||
|
const data = await this.#service.update(id, applicantId, req.body);
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
status: 200,
|
||||||
|
data,
|
||||||
|
message: "مهارت زبان ویرایش شد",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const applicantId = req.user?.id;
|
||||||
|
const { id } = req.params;
|
||||||
|
|
||||||
|
await this.#service.remove(id, applicantId);
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
status: 200,
|
||||||
|
data: {},
|
||||||
|
message: "مهارت زبان حذف شد",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const LanguageSkillController = new LanguageSkillControllerClass();
|
||||||
|
export default LanguageSkillController;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { Router } from "express";
|
||||||
|
import LanguageSkillController from "../controller/languageSkill.controller";
|
||||||
|
|
||||||
|
const LanguageSkillRouter = Router();
|
||||||
|
|
||||||
|
LanguageSkillRouter.post("/", LanguageSkillController.create.bind(LanguageSkillController));
|
||||||
|
LanguageSkillRouter.get("/", LanguageSkillController.getAll.bind(LanguageSkillController));
|
||||||
|
LanguageSkillRouter.get("/:id", LanguageSkillController.getOne.bind(LanguageSkillController));
|
||||||
|
LanguageSkillRouter.put("/:id", LanguageSkillController.update.bind(LanguageSkillController));
|
||||||
|
LanguageSkillRouter.delete("/:id", LanguageSkillController.remove.bind(LanguageSkillController));
|
||||||
|
|
||||||
|
export default LanguageSkillRouter;
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import { LanguageSkill } from "../../../../models/LanguageSkill";
|
||||||
|
|
||||||
|
type CreateLanguageSkillDTO = {
|
||||||
|
languageName?: string;
|
||||||
|
proficiency?: string;
|
||||||
|
hasCertificate?: boolean;
|
||||||
|
certificateType?: string;
|
||||||
|
description?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type UpdateLanguageSkillDTO = Partial<CreateLanguageSkillDTO>;
|
||||||
|
|
||||||
|
class LanguageSkillServiceClass {
|
||||||
|
async create(applicantId: string, data: CreateLanguageSkillDTO) {
|
||||||
|
return await LanguageSkill.create({
|
||||||
|
applicantId,
|
||||||
|
...data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllByApplicant(applicantId: string) {
|
||||||
|
return await LanguageSkill.findAll({
|
||||||
|
where: { applicantId },
|
||||||
|
order: [["createdAt", "DESC"]],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getById(id: string, applicantId: string) {
|
||||||
|
const record = await LanguageSkill.findOne({
|
||||||
|
where: { id, applicantId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!record) {
|
||||||
|
throw new Error("رکورد مهارت زبان یافت نشد");
|
||||||
|
}
|
||||||
|
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(id: string, applicantId: string, data: UpdateLanguageSkillDTO) {
|
||||||
|
const record = await LanguageSkill.findOne({
|
||||||
|
where: { id, applicantId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!record) {
|
||||||
|
throw new Error("رکورد مهارت زبان یافت نشد");
|
||||||
|
}
|
||||||
|
|
||||||
|
await record.update(data);
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(id: string, applicantId: string) {
|
||||||
|
const record = await LanguageSkill.findOne({
|
||||||
|
where: { id, applicantId },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!record) {
|
||||||
|
throw new Error("رکورد مهارت زبان یافت نشد");
|
||||||
|
}
|
||||||
|
|
||||||
|
await record.destroy();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const LanguageSkillService = new LanguageSkillServiceClass()
|
||||||
|
|
||||||
|
export default LanguageSkillService;
|
||||||
94
src/modules/forms/referral/controller/referral.controller.ts
Normal file
94
src/modules/forms/referral/controller/referral.controller.ts
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
// controllers/referral.controller.ts
|
||||||
|
import { NextFunction } from "express";
|
||||||
|
import { Controller } from "../../../../core/controller/main.controller";
|
||||||
|
import { ServerResponse } from "../../../../core/types";
|
||||||
|
import ReferralService from "../service/referral.service";
|
||||||
|
|
||||||
|
class ReferralControllerClass extends Controller {
|
||||||
|
#service;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
// فرض بر این است که ReferralService به صورت singleton (New شده) اکسپورت شده است.
|
||||||
|
// در غیر این صورت: this.#service = new ReferralService();
|
||||||
|
this.#service = ReferralService;
|
||||||
|
}
|
||||||
|
|
||||||
|
// دریافت لیست معرفهای کاربر فعلی
|
||||||
|
async getAll(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
// استخراج آیدی متقاضی از توکن برای امنیت بیشتر (فقط معرفهای خودش را ببیند)
|
||||||
|
const applicantId = req.user?.id || req.query?.applicantId;
|
||||||
|
|
||||||
|
const data = await this.#service.getAll(applicantId);
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
status: 200,
|
||||||
|
data,
|
||||||
|
message: "Ok",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error); // اضافه شدن هندلر خطا که قبلاً خالی بود
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ثبت معرف جدید
|
||||||
|
async create(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const applicantId = req.user?.id; // شناسه متقاضی از توکن امنیتی
|
||||||
|
|
||||||
|
const data = await this.#service.create({
|
||||||
|
...req.body,
|
||||||
|
applicantId: applicantId || req.body.applicantId // اولویت با توکن است
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(201).json({
|
||||||
|
status: 201,
|
||||||
|
data,
|
||||||
|
message: "ساخته شد",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ویرایش اطلاعات معرف با بررسی امنیت مالکیت رکورد
|
||||||
|
async update(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const id = req?.params?.id;
|
||||||
|
const applicantId = req.user?.id; // برای بررسی اینکه کاربر فقط معرف خودش را ادیت کند
|
||||||
|
|
||||||
|
const data = await this.#service.update(id, applicantId, req.body);
|
||||||
|
|
||||||
|
return res.status(200).json({ // تغییر وضعیت به 200 (OK)
|
||||||
|
status: 200,
|
||||||
|
data,
|
||||||
|
message: "ویرایش شد",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// حذف معرف با بررسی مالکیت رکورد
|
||||||
|
async remove(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const id = req?.params?.id;
|
||||||
|
const applicantId = req.user?.id; // برای جلوگیری از حذف معرفهای دیگران توسط کاربران غریبه
|
||||||
|
|
||||||
|
await this.#service.delete(id, applicantId);
|
||||||
|
|
||||||
|
return res.status(200).json({ // تغییر وضعیت به 200 (OK)
|
||||||
|
status: 200,
|
||||||
|
data: {},
|
||||||
|
message: "حذف شد",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ReferralController = new ReferralControllerClass();
|
||||||
|
|
||||||
|
export default ReferralController;
|
||||||
16
src/modules/forms/referral/routes/referral.routes.ts
Normal file
16
src/modules/forms/referral/routes/referral.routes.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// routes/referral.routes.ts
|
||||||
|
import { Router } from "express";
|
||||||
|
import ReferralController from "../controller/referral.controller";
|
||||||
|
|
||||||
|
|
||||||
|
const ReferralRouter = Router();
|
||||||
|
|
||||||
|
// تمامی عملیاتها نیاز به لاگین دارند
|
||||||
|
// ReferralRouter.use(authMiddleware);
|
||||||
|
|
||||||
|
ReferralRouter.post("/", ReferralController.create);
|
||||||
|
ReferralRouter.get("/", ReferralController.getAll);
|
||||||
|
ReferralRouter.put("/:id", ReferralController.update);
|
||||||
|
ReferralRouter.delete("/:id", ReferralController.remove);
|
||||||
|
|
||||||
|
export default ReferralRouter;
|
||||||
41
src/modules/forms/referral/service/referral.service.ts
Normal file
41
src/modules/forms/referral/service/referral.service.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import {Referral} from "../../../../models/Referral";
|
||||||
|
|
||||||
|
class ReferralServiceClass {
|
||||||
|
// ثبت معرف جدید
|
||||||
|
async create(data: any) {
|
||||||
|
return await Referral.create(data);
|
||||||
|
}
|
||||||
|
async getAll(applicantId?: string) {
|
||||||
|
const filter = applicantId ? {where: {applicantId}} : {};
|
||||||
|
return await Referral.findAll(filter);
|
||||||
|
}
|
||||||
|
// دریافت تمام معرفهای یک متقاضی خاص
|
||||||
|
async getByApplicant(applicantId: string) {
|
||||||
|
return await Referral.findAll({
|
||||||
|
where: {applicantId},
|
||||||
|
order: [["createdAt", "DESC"]],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// بروزرسانی اطلاعات معرف
|
||||||
|
async update(id: string, applicantId: string, data: any) {
|
||||||
|
const referral = await Referral.findOne({where: {id, applicantId}});
|
||||||
|
if (!referral)
|
||||||
|
throw new Error("معرف مورد نظر یافت نشد یا دسترسی مجاز نیست.");
|
||||||
|
|
||||||
|
return await referral.update(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// حذف معرف
|
||||||
|
async delete(id: string, applicantId: string) {
|
||||||
|
const referral = await Referral.findOne({where: {id, applicantId}});
|
||||||
|
if (!referral) throw new Error("معرف مورد نظر یافت نشد.");
|
||||||
|
|
||||||
|
await referral.destroy();
|
||||||
|
return {message: "معرف با موفقیت حذف شد."};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const ReferralService = new ReferralServiceClass();
|
||||||
|
|
||||||
|
export default ReferralService;
|
||||||
81
src/modules/forms/relation/controller/relation.controller.ts
Normal file
81
src/modules/forms/relation/controller/relation.controller.ts
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
// modules/relation/controller/relation.controller.ts
|
||||||
|
import { NextFunction } from "express";
|
||||||
|
|
||||||
|
import RelationService from "../service/relation.service";
|
||||||
|
import { ServerResponse } from "../../../../core/types";
|
||||||
|
import { Controller } from "../../../../core/controller/main.controller";
|
||||||
|
|
||||||
|
class RelationControllerClass extends Controller {
|
||||||
|
#service;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.#service = RelationService;
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const applicantId = req.user.id;
|
||||||
|
const data = await this.#service.create(applicantId, req.body);
|
||||||
|
|
||||||
|
return res.status(201).json({
|
||||||
|
status: 201,
|
||||||
|
data,
|
||||||
|
message: "اطلاعات آشنایان با موفقیت ثبت شد",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAll(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const applicantId = req.user.id;
|
||||||
|
const data = await this.#service.getAllByApplicant(applicantId);
|
||||||
|
|
||||||
|
return res.status(200).json({ status: 200, data, message: "Ok" });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const { id } = req.params;
|
||||||
|
const applicantId = req.user.id;
|
||||||
|
const data = await this.#service.update(id, applicantId, req.body);
|
||||||
|
|
||||||
|
return res.status(200).json({ status: 200, data, message: "بروزرسانی انجام شد" });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const { id } = req.params;
|
||||||
|
const applicantId = req.user.id;
|
||||||
|
await this.#service.remove(id, applicantId);
|
||||||
|
|
||||||
|
return res.status(200).json({ status: 200,data:{}, message: "رکورد حذف شد" });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// متد ثبت گروهی (Batch)
|
||||||
|
async bulkSave(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const applicantId = req.user.id;
|
||||||
|
const { relations } = req.body; // انتظار یک آرایه داریم
|
||||||
|
const data = await this.#service.bulkSync(applicantId, relations);
|
||||||
|
|
||||||
|
return res.status(200).json({ status: 200, data, message: "اطلاعات همگامسازی شد" });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const RelationController = new RelationControllerClass();
|
||||||
|
export default RelationController;
|
||||||
16
src/modules/forms/relation/routes/relation.routes.ts
Normal file
16
src/modules/forms/relation/routes/relation.routes.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
// modules/relation/routes/relation.routes.ts
|
||||||
|
import { Router } from "express";
|
||||||
|
import RelationController from "../controller/relation.controller";
|
||||||
|
|
||||||
|
const relationRouter = Router();
|
||||||
|
|
||||||
|
// مسیرهای اصلی
|
||||||
|
relationRouter.post("/", RelationController.create.bind(RelationController));
|
||||||
|
relationRouter.get("/", RelationController.getAll.bind(RelationController));
|
||||||
|
relationRouter.put("/:id", RelationController.update.bind(RelationController));
|
||||||
|
relationRouter.delete("/:id", RelationController.remove.bind(RelationController));
|
||||||
|
|
||||||
|
// مسیر کمکی برای ثبت همزمان چند آشنا
|
||||||
|
relationRouter.post("/bulk", RelationController.bulkSave.bind(RelationController));
|
||||||
|
|
||||||
|
export default relationRouter;
|
||||||
79
src/modules/forms/relation/service/relation.service.ts
Normal file
79
src/modules/forms/relation/service/relation.service.ts
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import { Relation } from "../../../../models/Relation";
|
||||||
|
|
||||||
|
|
||||||
|
type CreateRelationDTO = {
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
relationship: string;
|
||||||
|
jobTitle?: string;
|
||||||
|
workplaceName?: string;
|
||||||
|
phoneNumber: string;
|
||||||
|
order: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RelationServiceClass {
|
||||||
|
async create(applicantId: string, data: CreateRelationDTO) {
|
||||||
|
// بررسی اینکه آیا این اولویت (order) قبلاً برای این متقاضی ثبت شده یا خیر
|
||||||
|
const existingOrder = await Relation.findOne({
|
||||||
|
where: {applicantId, order: data.order},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existingOrder) {
|
||||||
|
throw new Error(
|
||||||
|
`اطلاعات برای اولویت ${data.order} قبلاً ثبت شده است. از متد آپدیت استفاده کنید.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await Relation.create({
|
||||||
|
applicantId,
|
||||||
|
...data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllByApplicant(applicantId: string) {
|
||||||
|
return await Relation.findAll({
|
||||||
|
where: {applicantId},
|
||||||
|
order: [["order", "ASC"]], // مرتبسازی بر اساس اولویت ۱، ۲ و...
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(
|
||||||
|
id: string,
|
||||||
|
applicantId: string,
|
||||||
|
data: Partial<CreateRelationDTO>,
|
||||||
|
) {
|
||||||
|
const relation = await Relation.findOne({
|
||||||
|
where: {id, applicantId},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!relation) throw new Error("رکورد مورد نظر یافت نشد.");
|
||||||
|
|
||||||
|
await relation.update(data);
|
||||||
|
return relation;
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(id: string, applicantId: string) {
|
||||||
|
const relation = await Relation.findOne({
|
||||||
|
where: {id, applicantId},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!relation) throw new Error("رکورد مورد نظر یافت نشد.");
|
||||||
|
|
||||||
|
await relation.destroy();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ثبت گروهی آشنایان (مثلاً هر دو آشنا در یک مرحله ارسال شوند)
|
||||||
|
*/
|
||||||
|
async bulkSync(applicantId: string, relations: CreateRelationDTO[]) {
|
||||||
|
// ابتدا رکوردهای قبلی را پاک میکنیم (یا میتوان منطق بروزرسانی پیچیده تری داشت)
|
||||||
|
await Relation.destroy({where: {applicantId}});
|
||||||
|
|
||||||
|
const dataWithId = relations.map((r) => ({...r, applicantId}));
|
||||||
|
return await Relation.bulkCreate(dataWithId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const RelationService = new RelationServiceClass()
|
||||||
|
export default RelationService;
|
||||||
@@ -0,0 +1,130 @@
|
|||||||
|
// modules/.../work-experience/controller/workExperience.controller.ts
|
||||||
|
import {NextFunction} from "express";
|
||||||
|
import {Controller} from "../../../../core/controller/main.controller";
|
||||||
|
import {ServerResponse} from "../../../../core/types";
|
||||||
|
import WorkExperienceService from "../service/workExperience.service";
|
||||||
|
|
||||||
|
class WorkExperienceControllerClass extends Controller {
|
||||||
|
#service;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.#service = WorkExperienceService;
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const applicantId = req.user?.id;
|
||||||
|
const data = await this.#service.create(applicantId, req.body);
|
||||||
|
|
||||||
|
return res.status(201).json({
|
||||||
|
status: 201,
|
||||||
|
data,
|
||||||
|
message: "سابقه کاری ثبت شد",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAll(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const applicantId = req.user?.id;
|
||||||
|
const data = await this.#service.getAllByApplicant(applicantId);
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
status: 200,
|
||||||
|
data,
|
||||||
|
message: "Ok",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getOne(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const applicantId = req.user?.id;
|
||||||
|
const {id} = req.params;
|
||||||
|
|
||||||
|
const data = await this.#service.getById(id, applicantId);
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
status: 200,
|
||||||
|
data,
|
||||||
|
message: "Ok",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const applicantId = req.user?.id;
|
||||||
|
const {id} = req.params;
|
||||||
|
|
||||||
|
const data = await this.#service.update(id, applicantId, req.body);
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
status: 200,
|
||||||
|
data,
|
||||||
|
message: "سابقه کاری ویرایش شد",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const applicantId = req.user?.id;
|
||||||
|
const {id} = req.params;
|
||||||
|
|
||||||
|
await this.#service.remove(id, applicantId);
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
status: 200,
|
||||||
|
data: {},
|
||||||
|
message: "سابقه کاری حذف شد",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// اختیاری: endpoint جدا برای "هیچ سابقهای ندارم"
|
||||||
|
async setHasNoExperience(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const applicantId = req.user?.id;
|
||||||
|
const data = await this.#service.setHasNoExperience(applicantId);
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
status: 200,
|
||||||
|
data,
|
||||||
|
message: "وضعیت «بدون سابقه کاری» ثبت شد",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async bulkSave(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const applicantId = req.user.id;
|
||||||
|
const {items} = req.body;
|
||||||
|
|
||||||
|
const data = await this.#service.bulkSync(applicantId, items);
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
status: 200,
|
||||||
|
data,
|
||||||
|
message: "سوابق کاری با موفقیت ذخیره شدند",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const WorkExperienceController = new WorkExperienceControllerClass();
|
||||||
|
export default WorkExperienceController;
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
// modules/.../work-experience/routes/workExperience.routes.ts
|
||||||
|
import { Router } from "express";
|
||||||
|
import WorkExperienceController from "../controller/workExperience.controller";
|
||||||
|
|
||||||
|
const workExperienceRouter = Router();
|
||||||
|
|
||||||
|
workExperienceRouter.post("/", WorkExperienceController.create.bind(WorkExperienceController));
|
||||||
|
workExperienceRouter.get("/", WorkExperienceController.getAll.bind(WorkExperienceController));
|
||||||
|
workExperienceRouter.get("/:id", WorkExperienceController.getOne.bind(WorkExperienceController));
|
||||||
|
workExperienceRouter.put("/:id", WorkExperienceController.update.bind(WorkExperienceController));
|
||||||
|
workExperienceRouter.delete("/:id", WorkExperienceController.remove.bind(WorkExperienceController));
|
||||||
|
|
||||||
|
// اختیاری:
|
||||||
|
workExperienceRouter.post("/no-experience", WorkExperienceController.setHasNoExperience.bind(WorkExperienceController));
|
||||||
|
workExperienceRouter.post("/bulk", WorkExperienceController.bulkSave.bind(WorkExperienceController));
|
||||||
|
|
||||||
|
export default workExperienceRouter;
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
import {WorkExperience} from "../../../../models/WorkExperience";
|
||||||
|
|
||||||
|
type CreateWorkExperienceDTO = {
|
||||||
|
hasNoExperience?: boolean;
|
||||||
|
companyName?: string;
|
||||||
|
lastPosition?: string;
|
||||||
|
startYear?: number;
|
||||||
|
endYear?: number;
|
||||||
|
leavingReason?: string;
|
||||||
|
description?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type UpdateWorkExperienceDTO = Partial<CreateWorkExperienceDTO>;
|
||||||
|
|
||||||
|
class WorkExperienceService {
|
||||||
|
private normalize(data: UpdateWorkExperienceDTO) {
|
||||||
|
const normalized: UpdateWorkExperienceDTO = {...data};
|
||||||
|
|
||||||
|
// اگر کاربر گفت هیچ سابقهای ندارم، اطلاعات سابقه را خالی کن
|
||||||
|
if (normalized.hasNoExperience === true) {
|
||||||
|
normalized.companyName = undefined;
|
||||||
|
normalized.lastPosition = undefined;
|
||||||
|
normalized.startYear = undefined;
|
||||||
|
normalized.endYear = undefined;
|
||||||
|
normalized.leavingReason = undefined;
|
||||||
|
normalized.description = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// اگر endYear < startYear شد، خطا بده (کنترل پایه)
|
||||||
|
if (
|
||||||
|
typeof normalized.startYear === "number" &&
|
||||||
|
typeof normalized.endYear === "number" &&
|
||||||
|
normalized.endYear < normalized.startYear
|
||||||
|
) {
|
||||||
|
throw new Error("سال پایان نمیتواند کمتر از سال شروع باشد");
|
||||||
|
}
|
||||||
|
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(applicantId: string, data: CreateWorkExperienceDTO) {
|
||||||
|
const normalized = this.normalize(data);
|
||||||
|
|
||||||
|
return await WorkExperience.create({
|
||||||
|
applicantId,
|
||||||
|
...normalized,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllByApplicant(applicantId: string) {
|
||||||
|
return await WorkExperience.findAll({
|
||||||
|
where: {applicantId},
|
||||||
|
order: [["createdAt", "DESC"]],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getById(id: string, applicantId: string) {
|
||||||
|
const record = await WorkExperience.findOne({
|
||||||
|
where: {id, applicantId},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!record) throw new Error("سابقه کاری یافت نشد");
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(id: string, applicantId: string, data: UpdateWorkExperienceDTO) {
|
||||||
|
const record = await WorkExperience.findOne({
|
||||||
|
where: {id, applicantId},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!record) throw new Error("سابقه کاری یافت نشد");
|
||||||
|
|
||||||
|
const normalized = this.normalize(data);
|
||||||
|
await record.update(normalized);
|
||||||
|
return record;
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(id: string, applicantId: string) {
|
||||||
|
const record = await WorkExperience.findOne({
|
||||||
|
where: {id, applicantId},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!record) throw new Error("سابقه کاری یافت نشد");
|
||||||
|
|
||||||
|
await record.destroy();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* کمکی برای سناریویی که کاربر میزند "هیچ سابقهای ندارم":
|
||||||
|
* میتونی همه سوابق قبلیاش را پاک کنی و یک رکورد فلگدار بسازی.
|
||||||
|
* (اختیاری، اگر در UX شما لازم است)
|
||||||
|
*/
|
||||||
|
async setHasNoExperience(applicantId: string) {
|
||||||
|
// پاک کردن سوابق قبلی
|
||||||
|
await WorkExperience.destroy({where: {applicantId}});
|
||||||
|
|
||||||
|
// ساخت یک رکورد فلگدار
|
||||||
|
return await WorkExperience.create({
|
||||||
|
applicantId,
|
||||||
|
hasNoExperience: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async bulkSync(applicantId: string, items: CreateWorkExperienceDTO[]) {
|
||||||
|
await WorkExperience.destroy({where: {applicantId}});
|
||||||
|
|
||||||
|
if (!items || items.length === 0) return [];
|
||||||
|
|
||||||
|
const normalizedItems = items.map((item) => ({
|
||||||
|
applicantId,
|
||||||
|
...this.normalize(item),
|
||||||
|
}));
|
||||||
|
|
||||||
|
return await WorkExperience.bulkCreate(normalizedItems);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new WorkExperienceService();
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
import {NextFunction} from "express";
|
||||||
|
|
||||||
|
import JobCategoryService from "../service/jobCategory.service";
|
||||||
|
import {ServerResponse} from "../../../core/types";
|
||||||
|
import {Controller} from "../../../core/controller/main.controller";
|
||||||
|
|
||||||
|
class JobCategoryControllerClass extends Controller {
|
||||||
|
#service;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.#service = JobCategoryService;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAll(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const data = await this.#service.getAll();
|
||||||
|
return res.status(200).json({
|
||||||
|
status: 200,
|
||||||
|
data,
|
||||||
|
message: "Ok",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const data = await this.#service.create(req.body);
|
||||||
|
|
||||||
|
return res.status(201).json({
|
||||||
|
status: 201,
|
||||||
|
data,
|
||||||
|
message: "ساخته شد",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const id = req?.params?.id;
|
||||||
|
const data = await this.#service.update(id, req.body);
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
status: 200,
|
||||||
|
data,
|
||||||
|
message: "ویرایش شد",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const id = req?.params?.id;
|
||||||
|
await this.#service.remove(id);
|
||||||
|
|
||||||
|
return res.status(200).json({
|
||||||
|
status: 200,
|
||||||
|
data: {},
|
||||||
|
message: "حذف شد",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const JobCategoryController = new JobCategoryControllerClass();
|
||||||
|
|
||||||
|
export default JobCategoryController;
|
||||||
12
src/modules/job-category/routes/jobCategory.routes.ts
Normal file
12
src/modules/job-category/routes/jobCategory.routes.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { Router } from "express";
|
||||||
|
import JobCategoryController from "../controller/jobCategory.controller";
|
||||||
|
|
||||||
|
const JobCategoryRouter = Router();
|
||||||
|
|
||||||
|
JobCategoryRouter.get("/", JobCategoryController.getAll.bind(JobCategoryController));
|
||||||
|
JobCategoryRouter.get("/:id", JobCategoryController.getById.bind(JobCategoryController));
|
||||||
|
JobCategoryRouter.post("/", JobCategoryController.create.bind(JobCategoryController));
|
||||||
|
JobCategoryRouter.put("/:id", JobCategoryController.update.bind(JobCategoryController));
|
||||||
|
JobCategoryRouter.delete("/:id", JobCategoryController.remove.bind(JobCategoryController));
|
||||||
|
|
||||||
|
export default JobCategoryRouter;
|
||||||
45
src/modules/job-category/service/jobCategory.service.ts
Normal file
45
src/modules/job-category/service/jobCategory.service.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { JobCategory } from "../../../models/JobCategory";
|
||||||
|
|
||||||
|
class JobCategoryServiceClass {
|
||||||
|
async getAll() {
|
||||||
|
return await JobCategory.findAll({
|
||||||
|
order: [["createdAt", "DESC"]],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getById(id: string) {
|
||||||
|
const item = await JobCategory.findByPk(id);
|
||||||
|
if (!item) {
|
||||||
|
throw new Error("رسته شغلی یافت نشد");
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(data: any) {
|
||||||
|
return await JobCategory.create(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(id: string, data: any) {
|
||||||
|
const item = await JobCategory.findByPk(id);
|
||||||
|
if (!item) {
|
||||||
|
throw new Error("رسته شغلی یافت نشد");
|
||||||
|
}
|
||||||
|
|
||||||
|
await item.update(data);
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(id: string) {
|
||||||
|
const item = await JobCategory.findByPk(id);
|
||||||
|
if (!item) {
|
||||||
|
throw new Error("رسته شغلی یافت نشد");
|
||||||
|
}
|
||||||
|
|
||||||
|
await item.destroy();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const JobCategoryService = new JobCategoryServiceClass();
|
||||||
|
|
||||||
|
export default JobCategoryService;
|
||||||
93
src/modules/user/controller/user.controller.ts
Normal file
93
src/modules/user/controller/user.controller.ts
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
// src/modules/user/controller/user.controller.ts
|
||||||
|
import { NextFunction } from "express";
|
||||||
|
import { Controller } from "../../../core/controller/main.controller";
|
||||||
|
import { ServerResponse } from "../../../core/types";
|
||||||
|
import UserService from "../service/user.service";
|
||||||
|
|
||||||
|
class UserControllerClass extends Controller {
|
||||||
|
#service;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.#service = UserService;
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const data = await this.#service.create(req.body);
|
||||||
|
return res.status(201).json({ status: 201, data, message: "کاربر ساخته شد" });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAll(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const data = await this.#service.getAll({
|
||||||
|
search: req.query.search as string | undefined,
|
||||||
|
isActive: req.query.isActive !== undefined ? req.query.isActive === "true" : undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).json({ status: 200, data, message: "Ok" });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getOne(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const data = await this.#service.getById(req.params.id);
|
||||||
|
return res.status(200).json({ status: 200, data, message: "Ok" });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const data = await this.#service.update(req.params.id, req.body);
|
||||||
|
return res.status(200).json({ status: 200, data, message: "آپدیت شد" });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async toggleActive(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const { isActive } = req.body as { isActive: boolean };
|
||||||
|
const data = await this.#service.toggleActive(req.params.id, isActive);
|
||||||
|
return res.status(200).json({ status: 200, data, message: "وضعیت بروزرسانی شد" });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
await this.#service.remove(req.params.id);
|
||||||
|
return res.status(200).json({ status: 200, data: {}, message: "حذف شد" });
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auth: لاگین
|
||||||
|
async login(req: any, res: ServerResponse, next: NextFunction) {
|
||||||
|
try {
|
||||||
|
const { email, password } = req.body;
|
||||||
|
const user = await this.#service.login(email, password);
|
||||||
|
|
||||||
|
// اینجا JWT بسازید و برگردانید (اگر دارید)
|
||||||
|
return res.status(200).json({
|
||||||
|
status: 200,
|
||||||
|
data: { user },
|
||||||
|
message: "ورود موفق",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const UserController = new UserControllerClass();
|
||||||
|
export default UserController;
|
||||||
18
src/modules/user/routes/user.routes.ts
Normal file
18
src/modules/user/routes/user.routes.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
// src/modules/user/routes/user.routes.ts
|
||||||
|
import { Router } from "express";
|
||||||
|
import UserController from "../controller/user.controller";
|
||||||
|
|
||||||
|
const userRouter = Router();
|
||||||
|
|
||||||
|
// Auth
|
||||||
|
userRouter.post("/login", UserController.login.bind(UserController));
|
||||||
|
|
||||||
|
// CRUD (معمولاً باید پشت adminMiddleware باشد)
|
||||||
|
userRouter.post("/", UserController.create.bind(UserController));
|
||||||
|
userRouter.get("/", UserController.getAll.bind(UserController));
|
||||||
|
userRouter.get("/:id", UserController.getOne.bind(UserController));
|
||||||
|
userRouter.put("/:id", UserController.update.bind(UserController));
|
||||||
|
userRouter.patch("/:id/active", UserController.toggleActive.bind(UserController));
|
||||||
|
userRouter.delete("/:id", UserController.remove.bind(UserController));
|
||||||
|
|
||||||
|
export default userRouter;
|
||||||
132
src/modules/user/service/user.service.ts
Normal file
132
src/modules/user/service/user.service.ts
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
// src/modules/user/service/user.service.ts
|
||||||
|
import bcrypt from "bcryptjs";
|
||||||
|
import { Op } from "sequelize";
|
||||||
|
import { User } from "../../../models/User";
|
||||||
|
|
||||||
|
type CreateUserDTO = {
|
||||||
|
fullname: string;
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
roleId: string;
|
||||||
|
isActive?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type UpdateUserDTO = Partial<{
|
||||||
|
fullname: string;
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
roleId: string;
|
||||||
|
isActive: boolean;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
class UserServiceClass {
|
||||||
|
private async hashPassword(password: string) {
|
||||||
|
const saltRounds = 10;
|
||||||
|
return await bcrypt.hash(password, saltRounds);
|
||||||
|
}
|
||||||
|
|
||||||
|
private sanitize(user: User) {
|
||||||
|
const json = user.toJSON() as any;
|
||||||
|
delete json.password;
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(data: CreateUserDTO) {
|
||||||
|
const exists = await User.findOne({ where: { email: data.email } });
|
||||||
|
if (exists) throw new Error("ایمیل تکراری است");
|
||||||
|
|
||||||
|
const hashed = await this.hashPassword(data.password);
|
||||||
|
|
||||||
|
const user = await User.create({
|
||||||
|
fullname: data.fullname,
|
||||||
|
email: data.email,
|
||||||
|
password: hashed,
|
||||||
|
roleId: data.roleId,
|
||||||
|
isActive: data.isActive ?? true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.sanitize(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAll(query?: { search?: string; isActive?: boolean }) {
|
||||||
|
const where: any = {};
|
||||||
|
|
||||||
|
if (typeof query?.isActive === "boolean") where.isActive = query.isActive;
|
||||||
|
|
||||||
|
if (query?.search) {
|
||||||
|
where[Op.or] = [
|
||||||
|
{ fullname: { [Op.iLike]: `%${query.search}%` } },
|
||||||
|
{ email: { [Op.iLike]: `%${query.search}%` } },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
const users = await User.findAll({
|
||||||
|
where,
|
||||||
|
order: [["createdAt", "DESC"]],
|
||||||
|
// اگر relation Role را attach کردهاید:
|
||||||
|
// include: [{ association: "role" }],
|
||||||
|
});
|
||||||
|
|
||||||
|
return users.map((u:any) => this.sanitize(u));
|
||||||
|
}
|
||||||
|
|
||||||
|
async getById(id: string) {
|
||||||
|
const user = await User.findByPk(id);
|
||||||
|
if (!user) throw new Error("کاربر یافت نشد");
|
||||||
|
return this.sanitize(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(id: string, data: UpdateUserDTO) {
|
||||||
|
const user = await User.findByPk(id);
|
||||||
|
if (!user) throw new Error("کاربر یافت نشد");
|
||||||
|
|
||||||
|
// اگر ایمیل تغییر میکند، تکراری نبودن را چک کن
|
||||||
|
if (data.email && data.email !== user.email) {
|
||||||
|
const exists = await User.findOne({ where: { email: data.email } });
|
||||||
|
if (exists) throw new Error("ایمیل تکراری است");
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatePayload: any = { ...data };
|
||||||
|
|
||||||
|
if (typeof data.password === "string" && data.password.trim().length > 0) {
|
||||||
|
updatePayload.password = await this.hashPassword(data.password);
|
||||||
|
} else {
|
||||||
|
delete updatePayload.password;
|
||||||
|
}
|
||||||
|
|
||||||
|
await user.update(updatePayload);
|
||||||
|
return this.sanitize(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
async toggleActive(id: string, isActive: boolean) {
|
||||||
|
const user = await User.findByPk(id);
|
||||||
|
if (!user) throw new Error("کاربر یافت نشد");
|
||||||
|
|
||||||
|
await user.update({ isActive });
|
||||||
|
return this.sanitize(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove(id: string) {
|
||||||
|
const user = await User.findByPk(id);
|
||||||
|
if (!user) throw new Error("کاربر یافت نشد");
|
||||||
|
await user.destroy();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auth: لاگین با ایمیل و پسورد
|
||||||
|
async login(email: string, password: string) {
|
||||||
|
const user = await User.findOne({ where: { email } });
|
||||||
|
if (!user) throw new Error("ایمیل یا رمز عبور اشتباه است");
|
||||||
|
|
||||||
|
if (!user.isActive) throw new Error("حساب کاربری غیرفعال است");
|
||||||
|
|
||||||
|
const ok = await bcrypt.compare(password, user.password);
|
||||||
|
if (!ok) throw new Error("ایمیل یا رمز عبور اشتباه است");
|
||||||
|
|
||||||
|
// اینجا معمولاً JWT صادر میکنید؛ من فقط user sanitize برمیگردونم
|
||||||
|
return this.sanitize(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const UserService = new UserServiceClass()
|
||||||
|
export default UserService;
|
||||||
Reference in New Issue
Block a user