diff --git a/package-lock.json b/package-lock.json index 60818eb..14e8527 100644 --- a/package-lock.json +++ b/package-lock.json @@ -705,6 +705,11 @@ "@types/node": "*" } }, + "@types/uuid-validate": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@types/uuid-validate/-/uuid-validate-0.0.1.tgz", + "integrity": "sha512-RbX9q0U00SLoV+l7loYX0Wrtv4QTClBC0QcdNts6x2b5G1HJN8NI9YlS1HNA6THrI9EH3OXSgya6eMQIlDjKFA==" + }, "@types/yargs": { "version": "13.0.3", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.3.tgz", @@ -2819,8 +2824,7 @@ "version": "2.1.1", "resolved": false, "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -2844,15 +2848,13 @@ "version": "1.0.0", "resolved": false, "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "resolved": false, "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2869,22 +2871,19 @@ "version": "1.1.0", "resolved": false, "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "resolved": false, "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "resolved": false, "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -3015,8 +3014,7 @@ "version": "2.0.3", "resolved": false, "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -3030,7 +3028,6 @@ "resolved": false, "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3047,7 +3044,6 @@ "resolved": false, "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -3056,15 +3052,13 @@ "version": "0.0.8", "resolved": false, "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "resolved": false, "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -3085,7 +3079,6 @@ "resolved": false, "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -3174,8 +3167,7 @@ "version": "1.0.1", "resolved": false, "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -3189,7 +3181,6 @@ "resolved": false, "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -3285,8 +3276,7 @@ "version": "5.1.2", "resolved": false, "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -3328,7 +3318,6 @@ "resolved": false, "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -3350,7 +3339,6 @@ "resolved": false, "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -3399,15 +3387,13 @@ "version": "1.0.2", "resolved": false, "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.3", "resolved": false, "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", - "dev": true, - "optional": true + "dev": true } } }, @@ -8240,6 +8226,11 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" }, + "uuid-validate": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/uuid-validate/-/uuid-validate-0.0.3.tgz", + "integrity": "sha512-Fykw5U4eZESbq739BeLvEBFRuJODfrlmjx5eJux7W817LjRaq4b7/i4t2zxQmhcX+fAj4nMfRdTzO4tmwLKn0w==" + }, "validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", diff --git a/package.json b/package.json index 89e8441..20e6147 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "@types/helmet": "0.0.44", "@types/node": "~13.1.1", "@types/uuid": "^3.4.6", + "@types/uuid-validate": "0.0.1", "body-parser": "^1.19.0", "bson": "^4.0.2", "compression": "^1.7.4", @@ -42,6 +43,7 @@ "ts-node": "^8.3.0", "typescript": "^3.4.5", "uuid": "^3.3.3", + "uuid-validate": "0.0.3", "winston": "^3.2.1" }, "devDependencies": { diff --git a/src/Contexts/Mooc/Courses/application/CourseCreator.ts b/src/Contexts/Mooc/Courses/application/CourseCreator.ts index 14b0699..46425ac 100644 --- a/src/Contexts/Mooc/Courses/application/CourseCreator.ts +++ b/src/Contexts/Mooc/Courses/application/CourseCreator.ts @@ -1,6 +1,7 @@ import { CourseRepository } from '../domain/CourseRepository'; import { Course } from '../domain/Course'; import { CreateCourseRequest } from './CreateCourseRequest'; +import { CourseId } from '../../Shared/domain/Courses/CourseId'; export class CourseCreator { private repository: CourseRepository; @@ -10,7 +11,7 @@ export class CourseCreator { } async run(request: CreateCourseRequest): Promise { - const course = new Course(request.id, request.name, request.duration); + const course = new Course(new CourseId(request.id), request.name, request.duration); return this.repository.save(course); } diff --git a/src/Contexts/Mooc/Courses/domain/Course.ts b/src/Contexts/Mooc/Courses/domain/Course.ts index 27b8a2b..19514e7 100644 --- a/src/Contexts/Mooc/Courses/domain/Course.ts +++ b/src/Contexts/Mooc/Courses/domain/Course.ts @@ -1,9 +1,11 @@ +import { CourseId } from '../../Shared/domain/Courses/CourseId'; + export class Course { - readonly id: string; + readonly id: CourseId; readonly name: string; readonly duration: string; - constructor(id: string, name: string, duration: string) { + constructor(id: CourseId, name: string, duration: string) { this.id = id; this.name = name; this.duration = duration; diff --git a/src/Contexts/Mooc/Courses/domain/CourseRepository.ts b/src/Contexts/Mooc/Courses/domain/CourseRepository.ts index 479bc0d..4588d16 100644 --- a/src/Contexts/Mooc/Courses/domain/CourseRepository.ts +++ b/src/Contexts/Mooc/Courses/domain/CourseRepository.ts @@ -1,8 +1,9 @@ import { Nullable } from '../../../Shared/domain/Nullable'; import { Course } from './Course'; +import { CourseId } from '../../Shared/domain/Courses/CourseId'; export interface CourseRepository { save(course: Course): Promise; - search(id: string): Promise>; + search(id: CourseId): Promise>; } diff --git a/src/Contexts/Mooc/Courses/infrastructure/FileCourseRepository.ts b/src/Contexts/Mooc/Courses/infrastructure/FileCourseRepository.ts index 997a6a5..3b1e5b0 100644 --- a/src/Contexts/Mooc/Courses/infrastructure/FileCourseRepository.ts +++ b/src/Contexts/Mooc/Courses/infrastructure/FileCourseRepository.ts @@ -3,22 +3,23 @@ import { Course } from '../domain/Course'; import fs from 'fs'; import BSON from 'bson'; import { Nullable } from '../../../Shared/domain/Nullable'; +import { CourseId } from '../../Shared/domain/Courses/CourseId'; export class FileCourseRepository implements CourseRepository { private FILE_PATH = `${__dirname}/courses`; async save(course: Course): Promise { - const filePath = this.filePath(course.id); + const filePath = this.filePath(course.id.value); const data = BSON.serialize(course); return fs.writeFileSync(filePath, data); } - async search(id: string): Promise> { - const filePath = this.filePath(id); + async search(id: CourseId): Promise> { + const filePath = this.filePath(id.value); const exists = fs.existsSync(filePath); - return exists ? BSON.deserialize(fs.readFileSync(this.filePath(id))) : null; + return exists ? BSON.deserialize(fs.readFileSync(this.filePath(id.value))) : null; } private filePath(id: string): string { diff --git a/src/Contexts/Mooc/Shared/domain/Courses/CourseId.ts b/src/Contexts/Mooc/Shared/domain/Courses/CourseId.ts new file mode 100644 index 0000000..2a5eb55 --- /dev/null +++ b/src/Contexts/Mooc/Shared/domain/Courses/CourseId.ts @@ -0,0 +1,3 @@ +import { Uuid } from '../../../../Shared/domain/value-object/Uuid'; + +export class CourseId extends Uuid {} diff --git a/src/Contexts/Shared/domain/value-object/InvalidArgumentError.ts b/src/Contexts/Shared/domain/value-object/InvalidArgumentError.ts new file mode 100644 index 0000000..1b49f05 --- /dev/null +++ b/src/Contexts/Shared/domain/value-object/InvalidArgumentError.ts @@ -0,0 +1 @@ +export class InvalidArgumentError extends Error {} diff --git a/src/Contexts/Shared/domain/value-object/Uuid.ts b/src/Contexts/Shared/domain/value-object/Uuid.ts new file mode 100644 index 0000000..da3e2ae --- /dev/null +++ b/src/Contexts/Shared/domain/value-object/Uuid.ts @@ -0,0 +1,27 @@ +import uuid from 'uuid/v4'; +import validate from 'uuid-validate'; +import { InvalidArgumentError } from './InvalidArgumentError'; + +export class Uuid { + readonly value: string; + + constructor(value: string) { + this.ensureIsValidUuid(value); + + this.value = value; + } + + static random(): Uuid { + return new Uuid(uuid()); + } + + private ensureIsValidUuid(id: string): void { + if (!validate(id)) { + throw new InvalidArgumentError(`<${this.constructor.name}> does not allow the value <${id}>`); + } + } + + toString(): string { + return this.value; + } +} diff --git a/tests/Contexts/Mooc/Courses/application/CourseCreator.test.ts b/tests/Contexts/Mooc/Courses/application/CourseCreator.test.ts index dfb772c..c1cb01c 100644 --- a/tests/Contexts/Mooc/Courses/application/CourseCreator.test.ts +++ b/tests/Contexts/Mooc/Courses/application/CourseCreator.test.ts @@ -1,6 +1,7 @@ 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'; +import { CourseId } from '../../../../../src/Contexts/Mooc/Shared/domain/Courses/CourseId'; describe('Course Creator', () => { it('should create a valid course', async () => { @@ -12,11 +13,11 @@ describe('Course Creator', () => { const createCourse = new CourseCreator(repository); - const id = 'some-id'; + const id = '0766c602-d4d4-48b6-9d50-d3253123275e'; const name = 'some-name'; const duration = 'some-duration'; - const course = new Course(id, name, duration); + const course = new Course(new CourseId(id), name, duration); await createCourse.run({ id, name, duration }); diff --git a/tests/Contexts/Mooc/Courses/infrastructure/FileCourseRepository.test.ts b/tests/Contexts/Mooc/Courses/infrastructure/FileCourseRepository.test.ts index c43d860..d51df17 100644 --- a/tests/Contexts/Mooc/Courses/infrastructure/FileCourseRepository.test.ts +++ b/tests/Contexts/Mooc/Courses/infrastructure/FileCourseRepository.test.ts @@ -1,10 +1,11 @@ import { FileCourseRepository } from '../../../../../src/Contexts/Mooc/Courses/infrastructure/FileCourseRepository'; import { Course } from '../../../../../src/Contexts/Mooc/Courses/domain/Course'; +import { CourseId } from '../../../../../src/Contexts/Mooc/Shared/domain/Courses/CourseId'; describe('Save Course', () => { it('should have a course', () => { const repository = new FileCourseRepository(); - const course = new Course('id', 'name', 'duration'); + const course = new Course(new CourseId('0766c602-d4d4-48b6-9d50-d3253123275e'), 'name', 'duration'); repository.save(course); });