This commit is contained in:
mahyargdz
2025-09-14 15:15:03 +03:30
commit be82059172
534 changed files with 51310 additions and 0 deletions
+32
View File
@@ -0,0 +1,32 @@
import { Expose } from "class-transformer";
import { IsEnum, IsNotEmpty, IsString } from "class-validator";
import { ApiProperty } from "../../../common/decorator/swggerDocs";
import { JobType } from "../models/Abstraction/IJob";
export class CreateJobDTO {
@Expose()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "title of job", example: "حسابدار" })
title: string;
@Expose()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "title of job", example: "حسابدار مسلط به هلو" })
description: string;
@Expose()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "location of job", example: "اراک" })
location: string;
@Expose()
@IsNotEmpty()
@IsEnum(JobType)
@IsString()
@ApiProperty({ type: "string", description: "title of job", example: "fullTime | partTime | contract" })
type: JobType;
}
+48
View File
@@ -0,0 +1,48 @@
import { Expose } from "class-transformer";
import { IsNotEmpty, IsString, Length } from "class-validator";
import { ApiProperty } from "../../../common/decorator/swggerDocs";
import { IsValidId, IsValidPersianDate } from "../../../common/decorator/validation.decorator";
import { JobModel } from "../models/job.model";
export class ResumeDTO {
@Expose()
@IsNotEmpty()
@IsString()
@Length(24, 24)
@IsValidId(JobModel)
@ApiProperty({ type: "string", description: "the id of the job", example: "66f3bcaee566db722a044c62" })
job: string;
@Expose()
@IsNotEmpty()
@IsString()
@Length(8)
@ApiProperty({ type: "string", description: "full name of applicant", example: "محمد محمدی" })
fullName: string;
@Expose()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "father name of applicant", example: "مجید محمدی" })
fatherName: string;
@Expose()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "the place of birth ", example: "اراک" })
placeOfBirth: string;
@Expose()
@IsNotEmpty()
@IsString()
@IsValidPersianDate()
@ApiProperty({ type: "string", description: "the id of the job", example: "66f3bcaee566db722a044c62" })
birthday: string;
@Expose()
@IsNotEmpty()
@IsString()
@ApiProperty({ type: "string", description: "the url of the resume that uploaded", example: "http://cdn.com" })
resumeUrl: string;
}
+64
View File
@@ -0,0 +1,64 @@
import { Request } from "express";
import { inject } from "inversify";
import { controller, httpGet, httpPost, queryParam, request, requestBody } from "inversify-express-utils";
import { JobService } from "./job.service";
import { HttpStatus } from "../../common";
import { ResumeDTO } from "./DTO/resume.dto";
import { BaseController } from "../../common/base/controller";
import { ApiFile, ApiModel, ApiOperation, ApiQuery, ApiResponse, ApiTags } from "../../common/decorator/swggerDocs";
import { ValidationMiddleware } from "../../core/middlewares/validator.middleware";
import { IOCTYPES } from "../../IOC/ioc.types";
import { UploadService } from "../../utils/upload.service";
@controller("/jobs")
@ApiTags("Jobs")
class JobController extends BaseController {
@inject(IOCTYPES.JobService) jobService: JobService;
@ApiOperation("get all job")
@ApiResponse("successful", HttpStatus.Ok)
@ApiQuery("limit", "the limit of return data")
@ApiQuery("page", "the page want to get")
@httpGet("")
public async getAllJob(@queryParam("limit") limit: string, @queryParam("page") page: string) {
const queries = {
limit: parseInt(limit),
page: parseInt(page),
};
const data = await this.jobService.getAvailableJobs(queries);
const { pager } = this.paginate(data.count);
return this.response({ pager, jobs: data.jobs });
}
@ApiOperation("send resume for a job")
@ApiResponse("successful", HttpStatus.Created)
@ApiModel(ResumeDTO)
@httpPost("/resume", ValidationMiddleware.validateInput(ResumeDTO))
public async submitResume(@requestBody() resumeDto: ResumeDTO) {
const data = await this.jobService.submitResume(resumeDto);
return this.response(data, HttpStatus.Created);
}
//media uploader
@ApiOperation("Upload a resume image")
@ApiResponse("File uploaded successfully", HttpStatus.Accepted)
@ApiFile("resume")
@httpPost("/upload/single", UploadService.single("resume", "resume-file"))
public async uploadImage(@request() req: Request) {
// eslint-disable-next-line no-undef
const file = req.file as Express.MulterS3.File;
const data = {
message: "file uploaded!",
url: {
originalname: file.originalname,
size: file.size,
url: file.location,
type: file.mimetype,
},
};
return this.response(data, HttpStatus.Accepted);
}
}
export { JobController };
+88
View File
@@ -0,0 +1,88 @@
import { inject, injectable } from "inversify";
import { isValidObjectId } from "mongoose";
import { CreateJobDTO } from "./DTO/createJob.dto";
import { ResumeDTO } from "./DTO/resume.dto";
import { JobRepo } from "./repository/job.repo";
import { ResumeRepo } from "./repository/resume.repo";
import { CommonMessage } from "../../common/enums/message.enum";
import { BadRequestError } from "../../core/app/app.errors";
import { IOCTYPES } from "../../IOC/ioc.types";
@injectable()
class JobService {
@inject(IOCTYPES.JobRepo) jobRepo: JobRepo;
@inject(IOCTYPES.ResumeRepo) resumeRepo: ResumeRepo;
//#####################################
//#####################################
async getAvailableJobs(queries: { limit: number; page: number }) {
const page = queries.page || 1;
const limit = queries.limit || 10;
const skip = (page - 1) * limit;
const count = await this.resumeRepo.model.countDocuments();
const jobs = await this.jobRepo.model.find().skip(skip).limit(limit);
return { jobs, count };
}
//#####################################
//#####################################
async createJob(jobDto: CreateJobDTO) {
const newJob = await this.jobRepo.model.create({
...jobDto,
});
return {
message: CommonMessage.Created,
newJob,
};
}
//#####################################
//#####################################
async submitResume(resumeDto: ResumeDTO) {
const resume = await this.resumeRepo.model.create({
...resumeDto,
});
return {
message: CommonMessage.Created,
resume,
};
}
//#####################################
//#####################################
async getResumesForJob(jobId: string) {
if (!isValidObjectId(jobId)) throw new BadRequestError(CommonMessage.NotValidId);
const resumes = await this.resumeRepo.model.find({ job: jobId }).populate("job");
return {
resumes,
};
}
//#####################################
//#####################################
async getAllResumes(queries: { limit: number; page: number }) {
const page = queries.page || 1;
const limit = queries.limit || 10;
const skip = (page - 1) * limit;
const count = await this.resumeRepo.model.countDocuments();
const resumes = await this.resumeRepo.model.find().populate("job").skip(skip).limit(limit);
return { resumes, count };
}
//#####################################
//#####################################
async deleteJob(jobId: string) {
if (!isValidObjectId(jobId)) throw new BadRequestError(CommonMessage.NotValidId);
const deleteJob = await this.jobRepo.model.findByIdAndDelete(jobId);
if (!deleteJob) throw new BadRequestError(CommonMessage.NotValidId);
return {
message: CommonMessage.Deleted,
};
}
}
export { JobService };
@@ -0,0 +1,14 @@
export enum JobType {
FullTime = "fullTime",
PartTime = "partTime",
Contract = "contract",
}
export interface IJob {
title: string;
description: string;
location: string;
type: JobType;
// salaryRange:string;
// skillsRequired:string[]
}
@@ -0,0 +1,14 @@
import { Types } from "mongoose";
import { StatusEnum } from "../../../../common/enums/status.enum";
export interface IResume {
job: Types.ObjectId;
// applicant: Types.ObjectId;
fullName: string;
fatherName: string;
placeOfBirth: string;
birthday: string;
resumeUrl: string;
status: StatusEnum;
}
+17
View File
@@ -0,0 +1,17 @@
import { Schema, model } from "mongoose";
import { IJob, JobType } from "./Abstraction/IJob";
const JobSchema = new Schema<IJob>(
{
title: { type: String, required: true },
description: { type: String, required: true },
location: { type: String, required: true },
type: { type: String, enum: JobType, required: true },
},
{ timestamps: true, toJSON: { versionKey: false, virtuals: true }, id: false },
);
const JobModel = model<IJob>("Job", JobSchema);
export { JobModel };
+21
View File
@@ -0,0 +1,21 @@
import { Schema, model } from "mongoose";
import { IResume } from "./Abstraction/IResume";
import { StatusEnum } from "../../../common/enums/status.enum";
const ResumeSchema = new Schema<IResume>(
{
job: { type: Schema.Types.ObjectId, ref: "Job", required: true },
fullName: { type: String, ref: "Job", required: true },
fatherName: { type: String, ref: "Job", required: true },
placeOfBirth: { type: String, ref: "Job", required: true },
birthday: { type: String, ref: "Job", required: true },
resumeUrl: { type: String, required: true },
status: { type: String, enum: StatusEnum, default: StatusEnum.Pending },
},
{ timestamps: true, toJSON: { versionKey: false, virtuals: true }, id: false },
);
const ResumeModel = model<IResume>("Resume", ResumeSchema);
export { ResumeModel };
+15
View File
@@ -0,0 +1,15 @@
import { BaseRepository } from "../../../common/base/repository";
import { IJob } from "../models/Abstraction/IJob";
import { JobModel } from "../models/job.model";
class JobRepo extends BaseRepository<IJob> {
constructor() {
super(JobModel);
}
}
function createJobRepo(): JobRepo {
return new JobRepo();
}
export { JobRepo, createJobRepo };
+15
View File
@@ -0,0 +1,15 @@
import { BaseRepository } from "../../../common/base/repository";
import { IResume } from "../models/Abstraction/IResume";
import { ResumeModel } from "../models/resume.model";
class ResumeRepo extends BaseRepository<IResume> {
constructor() {
super(ResumeModel);
}
}
function createResumeRepo(): ResumeRepo {
return new ResumeRepo();
}
export { ResumeRepo, createResumeRepo };