diff --git a/.gitignore b/.gitignore index 9a8b0a8..e049ccc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules/ dist/ .tmp +src/Contexts/Mooc/Courses/infrastructure/courses.* diff --git a/package-lock.json b/package-lock.json index 9b4198d..fe5fae9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -538,6 +538,11 @@ "@types/express": "*" } }, + "@types/events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", + "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==" + }, "@types/express": { "version": "4.17.1", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.1.tgz", @@ -563,6 +568,16 @@ "integrity": "sha512-YSDqoBEWYGdNk53xSkkb6REaUaVSlIjxIAGjj/nbLzlZOit7kUU+nA2zC2qQkIVO4MQ+3zl4Sz7aw+kbpHHHUQ==", "dev": true }, + "@types/glob": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.1.tgz", + "integrity": "sha512-1Bh06cbWJUHMC97acuD6UMG29nMt0Aqz1vF3guLfG+kHHJhy3AyohZFFxYk2f7Q1SQIrNwvncxAE0N/9s70F2w==", + "requires": { + "@types/events": "*", + "@types/minimatch": "*", + "@types/node": "*" + } + }, "@types/helmet": { "version": "0.0.44", "resolved": "https://registry.npmjs.org/@types/helmet/-/helmet-0.0.44.tgz", @@ -616,6 +631,11 @@ "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz", "integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==" }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==" + }, "@types/node": { "version": "11.13.20", "resolved": "https://registry.npmjs.org/@types/node/-/node-11.13.20.tgz", @@ -3211,9 +3231,9 @@ "dev": true }, "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", diff --git a/package.json b/package.json index 8e0981d..2010b1b 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@types/convict": "^4.2.1", "@types/errorhandler": "0.0.32", "@types/express": "^4.17.1", + "@types/glob": "^7.1.1", "@types/helmet": "0.0.44", "@types/node": "~11.13.0", "@types/uuid": "^3.4.5", @@ -32,6 +33,7 @@ "copy": "^0.3.2", "errorhandler": "^1.5.1", "express": "^4.17.1", + "glob": "^7.1.6", "helmet": "^3.21.1", "http-status": "^1.3.2", "mandrill-api": "^1.0.45", diff --git a/src/Mooc/Courses/application/CreateCourse.ts b/src/Contexts/Mooc/Courses/application/CourseCreator.ts similarity index 82% rename from src/Mooc/Courses/application/CreateCourse.ts rename to src/Contexts/Mooc/Courses/application/CourseCreator.ts index 6c15d02..65170f3 100644 --- a/src/Mooc/Courses/application/CreateCourse.ts +++ b/src/Contexts/Mooc/Courses/application/CourseCreator.ts @@ -1,7 +1,7 @@ import CourseRepository from '../domain/CourseRepository'; import Course from '../domain/Course'; -export default class CreateCourse { +export default class CourseCreator { private repository: CourseRepository; constructor(repository: CourseRepository) { @@ -11,6 +11,6 @@ export default class CreateCourse { async run(id: string, name: string, duration: string): Promise { const course = new Course(id, name, duration); - this.repository.save(course); + return this.repository.save(course); } } diff --git a/src/Contexts/Mooc/Courses/domain/Course.ts b/src/Contexts/Mooc/Courses/domain/Course.ts new file mode 100644 index 0000000..2111d70 --- /dev/null +++ b/src/Contexts/Mooc/Courses/domain/Course.ts @@ -0,0 +1,11 @@ +export default class Course { + readonly id: string; + readonly name: string; + readonly duration: string; + + constructor(id: string, name: string, duration: string) { + this.id = id; + this.name = name; + this.duration = duration; + } +} diff --git a/src/Contexts/Mooc/Courses/domain/CourseAlreadyExists.ts b/src/Contexts/Mooc/Courses/domain/CourseAlreadyExists.ts new file mode 100644 index 0000000..df773ee --- /dev/null +++ b/src/Contexts/Mooc/Courses/domain/CourseAlreadyExists.ts @@ -0,0 +1,5 @@ +export default class CourseAlreadyExists extends Error { + constructor(courseId: string) { + super(`Course ${courseId} already exists`); + } +} diff --git a/src/Contexts/Mooc/Courses/domain/CourseRepository.ts b/src/Contexts/Mooc/Courses/domain/CourseRepository.ts new file mode 100644 index 0000000..b0d574d --- /dev/null +++ b/src/Contexts/Mooc/Courses/domain/CourseRepository.ts @@ -0,0 +1,8 @@ +import Course from './Course'; +import { Nullable } from '../../../Shared/domain/Nullable'; + +export default interface CourseRepository { + save(course: Course): Promise; + + search(id: string): Promise>; +} diff --git a/src/Contexts/Mooc/Courses/infrastructure/FileCourseRepository.ts b/src/Contexts/Mooc/Courses/infrastructure/FileCourseRepository.ts new file mode 100644 index 0000000..2b86743 --- /dev/null +++ b/src/Contexts/Mooc/Courses/infrastructure/FileCourseRepository.ts @@ -0,0 +1,27 @@ +import CourseRepository from '../domain/CourseRepository'; +import Course from '../domain/Course'; +import fs from 'fs'; +import BSON from 'bson'; +import { Nullable } from '../../../Shared/domain/Nullable'; + +export default class FileCourseRepository implements CourseRepository { + private FILE_PATH = `${__dirname}/courses`; + + async save(course: Course): Promise { + const filePath = this.filePath(course.id); + const data = BSON.serialize(course); + + return fs.writeFileSync(filePath, data); + } + + async search(id: string): Promise> { + const filePath = this.filePath(id); + const exists = fs.existsSync(filePath); + + return exists ? BSON.deserialize(fs.readFileSync(this.filePath(id))) : null; + } + + private filePath(id: string): string { + return `${this.FILE_PATH}.${id}.repo`; + } +} diff --git a/src/Contexts/Shared/domain/Nullable.ts b/src/Contexts/Shared/domain/Nullable.ts new file mode 100644 index 0000000..aa32b2d --- /dev/null +++ b/src/Contexts/Shared/domain/Nullable.ts @@ -0,0 +1 @@ +export type Nullable = T | null; diff --git a/src/Mooc/Courses/domain/Course.ts b/src/Mooc/Courses/domain/Course.ts deleted file mode 100644 index a7a8432..0000000 --- a/src/Mooc/Courses/domain/Course.ts +++ /dev/null @@ -1,23 +0,0 @@ -export default class Course { - private _id: string; - private _name: string; - private _duration: string; - - constructor(id: string, name: string, duration: string) { - this._id = id; - this._name = name; - this._duration = duration; - } - - get id(): string { - return this._id; - } - - get name(): string { - return this._name; - } - - get duration(): string { - return this._duration; - } -} diff --git a/src/Mooc/Courses/domain/CourseRepository.ts b/src/Mooc/Courses/domain/CourseRepository.ts deleted file mode 100644 index 2dbe575..0000000 --- a/src/Mooc/Courses/domain/CourseRepository.ts +++ /dev/null @@ -1,7 +0,0 @@ -import Course from './Course'; - -export default interface CourseRepository { - save(course: Course): Promise | void; - - search(id: string): Promise | Course; -} diff --git a/src/Mooc/Courses/infrastructure/FileCourseRepository.ts b/src/Mooc/Courses/infrastructure/FileCourseRepository.ts deleted file mode 100644 index 75834cb..0000000 --- a/src/Mooc/Courses/infrastructure/FileCourseRepository.ts +++ /dev/null @@ -1,20 +0,0 @@ -import CourseRepository from '../domain/CourseRepository'; -import Course from '../domain/Course'; -import * as fs from 'fs'; -import BSON from 'bson'; - -export default class FileCourseRepository implements CourseRepository { - private FILE_PATH = `${__dirname}/courses`; - - save(course: Course): void | Promise { - fs.writeFileSync(this.filePath(course.id), BSON.serialize(course)); - } - - search(id: string): Course { - return fs.existsSync(this.filePath(id)) ? BSON.deserialize(fs.readFileSync(this.filePath(id))) : null; - } - - private filePath(id: string): string { - return `${this.FILE_PATH}.${id}.repo`; - } -} diff --git a/src/apps/mooc_backend/config/dependency-injection/application.yaml b/src/apps/mooc_backend/config/dependency-injection/application.yaml index c2904c6..f7afcf1 100644 --- a/src/apps/mooc_backend/config/dependency-injection/application.yaml +++ b/src/apps/mooc_backend/config/dependency-injection/application.yaml @@ -1,16 +1,16 @@ services: Mooc.courses.CourseRepository: - class: ../../../../Mooc/Courses/infrastructure/FileCourseRepository + class: ../../../../Contexts/Mooc/Courses/infrastructure/FileCourseRepository arguments: [] - Mooc.courses.CreateCourse: - class: ../../../../Mooc/Courses/application/CreateCourse + Mooc.courses.CourseCreator: + class: ../../../../Contexts/Mooc/Courses/application/CourseCreator arguments: ["@Mooc.courses.CourseRepository"] - Apps.mooc.controllers.CreateCourseController: - class: ../../controllers/CreateCourseController - arguments: ["@Mooc.courses.CreateCourse"] + Apps.mooc.controllers.CoursePutController: + class: ../../controllers/CoursePutController + arguments: ["@Mooc.courses.CourseCreator"] - Apps.mooc.controllers.StatusController: - class: ../../controllers/StatusController + Apps.mooc.controllers.StatusGetController: + class: ../../controllers/StatusGetController arguments: [] diff --git a/src/apps/mooc_backend/controllers/Controller.ts b/src/apps/mooc_backend/controllers/Controller.ts new file mode 100644 index 0000000..4969747 --- /dev/null +++ b/src/apps/mooc_backend/controllers/Controller.ts @@ -0,0 +1,5 @@ +import {Request, Response} from 'express'; + +export default interface Controller { + run(req: Request, res: Response): Promise; +} diff --git a/src/apps/mooc_backend/controllers/CoursePutController.ts b/src/apps/mooc_backend/controllers/CoursePutController.ts new file mode 100644 index 0000000..61e77c5 --- /dev/null +++ b/src/apps/mooc_backend/controllers/CoursePutController.ts @@ -0,0 +1,29 @@ +import { Request, Response } from 'express'; +import CourseCreator from '../../../Contexts/Mooc/Courses/application/CourseCreator'; +import httpStatus from 'http-status'; +import Controller from './Controller'; +import CourseAlreadyExists from '../../../Contexts/Mooc/Courses/domain/CourseAlreadyExists'; + +export default class CoursePutController implements Controller { + constructor(private courseCreator: CourseCreator) {} + + async run(req: Request, res: Response) { + const id: string = req.params.id; + const name: string = req.body.name; + const duration: string = req.body.duration; + + try { + await this.courseCreator.run(id, name, duration); + } catch (e) { + + if (e instanceof CourseAlreadyExists) { + res.status(httpStatus.BAD_REQUEST).send(e.message); + } else { + res.status(httpStatus.INTERNAL_SERVER_ERROR).json(e); + } + + } + + res.status(httpStatus.CREATED).send(); + } +} diff --git a/src/apps/mooc_backend/controllers/CreateCourseController.ts b/src/apps/mooc_backend/controllers/CreateCourseController.ts deleted file mode 100644 index 436c1f0..0000000 --- a/src/apps/mooc_backend/controllers/CreateCourseController.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { Request, Response } from 'express'; -import CreateCourse from '../../../Mooc/Courses/application/CreateCourse'; -import httpStatus from 'http-status'; - -export default class CreateCourseController { - constructor(private createCourse: CreateCourse) {} - - async create(req: Request, res: Response) { - const id: string = req.params.id; - const name: string = req.body.name; - const duration: string = req.body.duration; - - try { - await this.createCourse.run(id, name, duration); - } catch (e) { - res.status(500).json(e); - } - - res.status(httpStatus.CREATED).send(); - } -} diff --git a/src/apps/mooc_backend/controllers/StatusController.ts b/src/apps/mooc_backend/controllers/StatusController.ts deleted file mode 100644 index 44c7c44..0000000 --- a/src/apps/mooc_backend/controllers/StatusController.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Request, Response } from 'express'; -import httpStatus from 'http-status'; - -export default class StatusController { - async create(req: Request, res: Response) { - res.status(httpStatus.OK).send(); - } -} diff --git a/src/apps/mooc_backend/controllers/StatusGetController.ts b/src/apps/mooc_backend/controllers/StatusGetController.ts new file mode 100644 index 0000000..62e219a --- /dev/null +++ b/src/apps/mooc_backend/controllers/StatusGetController.ts @@ -0,0 +1,9 @@ +import { Request, Response } from 'express'; +import httpStatus from 'http-status'; +import Controller from './Controller'; + +export default class StatusGetController implements Controller { + async run(req: Request, res: Response) { + res.status(httpStatus.OK).send(); + } +} diff --git a/src/apps/mooc_backend/routes/courses.route.ts b/src/apps/mooc_backend/routes/courses.route.ts new file mode 100644 index 0000000..ebbb8bf --- /dev/null +++ b/src/apps/mooc_backend/routes/courses.route.ts @@ -0,0 +1,8 @@ +import { Express } from 'express'; +import container from '../config/dependency-injection'; +import CreateCourseController from '../controllers/CoursePutController'; + +export const register = (app: Express) => { + const controller: CreateCourseController = container.get('Apps.mooc.controllers.CoursePutController'); + app.put('/courses/:id', controller.run.bind(controller)); +}; diff --git a/src/apps/mooc_backend/routes/create-course.route.ts b/src/apps/mooc_backend/routes/create-course.route.ts deleted file mode 100644 index cafd913..0000000 --- a/src/apps/mooc_backend/routes/create-course.route.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { Express } from 'express'; -import container from '../config/dependency-injection'; -import CreateCourseController from '../controllers/CreateCourseController'; - -export const createUserRoute = (app: Express) => { - const controller: CreateCourseController = container.get('Apps.mooc.controllers.CreateCourseController'); - app.put('/courses/:id', (req, res) => controller.create(req, res)); -}; diff --git a/src/apps/mooc_backend/routes/index.ts b/src/apps/mooc_backend/routes/index.ts index 77959b7..844f646 100644 --- a/src/apps/mooc_backend/routes/index.ts +++ b/src/apps/mooc_backend/routes/index.ts @@ -1,8 +1,12 @@ import { Express } from 'express'; -import { statusRoute } from './status.route'; -import { createUserRoute } from './create-course.route'; +import glob from 'glob'; export function registerRoutes(app: Express) { - statusRoute(app); - createUserRoute(app); + const routes = glob.sync(__dirname + '/**/*.route.*'); + routes.map(route => register(route, app)); +} + +function register(routePath: string, app: Express) { + const route = require(routePath); + route.register(app); } diff --git a/src/apps/mooc_backend/routes/status.route.ts b/src/apps/mooc_backend/routes/status.route.ts index 28eeecb..e0ac7f1 100644 --- a/src/apps/mooc_backend/routes/status.route.ts +++ b/src/apps/mooc_backend/routes/status.route.ts @@ -1,8 +1,8 @@ import { Express } from 'express'; import container from '../config/dependency-injection'; -import StatusController from '../controllers/StatusController'; +import StatusController from '../controllers/StatusGetController'; -export const statusRoute = (app: Express) => { - const controller: StatusController = container.get('Apps.mooc.controllers.StatusController'); - app.get('/status', (req, res) => controller.create(req, res)); +export const register = (app: Express) => { + const controller: StatusController = container.get('Apps.mooc.controllers.StatusGetController'); + app.get('/status', controller.run.bind(controller)); }; diff --git a/tests/Mooc/Courses/application/CreateCourse.test.ts b/tests/Mooc/Courses/application/CourseCreator.test.ts similarity index 54% rename from tests/Mooc/Courses/application/CreateCourse.test.ts rename to tests/Mooc/Courses/application/CourseCreator.test.ts index e7cc8dd..043a1ed 100644 --- a/tests/Mooc/Courses/application/CreateCourse.test.ts +++ b/tests/Mooc/Courses/application/CourseCreator.test.ts @@ -1,8 +1,8 @@ -import Course from '../../../../src/Mooc/Courses/domain/Course'; -import CreateCourse from '../../../../src/Mooc/Courses/application/CreateCourse'; -import CourseRepository from '../../../../src/Mooc/Courses/domain/CourseRepository'; +import Course from '../../../../src/Contexts/Mooc/Courses/domain/Course'; +import CourseCreator from '../../../../src/Contexts/Mooc/Courses/application/CourseCreator'; +import CourseRepository from '../../../../src/Contexts/Mooc/Courses/domain/CourseRepository'; -describe('Create Course', () => { +describe('Course Creator', () => { it('should create a valid course', async () => { const save = jest.fn(); const repository: CourseRepository = { @@ -10,7 +10,7 @@ describe('Create Course', () => { search: jest.fn() }; - const createCourse = new CreateCourse(repository); + const createCourse = new CourseCreator(repository); const id = 'some-id'; const name = 'some-name'; diff --git a/tests/Mooc/Courses/infrastructure/FileCourseRepository.test.ts b/tests/Mooc/Courses/infrastructure/FileCourseRepository.test.ts index b68da3e..a5eed8a 100644 --- a/tests/Mooc/Courses/infrastructure/FileCourseRepository.test.ts +++ b/tests/Mooc/Courses/infrastructure/FileCourseRepository.test.ts @@ -1,5 +1,5 @@ -import Course from '../../../../src/Mooc/Courses/domain/Course'; -import FileCourseRepository from '../../../../src/Mooc/Courses/infrastructure/FileCourseRepository'; +import Course from '../../../../src/Contexts/Mooc/Courses/domain/Course'; +import FileCourseRepository from '../../../../src/Contexts/Mooc/Courses/infrastructure/FileCourseRepository'; describe('Save Course', () => { it('should have a course', () => { diff --git a/tsconfig.json b/tsconfig.json index 9951b09..eb52c0a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,6 +7,7 @@ "moduleResolution": "node", "sourceMap": false, "rootDir": ".", + "strict": true, "noEmit": false, "resolveJsonModule": true, "outDir": "./dist"