Skip to content

Commit

Permalink
feat: created endpoint for folder and files existence
Browse files Browse the repository at this point in the history
  • Loading branch information
apsantiso committed Jul 15, 2024
1 parent 82ac6d3 commit 026a8b2
Show file tree
Hide file tree
Showing 13 changed files with 577 additions and 2 deletions.
22 changes: 22 additions & 0 deletions src/modules/file/file.repository.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { FileStatus } from './file.domain';
import { FileModel } from './file.model';
import { FileRepository, SequelizeFileRepository } from './file.repository';
import { Op } from 'sequelize';
import { v4 } from 'uuid';

describe('FileRepository', () => {
let repository: FileRepository;
Expand Down Expand Up @@ -93,4 +94,25 @@ describe('FileRepository', () => {
expect(result).toEqual(fileSizes);
});
});

describe('findFileByFolderUuid', () => {
const folderUuid = v4();

it('When a file is searched, then it should handle the dynamic input', async () => {
const searchCriteria = { plainName: ['Report'], type: 'pdf' };

await repository.findFileByFolderUuid(folderUuid, searchCriteria);

expect(fileModel.findAll).toHaveBeenCalledWith({
where: expect.objectContaining({
folderUuid,
plainName: {
[Op.in]: searchCriteria.plainName,
},
type: searchCriteria.type,
status: FileStatus.EXISTS,
}),
});
});
});
});
27 changes: 26 additions & 1 deletion src/modules/file/file.repository.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectModel } from '@nestjs/sequelize';
import { File, FileAttributes, FileOptions, FileStatus } from './file.domain';
import { FindOptions, Op, Sequelize } from 'sequelize';
import { FindOptions, Op, Sequelize, WhereOptions } from 'sequelize';
import { Literal } from 'sequelize/types/utils';

import { User } from '../user/user.domain';
Expand Down Expand Up @@ -43,6 +43,10 @@ export interface FileRepository {
where: Partial<Omit<FileAttributes, 'name' | 'plainName'>>,
nameFilter: Pick<FileAttributes, 'name' | 'plainName'>,
): Promise<File | null>;
findFileByFolderUuid(
folderUuid: Folder['uuid'],
searchBy: { plainName: File['plainName'][]; type?: File['type'] },
): Promise<File[]>;
findByNameAndFolderUuid(
name: FileAttributes['name'],
type: FileAttributes['type'],
Expand Down Expand Up @@ -563,6 +567,27 @@ export class SequelizeFileRepository implements FileRepository {
return file ? this.toDomain(file) : null;
}

async findFileByFolderUuid(
folderUuid: Folder['uuid'],
searchBy: { plainName: File['plainName'][]; type?: File['type'] },
): Promise<File[]> {
const where: WhereOptions<File> = {
folderUuid,
...(searchBy?.type ? { type: searchBy.type } : null),
status: FileStatus.EXISTS,
};

if (searchBy?.plainName?.length) {
where.plainName = { [Op.in]: searchBy.plainName };
}

const files = await this.fileModel.findAll({
where,
});

return files.map(this.toDomain.bind(this));
}

async updateByFieldIdAndUserId(
fileId: FileAttributes['fileId'],
userId: FileAttributes['userId'],
Expand Down
14 changes: 14 additions & 0 deletions src/modules/file/file.usecase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { v4 } from 'uuid';
import { CreateFileDto } from './dto/create-file.dto';
import { UpdateFileMetaDto } from './dto/update-file-meta.dto';
import { WorkspaceAttributes } from '../workspaces/attributes/workspace.attributes';
import { Folder } from '../folder/folder.domain';

type SortParams = Array<[SortableFileAttributes, 'ASC' | 'DESC']>;

Expand Down Expand Up @@ -138,6 +139,19 @@ export class FileUseCases {
return newFile;
}

async searchFilesInFolder(
folderUuid: Folder['uuid'],
{
plainNames,
type,
}: { plainNames: File['plainName'][]; type?: File['type'] },
): Promise<File[]> {
return this.fileRepository.findFileByFolderUuid(folderUuid, {
plainName: plainNames,
type,
});
}

async updateFileMetaData(
user: User,
fileUuid: File['uuid'],
Expand Down
76 changes: 76 additions & 0 deletions src/modules/folder/dto/files-existence-in-folder.dto.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { validate } from 'class-validator';
import { plainToInstance } from 'class-transformer';
import { CheckFileExistenceInFolderDto } from './files-existence-in-folder.dto';

describe('CheckFileExistenceInFolderDto', () => {
it('When valid data is passed, then no errors should be returned', async () => {
const dto = plainToInstance(CheckFileExistenceInFolderDto, {
plainName: ['file1', 'file2'],
type: 'txt',
});

const errors = await validate(dto);
expect(errors.length).toBe(0);
});

it('When a single string is passed for plainName, then it should be transformed into an array and validate successfully', async () => {
const dto = plainToInstance(CheckFileExistenceInFolderDto, {
plainName: 'file1',
type: 'txt',
});

const errors = await validate(dto);
expect(errors.length).toBe(0);
expect(dto.plainName).toEqual(['file1']);
});

it('When plainName array exceeds max size, then it should fail', async () => {
const plainName = Array.from({ length: 51 }, (_, i) => `file${i + 1}`);
const dto = plainToInstance(CheckFileExistenceInFolderDto, {
plainName,
type: 'txt',
});

const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
expect(errors[0].constraints).toBeDefined();
});

it('When plainName contains non-string values, then it should fail', async () => {
const dto = plainToInstance(CheckFileExistenceInFolderDto, {
plainName: [1, 2, 3],
type: 'txt',
});

const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
});

it('When plainName is not provided, then it should fail', async () => {
const dto = plainToInstance(CheckFileExistenceInFolderDto, {
type: 'txt',
});

const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
});

it('When plainName is an empty array, then it should validate successfully', async () => {
const dto = plainToInstance(CheckFileExistenceInFolderDto, {
plainName: [],
type: 'txt',
});

const errors = await validate(dto);
expect(errors.length).toBe(0);
});

it('When type is not provided, then it should validate successfully', async () => {
const dto = plainToInstance(CheckFileExistenceInFolderDto, {
plainName: ['file1', 'file2'],
});

const errors = await validate(dto);
expect(errors.length).toBe(0);
});
});
32 changes: 32 additions & 0 deletions src/modules/folder/dto/files-existence-in-folder.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Transform } from 'class-transformer';
import { IsString, ArrayMaxSize, IsArray, IsOptional } from 'class-validator';
import { FileAttributes } from '../../file/file.domain';
import { ApiProperty } from '@nestjs/swagger';

export class CheckFileExistenceInFolderDto {
@ApiProperty({
description: 'Type of file',
example: 'pdf',
required: false,
})
@IsString()
@IsOptional()
type?: FileAttributes['type'];

@ApiProperty({
description: 'Plain name of file',
example: 'example',
})
@IsArray()
@ArrayMaxSize(50, {
message: 'Names parameter cannot contain more than 50 names',
})
@IsString({ each: true })
@Transform(({ value }) => {
if (typeof value === 'string') {
return [value];
}
return value;
})
plainName: FileAttributes['plainName'][];
}
56 changes: 56 additions & 0 deletions src/modules/folder/dto/folder-existence-in-folder.dto.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { validate } from 'class-validator';
import { plainToInstance } from 'class-transformer';
import { CheckFoldersExistenceDto } from './folder-existence-in-folder.dto';

describe('CheckFoldersExistenceDto', () => {
it('When valid data is passed, then no errors should be returned', async () => {
const dto = plainToInstance(CheckFoldersExistenceDto, {
plainName: ['folder1', 'folder2'],
});

const errors = await validate(dto);
expect(errors.length).toBe(0);
});

it('When a single string is passed, then it should be transformed into an array and validate successfully', async () => {
const dto = plainToInstance(CheckFoldersExistenceDto, {
plainName: 'folder1',
});

const errors = await validate(dto);
expect(errors.length).toBe(0);
expect(dto.plainName).toEqual(['folder1']);
});

it('When plainName array exceeds max size, then it should fail', async () => {
const plainName = Array.from({ length: 51 }, (_, i) => `folder${i + 1}`);
const dto = plainToInstance(CheckFoldersExistenceDto, { plainName });

const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
expect(errors[0].constraints).toBeDefined();
});

it('When plainName contains non-string values, then it should fail', async () => {
const dto = plainToInstance(CheckFoldersExistenceDto, {
plainName: [1, 2, 3],
});

const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
});

it('When plainName is not provided, then it should fail', async () => {
const dto = plainToInstance(CheckFoldersExistenceDto, {});

const errors = await validate(dto);
expect(errors.length).toBeGreaterThan(0);
});

it('When plainName is an empty array, then it should validate successfully', async () => {
const dto = plainToInstance(CheckFoldersExistenceDto, { plainName: [] });

const errors = await validate(dto);
expect(errors.length).toBe(0);
});
});
22 changes: 22 additions & 0 deletions src/modules/folder/dto/folder-existence-in-folder.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ApiProperty } from '@nestjs/swagger';
import { Transform } from 'class-transformer';
import { IsString, ArrayMaxSize, IsArray } from 'class-validator';

export class CheckFoldersExistenceDto {
@ApiProperty({
description: 'Plain name of folder',
example: 'my folder',
})
@IsArray()
@ArrayMaxSize(50, {
message: 'Names parameter cannot contain more than 50 names',
})
@IsString({ each: true })
@Transform(({ value }) => {
if (typeof value === 'string') {
return [value];
}
return value;
})
plainName: string[];
}
Loading

0 comments on commit 026a8b2

Please sign in to comment.