-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: Job CRUD * feat: Job CRUD server * feat: upload service * feat: upload service * feat: banner fields, CRUD
- Loading branch information
1 parent
b91453b
commit 890ff1c
Showing
9 changed files
with
378 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,70 @@ | ||
//Static | ||
import JobService from "../services/jobServices.js"; | ||
|
||
class JobController { | ||
constructor() { | ||
this.jobService = JobService; | ||
} | ||
|
||
async create(req, res) { | ||
try { | ||
const job = await this.jobService.create(req.body, req.file); | ||
res.status(201).json(job); | ||
} catch (error) { | ||
res.status(400).json({ error: error.message }); | ||
} | ||
} | ||
|
||
async findAll(req, res) { | ||
try { | ||
const jobs = await this.jobService.findAll(); | ||
res.json(jobs); | ||
} catch (error) { | ||
res.status(500).json({ error: error.message }); | ||
} | ||
} | ||
|
||
async findById(req, res) { | ||
try { | ||
const job = await this.jobService.findById(req.params.id); | ||
if (!job) { | ||
return res.status(404).json({ error: "Job not found" }); | ||
} | ||
res.json(job); | ||
} catch (error) { | ||
res.status(500).json({ error: error.message }); | ||
} | ||
} | ||
|
||
async update(req, res) { | ||
try { | ||
const job = await this.jobService.update( | ||
req.params.id, | ||
req.body, | ||
req.file | ||
); | ||
res.json(job); | ||
} catch (error) { | ||
res.status(400).json({ error: error.message }); | ||
} | ||
} | ||
|
||
async delete(req, res) { | ||
try { | ||
await this.jobService.delete(req.params.id); | ||
res.status(204).send(); | ||
} catch (error) { | ||
res.status(500).json({ error: error.message }); | ||
} | ||
} | ||
|
||
async deleteBanner(req, res) { | ||
try { | ||
const job = await this.jobService.deleteBanner(req.params.id); | ||
res.json(job); | ||
} catch (error) { | ||
res.status(400).json({ error: error.message }); | ||
} | ||
} | ||
} | ||
|
||
export default new JobController(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { EntitySchema } from 'typeorm'; | ||
|
||
const Job = new EntitySchema({ | ||
name: 'Job', | ||
tableName: 'jobs', | ||
columns: { | ||
id: { | ||
type: 'int', | ||
primary: true, | ||
generated: true | ||
}, | ||
title: { | ||
type: 'varchar', | ||
nullable: false | ||
}, | ||
description: { | ||
type: 'text', | ||
nullable: true | ||
}, | ||
company: { | ||
type: 'varchar', | ||
nullable: false | ||
}, | ||
location: { | ||
type: 'varchar', | ||
nullable: true | ||
}, | ||
salary: { | ||
type: 'decimal', | ||
nullable: true | ||
}, | ||
requiredSkills: { | ||
type: 'simple-array', | ||
nullable: true | ||
}, | ||
applicationLink: { | ||
type: 'varchar', | ||
nullable: true | ||
}, | ||
banner: { | ||
type: 'varchar', | ||
nullable: true | ||
}, | ||
bannerPublicId: { | ||
type: 'varchar', | ||
nullable: true | ||
}, | ||
createdAt: { | ||
type: 'timestamp', | ||
createDate: true | ||
}, | ||
updatedAt: { | ||
type: 'timestamp', | ||
updateDate: true | ||
} | ||
}, | ||
uniques: [ | ||
{ columns: ['title', 'company'] } | ||
] | ||
}); | ||
|
||
export default Job; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import express from "express"; | ||
import multer from "multer"; | ||
import JobController from "../controllers/job.controller.js"; | ||
|
||
const router = express.Router(); | ||
|
||
// Configure multer for file upload | ||
const storage = multer.diskStorage({ | ||
destination: "./uploads/temp", | ||
filename: (req, file, cb) => { | ||
const uniqueSuffix = Date.now() + "-" + Math.round(Math.random() * 1e9); | ||
cb( | ||
null, | ||
file.fieldname + | ||
"-" + | ||
uniqueSuffix + | ||
"." + | ||
file.originalname.split(".").pop() | ||
); | ||
} | ||
}); | ||
|
||
const upload = multer({ | ||
storage: storage, | ||
limits: { | ||
fileSize: 5 * 1024 * 1024 // 5MB limit | ||
}, | ||
fileFilter: (req, file, cb) => { | ||
if (file.mimetype.startsWith("image/")) { | ||
cb(null, true); | ||
} else { | ||
cb(new Error("Only image files are allowed!"), false); | ||
} | ||
} | ||
}); | ||
|
||
router.post( | ||
"/", | ||
upload.single("banner"), | ||
(req, res) => JobController.create(req, res) | ||
); | ||
|
||
router.get("/", (req, res) => JobController.findAll(req, res)); | ||
router.get("/:id", (req, res) => JobController.findById(req, res)); | ||
|
||
router.put( | ||
"/:id", | ||
upload.single("banner"), | ||
(req, res) => JobController.update(req, res) | ||
); | ||
|
||
router.delete("/:id", (req, res) => JobController.delete(req, res)); | ||
|
||
router.delete("/:id/banner", (req, res) => | ||
JobController.deleteBanner(req, res) | ||
); | ||
|
||
export default router; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,108 @@ | ||
//start | ||
import AppDataSource from "../config/config.js"; | ||
import Job from "../entities/job.entity.js"; | ||
import JobValidation from "../validation/job.validation.js"; | ||
import UploadService from "./upload.service.js"; | ||
|
||
class JobService { | ||
constructor() { | ||
this.jobRepository = AppDataSource.getRepository(Job); | ||
} | ||
|
||
async create(jobData, banner) { | ||
const { error } = JobValidation.validate(jobData); | ||
if (error) { | ||
throw new Error(`Validation Error: ${error.details[0].message}`); | ||
} | ||
|
||
const existingJob = await this.jobRepository.findOne({ | ||
where: { | ||
title: jobData.title, | ||
company: jobData.company | ||
} | ||
}); | ||
|
||
if (existingJob) { | ||
throw new Error("Job with this title and company already exists"); | ||
} | ||
|
||
if (banner) { | ||
const uploadResult = await UploadService.uploadFile(banner); | ||
jobData.banner = uploadResult.url; | ||
jobData.bannerPublicId = uploadResult.publicId; | ||
} | ||
|
||
const job = this.jobRepository.create(jobData); | ||
return await this.jobRepository.save(job); | ||
} | ||
|
||
async findAll() { | ||
return await this.jobRepository.find(); | ||
} | ||
|
||
async findById(id) { | ||
return await this.jobRepository.findOne({ where: { id } }); | ||
} | ||
|
||
async update(id, jobData, banner) { | ||
const { error } = JobValidation.validate(jobData); | ||
if (error) { | ||
throw new Error(`Validation Error: ${error.details[0].message}`); | ||
} | ||
|
||
const existingJob = await this.findById(id); | ||
if (!existingJob) { | ||
throw new Error("Job not found"); | ||
} | ||
|
||
if (banner) { | ||
// Delete old banner if exists | ||
if (existingJob.bannerPublicId) { | ||
await UploadService.deleteFile(existingJob.bannerPublicId); | ||
} | ||
|
||
// Upload new banner | ||
const uploadResult = await UploadService.uploadFile(banner); | ||
jobData.banner = uploadResult.url; | ||
jobData.bannerPublicId = uploadResult.publicId; | ||
} | ||
|
||
await this.jobRepository.update(id, jobData); | ||
return this.findById(id); | ||
} | ||
|
||
async delete(id) { | ||
const job = await this.findById(id); | ||
if (!job) { | ||
throw new Error("Job not found"); | ||
} | ||
|
||
// Delete banner if exists | ||
if (job.bannerPublicId) { | ||
await UploadService.deleteFile(job.bannerPublicId); | ||
} | ||
|
||
return await this.jobRepository.delete(id); | ||
} | ||
|
||
async deleteBanner(id) { | ||
const job = await this.findById(id); | ||
if (!job) { | ||
throw new Error("Job not found"); | ||
} | ||
|
||
if (!job.bannerPublicId) { | ||
throw new Error("No banner exists for this job"); | ||
} | ||
|
||
await UploadService.deleteFile(job.bannerPublicId); | ||
|
||
await this.jobRepository.update(id, { | ||
banner: null, | ||
bannerPublicId: null | ||
}); | ||
|
||
return this.findById(id); | ||
} | ||
} | ||
|
||
export default new JobService(); |
Oops, something went wrong.