Skip to content

Commit

Permalink
IMPLEMENT THE COURSE CRUD LOGIC (#8)
Browse files Browse the repository at this point in the history
* Add Course and UserCourse entities, integrate relationships, update auth middleware for CRUD operations, implement course routes, controller, and service

* updated the config file

* Updated the couseEntity to have Course video record stored

* resoved the dotenv issue

* updated the inport of entities in the connfig file

* updated inport in the jobService

* updated naming convention conflic resolve

* adjusted the server file and config

* resolved the conflict between App.js and Server.js
  • Loading branch information
OthmanImam authored Feb 1, 2025
1 parent 890ff1c commit 2095750
Show file tree
Hide file tree
Showing 12 changed files with 560 additions and 28 deletions.
27 changes: 16 additions & 11 deletions config/config.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
import { DataSource } from 'typeorm';
import User from '../entities/user.entity.js';
import Job from '../entities/job.entity.js';
import dotenv from "dotenv"
import { DataSource } from "typeorm";
import { Course } from "../entities/course.entity.js"
import { UserCourse } from "../entities/userCourse.entity.js"
import UserEntity from "../entities/user.entity.js";
import Job from "../entities/job.entity.js";

dotenv.config()

const AppDataSource = new DataSource({
type: 'postgres',
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT || '5432'),
type: "postgres",
host: process.env.DB_HOST,
port: Number.parseInt(process.env.DB_PORT),
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
synchronize: true,
dropSchema: false,
logging: false,
entities: [User, Job],
logging: true,
entities: [UserEntity, Course, UserCourse, Job],
subscribers: [],
migrations: [],
});
})

export default AppDataSource;

export default AppDataSource;
126 changes: 126 additions & 0 deletions controllers/course.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { CourseService } from "../services/courseServices.js"
import courseValidation from "../validation/course.validation.js"

class CourseController {
async createCourse(req, res) {
try {
const { error, value } = courseValidation.createCourse.validate(req.body)
if (error) {
return res.status(400).json({ message: error.details[0].message })
}

const course = await CourseService.createCourse(value)
res.status(201).json(course)
} catch (error) {
if (error.message === "Course already exists") {
res.status(409).json({ message: error.message })
} else {
res.status(400).json({ message: "Error creating course", error: error.message })
}
}
}

async getAllCourses(req, res) {
try {
const courses = await CourseService.getAllCourses()
res.json(courses)
} catch (error) {
res.status(500).json({ message: "Error fetching courses", error: error.message })
}
}

async getCourseById(req, res) {
try {
const { error, value } = courseValidation.courseIdParam.validate({ id: req.params.id })
if (error) {
return res.status(400).json({ message: error.details[0].message })
}

const course = await CourseService.getCourseById(value.id)
if (course) {
res.json(course)
} else {
res.status(404).json({ message: "Course not found" })
}
} catch (error) {
res.status(500).json({ message: "Error fetching course", error: error.message })
}
}

async updateCourse(req, res) {
try {
const idValidation = courseValidation.courseIdParam.validate({ id: req.params.id })
if (idValidation.error) {
return res.status(400).json({ message: idValidation.error.details[0].message })
}

const { error, value } = courseValidation.updateCourse.validate(req.body)
if (error) {
return res.status(400).json({ message: error.details[0].message })
}

const course = await CourseService.updateCourse(idValidation.value.id, value)
if (course) {
res.json(course)
} else {
res.status(404).json({ message: "Course not found" })
}
} catch (error) {
if (error.message === "Course already exists") {
res.status(409).json({ message: error.message })
} else {
res.status(400).json({ message: "Error updating course", error: error.message })
}
}
}

async deleteCourse(req, res) {
try {
const { error, value } = courseValidation.courseIdParam.validate({ id: req.params.id })
if (error) {
return res.status(400).json({ message: error.details[0].message })
}

await CourseService.deleteCourse(value.id)
res.status(204).send()
} catch (error) {
if (error.message === "Course not found") {
res.status(404).json({ message: error.message })
} else {
res.status(500).json({ message: "Error deleting course", error: error.message })
}
}
}

async updateCourseVideo(req, res) {
try {
const { error: idError, value: idValue } = courseValidation.courseIdParam.validate({ id: req.params.id })
if (idError) {
return res.status(400).json({ message: idError.details[0].message })
}

if (!req.file) {
return res.status(400).json({ message: "No video file uploaded" })
}

const videoUrl = `/uploads/courses/videos/${req.file.filename}`

const { error, value } = courseValidation.updateCourseVideo.validate({ videoUrl })
if (error) {
return res.status(400).json({ message: error.details[0].message })
}

const updatedCourse = await CourseService.updateCourseVideo(idValue.id, value.videoUrl)
if (updatedCourse) {
res.json(updatedCourse)
} else {
res.status(404).json({ message: "Course not found" })
}
} catch (error) {
res.status(500).json({ message: "Error updating course video", error: error.message })
}
}
}

export default new CourseController()

65 changes: 65 additions & 0 deletions entities/course.entity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { EntitySchema } from "typeorm"

export const Course = new EntitySchema({
name: "Course",
tableName: "courses",
columns: {
id: {
type: "int",
primary: true,
generated: true,
},
title: {
type: "varchar",
unique: true,
nullable: false,
},
description: {
type: "text",
nullable: true,
},
instructorId: {
type: "int",
nullable: false,
},
level: {
type: "varchar",
nullable: true,
},
duration: {
type: "int",
nullable: true,
},
videoUrl: {
type: "varchar",
nullable: true,
},
createdAt: {
type: "timestamp",
createDate: true,
},
},
relations: {
users: {
type: "many-to-many",
target: "UserEntity",
joinTable: {
name: "user_courses",
joinColumn: {
name: "courseId",
referencedColumnName: "id",
},
inverseJoinColumn: {
name: "userId",
referencedColumnName: "id",
},
},
},
userCourses: {
type: "one-to-many",
target: "UserCourse",
inverseSide: "course",
},
},
})

28 changes: 23 additions & 5 deletions entities/user.entity.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ const UserEntity = new EntitySchema({
type: 'varchar',
length: 20,
},
// Generalizes to job seeker/employer/institution name
name: {
type: 'varchar',
length: 100,
Expand All @@ -26,18 +25,15 @@ const UserEntity = new EntitySchema({
type: 'varchar',
unique: true,
},
// Tutor-specific (nullable)
title: {
type: 'varchar',
length: 100,
nullable: true,
},
// Used as bio (Job Seeker, Tutor, Institution)/description (Company)
bio: {
type: 'text',
nullable: true,
},
// Tutor-specific (nullable)
skills: {
type: 'simple-array',
nullable: true,
Expand All @@ -47,11 +43,33 @@ const UserEntity = new EntitySchema({
createDate: true,
},
},
relations: {
courses: {
type: 'many-to-many',
target: 'Course',
joinTable: {
name: 'user_courses',
joinColumn: {
name: 'userId',
referencedColumnName: 'id',
},
inverseJoinColumn: {
name: 'courseId',
referencedColumnName: 'id',
},
},
},
userCourses: {
type: 'one-to-many',
target: 'UserCourse',
inverseSide: 'user',
},
},
indices: [
{ columns: ['walletAddress'] },
{ columns: ['email'] },
{ columns: ['role'] }, // Index role if querying by it frequently
],
});

export default UserEntity;
export default UserEntity
45 changes: 45 additions & 0 deletions entities/userCourse.entity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { EntitySchema } from "typeorm"

export const UserCourse = new EntitySchema({
name: "UserCourse",
tableName: "user_courses",
columns: {
userId: {
primary: true,
type: "int",
},
courseId: {
primary: true,
type: "int",
},
progress: {
type: "int",
default: 0,
},
enrollmentDate: {
type: "timestamp",
createDate: true,
},
},
relations: {
user: {
type: "many-to-one",
target: "User",
joinColumn: {
name: "userId",
referencedColumnName: "id",
},
onDelete: "CASCADE",
},
course: {
type: "many-to-one",
target: "Course",
joinColumn: {
name: "courseId",
referencedColumnName: "id",
},
onDelete: "CASCADE",
},
},
})

46 changes: 46 additions & 0 deletions http/courseRoute.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
@baseUrl = http://localhost:3000/api/courses

### Create a new course
POST {{baseUrl}}
Content-Type: application/json

{
"title": "Advance Blockchain",
"description": "Learn the basics of blockchain technology",
"instructorId": 1,
"level": "beginner",
"duration": 5
}

### Get all courses
GET {{baseUrl}}

### Get a specific course
GET {{baseUrl}}/1

### Update a course
PUT {{baseUrl}}/1
Content-Type: application/json
{
"title": "Advanced Blockchain Concepts",
"description": "Dive deeper into blockchain technology and its applications"
}

### Delete a course
DELETE {{baseUrl}}/4

### Create another course
POST {{baseUrl}}
Content-Type: application/json

{
"title": "Advance Smart Contract Development",
"description": "Learn how to create and deploy smart contracts on blockchain platforms",
"instructorId": 2,
"level": "beginner",
"duration": 5
}

### Get all courses after operations
GET {{baseUrl}}

Loading

0 comments on commit 2095750

Please sign in to comment.