Skip to content

Commit

Permalink
feat: Job CRUD (#6)
Browse files Browse the repository at this point in the history
* feat: Job CRUD

* feat: Job CRUD server

* feat: upload service

* feat: upload service

* feat: banner fields, CRUD
  • Loading branch information
anonfedora authored Jan 28, 2025
1 parent b91453b commit 890ff1c
Show file tree
Hide file tree
Showing 9 changed files with 378 additions and 5 deletions.
5 changes: 4 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@ DB_USER=postgres
DB_PASSWORD=your_password
DB_NAME=skillnet
JWT_SECRET=your_jwt_secret_key
PORT=5000
PORT=5000
CLOUDINARY_CLOUD_NAME=your_cloud_name
CLOUDINARY_API_KEY=your_api_key
CLOUDINARY_API_SECRET=your_api_secret
3 changes: 2 additions & 1 deletion config/config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { DataSource } from 'typeorm';
import User from '../entities/user.entity.js';
import Job from '../entities/job.entity.js';

const AppDataSource = new DataSource({
type: 'postgres',
Expand All @@ -11,7 +12,7 @@ const AppDataSource = new DataSource({
synchronize: true,
dropSchema: false,
logging: false,
entities: [User],
entities: [User, Job],
subscribers: [],
migrations: [],
});
Expand Down
71 changes: 70 additions & 1 deletion controllers/job.controller.js
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();
62 changes: 62 additions & 0 deletions entities/job.entity.js
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;
58 changes: 58 additions & 0 deletions routes/jobRoutes.js
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;
5 changes: 4 additions & 1 deletion server.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import 'dotenv/config'
import AppDataSource from "./config/config.js";
import express, { json } from 'express';
import authRouter from './routes/auth.routes.js';
import jobRoutes from './routes/jobRoutes.js';

import helmet from 'helmet';
import cors from 'cors';

Expand All @@ -20,7 +22,8 @@ AppDataSource.initialize()

// Routes
app.use('/api/auth', authRouter);

app.use('/api/jobs', jobRoutes);

// Error handling
app.use((err, req, res, next) => {
console.error(err.stack);
Expand Down
109 changes: 108 additions & 1 deletion services/jobServices.js
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();
Loading

0 comments on commit 890ff1c

Please sign in to comment.