-
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.
Implement Course and Exam Payment CRUD logic (#15)
* feat: Add `PaymentEntity` * refactor: Rename payment entity and update module export * feat: add create payment route * Update the 'amount' field decimal precision * feat: add `getAllPayments` and `getPaymentById` endpoints * feat: Add update and delete payments endpoints * feat: Add payments router to global router * feat: Add filter to `PaymentService.getAllPayments` method * feat: Add endpoint to retrieve sender and receiver payment data * fix: Importation error * feat: Add payment entity to `AppDataSource` * feat: Add ID parameter validation to payments route * fix: Preserve 'this' context in callback function
- Loading branch information
Showing
10 changed files
with
456 additions
and
6 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import { PaymentService } from "../services/paymentServices.js"; | ||
import { AppError } from "../utils/errors.js"; | ||
import { idParamValidation } from "../validation/common.validation.js"; | ||
import { | ||
createPaymentValidation, updatePaymentValidation | ||
} from "../validation/payment.validation.js"; | ||
|
||
|
||
class PaymentController { | ||
constructor() { | ||
this.paymentService = new PaymentService(); | ||
} | ||
|
||
async createPayment(req, res) { | ||
try { | ||
const { error, value } = createPaymentValidation(req.body); | ||
if (error) { | ||
return res.status(400).json({ message: error.details[0].message }); | ||
} | ||
const payment = await this.paymentService.createPayment(value); | ||
|
||
res.status(201).json({ data: payment }); | ||
} catch (error) { | ||
res.status(400).json({ message: error.message }); | ||
} | ||
} | ||
|
||
async getSenderPayments(req, res) { | ||
try { | ||
const senderId = idParamValidation(req.params); | ||
const payments = await this.paymentService.getSenderPayments(senderId); | ||
|
||
res.status(200).json({ data: payments }); | ||
} catch (error) { | ||
if (error instanceof AppError) { | ||
res.status(error.statusCode).json({ message: error.message }); | ||
} else { | ||
res.status(400).json({ message: error.message }); | ||
} | ||
} | ||
} | ||
|
||
async getReceiverPayments(req, res) { | ||
try { | ||
const receiverId = req.params.id; | ||
const payments = await this.paymentService.getReceiverPayments(receiverId); | ||
|
||
res.status(200).json({ data: payments }); | ||
} catch (error) { | ||
if (error instanceof AppError) { | ||
res.status(error.statusCode).json({ message: error.message }); | ||
} else { | ||
res.status(400).json({ message: error.message }); | ||
} | ||
} | ||
} | ||
|
||
async getAllPayments(req, res) { | ||
try { | ||
const payments = await this.paymentService.getAllPayments(); | ||
|
||
res.status(200).json({ data: payments }); | ||
} catch (error) { | ||
res.status(400).json({ message: error.message }); | ||
} | ||
} | ||
|
||
async getPaymentById(req, res) { | ||
try { | ||
const id = req.params.id; | ||
const payment = await this.paymentService.getPaymentByid(id); | ||
|
||
res.status(200).json({ data: payment }); | ||
} catch (error) { | ||
if (error instanceof AppError) { | ||
res.status(error.statusCode).json({ message: error.message }); | ||
} else { | ||
res.status(400).json({ message: error.message }); | ||
} | ||
} | ||
} | ||
|
||
async updatePayment(req, res) { | ||
try { | ||
const id = idParamValidation(req.params); | ||
const { error, value } = updatePaymentValidation(req.body); | ||
if (error) { | ||
return res.status(400).json({ message: error.details[0].message }); | ||
} | ||
const payment = await this.paymentService.updatePayment(id, value); | ||
|
||
res.status(200).json({ data: payment }); | ||
} catch (error) { | ||
if (error instanceof AppError) { | ||
res.status(error.statusCode).json({ message: error.message }); | ||
} else { | ||
res.status(400).json({ message: error.message }); | ||
} | ||
} | ||
} | ||
|
||
async deletePayment(req, res) { | ||
try { | ||
const id = req.params.id; | ||
await this.paymentService.deletePayment(id); | ||
res.status(200).json({ message: "Delete operation was successful"}); | ||
} catch (error) { | ||
res.status(400).json({ error: error.message }); | ||
} | ||
} | ||
} | ||
|
||
export default new PaymentController(); |
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,71 @@ | ||
import { EntitySchema } from 'typeorm'; | ||
|
||
export const Payment = new EntitySchema({ | ||
name: 'Payment', | ||
tableName: 'payment', | ||
columns: { | ||
id: { | ||
primary: true, | ||
type: 'int', | ||
generated: true, | ||
}, | ||
senderId: { | ||
type: 'int', | ||
nullable: true, | ||
}, | ||
receiverId: { | ||
type: 'int', | ||
nullable: true, | ||
}, | ||
date: { | ||
type: 'timestamptz', | ||
default: () => "CURRENT_TIMESTAMP", | ||
nullable: false, | ||
}, | ||
amount: { | ||
type: 'decimal', | ||
precision: 10, | ||
scale: 5, | ||
nullable: false, | ||
}, | ||
payment_code: { | ||
type: 'varchar', | ||
length: 50, | ||
nullable: false, | ||
unique: true | ||
}, | ||
transaction_type: { | ||
type: "enum", | ||
enum: [ | ||
"course_creation", | ||
"course_enrollment", | ||
"certification_exam" | ||
], | ||
nullable: false, | ||
}, | ||
created_at: { | ||
type: 'timestamptz', | ||
default: () => 'CURRENT_TIMESTAMP' | ||
}, | ||
}, | ||
relations: { | ||
sender: { | ||
type: "many-to-one", | ||
target: "User", | ||
joinColumn: { | ||
name: 'senderId', | ||
referencedColumnName: 'id' | ||
}, | ||
onDelete: 'SET NULL' | ||
}, | ||
receiver: { | ||
type: 'many-to-one', | ||
target: 'User', | ||
joinColumn: { | ||
name: 'receiverId', | ||
referencedColumnName: 'id' | ||
}, | ||
onDelete: 'SET NULL' | ||
} | ||
} | ||
}); |
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,49 @@ | ||
//start | ||
import express from "express"; | ||
import { authenticateToken } from "../middlewares/auth.middleware.js"; | ||
import PaymentController from "../controllers/payment.controller.js"; | ||
|
||
const router = express.Router(); | ||
|
||
router.post( | ||
"/", | ||
authenticateToken, | ||
(req, res) => PaymentController.createPayment(req, res) | ||
); | ||
|
||
router.get( | ||
"/sender/:id", | ||
authenticateToken, | ||
(req, res) => PaymentController.getSenderPayments(req, res) | ||
); | ||
|
||
router.get( | ||
"/receiver/:id", | ||
authenticateToken, | ||
(req, res) => PaymentController.getReceiverPayments(req, res) | ||
); | ||
|
||
router.get( | ||
"/", | ||
authenticateToken, | ||
(req, res) => PaymentController.getAllPayments(req, res) | ||
); | ||
|
||
router.get( | ||
"/:id", | ||
authenticateToken, | ||
(req, res) => PaymentController.getPaymentById(req, res) | ||
); | ||
|
||
router.put( | ||
"/:id", | ||
authenticateToken, | ||
(req, res) => PaymentController.updatePayment(req, res) | ||
); | ||
|
||
router.delete( | ||
"/:id", | ||
authenticateToken, | ||
(req, res) => PaymentController.deletePayment(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 |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import AppDataSource from "../config/config.js"; | ||
import { Payment } from "../entities/payment.entity.js"; | ||
import userService from "../services/userServices.js"; | ||
import { AppError } from "../utils/errors.js"; | ||
|
||
export class PaymentService { | ||
constructor() { | ||
this.repository = AppDataSource.getRepository(Payment); | ||
} | ||
|
||
async createPayment(paymentData) { | ||
const existingPayment = await this.repository.findOne({ | ||
where: { | ||
payment_code: paymentData.payment_code | ||
} | ||
}); | ||
if (existingPayment) { | ||
throw new Error("Duplicate Payment Code"); | ||
} | ||
const payment = this.repository.create(paymentData); | ||
|
||
return this.repository.save(payment); | ||
} | ||
|
||
async getAllPayments(filter) { | ||
return this.repository.find({ where: filter }); | ||
} | ||
|
||
async getSenderPayments(senderId) { | ||
const sender = await userService.getUserById(senderId); | ||
|
||
return this.getAllPayments({ senderId }); | ||
} | ||
|
||
async getReceiverPayments(receiverId) { | ||
const receiver = await userService.getUserById(receiverId); | ||
|
||
return this.getAllPayments({ receiverId }); | ||
} | ||
|
||
async getPaymentByid(id) { | ||
const payment = await this.repository.findOne({ where: { id } }); | ||
if (!payment) { | ||
throw new AppError(`Payment with ID ${id} not found.`, 404); | ||
} | ||
|
||
return payment; | ||
} | ||
|
||
async updatePayment(id, updateData) { | ||
const payment = await this.getPaymentByid(id); | ||
Object.assign(payment, updateData); | ||
|
||
return this.repository.save(payment); | ||
} | ||
|
||
async deletePayment(id) { | ||
const payment = await this.getPaymentByid(id); | ||
|
||
return this.repository.delete(id); | ||
} | ||
} |
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,22 @@ | ||
import AppDataSource from "../config/config.js"; | ||
import { AppError } from "../utils/errors.js"; | ||
import User from "../entities/user.entity.js"; | ||
|
||
class UserService { | ||
constructor() { | ||
this.userRepository = AppDataSource.getRepository(User); | ||
} | ||
|
||
async getUserById(id) { | ||
const user = await this.userRepository.findOne({ | ||
where: { id } | ||
}); | ||
if (!user) { | ||
throw new AppError(`User with this ID ${id} not found`, 404); | ||
} | ||
|
||
return user; | ||
} | ||
} | ||
|
||
export default new UserService(); |
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,6 @@ | ||
export class AppError extends Error { | ||
constructor(message, statusCode=400) { | ||
super(message); | ||
this.statusCode = statusCode | ||
} | ||
} |
Oops, something went wrong.