Skip to content

Commit

Permalink
Merge pull request #13 from Artur-Poffo/feat-register-tag
Browse files Browse the repository at this point in the history
Feat(Tag): Register many tags at same time
  • Loading branch information
Artur-Poffo committed Jan 30, 2024
2 parents 4bcf470 + 13a5caa commit 6d87a65
Show file tree
Hide file tree
Showing 8 changed files with 174 additions and 1 deletion.
3 changes: 2 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/space-before-function-paren": "off",
"@typescript-eslint/no-extraneous-class": "off",
"@typescript-eslint/strict-boolean-expressions": "off"
"@typescript-eslint/strict-boolean-expressions": "off",
"@typescript-eslint/no-misused-promises": "off"
}
}
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- [x] Instructors must be able to register courses on the platform.
- [x] Instructors can register modules for a course.
- [x] Instructors can add classes to modules.
- [x] Should be able to register tags
- [ ] Responsible instructor can add "tags" to their course to inform students about technologies present in the course.
- [ ] Instructors can upload videos.
- [ ] Instructor can upload a certificate template for students upon course completion.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { type Tag } from '../../enterprise/entities/tag'

export interface TagsRepository {
findById: (id: string) => Promise<Tag | null>
findByValue: (value: string) => Promise<Tag | null>
create: (tag: Tag) => Promise<Tag>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { type UseCaseError } from '@/core/errors/use-case-error'

export class RepeatedTagError extends Error implements UseCaseError {
constructor() {
super('Repeated tag found')
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { type UseCaseError } from '@/core/errors/use-case-error'

export class TagAlreadyExistsError extends Error implements UseCaseError {
constructor(name: string) {
super(`Tag "${name}" already exists.`)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { InMemoryTagsRepository } from './../../../../../test/repositories/in-memory-tags-repository'
import { RepeatedTagError } from './errors/repeated-tag-error'
import { TagAlreadyExistsError } from './errors/tag-already-exists-error'
import { RegisterTagUseCase } from './register-tag'

let inMemoryTagsRepository: InMemoryTagsRepository
let sut: RegisterTagUseCase

describe('Register tag use case', () => {
beforeEach(() => {
inMemoryTagsRepository = new InMemoryTagsRepository()
sut = new RegisterTagUseCase(inMemoryTagsRepository)
})

it('should be able to register a new tag for the application', async () => {
const result = await sut.exec({
tags: 'TypeScript'
})

expect(result.isRight()).toBe(true)
expect(inMemoryTagsRepository.items[0].value).toBe('TYPESCRIPT')
})

it('should be able to register many tags for the application at the same time', async () => {
const result = await sut.exec({
tags: ['TypeScript', 'Next.js', 'Vue.js']
})

expect(result.isRight()).toBe(true)
expect(result.value).toMatchObject({
tags: expect.arrayContaining([
expect.objectContaining({
value: 'TYPESCRIPT'
}),
expect.objectContaining({
value: 'VUE.JS'
})
])
})
})

it('should not be able to register a new tag for the application with same value twice', async () => {
await sut.exec({
tags: 'TypeScript'
})

const result = await sut.exec({
tags: 'TypeScript'
})

expect(result.isLeft()).toBe(true)
expect(result.value).toBeInstanceOf(TagAlreadyExistsError)
})

it('should not be able to register a new tag for the application with same value twice in same exec', async () => {
const result = await sut.exec({
tags: ['TypeScript', 'TypeScript']
})

expect(result.isLeft()).toBe(true)
expect(result.value).toBeInstanceOf(RepeatedTagError)
})
})
56 changes: 56 additions & 0 deletions src/domain/course-management/application/use-cases/register-tag.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { left, right, type Either } from '@/core/either'
import { type UseCase } from '@/core/use-cases/use-case'
import { Tag } from '../../enterprise/entities/tag'
import { type TagsRepository } from '../repositories/tags-repository'
import { RepeatedTagError } from './errors/repeated-tag-error'
import { TagAlreadyExistsError } from './errors/tag-already-exists-error'

interface RegisterTagUseCaseRequest {
tags: string | string[]
}

type RegisterTagUseCaseResponse = Either<
TagAlreadyExistsError,
{
tags: Tag[]
}
>

export class RegisterTagUseCase implements UseCase<RegisterTagUseCaseRequest, RegisterTagUseCaseResponse> {
constructor(
private readonly tagsRepository: TagsRepository
) { }

async exec({
tags
}: RegisterTagUseCaseRequest): Promise<RegisterTagUseCaseResponse> {
const tagsToRegister = Array.isArray(tags) ? tags : [tags]

const haveRepeatedTags = new Set(tagsToRegister).size !== tagsToRegister.length

if (haveRepeatedTags) {
return left(new RepeatedTagError())
}

const existingTags = await Promise.all(
tagsToRegister.map(async (tagValue) => ({
tagValue: tagValue.toUpperCase(),
exists: !!(await this.tagsRepository.findByValue(tagValue.toUpperCase()))
}))
)

const alreadyExists = existingTags.find((tag) => tag.exists)

if (alreadyExists) {
return left(new TagAlreadyExistsError(alreadyExists.tagValue))
}

const newTags = tagsToRegister.map((value) => Tag.create({ value: value.toUpperCase() }))

await Promise.all(newTags.map(async (tag) => await this.tagsRepository.create(tag)))

return right({
tags: newTags
})
}
}
31 changes: 31 additions & 0 deletions test/repositories/in-memory-tags-repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { type TagsRepository } from '@/domain/course-management/application/repositories/tags-repository'
import { type Tag } from '@/domain/course-management/enterprise/entities/tag'

export class InMemoryTagsRepository implements TagsRepository {
public items: Tag[] = []

async findById(id: string): Promise<Tag | null> {
const tag = this.items.find(tagToCompare => tagToCompare.id.toString() === id)

if (!tag) {
return null
}

return tag
}

async findByValue(value: string): Promise<Tag | null> {
const tag = this.items.find(tagToCompare => tagToCompare.value === value)

if (!tag) {
return null
}

return tag
}

async create(tag: Tag): Promise<Tag> {
this.items.push(tag)
return tag
}
}

0 comments on commit 6d87a65

Please sign in to comment.