added modules & forms jobcaegory-jobrequest-referral-relation etc

This commit is contained in:
2026-05-27 22:50:29 +03:30
parent 83fd5c1a86
commit df1dda764c
32 changed files with 1624 additions and 46 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
node_modules

5
package-lock.json generated
View File

@@ -211,6 +211,7 @@
"integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@types/body-parser": "*",
"@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",
"integrity": "sha512-AOQwYUNolgy3VosiRqXrACUXTN8nJUtPl7FJXMqZVyxiiCLhQuG3jXKvCS1ALr+Y2OmZhzzLVlYPEqJaiqkaJQ==",
"license": "MIT",
"peer": true,
"dependencies": {
"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",
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
"license": "MIT",
"peer": true,
"dependencies": {
"accepts": "^2.0.0",
"body-parser": "^2.2.1",
@@ -2245,6 +2248,7 @@
"resolved": "https://package-mirror.liara.ir/repository/npm/pg/-/pg-8.20.0.tgz",
"integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==",
"license": "MIT",
"peer": true,
"dependencies": {
"pg-connection-string": "^2.12.0",
"pg-pool": "^3.13.0",
@@ -3148,6 +3152,7 @@
"integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"

View File

@@ -1,12 +1,12 @@
import { Router } from 'express';
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();
// mainRouter.use('/user',userRouter)
// mainRouter.use('/applicant',applicantRouter)
mainRouter.use('/auth',AuthRouter)
// mainRouter.use('/user',userRouter)
mainRouter.use('/user',userRouter)
mainRouter.use('/form',formRouter)
export default mainRouter;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -1,6 +1,6 @@
import { Controller } from "../../../../core/controller/main.controller";
import { ServerResponse } from "../../../../core/types";
import { NextFunction } from "express";
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 {
@@ -12,7 +12,7 @@ class CourseInfoControllerClass extends Controller {
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 });
res.status(201).json({status: 200, data: course});
} catch (err) {
next(err);
}
@@ -21,7 +21,7 @@ class CourseInfoControllerClass extends Controller {
async getById(req: any, res: ServerResponse, next: NextFunction) {
try {
const course = await this.#service.getById(req.params.id);
res.json({ status: 200, data: course });
res.json({status: 200, data: course});
} catch (err) {
next(err);
}
@@ -29,9 +29,9 @@ class CourseInfoControllerClass extends Controller {
async list(req: any, res: ServerResponse, next: NextFunction) {
try {
const { applicantId } = req.query as { applicantId?: string };
const {applicantId} = req.query as {applicantId?: string};
const courses = await this.#service.list(applicantId);
res.json({ status: 200, data: courses });
res.json({status: 200, data: courses});
} catch (err) {
next(err);
}
@@ -40,7 +40,7 @@ class CourseInfoControllerClass extends Controller {
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 });
res.json({status: 200, data: updated});
} catch (err) {
next(err);
}
@@ -49,11 +49,35 @@ class CourseInfoControllerClass extends Controller {
async delete(req: any, res: ServerResponse, next: NextFunction) {
try {
const result = await this.#service.delete(req.params.id);
res.json({ status: 200, data: result });
res.json({status: 200, data: result});
} catch (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();

View File

@@ -1,17 +1,14 @@
import createHttpError from "http-errors";
import { Controller } from "../../../../core/controller/main.controller";
import { Course } from "../../../../models/Course";
import {Controller} from "../../../../core/controller/main.controller";
import {Course} from "../../../../models/Course";
import { sequelize } from "../../../../models";
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;
title: string;
institution: string;
year: number;
duration: string;
description?: string;
}
class CourseServiceClass extends Controller {
@@ -26,21 +23,43 @@ class CourseServiceClass extends Controller {
}
async list(applicantId?: string) {
const where = applicantId ? { applicantId } : {};
return await Course.findAll({ where });
const where = applicantId ? {applicantId} : {};
return await Course.findAll({where});
}
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)
throw new createHttpError.NotFound("دوره برای ویرایش یافت نشد");
return await Course.findByPk(id);
}
async delete(id: string) {
const deleted = await Course.destroy({ where: { id } });
const deleted = await Course.destroy({where: {id}});
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});
});
}
}

View File

@@ -64,6 +64,27 @@ class EducationInfoControllerClass extends Controller {
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();

View File

@@ -1,22 +1,32 @@
import createHttpError from "http-errors";
import { Controller } from "../../../../core/controller/main.controller";
import { Education } from "../../../../models/Education";
import {Controller} from "../../../../core/controller/main.controller";
import {Education, EducationAttributes} from "../../../../models/Education";
import {sequelize} from "../../../../models";
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;
startYear?: number;
endYear?: number;
gpa?: number;
description?: string;
certificateImageId?: string;
}
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) {
try {
const education = await Education.create({
@@ -51,14 +61,14 @@ class EducationServiceClass extends Controller {
}
// لیست سوابق، مثلا بر اساس applicantId
async list(filter?: { applicantId?: string }) {
async list(filter?: {applicantId?: string}) {
const where: any = {};
if (filter?.applicantId) {
where.applicantId = filter.applicantId;
}
const list = await Education.findAll({ where });
const list = await Education.findAll({where});
return list;
}
@@ -106,13 +116,35 @@ class EducationServiceClass extends Controller {
try {
await education.destroy();
return { success: true };
return {success: true};
} 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();

View File

@@ -1,9 +1,15 @@
import { Router } from "express";
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";
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();
@@ -12,5 +18,11 @@ formRouter.use("/personal-info", PersonalInfoRouter);
formRouter.use("/physical-info", PhysicalInfoRouter);
formRouter.use("/education", EducationInfoRouter);
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;

View File

@@ -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;

View 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;

View 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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -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();

View File

@@ -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;

View 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;

View 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;

View 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;

View 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;

View 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;