-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feat(Image, File, Upload Events): Image entity and image upload
- Loading branch information
1 parent
d9ff9dc
commit 3e49ea5
Showing
11 changed files
with
315 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
7 changes: 7 additions & 0 deletions
7
src/domain/course-management/application/repositories/images-repository.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { type Image } from '../../enterprise/entities/image' | ||
|
||
export interface ImagesRepository { | ||
findById: (id: string) => Promise<Image | null> | ||
appendImageKey: (imageKey: string, imageId: string) => Promise<Image | null> | ||
create: (image: Image) => Promise<Image> | ||
} |
24 changes: 24 additions & 0 deletions
24
src/domain/course-management/application/use-cases/upload-image.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import { InMemoryImagesRepository } from './../../../../../test/repositories/in-memory-images-repository' | ||
import { UploadImageUseCase } from './upload-image' | ||
|
||
let inMemoryImagesRepository: InMemoryImagesRepository | ||
let sut: UploadImageUseCase | ||
|
||
describe('Add image to class use case', () => { | ||
beforeEach(() => { | ||
inMemoryImagesRepository = new InMemoryImagesRepository() | ||
sut = new UploadImageUseCase(inMemoryImagesRepository) | ||
}) | ||
|
||
it('should be able to upload a image', async () => { | ||
const result = await sut.exec({ | ||
imageName: 'imageName', | ||
imageType: 'image/jpeg', | ||
body: Buffer.from('imageBody'), | ||
duration: 60 * 10, // Ten minutes, | ||
size: 1024 * 1024 // 1MB | ||
}) | ||
|
||
expect(result.isRight()).toBe(true) | ||
}) | ||
}) |
47 changes: 47 additions & 0 deletions
47
src/domain/course-management/application/use-cases/upload-image.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import { right, type Either } from '@/core/either' | ||
import { type ResourceNotFoundError } from '@/core/errors/errors/resource-not-found-error' | ||
import { type UseCase } from '@/core/use-cases/use-case' | ||
import { Image } from '../../enterprise/entities/image' | ||
import { type ImagesRepository } from './../repositories/images-repository' | ||
|
||
interface UploadImageUseCaseRequest { | ||
imageName: string | ||
imageType?: 'image/jpeg' | 'image/png' | ||
body: Buffer | ||
duration: number | ||
size: number | ||
} | ||
|
||
type UploadImageUseCaseResponse = Either< | ||
ResourceNotFoundError, | ||
{ | ||
image: Image | ||
} | ||
> | ||
|
||
export class UploadImageUseCase implements UseCase<UploadImageUseCaseRequest, UploadImageUseCaseResponse> { | ||
constructor( | ||
private readonly imagesRepository: ImagesRepository | ||
) {} | ||
|
||
async exec({ | ||
imageName, | ||
imageType, | ||
body, | ||
duration, | ||
size | ||
}: UploadImageUseCaseRequest): Promise<UploadImageUseCaseResponse> { | ||
const image = Image.create({ | ||
imageName, | ||
imageType, | ||
body, | ||
size | ||
}) | ||
|
||
await this.imagesRepository.create(image) | ||
|
||
return right({ | ||
image | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import { AggregateRoot } from '@/core/entities/aggregate-root' | ||
import { type UniqueEntityID } from '@/core/entities/unique-entity-id' | ||
import { type Optional } from '@/core/types/optional' | ||
import { ImageUploadedEvent } from '../events/image-uploaded' | ||
|
||
export interface ImageProps { | ||
imageName: string | ||
imageType: 'image/jpeg' | 'image/png' | ||
body: Buffer | ||
size: number | ||
imageKey?: string | ||
storedAt: Date | ||
} | ||
|
||
export class Image extends AggregateRoot<ImageProps> { | ||
get imageName() { | ||
return this.props.imageName | ||
} | ||
|
||
get imageType() { | ||
return this.props.imageType | ||
} | ||
|
||
get body() { | ||
return this.props.body | ||
} | ||
|
||
get size() { | ||
return this.props.size | ||
} | ||
|
||
get imageKey() { | ||
return this.props.imageKey | ||
} | ||
|
||
set imageKey(imageKeyToAppend) { | ||
this.props.imageKey = imageKeyToAppend | ||
} | ||
|
||
get storedAt() { | ||
return this.props.storedAt | ||
} | ||
|
||
static create( | ||
props: Optional<ImageProps, 'storedAt' | 'imageType'>, | ||
id?: UniqueEntityID | ||
) { | ||
const image = new Image( | ||
{ | ||
imageType: props.imageType ?? 'image/jpeg', | ||
storedAt: props.storedAt ?? new Date(), | ||
...props | ||
}, | ||
id | ||
) | ||
|
||
const isNewImage = !id | ||
|
||
if (isNewImage) { | ||
image.addDomainEvent(new ImageUploadedEvent(image)) | ||
} | ||
|
||
return image | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
src/domain/course-management/enterprise/events/image-uploaded.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 Image } from '../entities/image' | ||
|
||
export class ImageUploadedEvent implements DomainEvent { | ||
public image: Image | ||
public ocurredAt: Date | ||
|
||
constructor(image: Image) { | ||
this.image = image | ||
this.ocurredAt = new Date() | ||
} | ||
|
||
getAggregateId(): UniqueEntityID { | ||
return this.image.id | ||
} | ||
} |
46 changes: 46 additions & 0 deletions
46
src/domain/storage/application/subscribers/on-image-uploaded.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { makeImage } from '../../../../../test/factories/make-image' | ||
import { InMemoryFilesRepository } from '../../../../../test/repositories/in-memory-files-repository' | ||
import { InMemoryImagesRepository } from '../../../../../test/repositories/in-memory-images-repository' | ||
import { FakeUploader } from '../../../../../test/storage/fake-uploader' | ||
import { waitFor } from '../../../../../test/utils/wait-for' | ||
import { UploadFileUseCase } from './../use-cases/upload-file' | ||
import { OnImageUploaded } from './on-image-uploaded' | ||
|
||
let inMemoryFilesRepository: InMemoryFilesRepository | ||
let inMemoryImagesRepository: InMemoryImagesRepository | ||
let fakeUploader: FakeUploader | ||
let uploadFileUseCase: UploadFileUseCase | ||
|
||
describe('On image uploaded', () => { | ||
beforeEach(() => { | ||
inMemoryFilesRepository = new InMemoryFilesRepository() | ||
inMemoryImagesRepository = new InMemoryImagesRepository() | ||
fakeUploader = new FakeUploader() | ||
uploadFileUseCase = new UploadFileUseCase(inMemoryFilesRepository, fakeUploader) | ||
|
||
new OnImageUploaded(uploadFileUseCase, inMemoryImagesRepository) | ||
}) | ||
|
||
it('should be able to upload a image', async () => { | ||
const image = makeImage() | ||
await inMemoryImagesRepository.create(image) | ||
|
||
expect(inMemoryFilesRepository.items[0]).toMatchObject({ | ||
fileName: image.imageName | ||
}) | ||
expect(fakeUploader.files[0]).toMatchObject({ | ||
fileName: image.imageName | ||
}) | ||
}) | ||
|
||
it('should append generated fileKey to pattern file on courses domain', async () => { | ||
const image = makeImage() | ||
await inMemoryImagesRepository.create(image) | ||
|
||
await waitFor(() => { | ||
expect(inMemoryImagesRepository.items[0]).toMatchObject({ | ||
imageKey: fakeUploader.files[0].fileKey | ||
}) | ||
}) | ||
}) | ||
}) |
37 changes: 37 additions & 0 deletions
37
src/domain/storage/application/subscribers/on-image-uploaded.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { DomainEvents } from '@/core/events/domain-events' | ||
import { type EventHandler } from '@/core/events/event-handler' | ||
import { type ImagesRepository } from '@/domain/course-management/application/repositories/images-repository' | ||
import { ImageUploadedEvent } from '@/domain/course-management/enterprise/events/image-uploaded' | ||
import { type UploadFileUseCase } from '../use-cases/upload-file' | ||
|
||
export class OnImageUploaded implements EventHandler { | ||
constructor( | ||
private readonly uploadFileUseCase: UploadFileUseCase, | ||
private readonly imagesRepository: ImagesRepository | ||
) { | ||
this.setupSubscriptions() | ||
} | ||
|
||
setupSubscriptions(): void { | ||
DomainEvents.register( | ||
this.uploadImage.bind(this) as (event: unknown) => void, | ||
ImageUploadedEvent.name | ||
) | ||
} | ||
|
||
private async uploadImage({ image }: ImageUploadedEvent) { | ||
const result = await this.uploadFileUseCase.exec({ | ||
fileName: image.imageName, | ||
fileType: image.imageType, | ||
body: image.body, | ||
size: image.size, | ||
storedAt: image.storedAt | ||
}) | ||
|
||
if (result.isRight()) { | ||
const { fileKey } = result.value.file | ||
|
||
await this.imagesRepository.appendImageKey(fileKey, image.id.toString()) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
import { type UniqueEntityID } from '@/core/entities/unique-entity-id' | ||
import { Image, type ImageProps } from '@/domain/course-management/enterprise/entities/image' | ||
import { faker } from '@faker-js/faker' | ||
|
||
export function makeImage( | ||
override: Partial<ImageProps> = {}, | ||
id?: UniqueEntityID | ||
) { | ||
const image = Image.create( | ||
{ | ||
imageName: faker.company.name(), | ||
imageType: 'image/jpeg', | ||
body: Buffer.from(faker.lorem.slug()), | ||
size: faker.number.int(), | ||
...override | ||
}, | ||
id | ||
) | ||
|
||
return image | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { DomainEvents } from '@/core/events/domain-events' | ||
import { type ImagesRepository } from '@/domain/course-management/application/repositories/images-repository' | ||
import { type Image } from '@/domain/course-management/enterprise/entities/image' | ||
|
||
export class InMemoryImagesRepository implements ImagesRepository { | ||
items: Image[] = [] | ||
|
||
async findById(id: string): Promise<Image | null> { | ||
const image = this.items.find(imageToCompare => imageToCompare.id.toString() === id) | ||
|
||
if (!image) { | ||
return null | ||
} | ||
|
||
return image | ||
} | ||
|
||
async appendImageKey(imageKey: string, id: string): Promise<Image | null> { | ||
const imageToAppendKey = this.items.find(imageToCompare => imageToCompare.id.toString() === id) | ||
|
||
if (!imageToAppendKey) { | ||
return null | ||
} | ||
|
||
if (!imageToAppendKey.imageKey) { | ||
imageToAppendKey.imageKey = imageKey | ||
} | ||
|
||
return imageToAppendKey | ||
} | ||
|
||
async create(image: Image): Promise<Image> { | ||
this.items.push(image) | ||
|
||
DomainEvents.dispatchEventsForAggregate(image.id) | ||
|
||
return image | ||
} | ||
} |