Skip to content

Commit

Permalink
Feat(Event): Add video uploaded event
Browse files Browse the repository at this point in the history
  • Loading branch information
Artur-Poffo committed Feb 1, 2024
1 parent 7bffeba commit 2c5044d
Show file tree
Hide file tree
Showing 9 changed files with 142 additions and 9 deletions.
19 changes: 17 additions & 2 deletions src/domain/course-management/enterprise/entities/video.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Entity } from '@/core/entities/entity'
import { AggregateRoot } from '@/core/entities/aggregate-root'
import { type UniqueEntityID } from '@/core/entities/unique-entity-id'
import { type Optional } from '@/core/types/optional'
import { VideoUploadedEvent } from '../events/video-uploaded'

export interface VideoProps {
videoName: string
Expand All @@ -11,7 +12,7 @@ export interface VideoProps {
storedAt: Date
}

export class Video extends Entity<VideoProps> {
export class Video extends AggregateRoot<VideoProps> {
get videoName() {
return this.props.videoName
}
Expand All @@ -28,6 +29,14 @@ export class Video extends Entity<VideoProps> {
return this.props.duration
}

get size() {
return this.props.size
}

get storedAt() {
return this.props.storedAt
}

static create(
props: Optional<VideoProps, 'storedAt' | 'videoType'>,
id?: UniqueEntityID
Expand All @@ -41,6 +50,12 @@ export class Video extends Entity<VideoProps> {
id
)

const isNewVideo = !id

if (isNewVideo) {
video.addDomainEvent(new VideoUploadedEvent(video))
}

return video
}
}
17 changes: 17 additions & 0 deletions src/domain/course-management/enterprise/events/video-uploaded.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { type UniqueEntityID } from '@/core/entities/unique-entity-id'
import { type DomainEvent } from '@/core/events/domain-event'
import { type Video } from '../entities/video'

export class VideoUploadedEvent implements DomainEvent {
public video: Video
public ocurredAt: Date

constructor(video: Video) {
this.video = video
this.ocurredAt = new Date()
}

getAggregateId(): UniqueEntityID {
return this.video.id
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { makeVideo } from '../../../../../test/factories/make-video'
import { InMemoryFilesRepository } from '../../../../../test/repositories/in-memory-files-repository'
import { InMemoryVideosRepository } from '../../../../../test/repositories/in-memory-videos-repository'
import { FakeUploader } from '../../../../../test/storage/fake-uploader'

let inMemoryFilesRepository: InMemoryFilesRepository
let inMemoryVideosRepository: InMemoryVideosRepository
let fakeUploader: FakeUploader

describe('On video uploaded', () => {
beforeEach(() => {
inMemoryFilesRepository = new InMemoryFilesRepository()
inMemoryVideosRepository = new InMemoryVideosRepository()
fakeUploader = new FakeUploader()
})

it('should be able to upload a video', async () => {
const video = makeVideo()
await inMemoryVideosRepository.create(video)

expect(inMemoryFilesRepository.items[0]).toBe({
name: video.videoName
})
expect(fakeUploader.files[0]).toBe({
name: video.videoName
})
})
})
31 changes: 31 additions & 0 deletions src/domain/storage/application/subscribers/on-video-uploaded.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { DomainEvents } from '@/core/events/domain-events'
import { type EventHandler } from '@/core/events/event-handler'
import { VideoUploadedEvent } from '@/domain/course-management/enterprise/events/video-uploaded'
import { type UploadFileUseCase } from '../use-cases/upload-file'

export class OnVideoUploaded implements EventHandler {
constructor(
private readonly uploadFileUseCase: UploadFileUseCase
) {
this.setupSubscriptions()
}

setupSubscriptions(): void {
DomainEvents.register(
this.uploadVideo.bind(this) as (event: unknown) => void,
VideoUploadedEvent.name
)
}

private async uploadVideo({ video }: VideoUploadedEvent) {
console.log('olá')

await this.uploadFileUseCase.exec({
fileName: video.videoName,
fileType: video.videoType,
body: video.body,
size: video.size,
storedAt: video.storedAt
})
}
}
6 changes: 4 additions & 2 deletions src/domain/storage/application/use-cases/upload-file.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ describe('Upload file use case', () => {
fileName: 'profile',
fileType: 'image/jpeg',
size: 1024 * 1024, // One megabyte
body: Buffer.from('body-image')
body: Buffer.from('body-image'),
storedAt: new Date()
})

expect(result.isRight()).toBe(true)
Expand All @@ -36,7 +37,8 @@ describe('Upload file use case', () => {
fileName: 'profile',
fileType: 'image/svg', // Invalid Mime Type
size: 1024 * 1024, // One megabyte
body: Buffer.from('body-image')
body: Buffer.from('body-image'),
storedAt: new Date()
})

expect(result.isLeft()).toBe(true)
Expand Down
7 changes: 5 additions & 2 deletions src/domain/storage/application/use-cases/upload-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ interface UploadFileUseCaseRequest {
fileType: string
body: Buffer
size: number
storedAt: Date
}

type UploadFileUseCaseResponse = Either<
Expand All @@ -29,7 +30,8 @@ export class UploadFileUseCase implements UseCase<UploadFileUseCaseRequest, Uplo
fileName,
fileType,
body,
size
size,
storedAt
}: UploadFileUseCaseRequest): Promise<UploadFileUseCaseResponse> {
if (!/image\/(jpeg|png)|video\/(mp4|avi)/.test(fileType)) {
return left(new InvalidMimeTypeError(fileType))
Expand All @@ -41,7 +43,8 @@ export class UploadFileUseCase implements UseCase<UploadFileUseCaseRequest, Uplo
fileName,
fileType,
fileKey: key,
size
size,
storedAt
})

await this.filesRepository.create(file)
Expand Down
8 changes: 5 additions & 3 deletions src/domain/storage/enterprise/entities/file.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { AggregateRoot } from '@/core/entities/aggregate-root'
import { type UniqueEntityID } from '@/core/entities/unique-entity-id'
import { type Optional } from '@/core/types/optional'

export interface FileProps {
fileName: string
Expand All @@ -23,17 +22,20 @@ export class File extends AggregateRoot<FileProps> {
return this.props.fileKey
}

get size() {
return this.props.size
}

get storedAt() {
return this.props.storedAt
}

static create(
props: Optional<FileProps, 'storedAt'>,
props: FileProps,
id?: UniqueEntityID
) {
const file = new File(
{
storedAt: props.storedAt ?? new Date(),
...props
},
id
Expand Down
4 changes: 4 additions & 0 deletions test/repositories/in-memory-videos-repository.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { DomainEvents } from '@/core/events/domain-events'
import { type VideosRepository } from '@/domain/course-management/application/repositories/videos-repository'
import { type Video } from '@/domain/course-management/enterprise/entities/video'

Expand All @@ -16,6 +17,9 @@ export class InMemoryVideosRepository implements VideosRepository {

async create(video: Video): Promise<Video> {
this.items.push(video)

DomainEvents.dispatchEventsForAggregate(video.id)

return video
}
}
31 changes: 31 additions & 0 deletions test/utils/wait-for.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* This function loops through a function rerunning all assertions
* inside of it until it gets a truthy result.
*
* If the maximum duration is reached, it then rejects.
*
* @param expectations A function containing all tests assertions
* @param maxDuration Maximum wait time before rejecting
*/
export async function waitFor(
assertions: () => void | Promise<void>,
maxDuration = 1000
): Promise<void> {
await new Promise<void>((resolve, reject) => {
let elapsedTime = 0

const interval = setInterval(async () => {
elapsedTime += 10

try {
await assertions()
clearInterval(interval)
resolve()
} catch (err) {
if (elapsedTime >= maxDuration) {
reject(err)
}
}
}, 10)
})
}

0 comments on commit 2c5044d

Please sign in to comment.