Skip to content

Commit

Permalink
update:
Browse files Browse the repository at this point in the history
- re-structure admin and user controller
- stream / view / download file for user and admin
- fix some authorization in admin endpoint
  • Loading branch information
dekiakbar committed Apr 20, 2023
1 parent ff24c7a commit 1cd296b
Show file tree
Hide file tree
Showing 14 changed files with 227 additions and 35 deletions.
18 changes: 18 additions & 0 deletions src/file/controller/admin/file.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { FileController } from './file.controller';

describe('FileController', () => {
let controller: FileController;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [FileController],
}).compile();

controller = module.get<FileController>(FileController);
});

it('should be defined', () => {
expect(controller).toBeDefined();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,31 @@ import {
UseGuards,
Request,
Query,
StreamableFile,
Res,
} from '@nestjs/common';
import { FileService } from '../service/file.service';
import { FilesInterceptor } from '@nestjs/platform-express';
import { AuthGuard } from '@nestjs/passport';
import { PageOptionsDto } from 'src/common/dto/page-options.dto';
import { Roles } from 'src/auth/decorator/roles.decorator';
import { RoleEnum } from 'src/user/enum/role.enum';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { ApiPaginatedResponse } from 'src/common/decorator/api-paginated-response.decoratos';
import { FileResponseDto } from '../dto/file-response.dto';
import { FileMineResponseDto } from '../dto/file-mine-response.dto';
import { FileService } from 'src/file/service/file.service';
import { FileResponseDto } from 'src/file/dto/file-response.dto';
import { ApiSuccessResponse } from 'src/common/decorator/api-success-response';
import { Stream } from 'stream';
import { Response } from 'express';

@UseGuards(AuthGuard('jwt'))
@ApiTags('File')
@ApiBearerAuth('Bearer')
@Controller('file')
@Controller('admin/file')
export class FileController {
constructor(private readonly fileService: FileService) {}

@ApiSuccessResponse(FileResponseDto, 'Successfully upload file')
@Roles(RoleEnum.ADMIN)
@Post()
@UseInterceptors(FilesInterceptor('files'))
async upload(
Expand All @@ -45,32 +50,32 @@ export class FileController {
return this.fileService.findAll(pageOptionsDto);
}

@ApiPaginatedResponse(FileMineResponseDto)
@Get('mine')
mine(@Query() pageOptionsDto: PageOptionsDto, @Request() request) {
return this.fileService.findAll(pageOptionsDto, request.user.id);
}

@ApiSuccessResponse(FileResponseDto, 'Successfully received file detail')
@Roles(RoleEnum.ADMIN)
@Get(':id')
detail(@Param('id') id: string) {
return this.fileService.detail(+id);
}

@Roles(RoleEnum.ADMIN)
@Delete(':id')
remove(@Param('id') id: string) {
return this.fileService.remove(+id);
}

@ApiSuccessResponse(FileMineResponseDto, 'Successfully received file detail')
@Get('mine/:id')
mineDetail(@Param('id') id: string, @Request() request) {
return this.fileService.mineDetail(+id, request.user.id);
}
@Get('stream/:internalCid')
@Roles(RoleEnum.ADMIN)
async stream(
@Param('internalCid') internalCid: string,
@Res({ passthrough: true }) res: Response,
): Promise<StreamableFile> {
const fileMetaData = await this.fileService.stream(internalCid);
const file = Stream.Readable.from(fileMetaData.buffer);

res.set({
'Content-Type': fileMetaData.mimeType,
});

@Delete('mine/:id')
mineRemove(@Param('id') id: string, @Request() request) {
return this.fileService.mineRemove(+id, request.user.id);
return new StreamableFile(file);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Test, TestingModule } from '@nestjs/testing';
import { FileController } from './file.controller';
import { FileService } from '../service/file.service';
import { FileService } from '../../service/file.service';

describe('FileController', () => {
let controller: FileController;
Expand Down
79 changes: 79 additions & 0 deletions src/file/controller/user/file.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import {
Controller,
Get,
Post,
Param,
Delete,
UseInterceptors,
UploadedFiles,
UseGuards,
Request,
Query,
Res,
StreamableFile,
} from '@nestjs/common';
import { FilesInterceptor } from '@nestjs/platform-express';
import { AuthGuard } from '@nestjs/passport';
import { PageOptionsDto } from 'src/common/dto/page-options.dto';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { ApiPaginatedResponse } from 'src/common/decorator/api-paginated-response.decoratos';
import { FileService } from 'src/file/service/file.service';
import { FileResponseDto } from 'src/file/dto/file-response.dto';
import { FileMineResponseDto } from 'src/file/dto/file-mine-response.dto';
import { ApiSuccessResponse } from 'src/common/decorator/api-success-response';
import { Stream } from 'stream';
import { Response } from 'express';

@UseGuards(AuthGuard('jwt'))
@ApiTags('File')
@ApiBearerAuth('Bearer')
@Controller('file')
export class FileController {
constructor(private readonly fileService: FileService) {}

@ApiSuccessResponse(FileResponseDto, 'Successfully upload file')
@Post()
@UseInterceptors(FilesInterceptor('files'))
async upload(
@UploadedFiles() files: Array<Express.Multer.File>,
@Request() request,
) {
return await this.fileService.upload(files, request.user.id);
}

@ApiPaginatedResponse(FileMineResponseDto)
@Get()
mine(@Query() pageOptionsDto: PageOptionsDto, @Request() request) {
return this.fileService.findAll(pageOptionsDto, request.user.id);
}

@ApiSuccessResponse(FileMineResponseDto, 'Successfully received file detail')
@Get(':id')
mineDetail(@Param('id') id: string, @Request() request) {
return this.fileService.mineDetail(+id, request.user.id);
}

@Delete(':id')
mineRemove(@Param('id') id: string, @Request() request) {
return this.fileService.mineRemove(+id, request.user.id);
}

@Get('stream/:internalCid')
async stream(
@Param('internalCid') internalCid: string,
@Res({ passthrough: true }) res: Response,
@Request() request,
): Promise<StreamableFile> {
const fileMetaData = await this.fileService.mineStream(
internalCid,
request.user.id,
);
const file = Stream.Readable.from(fileMetaData.buffer);

res.set({
'Content-Type': fileMetaData.mimeType,
});

return new StreamableFile(file);
}
}
2 changes: 1 addition & 1 deletion src/file/dto/file-mine-response.dto.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ApiProperty } from '@nestjs/swagger';
import { FileModel } from '../model/files.mode';
import { FileModel } from '../model/files.model';

export class FileMineResponseDto {
@ApiProperty()
Expand Down
2 changes: 1 addition & 1 deletion src/file/dto/file-response.dto.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ApiProperty } from '@nestjs/swagger';
import { FileMineResponseDto } from './file-mine-response.dto';
import { UsersModel } from 'src/user/model/users.model';
import { FileModel } from '../model/files.mode';
import { FileModel } from '../model/files.model';

export class FileResponseDto extends FileMineResponseDto {
@ApiProperty()
Expand Down
13 changes: 13 additions & 0 deletions src/file/dto/file-view-response.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ApiProperty } from '@nestjs/swagger';
import { FileMineResponseDto } from './file-mine-response.dto';
import { FileModel } from '../model/files.model';

export class FileViewResponseDto extends FileMineResponseDto {
@ApiProperty()
buffer: Buffer;

constructor(file: FileModel, buffer: Buffer) {
super(file);
this.buffer = buffer;
}
}
7 changes: 4 additions & 3 deletions src/file/file.module.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Module } from '@nestjs/common';
import { FileService } from './service/file.service';
import { FileController } from './controller/file.controller';
import { FileController as UserFileController } from './controller/user/file.controller';
import { IpfsModule } from 'src/ipfs/ipfs.module';
import { DatabaseModule } from 'src/database/database.module';
import { SequelizeModule } from '@nestjs/sequelize';
import { FileModel } from './model/files.mode';
import { FileModel } from './model/files.model';
import { UsersModule } from 'src/user/users.module';
import { FileController as AdminFileController } from './controller/admin/file.controller';

@Module({
imports: [
Expand All @@ -14,7 +15,7 @@ import { UsersModule } from 'src/user/users.module';
SequelizeModule.forFeature([FileModel]),
UsersModule,
],
controllers: [FileController],
controllers: [AdminFileController, UserFileController],
providers: [FileService],
})
export class FileModule {}
File renamed without changes.
68 changes: 65 additions & 3 deletions src/file/service/file.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ import {
BadRequestException,
Injectable,
NotFoundException,
UnauthorizedException,
} from '@nestjs/common';
import { IpfsService } from 'src/ipfs/service/ipfs.service';
import { FileModel } from '../model/files.mode';
import { FileModel } from '../model/files.model';
import { InjectModel } from '@nestjs/sequelize';
import { UsersModel } from 'src/user/model/users.model';
import { UsersService } from 'src/user/service/users.service';
Expand All @@ -15,6 +16,7 @@ import { FileMineResponseDto } from '../dto/file-mine-response.dto';
import { FindOptions } from 'sequelize';
import { FileResponseDto } from '../dto/file-response.dto';
import { ConfigService } from '@nestjs/config';
import { FileViewResponseDto } from '../dto/file-view-response.dto';
@Injectable()
export class FileService {
constructor(
Expand Down Expand Up @@ -83,6 +85,10 @@ export class FileService {
}

async findOne(id: number, userId: number = null): Promise<FileModel> {
if (!id) {
throw new BadRequestException("Id can't be empty");
}

const query: FindOptions = {
where: {
id: id,
Expand All @@ -108,14 +114,47 @@ export class FileService {
return file;
}

async findOneByInternalCid(
internalCid: string,
userId: number = null,
): Promise<FileModel> {
if (!internalCid) {
throw new BadRequestException("InternalCid can't be empty");
}

const query: FindOptions = {
where: {
internalCid: internalCid,
},
include: {
model: UsersModel,
},
};

if (userId) {
query.where = {
internalCid: internalCid,
userId: userId,
};
}

const file = await this.fileModel.findOne(query);

if (!file) {
throw new NotFoundException('File is not exist');
}

return file;
}

async detail(id: number): Promise<FileResponseDto> {
const file = await this.findOne(id);
return new FileResponseDto(file);
}

async mineDetail(id: number, userId: number): Promise<FileMineResponseDto> {
if (!userId) {
throw new BadRequestException('you are not authorized.');
throw new UnauthorizedException('You are not authorized.');
}

const file = await this.findOne(id, userId);
Expand All @@ -133,7 +172,7 @@ export class FileService {

async mineRemove(id: number, userId: number): Promise<any> {
if (!userId) {
throw new BadRequestException('you are not authorized.');
throw new UnauthorizedException('You are not authorized.');
}

const file = await this.findOne(id, userId);
Expand All @@ -157,4 +196,27 @@ export class FileService {

return result;
}

async stream(internalCid: string): Promise<FileViewResponseDto> {
const fileData = await this.findOneByInternalCid(internalCid);
const fileBuffer = await this.ipfsService.get(fileData.cid);
const fileMetaData = new FileViewResponseDto(fileData, fileBuffer);

return fileMetaData;
}

async mineStream(
internalCid: string,
userId: number,
): Promise<FileViewResponseDto> {
if (!userId) {
throw new UnauthorizedException('You are not authorized.');
}

const fileData = await this.findOneByInternalCid(internalCid, userId);
const fileBuffer = await this.ipfsService.get(fileData.cid);
const fileMetaData = new FileViewResponseDto(fileData, fileBuffer);

return fileMetaData;
}
}
11 changes: 11 additions & 0 deletions src/ipfs/service/ipfs.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,15 @@ export class IpfsService {
const result = await this.ipfs.pin.rm(cid);
return result;
}

async get(cid: string): Promise<Buffer> {
const dataStream = this.ipfs.cat(cid);
const chunks = [];
for await (const chunk of dataStream) {
chunks.push(chunk);
}
const buffer = Buffer.concat(chunks);

return buffer;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Test, TestingModule } from '@nestjs/testing';
import { UsersController } from './users.controller';
import { UsersService } from '../service/users.service';
import { UsersService } from '../../service/users.service';

describe('UsersController', () => {
let controller: UsersController;
Expand Down
Loading

0 comments on commit 1cd296b

Please sign in to comment.