Skip to content

Commit c2e232d

Browse files
authored
Merge pull request #10 from labscommunity/kranthi/upload-get-handlers
feat: add get handlers for upload module
2 parents 38bd9b4 + dafb1a8 commit c2e232d

File tree

3 files changed

+129
-36
lines changed

3 files changed

+129
-36
lines changed

src/common/utils/paginator.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
export interface PaginatedResult<T> {
2+
data: T[]
3+
meta: {
4+
total: number
5+
lastPage: number
6+
currentPage: number
7+
perPage: number
8+
prev: number | null
9+
next: number | null
10+
}
11+
}
12+
13+
export type PaginateOptions = { page?: number | string, perPage?: number | string }
14+
export type PaginateFunction = <T, K>(model: any, args?: K, options?: PaginateOptions) => Promise<PaginatedResult<T>>
15+
16+
export const paginator = (defaultOptions: PaginateOptions): PaginateFunction => {
17+
return async (model, args: any = { where: undefined }, options) => {
18+
const page = Number(options?.page || defaultOptions?.page) || 1;
19+
const perPage = Number(options?.perPage || defaultOptions?.perPage) || 10;
20+
21+
const skip = page > 0 ? perPage * (page - 1) : 0;
22+
const [total, data] = await Promise.all([
23+
model.count({ where: args.where }),
24+
model.findMany({
25+
...args,
26+
take: perPage,
27+
skip,
28+
}),
29+
]);
30+
const lastPage = Math.ceil(total / perPage);
31+
32+
return {
33+
data,
34+
meta: {
35+
total,
36+
lastPage,
37+
currentPage: page,
38+
perPage,
39+
prev: page > 1 ? page - 1 : null,
40+
next: page < lastPage ? page + 1 : null,
41+
},
42+
};
43+
};
44+
};

src/modules/upload/upload.controller.ts

Lines changed: 58 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { existsSync, mkdirSync } from 'node:fs';
22
import { extname, parse } from 'node:path';
33

4-
import { BadRequestException, Body, Controller, Post, Req, UploadedFiles, UseInterceptors } from '@nestjs/common';
4+
import { BadRequestException, Body, Controller, Get, Param, Post, Query, Req, UploadedFiles, UseInterceptors } from '@nestjs/common';
55
import { FilesInterceptor } from '@nestjs/platform-express';
66
import { UploadStatus, UploadType, User } from '@prisma/client';
77
import * as crypto from 'crypto';
@@ -19,43 +19,11 @@ import { UploadService } from './upload.service';
1919
export class UploadController {
2020
constructor(private readonly uploadService: UploadService) { }
2121

22-
@Post('cost')
23-
getEstimate(@Body() body: EstimatesDto) {
24-
return this.uploadService.getCostEstimate(body);
22+
@Get()
23+
async getUploadRequests(@Query('page') page: number, @Query('limit') limit: number) {
24+
return this.uploadService.getUploadRequests({ page, perPage: limit });
2525
}
2626

27-
@Post('create')
28-
createUploadRequest(@Body() body: CreateUploadRequestDto, @Req() req: Request) {
29-
const { totalChunks, uploadType, fileName, size } = body
30-
const user = (req as any).user as User
31-
if (!user) {
32-
throw new BadRequestException('User not found');
33-
}
34-
35-
if (totalChunks !== 1) {
36-
throw new BadRequestException('Total chunks must be 1');
37-
}
38-
39-
if (uploadType === UploadType.MULTIPART_FILE) {
40-
throw new BadRequestException('Invalid upload type. Only single file upload is supported.');
41-
}
42-
43-
// Validate filename has proper extension
44-
if (!fileName.includes('.')) {
45-
throw new BadRequestException('Filename must include a file extension');
46-
}
47-
48-
const extension = fileName.split('.').pop();
49-
if (!extension || extension.length === 0) {
50-
throw new BadRequestException('Invalid file. No extension found');
51-
}
52-
53-
if (size <= 0) {
54-
throw new BadRequestException('File size must be greater than 0');
55-
}
56-
57-
return this.uploadService.createUploadRequest(body, user);
58-
}
5927

6028
@Post()
6129
@UseInterceptors(FilesInterceptor('file', 1, {
@@ -127,6 +95,45 @@ export class UploadController {
12795
return receipt
12896
}
12997

98+
99+
@Post('cost')
100+
getEstimate(@Body() body: EstimatesDto) {
101+
return this.uploadService.getCostEstimate(body);
102+
}
103+
104+
@Post('create')
105+
createUploadRequest(@Body() body: CreateUploadRequestDto, @Req() req: Request) {
106+
const { totalChunks, uploadType, fileName, size } = body
107+
const user = (req as any).user as User
108+
if (!user) {
109+
throw new BadRequestException('User not found');
110+
}
111+
112+
if (totalChunks !== 1) {
113+
throw new BadRequestException('Total chunks must be 1');
114+
}
115+
116+
if (uploadType === UploadType.MULTIPART_FILE) {
117+
throw new BadRequestException('Invalid upload type. Only single file upload is supported.');
118+
}
119+
120+
// Validate filename has proper extension
121+
if (!fileName.includes('.')) {
122+
throw new BadRequestException('Filename must include a file extension');
123+
}
124+
125+
const extension = fileName.split('.').pop();
126+
if (!extension || extension.length === 0) {
127+
throw new BadRequestException('Invalid file. No extension found');
128+
}
129+
130+
if (size <= 0) {
131+
throw new BadRequestException('File size must be greater than 0');
132+
}
133+
134+
return this.uploadService.createUploadRequest(body, user);
135+
}
136+
130137
@Post('chunk')
131138
async uploadChunk(@Req() req: Request) {
132139
const currentChunk = req.headers['x-current-chunk'];
@@ -199,4 +206,19 @@ export class UploadController {
199206

200207
return rest;
201208
}
209+
210+
@Get('receipt')
211+
async getReceipts(@Query('page') page: number, @Query('limit') limit: number) {
212+
return this.uploadService.getReceipts({ page, perPage: limit });
213+
}
214+
215+
@Get('receipt/:id')
216+
async getReceipt(@Param('id') id: string) {
217+
return this.uploadService.getReceiptById(id);
218+
}
219+
220+
@Get(':id')
221+
async getUploadRequest(@Param('id') id: string) {
222+
return this.uploadService.getUploadRequest(id);
223+
}
202224
}

src/modules/upload/upload.service.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import BigNumber from 'bignumber.js';
66
import { Contract, getAddress, Interface, Provider } from 'ethers';
77
import * as fs from 'fs';
88
import path from 'path';
9+
import { paginator } from 'src/common/utils/paginator';
910
import { DatabaseService } from 'src/database/database.service';
1011
import { pipeline } from 'stream';
1112
import { promisify } from 'util';
@@ -24,6 +25,8 @@ const ERC20_ABI = [
2425
];
2526
const pump = promisify(pipeline);
2627

28+
const paginate = paginator({ perPage: 10 });
29+
2730
@Injectable()
2831
export class UploadService {
2932
constructor(
@@ -67,6 +70,22 @@ export class UploadService {
6770
};
6871
}
6972

73+
async getUploadRequests({ page = 1, perPage = 10 }: { page?: number, perPage?: number }) {
74+
return await paginate(this.databaseService.upload, {
75+
orderBy: {
76+
createdAt: 'desc'
77+
}
78+
}, { page, perPage });
79+
}
80+
81+
async getReceipts({ page = 1, perPage = 10 }: { page?: number, perPage?: number }) {
82+
return await paginate(this.databaseService.receipt, {
83+
orderBy: {
84+
createdAt: 'desc'
85+
}
86+
}, { page, perPage });
87+
}
88+
7089
async createUploadRequest(createUploadRequestDto: CreateUploadRequestDto, user: User) {
7190
const { totalChunks, uploadType, fileName, size, tokenTicker, mimeType, network, chainId, tags } = createUploadRequestDto
7291
const { chainType } = user
@@ -404,6 +423,14 @@ export class UploadService {
404423
return feeTransaction;
405424
}
406425

426+
async getReceiptById(receiptId: string) {
427+
return await this.databaseService.receipt.findFirst({
428+
where: {
429+
id: receiptId,
430+
},
431+
});
432+
}
433+
407434
async getReceiptByUploadId(uploadId: string) {
408435
return await this.databaseService.receipt.findFirst({
409436
where: {

0 commit comments

Comments
 (0)