├── Assignment13 ├── ci-pipeline.md ├── image.png ├── image-1.png ├── image-2.png ├── image-3.png └── PROTECTION.md ├── jest.config.cjs ├── Assignment14 ├── Voting_Results.md ├── Contributing.md ├── License.md └── Reflection.md ├── Assignment11 ├── repositories │ ├── interfaces │ │ ├── UserRepository.mjs │ │ ├── GradeRepository.mjs │ │ ├── PointRepository.mjs │ │ ├── StudentRepository.mjs │ │ ├── AdminRepository.mjs │ │ ├── DisputeRepository.mjs │ │ ├── EducatorRepository.mjs │ │ ├── RedemptionRepository.mjs │ │ ├── NotificationRepository.mjs │ │ ├── RedemptionRuleRepository.mjs │ │ └── Repository.mjs │ └── inMemory │ │ ├── InMemoryUserRepository.mjs │ │ ├── InMemoryAdminRepository.mjs │ │ ├── InMemoryGradeRepository.mjs │ │ ├── InMemoryPointRepository.mjs │ │ ├── InMemoryDisputeRepository.mjs │ │ ├── InMemoryStudentRepository.mjs │ │ ├── InMemoryEducatorRepository.mjs │ │ ├── InMemoryRedemptionRepository.mjs │ │ ├── InMemoryNotificationRepository.mjs │ │ └── InMemoryRedemptionRuleRepository.mjs ├── entities │ ├── AdminEntity.mjs │ ├── EducatorEntity.mjs │ ├── GradeEntity.mjs │ ├── RedemptionRule.mjs │ ├── PointEntity.mjs │ ├── RedemptionEntity.mjs │ ├── StudentEntity.mjs │ ├── NotificationEntity.mjs │ ├── UserEntity.mjs │ └── DisputeEntity.mjs ├── factories │ ├── UserRepositoryFactory.mjs │ ├── AdminRepositoryFactory.mjs │ ├── PointRepositoryFactory.mjs │ ├── DisputeRepositoryFactory.mjs │ ├── StudentRepositoryFactory.mjs │ ├── EducatorRepositoryFactory.mjs │ ├── GradeRepositoryFactory.mjs │ ├── RepositoryFactory.mjs │ ├── RedemptionRepositoryFactory.mjs │ ├── NotificationRepositoryFactory.mjs │ └── RedemptionRuleRepositoryFactory.mjs ├── README.md ├── database │ ├── DatabaseUserRepository.mjs │ ├── DatabaseAdminRepository.mjs │ ├── DatabaseGradeRepository.mjs │ ├── DatabasePointRepository.mjs │ ├── DatabaseDisputeRepository.mjs │ ├── DatabaseStudentRepository.mjs │ ├── DatabaseEducatorRepository.mjs │ ├── DatabaseRedemptionRepository.mjs │ ├── DatabaseNotificationRepository.mjs │ └── DatabaseRedemptionRuleRepository.mjs └── tests │ ├── InMemoryAdminRepository.test.mjs │ ├── InMemoryGradeRepository.test.mjs │ ├── InMemoryPointRepository.test.mjs │ ├── InMemoryStudentRepository.test.mjs │ ├── InMemoryEducatorRepository.test.mjs │ ├── InMemoryUserRepository.test.mjs │ ├── InMemoryRedemptionRuleRepository.test.mjs │ ├── InMemoryRedemptionRepository.test.mjs │ ├── InMemoryNotificationRepository.test.mjs │ └── InMemoryDisputeRepository.test.mjs ├── Assignment10 ├── creational_patterns │ ├── redemptionrule-patterns.mjs │ ├── admin-patterns.mjs │ ├── redemption-patterns.mjs │ ├── point-patterns.mjs │ ├── educator-patterns.mjs │ ├── grade-patterns.mjs │ ├── dispute-patterns.mjs │ ├── notification-patterns.mjs │ ├── user-patterns.mjs │ └── student-patterns.mjs ├── src │ ├── point.mjs │ ├── redemptionrule.mjs │ ├── admin.mjs │ ├── dispute.mjs │ ├── educator.mjs │ ├── grade.mjs │ ├── notification.mjs │ ├── redemption.mjs │ ├── user.mjs │ └── student.mjs ├── unit_tests │ ├── student-pattern.test.mjs │ ├── admin-pattern.test.mjs │ ├── dispute-pattern.test.mjs │ ├── educator-pattern.test.mjs │ ├── point-pattern.test.mjs │ ├── notification-pattern.test.mjs │ ├── redemption-pattern.test.mjs │ ├── redemptionrule-pattern.test.mjs │ ├── grade-pattern.test.mjs │ └── user-pattern.test.mjs └── README.md ├── package.json ├── Assignment7 ├── reflection.md ├── kanban_explanation.md └── template_analysis.md ├── ci.yaml ├── Assignment3 ├── README.md ├── ARCHITECTURE.md └── SPECIFICATION.md ├── Assignment6 ├── AgilePlanningDocument.md ├── SprintPlanningTable.md ├── Reflection.md ├── ProductBacklogCreation.md └── UserStoryCreation.md ├── Assignment4 └── Reflection.md ├── Assignment12 ├── services │ ├── AdminService.mjs │ ├── UserService.mjs │ ├── PointService.mjs │ ├── GradeService.mjs │ ├── DisputeService.mjs │ ├── EducatorService.mjs │ ├── StudentService.mjs │ ├── RedemptionService.mjs │ ├── NotificationService.mjs │ └── RedemptionRuleService.mjs └── api │ ├── AdminApi.mjs │ ├── UserApi.mjs │ ├── GradeApi.mjs │ ├── PointApi.mjs │ ├── StudentApi.mjs │ ├── DisputeApi.mjs │ ├── EducatorApi.mjs │ ├── RedemptionApi.mjs │ ├── NotificationApi.mjs │ └── RedemptionRuleApi.mjs ├── Assignment8 ├── StateTransitionExplanation.md └── StateTransitionDiagrams.md ├── README.md ├── .github └── workflows │ └── ci.yml ├── Assignment9 ├── DomainModelDocumentation.md ├── Reflection.md └── ClassDiagram.md └── Assignment5 └── Use Case Diagrams ├── writtenExplanation.md └── testCasesTable.md /Assignment13/ci-pipeline.md: -------------------------------------------------------------------------------- 1 | ![Test Automation](image.png) -------------------------------------------------------------------------------- /jest.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transform: {}, 3 | testEnvironment: 'node', 4 | }; 5 | -------------------------------------------------------------------------------- /Assignment14/Voting_Results.md: -------------------------------------------------------------------------------- 1 | **Voting Engagement** 2 | 3 | Fork count: 19 forks 4 | Star count: 22 stars -------------------------------------------------------------------------------- /Assignment13/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaymeechelsea/grade-points-system/HEAD/Assignment13/image.png -------------------------------------------------------------------------------- /Assignment13/image-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaymeechelsea/grade-points-system/HEAD/Assignment13/image-1.png -------------------------------------------------------------------------------- /Assignment13/image-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaymeechelsea/grade-points-system/HEAD/Assignment13/image-2.png -------------------------------------------------------------------------------- /Assignment13/image-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaymeechelsea/grade-points-system/HEAD/Assignment13/image-3.png -------------------------------------------------------------------------------- /Assignment11/repositories/interfaces/UserRepository.mjs: -------------------------------------------------------------------------------- 1 | import { Repository } from './Repository.mjs' 2 | 3 | export class UserRepository extends Repository { 4 | 5 | } -------------------------------------------------------------------------------- /Assignment11/repositories/interfaces/GradeRepository.mjs: -------------------------------------------------------------------------------- 1 | import { Repository } from './Repository.mjs' 2 | 3 | export class GradeRepository extends Repository { 4 | 5 | } -------------------------------------------------------------------------------- /Assignment11/repositories/interfaces/PointRepository.mjs: -------------------------------------------------------------------------------- 1 | import { Repository } from './Repository.mjs' 2 | 3 | export class PointRepository extends Repository { 4 | 5 | } -------------------------------------------------------------------------------- /Assignment11/repositories/interfaces/StudentRepository.mjs: -------------------------------------------------------------------------------- 1 | import { Repository } from './Repository.mjs' 2 | export class StudentRepository extends Repository { 3 | 4 | } -------------------------------------------------------------------------------- /Assignment11/repositories/interfaces/AdminRepository.mjs: -------------------------------------------------------------------------------- 1 | import { Repository } from './Repository.mjs' 2 | 3 | export class AdminRepositoryInterface extends Repository { 4 | 5 | } -------------------------------------------------------------------------------- /Assignment11/repositories/interfaces/DisputeRepository.mjs: -------------------------------------------------------------------------------- 1 | import { Repository } from './Repository.mjs' 2 | 3 | export class DisputeRepository extends Repository { 4 | 5 | } -------------------------------------------------------------------------------- /Assignment11/repositories/interfaces/EducatorRepository.mjs: -------------------------------------------------------------------------------- 1 | import { Repository } from './Repository.mjs' 2 | 3 | export class EducatorRepository extends Repository { 4 | 5 | } -------------------------------------------------------------------------------- /Assignment11/repositories/interfaces/RedemptionRepository.mjs: -------------------------------------------------------------------------------- 1 | import { Repository } from './Repository.mjs' 2 | 3 | export class RedemptionRepository extends Repository { 4 | 5 | } -------------------------------------------------------------------------------- /Assignment11/repositories/interfaces/NotificationRepository.mjs: -------------------------------------------------------------------------------- 1 | import { Repository } from './Repository.mjs' 2 | 3 | export class notificationRepository extends Repository { 4 | 5 | } -------------------------------------------------------------------------------- /Assignment11/repositories/interfaces/RedemptionRuleRepository.mjs: -------------------------------------------------------------------------------- 1 | import { Repository } from './Repository.mjs' 2 | 3 | export class RedemptionRuleRepository extends Repository { 4 | 5 | } -------------------------------------------------------------------------------- /Assignment11/entities/AdminEntity.mjs: -------------------------------------------------------------------------------- 1 | export class AdminEntity { 2 | constructor(admin_id, user_id, permissions, created_at) { 3 | this.admin_id = admin_id; 4 | this.user_id = user_id; 5 | this.permissions = permissions; 6 | this.created_at = created_at; 7 | } 8 | } -------------------------------------------------------------------------------- /Assignment11/entities/EducatorEntity.mjs: -------------------------------------------------------------------------------- 1 | export class EducatorEntity { 2 | constructor(educator_id, user_id, department, courses, created_at) { 3 | this.educator_id = educator_id; 4 | this.user_id = user_id; 5 | this.department = department; 6 | this.courses = courses; 7 | this.created_at = created_at; 8 | } 9 | } -------------------------------------------------------------------------------- /Assignment11/entities/GradeEntity.mjs: -------------------------------------------------------------------------------- 1 | export class GradeEntity { 2 | constructor(grade_id, student_id, course_id, grade_value, created_at) { 3 | this.grade_id = grade_id; 4 | this.student_id = student_id; 5 | this.course_id = course_id; 6 | this.grade_value = grade_value; 7 | this.created_at = created_at; 8 | } 9 | } -------------------------------------------------------------------------------- /Assignment11/entities/RedemptionRule.mjs: -------------------------------------------------------------------------------- 1 | export class RedemptionRuleEntity { 2 | constructor(rule_id, description, points_required, reward, created_at) { 3 | this.rule_id = rule_id; 4 | this.description = description; 5 | this.points_required = points_required; 6 | this.reward = reward; 7 | this.created_at = created_at; 8 | } 9 | } -------------------------------------------------------------------------------- /Assignment11/entities/PointEntity.mjs: -------------------------------------------------------------------------------- 1 | export class PointEntity { 2 | constructor(point_id, student_id, points_earned, points_redeemed, created_at) { 3 | this.point_id = point_id; 4 | this.student_id = student_id; 5 | this.points_earned = points_earned; 6 | this.points_redeemed = points_redeemed; 7 | this.created_at = created_at; 8 | } 9 | } -------------------------------------------------------------------------------- /Assignment11/entities/RedemptionEntity.mjs: -------------------------------------------------------------------------------- 1 | export class RedemptionEntity { 2 | constructor(redemption_id, student_id, points_redeemed, reward, created_at) { 3 | this.redemption_id = redemption_id; 4 | this.student_id = student_id; 5 | this.points_redeemed = points_redeemed; 6 | this.reward = reward; 7 | this.created_at = created_at; 8 | } 9 | } -------------------------------------------------------------------------------- /Assignment11/entities/StudentEntity.mjs: -------------------------------------------------------------------------------- 1 | export class StudentEntity { 2 | constructor(student_id, user_id, total_points, redeemed_points, grade_average) { 3 | this.student_id = student_id; 4 | this.user_id = user_id; 5 | this.total_points = total_points; 6 | this.redeemed_points = redeemed_points; 7 | this.grade_average = grade_average; 8 | } 9 | } -------------------------------------------------------------------------------- /Assignment10/creational_patterns/redemptionrule-patterns.mjs: -------------------------------------------------------------------------------- 1 | import RedemptionRule from '../src/redemptionrule.mjs'; 2 | 3 | export default class SimpleRedemptionRuleFactory { 4 | static createRule(rule_id, conversion_rate, updated_by, effective_from = new Date(), created_at = new Date()) { 5 | return new RedemptionRule(rule_id, conversion_rate, updated_by, effective_from, created_at); 6 | } 7 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grade-points-system", 3 | "version": "16.20.2", 4 | "type": "module", 5 | "scripts": { 6 | "test": "node --experimental-vm-modules node_modules/.bin/jest --config jest.config.cjs" 7 | }, 8 | "devDependencies": { 9 | "@babel/core": "^7.26.10", 10 | "@babel/preset-env": "^7.26.9", 11 | "babel-jest": "^29.7.0", 12 | "jest": "^29.7.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Assignment11/entities/NotificationEntity.mjs: -------------------------------------------------------------------------------- 1 | export class NotificationEntity { 2 | constructor(notification_id, user_id, message, type, is_read, created_at) { 3 | this.notification_id = notification_id; 4 | this.user_id = user_id; 5 | this.message = message; 6 | this.type = type; 7 | this.is_read = is_read; 8 | this.created_at = created_at; 9 | } 10 | } -------------------------------------------------------------------------------- /Assignment11/entities/UserEntity.mjs: -------------------------------------------------------------------------------- 1 | export class UserEntity { 2 | constructor(user_id, name, email, password_hash, role, is_verified, created_at) { 3 | this.user_id = user_id; 4 | this.name = name; 5 | this.email = email; 6 | this.password_hash = password_hash; 7 | this.role = role; 8 | this.is_verified = is_verified; 9 | this.created_at = created_at; 10 | } 11 | } -------------------------------------------------------------------------------- /Assignment11/entities/DisputeEntity.mjs: -------------------------------------------------------------------------------- 1 | export class DisputeEntity { 2 | constructor(dispute_id, student_id, description, status, created_at, resolved_at = null) { 3 | this.dispute_id = dispute_id; 4 | this.student_id = student_id; 5 | this.description = description; 6 | this.status = status; 7 | this.created_at = created_at; 8 | this.resolved_at = resolved_at; 9 | } 10 | } -------------------------------------------------------------------------------- /Assignment14/Contributing.md: -------------------------------------------------------------------------------- 1 | **Contributing Documentation** 2 | 3 | 1) Setup Instructions 4 | - Prerequisites: Install Node.js (v16+ recommended) 5 | - Clone Repository: git clone https://github.com/your-username/grade-points-system.git 6 | - Install Dependencies: npm install 7 | 8 | 2) Coding Standards 9 | - Linting: Follow consistent JavaScript/ESM formatting 10 | - Testing: Write and run unit tests using Jest: 11 | - Naming: Use camelCase for variables and PascalCase for classes 12 | -------------------------------------------------------------------------------- /Assignment11/factories/UserRepositoryFactory.mjs: -------------------------------------------------------------------------------- 1 | import { InMemoryUserRepository } from '../repositories/inMemory/InMemoryUserRepository.mjs'; 2 | 3 | export class UserRepositoryFactory { 4 | static getRepository(storageType = 'MEMORY') { 5 | switch (storageType) { 6 | case 'MEMORY': 7 | return new InMemoryUserRepository(); 8 | default: 9 | throw new Error('Invalid storage type for UserRepository'); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Assignment11/repositories/interfaces/Repository.mjs: -------------------------------------------------------------------------------- 1 | export class Repository { 2 | save(entity) { 3 | throw new Error('Method "save" must be implemented.'); 4 | } 5 | 6 | findById(id) { 7 | throw new Error('Method "findById" must be implemented.'); 8 | } 9 | 10 | findAll() { 11 | throw new Error('Method "findAll" must be implemented.'); 12 | } 13 | 14 | delete(id) { 15 | throw new Error('Method "delete" must be implemented.'); 16 | } 17 | } -------------------------------------------------------------------------------- /Assignment11/factories/AdminRepositoryFactory.mjs: -------------------------------------------------------------------------------- 1 | import { InMemoryAdminRepository } from '../repositories/inMemory/InMemoryAdminRepository.mjs'; 2 | 3 | export class AdminRepositoryFactory { 4 | static getRepository(storageType = 'MEMORY') { 5 | switch (storageType) { 6 | case 'MEMORY': 7 | return new InMemoryAdminRepository(); 8 | default: 9 | throw new Error('Invalid storage type for AdminRepository'); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Assignment11/factories/PointRepositoryFactory.mjs: -------------------------------------------------------------------------------- 1 | import { InMemoryPointRepository } from '../repositories/inMemory/InMemoryPointRepository.mjs'; 2 | 3 | export class PointRepositoryFactory { 4 | static getRepository(storageType = 'MEMORY') { 5 | switch (storageType) { 6 | case 'MEMORY': 7 | return new InMemoryPointRepository(); 8 | default: 9 | throw new Error('Invalid storage type for PointRepository'); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Assignment11/factories/DisputeRepositoryFactory.mjs: -------------------------------------------------------------------------------- 1 | import { InMemoryDisputeRepository } from '../repositories/inMemory/InMemoryDisputeRepository.mjs'; 2 | 3 | export class DisputeRepositoryFactory { 4 | static getRepository(storageType = 'MEMORY') { 5 | switch (storageType) { 6 | case 'MEMORY': 7 | return new InMemoryDisputeRepository(); 8 | default: 9 | throw new Error('Invalid storage type for DisputeRepository'); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Assignment11/factories/StudentRepositoryFactory.mjs: -------------------------------------------------------------------------------- 1 | import { InMemoryStudentRepository } from '../repositories/inMemory/InMemoryStudentRepository.mjs'; 2 | 3 | export class StudentRepositoryFactory { 4 | static getRepository(storageType = 'MEMORY') { 5 | switch (storageType) { 6 | case 'MEMORY': 7 | return new InMemoryStudentRepository(); 8 | default: 9 | throw new Error('Invalid storage type for StudentRepository'); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Assignment11/factories/EducatorRepositoryFactory.mjs: -------------------------------------------------------------------------------- 1 | import { InMemoryEducatorRepository } from '../repositories/inMemory/InMemoryEducatorRepository.mjs'; 2 | 3 | export class EducatorRepositoryFactory { 4 | static getRepository(storageType = 'MEMORY') { 5 | switch (storageType) { 6 | case 'MEMORY': 7 | return new InMemoryEducatorRepository(); 8 | default: 9 | throw new Error('Invalid storage type for EducatorRepository'); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Assignment11/factories/GradeRepositoryFactory.mjs: -------------------------------------------------------------------------------- 1 | import { InMemoryEducatorRepository } from '../repositories/inMemory/InMemoryEducatorRepository.mjs'; 2 | 3 | export class EducatorRepositoryFactory { 4 | static getRepository(storageType = 'MEMORY') { 5 | switch (storageType) { 6 | case 'MEMORY': 7 | return new InMemoryEducatorRepository(); 8 | default: 9 | throw new Error('Invalid storage type for EducatorRepository'); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Assignment11/factories/RepositoryFactory.mjs: -------------------------------------------------------------------------------- 1 | import { InMemoryRepositoryRepository } from '../repositories/inMemory/InMemoryRepositoryRepository.mjs'; 2 | 3 | export class RepositoryRepositoryFactory { 4 | static getRepository(storageType = 'MEMORY') { 5 | switch (storageType) { 6 | case 'MEMORY': 7 | return new InMemoryRepositoryRepository(); 8 | default: 9 | throw new Error('Invalid storage type for RepositoryRepository'); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Assignment11/factories/RedemptionRepositoryFactory.mjs: -------------------------------------------------------------------------------- 1 | import { InMemoryRedemptionRepository } from '../repositories/inMemory/InMemoryRedemptionRepository.mjs'; 2 | 3 | export class RedemptionRepositoryFactory { 4 | static getRepository(storageType = 'MEMORY') { 5 | switch (storageType) { 6 | case 'MEMORY': 7 | return new InMemoryRedemptionRepository(); 8 | default: 9 | throw new Error('Invalid storage type for RedemptionRepository'); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Assignment11/factories/NotificationRepositoryFactory.mjs: -------------------------------------------------------------------------------- 1 | import { InMemoryNotificationRepository } from '../repositories/inMemory/InMemoryNotificationRepository.mjs'; 2 | 3 | export class NotificationRepositoryFactory { 4 | static getRepository(storageType = 'MEMORY') { 5 | switch (storageType) { 6 | case 'MEMORY': 7 | return new InMemoryNotificationRepository(); 8 | default: 9 | throw new Error('Invalid storage type for NotificationRepository'); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Assignment11/factories/RedemptionRuleRepositoryFactory.mjs: -------------------------------------------------------------------------------- 1 | import { InMemoryRedemptionRuleRepository } from '../repositories/inMemory/InMemoryRedemptionRuleRepository.mjs'; 2 | 3 | export class RedemptionRuleRepositoryFactory { 4 | static getRepository(storageType = 'MEMORY') { 5 | switch (storageType) { 6 | case 'MEMORY': 7 | return new InMemoryRedemptionRuleRepository(); 8 | default: 9 | throw new Error('Invalid storage type for RedemptionRuleRepository'); 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Assignment7/reflection.md: -------------------------------------------------------------------------------- 1 | **Reflection** 2 | 3 | I was stuck between wanting to use the Team Planning template and the Automated Kanban template, then realised that the Automated Kanban template would be the better fit for the agile principles I was looking for in completing this assignment. I had trouble figuring out which columns to add to the current template due to the many options to add to choose from and trying to figure out which two would work well with the my project at hand. 4 | 5 | Github's tempalates look visibly more appealing and seems so much easier to use than Jira. -------------------------------------------------------------------------------- /ci.yaml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout repository 15 | uses: actions/checkout@v3 16 | 17 | - name: Set up Node.js 18 | uses: actions/setup-node@v3 19 | 20 | with: 21 | node-version: '16.20.2' 22 | 23 | - name: Install dependencies 24 | run: npm install 25 | 26 | - name: Run Jest tests 27 | run: npx jest 28 | -------------------------------------------------------------------------------- /Assignment3/README.md: -------------------------------------------------------------------------------- 1 | # gradebook-points-system 2 | 3 | ## Project Title and Description 4 | Title: Gradebook-Points-System 5 | Description: 6 | This system rewards users(students) with points for completing tasks, assignments, and tests. Points are allocated based on performance, the earned points can be converted into money. Features include automated point allocation, a conversion system, leaderboard, and an admin dashboard for management and security. 7 | 8 | ## Specification.md link 9 | [SPECIFICATION.md](SPECIFICATION.md) 10 | ## Architecture.md link 11 | [ARCHITECTURE.md](ARCHITECTURE.md) -------------------------------------------------------------------------------- /Assignment11/README.md: -------------------------------------------------------------------------------- 1 | **Storage Abstraction:** 2 | The reason I chose the Factory Pattern over the Dependency Injection is because it is simple and centralized. This means that the Factory Pattern makes it easy to manage different types without complex Dependency Injection containers. It is also quite extensible when having to add new backends an example would be to add a database or file storage it just requires that I extend the factory. Another reason is that it is ideal for small to medium sized applications, where Dependency Injection would mean there will be more overhead for the current project size. -------------------------------------------------------------------------------- /Assignment10/src/point.mjs: -------------------------------------------------------------------------------- 1 | export default class Point { 2 | constructor(point_id, student_id, grade_id, value, awarded_at = new Date()) { 3 | this._point_id = point_id; 4 | this._student_id = student_id; 5 | this._grade_id = grade_id; 6 | this._value = value; 7 | this._awarded_at = awarded_at; 8 | } 9 | 10 | calculateValue(multiplier = 1) { 11 | const calculated = this._value * multiplier; 12 | console.log(`Calculated value: ${calculated}`); 13 | return calculated; 14 | } 15 | 16 | expirePoint() { 17 | this._value = 0; 18 | console.log(`Point ${this._point_id} has expired.`); 19 | } 20 | } -------------------------------------------------------------------------------- /Assignment10/src/redemptionrule.mjs: -------------------------------------------------------------------------------- 1 | export default class RedemptionRule { 2 | constructor(rule_id, conversion_rate, updated_by, effective_from = new Date(), created_at = new Date()) { 3 | this._rule_id = rule_id; 4 | this._conversion_rate = conversion_rate; 5 | this._updated_by = updated_by; 6 | this._effective_from = effective_from; 7 | this._created_at = created_at; 8 | } 9 | 10 | setRate(newRate) { 11 | this._conversion_rate = newRate; 12 | console.log(`Conversion rate updated to ${newRate} by ${this._updated_by}`); 13 | } 14 | 15 | getCurrentRate() { 16 | return this._conversion_rate; 17 | } 18 | } -------------------------------------------------------------------------------- /Assignment10/src/admin.mjs: -------------------------------------------------------------------------------- 1 | export default class Admin { 2 | constructor(admin_id, user_id, permissions = []) { 3 | this._admin_id = admin_id; 4 | this._user_id = user_id; 5 | this._permissions = permissions; 6 | } 7 | 8 | assignRoles(userId, role) { 9 | console.log(`Assigned role '${role}' to user ${userId}`); 10 | } 11 | 12 | setRedemptionRate(rate) { 13 | console.log(`Redemption rate set to ${rate} points per unit.`); 14 | } 15 | 16 | manageUsers() { 17 | console.log(`Managing users...`); 18 | } 19 | 20 | generateReports() { 21 | console.log(`Generating reports for admin ${this._admin_id}...`); 22 | } 23 | } -------------------------------------------------------------------------------- /Assignment10/src/dispute.mjs: -------------------------------------------------------------------------------- 1 | export default class Dispute { 2 | constructor(dispute_id, student_id, grade_id, reason, status = 'pending', submitted_at = new Date()) { 3 | this._dispute_id = dispute_id; 4 | this._student_id = student_id; 5 | this._grade_id = grade_id; 6 | this._reason = reason; 7 | this._status = status; 8 | this._submitted_at = submitted_at; 9 | } 10 | 11 | submitDispute() { 12 | console.log(`Dispute submitted by Student ${this._student_id} for Grade ${this._grade_id}.`); 13 | } 14 | 15 | resolveDispute() { 16 | this._status = 'resolved'; 17 | console.log(`Dispute ${this._dispute_id} resolved.`); 18 | } 19 | } -------------------------------------------------------------------------------- /Assignment10/src/educator.mjs: -------------------------------------------------------------------------------- 1 | export default class Educator { 2 | constructor(educator_id, user_id, department, assigned_courses = []) { 3 | this._educator_id = educator_id; 4 | this._user_id = user_id; 5 | this._department = department; 6 | this._assigned_courses = assigned_courses; 7 | } 8 | 9 | uploadGrades(courseId, grades) { 10 | console.log(`Grades uploaded for course ${courseId}.`); 11 | } 12 | 13 | resolveDispute(disputeId) { 14 | console.log(`Dispute ${disputeId} resolved by Educator ${this._educator_id}.`); 15 | } 16 | 17 | viewStudentProgress(studentId) { 18 | console.log(`Viewing progress for Student ${studentId}.`); 19 | } 20 | } -------------------------------------------------------------------------------- /Assignment10/src/grade.mjs: -------------------------------------------------------------------------------- 1 | export default class Grade { 2 | constructor(grade_id, student_id, course_name, score, uploaded_by) { 3 | this._grade_id = grade_id; 4 | this._student_id = student_id; 5 | this._course_name = course_name; 6 | this._score = score; 7 | this._uploaded_by = uploaded_by; 8 | this._uploaded_at = new Date(); 9 | } 10 | 11 | convertToPoints() { 12 | const points = this._score * 1.5; // Example conversion rate 13 | console.log(`Converted ${this._score} to ${points} points.`); 14 | return points; 15 | } 16 | 17 | flagForDispute(reason) { 18 | console.log(`Grade ${this._grade_id} flagged for dispute: ${reason}`); 19 | } 20 | } -------------------------------------------------------------------------------- /Assignment10/src/notification.mjs: -------------------------------------------------------------------------------- 1 | export default class Notification { 2 | constructor(notification_id, user_id, message, type, is_read = false, created_at = new Date()) { 3 | this._notification_id = notification_id; 4 | this._user_id = user_id; 5 | this._message = message; 6 | this._type = type; 7 | this._is_read = is_read; 8 | this._created_at = created_at; 9 | } 10 | 11 | sendNotification() { 12 | console.log(`Notification sent to user ${this._user_id}: ${this._message}`); 13 | } 14 | 15 | markAsRead() { 16 | this._is_read = true; 17 | console.log(`Notification ${this._notification_id} marked as read.`); 18 | } 19 | 20 | unsubscribe() { 21 | console.log(`User ${this._user_id} unsubscribed from ${this._type} notifications.`); 22 | } 23 | } -------------------------------------------------------------------------------- /Assignment10/src/redemption.mjs: -------------------------------------------------------------------------------- 1 | export default class Redemption { 2 | constructor(redemption_id, student_id, points_redeemed, monetary_value, redeemed_at = new Date()) { 3 | this._redemption_id = redemption_id; 4 | this._student_id = student_id; 5 | this._points_redeemed = points_redeemed; 6 | this._monetary_value = monetary_value; 7 | this._redeemed_at = redeemed_at; 8 | } 9 | 10 | processRedemption() { 11 | console.log(`Processed redemption of ${this._points_redeemed} points worth $${this._monetary_value}.`); 12 | } 13 | 14 | generateReceipt() { 15 | return `Receipt - ID: ${this._redemption_id}, Student: ${this._student_id}, Points: ${this._points_redeemed}, Value: $${this._monetary_value}, Date: ${this._redeemed_at}`; 16 | } 17 | } -------------------------------------------------------------------------------- /Assignment7/kanban_explanation.md: -------------------------------------------------------------------------------- 1 | **Kanban Explanation** 2 | 3 | A Kanban Board is like a visual board showing all yours or your teams tasks that are needed to complete a project. It is used to show and manage the tasks more effectively. Kanban boards usually limit work in progess to help users focus on tasks and to not overwhelm them. It also promotes transparency so everyone can see where everyone is with different tasks. It includes collaboration between team members and allows for continuous improvement due to the visual setup of the columns and tasks. 4 | 5 | The board I'm using is the Automated Kanban board where each column repesents different progress points along the task completion stages. In this way it supports agile principles because users can visualise and iterate through tasks on the board. It limits work in progress to reduce bottlenecks between tasks. -------------------------------------------------------------------------------- /Assignment11/database/DatabaseUserRepository.mjs: -------------------------------------------------------------------------------- 1 | import { UserRepository } from '../interfaces/UserRepository.mjs'; 2 | 3 | export class DatabaseUserRepository extends UserRepository { 4 | constructor(databaseConnection) { 5 | super('user'); 6 | this.databaseConnection = databaseConnection; 7 | } 8 | 9 | async save(user) { 10 | console.log(`Saving User ${user.user_id} to database.`); 11 | } 12 | 13 | async findById(user_id) { 14 | console.log(`Finding User with ID ${user_id} from database.`); 15 | return null; 16 | } 17 | 18 | async findAll() { 19 | console.log('Finding all Users from database.'); 20 | return []; 21 | } 22 | 23 | async delete(user_id) { 24 | console.log(`Deleting User with ID ${user_id} from database.`); 25 | } 26 | } 27 | 28 | export default DatabaseUserRepository; 29 | -------------------------------------------------------------------------------- /Assignment6/AgilePlanningDocument.md: -------------------------------------------------------------------------------- 1 | **Agile Planning Document** 2 | 3 | [Assignment 4 Requirements Document](https://github.com/jaymeechelsea/grade-points-system/blob/main/Assignment4/SystemRequirementsDocument.md) 4 | 5 | *Link to each section 6 | [User Stories Table](https://github.com/jaymeechelsea/grade-points-system/blob/main/Assignment6/UserStoryCreation.md) 7 | [Product Backlog](https://github.com/jaymeechelsea/grade-points-system/blob/main/Assignment6/ProductBacklogCreation.md) 8 | 9 | [Sprint Plan](https://github.com/jaymeechelsea/grade-points-system/blob/main/Assignment6/SprintPlanningTable.md) 10 | 11 | [Reflection Link](https://github.com/jaymeechelsea/grade-points-system/blob/main/Assignment6/Reflection.md) 12 | 13 | [Project Board](https://github.com/users/jaymeechelsea/projects/1/views/1?layout=board) 14 | 15 | [Issues Board](https://github.com/jaymeechelsea/grade-points-system/issues) -------------------------------------------------------------------------------- /Assignment11/database/DatabaseAdminRepository.mjs: -------------------------------------------------------------------------------- 1 | import { AdminRepository } from '../interfaces/AdminRepository.mjs'; 2 | 3 | export class DatabaseAdminRepository extends AdminRepository { 4 | constructor(databaseConnection) { 5 | super('admin'); 6 | this.databaseConnection = databaseConnection; 7 | } 8 | 9 | async save(admin) { 10 | console.log(`Saving Admin ${admin.admin_id} to database.`); 11 | } 12 | 13 | async findById(admin_id) { 14 | console.log(`Finding Admin with ID ${admin_id} from database.`); 15 | return null; 16 | } 17 | 18 | async findAll() { 19 | console.log('Finding all Admins from database.'); 20 | return []; 21 | } 22 | 23 | async delete(admin_id) { 24 | console.log(`Deleting Admin with ID ${admin_id} from database.`); 25 | } 26 | } 27 | 28 | export default DatabaseAdminRepository; 29 | -------------------------------------------------------------------------------- /Assignment11/database/DatabaseGradeRepository.mjs: -------------------------------------------------------------------------------- 1 | import { GradeRepository } from '../interfaces/GradeRepository.mjs'; 2 | 3 | export class DatabaseGradeRepository extends GradeRepository { 4 | constructor(databaseConnection) { 5 | super('grade'); 6 | this.databaseConnection = databaseConnection; 7 | } 8 | 9 | async save(grade) { 10 | console.log(`Saving Grade ${grade.grade_id} to database.`); 11 | } 12 | 13 | async findById(grade_id) { 14 | console.log(`Finding Grade with ID ${grade_id} from database.`); 15 | return null; 16 | } 17 | 18 | async findAll() { 19 | console.log('Finding all Grades from database.'); 20 | return []; 21 | } 22 | 23 | async delete(grade_id) { 24 | console.log(`Deleting Grade with ID ${grade_id} from database.`); 25 | } 26 | } 27 | 28 | export default DatabaseGradeRepository; 29 | -------------------------------------------------------------------------------- /Assignment11/database/DatabasePointRepository.mjs: -------------------------------------------------------------------------------- 1 | import { PointRepository } from '../interfaces/PointRepository.mjs'; 2 | 3 | export class DatabasePointRepository extends PointRepository { 4 | constructor(databaseConnection) { 5 | super('point'); 6 | this.databaseConnection = databaseConnection; 7 | } 8 | 9 | async save(point) { 10 | console.log(`Saving Point ${point.point_id} to database.`); 11 | } 12 | 13 | async findById(point_id) { 14 | console.log(`Finding Point with ID ${point_id} from database.`); 15 | return null; 16 | } 17 | 18 | async findAll() { 19 | console.log('Finding all Points from database.'); 20 | return []; 21 | } 22 | 23 | async delete(point_id) { 24 | console.log(`Deleting Point with ID ${point_id} from database.`); 25 | } 26 | } 27 | 28 | export default DatabasePointRepository; 29 | -------------------------------------------------------------------------------- /Assignment11/database/DatabaseDisputeRepository.mjs: -------------------------------------------------------------------------------- 1 | import { DisputeRepository } from '../interfaces/DisputeRepository.mjs'; 2 | 3 | export class DatabaseDisputeRepository extends DisputeRepository { 4 | constructor(databaseConnection) { 5 | super('dispute'); 6 | this.databaseConnection = databaseConnection; 7 | } 8 | 9 | async save(dispute) { 10 | console.log(`Saving Dispute ${dispute.dispute_id} to database.`); 11 | } 12 | 13 | async findById(dispute_id) { 14 | console.log(`Finding Dispute with ID ${dispute_id} from database.`); 15 | return null; 16 | } 17 | 18 | async findAll() { 19 | console.log('Finding all Disputes from database.'); 20 | return []; 21 | } 22 | 23 | async delete(dispute_id) { 24 | console.log(`Deleting Dispute with ID ${dispute_id} from database.`); 25 | } 26 | } 27 | 28 | export default DatabaseDisputeRepository; 29 | -------------------------------------------------------------------------------- /Assignment11/database/DatabaseStudentRepository.mjs: -------------------------------------------------------------------------------- 1 | import { StudentRepository } from '../interfaces/StudentRepository.mjs'; 2 | 3 | export class DatabaseStudentRepository extends StudentRepository { 4 | constructor(databaseConnection) { 5 | super('student'); 6 | this.databaseConnection = databaseConnection; 7 | } 8 | 9 | async save(student) { 10 | console.log(`Saving Student ${student.student_id} to database.`); 11 | } 12 | 13 | async findById(student_id) { 14 | console.log(`Finding Student with ID ${student_id} from database.`); 15 | return null; 16 | } 17 | 18 | async findAll() { 19 | console.log('Finding all Students from database.'); 20 | return []; 21 | } 22 | 23 | async delete(student_id) { 24 | console.log(`Deleting Student with ID ${student_id} from database.`); 25 | } 26 | } 27 | 28 | export default DatabaseStudentRepository; 29 | -------------------------------------------------------------------------------- /Assignment11/database/DatabaseEducatorRepository.mjs: -------------------------------------------------------------------------------- 1 | import { EducatorRepository } from '../interfaces/EducatorRepository.mjs'; 2 | 3 | export class DatabaseEducatorRepository extends EducatorRepository { 4 | constructor(databaseConnection) { 5 | super('educator'); 6 | this.databaseConnection = databaseConnection; 7 | } 8 | 9 | async save(educator) { 10 | console.log(`Saving Educator ${educator.educator_id} to database.`); 11 | } 12 | 13 | async findById(educator_id) { 14 | console.log(`Finding Educator with ID ${educator_id} from database.`); 15 | return null; 16 | } 17 | 18 | async findAll() { 19 | console.log('Finding all Educators from database.'); 20 | return []; 21 | } 22 | 23 | async delete(educator_id) { 24 | console.log(`Deleting Educator with ID ${educator_id} from database.`); 25 | } 26 | } 27 | 28 | export default DatabaseEducatorRepository; 29 | -------------------------------------------------------------------------------- /Assignment4/Reflection.md: -------------------------------------------------------------------------------- 1 | **Reflection** 2 | 3 | Trying to design a system that meets the needs of all stakeholders including, the students, educators, and admins while maintaining strong security, performance, scalability, and usability is a difficult challenge. The goal is to create a system that meets the functional and non-functional requirements of all parties while maintaining a smooth user experience, secure operations, and scalability. The secret is prioritising the requirements based on core goals of the system, engage with stakeholders early in the design process, and iterate based on feedback. Constant testing, user input, and a deep understanding of each stakeholder’s needs are essential for making informed trade-offs that result in a functional, secure, and user-friendly system. At the end of the day finding a good balance between the competing needs of all the stakeholders constant vigilance and a willingness to adapt as new requirements emerge and technology evolves. -------------------------------------------------------------------------------- /Assignment10/src/user.mjs: -------------------------------------------------------------------------------- 1 | export default class User { 2 | constructor(user_id, name, email, password_hash, role = 'user') { 3 | this._user_id = user_id; 4 | this._name = name; 5 | this._email = email; 6 | this._password_hash = password_hash; 7 | this._role = role; 8 | this._is_verified = false; 9 | this._created_at = new Date(); 10 | } 11 | 12 | register() { 13 | console.log(`${this._name} registered successfully.`); 14 | } 15 | 16 | login() { 17 | console.log(`${this._name} logged in.`); 18 | } 19 | 20 | verifyEmail() { 21 | this._is_verified = true; 22 | console.log(`${this._email} is verified.`); 23 | } 24 | 25 | updateProfile(name, email) { 26 | this._name = name; 27 | this._email = email; 28 | console.log(`Profile updated for ${this._user_id}`); 29 | } 30 | 31 | changePassword(newHash) { 32 | this._password_hash = newHash; 33 | console.log(`Password updated for ${this._user_id}`); 34 | } 35 | } -------------------------------------------------------------------------------- /Assignment11/database/DatabaseRedemptionRepository.mjs: -------------------------------------------------------------------------------- 1 | import { RedemptionRepository } from '../interfaces/RedemptionRepository.mjs'; 2 | 3 | export class DatabaseRedemptionRepository extends RedemptionRepository { 4 | constructor(databaseConnection) { 5 | super('redemption'); 6 | this.databaseConnection = databaseConnection; 7 | } 8 | 9 | async save(redemption) { 10 | console.log(`Saving Redemption ${redemption.redemption_id} to database.`); 11 | } 12 | 13 | async findById(redemption_id) { 14 | console.log(`Finding Redemption with ID ${redemption_id} from database.`); 15 | return null; 16 | } 17 | 18 | async findAll() { 19 | console.log('Finding all Redemptions from database.'); 20 | return []; 21 | } 22 | 23 | async delete(redemption_id) { 24 | console.log(`Deleting Redemption with ID ${redemption_id} from database.`); 25 | } 26 | } 27 | 28 | export default DatabaseRedemptionRepository; 29 | -------------------------------------------------------------------------------- /Assignment12/services/AdminService.mjs: -------------------------------------------------------------------------------- 1 | export class AdminService { 2 | constructor(adminRepository) { 3 | this.adminRepository = adminRepository; 4 | } 5 | 6 | createAdmin(adminEntity) { 7 | if (!adminEntity.admin_id || !adminEntity.user_id) { 8 | throw new Error('Missing required fields'); 9 | } 10 | this.adminRepository.save(adminEntity); 11 | } 12 | 13 | getAdminById(admin_id) { 14 | return this.adminRepository.findById(admin_id); 15 | } 16 | 17 | getAllAdmins() { 18 | return this.adminRepository.findAll(); 19 | } 20 | 21 | updateAdmin(admin_id, updatedFields) { 22 | const admin = this.adminRepository.findById(admin_id); 23 | if (!admin) throw new Error('Admin not found'); 24 | 25 | const updated = { ...admin, ...updatedFields }; 26 | this.adminRepository.save(updated); 27 | } 28 | 29 | deleteAdmin(admin_id) { 30 | this.adminRepository.delete(admin_id); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Assignment11/database/DatabaseNotificationRepository.mjs: -------------------------------------------------------------------------------- 1 | import { NotificationRepository } from '../interfaces/NotificationRepository.mjs'; 2 | 3 | export class DatabaseNotificationRepository extends NotificationRepository { 4 | constructor(databaseConnection) { 5 | super('notification'); 6 | this.databaseConnection = databaseConnection; 7 | } 8 | 9 | async save(notification) { 10 | console.log(`Saving Notification ${notification.notification_id} to database.`); 11 | } 12 | 13 | async findById(notification_id) { 14 | console.log(`Finding Notification with ID ${notification_id} from database.`); 15 | return null; 16 | } 17 | 18 | async findAll() { 19 | console.log('Finding all Notifications from database.'); 20 | return []; 21 | } 22 | 23 | async delete(notification_id) { 24 | console.log(`Deleting Notification with ID ${notification_id} from database.`); 25 | } 26 | } 27 | 28 | export default DatabaseNotificationRepository; 29 | -------------------------------------------------------------------------------- /Assignment12/services/UserService.mjs: -------------------------------------------------------------------------------- 1 | export class UserService { 2 | constructor(userRepository) { 3 | this.userRepository = userRepository; 4 | } 5 | 6 | createUser(userEntity) { 7 | if (!userEntity.user_id || !userEntity.name || !userEntity.email || !userEntity.password_hash) { 8 | throw new Error('Missing required fields'); 9 | } 10 | this.userRepository.save(userEntity); 11 | } 12 | 13 | getUserById(user_id) { 14 | return this.userRepository.findById(user_id); 15 | } 16 | 17 | getAllUsers() { 18 | return this.userRepository.findAll(); 19 | } 20 | 21 | updateUser(user_id, updatedFields) { 22 | const user = this.userRepository.findById(user_id); 23 | if (!user) throw new Error('User not found'); 24 | 25 | const updated = { ...user, ...updatedFields }; 26 | this.userRepository.save(updated); 27 | } 28 | 29 | deleteUser(user_id) { 30 | this.userRepository.delete(user_id); 31 | } 32 | } -------------------------------------------------------------------------------- /Assignment10/src/student.mjs: -------------------------------------------------------------------------------- 1 | export default class Student { 2 | constructor(student_id, user_id, total_points = 0, redeemed_points = 0, grade_average = 0.0) { 3 | this._student_id = student_id; 4 | this._user_id = user_id; 5 | this._total_points = total_points; 6 | this._redeemed_points = redeemed_points; 7 | this._grade_average = grade_average; 8 | } 9 | 10 | redeemPoints(points) { 11 | if (points > this._total_points - this._redeemed_points) { 12 | console.log("Not enough points to redeem."); 13 | return; 14 | } 15 | this._redeemed_points += points; 16 | console.log(`${points} points redeemed.`); 17 | } 18 | 19 | viewDashboard() { 20 | console.log(`Student Dashboard:\nTotal Points: ${this._total_points}\nRedeemed: ${this._redeemed_points}\nGrade Avg: ${this._grade_average}`); 21 | } 22 | 23 | raiseDispute(reason) { 24 | console.log(`Dispute raised by Student ${this._student_id}: ${reason}`); 25 | } 26 | } -------------------------------------------------------------------------------- /Assignment8/StateTransitionExplanation.md: -------------------------------------------------------------------------------- 1 | **State Transition Explanation** 2 | 3 | *User* 4 | The user state addresses the FR-001 requirement allowing the user to register to use the system. 5 | 6 | *Grade* 7 | The grade state addresses the FR-002 requirement where the educators are able to upload the students grades into the system manually. 8 | 9 | *Points Balance* 10 | The points balance state addresses the FR-003 requirement allowing the students to redeem their points. 11 | 12 | *Rewards Redemption* 13 | The rewards redemption state addresses the FR-004 requirement of processing the payment or when the student is redeeming the points. 14 | 15 | *Dispute* 16 | The dispute state addresses the FR-007 requirement to allow students to submit disputes and have the educators handle them. 17 | 18 | *Transaction* 19 | The transaction state addresses the FR-003 requirement so students can redeem points. 20 | 21 | *Security Policy* 22 | The security policy state addresses the FR-008 requirement where the network traffic is monitored during the login process. 23 | -------------------------------------------------------------------------------- /Assignment11/database/DatabaseRedemptionRuleRepository.mjs: -------------------------------------------------------------------------------- 1 | import { RedemptionRuleRepository } from '../interfaces/RedemptionRuleRepository.mjs'; 2 | 3 | export class DatabaseRedemptionRuleRepository extends RedemptionRuleRepository { 4 | constructor(databaseConnection) { 5 | super('redemptionrule'); 6 | this.databaseConnection = databaseConnection; 7 | } 8 | 9 | async save(redemptionrule) { 10 | console.log(`Saving RedemptionRule ${redemptionrule.redemptionrule_id} to database.`); 11 | } 12 | 13 | async findById(redemptionrule_id) { 14 | console.log(`Finding RedemptionRule with ID ${redemptionrule_id} from database.`); 15 | return null; 16 | } 17 | 18 | async findAll() { 19 | console.log('Finding all RedemptionRules from database.'); 20 | return []; 21 | } 22 | 23 | async delete(redemptionrule_id) { 24 | console.log(`Deleting RedemptionRule with ID ${redemptionrule_id} from database.`); 25 | } 26 | } 27 | 28 | export default DatabaseRedemptionRuleRepository; 29 | -------------------------------------------------------------------------------- /Assignment12/services/PointService.mjs: -------------------------------------------------------------------------------- 1 | export class PointService { 2 | constructor(pointRepository) { 3 | this.pointRepository = pointRepository; 4 | } 5 | 6 | createPoint(pointEntity) { 7 | if (!pointEntity.point_id || !pointEntity.student_id || pointEntity.points_earned === undefined) { 8 | throw new Error('Missing required fields'); 9 | } 10 | this.pointRepository.save(pointEntity); 11 | } 12 | 13 | getPointById(point_id) { 14 | return this.pointRepository.findById(point_id); 15 | } 16 | 17 | getAllPoints() { 18 | return this.pointRepository.findAll(); 19 | } 20 | 21 | updatePoint(point_id, updatedFields) { 22 | const point = this.pointRepository.findById(point_id); 23 | if (!point) throw new Error('Point record not found'); 24 | 25 | const updated = { ...point, ...updatedFields }; 26 | this.pointRepository.save(updated); 27 | } 28 | 29 | deletePoint(point_id) { 30 | this.pointRepository.delete(point_id); 31 | } 32 | } -------------------------------------------------------------------------------- /Assignment12/services/GradeService.mjs: -------------------------------------------------------------------------------- 1 | export class GradeService { 2 | constructor(gradeRepository) { 3 | this.gradeRepository = gradeRepository; 4 | } 5 | 6 | createGrade(gradeEntity) { 7 | if (!gradeEntity.grade_id || !gradeEntity.student_id || !gradeEntity.course_id || !gradeEntity.grade_value) { 8 | throw new Error('Missing required fields'); 9 | } 10 | this.gradeRepository.save(gradeEntity); 11 | } 12 | 13 | getGradeById(grade_id) { 14 | return this.gradeRepository.findById(grade_id); 15 | } 16 | 17 | getAllGrades() { 18 | return this.gradeRepository.findAll(); 19 | } 20 | 21 | updateGrade(grade_id, updatedFields) { 22 | const grade = this.gradeRepository.findById(grade_id); 23 | if (!grade) throw new Error('Grade not found'); 24 | 25 | const updated = { ...grade, ...updatedFields }; 26 | this.gradeRepository.save(updated); 27 | } 28 | 29 | deleteGrade(grade_id) { 30 | this.gradeRepository.delete(grade_id); 31 | } 32 | } -------------------------------------------------------------------------------- /Assignment12/services/DisputeService.mjs: -------------------------------------------------------------------------------- 1 | export class DisputeService { 2 | constructor(disputeRepository) { 3 | this.disputeRepository = disputeRepository; 4 | } 5 | 6 | createDispute(disputeEntity) { 7 | if (!disputeEntity.dispute_id || !disputeEntity.student_id || !disputeEntity.description) { 8 | throw new Error('Missing required fields'); 9 | } 10 | this.disputeRepository.save(disputeEntity); 11 | } 12 | 13 | getDisputeById(dispute_id) { 14 | return this.disputeRepository.findById(dispute_id); 15 | } 16 | 17 | getAllDisputes() { 18 | return this.disputeRepository.findAll(); 19 | } 20 | 21 | updateDispute(dispute_id, updatedFields) { 22 | const dispute = this.disputeRepository.findById(dispute_id); 23 | if (!dispute) throw new Error('Dispute not found'); 24 | 25 | const updated = { ...dispute, ...updatedFields }; 26 | this.disputeRepository.save(updated); 27 | } 28 | 29 | deleteDispute(dispute_id) { 30 | this.disputeRepository.delete(dispute_id); 31 | } 32 | } -------------------------------------------------------------------------------- /Assignment12/services/EducatorService.mjs: -------------------------------------------------------------------------------- 1 | export class EducatorService { 2 | constructor(educatorRepository) { 3 | this.educatorRepository = educatorRepository; 4 | } 5 | 6 | createEducator(educatorEntity) { 7 | if (!educatorEntity.educator_id || !educatorEntity.user_id || !educatorEntity.department) { 8 | throw new Error('Missing required fields'); 9 | } 10 | this.educatorRepository.save(educatorEntity); 11 | } 12 | 13 | getEducatorById(educator_id) { 14 | return this.educatorRepository.findById(educator_id); 15 | } 16 | 17 | getAllEducators() { 18 | return this.educatorRepository.findAll(); 19 | } 20 | 21 | updateEducator(educator_id, updatedFields) { 22 | const educator = this.educatorRepository.findById(educator_id); 23 | if (!educator) throw new Error('Educator not found'); 24 | 25 | const updated = { ...educator, ...updatedFields }; 26 | this.educatorRepository.save(updated); 27 | } 28 | 29 | deleteEducator(educator_id) { 30 | this.educatorRepository.delete(educator_id); 31 | } 32 | } -------------------------------------------------------------------------------- /Assignment11/repositories/inMemory/InMemoryUserRepository.mjs: -------------------------------------------------------------------------------- 1 | import { UserEntity } from '../../entities/UserEntity.mjs'; 2 | import { UserRepository } from '../interfaces/UserRepository.mjs'; 3 | 4 | export class InMemoryUserRepository extends UserRepository { 5 | constructor() { 6 | super('user'); 7 | this.users = new Map(); 8 | } 9 | 10 | // Save (Create/Update) 11 | save(user) { 12 | if (!(user instanceof UserEntity)) { 13 | throw new Error('Only UserEntity instances can be saved.'); 14 | } 15 | this.users.set(user.user_id, user); 16 | } 17 | 18 | // Find by ID 19 | findById(user_id) { 20 | return this.users.get(user_id) || null; 21 | } 22 | 23 | // Find all 24 | findAll() { 25 | return Array.from(this.users.values()); 26 | } 27 | 28 | // Delete by ID 29 | delete(user_id) { 30 | if (!this.users.has(user_id)) { 31 | throw new Error(`User with ID ${user_id} not found.`); 32 | } 33 | this.users.delete(user_id); 34 | } 35 | } 36 | 37 | export default InMemoryUserRepository; 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | **Assignment 7 screenshots** 3 | ![image](https://github.com/user-attachments/assets/b64fd132-4bed-4a2f-ba57-6083e58fcce7) 4 | ![image](https://github.com/user-attachments/assets/eb674918-190b-47bf-abee-c5b69bb3c27b) 5 | ![image](https://github.com/user-attachments/assets/390ced18-e900-4580-b160-620c33cf628d) 6 | Added the testing and on hold columns to align with QA requirements. 7 | 8 | **Assignment 14** 9 | 10 | *Getting Started* 11 | 1) Clone this repository: git clone https://github.com/your-username/grade-points-system.git 12 | 2) Install dependencies: npm install 13 | 3) Run tests: npm test 14 | 15 | **Features for Contribution** 16 | 17 | | Feature | Status | Description | 18 | |----------|-----------------------|---------------------| 19 | | Redis Caching | Planned | Cache frequent reads| 20 | | Database Repository Layer | In Progress | PostgreSQL backend support | 21 | | Role-based API Access | Planned | Limit endpoints based on user role| 22 | | Swagger API Documentation | Not Started | Add OpenAPI specification for REST API | 23 | | Authentication Middleware | Planned | JWT-based user login and verification | 24 | -------------------------------------------------------------------------------- /Assignment11/repositories/inMemory/InMemoryAdminRepository.mjs: -------------------------------------------------------------------------------- 1 | import { AdminEntity } from '../../entities/AdminEntity.mjs'; 2 | import { AdminRepository } from '../interfaces/AdminRepository.mjs'; 3 | 4 | export class InMemoryAdminRepository extends AdminRepository { 5 | constructor() { 6 | super('admin'); 7 | this.admins = new Map(); 8 | } 9 | 10 | // Save (Create/Update) 11 | save(admin) { 12 | if (!(admin instanceof AdminEntity)) { 13 | throw new Error('Only AdminEntity instances can be saved.'); 14 | } 15 | this.admins.set(admin.admin_id, admin); 16 | } 17 | 18 | // Find by ID 19 | findById(admin_id) { 20 | return this.admins.get(admin_id) || null; 21 | } 22 | 23 | // Find all 24 | findAll() { 25 | return Array.from(this.admins.values()); 26 | } 27 | 28 | // Delete by ID 29 | delete(admin_id) { 30 | if (!this.admins.has(admin_id)) { 31 | throw new Error(`Admin with ID ${admin_id} not found.`); 32 | } 33 | this.admins.delete(admin_id); 34 | } 35 | } 36 | 37 | export default InMemoryAdminRepository; 38 | -------------------------------------------------------------------------------- /Assignment11/repositories/inMemory/InMemoryGradeRepository.mjs: -------------------------------------------------------------------------------- 1 | import { GradeEntity } from '../../entities/GradeEntity.mjs'; 2 | import { GradeRepository } from '../interfaces/GradeRepository.mjs'; 3 | 4 | export class InMemoryGradeRepository extends GradeRepository { 5 | constructor() { 6 | super('grade'); 7 | this.grades = new Map(); 8 | } 9 | 10 | // Save (Create/Update) 11 | save(grade) { 12 | if (!(grade instanceof GradeEntity)) { 13 | throw new Error('Only GradeEntity instances can be saved.'); 14 | } 15 | this.grades.set(grade.grade_id, grade); 16 | } 17 | 18 | // Find by ID 19 | findById(grade_id) { 20 | return this.grades.get(grade_id) || null; 21 | } 22 | 23 | // Find all 24 | findAll() { 25 | return Array.from(this.grades.values()); 26 | } 27 | 28 | // Delete by ID 29 | delete(grade_id) { 30 | if (!this.grades.has(grade_id)) { 31 | throw new Error(`Grade with ID ${grade_id} not found.`); 32 | } 33 | this.grades.delete(grade_id); 34 | } 35 | } 36 | 37 | export default InMemoryGradeRepository; 38 | -------------------------------------------------------------------------------- /Assignment11/repositories/inMemory/InMemoryPointRepository.mjs: -------------------------------------------------------------------------------- 1 | import { PointEntity } from '../../entities/PointEntity.mjs'; 2 | import { PointRepository } from '../interfaces/PointRepository.mjs'; 3 | 4 | export class InMemoryPointRepository extends PointRepository { 5 | constructor() { 6 | super('point'); 7 | this.points = new Map(); 8 | } 9 | 10 | // Save (Create/Update) 11 | save(point) { 12 | if (!(point instanceof PointEntity)) { 13 | throw new Error('Only PointEntity instances can be saved.'); 14 | } 15 | this.points.set(point.point_id, point); 16 | } 17 | 18 | // Find by ID 19 | findById(point_id) { 20 | return this.points.get(point_id) || null; 21 | } 22 | 23 | // Find all 24 | findAll() { 25 | return Array.from(this.points.values()); 26 | } 27 | 28 | // Delete by ID 29 | delete(point_id) { 30 | if (!this.points.has(point_id)) { 31 | throw new Error(`Point with ID ${point_id} not found.`); 32 | } 33 | this.points.delete(point_id); 34 | } 35 | } 36 | 37 | export default InMemoryPointRepository; 38 | -------------------------------------------------------------------------------- /Assignment12/services/StudentService.mjs: -------------------------------------------------------------------------------- 1 | export class StudentService { 2 | constructor(studentRepository) { 3 | this.studentRepository = studentRepository; 4 | } 5 | 6 | createStudent(studentEntity) { 7 | if (!studentEntity.student_id || !studentEntity.user_id || studentEntity.total_points === undefined || studentEntity.redeemed_points === undefined) { 8 | throw new Error('Missing required fields'); 9 | } 10 | this.studentRepository.save(studentEntity); 11 | } 12 | 13 | getStudentById(student_id) { 14 | return this.studentRepository.findById(student_id); 15 | } 16 | 17 | getAllStudents() { 18 | return this.studentRepository.findAll(); 19 | } 20 | 21 | updateStudent(student_id, updatedFields) { 22 | const student = this.studentRepository.findById(student_id); 23 | if (!student) throw new Error('Student not found'); 24 | 25 | const updated = { ...student, ...updatedFields }; 26 | this.studentRepository.save(updated); 27 | } 28 | 29 | deleteStudent(student_id) { 30 | this.studentRepository.delete(student_id); 31 | } 32 | } -------------------------------------------------------------------------------- /Assignment11/repositories/inMemory/InMemoryDisputeRepository.mjs: -------------------------------------------------------------------------------- 1 | import { DisputeEntity } from '../../entities/DisputeEntity.mjs'; 2 | import { DisputeRepository } from '../interfaces/DisputeRepository.mjs'; 3 | 4 | export class InMemoryDisputeRepository extends DisputeRepository { 5 | constructor() { 6 | super('dispute'); 7 | this.disputes = new Map(); 8 | } 9 | 10 | // Save (Create/Update) 11 | save(dispute) { 12 | if (!(dispute instanceof DisputeEntity)) { 13 | throw new Error('Only DisputeEntity instances can be saved.'); 14 | } 15 | this.disputes.set(dispute.dispute_id, dispute); 16 | } 17 | 18 | // Find by ID 19 | findById(dispute_id) { 20 | return this.disputes.get(dispute_id) || null; 21 | } 22 | 23 | // Find all 24 | findAll() { 25 | return Array.from(this.disputes.values()); 26 | } 27 | 28 | // Delete by ID 29 | delete(dispute_id) { 30 | if (!this.disputes.has(dispute_id)) { 31 | throw new Error(`Dispute with ID ${dispute_id} not found.`); 32 | } 33 | this.disputes.delete(dispute_id); 34 | } 35 | } 36 | 37 | export default InMemoryDisputeRepository; 38 | -------------------------------------------------------------------------------- /Assignment11/repositories/inMemory/InMemoryStudentRepository.mjs: -------------------------------------------------------------------------------- 1 | import { StudentEntity } from '../../entities/StudentEntity.mjs'; 2 | import { StudentRepository } from '../interfaces/StudentRepository.mjs'; 3 | 4 | export class InMemoryStudentRepository extends StudentRepository { 5 | constructor() { 6 | super('student'); 7 | this.students = new Map(); 8 | } 9 | 10 | // Save (Create/Update) 11 | save(student) { 12 | if (!(student instanceof StudentEntity)) { 13 | throw new Error('Only StudentEntity instances can be saved.'); 14 | } 15 | this.students.set(student.student_id, student); 16 | } 17 | 18 | // Find by ID 19 | findById(student_id) { 20 | return this.students.get(student_id) || null; 21 | } 22 | 23 | // Find all 24 | findAll() { 25 | return Array.from(this.students.values()); 26 | } 27 | 28 | // Delete by ID 29 | delete(student_id) { 30 | if (!this.students.has(student_id)) { 31 | throw new Error(`Student with ID ${student_id} not found.`); 32 | } 33 | this.students.delete(student_id); 34 | } 35 | } 36 | 37 | export default InMemoryStudentRepository; 38 | -------------------------------------------------------------------------------- /Assignment12/services/RedemptionService.mjs: -------------------------------------------------------------------------------- 1 | export class RedemptionService { 2 | constructor(redemptionRepository) { 3 | this.redemptionRepository = redemptionRepository; 4 | } 5 | 6 | createRedemption(redemptionEntity) { 7 | if (!redemptionEntity.redemption_id || !redemptionEntity.student_id || redemptionEntity.points_redeemed === undefined || !redemptionEntity.reward) { 8 | throw new Error('Missing required fields'); 9 | } 10 | this.redemptionRepository.save(redemptionEntity); 11 | } 12 | 13 | getRedemptionById(redemption_id) { 14 | return this.redemptionRepository.findById(redemption_id); 15 | } 16 | 17 | getAllRedemptions() { 18 | return this.redemptionRepository.findAll(); 19 | } 20 | 21 | updateRedemption(redemption_id, updatedFields) { 22 | const redemption = this.redemptionRepository.findById(redemption_id); 23 | if (!redemption) throw new Error('Redemption not found'); 24 | 25 | const updated = { ...redemption, ...updatedFields }; 26 | this.redemptionRepository.save(updated); 27 | } 28 | 29 | deleteRedemption(redemption_id) { 30 | this.redemptionRepository.delete(redemption_id); 31 | } 32 | } -------------------------------------------------------------------------------- /Assignment11/repositories/inMemory/InMemoryEducatorRepository.mjs: -------------------------------------------------------------------------------- 1 | import { EducatorEntity } from '../../entities/EducatorEntity.mjs'; 2 | import { EducatorRepository } from '../interfaces/EducatorRepository.mjs'; 3 | 4 | export class InMemoryEducatorRepository extends EducatorRepository { 5 | constructor() { 6 | super('educator'); 7 | this.educators = new Map(); 8 | } 9 | 10 | // Save (Create/Update) 11 | save(educator) { 12 | if (!(educator instanceof EducatorEntity)) { 13 | throw new Error('Only EducatorEntity instances can be saved.'); 14 | } 15 | this.educators.set(educator.educator_id, educator); 16 | } 17 | 18 | // Find by ID 19 | findById(educator_id) { 20 | return this.educators.get(educator_id) || null; 21 | } 22 | 23 | // Find all 24 | findAll() { 25 | return Array.from(this.educators.values()); 26 | } 27 | 28 | // Delete by ID 29 | delete(educator_id) { 30 | if (!this.educators.has(educator_id)) { 31 | throw new Error(`Educator with ID ${educator_id} not found.`); 32 | } 33 | this.educators.delete(educator_id); 34 | } 35 | } 36 | 37 | export default InMemoryEducatorRepository; 38 | -------------------------------------------------------------------------------- /Assignment12/services/NotificationService.mjs: -------------------------------------------------------------------------------- 1 | export class NotificationService { 2 | constructor(notificationRepository) { 3 | this.notificationRepository = notificationRepository; 4 | } 5 | 6 | createNotification(notificationEntity) { 7 | if (!notificationEntity.notification_id || !notificationEntity.user_id || !notificationEntity.message) { 8 | throw new Error('Missing required fields'); 9 | } 10 | this.notificationRepository.save(notificationEntity); 11 | } 12 | 13 | getNotificationById(notification_id) { 14 | return this.notificationRepository.findById(notification_id); 15 | } 16 | 17 | getAllNotifications() { 18 | return this.notificationRepository.findAll(); 19 | } 20 | 21 | updateNotification(notification_id, updatedFields) { 22 | const notification = this.notificationRepository.findById(notification_id); 23 | if (!notification) throw new Error('Notification not found'); 24 | 25 | const updated = { ...notification, ...updatedFields }; 26 | this.notificationRepository.save(updated); 27 | } 28 | 29 | deleteNotification(notification_id) { 30 | this.notificationRepository.delete(notification_id); 31 | } 32 | } -------------------------------------------------------------------------------- /Assignment11/repositories/inMemory/InMemoryRedemptionRepository.mjs: -------------------------------------------------------------------------------- 1 | import { RedemptionEntity } from '../../entities/RedemptionEntity.mjs'; 2 | import { RedemptionRepository } from '../interfaces/RedemptionRepository.mjs'; 3 | 4 | export class InMemoryRedemptionRepository extends RedemptionRepository { 5 | constructor() { 6 | super('redemption'); 7 | this.redemptions = new Map(); 8 | } 9 | 10 | // Save (Create/Update) 11 | save(redemption) { 12 | if (!(redemption instanceof RedemptionEntity)) { 13 | throw new Error('Only RedemptionEntity instances can be saved.'); 14 | } 15 | this.redemptions.set(redemption.redemption_id, redemption); 16 | } 17 | 18 | // Find by ID 19 | findById(redemption_id) { 20 | return this.redemptions.get(redemption_id) || null; 21 | } 22 | 23 | // Find all 24 | findAll() { 25 | return Array.from(this.redemptions.values()); 26 | } 27 | 28 | // Delete by ID 29 | delete(redemption_id) { 30 | if (!this.redemptions.has(redemption_id)) { 31 | throw new Error(`Redemption with ID ${redemption_id} not found.`); 32 | } 33 | this.redemptions.delete(redemption_id); 34 | } 35 | } 36 | 37 | export default InMemoryRedemptionRepository; 38 | -------------------------------------------------------------------------------- /Assignment12/services/RedemptionRuleService.mjs: -------------------------------------------------------------------------------- 1 | export class RedemptionRuleService { 2 | constructor(redemptionRuleRepository) { 3 | this.redemptionRuleRepository = redemptionRuleRepository; 4 | } 5 | 6 | createRedemptionRule(redemptionRuleEntity) { 7 | if (!redemptionRuleEntity.rule_id || !redemptionRuleEntity.description || redemptionRuleEntity.points_required === undefined || !redemptionRuleEntity.reward) { 8 | throw new Error('Missing required fields'); 9 | } 10 | this.redemptionRuleRepository.save(redemptionRuleEntity); 11 | } 12 | 13 | getRedemptionRuleById(rule_id) { 14 | return this.redemptionRuleRepository.findById(rule_id); 15 | } 16 | 17 | getAllRedemptionRules() { 18 | return this.redemptionRuleRepository.findAll(); 19 | } 20 | 21 | updateRedemptionRule(rule_id, updatedFields) { 22 | const redemptionRule = this.redemptionRuleRepository.findById(rule_id); 23 | if (!redemptionRule) throw new Error('Redemption rule not found'); 24 | 25 | const updated = { ...redemptionRule, ...updatedFields }; 26 | this.redemptionRuleRepository.save(updated); 27 | } 28 | 29 | deleteRedemptionRule(rule_id) { 30 | this.redemptionRuleRepository.delete(rule_id); 31 | } 32 | } -------------------------------------------------------------------------------- /Assignment13/PROTECTION.md: -------------------------------------------------------------------------------- 1 | **Branch Protection Rules for main branch Explanation** 2 | To maintain code quality and ensure collaboration best practices, the following branch protection rules have been applied to the main branch: 3 | 4 | 1) Require Pull Request Reviews 5 | - Each and every code change must be reviewed by at least one team member before merging, in this case I made the owner of the code the person to review any changes. This will reduce any bugs by a large amount, and this also encourages knowledge sharing amoungst the team members. Another benefit to having PR reviews is that it promotes accountability in the development process of applications or systems. 6 | 7 | 2) Require Status Checks to Pass 8 | - Requiring automated tests to pass before changes are merged is to prevent code from breaking or broken code entering the current code. This ensures there is consistency and stability in the code. 9 | 10 | 3) Disable Direct Pushes 11 | - Developers are not able to push their code directly to the main branch. The changes they want to add needs to go through a pull request (PR). This will enforce peer review and CI checks, it also protects the integrity of production-ready code. 12 | 13 | **Branch Rules** 14 | ![Branch rules part 1](image-1.png) 15 | ![Branch rules part 2](image-2.png) 16 | ![Branch rules part 3](image-3.png) -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI/CD Pipeline 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | 18 | - name: Setup Node.js 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version: 20 22 | 23 | - name: Install dependencies 24 | run: npm install 25 | 26 | - name: Run tests 27 | run: npm test 28 | 29 | release: 30 | needs: test 31 | if: github.ref == 'refs/heads/main' 32 | runs-on: ubuntu-latest 33 | 34 | steps: 35 | - uses: actions/checkout@v3 36 | 37 | - name: Setup Node.js 38 | uses: actions/setup-node@v3 39 | with: 40 | node-version: 20 41 | 42 | - name: Install dependencies 43 | run: npm install 44 | 45 | - name: Build app 46 | run: npm run build 47 | 48 | - name: Create zip archive of project 49 | run: | 50 | mkdir output 51 | zip -r output/release.zip . -x ".git/*" "node_modules/*" 52 | 53 | - name: Upload artifact 54 | uses: actions/upload-artifact@v3 55 | with: 56 | name: release-artifact 57 | path: output/release.zip 58 | -------------------------------------------------------------------------------- /Assignment11/repositories/inMemory/InMemoryNotificationRepository.mjs: -------------------------------------------------------------------------------- 1 | import { NotificationEntity } from '../../entities/NotificationEntity.mjs'; 2 | import { NotificationRepository } from '../interfaces/NotificationRepository.mjs'; 3 | 4 | export class InMemoryNotificationRepository extends NotificationRepository { 5 | constructor() { 6 | super('notification'); 7 | this.notifications = new Map(); 8 | } 9 | 10 | // Save (Create/Update) 11 | save(notification) { 12 | if (!(notification instanceof NotificationEntity)) { 13 | throw new Error('Only NotificationEntity instances can be saved.'); 14 | } 15 | this.notifications.set(notification.notification_id, notification); 16 | } 17 | 18 | // Find by ID 19 | findById(notification_id) { 20 | return this.notifications.get(notification_id) || null; 21 | } 22 | 23 | // Find all 24 | findAll() { 25 | return Array.from(this.notifications.values()); 26 | } 27 | 28 | // Delete by ID 29 | delete(notification_id) { 30 | if (!this.notifications.has(notification_id)) { 31 | throw new Error(`Notification with ID ${notification_id} not found.`); 32 | } 33 | this.notifications.delete(notification_id); 34 | } 35 | } 36 | 37 | export default InMemoryNotificationRepository; 38 | -------------------------------------------------------------------------------- /Assignment11/repositories/inMemory/InMemoryRedemptionRuleRepository.mjs: -------------------------------------------------------------------------------- 1 | import { RedemptionRuleEntity } from '../../entities/RedemptionRuleEntity.mjs'; 2 | import { RedemptionRuleRepository } from '../interfaces/RedemptionRuleRepository.mjs'; 3 | 4 | export class InMemoryRedemptionRuleRepository extends RedemptionRuleRepository { 5 | constructor() { 6 | super('redemptionrule'); 7 | this.redemptionrules = new Map(); 8 | } 9 | 10 | // Save (Create/Update) 11 | save(redemptionrule) { 12 | if (!(redemptionrule instanceof RedemptionRuleEntity)) { 13 | throw new Error('Only RedemptionRuleEntity instances can be saved.'); 14 | } 15 | this.redemptionrules.set(redemptionrule.redemptionrule_id, redemptionrule); 16 | } 17 | 18 | // Find by ID 19 | findById(redemptionrule_id) { 20 | return this.redemptionrules.get(redemptionrule_id) || null; 21 | } 22 | 23 | // Find all 24 | findAll() { 25 | return Array.from(this.redemptionrules.values()); 26 | } 27 | 28 | // Delete by ID 29 | delete(redemptionrule_id) { 30 | if (!this.redemptionrules.has(redemptionrule_id)) { 31 | throw new Error(`RedemptionRule with ID ${redemptionrule_id} not found.`); 32 | } 33 | this.redemptionrules.delete(redemptionrule_id); 34 | } 35 | } 36 | 37 | export default InMemoryRedemptionRuleRepository; 38 | -------------------------------------------------------------------------------- /Assignment10/unit_tests/student-pattern.test.mjs: -------------------------------------------------------------------------------- 1 | describe('Student Pattern', () => { 2 | class Student { 3 | constructor(id, name, email, role = 'student') { 4 | this.id = id; 5 | this.name = name; 6 | this.email = email; 7 | this.role = role; 8 | } 9 | } 10 | 11 | class StudentFactory { 12 | static createStudent(id, name, email) { 13 | if (!id || !name || !email) { 14 | throw new Error('Missing required properties'); 15 | } 16 | return new Student(id, name, email); 17 | } 18 | } 19 | 20 | test('should create a student with valid properties', () => { 21 | const student = StudentFactory.createStudent('1', 'John Doe', 'john.doe@test.com'); 22 | expect(student).toBeDefined(); 23 | expect(student.id).toBe('1'); 24 | expect(student.name).toBe('John Doe'); 25 | expect(student.email).toBe('john.doe@test.com'); 26 | expect(student.role).toBe('student'); 27 | }); 28 | 29 | test('should throw an error for missing required properties', () => { 30 | expect(() => { 31 | StudentFactory.createStudent('2', 'Jane Doe'); 32 | }).toThrow('Missing required properties'); 33 | }); 34 | 35 | test('should allow customization of the role', () => { 36 | const student = new Student('3', 'Alice', 'alice@test.com', 'custom-role'); 37 | expect(student.role).toBe('custom-role'); 38 | }); 39 | }); -------------------------------------------------------------------------------- /Assignment10/unit_tests/admin-pattern.test.mjs: -------------------------------------------------------------------------------- 1 | describe('AdminUserFactory', () => { 2 | class User { 3 | constructor(id, name, email, role = 'user') { 4 | this.id = id; 5 | this.name = name; 6 | this.email = email; 7 | this.role = role; 8 | } 9 | } 10 | 11 | class SimpleUserFactory { 12 | static createUser(id, name, email, password) { 13 | if (!id || !name || !email || !password) { 14 | throw new Error('Missing required properties'); 15 | } 16 | return new User(id, name, email); 17 | } 18 | } 19 | 20 | class AdminUserFactory { 21 | static createUser(id, name, email, password) { 22 | const user = SimpleUserFactory.createUser(id, name, email, password); 23 | user.role = 'admin'; 24 | return user; 25 | } 26 | } 27 | 28 | test('should create an admin user', () => { 29 | const admin = AdminUserFactory.createUser('1', 'Admin User', 'admin@test.com', 'pass1'); 30 | expect(admin).toBeDefined(); 31 | expect(admin.id).toBe('1'); 32 | expect(admin.name).toBe('Admin User'); 33 | expect(admin.email).toBe('admin@test.com'); 34 | expect(admin.role).toBe('admin'); 35 | }); 36 | 37 | test('should throw an error for missing properties', () => { 38 | expect(() => { 39 | AdminUserFactory.createUser('2', 'Admin User'); 40 | }).toThrow('Missing required properties'); 41 | }); 42 | }); -------------------------------------------------------------------------------- /Assignment10/unit_tests/dispute-pattern.test.mjs: -------------------------------------------------------------------------------- 1 | describe('DisputePattern', () => { 2 | class Dispute { 3 | constructor(id, description, status = 'open') { 4 | this.id = id; 5 | this.description = description; 6 | this.status = status; 7 | } 8 | 9 | closeDispute() { 10 | this.status = 'closed'; 11 | } 12 | } 13 | 14 | class DisputeFactory { 15 | static createDispute(id, description) { 16 | if (!id || !description) { 17 | throw new Error('Missing required properties'); 18 | } 19 | return new Dispute(id, description); 20 | } 21 | } 22 | 23 | test('should create a dispute with valid properties', () => { 24 | const dispute = DisputeFactory.createDispute('1', 'Incorrect grade assigned'); 25 | expect(dispute).toBeDefined(); 26 | expect(dispute.id).toBe('1'); 27 | expect(dispute.description).toBe('Incorrect grade assigned'); 28 | expect(dispute.status).toBe('open'); 29 | }); 30 | 31 | test('should throw an error for missing properties', () => { 32 | expect(() => { 33 | DisputeFactory.createDispute('2'); 34 | }).toThrow('Missing required properties'); 35 | }); 36 | 37 | test('should close a dispute', () => { 38 | const dispute = DisputeFactory.createDispute('3', 'Late submission penalty dispute'); 39 | dispute.closeDispute(); 40 | expect(dispute.status).toBe('closed'); 41 | }); 42 | }); -------------------------------------------------------------------------------- /Assignment10/unit_tests/educator-pattern.test.mjs: -------------------------------------------------------------------------------- 1 | describe('EducatorUserFactory', () => { 2 | class User { 3 | constructor(id, name, email, role = 'user') { 4 | this.id = id; 5 | this.name = name; 6 | this.email = email; 7 | this.role = role; 8 | } 9 | } 10 | 11 | class SimpleUserFactory { 12 | static createUser(id, name, email, password) { 13 | if (!id || !name || !email || !password) { 14 | throw new Error('Missing required properties'); 15 | } 16 | return new User(id, name, email); 17 | } 18 | } 19 | 20 | class EducatorUserFactory { 21 | static createUser(id, name, email, password) { 22 | const user = SimpleUserFactory.createUser(id, name, email, password); 23 | user.role = 'educator'; 24 | return user; 25 | } 26 | } 27 | 28 | test('should create an educator user', () => { 29 | const educator = EducatorUserFactory.createUser('1', 'Jane Doe', 'jane.doe@test.com', 'pass1'); 30 | expect(educator).toBeDefined(); 31 | expect(educator.id).toBe('1'); 32 | expect(educator.name).toBe('Jane Doe'); 33 | expect(educator.email).toBe('jane.doe@test.com'); 34 | expect(educator.role).toBe('educator'); 35 | }); 36 | 37 | test('should throw an error for missing properties', () => { 38 | expect(() => { 39 | EducatorUserFactory.createUser('2', 'John Doe'); 40 | }).toThrow('Missing required properties'); 41 | }); 42 | }); -------------------------------------------------------------------------------- /Assignment6/SprintPlanningTable.md: -------------------------------------------------------------------------------- 1 | **Sprint Planning Table** 2 | 3 | | Task ID | Task Description | Assigned to | Estimated Hours | Status(To Do/In Progress/ Done)| 4 | |---------|------------------|-------------|-----------------|--------------------------------| 5 | | T-001 | Develop the backend authentication API with an email and password parameters. | Backend Team | 8 | To Do | 6 | | T-002 | Integrate the SSO. | Backend Team | 12 | To Do | 7 | | T-003 | Design and implement the login and registration user interface. | Frontend Team | 10 | To Do | 8 | | T-004 | Build the grade uploading user interface for the educators. | Frontend Team | 8 | To Do | 9 | | T-005 | Develop a backend API for converting the uploaded grades into points. | Backend Team | 10 | To Do | 10 | | T-006 | Implement an error handling functionality for uploading grades. | Backend Team | 6 | To Do | 11 | | T-007 | Design the dashboard layout including viewing the points, progress bar, and history tab. | Frontend Team | 12 | To Do | 12 | | T-008 | Develop an API to get the dashboard data the points, and transactions. | Backend Team | 8 | To Do | 13 | | T-009 | Implement the data encryption for user records. | Backend Team | 8 | To Do | 14 | | T-010 | Add the role-based access controls so the admin, student and educator views. | Full Stack Team | 10 | To Do | 15 | 16 | *Total Sprint Points*: 92 17 | 18 | *Sprint Goal Statement*: To execute a system with secure user authentication, grade processing, and a dashboard functionality to enable students and educators to change academic performance into redeemable points. -------------------------------------------------------------------------------- /Assignment10/README.md: -------------------------------------------------------------------------------- 1 | **Explanation** 2 | I chose to use JavaScript because I'm used to implementing this language and I figured it was best for a web development project like mine where it is easier to implement frontend and backend with Node.js. It also supports OOP and the functional paradigms. It is widely used for UI/UX and REST API's usually so I decided to use it here too, it also makes the patterns a lot cleaner. A worry about using this language is that it is not as strict about classes or inheritance as the other OOP languages, but all in all it has good structure and pattern support. 3 | 4 | **Design Patterns Justification** 5 | The system leverages a variety of design patterns to model user and domain entities efficiently, ensuring modularity, scalability, and maintainability. The User class employs several patterns including the Simple Factory for centralized object creation, the Factory Method and Abstract Factory for user-specific instantiation logic, the Builder for step-by-step configuration, the Prototype for object cloning, and the Singleton for managing a consistent shared instance of users. These patterns provide flexibility to handle different user roles and behaviors. 6 | 7 | Domain-specific classes such as Student, Educator, Grade, Admin, Point, Redemption, Dispute, Notification, and RedemptionRule also use Simple Factory, Builder, Prototype, and Singleton patterns where applicable. These allow for clean object instantiation, cloning of similar entities, structured configuration, and shared registries. Overall, these patterns were chosen to reduce tight coupling, enhance reusability, and enable easier future enhancements in the system's architecture. 8 | 9 | -------------------------------------------------------------------------------- /Assignment10/unit_tests/point-pattern.test.mjs: -------------------------------------------------------------------------------- 1 | describe('NotificationPattern', () => { 2 | class Notification { 3 | constructor(id, message, type = 'info') { 4 | this.id = id; 5 | this.message = message; 6 | this.type = type; 7 | this.status = 'unread'; 8 | } 9 | 10 | markAsRead() { 11 | this.status = 'read'; 12 | } 13 | } 14 | 15 | class NotificationFactory { 16 | static createNotification(id, message, type) { 17 | if (!id || !message) { 18 | throw new Error('Missing required properties'); 19 | } 20 | return new Notification(id, message, type); 21 | } 22 | } 23 | 24 | test('should create a notification with valid properties', () => { 25 | const notification = NotificationFactory.createNotification('1', 'You have a new message', 'info'); 26 | expect(notification).toBeDefined(); 27 | expect(notification.id).toBe('1'); 28 | expect(notification.message).toBe('You have a new message'); 29 | expect(notification.type).toBe('info'); 30 | expect(notification.status).toBe('unread'); 31 | }); 32 | 33 | test('should throw an error for missing properties', () => { 34 | expect(() => { 35 | NotificationFactory.createNotification('2'); 36 | }).toThrow('Missing required properties'); 37 | }); 38 | 39 | test('should mark a notification as read', () => { 40 | const notification = NotificationFactory.createNotification('3', 'Your assignment has been graded', 'success'); 41 | notification.markAsRead(); 42 | expect(notification.status).toBe('read'); 43 | }); 44 | }); -------------------------------------------------------------------------------- /Assignment10/unit_tests/notification-pattern.test.mjs: -------------------------------------------------------------------------------- 1 | describe('NotificationPattern', () => { 2 | class Notification { 3 | constructor(id, message, type = 'info') { 4 | this.id = id; 5 | this.message = message; 6 | this.type = type; 7 | this.status = 'unread'; 8 | } 9 | 10 | markAsRead() { 11 | this.status = 'read'; 12 | } 13 | } 14 | 15 | class NotificationFactory { 16 | static createNotification(id, message, type) { 17 | if (!id || !message) { 18 | throw new Error('Missing required properties'); 19 | } 20 | return new Notification(id, message, type); 21 | } 22 | } 23 | 24 | test('should create a notification with valid properties', () => { 25 | const notification = NotificationFactory.createNotification('1', 'You have a new message', 'info'); 26 | expect(notification).toBeDefined(); 27 | expect(notification.id).toBe('1'); 28 | expect(notification.message).toBe('You have a new message'); 29 | expect(notification.type).toBe('info'); 30 | expect(notification.status).toBe('unread'); 31 | }); 32 | 33 | test('should throw an error for missing properties', () => { 34 | expect(() => { 35 | NotificationFactory.createNotification('2'); 36 | }).toThrow('Missing required properties'); 37 | }); 38 | 39 | test('should mark a notification as read', () => { 40 | const notification = NotificationFactory.createNotification('3', 'Your assignment has been graded', 'success'); 41 | notification.markAsRead(); 42 | expect(notification.status).toBe('read'); 43 | }); 44 | }); -------------------------------------------------------------------------------- /Assignment10/unit_tests/redemption-pattern.test.mjs: -------------------------------------------------------------------------------- 1 | describe('NotificationPattern', () => { 2 | class Notification { 3 | constructor(id, message, type = 'info') { 4 | this.id = id; 5 | this.message = message; 6 | this.type = type; 7 | this.status = 'unread'; 8 | } 9 | 10 | markAsRead() { 11 | this.status = 'read'; 12 | } 13 | } 14 | 15 | class NotificationFactory { 16 | static createNotification(id, message, type) { 17 | if (!id || !message) { 18 | throw new Error('Missing required properties'); 19 | } 20 | return new Notification(id, message, type); 21 | } 22 | } 23 | 24 | test('should create a notification with valid properties', () => { 25 | const notification = NotificationFactory.createNotification('1', 'You have a new message', 'info'); 26 | expect(notification).toBeDefined(); 27 | expect(notification.id).toBe('1'); 28 | expect(notification.message).toBe('You have a new message'); 29 | expect(notification.type).toBe('info'); 30 | expect(notification.status).toBe('unread'); 31 | }); 32 | 33 | test('should throw an error for missing properties', () => { 34 | expect(() => { 35 | NotificationFactory.createNotification('2'); 36 | }).toThrow('Missing required properties'); 37 | }); 38 | 39 | test('should mark a notification as read', () => { 40 | const notification = NotificationFactory.createNotification('3', 'Your assignment has been graded', 'success'); 41 | notification.markAsRead(); 42 | expect(notification.status).toBe('read'); 43 | }); 44 | }); -------------------------------------------------------------------------------- /Assignment10/unit_tests/redemptionrule-pattern.test.mjs: -------------------------------------------------------------------------------- 1 | describe('NotificationPattern', () => { 2 | class Notification { 3 | constructor(id, message, type = 'info') { 4 | this.id = id; 5 | this.message = message; 6 | this.type = type; 7 | this.status = 'unread'; 8 | } 9 | 10 | markAsRead() { 11 | this.status = 'read'; 12 | } 13 | } 14 | 15 | class NotificationFactory { 16 | static createNotification(id, message, type) { 17 | if (!id || !message) { 18 | throw new Error('Missing required properties'); 19 | } 20 | return new Notification(id, message, type); 21 | } 22 | } 23 | 24 | test('should create a notification with valid properties', () => { 25 | const notification = NotificationFactory.createNotification('1', 'You have a new message', 'info'); 26 | expect(notification).toBeDefined(); 27 | expect(notification.id).toBe('1'); 28 | expect(notification.message).toBe('You have a new message'); 29 | expect(notification.type).toBe('info'); 30 | expect(notification.status).toBe('unread'); 31 | }); 32 | 33 | test('should throw an error for missing properties', () => { 34 | expect(() => { 35 | NotificationFactory.createNotification('2'); 36 | }).toThrow('Missing required properties'); 37 | }); 38 | 39 | test('should mark a notification as read', () => { 40 | const notification = NotificationFactory.createNotification('3', 'Your assignment has been graded', 'success'); 41 | notification.markAsRead(); 42 | expect(notification.status).toBe('read'); 43 | }); 44 | }); -------------------------------------------------------------------------------- /Assignment14/License.md: -------------------------------------------------------------------------------- 1 | **MIT License** 2 | 3 | Copyright (c) 2025 Jaymee Henderson 251441105 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | 1. *Attribution Required* 8 | Any derivative work or distribution must retain the above copyright notice and this permission notice in all copies or substantial portions of the Software. 9 | 10 | 2. *Non-Academic Use Notice* 11 | While this software is primarily developed for academic learning purposes, it may be used in commercial applications. However, any non-academic use should include proper attribution to the original authors or contributors in documentation, UI footers, or credits. 12 | 13 | 3. *No Warranty* 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | 4. *Contribution Clause* 17 | All contributions submitted to this project shall be considered licensed under the MIT License. 18 | By submitting a contribution (e.g., via pull request), you affirm you have the right to license 19 | your changes under the MIT license and that your code does not violate any third-party rights. 20 | -------------------------------------------------------------------------------- /Assignment3/ARCHITECTURE.md: -------------------------------------------------------------------------------- 1 | # C4 Architectural Diagrams (ARCHITECTURE.md) 2 | 3 | ## 1. Context Diagram 4 | The **Gradebook Points System** is an educational platform that tracks student performance and rewards points that can be converted into money. Below is a high-level view of how different users and systems interact with the application. 5 | 6 | ```mermaid 7 | graph TD 8 | A[Students] -->|"Complete tasks & redeem points"| B[Frontend Web-Mobile App] 9 | C[Teachers] -->|"Upload grades, verify tasks"| B 10 | D[Admins] -->|"Manage users & rewards"| B 11 | B --> E[Backend API Server] 12 | E --> F[Database] 13 | E --> G[Payment System] 14 | E --> H[External Educational APIs] 15 | G -->|"Convert points to money"| I[Payment Gateway] 16 | H -->|"Fetch grading data & integrate with LMS"| J[External Educational APIs] 17 | 18 | ``` 19 | ## 2. Container Diagram 20 | The system consists of several containers that communicate with each other. 21 | 22 | ```mermaid 23 | graph TB 24 | A[Frontend Web-Mobile App] -->|Displays student progress, reward points| B[Backend API Server] 25 | B -->|Handles authentication, grading logic, and point conversion| C[Database] 26 | B -->|Exposes APIs for frontend and external systems| D[Payment Gateway] 27 | B -->|Connects with external educational systems| E[External APIs] 28 | D -->|Processes point-to-money conversion| F[Payment System] 29 | E -->|Fetches grading data| G[External Educational APIs] 30 | 31 | ``` 32 | ## 3. Component Diagram 33 | The key components include the following. 34 | 35 | ```mermaid 36 | graph LR 37 | A[Authentication Service] -->|Manages user login and access| B[Grading Service] 38 | B -->|Processes assignments, awards points| C[Points Calculation Engine] 39 | C -->|Tracks points and balances| D[Reward System] 40 | D -->|Facilitates redemption and cash conversion| E[Admin Panel] 41 | 42 | ``` 43 | -------------------------------------------------------------------------------- /Assignment12/api/AdminApi.mjs: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { AdminEntity } from '../../Assignment11/entities/AdminEntity.mjs' 3 | import { InMemoryAdminRepository } from '../../Assignment11/repositories/inMemory/InMemoryAdminRepository.mjs'; 4 | import { AdminService } from '../services/AdminService.mjs'; 5 | 6 | const router = express.Router(); 7 | const adminRepository = new InMemoryAdminRepository(); 8 | const adminService = new AdminService(adminRepository); 9 | 10 | // GET /api/admins - Fetch all admins 11 | router.get('/admins', (req, res) => { 12 | const admins = adminRepository.findAll(); 13 | res.json(admins); 14 | }); 15 | 16 | // POST /api/admins - Create a new admin 17 | router.post('/admins', (req, res) => { 18 | const { admin_id, user_id, permissions, created_at } = req.body; 19 | const admin = new AdminEntity(admin_id, user_id, permissions, new Date(created_at)); 20 | adminService.createAdmin(admin); 21 | res.status(201).json({ message: 'Admin created', admin }); 22 | }); 23 | 24 | // PUT /api/admins/:id - Update an admin 25 | router.put('/admins/:id', (req, res) => { 26 | const admin = adminRepository.findById(req.params.id); 27 | if (!admin) { 28 | return res.status(404).json({ message: 'Admin not found' }); 29 | } 30 | const updated = { ...admin, ...req.body }; 31 | adminRepository.save(updated); 32 | res.json({ message: 'Admin updated', admin: updated }); 33 | }); 34 | 35 | // POST /api/admins/:id/promote - Example business workflow 36 | router.post('/admins/:id/promote', (req, res) => { 37 | const admin = adminRepository.findById(req.params.id); 38 | if (!admin) { 39 | return res.status(404).json({ message: 'Admin not found' }); 40 | } 41 | admin.permissions.push('super-admin'); 42 | adminRepository.save(admin); 43 | res.json({ message: 'Admin promoted', admin }); 44 | }); 45 | 46 | export default router; 47 | -------------------------------------------------------------------------------- /Assignment12/api/UserApi.mjs: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { UserService } from '../services/UserService.mjs'; 3 | 4 | const router = express.Router(); 5 | const userService = new UserService(); 6 | 7 | // Create a new user 8 | router.post('/users', (req, res) => { 9 | try { 10 | const user = req.body; 11 | userService.createUser(user); 12 | res.status(201).json({ message: 'User created successfully', user }); 13 | } catch (error) { 14 | res.status(400).json({ error: error.message }); 15 | } 16 | }); 17 | 18 | // Get a user by ID 19 | router.get('/users/:id', (req, res) => { 20 | try { 21 | const user = userService.getUserById(req.params.id); 22 | if (!user) { 23 | return res.status(404).json({ error: 'User not found' }); 24 | } 25 | res.status(200).json(user); 26 | } catch (error) { 27 | res.status(500).json({ error: error.message }); 28 | } 29 | }); 30 | 31 | // Get all users 32 | router.get('/users', (req, res) => { 33 | try { 34 | const users = userService.getAllUsers(); 35 | res.status(200).json(users); 36 | } catch (error) { 37 | res.status(500).json({ error: error.message }); 38 | } 39 | }); 40 | 41 | // Update a user 42 | router.put('/users/:id', (req, res) => { 43 | try { 44 | const updatedFields = req.body; 45 | userService.updateUser(req.params.id, updatedFields); 46 | res.status(200).json({ message: 'User updated successfully' }); 47 | } catch (error) { 48 | res.status(400).json({ error: error.message }); 49 | } 50 | }); 51 | 52 | // Delete a user 53 | router.delete('/users/:id', (req, res) => { 54 | try { 55 | userService.deleteUser(req.params.id); 56 | res.status(200).json({ message: 'User deleted successfully' }); 57 | } catch (error) { 58 | res.status(400).json({ error: error.message }); 59 | } 60 | }); 61 | 62 | export default router; -------------------------------------------------------------------------------- /Assignment12/api/GradeApi.mjs: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { GradeService } from '../services/GradeService.mjs'; 3 | 4 | const router = express.Router(); 5 | const gradeService = new GradeService(); 6 | 7 | // Create a new grade 8 | router.post('/grades', (req, res) => { 9 | try { 10 | const grade = req.body; 11 | gradeService.createGrade(grade); 12 | res.status(201).json({ message: 'Grade created successfully', grade }); 13 | } catch (error) { 14 | res.status(400).json({ error: error.message }); 15 | } 16 | }); 17 | 18 | // Get a grade by ID 19 | router.get('/grades/:id', (req, res) => { 20 | try { 21 | const grade = gradeService.getGradeById(req.params.id); 22 | if (!grade) { 23 | return res.status(404).json({ error: 'Grade not found' }); 24 | } 25 | res.status(200).json(grade); 26 | } catch (error) { 27 | res.status(500).json({ error: error.message }); 28 | } 29 | }); 30 | 31 | // Get all grades 32 | router.get('/grades', (req, res) => { 33 | try { 34 | const grades = gradeService.getAllGrades(); 35 | res.status(200).json(grades); 36 | } catch (error) { 37 | res.status(500).json({ error: error.message }); 38 | } 39 | }); 40 | 41 | // Update a grade 42 | router.put('/grades/:id', (req, res) => { 43 | try { 44 | const updatedFields = req.body; 45 | gradeService.updateGrade(req.params.id, updatedFields); 46 | res.status(200).json({ message: 'Grade updated successfully' }); 47 | } catch (error) { 48 | res.status(400).json({ error: error.message }); 49 | } 50 | }); 51 | 52 | // Delete a grade 53 | router.delete('/grades/:id', (req, res) => { 54 | try { 55 | gradeService.deleteGrade(req.params.id); 56 | res.status(200).json({ message: 'Grade deleted successfully' }); 57 | } catch (error) { 58 | res.status(400).json({ error: error.message }); 59 | } 60 | }); 61 | 62 | export default router; -------------------------------------------------------------------------------- /Assignment12/api/PointApi.mjs: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { PointService } from '../services/PointService.mjs'; 3 | 4 | const router = express.Router(); 5 | const pointService = new PointService(); 6 | 7 | // Create a new point record 8 | router.post('/points', (req, res) => { 9 | try { 10 | const point = req.body; 11 | pointService.createPoint(point); 12 | res.status(201).json({ message: 'Point record created successfully', point }); 13 | } catch (error) { 14 | res.status(400).json({ error: error.message }); 15 | } 16 | }); 17 | 18 | // Get a point record by ID 19 | router.get('/points/:id', (req, res) => { 20 | try { 21 | const point = pointService.getPointById(req.params.id); 22 | if (!point) { 23 | return res.status(404).json({ error: 'Point record not found' }); 24 | } 25 | res.status(200).json(point); 26 | } catch (error) { 27 | res.status(500).json({ error: error.message }); 28 | } 29 | }); 30 | 31 | // Get all point records 32 | router.get('/points', (req, res) => { 33 | try { 34 | const points = pointService.getAllPoints(); 35 | res.status(200).json(points); 36 | } catch (error) { 37 | res.status(500).json({ error: error.message }); 38 | } 39 | }); 40 | 41 | // Update a point record 42 | router.put('/points/:id', (req, res) => { 43 | try { 44 | const updatedFields = req.body; 45 | pointService.updatePoint(req.params.id, updatedFields); 46 | res.status(200).json({ message: 'Point record updated successfully' }); 47 | } catch (error) { 48 | res.status(400).json({ error: error.message }); 49 | } 50 | }); 51 | 52 | // Delete a point record 53 | router.delete('/points/:id', (req, res) => { 54 | try { 55 | pointService.deletePoint(req.params.id); 56 | res.status(200).json({ message: 'Point record deleted successfully' }); 57 | } catch (error) { 58 | res.status(400).json({ error: error.message }); 59 | } 60 | }); 61 | 62 | export default router; -------------------------------------------------------------------------------- /Assignment12/api/StudentApi.mjs: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { StudentService } from '../services/StudentService.mjs'; 3 | 4 | const router = express.Router(); 5 | const studentService = new StudentService(); 6 | 7 | // Create a new student 8 | router.post('/students', (req, res) => { 9 | try { 10 | const student = req.body; 11 | studentService.createStudent(student); 12 | res.status(201).json({ message: 'Student created successfully', student }); 13 | } catch (error) { 14 | res.status(400).json({ error: error.message }); 15 | } 16 | }); 17 | 18 | // Get a student by ID 19 | router.get('/students/:id', (req, res) => { 20 | try { 21 | const student = studentService.getStudentById(req.params.id); 22 | if (!student) { 23 | return res.status(404).json({ error: 'Student not found' }); 24 | } 25 | res.status(200).json(student); 26 | } catch (error) { 27 | res.status(500).json({ error: error.message }); 28 | } 29 | }); 30 | 31 | // Get all students 32 | router.get('/students', (req, res) => { 33 | try { 34 | const students = studentService.getAllStudents(); 35 | res.status(200).json(students); 36 | } catch (error) { 37 | res.status(500).json({ error: error.message }); 38 | } 39 | }); 40 | 41 | // Update a student 42 | router.put('/students/:id', (req, res) => { 43 | try { 44 | const updatedFields = req.body; 45 | studentService.updateStudent(req.params.id, updatedFields); 46 | res.status(200).json({ message: 'Student updated successfully' }); 47 | } catch (error) { 48 | res.status(400).json({ error: error.message }); 49 | } 50 | }); 51 | 52 | // Delete a student 53 | router.delete('/students/:id', (req, res) => { 54 | try { 55 | studentService.deleteStudent(req.params.id); 56 | res.status(200).json({ message: 'Student deleted successfully' }); 57 | } catch (error) { 58 | res.status(400).json({ error: error.message }); 59 | } 60 | }); 61 | 62 | export default router; -------------------------------------------------------------------------------- /Assignment12/api/DisputeApi.mjs: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { DisputeService } from '../services/DisputeService.mjs'; 3 | 4 | const router = express.Router(); 5 | const disputeService = new DisputeService(); 6 | 7 | // Create a new dispute 8 | router.post('/disputes', (req, res) => { 9 | try { 10 | const dispute = req.body; 11 | disputeService.createDispute(dispute); 12 | res.status(201).json({ message: 'Dispute created successfully', dispute }); 13 | } catch (error) { 14 | res.status(400).json({ error: error.message }); 15 | } 16 | }); 17 | 18 | // Get a dispute by ID 19 | router.get('/disputes/:id', (req, res) => { 20 | try { 21 | const dispute = disputeService.getDisputeById(req.params.id); 22 | if (!dispute) { 23 | return res.status(404).json({ error: 'Dispute not found' }); 24 | } 25 | res.status(200).json(dispute); 26 | } catch (error) { 27 | res.status(500).json({ error: error.message }); 28 | } 29 | }); 30 | 31 | // Get all disputes 32 | router.get('/disputes', (req, res) => { 33 | try { 34 | const disputes = disputeService.getAllDisputes(); 35 | res.status(200).json(disputes); 36 | } catch (error) { 37 | res.status(500).json({ error: error.message }); 38 | } 39 | }); 40 | 41 | // Update a dispute 42 | router.put('/disputes/:id', (req, res) => { 43 | try { 44 | const updatedFields = req.body; 45 | disputeService.updateDispute(req.params.id, updatedFields); 46 | res.status(200).json({ message: 'Dispute updated successfully' }); 47 | } catch (error) { 48 | res.status(400).json({ error: error.message }); 49 | } 50 | }); 51 | 52 | // Delete a dispute 53 | router.delete('/disputes/:id', (req, res) => { 54 | try { 55 | disputeService.deleteDispute(req.params.id); 56 | res.status(200).json({ message: 'Dispute deleted successfully' }); 57 | } catch (error) { 58 | res.status(400).json({ error: error.message }); 59 | } 60 | }); 61 | 62 | export default router; -------------------------------------------------------------------------------- /Assignment12/api/EducatorApi.mjs: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { EducatorService } from '../services/EducatorService.mjs'; 3 | 4 | const router = express.Router(); 5 | const educatorService = new EducatorService(); 6 | 7 | // Create a new educator 8 | router.post('/educators', (req, res) => { 9 | try { 10 | const educator = req.body; 11 | educatorService.createEducator(educator); 12 | res.status(201).json({ message: 'Educator created successfully', educator }); 13 | } catch (error) { 14 | res.status(400).json({ error: error.message }); 15 | } 16 | }); 17 | 18 | // Get an educator by ID 19 | router.get('/educators/:id', (req, res) => { 20 | try { 21 | const educator = educatorService.getEducatorById(req.params.id); 22 | if (!educator) { 23 | return res.status(404).json({ error: 'Educator not found' }); 24 | } 25 | res.status(200).json(educator); 26 | } catch (error) { 27 | res.status(500).json({ error: error.message }); 28 | } 29 | }); 30 | 31 | // Get all educators 32 | router.get('/educators', (req, res) => { 33 | try { 34 | const educators = educatorService.getAllEducators(); 35 | res.status(200).json(educators); 36 | } catch (error) { 37 | res.status(500).json({ error: error.message }); 38 | } 39 | }); 40 | 41 | // Update an educator 42 | router.put('/educators/:id', (req, res) => { 43 | try { 44 | const updatedFields = req.body; 45 | educatorService.updateEducator(req.params.id, updatedFields); 46 | res.status(200).json({ message: 'Educator updated successfully' }); 47 | } catch (error) { 48 | res.status(400).json({ error: error.message }); 49 | } 50 | }); 51 | 52 | // Delete an educator 53 | router.delete('/educators/:id', (req, res) => { 54 | try { 55 | educatorService.deleteEducator(req.params.id); 56 | res.status(200).json({ message: 'Educator deleted successfully' }); 57 | } catch (error) { 58 | res.status(400).json({ error: error.message }); 59 | } 60 | }); 61 | 62 | export default router; -------------------------------------------------------------------------------- /Assignment9/DomainModelDocumentation.md: -------------------------------------------------------------------------------- 1 | | Entity | Attributes | Methods | Relationships | 2 | |--------|------------|---------|---------------| 3 | | User | user_id, name, email, password_hash, role, is_verified, created_at | register(), login(), verify_email(), update_profile(), change_password() | A User can be a Student, Educator, or Admin (Role). | 4 | | Student | student_id, user_id (FK), total_points, redeemed_points, grade_average | redeem_points(), view_dashboard(), raise_dispute()| A Student is a User; can receive Grades, earn Points, make Redemptions, and raise Disputes. | 5 | | Educator | educator_id, user_id (FK), department, assigned_courses | upload_grades(), resolve_dispute(), view_student_progress() | An Educator is a User; uploads Grades and manages Disputes. | 6 | | Admin | admin_id, user_id (FK), permissions | assign_roles(), set_redemption_rate(), manage_users(), generate_reports() | An Admin is a User; manages Roles, RedemptionRules, and Users. | 7 | | Grade | grade_id, student_id (FK), course_name, score, uploaded_by, uploaded_at | convert_to_points(), flag_for_dispute() | A Grade belongs to a Student; uploaded by an Educator. | 8 | | Point | point_id, student_id (FK), grade_id (FK), value, awarded_at | calculate_value(), expire_point() | Points are derived from Grades; belong to a Student. | 9 | | Redemption | redemption_id, student_id (FK), points_redeemed, monetary_value, redeemed_at | process_redemption(), generate_receipt() | A Redemption belongs to a Student; based on Points. | 10 | | Dispute | dispute_id, student_id (FK), grade_id (FK), reason, status, submitted_at | submit_dispute(), resolve_dispute() | A Dispute is raised by a Student; resolved by an Educator/Admin; targets a Grade. | 11 | | Notification | notification_id, user_id (FK), message, type, is_read, created_at | send_notification(), mark_as_read(), unsubscribe() | Notifications are sent to Users based on system events (grades, rewards, etc).| 12 | | RedemptionRule | rule_id, conversion_rate, updated_by (FK), effective_from, created_at | set_rate(), get_current_rate() | Defined and updated by Admins; used in Point -> Redemption calculation. | 13 | -------------------------------------------------------------------------------- /Assignment10/unit_tests/grade-pattern.test.mjs: -------------------------------------------------------------------------------- 1 | describe('GradePattern', () => { 2 | class Grade { 3 | constructor(studentId, subject, score) { 4 | this.studentId = studentId; 5 | this.subject = subject; 6 | this.score = score; 7 | this.grade = this.calculateGrade(); 8 | } 9 | 10 | calculateGrade() { 11 | if (this.score >= 90) return 'A'; 12 | if (this.score >= 80) return 'B'; 13 | if (this.score >= 70) return 'C'; 14 | if (this.score >= 60) return 'D'; 15 | return 'F'; 16 | } 17 | } 18 | 19 | class GradeFactory { 20 | static createGrade(studentId, subject, score) { 21 | if (!studentId || !subject || score === undefined) { 22 | throw new Error('Missing required properties'); 23 | } 24 | return new Grade(studentId, subject, score); 25 | } 26 | } 27 | 28 | test('should create a grade with valid properties', () => { 29 | const grade = GradeFactory.createGrade('1', 'Mathematics', 85); 30 | expect(grade).toBeDefined(); 31 | expect(grade.studentId).toBe('1'); 32 | expect(grade.subject).toBe('Mathematics'); 33 | expect(grade.score).toBe(85); 34 | expect(grade.grade).toBe('B'); 35 | }); 36 | 37 | test('should throw an error for missing properties', () => { 38 | expect(() => { 39 | GradeFactory.createGrade('2', 'Science'); 40 | }).toThrow('Missing required properties'); 41 | }); 42 | 43 | test('should calculate the correct grade based on score', () => { 44 | const gradeA = GradeFactory.createGrade('3', 'English', 95); 45 | const gradeB = GradeFactory.createGrade('4', 'History', 82); 46 | const gradeC = GradeFactory.createGrade('5', 'Geography', 75); 47 | const gradeD = GradeFactory.createGrade('6', 'Biology', 65); 48 | const gradeF = GradeFactory.createGrade('7', 'Chemistry', 50); 49 | 50 | expect(gradeA.grade).toBe('A'); 51 | expect(gradeB.grade).toBe('B'); 52 | expect(gradeC.grade).toBe('C'); 53 | expect(gradeD.grade).toBe('D'); 54 | expect(gradeF.grade).toBe('F'); 55 | }); 56 | }); -------------------------------------------------------------------------------- /Assignment12/api/RedemptionApi.mjs: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { RedemptionService } from '../services/RedemptionService.mjs'; 3 | 4 | const router = express.Router(); 5 | const redemptionService = new RedemptionService(); 6 | 7 | // Create a new redemption 8 | router.post('/redemptions', (req, res) => { 9 | try { 10 | const redemption = req.body; 11 | redemptionService.createRedemption(redemption); 12 | res.status(201).json({ message: 'Redemption created successfully', redemption }); 13 | } catch (error) { 14 | res.status(400).json({ error: error.message }); 15 | } 16 | }); 17 | 18 | // Get a redemption by ID 19 | router.get('/redemptions/:id', (req, res) => { 20 | try { 21 | const redemption = redemptionService.getRedemptionById(req.params.id); 22 | if (!redemption) { 23 | return res.status(404).json({ error: 'Redemption not found' }); 24 | } 25 | res.status(200).json(redemption); 26 | } catch (error) { 27 | res.status(500).json({ error: error.message }); 28 | } 29 | }); 30 | 31 | // Get all redemptions 32 | router.get('/redemptions', (req, res) => { 33 | try { 34 | const redemptions = redemptionService.getAllRedemptions(); 35 | res.status(200).json(redemptions); 36 | } catch (error) { 37 | res.status(500).json({ error: error.message }); 38 | } 39 | }); 40 | 41 | // Update a redemption 42 | router.put('/redemptions/:id', (req, res) => { 43 | try { 44 | const updatedFields = req.body; 45 | redemptionService.updateRedemption(req.params.id, updatedFields); 46 | res.status(200).json({ message: 'Redemption updated successfully' }); 47 | } catch (error) { 48 | res.status(400).json({ error: error.message }); 49 | } 50 | }); 51 | 52 | // Delete a redemption 53 | router.delete('/redemptions/:id', (req, res) => { 54 | try { 55 | redemptionService.deleteRedemption(req.params.id); 56 | res.status(200).json({ message: 'Redemption deleted successfully' }); 57 | } catch (error) { 58 | res.status(400).json({ error: error.message }); 59 | } 60 | }); 61 | 62 | export default router; -------------------------------------------------------------------------------- /Assignment12/api/NotificationApi.mjs: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { NotificationService } from '../services/NotificationService.mjs'; 3 | 4 | const router = express.Router(); 5 | const notificationService = new NotificationService(); 6 | 7 | // Create a new notification 8 | router.post('/notifications', (req, res) => { 9 | try { 10 | const notification = req.body; 11 | notificationService.createNotification(notification); 12 | res.status(201).json({ message: 'Notification created successfully', notification }); 13 | } catch (error) { 14 | res.status(400).json({ error: error.message }); 15 | } 16 | }); 17 | 18 | // Get a notification by ID 19 | router.get('/notifications/:id', (req, res) => { 20 | try { 21 | const notification = notificationService.getNotificationById(req.params.id); 22 | if (!notification) { 23 | return res.status(404).json({ error: 'Notification not found' }); 24 | } 25 | res.status(200).json(notification); 26 | } catch (error) { 27 | res.status(500).json({ error: error.message }); 28 | } 29 | }); 30 | 31 | // Get all notifications 32 | router.get('/notifications', (req, res) => { 33 | try { 34 | const notifications = notificationService.getAllNotifications(); 35 | res.status(200).json(notifications); 36 | } catch (error) { 37 | res.status(500).json({ error: error.message }); 38 | } 39 | }); 40 | 41 | // Update a notification 42 | router.put('/notifications/:id', (req, res) => { 43 | try { 44 | const updatedFields = req.body; 45 | notificationService.updateNotification(req.params.id, updatedFields); 46 | res.status(200).json({ message: 'Notification updated successfully' }); 47 | } catch (error) { 48 | res.status(400).json({ error: error.message }); 49 | } 50 | }); 51 | 52 | // Delete a notification 53 | router.delete('/notifications/:id', (req, res) => { 54 | try { 55 | notificationService.deleteNotification(req.params.id); 56 | res.status(200).json({ message: 'Notification deleted successfully' }); 57 | } catch (error) { 58 | res.status(400).json({ error: error.message }); 59 | } 60 | }); 61 | 62 | export default router; -------------------------------------------------------------------------------- /Assignment10/creational_patterns/admin-patterns.mjs: -------------------------------------------------------------------------------- 1 | import Admin from '../src/admin.js'; 2 | 3 | // SimpleFactory 4 | export default class SimpleAdminFactory { 5 | static createAdmin(admin_id, user_id, permissions = []) { 6 | return new Admin(admin_id, user_id, permissions); 7 | } 8 | } 9 | 10 | // FactoryMethod 11 | export class FactoryAdmin { 12 | constructor(admin_id, user_id, permissions = []) { 13 | this.admin_id = admin_id; 14 | this.user_id = user_id; 15 | this.permissions = permissions; 16 | } 17 | 18 | createAdmin() { 19 | return new Admin(this.admin_id, this.user_id, this.permissions); 20 | } 21 | } 22 | 23 | // AbstractFactory 24 | class AdminFactory { 25 | createAdmin(admin_id, user_id, permissions = []) { 26 | throw new Error('This method should be overridden.'); 27 | } 28 | } 29 | 30 | export class SuperAdminFactory extends AdminFactory { 31 | createAdmin(admin_id, user_id, permissions = []) { 32 | return new Admin(admin_id, user_id, permissions); 33 | } 34 | } 35 | 36 | // Builder 37 | export class AdminBuilder { 38 | constructor() { 39 | this.admin = new Admin(); 40 | } 41 | 42 | setAdminId(admin_id) { 43 | this.admin.admin_id = admin_id; 44 | return this; 45 | } 46 | 47 | setUserId(user_id) { 48 | this.admin.user_id = user_id; 49 | return this; 50 | } 51 | 52 | setPermissions(permissions) { 53 | this.admin.permissions = permissions; 54 | return this; 55 | } 56 | 57 | build() { 58 | return this.admin; 59 | } 60 | } 61 | 62 | // Prototype 63 | export class AdminPrototype { 64 | constructor(admin) { 65 | this.admin = admin; 66 | } 67 | 68 | clone() { 69 | return new AdminPrototype({ ...this.admin }); 70 | } 71 | } 72 | 73 | // Singleton 74 | export class AdminSingleton { 75 | constructor() { 76 | if (!AdminSingleton.instance) { 77 | this.admins = []; 78 | AdminSingleton.instance = this; 79 | } 80 | return AdminSingleton.instance; 81 | } 82 | 83 | addAdmin(admin) { 84 | this.admins.push(admin); 85 | } 86 | 87 | getAdmins() { 88 | return this.admins; 89 | } 90 | } -------------------------------------------------------------------------------- /Assignment10/unit_tests/user-pattern.test.mjs: -------------------------------------------------------------------------------- 1 | describe('User Pattern', () => { 2 | class User { 3 | constructor(id, name, email, role = 'user') { 4 | this.id = id; 5 | this.name = name; 6 | this.email = email; 7 | this.role = role; 8 | } 9 | } 10 | 11 | class SimpleUserFactory { 12 | static createUser(id, name, email, password) { 13 | if (!id || !name || !email || !password) { 14 | throw new Error('Missing required properties'); 15 | } 16 | return new User(id, name, email); 17 | } 18 | } 19 | 20 | class StudentUserFactory { 21 | static createUser(id, name, email, password) { 22 | const user = SimpleUserFactory.createUser(id, name, email, password); 23 | user.role = 'student'; 24 | return user; 25 | } 26 | } 27 | 28 | class EducatorUserFactory { 29 | static createUser(id, name, email, password) { 30 | const user = SimpleUserFactory.createUser(id, name, email, password); 31 | user.role = 'educator'; 32 | return user; 33 | } 34 | } 35 | 36 | test('SimpleUserFactory creates a default user', () => { 37 | const user = SimpleUserFactory.createUser('1', 'Alice', 'alice@test.com', 'pass1'); 38 | expect(user).toBeDefined(); 39 | expect(user.id).toBe('1'); 40 | expect(user.name).toBe('Alice'); 41 | expect(user.email).toBe('alice@test.com'); 42 | expect(user.role).toBe('user'); 43 | }); 44 | 45 | test('StudentUserFactory creates a student user', () => { 46 | const student = StudentUserFactory.createUser('2', 'Bob', 'bob@test.com', 'pass2'); 47 | expect(student).toBeDefined(); 48 | expect(student.role).toBe('student'); 49 | }); 50 | 51 | test('EducatorUserFactory creates an educator user', () => { 52 | const educator = EducatorUserFactory.createUser('3', 'Cara', 'cara@test.com', 'pass3'); 53 | expect(educator).toBeDefined(); 54 | expect(educator.role).toBe('educator'); 55 | }); 56 | 57 | test('SimpleUserFactory throws an error for missing properties', () => { 58 | expect(() => { 59 | SimpleUserFactory.createUser('4', 'Dave', 'dave@test.com'); 60 | }).toThrow('Missing required properties'); 61 | }); 62 | }); -------------------------------------------------------------------------------- /Assignment12/api/RedemptionRuleApi.mjs: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { RedemptionRuleService } from '../services/RedemptionRuleService.mjs'; 3 | 4 | const router = express.Router(); 5 | const redemptionRuleService = new RedemptionRuleService(); 6 | 7 | // Create a new redemption rule 8 | router.post('/redemption-rules', (req, res) => { 9 | try { 10 | const redemptionRule = req.body; 11 | redemptionRuleService.createRedemptionRule(redemptionRule); 12 | res.status(201).json({ message: 'Redemption rule created successfully', redemptionRule }); 13 | } catch (error) { 14 | res.status(400).json({ error: error.message }); 15 | } 16 | }); 17 | 18 | // Get a redemption rule by ID 19 | router.get('/redemption-rules/:id', (req, res) => { 20 | try { 21 | const redemptionRule = redemptionRuleService.getRedemptionRuleById(req.params.id); 22 | if (!redemptionRule) { 23 | return res.status(404).json({ error: 'Redemption rule not found' }); 24 | } 25 | res.status(200).json(redemptionRule); 26 | } catch (error) { 27 | res.status(500).json({ error: error.message }); 28 | } 29 | }); 30 | 31 | // Get all redemption rules 32 | router.get('/redemption-rules', (req, res) => { 33 | try { 34 | const redemptionRules = redemptionRuleService.getAllRedemptionRules(); 35 | res.status(200).json(redemptionRules); 36 | } catch (error) { 37 | res.status(500).json({ error: error.message }); 38 | } 39 | }); 40 | 41 | // Update a redemption rule 42 | router.put('/redemption-rules/:id', (req, res) => { 43 | try { 44 | const updatedFields = req.body; 45 | redemptionRuleService.updateRedemptionRule(req.params.id, updatedFields); 46 | res.status(200).json({ message: 'Redemption rule updated successfully' }); 47 | } catch (error) { 48 | res.status(400).json({ error: error.message }); 49 | } 50 | }); 51 | 52 | // Delete a redemption rule 53 | router.delete('/redemption-rules/:id', (req, res) => { 54 | try { 55 | redemptionRuleService.deleteRedemptionRule(req.params.id); 56 | res.status(200).json({ message: 'Redemption rule deleted successfully' }); 57 | } catch (error) { 58 | res.status(400).json({ error: error.message }); 59 | } 60 | }); 61 | 62 | export default router; -------------------------------------------------------------------------------- /Assignment6/Reflection.md: -------------------------------------------------------------------------------- 1 | **Reflection** 2 | 3 | As the only stakeholder involved in this exercise, the whole process of creating a product backlog, prioritizing the user stories, and doing the sprint planning created a difficult internal battle. Agile methodologies highlight a collaborative and iterative feedback loop. Working by myself forced me to face my own biases, resistances, and unknowns which posed a difficult task. 4 | There is an illusion of being objective while trying to prioritize the user stories using the MoSCoW framework seemed simple in the beginning. Except being the only stakeholder, I really struggled to separate my assumptions about what I thought the user needs from what the user actually needs. As well as the internal conflict between how fast I can deliver a functional MVP and ensuring the system is sustainable became a repeated battle. Not having other stakeholders to challenge or validate my views, meant I changed between overconfidence and my self-doubt. 5 | Estimating the effort alone increased the risk of cognitive biases. With the absence of a team to double check my estimates there are biases. I’m only able to fixate on my initial guesses, therefore, pertaining to planning fallacy. The isolation of my being the only one estimating made it easy to ignore the hidden tasks, like the documentation and or the error logging, which are critical things to consider too. 6 | Because agile thrives on feedback loops and shared ownership, I found it difficult to embrace flexibility. Halfway through sprint planning, I realized that my rigid comfortability to initial priorities contradicted agile’s adaptive ethos. The lack of daily standups or retrospectives means I failed to see the burnout risks or adjust the sprint scope when tasks overran. 7 | The lessons I think I learned is to embrace me being my biggest critic. To counter the aloneness, I can role-play me being a sceptic of my work questioning everything to try force objectivity. I can also look to historical data and use any past projects to figure out better estimates. Limiting my task hours also forces me to identify where I need to prioritize and therefore also avoiding perfectionism. 8 | This assignment highlighted the biggest agile challenge for a solo stakeholder is resisting the urge to blend one’s own preferences with user needs. Acknowledging my internal resistance, I can better imitate the checks and balances of a team. The main takeaway would be that agile’s principles will definitely be valuable in one team projects, but only if I actively try to counterbalance my biases with my discipline and structured self-reflection. 9 | -------------------------------------------------------------------------------- /Assignment9/Reflection.md: -------------------------------------------------------------------------------- 1 | **Reflection** 2 | 3 | While I was trying to design the domain model and the class diagram for the Grade Points’ System they were both quite difficult to put together. One of the big difficulties was in figuring out and deciding which details were necessary for the core functionality and which I could leave out. An example of this would be while I was tempted to over explain how the notifications would be sent either via email, SMS or push notifications, I realised I needed to make this into a general Notification entity to keep everything clear and to ensure there is flexibility. Another challenge came up while I was trying to figure out the relationships for each entity like association, aggregation, and composition. I spent a lot of time trying to decide if the entities like Point should be tightly bound to Grade or kept as independent constructs. I ended up choosing association to ensure it could be reused and for better scalability. Figuring out the right responsibilities and methods for each class was also quite difficult. It was quite easy to give too much to entities like Admin or User with unnecessary logic, but I tried to aim to link them by assigning more focused responsibilities. The class diagram is closely aligned with our earlier assignment’s components such as the user stories, the use cases, and the state diagrams. An example would be that the student and educator use cases are represented by the respective subclasses of User, and then the user has actions like redeeming points or submitting disputes which are accurately modelled in class methods. But the system’s scalability and security requirements, which was noted in the initial specifications, are addressed through methods like encrypt_user_data() and load-handling relationships. There were some trade-offs I had to make which were necessary, an example would be while choosing the inheritance over composition for the user roles. The composition relationship does offer more flexibility than the inheritance relationship, the inheritance does provide a more clear and intuitive structure for our current system needs. I had to simplify certain relationships and add them into aggregations to reduce their complexity but improved the readability. While doing this assignment I gained a deeper understanding of object-oriented design principles, learning more about encapsulation, responsibility-driven design, and the importance of clear, maintainable architecture. I also learned that having perfect foresight isn’t a requirement and that it’s better to design for your current known requirements to ensure the system is able to withstand future growth. This whole assignment helped build my confidence in domain modelling and showed me the value of a clean, and logical design while building a scalable, and user-aligned system. -------------------------------------------------------------------------------- /Assignment8/StateTransitionDiagrams.md: -------------------------------------------------------------------------------- 1 | **State Transition Diagrams** 2 | 3 | *User* 4 | ```mermaid 5 | stateDiagram-v2 6 | [*] --> Pending: User registers 7 | Pending --> Active: User verifies email 8 | Active --> Locked: After 3 failed login attempts [within 5 minutes] 9 | Locked --> Active: Admin unlocks the account 10 | Active --> Deactivated: Admin then deactivates account 11 | Deactivated --> Active: Admin then reactivates account 12 | ``` 13 | *Grade* 14 | ```mermaid 15 | stateDiagram-v2 16 | [*] --> Draft: Educator creates a grade 17 | Draft --> Submitted: Educator submits the grade 18 | Submitted --> Disputed: Student disputes the created grade [within 7 days] 19 | Disputed --> Finalized: Educator resolves the dispute 20 | Submitted --> Finalized: No dispute [after 7 days] 21 | ``` 22 | *Points Balance* 23 | ```mermaid 24 | stateDiagram-v2 25 | [*] --> Active: Student earns points 26 | Active --> Active: Points is redeemed [if availablePoints ≥ requested] 27 | Active --> Frozen: Admin can freeze account [fraud detected] 28 | Frozen --> Active: Admin can unfreezes account 29 | ``` 30 | *Rewards Redemption* 31 | ```mermaid 32 | stateDiagram-v2 33 | [*] --> Pending: Student initiates their redemption 34 | Pending --> Processed: Payment is successful [valid payment details] 35 | Pending --> Failed: Payment provider error [invalid account] 36 | Failed --> Processed: Retry succeeds [after 24 hours] 37 | ``` 38 | *Dispute* 39 | ```mermaid 40 | stateDiagram-v2 41 | [*] --> Open: Student submits a dispute 42 | Open --> Under Review: Educator will assign the dispute 43 | Under Review --> Resolved: Educator approves or denies the dispute [with added comments] 44 | Open --> Closed: Student is able to withdraw the dispute 45 | Under Review --> Closed: No action [after 14 days] 46 | ``` 47 | *Transaction* 48 | ```mermaid 49 | stateDiagram-v2 50 | [*] --> Initiated: Redemption request is sent 51 | Initiated --> Completed: Payment is confirmed [status=success] 52 | Initiated --> Failed: Payment is rejected [status=declined] 53 | Failed --> Completed: Retry succeeds [within 24 hours] 54 | ``` 55 | *Security Policy* 56 | ```mermaid 57 | stateDiagram-v2 58 | [*] --> Draft: Admin creates a policy 59 | Draft --> Active: Admin enables the policy [compliance passed] 60 | Active --> Archived: Admin then replaces policy 61 | Archived --> Active: Admin then restores policy 62 | ``` 63 | *Redemption Rate* 64 | ```mermaid 65 | stateDiagram-v2 66 | [*] --> Draft: Admin defines rate 67 | Draft --> Active: Admin activates rate [effectiveDate ≤ today] 68 | Active --> Expired: New rate replaces old rate [effectiveDate > today] 69 | Expired --> Active: Admin reactivates rate 70 | ``` -------------------------------------------------------------------------------- /Assignment10/creational_patterns/redemption-patterns.mjs: -------------------------------------------------------------------------------- 1 | import Redemption from '../src/redemption.js'; 2 | 3 | // SimpleFactory 4 | export default class SimpleRedemptionFactory { 5 | static createRedemption(redemption_id, student_id, points_redeemed, monetary_value, redeemed_at = new Date()) { 6 | return new Redemption(redemption_id, student_id, points_redeemed, monetary_value, redeemed_at); 7 | } 8 | } 9 | 10 | // FactoryMethod 11 | export class RedemptionFactory { 12 | static createRedemption(redemption_id, student_id, points_redeemed, monetary_value, redeemed_at = new Date()) { 13 | return new Redemption(redemption_id, student_id, points_redeemed, monetary_value, redeemed_at); 14 | } 15 | } 16 | 17 | // AbstractFactory 18 | export class RedemptionAbstractFactory { 19 | static createFactory(type) { 20 | switch (type) { 21 | case 'giftcard': 22 | return new GiftCardRedemptionFactory(); 23 | default: 24 | throw new Error('No factory found for this type.'); 25 | } 26 | } 27 | } 28 | 29 | // Builder 30 | export class RedemptionBuilder { 31 | constructor() { 32 | this._redemption = {}; 33 | } 34 | 35 | setRedemptionId(redemption_id) { 36 | this._redemption.redemption_id = redemption_id; 37 | return this; 38 | } 39 | 40 | setStudentId(student_id) { 41 | this._redemption.student_id = student_id; 42 | return this; 43 | } 44 | 45 | setPointsRedeemed(points_redeemed) { 46 | this._redemption.points_redeemed = points_redeemed; 47 | return this; 48 | } 49 | 50 | setMonetaryValue(monetary_value) { 51 | this._redemption.monetary_value = monetary_value; 52 | return this; 53 | } 54 | 55 | setRedeemedAt(redeemed_at) { 56 | this._redemption.redeemed_at = redeemed_at; 57 | return this; 58 | } 59 | 60 | build() { 61 | return new Redemption( 62 | this._redemption.redemption_id, 63 | this._redemption.student_id, 64 | this._redemption.points_redeemed, 65 | this._redemption.monetary_value, 66 | this._redemption.redeemed_at 67 | ); 68 | } 69 | } 70 | 71 | // Prototype 72 | export class RedemptionPrototype { 73 | constructor(redemption) { 74 | this.redemption = redemption; 75 | } 76 | 77 | clone() { 78 | return new RedemptionPrototype(Object.assign({}, this.redemption)); 79 | } 80 | } 81 | 82 | // Singleton 83 | export class RedemptionSingleton { 84 | static getInstance() { 85 | if (!RedemptionSingleton.instance) { 86 | RedemptionSingleton.instance = new Redemption(); 87 | } 88 | return RedemptionSingleton.instance; 89 | } 90 | } -------------------------------------------------------------------------------- /Assignment11/tests/InMemoryAdminRepository.test.mjs: -------------------------------------------------------------------------------- 1 | import { describe, test, expect } from '@jest/globals'; 2 | import InMemoryAdminRepository from '../repositories/inMemory/InMemoryAdminRepository.mjs'; 3 | 4 | describe('InMemoryAdminRepository', () => { 5 | let repository; 6 | 7 | beforeEach(() => { 8 | repository = new InMemoryAdminRepository(); 9 | }); 10 | 11 | test('should save a new admin', () => { 12 | const admin = { admin_id: 'admin_1', name: 'John Doe', permissions: ['manage_users'] }; 13 | repository.save(admin); 14 | const savedAdmin = repository.findById('admin_1'); 15 | expect(savedAdmin).toEqual(admin); 16 | }); 17 | 18 | test('should update an existing admin', () => { 19 | const admin = { admin_id: 'admin_1', name: 'John Doe', permissions: ['manage_users'] }; 20 | repository.save(admin); 21 | 22 | const updatedAdmin = { admin_id: 'admin_1', name: 'John Smith', permissions: ['manage_users', 'view_reports'] }; 23 | repository.save(updatedAdmin); 24 | 25 | const foundUpdatedAdmin = repository.findById('admin_1'); 26 | expect(foundUpdatedAdmin.name).toBe('John Smith'); 27 | expect(foundUpdatedAdmin.permissions).toContain('view_reports'); 28 | }); 29 | 30 | test('should find an admin by ID', () => { 31 | const admin = { admin_id: 'admin_1', name: 'John Doe', permissions: ['manage_users'] }; 32 | repository.save(admin); 33 | 34 | const foundAdmin = repository.findById('admin_1'); 35 | expect(foundAdmin).toEqual(admin); 36 | }); 37 | 38 | test('should return null for a non-existent admin', () => { 39 | const foundAdmin = repository.findById('non_existent_id'); 40 | expect(foundAdmin).toBeNull(); 41 | }); 42 | 43 | test('should find all admins', () => { 44 | const admin1 = { admin_id: 'admin_1', name: 'John Doe', permissions: ['manage_users'] }; 45 | const admin2 = { admin_id: 'admin_2', name: 'Jane Doe', permissions: ['view_reports'] }; 46 | repository.save(admin1); 47 | repository.save(admin2); 48 | 49 | const allAdmins = repository.findAll(); 50 | expect(allAdmins).toHaveLength(2); 51 | expect(allAdmins).toEqual(expect.arrayContaining([admin1, admin2])); 52 | }); 53 | 54 | test('should delete an admin by ID', () => { 55 | const admin = { admin_id: 'admin_1', name: 'John Doe', permissions: ['manage_users'] }; 56 | repository.save(admin); 57 | 58 | repository.delete('admin_1'); 59 | const deletedAdmin = repository.findById('admin_1'); 60 | expect(deletedAdmin).toBeNull(); 61 | }); 62 | 63 | test('should throw an error when deleting a non-existent admin', () => { 64 | expect(() => repository.delete('non_existent_id')).toThrow('Admin with ID non_existent_id not found.'); 65 | }); 66 | }); -------------------------------------------------------------------------------- /Assignment3/SPECIFICATION.md: -------------------------------------------------------------------------------- 1 | # System Specification Document (SPECIFICATION.md) 2 | 3 | ## 2.2.1 Introduction 4 | 5 | ### Project Title: Gradebook Points System 6 | 7 | ### Domain: Education & Rewards 8 | The **Gradebook Points System** falls under the **Education & Rewards** domain. This system is designed for students and educators to track academic performance while providing a rewards-based incentive. The system awards points to students based on their performance in assignments, tests, and tasks. These points can then be converted into monetary rewards, motivating students to actively engage in their studies and improve their academic performance. 9 | 10 | ### Problem Statement 11 | Traditional grading systems provide students with marks but often lack immediate incentives that encourage continuous effort and engagement. The **Gradebook Points System** aims to address this by integrating a reward mechanism where students earn points for completing tasks and achieving good grades. This system fosters a culture of academic motivation and improvement by providing tangible benefits for students' hard work. 12 | 13 | ### Individual Scope 14 | The feasibility of the **Gradebook Points System** is justified by: 15 | - **Technological Advancements:** The system can be implemented using cloud-based applications, databases, and automation tools to efficiently track and manage student points. 16 | - **Educational Benefits:** Encouraging student engagement and performance through incentives can lead to better learning outcomes and retention rates. 17 | - **Scalability:** The system can be adapted for different educational institutions, from schools to universities, and customized to suit various grading structures. 18 | - **Impact Potential:** By linking academic success to tangible rewards, students are more likely to stay motivated, reduce procrastination, and develop a stronger work ethic, leading to long-term benefits in education and personal development. 19 | 20 | ### Potential Challenges 21 | Despite its benefits, the **Gradebook Points System** may face several challenges: 22 | - **Financial Sustainability:** Ensuring that there is a sustainable funding model for converting points into monetary rewards. 23 | - **Academic Integrity Issues:** Some students may attempt to exploit the system by cheating or manipulating grades to earn more points. 24 | - **Fairness and Equity:** Differences in difficulty levels of assignments and tests may create inequalities in point distribution. 25 | - **Administrative Overhead:** Teachers and administrators may have an increased workload in managing the system and verifying points. 26 | - **Student Overemphasis on Rewards:** There is a risk that students may focus more on earning money rather than learning and understanding the subject matter. 27 | - **Technical Issues:** Possible system downtimes, security vulnerabilities, or integration difficulties with existing educational platforms. -------------------------------------------------------------------------------- /Assignment10/creational_patterns/point-patterns.mjs: -------------------------------------------------------------------------------- 1 | import Point from '../src/point.js'; 2 | 3 | // SimpleFactory 4 | export default class SimplePointFactory { 5 | static createPoint(point_id, student_id, grade_id, value, awarded_at = new Date()) { 6 | return new Point(point_id, student_id, grade_id, value, awarded_at); 7 | } 8 | } 9 | 10 | // FactoryMethod 11 | export class PointFactory { 12 | createPoint(point_id, student_id, grade_id, value, awarded_at = new Date()) { 13 | throw new Error('This method should be overridden.'); 14 | } 15 | } 16 | export class VerifiedPointFactory extends PointFactory { 17 | createPoint(point_id, student_id, grade_id, value, awarded_at = new Date()) { 18 | return new Point(point_id, student_id, grade_id, value, awarded_at); 19 | } 20 | } 21 | 22 | // AbstractFactory 23 | export class PointAbstractFactory { 24 | static createFactory(type) { 25 | switch (type) { 26 | case 'verified': 27 | return new VerifiedPointFactory(); 28 | default: 29 | throw new Error('No factory found for this type.'); 30 | } 31 | } 32 | } 33 | 34 | // Builder 35 | export class PointBuilder { 36 | constructor() { 37 | this._point = {}; 38 | } 39 | 40 | setPointId(id) { 41 | this._point.point_id = id; 42 | return this; 43 | } 44 | 45 | setStudentId(studentId) { 46 | this._point.student_id = studentId; 47 | return this; 48 | } 49 | 50 | setGradeId(gradeId) { 51 | this._point.grade_id = gradeId; 52 | return this; 53 | } 54 | 55 | setValue(value) { 56 | this._point.value = value; 57 | return this; 58 | } 59 | 60 | setAwardedAt(date) { 61 | this._point.awarded_at = date; 62 | return this; 63 | } 64 | 65 | build() { 66 | return new Point( 67 | this._point.point_id, 68 | this._point.student_id, 69 | this._point.grade_id, 70 | this._point.value, 71 | this._point.awarded_at 72 | ); 73 | } 74 | } 75 | 76 | // Prototype 77 | export class PointPrototype { 78 | constructor(point) { 79 | this._point = point; 80 | } 81 | 82 | clone() { 83 | return new PointPrototype(Object.assign({}, this._point)); 84 | } 85 | } 86 | 87 | // Singleton 88 | export class PointSingleton { 89 | constructor() { 90 | if (!PointSingleton.instance) { 91 | this._point = new Point(); 92 | PointSingleton.instance = this; 93 | } 94 | return PointSingleton.instance; 95 | } 96 | 97 | getPoint() { 98 | return this._point; 99 | } 100 | 101 | setPoint(point) { 102 | this._point = point; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Assignment10/creational_patterns/educator-patterns.mjs: -------------------------------------------------------------------------------- 1 | import Educator from '../src/educator.mjs'; 2 | 3 | // SimpleFactory 4 | export class SimpleEducatorFactory { 5 | static createEducator(educator_id, user_id, department, assigned_courses = []) { 6 | return new Educator(educator_id, user_id, department, assigned_courses); 7 | } 8 | } 9 | 10 | // FactoryMethod 11 | class EducatorFactory { 12 | createEducator(educator_id, user_id, department, assigned_courses = []) { 13 | throw new Error('This method should be overridden.'); 14 | } 15 | } 16 | 17 | export class SeniorEducatorFactory extends EducatorFactory { 18 | createEducator(educator_id, user_id, department, assigned_courses = []) { 19 | return new Educator(educator_id, user_id, department, assigned_courses); 20 | } 21 | } 22 | 23 | // AbstractFactory 24 | export class EducatorAbstractFactory { 25 | static createFactory(role) { 26 | switch (role) { 27 | case 'senior': 28 | return new SeniorEducatorFactory(); 29 | default: 30 | throw new Error('No factory found for this role.'); 31 | } 32 | } 33 | } 34 | 35 | // Builder 36 | export class EducatorBuilder { 37 | constructor() { 38 | this._educator = {}; 39 | } 40 | 41 | setEducatorId(id) { 42 | this._educator.educator_id = id; 43 | return this; 44 | } 45 | 46 | setUserId(userId) { 47 | this._educator.user_id = userId; 48 | return this; 49 | } 50 | 51 | setDepartment(dept) { 52 | this._educator.department = dept; 53 | return this; 54 | } 55 | 56 | setAssignedCourses(courses) { 57 | this._educator.assigned_courses = courses; 58 | return this; 59 | } 60 | 61 | build() { 62 | return new Educator( 63 | this._educator.educator_id, 64 | this._educator.user_id, 65 | this._educator.department, 66 | this._educator.assigned_courses 67 | ); 68 | } 69 | } 70 | 71 | // Prototype 72 | export class EducatorPrototype { 73 | constructor(educator) { 74 | this.educator = educator; 75 | } 76 | 77 | clone() { 78 | return Object.assign( 79 | Object.create(Object.getPrototypeOf(this.educator)), 80 | this.educator 81 | ); 82 | } 83 | } 84 | 85 | // Singleton 86 | export class EducatorSingleton { 87 | constructor() { 88 | if (EducatorSingleton.instance) { 89 | return EducatorSingleton.instance; 90 | } 91 | this.educators = []; 92 | EducatorSingleton.instance = this; 93 | } 94 | 95 | addEducator(educator) { 96 | this.educators.push(educator); 97 | } 98 | 99 | getEducators() { 100 | return this.educators; 101 | } 102 | } -------------------------------------------------------------------------------- /Assignment10/creational_patterns/grade-patterns.mjs: -------------------------------------------------------------------------------- 1 | import Grade from '../src/grade.mjs'; 2 | 3 | // SimpleFactory 4 | export default class SimpleGradeFactory { 5 | static createGrade(grade_id, student_id, course_name, score, uploaded_by) { 6 | return new Grade(grade_id, student_id, course_name, score, uploaded_by); 7 | } 8 | } 9 | 10 | // FactoryMethod 11 | class GradeFactory { 12 | createGrade(grade_id, student_id, course_name, score, uploaded_by) { 13 | throw new Error('This method should be overridden.'); 14 | } 15 | } 16 | 17 | export class VerifiedGradeFactory extends GradeFactory { 18 | createGrade(grade_id, student_id, course_name, score, uploaded_by) { 19 | return new Grade(grade_id, student_id, course_name, score, uploaded_by); 20 | } 21 | } 22 | 23 | // AbstractFactory 24 | export class GradeAbstractFactory { 25 | static createFactory(type) { 26 | switch (type) { 27 | case 'verified': 28 | return new VerifiedGradeFactory(); 29 | default: 30 | throw new Error('No factory found for this type.'); 31 | } 32 | } 33 | } 34 | 35 | // Builder 36 | export class GradeBuilder { 37 | constructor() { 38 | this._grade = {}; 39 | } 40 | 41 | setGradeId(id) { 42 | this._grade.grade_id = id; 43 | return this; 44 | } 45 | 46 | setStudentId(studentId) { 47 | this._grade.student_id = studentId; 48 | return this; 49 | } 50 | 51 | setCourseName(courseName) { 52 | this._grade.course_name = courseName; 53 | return this; 54 | } 55 | 56 | setScore(score) { 57 | this._grade.score = score; 58 | return this; 59 | } 60 | 61 | setUploadedBy(uploadedBy) { 62 | this._grade.uploaded_by = uploadedBy; 63 | return this; 64 | } 65 | 66 | build() { 67 | return new Grade( 68 | this._grade.grade_id, 69 | this._grade.student_id, 70 | this._grade.course_name, 71 | this._grade.score, 72 | this._grade.uploaded_by 73 | ); 74 | } 75 | } 76 | 77 | // Prototype 78 | export class GradePrototype { 79 | constructor(grade) { 80 | this.grade = grade; 81 | } 82 | 83 | clone() { 84 | return Object.assign( 85 | Object.create(Object.getPrototypeOf(this.grade)), 86 | this.grade 87 | ); 88 | } 89 | } 90 | 91 | // Singleton 92 | export class GradeManager { 93 | constructor() { 94 | if (GradeManager.instance) { 95 | return GradeManager.instance; 96 | } 97 | this.grades = []; 98 | GradeManager.instance = this; 99 | } 100 | addGrade(grade) { 101 | this.grades.push(grade); 102 | } 103 | getGrades() { 104 | return this.grades; 105 | } 106 | } -------------------------------------------------------------------------------- /Assignment11/tests/InMemoryGradeRepository.test.mjs: -------------------------------------------------------------------------------- 1 | import { describe, test, expect } from '@jest/globals'; 2 | import InMemoryGradeRepository from '../repositories/inMemory/InMemoryGradeRepository.mjs'; 3 | 4 | describe('InMemoryGradeRepository', () => { 5 | let repository; 6 | 7 | beforeEach(() => { 8 | repository = new InMemoryGradeRepository(); 9 | }); 10 | 11 | test('should save a new grade', () => { 12 | const grade = { grade_id: 'grade_1', student_id: 'student_1', course_id: 'course_1', grade_value: 'A', created_at: new Date() }; 13 | repository.save(grade); 14 | const savedGrade = repository.findById('grade_1'); 15 | expect(savedGrade).toEqual(grade); 16 | }); 17 | 18 | test('should update an existing grade', () => { 19 | const grade = { grade_id: 'grade_1', student_id: 'student_1', course_id: 'course_1', grade_value: 'A', created_at: new Date() }; 20 | repository.save(grade); 21 | 22 | const updatedGrade = { grade_id: 'grade_1', student_id: 'student_1', course_id: 'course_1', grade_value: 'B', created_at: new Date() }; 23 | repository.save(updatedGrade); 24 | 25 | const foundUpdatedGrade = repository.findById('grade_1'); 26 | expect(foundUpdatedGrade.grade_value).toBe('B'); 27 | }); 28 | 29 | test('should find a grade by ID', () => { 30 | const grade = { grade_id: 'grade_1', student_id: 'student_1', course_id: 'course_1', grade_value: 'A', created_at: new Date() }; 31 | repository.save(grade); 32 | 33 | const foundGrade = repository.findById('grade_1'); 34 | expect(foundGrade).toEqual(grade); 35 | }); 36 | 37 | test('should return null for a non-existent grade', () => { 38 | const foundGrade = repository.findById('non_existent_id'); 39 | expect(foundGrade).toBeNull(); 40 | }); 41 | 42 | test('should find all grades', () => { 43 | const grade1 = { grade_id: 'grade_1', student_id: 'student_1', course_id: 'course_1', grade_value: 'A', created_at: new Date() }; 44 | const grade2 = { grade_id: 'grade_2', student_id: 'student_2', course_id: 'course_2', grade_value: 'B', created_at: new Date() }; 45 | repository.save(grade1); 46 | repository.save(grade2); 47 | 48 | const allGrades = repository.findAll(); 49 | expect(allGrades).toHaveLength(2); 50 | expect(allGrades).toEqual(expect.arrayContaining([grade1, grade2])); 51 | }); 52 | 53 | test('should delete a grade by ID', () => { 54 | const grade = { grade_id: 'grade_1', student_id: 'student_1', course_id: 'course_1', grade_value: 'A', created_at: new Date() }; 55 | repository.save(grade); 56 | 57 | repository.delete('grade_1'); 58 | const deletedGrade = repository.findById('grade_1'); 59 | expect(deletedGrade).toBeNull(); 60 | }); 61 | 62 | test('should throw an error when deleting a non-existent grade', () => { 63 | expect(() => repository.delete('non_existent_id')).toThrow('Grade with ID non_existent_id not found.'); 64 | }); 65 | }); -------------------------------------------------------------------------------- /Assignment11/tests/InMemoryPointRepository.test.mjs: -------------------------------------------------------------------------------- 1 | import { describe, test, expect } from '@jest/globals'; 2 | import InMemoryPointRepository from '../repositories/inMemory/InMemoryPointRepository.mjs'; 3 | 4 | describe('InMemoryPointRepository', () => { 5 | let repository; 6 | 7 | beforeEach(() => { 8 | repository = new InMemoryPointRepository(); 9 | }); 10 | 11 | test('should save a new point record', () => { 12 | const point = { point_id: 'point_1', student_id: 'student_1', points_earned: 50, points_redeemed: 20, created_at: new Date() }; 13 | repository.save(point); 14 | const savedPoint = repository.findById('point_1'); 15 | expect(savedPoint).toEqual(point); 16 | }); 17 | 18 | test('should update an existing point record', () => { 19 | const point = { point_id: 'point_1', student_id: 'student_1', points_earned: 50, points_redeemed: 20, created_at: new Date() }; 20 | repository.save(point); 21 | 22 | const updatedPoint = { point_id: 'point_1', student_id: 'student_1', points_earned: 70, points_redeemed: 30, created_at: new Date() }; 23 | repository.save(updatedPoint); 24 | 25 | const foundUpdatedPoint = repository.findById('point_1'); 26 | expect(foundUpdatedPoint.points_earned).toBe(70); 27 | expect(foundUpdatedPoint.points_redeemed).toBe(30); 28 | }); 29 | 30 | test('should find a point record by ID', () => { 31 | const point = { point_id: 'point_1', student_id: 'student_1', points_earned: 50, points_redeemed: 20, created_at: new Date() }; 32 | repository.save(point); 33 | 34 | const foundPoint = repository.findById('point_1'); 35 | expect(foundPoint).toEqual(point); 36 | }); 37 | 38 | test('should return null for a non-existent point record', () => { 39 | const foundPoint = repository.findById('non_existent_id'); 40 | expect(foundPoint).toBeNull(); 41 | }); 42 | 43 | test('should find all point records', () => { 44 | const point1 = { point_id: 'point_1', student_id: 'student_1', points_earned: 50, points_redeemed: 20, created_at: new Date() }; 45 | const point2 = { point_id: 'point_2', student_id: 'student_2', points_earned: 100, points_redeemed: 50, created_at: new Date() }; 46 | repository.save(point1); 47 | repository.save(point2); 48 | 49 | const allPoints = repository.findAll(); 50 | expect(allPoints).toHaveLength(2); 51 | expect(allPoints).toEqual(expect.arrayContaining([point1, point2])); 52 | }); 53 | 54 | test('should delete a point record by ID', () => { 55 | const point = { point_id: 'point_1', student_id: 'student_1', points_earned: 50, points_redeemed: 20, created_at: new Date() }; 56 | repository.save(point); 57 | 58 | repository.delete('point_1'); 59 | const deletedPoint = repository.findById('point_1'); 60 | expect(deletedPoint).toBeNull(); 61 | }); 62 | 63 | test('should throw an error when deleting a non-existent point record', () => { 64 | expect(() => repository.delete('non_existent_id')).toThrow('Point with ID non_existent_id not found.'); 65 | }); 66 | }); -------------------------------------------------------------------------------- /Assignment11/tests/InMemoryStudentRepository.test.mjs: -------------------------------------------------------------------------------- 1 | import { describe, test, expect } from '@jest/globals'; 2 | import InMemoryStudentRepository from '../repositories/inMemory/InMemoryStudentRepository.mjs'; 3 | 4 | describe('InMemoryStudentRepository', () => { 5 | let repository; 6 | 7 | beforeEach(() => { 8 | repository = new InMemoryStudentRepository(); 9 | }); 10 | 11 | test('should save a new student', () => { 12 | const student = { student_id: 'student_1', user_id: 'user_1', total_points: 100, redeemed_points: 20, grade_average: 85.5 }; 13 | repository.save(student); 14 | const savedStudent = repository.findById('student_1'); 15 | expect(savedStudent).toEqual(student); 16 | }); 17 | 18 | test('should update an existing student', () => { 19 | const student = { student_id: 'student_1', user_id: 'user_1', total_points: 100, redeemed_points: 20, grade_average: 85.5 }; 20 | repository.save(student); 21 | 22 | const updatedStudent = { student_id: 'student_1', user_id: 'user_1', total_points: 120, redeemed_points: 30, grade_average: 90.0 }; 23 | repository.save(updatedStudent); 24 | 25 | const foundUpdatedStudent = repository.findById('student_1'); 26 | expect(foundUpdatedStudent.total_points).toBe(120); 27 | expect(foundUpdatedStudent.grade_average).toBe(90.0); 28 | }); 29 | 30 | test('should find a student by ID', () => { 31 | const student = { student_id: 'student_1', user_id: 'user_1', total_points: 100, redeemed_points: 20, grade_average: 85.5 }; 32 | repository.save(student); 33 | 34 | const foundStudent = repository.findById('student_1'); 35 | expect(foundStudent).toEqual(student); 36 | }); 37 | 38 | test('should return null for a non-existent student', () => { 39 | const foundStudent = repository.findById('non_existent_id'); 40 | expect(foundStudent).toBeNull(); 41 | }); 42 | 43 | test('should find all students', () => { 44 | const student1 = { student_id: 'student_1', user_id: 'user_1', total_points: 100, redeemed_points: 20, grade_average: 85.5 }; 45 | const student2 = { student_id: 'student_2', user_id: 'user_2', total_points: 200, redeemed_points: 50, grade_average: 90.0 }; 46 | repository.save(student1); 47 | repository.save(student2); 48 | 49 | const allStudents = repository.findAll(); 50 | expect(allStudents).toHaveLength(2); 51 | expect(allStudents).toEqual(expect.arrayContaining([student1, student2])); 52 | }); 53 | 54 | test('should delete a student by ID', () => { 55 | const student = { student_id: 'student_1', user_id: 'user_1', total_points: 100, redeemed_points: 20, grade_average: 85.5 }; 56 | repository.save(student); 57 | 58 | repository.delete('student_1'); 59 | const deletedStudent = repository.findById('student_1'); 60 | expect(deletedStudent).toBeNull(); 61 | }); 62 | 63 | test('should throw an error when deleting a non-existent student', () => { 64 | expect(() => repository.delete('non_existent_id')).toThrow('Student with ID non_existent_id not found.'); 65 | }); 66 | }); -------------------------------------------------------------------------------- /Assignment11/tests/InMemoryEducatorRepository.test.mjs: -------------------------------------------------------------------------------- 1 | import { describe, test, expect } from '@jest/globals'; 2 | import InMemoryEducatorRepository from '../repositories/inMemory/InMemoryEducatorRepository.mjs'; 3 | 4 | describe('InMemoryEducatorRepository', () => { 5 | let repository; 6 | 7 | beforeEach(() => { 8 | repository = new InMemoryEducatorRepository(); 9 | }); 10 | 11 | test('should save a new educator', () => { 12 | const educator = { educator_id: 'educator_1', user_id: 'user_1', department: 'Math', courses: ['Math101'], created_at: new Date() }; 13 | repository.save(educator); 14 | const savedEducator = repository.findById('educator_1'); 15 | expect(savedEducator).toEqual(educator); 16 | }); 17 | 18 | test('should update an existing educator', () => { 19 | const educator = { educator_id: 'educator_1', user_id: 'user_1', department: 'Math', courses: ['Math101'], created_at: new Date() }; 20 | repository.save(educator); 21 | 22 | const updatedEducator = { educator_id: 'educator_1', user_id: 'user_1', department: 'Science', courses: ['Sci101'], created_at: new Date() }; 23 | repository.save(updatedEducator); 24 | 25 | const foundUpdatedEducator = repository.findById('educator_1'); 26 | expect(foundUpdatedEducator.department).toBe('Science'); 27 | expect(foundUpdatedEducator.courses).toContain('Sci101'); 28 | }); 29 | 30 | test('should find an educator by ID', () => { 31 | const educator = { educator_id: 'educator_1', user_id: 'user_1', department: 'Math', courses: ['Math101'], created_at: new Date() }; 32 | repository.save(educator); 33 | 34 | const foundEducator = repository.findById('educator_1'); 35 | expect(foundEducator).toEqual(educator); 36 | }); 37 | 38 | test('should return null for a non-existent educator', () => { 39 | const foundEducator = repository.findById('non_existent_id'); 40 | expect(foundEducator).toBeNull(); 41 | }); 42 | 43 | test('should find all educators', () => { 44 | const educator1 = { educator_id: 'educator_1', user_id: 'user_1', department: 'Math', courses: ['Math101'], created_at: new Date() }; 45 | const educator2 = { educator_id: 'educator_2', user_id: 'user_2', department: 'Science', courses: ['Sci101'], created_at: new Date() }; 46 | repository.save(educator1); 47 | repository.save(educator2); 48 | 49 | const allEducators = repository.findAll(); 50 | expect(allEducators).toHaveLength(2); 51 | expect(allEducators).toEqual(expect.arrayContaining([educator1, educator2])); 52 | }); 53 | 54 | test('should delete an educator by ID', () => { 55 | const educator = { educator_id: 'educator_1', user_id: 'user_1', department: 'Math', courses: ['Math101'], created_at: new Date() }; 56 | repository.save(educator); 57 | 58 | repository.delete('educator_1'); 59 | const deletedEducator = repository.findById('educator_1'); 60 | expect(deletedEducator).toBeNull(); 61 | }); 62 | 63 | test('should throw an error when deleting a non-existent educator', () => { 64 | expect(() => repository.delete('non_existent_id')).toThrow('Educator with ID non_existent_id not found.'); 65 | }); 66 | }); -------------------------------------------------------------------------------- /Assignment10/creational_patterns/dispute-patterns.mjs: -------------------------------------------------------------------------------- 1 | import Dispute from '../src/dispute.js'; 2 | 3 | // SimpleFactory 4 | export default class SimpleDisputeFactory { 5 | static createDispute(dispute_id, student_id, grade_id, reason, status = 'pending', submitted_at = new Date()) { 6 | return new Dispute(dispute_id, student_id, grade_id, reason, status, submitted_at); 7 | } 8 | } 9 | 10 | // FactoryMethod 11 | class DisputeFactory { 12 | createDispute(dispute_id, student_id, grade_id, reason, status = 'pending', submitted_at = new Date()) { 13 | throw new Error('This method should be overridden.'); 14 | } 15 | } 16 | 17 | export class AcademicDisputeFactory extends DisputeFactory { 18 | createDispute(dispute_id, student_id, grade_id, reason, status = 'pending', submitted_at = new Date()) { 19 | return new Dispute(dispute_id, student_id, grade_id, `[ACADEMIC] ${reason}`, status, submitted_at); 20 | } 21 | } 22 | 23 | // AbstractFactory 24 | export class DisputeAbstractFactory { 25 | static createFactory(type) { 26 | switch (type) { 27 | case 'academic': 28 | return new AcademicDisputeFactory(); 29 | default: 30 | throw new Error('No factory found for this type.'); 31 | } 32 | } 33 | } 34 | 35 | // Builder 36 | export class DisputeBuilder { 37 | constructor() { 38 | this._dispute = {}; 39 | } 40 | 41 | setDisputeId(id) { 42 | this._dispute.dispute_id = id; 43 | return this; 44 | } 45 | 46 | setStudentId(studentId) { 47 | this._dispute.student_id = studentId; 48 | return this; 49 | } 50 | 51 | setGradeId(gradeId) { 52 | this._dispute.grade_id = gradeId; 53 | return this; 54 | } 55 | 56 | setReason(reason) { 57 | this._dispute.reason = reason; 58 | return this; 59 | } 60 | 61 | setStatus(status) { 62 | this._dispute.status = status; 63 | return this; 64 | } 65 | 66 | setSubmittedAt(date) { 67 | this._dispute.submitted_at = date; 68 | return this; 69 | } 70 | 71 | build() { 72 | return new Dispute( 73 | this._dispute.dispute_id, 74 | this._dispute.student_id, 75 | this._dispute.grade_id, 76 | this._dispute.reason, 77 | this._dispute.status, 78 | this._dispute.submitted_at 79 | ); 80 | } 81 | } 82 | 83 | // Prototype 84 | export class DisputePrototype { 85 | constructor(dispute) { 86 | this.dispute = dispute; 87 | } 88 | 89 | clone() { 90 | return Object.assign( 91 | Object.create(Object.getPrototypeOf(this.proto)), 92 | this.proto 93 | ); 94 | } 95 | } 96 | 97 | // Singleton 98 | export class DisputeSingleton { 99 | constructor() { 100 | if (DisputeSingleton.instance) { 101 | return DisputeSingleton.instance; 102 | } 103 | this.disputes = []; 104 | DisputeSingleton.instance = this; 105 | } 106 | 107 | addDispute(dispute) { 108 | this.disputes.push(dispute); 109 | } 110 | 111 | getDisputes() { 112 | return this.disputes; 113 | } 114 | } -------------------------------------------------------------------------------- /Assignment6/ProductBacklogCreation.md: -------------------------------------------------------------------------------- 1 | **Product Backlog Creation Table** 2 | 3 | |Story ID | User Story | Priority (MoSCoW) | Effort Estimate (SP) | Dependencies | 4 | |---------|------------|-------------------|----------------------|--------------| 5 | | US-001 | Register and log in securely using email and password or SSO. | Must-have | 5 | None | 6 | | US-002 | Process and convert grades into points. | Must-have | 5 | US-007 | 7 | | US-003 | Redeem points for monetary rewards. | Must-have | 5 | US-002 | 8 | | US-004 | View academic progress, the total points earned, and the transaction history on a dashboard. | Must-have | 5 | US-002, US-003 | 9 | | US-005 | Send notifications when grades are uploaded, and when points are earned or redeemed. | Should-have | 3 | US-002, US-003 | 10 | | US-006 | Submit a dispute via form or and API. | Should-have | 3 | US-002 | 11 | | US-007 | Upload student grades manually to manage student records. | Must-have | 5 | None | 12 | |US-008 | Assign roles to students, educators, and admins for specific access control. | Must-have | 3 | US-001 | 13 | | US-009 | Encrypt user data for security and compliance. | Must-have | 5 | None | 14 | | US-010 | Generate transaction receipts for redemptions. | Should-have | 3 | US-003 | 15 | 16 | *Justifying Prioritization* 17 | 18 | Must-have Stories: 19 | 20 | They are critical to the minimum viable product (MVP) and align with core stakeholder success metrics (security, usability, compliance, and core functionality): 21 | 22 | *US-001 (Registration/Login)* To secure the authentication of users is foundational for the users trust and system access. SSO supports and ensures the flexibility for institutional users. 23 | 24 | *US-002 (Process Grades into Points)* This directly relates to the product’s value proposition which ultimately is to convert academic performance into rewards. It depends on story ID US-007 which is to upload grades, which is also a must-have. 25 | 26 | *US-003 (Redeem Points for Rewards)* At it's core is a monetization or reward structure. If this does not work then the product doe not deliver the main motive. 27 | 28 | *US-004 (Dashboard for Progress/History)* Unifies the users experience by displaying their earned points, academic progress, and their transactions in one place and this is key for of the system transparency and engagement within the. 29 | 30 | *US-007 (Upload Grades Manually)* Allows the educators and/or admins to input the data needed for US-002. If the grade uploads does not work, the system is not functioning. 31 | 32 | *US-008 (Role-Based Access Control)* This feature is critical for security and compliance purposes. It depends on US-001 though. 33 | 34 | *US-009 (Data Encryption)* This is a non-negotiable for compliance with data protection laws and user trust. 35 | 36 | Should-Have Stories: 37 | 38 | They enhance the systems usability and functionality, but are not critical to launch: 39 | 40 | *US-005 (Notifications)* This improves the users engagement in the system by alerting users about grade updates and point changes, although the core system can operate without it. 41 | 42 | *US-006 (Dispute Submission)* It adds a layer of user support, but can be left out if the initial dispute handling is managed manually. 43 | 44 | *US-010 (Generate Transaction Receipts)* This is to build trust by documenting redemptions and it is secondary to enabling the redemption process itself. -------------------------------------------------------------------------------- /Assignment7/template_analysis.md: -------------------------------------------------------------------------------- 1 | **Template Analysis Table** 2 | 3 | | Template | Columns and Workflows | Automation Features | Suitability for Agile Methodologies | 4 | |----------|-----------------------|---------------------|-------------------------------------| 5 | | Basic Kanban | The columns are customizable with each being able to reflect the different team stages. The workflow is quite simple with a linear workflow. An example would look like "To Do" -> "In Progress" -> "Done". This template focuses on minimalism, and there are no automated rules for transitions. | There is barely any automation and users have to manually move tickets through each column. It's structure in it's simplest form and is best used for teams needing full control without much automation.| It is a core agile tool which supports iterative workflows, the daily standups, and has work in progress limits. It is both simple and flexible for Scrum or Kanban teams. | 6 | | Automated Kanban | Is very similar to the Basic Kanban template, but has more rules allowing for automated transitions. An example would include something like "Code Review" -> "Testing on PR approval". The workflows have triggers that move tasks automatically. The rules are put in place to reduce the manual work. | It has the most robust automation out of all the templates. The rule-based triggers include auto-move tasks on completion, notifying the assignees, and the Continuous Integration/Continuous Delivery (CI/CD) pipelines. | It enhances the agile methodology with automation by reducing the manual overhead in sprint planning and the execution. | 7 | | Bug Triage | It has columns that are tailored for bug lifecycles it includes specialised fields like severity or priority labels. An example of this template's worklfow would look like "New" -> "Triage" -> "Investigating" -> "Fixing" -> "Resolved". Bug Triage is mainly focused on bug fixing.| It has an auto-labeling feature, and escalates rules for high-priority bugs. It also focuses on streamlining bug prioritization and assignment of tasks.| It definitely fits the agile methodology and focuses on the continuous improvement. It is very much specialized for agile teams managing bug backlogs. 8 | | Team Planning | It combines task boards with timelines or Gantt charts for dependencies and milestones. It includes multi-dimensional workflows which may look similar to "Backlog" -> "Sprint Planning" -> "In Progress" -> "Review" -> "Done". It supports very complex project phases. | Team Planning automates timeline risks, the deadline reminders, and dependency alerts. The automation includes timeline management and proper resource allocation. | It supports a scaled agile methodology with timelines and cross-team dependencies. It's not a typical agile methodolgy structure but it is definitely adaptable. It works well with a hybrid Agile-Waterfall project framework or a large-scale Agile coordination. | 9 | 10 | 11 | **Justification** 12 | 13 | Automated Kanban integrates CI/CD pipelines which automatically moves the tasks to the different columns as soon as the code is merged. This reduces the manual work of doing it, giving the team working on this project more time to focus on their tasks then worrying about moving tickets on the board. It also allows everyone working on the project to see where everyone is with their tickets, so if they need to use code that another person is working on they will be able to see it in "real-time". -------------------------------------------------------------------------------- /Assignment11/tests/InMemoryUserRepository.test.mjs: -------------------------------------------------------------------------------- 1 | import { describe, test, expect } from '@jest/globals'; 2 | import InMemoryUserRepository from '../repositories/inMemory/InMemoryUserRepository.mjs'; 3 | 4 | describe('InMemoryUserRepository', () => { 5 | let repository; 6 | 7 | beforeEach(() => { 8 | repository = new InMemoryUserRepository(); 9 | }); 10 | 11 | test('should save a new user', () => { 12 | const user = { user_id: 'user_1', name: 'John Doe', email: 'john@example.com', password_hash: 'hashed_password', role: 'admin', is_verified: true, created_at: new Date() }; 13 | repository.save(user); 14 | const savedUser = repository.findById('user_1'); 15 | expect(savedUser).toEqual(user); 16 | }); 17 | 18 | test('should update an existing user', () => { 19 | const user = { user_id: 'user_1', name: 'John Doe', email: 'john@example.com', password_hash: 'hashed_password', role: 'admin', is_verified: true, created_at: new Date() }; 20 | repository.save(user); 21 | 22 | const updatedUser = { user_id: 'user_1', name: 'John Smith', email: 'johnsmith@example.com', password_hash: 'new_hashed_password', role: 'user', is_verified: false, created_at: new Date() }; 23 | repository.save(updatedUser); 24 | 25 | const foundUpdatedUser = repository.findById('user_1'); 26 | expect(foundUpdatedUser.name).toBe('John Smith'); 27 | expect(foundUpdatedUser.email).toBe('johnsmith@example.com'); 28 | }); 29 | 30 | test('should find a user by ID', () => { 31 | const user = { user_id: 'user_1', name: 'John Doe', email: 'john@example.com', password_hash: 'hashed_password', role: 'admin', is_verified: true, created_at: new Date() }; 32 | repository.save(user); 33 | 34 | const foundUser = repository.findById('user_1'); 35 | expect(foundUser).toEqual(user); 36 | }); 37 | 38 | test('should return null for a non-existent user', () => { 39 | const foundUser = repository.findById('non_existent_id'); 40 | expect(foundUser).toBeNull(); 41 | }); 42 | 43 | test('should find all users', () => { 44 | const user1 = { user_id: 'user_1', name: 'John Doe', email: 'john@example.com', password_hash: 'hashed_password', role: 'admin', is_verified: true, created_at: new Date() }; 45 | const user2 = { user_id: 'user_2', name: 'Jane Doe', email: 'jane@example.com', password_hash: 'hashed_password', role: 'user', is_verified: false, created_at: new Date() }; 46 | repository.save(user1); 47 | repository.save(user2); 48 | 49 | const allUsers = repository.findAll(); 50 | expect(allUsers).toHaveLength(2); 51 | expect(allUsers).toEqual(expect.arrayContaining([user1, user2])); 52 | }); 53 | 54 | test('should delete a user by ID', () => { 55 | const user = { user_id: 'user_1', name: 'John Doe', email: 'john@example.com', password_hash: 'hashed_password', role: 'admin', is_verified: true, created_at: new Date() }; 56 | repository.save(user); 57 | 58 | repository.delete('user_1'); 59 | const deletedUser = repository.findById('user_1'); 60 | expect(deletedUser).toBeNull(); 61 | }); 62 | 63 | test('should throw an error when deleting a non-existent user', () => { 64 | expect(() => repository.delete('non_existent_id')).toThrow('User with ID non_existent_id not found.'); 65 | }); 66 | }); -------------------------------------------------------------------------------- /Assignment11/tests/InMemoryRedemptionRuleRepository.test.mjs: -------------------------------------------------------------------------------- 1 | import { describe, test, expect } from '@jest/globals'; 2 | import InMemoryRedemptionRuleRepository from '../repositories/inMemory/InMemoryRedemptionRuleRepository.mjs'; 3 | 4 | describe('InMemoryRedemptionRuleRepository', () => { 5 | let repository; 6 | 7 | beforeEach(() => { 8 | repository = new InMemoryRedemptionRuleRepository(); 9 | }); 10 | 11 | test('should save a new redemption rule', () => { 12 | const rule = { rule_id: 'rule_1', description: 'Redeem 100 points for a $10 voucher', points_required: 100, reward: 'Voucher', created_at: new Date() }; 13 | repository.save(rule); 14 | const savedRule = repository.findById('rule_1'); 15 | expect(savedRule).toEqual(rule); 16 | }); 17 | 18 | test('should update an existing redemption rule', () => { 19 | const rule = { rule_id: 'rule_1', description: 'Redeem 100 points for a $10 voucher', points_required: 100, reward: 'Voucher', created_at: new Date() }; 20 | repository.save(rule); 21 | 22 | const updatedRule = { rule_id: 'rule_1', description: 'Redeem 150 points for a $15 voucher', points_required: 150, reward: 'Voucher', created_at: new Date() }; 23 | repository.save(updatedRule); 24 | 25 | const foundUpdatedRule = repository.findById('rule_1'); 26 | expect(foundUpdatedRule.points_required).toBe(150); 27 | expect(foundUpdatedRule.description).toBe('Redeem 150 points for a $15 voucher'); 28 | }); 29 | 30 | test('should find a redemption rule by ID', () => { 31 | const rule = { rule_id: 'rule_1', description: 'Redeem 100 points for a $10 voucher', points_required: 100, reward: 'Voucher', created_at: new Date() }; 32 | repository.save(rule); 33 | 34 | const foundRule = repository.findById('rule_1'); 35 | expect(foundRule).toEqual(rule); 36 | }); 37 | 38 | test('should return null for a non-existent redemption rule', () => { 39 | const foundRule = repository.findById('non_existent_id'); 40 | expect(foundRule).toBeNull(); 41 | }); 42 | 43 | test('should find all redemption rules', () => { 44 | const rule1 = { rule_id: 'rule_1', description: 'Redeem 100 points for a $10 voucher', points_required: 100, reward: 'Voucher', created_at: new Date() }; 45 | const rule2 = { rule_id: 'rule_2', description: 'Redeem 200 points for a $20 voucher', points_required: 200, reward: 'Voucher', created_at: new Date() }; 46 | repository.save(rule1); 47 | repository.save(rule2); 48 | 49 | const allRules = repository.findAll(); 50 | expect(allRules).toHaveLength(2); 51 | expect(allRules).toEqual(expect.arrayContaining([rule1, rule2])); 52 | }); 53 | 54 | test('should delete a redemption rule by ID', () => { 55 | const rule = { rule_id: 'rule_1', description: 'Redeem 100 points for a $10 voucher', points_required: 100, reward: 'Voucher', created_at: new Date() }; 56 | repository.save(rule); 57 | 58 | repository.delete('rule_1'); 59 | const deletedRule = repository.findById('rule_1'); 60 | expect(deletedRule).toBeNull(); 61 | }); 62 | 63 | test('should throw an error when deleting a non-existent redemption rule', () => { 64 | expect(() => repository.delete('non_existent_id')).toThrow('Redemption rule with ID non_existent_id not found.'); 65 | }); 66 | }); -------------------------------------------------------------------------------- /Assignment11/tests/InMemoryRedemptionRepository.test.mjs: -------------------------------------------------------------------------------- 1 | import { describe, test, expect } from '@jest/globals'; 2 | import InMemoryRedemptionRepository from '../repositories/inMemory/InMemoryRedemptionRepository.mjs'; 3 | 4 | describe('InMemoryRedemptionRepository', () => { 5 | let repository; 6 | 7 | beforeEach(() => { 8 | repository = new InMemoryRedemptionRepository(); 9 | }); 10 | 11 | test('should save a new redemption', () => { 12 | const redemption = { redemption_id: 'redemption_1', student_id: 'student_1', points_redeemed: 30, reward: 'Gift Card', created_at: new Date() }; 13 | repository.save(redemption); 14 | const savedRedemption = repository.findById('redemption_1'); 15 | expect(savedRedemption).toEqual(redemption); 16 | }); 17 | 18 | test('should update an existing redemption', () => { 19 | const redemption = { redemption_id: 'redemption_1', student_id: 'student_1', points_redeemed: 30, reward: 'Gift Card', created_at: new Date() }; 20 | repository.save(redemption); 21 | 22 | const updatedRedemption = { redemption_id: 'redemption_1', student_id: 'student_1', points_redeemed: 50, reward: 'Voucher', created_at: new Date() }; 23 | repository.save(updatedRedemption); 24 | 25 | const foundUpdatedRedemption = repository.findById('redemption_1'); 26 | expect(foundUpdatedRedemption.points_redeemed).toBe(50); 27 | expect(foundUpdatedRedemption.reward).toBe('Voucher'); 28 | }); 29 | 30 | test('should find a redemption by ID', () => { 31 | const redemption = { redemption_id: 'redemption_1', student_id: 'student_1', points_redeemed: 30, reward: 'Gift Card', created_at: new Date() }; 32 | repository.save(redemption); 33 | 34 | const foundRedemption = repository.findById('redemption_1'); 35 | expect(foundRedemption).toEqual(redemption); 36 | }); 37 | 38 | test('should return null for a non-existent redemption', () => { 39 | const foundRedemption = repository.findById('non_existent_id'); 40 | expect(foundRedemption).toBeNull(); 41 | }); 42 | 43 | test('should find all redemptions', () => { 44 | const redemption1 = { redemption_id: 'redemption_1', student_id: 'student_1', points_redeemed: 30, reward: 'Gift Card', created_at: new Date() }; 45 | const redemption2 = { redemption_id: 'redemption_2', student_id: 'student_2', points_redeemed: 50, reward: 'Voucher', created_at: new Date() }; 46 | repository.save(redemption1); 47 | repository.save(redemption2); 48 | 49 | const allRedemptions = repository.findAll(); 50 | expect(allRedemptions).toHaveLength(2); 51 | expect(allRedemptions).toEqual(expect.arrayContaining([redemption1, redemption2])); 52 | }); 53 | 54 | test('should delete a redemption by ID', () => { 55 | const redemption = { redemption_id: 'redemption_1', student_id: 'student_1', points_redeemed: 30, reward: 'Gift Card', created_at: new Date() }; 56 | repository.save(redemption); 57 | 58 | repository.delete('redemption_1'); 59 | const deletedRedemption = repository.findById('redemption_1'); 60 | expect(deletedRedemption).toBeNull(); 61 | }); 62 | 63 | test('should throw an error when deleting a non-existent redemption', () => { 64 | expect(() => repository.delete('non_existent_id')).toThrow('Redemption with ID non_existent_id not found.'); 65 | }); 66 | }); -------------------------------------------------------------------------------- /Assignment10/creational_patterns/notification-patterns.mjs: -------------------------------------------------------------------------------- 1 | import Notification from '../src/notification.js'; 2 | 3 | // SimpleFactory 4 | export default class SimpleNotificationFactory { 5 | static createNotification(notification_id, user_id, message, type, is_read = false, created_at = new Date()) { 6 | return new Notification(notification_id, user_id, message, type, is_read, created_at); 7 | } 8 | } 9 | 10 | // FactoryMethod 11 | class NotificationFactory { 12 | createNotification(notification_id, user_id, message, type, is_read = false, created_at = new Date()) { 13 | throw new Error('This method should be overridden.'); 14 | } 15 | } 16 | 17 | export class AlertNotificationFactory extends NotificationFactory { 18 | createNotification(notification_id, user_id, message, type = 'alert', is_read = false, created_at = new Date()) { 19 | return new Notification(notification_id, user_id, `[ALERT] ${message}`, type, is_read, created_at); 20 | } 21 | } 22 | 23 | // AbstractFactory 24 | export class NotificationAbstractFactory { 25 | static createFactory(type) { 26 | switch (type) { 27 | case 'alert': 28 | return new AlertNotificationFactory(); 29 | default: 30 | throw new Error('No factory found for this type.'); 31 | } 32 | } 33 | } 34 | 35 | // Builder 36 | export class NotificationBuilder { 37 | constructor() { 38 | this._notification = {}; 39 | } 40 | 41 | setNotificationId(id) { 42 | this._notification.notification_id = id; 43 | return this; 44 | } 45 | 46 | setUserId(userId) { 47 | this._notification.user_id = userId; 48 | return this; 49 | } 50 | 51 | setMessage(msg) { 52 | this._notification.message = msg; 53 | return this; 54 | } 55 | 56 | setType(type) { 57 | this._notification.type = type; 58 | return this; 59 | } 60 | 61 | setIsRead(status) { 62 | this._notification.is_read = status; 63 | return this; 64 | } 65 | 66 | setCreatedAt(date) { 67 | this._notification.created_at = date; 68 | return this; 69 | } 70 | 71 | build() { 72 | return new Notification( 73 | this._notification.notification_id, 74 | this._notification.user_id, 75 | this._notification.message, 76 | this._notification.type, 77 | this._notification.is_read, 78 | this._notification.created_at 79 | ); 80 | } 81 | } 82 | 83 | // Prototype 84 | export class NotificationPrototype { 85 | constructor(notification) { 86 | this.notification = notification; 87 | } 88 | 89 | clone() { 90 | return Object.assign( 91 | Object.create(Object.getPrototypeOf(this.notification)), 92 | this.notification 93 | ); 94 | } 95 | } 96 | 97 | // Singleton 98 | export class NotificationSingleton { 99 | constructor() { 100 | if (NotificationSingleton.instance) { 101 | return NotificationSingleton.instance; 102 | } 103 | this.notifications = []; 104 | NotificationSingleton.instance = this; 105 | } 106 | 107 | addNotification(notification) { 108 | this.notifications.push(notification); 109 | } 110 | 111 | getNotifications() { 112 | return this.notifications; 113 | } 114 | } -------------------------------------------------------------------------------- /Assignment11/tests/InMemoryNotificationRepository.test.mjs: -------------------------------------------------------------------------------- 1 | import { describe, test, expect } from '@jest/globals'; 2 | import InMemoryNotificationRepository from '../repositories/inMemory/InMemoryNotificationRepository.mjs'; 3 | 4 | describe('InMemoryNotificationRepository', () => { 5 | let repository; 6 | 7 | beforeEach(() => { 8 | repository = new InMemoryNotificationRepository(); 9 | }); 10 | 11 | test('should save a new notification', () => { 12 | const notification = { notification_id: 'notif_1', user_id: 'user_1', message: 'Test message', type: 'info', is_read: false, created_at: new Date() }; 13 | repository.save(notification); 14 | const savedNotification = repository.findById('notif_1'); 15 | expect(savedNotification).toEqual(notification); 16 | }); 17 | 18 | test('should update an existing notification', () => { 19 | const notification = { notification_id: 'notif_1', user_id: 'user_1', message: 'Test message', type: 'info', is_read: false, created_at: new Date() }; 20 | repository.save(notification); 21 | 22 | const updatedNotification = { notification_id: 'notif_1', user_id: 'user_1', message: 'Updated message', type: 'warning', is_read: true, created_at: new Date() }; 23 | repository.save(updatedNotification); 24 | 25 | const foundUpdatedNotification = repository.findById('notif_1'); 26 | expect(foundUpdatedNotification.message).toBe('Updated message'); 27 | expect(foundUpdatedNotification.type).toBe('warning'); 28 | }); 29 | 30 | test('should find a notification by ID', () => { 31 | const notification = { notification_id: 'notif_1', user_id: 'user_1', message: 'Test message', type: 'info', is_read: false, created_at: new Date() }; 32 | repository.save(notification); 33 | 34 | const foundNotification = repository.findById('notif_1'); 35 | expect(foundNotification).toEqual(notification); 36 | }); 37 | 38 | test('should return null for a non-existent notification', () => { 39 | const foundNotification = repository.findById('non_existent_id'); 40 | expect(foundNotification).toBeNull(); 41 | }); 42 | 43 | test('should find all notifications', () => { 44 | const notif1 = { notification_id: 'notif_1', user_id: 'user_1', message: 'Test message 1', type: 'info', is_read: false, created_at: new Date() }; 45 | const notif2 = { notification_id: 'notif_2', user_id: 'user_2', message: 'Test message 2', type: 'alert', is_read: true, created_at: new Date() }; 46 | repository.save(notif1); 47 | repository.save(notif2); 48 | 49 | const allNotifications = repository.findAll(); 50 | expect(allNotifications).toHaveLength(2); 51 | expect(allNotifications).toEqual(expect.arrayContaining([notif1, notif2])); 52 | }); 53 | 54 | test('should delete a notification by ID', () => { 55 | const notification = { notification_id: 'notif_1', user_id: 'user_1', message: 'Test message', type: 'info', is_read: false, created_at: new Date() }; 56 | repository.save(notification); 57 | 58 | repository.delete('notif_1'); 59 | const deletedNotification = repository.findById('notif_1'); 60 | expect(deletedNotification).toBeNull(); 61 | }); 62 | 63 | test('should throw an error when deleting a non-existent notification', () => { 64 | expect(() => repository.delete('non_existent_id')).toThrow('Notification with ID non_existent_id not found.'); 65 | }); 66 | }); -------------------------------------------------------------------------------- /Assignment11/tests/InMemoryDisputeRepository.test.mjs: -------------------------------------------------------------------------------- 1 | import { describe, test, expect } from '@jest/globals'; 2 | import InMemoryDisputeRepository from '../repositories/inMemory/InMemoryDisputeRepository.mjs'; 3 | 4 | describe('InMemoryDisputeRepository', () => { 5 | let repository; 6 | 7 | beforeEach(() => { 8 | repository = new InMemoryDisputeRepository(); 9 | }); 10 | 11 | test('should save a new dispute', () => { 12 | const dispute = { dispute_id: 'dispute_1', student_id: 'student_1', description: 'Incorrect grade assigned', status: 'pending', created_at: new Date(), resolved_at: null }; 13 | repository.save(dispute); 14 | const savedDispute = repository.findById('dispute_1'); 15 | expect(savedDispute).toEqual(dispute); 16 | }); 17 | 18 | test('should update an existing dispute', () => { 19 | const dispute = { dispute_id: 'dispute_1', student_id: 'student_1', description: 'Incorrect grade assigned', status: 'pending', created_at: new Date(), resolved_at: null }; 20 | repository.save(dispute); 21 | 22 | const updatedDispute = { dispute_id: 'dispute_1', student_id: 'student_1', description: 'Grade corrected', status: 'resolved', created_at: new Date(), resolved_at: new Date() }; 23 | repository.save(updatedDispute); 24 | 25 | const foundUpdatedDispute = repository.findById('dispute_1'); 26 | expect(foundUpdatedDispute.status).toBe('resolved'); 27 | expect(foundUpdatedDispute.description).toBe('Grade corrected'); 28 | }); 29 | 30 | test('should find a dispute by ID', () => { 31 | const dispute = { dispute_id: 'dispute_1', student_id: 'student_1', description: 'Incorrect grade assigned', status: 'pending', created_at: new Date(), resolved_at: null }; 32 | repository.save(dispute); 33 | 34 | const foundDispute = repository.findById('dispute_1'); 35 | expect(foundDispute).toEqual(dispute); 36 | }); 37 | 38 | test('should return null for a non-existent dispute', () => { 39 | const foundDispute = repository.findById('non_existent_id'); 40 | expect(foundDispute).toBeNull(); 41 | }); 42 | 43 | test('should find all disputes', () => { 44 | const dispute1 = { dispute_id: 'dispute_1', student_id: 'student_1', description: 'Incorrect grade assigned', status: 'pending', created_at: new Date(), resolved_at: null }; 45 | const dispute2 = { dispute_id: 'dispute_2', student_id: 'student_2', description: 'Points not updated', status: 'pending', created_at: new Date(), resolved_at: null }; 46 | repository.save(dispute1); 47 | repository.save(dispute2); 48 | 49 | const allDisputes = repository.findAll(); 50 | expect(allDisputes).toHaveLength(2); 51 | expect(allDisputes).toEqual(expect.arrayContaining([dispute1, dispute2])); 52 | }); 53 | 54 | test('should delete a dispute by ID', () => { 55 | const dispute = { dispute_id: 'dispute_1', student_id: 'student_1', description: 'Incorrect grade assigned', status: 'pending', created_at: new Date(), resolved_at: null }; 56 | repository.save(dispute); 57 | 58 | repository.delete('dispute_1'); 59 | const deletedDispute = repository.findById('dispute_1'); 60 | expect(deletedDispute).toBeNull(); 61 | }); 62 | 63 | test('should throw an error when deleting a non-existent dispute', () => { 64 | expect(() => repository.delete('non_existent_id')).toThrow('Dispute with ID non_existent_id not found.'); 65 | }); 66 | }); -------------------------------------------------------------------------------- /Assignment5/Use Case Diagrams/writtenExplanation.md: -------------------------------------------------------------------------------- 1 | **Written Explanation** 2 | 3 | | Key Actors | Use Cases | Explanation | 4 | |-------------|-----------|-------------| 5 | | **Students**| Register, Login, View Dashboard, Redeem Points, Submit Disputes | The *Student* a primary actor that initiates the Redeem Points use case, which has an inclusion relationship with the Process Peyment use case initiated by the *Payment Provider*. The *Student can also inititate the Submit Disputes use case that has an extended relationship with the Review Disputes use case inititated by the *Educators*. This supports the *Students* concerns about accurate point calculation and fast reward redemptions.| 6 | | **Educators**| Login, View Educator Dashboard, Upload Grades, Sync Grades, Manage Points, Review Disputes | The *Educators* a primary actor initiates the Sync Grades use case which has an inclusion relationship with the Provide Grade Data use case, it also has an extended relationship with the Troubleshoot System Errors use case. These support the *Educators* concerns with the heavy workload of grading and with quick and accurate resolutions of disputes.| 7 | | **Administrators**| Manage Users, Configure RBAC, Set Redemption Rates, Audit Logs, Handle System Configuration | The *Administators* actor initiates the Audit Logs use case, it has an inclusion relationship with Review Compliance Reports that's initiated by Government & Educational Regulators. This supports the *Administrators* concerns with secure systems and complying with regulations.| 8 | | **Educational Institutions**| Provide Grade Data, Authorise Integration, Validate Compliance | The *Educational Institutions* initiate the Provide Data use case. It also initiates the Authorise Integration which has an inclusive relationship with the Handle System Configuration use case initiated by the *Administrator* actor. It also initiates the Validate Compliance use case which has an inclusion relationship with the Review Compliance Reports use case initiated by the *Government & Educational Regulators* actor. This supports the *Educational Institutions* concerns with upholding a ethical reward system and ensuring data privacy. | 9 | | **Payment Providers**| Process Payment | The *Payment Providers* actor initiates the Process Payment use case that has both an inclusion relationship with Redeem Points use case initiated by the *Students* it also has an extended relationship with the Troubleshoot System Errors use case initiated by the *Technical Support & Developers* actor. This supports the *Payment Providers* concerns with preventing fraudulent activites and ensuring reliable transactions. | 10 | | **Government & Education Regulators**| Review Compliance Reports, Enforce Policy Changes | The *Government & Education Regulators* actor initiates the Review Compliance Report use case that has an inclusion relationship with Audit Logs initiated by the *Administrator* actor and the same relationship with the Validate Compliance use case initiated by the Educational Institution* actor. This supports the *Government & Education Regulators* concerns with legal compliance a implementing a fair reward allocation. | 11 | | **Technical Support & Developers**| Perform System Maintenance, Troubleshoot System Errors, Monitor System Health, Implement Security Updates | The *Technical Support & Developers* actor initiates the Troubleshoot System Errors use case which has inclusion relationships with Sync Grades and Process Payment both initiated by the *Educator* actor and *Payment Providers* actors respectively. This supports the *Technical Support & Developers* concerns with system uptime and the scalability of the system. | 12 | | -------------------------------------------------------------------------------- /Assignment14/Reflection.md: -------------------------------------------------------------------------------- 1 | **Reflection** 2 | This assignment has been a meaningful learning experience, particularly in understanding the real-world challenges and considerations involved in building, managing, and maintaining an open-source software repository. Through the various tasks ranging from code design, CI/CD integration, REST API development, to community contribution and collaboration. I have gained deeper insight into both the technical and social dynamics of software engineering. 3 | 4 | 1. Improvements Based on Peer Feedback and Assignment Specifications 5 | One of the most significant improvements I need to make to the repository comes after receiving peer feedback regarding the project’s structure and testing strategy. Initially, the test scripts are scattered and not well organized, and some of the key entities lacks proper service or integration tests. Based on this input, I will reorganize the project directory to clearly separate entities, repositories, services, api, and unit_tests. I will also ensure each entity has a corresponding service class and in-memory repository with a set of unit tests, improving maintainability and scalability. 6 | 7 | Additionally, I implemented clearer naming conventions and refactored redundant logic to align with best practices. 8 | 9 | The assignment specifications also prompted me to add several critical improvements. For example, I updated the README.md to include a comprehensive “Getting Started” section, ensuring that any new developer or contributor could clone the repository, install dependencies, and run tests or the application without confusion. Furthermore, I created a CONTRIBUTING.md file to outline coding standards, test expectations, and contribution workflows. This not only improved the professionalism of the repository but also made it more inviting to potential contributors. 10 | 11 | CI/CD automation was another area of improvement. I implemented a GitHub Actions workflow that runs unit tests automatically on every push or pull request. This built trust in the codebase and ensured early detection of potential regressions. 12 | 13 | 2. Challenges in Onboarding Contributors 14 | One of the main challenges I faced was anticipating what contributors would struggle with when joining the project. Without adequate documentation or onboarding support, it’s easy for a new contributor to get lost especially in a project with abstract concepts like repositories and service layers. 15 | 16 | Another issue was aligning on coding style. Despite using tools like Prettier or ESLint, differences in formatting and structure occasionally led to inconsistencies. I realized that pre-commit hooks and strict CI checks can help enforce consistency but must be clearly documented and integrated early. 17 | 18 | Setting up meaningful issues was also tricky. To ease onboarding, I will begin tagging issues with labels like good-first-issue and feature-request, to offer clear descriptions and expected outcomes. This will help break down work into manageable chunks and improve the overall contributor experience. 19 | 20 | 3. Lessons Learned About Open-Source Collaboration 21 | This experience taught me that open-source collaboration goes far beyond just writing functional code. It’s about fostering a shared understanding, reducing barriers to entry, and designing systems that welcome participation. 22 | 23 | I learned that automation builds trust and reliability, while structure and documentation create an inclusive and scalable project. Most importantly, I learned that communication through issues, pull requests, and documentation is what transforms code into a collaborative effort. By combining these principles, I was able to create a more robust, accessible, and community-friendly project. -------------------------------------------------------------------------------- /Assignment9/ClassDiagram.md: -------------------------------------------------------------------------------- 1 | ```mermaid 2 | classDiagram 3 | class User { 4 | +String user_id 5 | +String name 6 | +String email 7 | +String password_hash 8 | +String role 9 | +Boolean is_verified 10 | +DateTime created_at 11 | +register() 12 | +login() 13 | +verify_email() 14 | +update_profile() 15 | +change_password() 16 | } 17 | 18 | class Student { 19 | +String student_id 20 | +String user_id 21 | +Float total_points 22 | +Float redeemed_points 23 | +Float grade_average 24 | +redeem_points() 25 | +view_dashboard() 26 | +raise_dispute() 27 | } 28 | 29 | class Educator { 30 | +String educator_id 31 | +String user_id 32 | +String department 33 | +List~String~ assigned_courses 34 | +upload_grades() 35 | +resolve_dispute() 36 | +view_student_progress() 37 | } 38 | 39 | class Admin { 40 | +String admin_id 41 | +String user_id 42 | +List~String~ permissions 43 | +assign_roles() 44 | +set_redemption_rate() 45 | +manage_users() 46 | +generate_reports() 47 | } 48 | 49 | class Grade { 50 | +String grade_id 51 | +String student_id 52 | +String course_name 53 | +Float score 54 | +String uploaded_by 55 | +DateTime uploaded_at 56 | +convert_to_points() 57 | +flag_for_dispute() 58 | } 59 | 60 | class Point { 61 | +String point_id 62 | +String student_id 63 | +String grade_id 64 | +Float value 65 | +DateTime awarded_at 66 | +calculate_value() 67 | +expire_point() 68 | } 69 | 70 | class Redemption { 71 | +String redemption_id 72 | +String student_id 73 | +Float points_redeemed 74 | +Float monetary_value 75 | +DateTime redeemed_at 76 | +process_redemption() 77 | +generate_receipt() 78 | } 79 | 80 | class Dispute { 81 | +String dispute_id 82 | +String student_id 83 | +String grade_id 84 | +String reason 85 | +String status 86 | +DateTime submitted_at 87 | +submit_dispute() 88 | +resolve_dispute() 89 | } 90 | 91 | class Notification { 92 | +String notification_id 93 | +String user_id 94 | +String message 95 | +String type 96 | +Boolean is_read 97 | +DateTime created_at 98 | +send_notification() 99 | +mark_as_read() 100 | +unsubscribe() 101 | } 102 | 103 | class RedemptionRule { 104 | +String rule_id 105 | +Float conversion_rate 106 | +String updated_by 107 | +DateTime effective_from 108 | +DateTime created_at 109 | +set_rate() 110 | +get_current_rate() 111 | } 112 | 113 | Student --> User : inherits > 114 | Educator --> User : inherits > 115 | Admin --> User : inherits > 116 | 117 | Student "1" --> "0..*" Grade : has > 118 | Educator "1" --> "0..*" Grade : uploads > 119 | Grade "1" --> "1" Student : belongs to > 120 | 121 | Grade "1" --> "0..1" Dispute : may have > 122 | Student "1" --> "0..*" Dispute : raises > 123 | Educator "1" --> "0..*" Dispute : resolves > 124 | 125 | Grade "1" --> "1" Point : converted to > 126 | Student "1" --> "0..*" Point : earns > 127 | 128 | Student "1" --> "0..*" Redemption : makes > 129 | Point "0..*" --> "1" Redemption : used in > 130 | 131 | Admin "1" --> "0..*" RedemptionRule : defines > 132 | 133 | User "1" --> "0..*" Notification : receives > 134 | 135 | note for Student "Inherits from User.\nCan earn points and redeem rewards." 136 | note for Grade "Uploaded by Educator; used to compute Points." 137 | note for RedemptionRule "Used to calculate value during point redemption." 138 | 139 | ``` -------------------------------------------------------------------------------- /Assignment10/creational_patterns/user-patterns.mjs: -------------------------------------------------------------------------------- 1 | import User from '../src/user.mjs'; 2 | 3 | // SimpleFactory 4 | export class SimpleUserFactory { 5 | static createUser(user_id, name, email, password_hash, role = 'user') { 6 | return new User(user_id, name, email, password_hash, role); 7 | } 8 | } 9 | 10 | // FactoryMethod 11 | class UserFactory { 12 | createUser(user_id, name, email, password_hash) { 13 | throw new Error("This method should be overridden."); 14 | } 15 | } 16 | 17 | export class StudentUserFactory extends UserFactory { 18 | createUser(user_id, name, email, password_hash) { 19 | return new User(user_id, name, email, password_hash, 'student'); 20 | } 21 | } 22 | 23 | export class EducatorUserFactory extends UserFactory { 24 | createUser(user_id, name, email, password_hash) { 25 | return new User(user_id, name, email, password_hash, 'educator'); 26 | } 27 | } 28 | 29 | export class AdminUserFactory extends UserFactory { 30 | createUser(user_id, name, email, password_hash) { 31 | return new User(user_id, name, email, password_hash, 'admin'); 32 | } 33 | } 34 | 35 | // AbstractFactory 36 | class AbstractUserFactory { 37 | createUser() { 38 | throw new Error("createUser method must be implemented"); 39 | } 40 | } 41 | 42 | export class VerifiedUserFactory extends AbstractUserFactory { 43 | createUser(user_id, name, email, password_hash) { 44 | const user = new User(user_id, name, email, password_hash); 45 | user.verifyEmail(); 46 | return user; 47 | } 48 | } 49 | 50 | export class GuestUserFactory extends AbstractUserFactory { 51 | createUser(user_id, name, email, password_hash) { 52 | return new User(user_id, name, email, password_hash, 'guest'); 53 | } 54 | } 55 | 56 | // Builder 57 | export class UserBuilder { 58 | constructor() { 59 | this._user = {}; 60 | } 61 | 62 | setId(id) { 63 | this._user.user_id = id; 64 | return this; 65 | } 66 | 67 | setName(name) { 68 | this._user.name = name; 69 | return this; 70 | } 71 | 72 | setEmail(email) { 73 | this._user.email = email; 74 | return this; 75 | } 76 | 77 | setPasswordHash(hash) { 78 | this._user.password_hash = hash; 79 | return this; 80 | } 81 | 82 | setRole(role) { 83 | this._user.role = role; 84 | return this; 85 | } 86 | 87 | build() { 88 | return new User( 89 | this._user.user_id, 90 | this._user.name, 91 | this._user.email, 92 | this._user.password_hash, 93 | this._user.role || 'user' 94 | ); 95 | } 96 | } 97 | 98 | // Prototype 99 | export class UserPrototype { 100 | constructor(user) { 101 | this.user = user; 102 | } 103 | 104 | clone() { 105 | return Object.assign( 106 | Object.create(Object.getPrototypeOf(this.user)), 107 | this.user 108 | ); 109 | } 110 | } 111 | 112 | // Singleton 113 | export default class UserSingleton { 114 | constructor() { 115 | if (UserSingleton.instance) { 116 | return UserSingleton.instance; 117 | } 118 | this.users = []; 119 | UserSingleton.instance = this; 120 | } 121 | 122 | addUser(user) { 123 | this.users.push(user); 124 | } 125 | 126 | getUsers() { 127 | return this.users; 128 | } 129 | } 130 | 131 | // Usage examples 132 | // const singleton = new UserSingleton(); 133 | // const user1 = SimpleUserFactory.createUser("1", "Abdul", "abdul@email.com", "hashed_pw1"); 134 | // singleton.addUser(user1); 135 | 136 | // const builder = new UserBuilder(); 137 | // const user2 = builder.setId("2").setName("Ruth").setEmail("ruth@email.com").setPasswordHash("hashed_pw2").build(); 138 | // singleton.addUser(user2); 139 | 140 | // const adminFactory = new AdminUserFactory(); 141 | // const admin = adminFactory.createUser("3", "AdminUser", "admin@email.com", "admin_pw"); 142 | // singleton.addUser(admin); 143 | 144 | // const prototype = new UserPrototype(user1); 145 | // const userClone = prototype.clone(); 146 | // userClone._name = "ClonedAbdul"; 147 | // singleton.addUser(userClone); 148 | 149 | // console.log(singleton.getUsers()); -------------------------------------------------------------------------------- /Assignment10/creational_patterns/student-patterns.mjs: -------------------------------------------------------------------------------- 1 | import Student from '../src/student.mjs'; 2 | 3 | // SimpleFactory 4 | export class SimpleStudentFactory { 5 | static createStudent(student_id, user_id, total_points = 0, redeemed_points = 0, grade_average = 0.0) { 6 | return new Student(student_id, user_id, total_points, redeemed_points, grade_average); 7 | } 8 | } 9 | 10 | // FactoryMethod 11 | export class FactoryStudent { 12 | constructor(student_id, user_id, total_points = 0, redeemed_points = 0, grade_average = 0.0) { 13 | this._student = new Student(student_id, user_id, total_points, redeemed_points, grade_average); 14 | } 15 | 16 | get student() { 17 | return this._student; 18 | } 19 | } 20 | 21 | // AbstractFactory 22 | export class AbstractFactory { 23 | createStudentFactory() { 24 | throw new Error("Method not implemented."); 25 | } 26 | } 27 | 28 | export class ConcreteFactory extends AbstractFactory { 29 | createStudentFactory() { 30 | return new RegularStudentFactory(); 31 | } 32 | } 33 | 34 | // Builder 35 | export class StudentBuilder { 36 | constructor() { 37 | this._student = {}; 38 | } 39 | 40 | setStudentId(id) { 41 | this._student.student_id = id; 42 | return this; 43 | } 44 | 45 | setUserId(userId) { 46 | this._student.user_id = userId; 47 | return this; 48 | } 49 | 50 | setTotalPoints(points) { 51 | this._student.total_points = points; 52 | return this; 53 | } 54 | 55 | setRedeemedPoints(points) { 56 | this._student.redeemed_points = points; 57 | return this; 58 | } 59 | 60 | setGradeAverage(avg) { 61 | this._student.grade_average = avg; 62 | return this; 63 | } 64 | 65 | build() { 66 | return new Student( 67 | this._student.student_id, 68 | this._student.user_id, 69 | this._student.total_points, 70 | this._student.redeemed_points, 71 | this._student.grade_average 72 | ); 73 | } 74 | } 75 | 76 | // Prototype 77 | export class StudentPrototype { 78 | constructor(student) { 79 | this.student = student; 80 | } 81 | 82 | clone() { 83 | return Object.assign( 84 | Object.create(Object.getPrototypeOf(this.student)), 85 | this.student 86 | ); 87 | } 88 | } 89 | 90 | // Singleton 91 | export class StudentSingleton { 92 | constructor() { 93 | if (StudentSingleton.instance) { 94 | return StudentSingleton.instance; 95 | } 96 | this.students = []; 97 | StudentSingleton.instance = this; 98 | } 99 | 100 | addStudent(student) { 101 | this.students.push(student); 102 | } 103 | 104 | getStudents() { 105 | return this.students; 106 | } 107 | } 108 | 109 | // // Usage examples 110 | // const studentSingleton = new StudentSingleton(); 111 | // const student1 = SimpleStudentFactory.createStudent("stu1", "1", 100, 20, 75.5); 112 | // studentSingleton.addStudent(student1); 113 | 114 | // const studentBuilder = new StudentBuilder(); 115 | // const student2 = studentBuilder.setStudentId("stu2").setUserId("2").setTotalPoints(80).setRedeemedPoints(10).setGradeAverage(82.3).build(); 116 | // studentSingleton.addStudent(student2); 117 | 118 | // const studentPrototype = new StudentPrototype(student1); 119 | // const studentClone = studentPrototype.clone(); 120 | // studentClone._student_id = "stuClone"; 121 | // studentSingleton.addStudent(studentClone); 122 | 123 | // const studentFactoryMethod = new RegularStudentFactory(); 124 | // const student3 = studentFactoryMethod.createStudent("stu3", "3", 90, 15, 88.0); 125 | // studentSingleton.addStudent(student3); 126 | 127 | // const abstractFactory = new ConcreteFactory(); 128 | // const userFactoryFromAbstract = abstractFactory.createUserFactory(); 129 | // const studentFactoryFromAbstract = abstractFactory.createStudentFactory(); 130 | 131 | // const admin2 = userFactoryFromAbstract.createUser("4", "FactoryAdmin", "facadmin@email.com", "pw4"); 132 | // const student4 = studentFactoryFromAbstract.createStudent("stu4", "4", 95, 5, 91.2); 133 | 134 | // singleton.addUser(admin2); 135 | // studentSingleton.addStudent(student4); 136 | 137 | // console.log(studentSingleton.getStudents()); 138 | -------------------------------------------------------------------------------- /Assignment5/Use Case Diagrams/testCasesTable.md: -------------------------------------------------------------------------------- 1 | **Test Cases** 2 | 3 | **Functional Requirements** 4 | 5 | | Test Case ID | Requirement ID | Description | Steps | Expected Result | Actual Result | Status (Pass/Fail) | 6 | |---------------|----------------|-------------|--------|-----------------|---------------|--------------------| 7 | | TC-001 | FR-001 | User Registration via Email/Password | 1. The user navigates to the registration page. 2. They enter a valid email address and password. 3. Submit. | The account is created; a confirmation is email sent. Passwords are stored as encrypted hashes in the database. | The account is created; a confirmation is email received. Password is stored as SHA-256 hash in the database. | Pass | 8 | |TC-002 | FR-001 | Admin Login with 2FA | 1. Admin enters their credentials. 2. They enter the 2FA code. 3. Submit. |Admin then gains access to the dashboard. Their login is blocked if 2FA code is incorrect. | Their login was a success with a valid 2FA code. The login failed with an incorrect code. | Pass | 9 | | TC-003 | FR-002 |Manual Grade Upload by Educator |1. The educator uploads a CSV file with the students grades. 2. Submit. 3. The system will validate the data. | The grades are then converted to points. The students will see the updated points on their dashboards. | The grades are uploaded; the points are calculated. The students see the updates within a 1 minute. | Pass | 10 | | TC-004 | FR-002 | Grade Sync from Educational Institution | 1. The educator will trigger a grade sync function. 2. The system then fetches the grades via an API. 3. Validate the data's integrity. | The grades are then imported; and the students are notified. A sync failure trigger alerts to the educators. | The grades are synced successfully. Students do receive the notifications. | Pass | 11 | | TC-005 | FR-003 | Student Redeems Points | 1. The student selects the "Redeem Points" button. 2. They enter the amount. 3. Confirm. | Their points are deducted; the payment is processed via a gateway. The receipt is sent to the student. | The points are deducted; the payment is processed. A receipt is emailed instantly to the student. | Pass | 12 | | TC-006 | FR-003 | Insufficient Points for Redemption | 1. A student with 50 points tries to redeem 100 points. 2. Confirm. | The redemption is blocked. The error message reads: "Insufficient points." | The error message displayed is: "Insufficient points." | Pass | 13 | | TC-007 | FR-004 | Payment Processing via Gateway |1. Redeeming points. 2. Mock a payment provider response. 3. Validate transaction. | Payment is confirmed. The transaction is logged with a timestamp, an amount, and the providers details. | The error message is displayed as: "Insufficient points." | Pass | 14 | | TC-008 | FR-005 | Student Dashboard Load Time | 1. A student logs in. 2. A measure of the dashboard load time is taken. | The dashboard is fully loaded in <2 seconds. It displays the students grades, their points, and the transaction history. | The error message displayed is: "Insufficient points." | Pass | 15 | | TC-009 | FR-006 | Grade Update Notification | 1. Educator uploads grades. 2. Check student notifications. | Student receives email/SMS/in-app alert within 1 minute. | An in-app notification is received in 45 seconds. The email is delayed by 3 minutes. | Fail | 16 | | TC-010 | FR-007 | Student Submits Dispute | 1. The student files a dispute via a form. 2. The educator reviews the dispute.3. Approve/deny. | The dispute is logged. The student is notified of the resolution. An audit trail is created. | A dispute is logged. The student is notified via an email. The audit trail is missing a timestamp. | Fail | 17 | | TC-011 | FR-008 | Data Encryption Check | 1. Inspection of the database storage. 2. Monitoring the network traffic during the login process. | The sensitive data such as, passwords, and grades are encrypted with AES-256 at rest, and TLS 1.3 in transit. | The passwords are encrypted with AES-256. The network traffic uses TLS 1.3. | Pass | 18 | | TC-012 | FR-010 | Concurrent User Load Test | 1. Simulate 5,000 users accessing their dashboards. 2. Monitor the response time. | All the dashboards load in <2 seconds. No server crashes or timeouts occur. | 4,950/5,000 dashboards loaded in <2s. 50 users timed out due to server overload. | Fail | 19 | | 20 | 21 | **Non-Functional Requirements** 22 | 23 | *Performance*: Validate the system’s ability to handle 5,000 users accessing dashboards at the same time with a response time of less than or equal to 2 seconds. 24 | 25 | *Security*: Ensure the sensitive data is encrypted and in accordance with compliance regulations. -------------------------------------------------------------------------------- /Assignment6/UserStoryCreation.md: -------------------------------------------------------------------------------- 1 | **User Story Creation Table** 2 | 3 | | Story ID | User Story | Acceptance Criteria| Priority(High/Medium/Low) | 4 | |---------------|----------------|-------------|--------| 5 | | US-001 | As a student, I want to be able to register and then log into the system securely using an email/password or Single Sign-On in order to access my academic records safely. | The user can register using an email/password or OAuth; their passwords are encrypted; the login attempts will be limited. | High | 6 | | US-002 | As a student, I want my grades to be processed and converted into points so that I can keep track of my academic performance and inturn earn incentives. | The Grade-to-point conversion follows a set of predefined rules; the points are displayed automatically once the grades are uploaded by the educator. | High | 7 | | US-003 | As a student, I want to be able redeem the points I earned that has been converted into monetary rewards so that I can benefit financially from my academic achievements. | The redemption process is carried out via a payment gateway; the students will then receive a confirmation message once the process is complete; the transaction logs will then update. | High | 8 | | US-004 | As a student, I want to be able to view my academic progress, the total points I earned, and my transaction history in a dashboard so that I can track everything in one place. | The dashboard will load in less than 2 seconds; it will then display the grades available, the points available, the pending rewards, and the transaction history for each student. | High | 9 | | US-005 | As a student, I want to get notifications of when the educator uploads my grades or when I earn/redeem my points so that I can stay updated on my progress. | The notifications are sent via email/SMS/on the app; the students can opt out of receiving specific notifications. | Medium | 10 | | US-006 | As a student, I want to be able to submit a dispute if I suspect there is something wrong with my grade or with the calculating of points, so that I can ensure fair grading. | Disputes will be submitted through a form; the students then receive confirmation; and a log tracks the disputes and resolutions. | Medium | 11 | | US-007 | As an educator, I want to upload student grades manually so I can manage the student's grades correctly. | Educators will upload the student's grades through form; the grades will automatically get synced; any errors will trigger alerts shown on the form. | High | 12 | | US-008 | As an educator, I want to navigate a dashboard that allows me to upload the student's grades and track their academic performance in order to manage the grading as efficiently as possible. | The dashboard will allow the grades to be uploaded, and displays the student progress, it also manages the distribution of points. | High | 13 | | US-009 | As an educator, I want to get notifications of any pending grading tasks or disputes that I can resolve as soon as possible. | The educators receive alerts for pending grades and disputes; these notifications include the students details and a timestamps. | Medium | 14 | | US-010 | As an educator, I want to view and resolve the student disputes on a panel dedicated to disputes I can efficiently address any grading concerns. | The educators will be able to view, and approve, or deny disputes; all actions are logged for auditing. | Medium | 15 | | US-011 | As an educator, I want to enable the two-factor authentication (2FA) to ensure that my account is security protected from any unauthorized access. | The 2FA is only for admins and educators at the moment; any user verifies their identity via an app or a SMS before accessing the sensitive features. | High | 16 | | US-012 | As an admin, I want to assign roles to the students, educators, and admin alike so that users have the appropriate access to the systems functionalities. | Role-based access control (RBAC) is implemented; the users will only have access to the features they are allowed based on their role. | High | 17 | | US-013 | As an admin, I want the system to generate and send transaction receipts after every reward redemption so that students have a record of their transactions. | The receipts will include the date, the redeemed points total, the converted amounts, and the payment provider details; the notifications will be sent via an email or an in-app notification. | Medium | 18 | | US-014 | As an admin, I want to be able to define and change the redemption rates of the points so that I can regulate the reward values based on the institutions policies put in place. | The admins have authority to set and modify the redemption rates via an interface; the changes can only apply to future transactions. | Medium | 19 | | US-015 | As an admin, I want the system to be able to encrypt all the user data so that student and educator information is secure and therefore compliant with regulations. | Data is encrypted at rest and while in transit; ensuring the system complies with GDPR/CCPA regulations. | High | 20 | | US-016 | As an admin, I want thousands of users to access the system at the same time without performance issues so that when peak usage times spike it won't affect the usability. | The system is able to scale dynamically; a load test is performed to confirm support for more than 5,000 users. |High | | --------------------------------------------------------------------------------