Skip to content

Commit 76c5dc0

Browse files
committed
s3 widget by row pk instead of filename
1 parent 9e3a7d8 commit 76c5dc0

9 files changed

Lines changed: 1927 additions & 457 deletions

File tree

.husky/pre-commit

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
npx lint-staged
Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,30 @@
11
export class S3GetFileUrlDs {
2-
connectionId: string;
3-
tableName: string;
4-
fieldName: string;
5-
fileKey: string;
6-
userId: string;
7-
masterPwd: string;
2+
connectionId: string;
3+
tableName: string;
4+
fieldName: string;
5+
rowPrimaryKey: Record<string, unknown>;
6+
userId: string;
7+
masterPwd: string;
88
}
99

1010
export class S3GetUploadUrlDs {
11-
connectionId: string;
12-
tableName: string;
13-
fieldName: string;
14-
userId: string;
15-
masterPwd: string;
16-
filename: string;
17-
contentType: string;
11+
connectionId: string;
12+
tableName: string;
13+
fieldName: string;
14+
userId: string;
15+
masterPwd: string;
16+
filename: string;
17+
contentType: string;
1818
}
1919

2020
export class S3FileUrlResponseDs {
21-
url: string;
22-
key: string;
23-
expiresIn: number;
21+
url: string;
22+
key: string;
23+
expiresIn: number;
2424
}
2525

2626
export class S3UploadUrlResponseDs {
27-
uploadUrl: string;
28-
key: string;
29-
expiresIn: number;
27+
uploadUrl: string;
28+
key: string;
29+
expiresIn: number;
3030
}
Lines changed: 175 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,131 +1,187 @@
11
import {
2-
Body,
3-
Controller,
4-
Get,
5-
HttpStatus,
6-
Inject,
7-
Injectable,
8-
Post,
9-
Query,
10-
UseGuards,
11-
UseInterceptors,
12-
} from '@nestjs/common';
13-
import { HttpException } from '@nestjs/common/exceptions/http.exception.js';
14-
import { ApiBearerAuth, ApiBody, ApiOperation, ApiQuery, ApiResponse, ApiTags } from '@nestjs/swagger';
15-
import { UseCaseType } from '../../common/data-injection.tokens.js';
16-
import { MasterPassword, QueryTableName, SlugUuid, UserId } from '../../decorators/index.js';
17-
import { InTransactionEnum } from '../../enums/index.js';
18-
import { Messages } from '../../exceptions/text/messages.js';
19-
import { ConnectionEditGuard, ConnectionReadGuard } from '../../guards/index.js';
20-
import { SentryInterceptor } from '../../interceptors/index.js';
21-
import { S3FileUrlResponseDs, S3UploadUrlResponseDs } from './application/data-structures/s3-operation.ds.js';
22-
import { IGetS3FileUrl, IGetS3UploadUrl } from './use-cases/s3-use-cases.interface.js';
2+
Body,
3+
Controller,
4+
Get,
5+
HttpStatus,
6+
Inject,
7+
Injectable,
8+
Post,
9+
Query,
10+
UseGuards,
11+
UseInterceptors,
12+
} from "@nestjs/common";
13+
import { HttpException } from "@nestjs/common/exceptions/http.exception.js";
14+
import {
15+
ApiBearerAuth,
16+
ApiBody,
17+
ApiOperation,
18+
ApiQuery,
19+
ApiResponse,
20+
ApiTags,
21+
} from "@nestjs/swagger";
22+
import { UseCaseType } from "../../common/data-injection.tokens.js";
23+
import {
24+
MasterPassword,
25+
QueryTableName,
26+
SlugUuid,
27+
UserId,
28+
} from "../../decorators/index.js";
29+
import { InTransactionEnum } from "../../enums/index.js";
30+
import { Messages } from "../../exceptions/text/messages.js";
31+
import {
32+
ConnectionEditGuard,
33+
ConnectionReadGuard,
34+
} from "../../guards/index.js";
35+
import { SentryInterceptor } from "../../interceptors/index.js";
36+
import {
37+
S3FileUrlResponseDs,
38+
S3UploadUrlResponseDs,
39+
} from "./application/data-structures/s3-operation.ds.js";
40+
import {
41+
IGetS3FileUrl,
42+
IGetS3UploadUrl,
43+
} from "./use-cases/s3-use-cases.interface.js";
2344

2445
@UseInterceptors(SentryInterceptor)
2546
@Controller()
2647
@ApiBearerAuth()
27-
@ApiTags('S3 Widget')
48+
@ApiTags("S3 Widget")
2849
@Injectable()
2950
export class S3WidgetController {
30-
constructor(
31-
@Inject(UseCaseType.GET_S3_FILE_URL)
32-
private readonly getS3FileUrlUseCase: IGetS3FileUrl,
33-
@Inject(UseCaseType.GET_S3_UPLOAD_URL)
34-
private readonly getS3UploadUrlUseCase: IGetS3UploadUrl,
35-
) {}
51+
constructor(
52+
@Inject(UseCaseType.GET_S3_FILE_URL)
53+
private readonly getS3FileUrlUseCase: IGetS3FileUrl,
54+
@Inject(UseCaseType.GET_S3_UPLOAD_URL)
55+
private readonly getS3UploadUrlUseCase: IGetS3UploadUrl,
56+
) {}
57+
58+
@UseGuards(ConnectionReadGuard)
59+
@ApiOperation({ summary: "Get pre-signed URL for S3 file download" })
60+
@ApiResponse({
61+
status: 200,
62+
description: "Pre-signed URL generated successfully.",
63+
})
64+
@ApiQuery({ name: "tableName", required: true })
65+
@ApiQuery({ name: "fieldName", required: true })
66+
@ApiQuery({
67+
name: "rowPrimaryKey",
68+
required: true,
69+
description: "JSON-encoded primary key object",
70+
})
71+
@Get("/s3/file/:connectionId")
72+
async getFileUrl(
73+
@SlugUuid("connectionId") connectionId: string,
74+
@UserId() userId: string,
75+
@MasterPassword() masterPwd: string,
76+
@QueryTableName() tableName: string,
77+
@Query("fieldName") fieldName: string,
78+
@Query("rowPrimaryKey") rowPrimaryKeyStr: string,
79+
): Promise<S3FileUrlResponseDs> {
80+
if (!connectionId) {
81+
throw new HttpException(
82+
{ message: Messages.CONNECTION_ID_MISSING },
83+
HttpStatus.BAD_REQUEST,
84+
);
85+
}
86+
if (!fieldName) {
87+
throw new HttpException(
88+
{ message: "Field name is required" },
89+
HttpStatus.BAD_REQUEST,
90+
);
91+
}
92+
if (!rowPrimaryKeyStr) {
93+
throw new HttpException(
94+
{ message: "Row primary key is required" },
95+
HttpStatus.BAD_REQUEST,
96+
);
97+
}
3698

37-
@UseGuards(ConnectionReadGuard)
38-
@ApiOperation({ summary: 'Get pre-signed URL for S3 file download' })
39-
@ApiResponse({
40-
status: 200,
41-
description: 'Pre-signed URL generated successfully.',
42-
})
43-
@ApiQuery({ name: 'tableName', required: true })
44-
@ApiQuery({ name: 'fieldName', required: true })
45-
@ApiQuery({ name: 'fileKey', required: true })
46-
@Get('/s3/file/:connectionId')
47-
async getFileUrl(
48-
@SlugUuid('connectionId') connectionId: string,
49-
@UserId() userId: string,
50-
@MasterPassword() masterPwd: string,
51-
@QueryTableName() tableName: string,
52-
@Query('fieldName') fieldName: string,
53-
@Query('fileKey') fileKey: string,
54-
): Promise<S3FileUrlResponseDs> {
55-
if (!connectionId) {
56-
throw new HttpException({ message: Messages.CONNECTION_ID_MISSING }, HttpStatus.BAD_REQUEST);
57-
}
58-
if (!fieldName) {
59-
throw new HttpException({ message: 'Field name is required' }, HttpStatus.BAD_REQUEST);
60-
}
61-
if (!fileKey) {
62-
throw new HttpException({ message: 'File key is required' }, HttpStatus.BAD_REQUEST);
63-
}
99+
let rowPrimaryKey: Record<string, unknown>;
100+
try {
101+
rowPrimaryKey = JSON.parse(rowPrimaryKeyStr);
102+
} catch {
103+
throw new HttpException(
104+
{ message: "Invalid row primary key format" },
105+
HttpStatus.BAD_REQUEST,
106+
);
107+
}
64108

65-
return await this.getS3FileUrlUseCase.execute(
66-
{
67-
connectionId,
68-
tableName,
69-
fieldName,
70-
fileKey,
71-
userId,
72-
masterPwd,
73-
},
74-
InTransactionEnum.OFF,
75-
);
76-
}
109+
return await this.getS3FileUrlUseCase.execute(
110+
{
111+
connectionId,
112+
tableName,
113+
fieldName,
114+
rowPrimaryKey,
115+
userId,
116+
masterPwd,
117+
},
118+
InTransactionEnum.OFF,
119+
);
120+
}
77121

78-
@UseGuards(ConnectionEditGuard)
79-
@ApiOperation({ summary: 'Get pre-signed URL for S3 file upload' })
80-
@ApiResponse({
81-
status: 201,
82-
description: 'Pre-signed upload URL generated successfully.',
83-
})
84-
@ApiQuery({ name: 'tableName', required: true })
85-
@ApiQuery({ name: 'fieldName', required: true })
86-
@ApiBody({
87-
schema: {
88-
type: 'object',
89-
properties: {
90-
filename: { type: 'string', description: 'Name of the file to upload' },
91-
contentType: { type: 'string', description: 'MIME type of the file' },
92-
},
93-
required: ['filename', 'contentType'],
94-
},
95-
})
96-
@Post('/s3/upload-url/:connectionId')
97-
async getUploadUrl(
98-
@SlugUuid('connectionId') connectionId: string,
99-
@UserId() userId: string,
100-
@MasterPassword() masterPwd: string,
101-
@QueryTableName() tableName: string,
102-
@Query('fieldName') fieldName: string,
103-
@Body() body: { filename: string; contentType: string },
104-
): Promise<S3UploadUrlResponseDs> {
105-
if (!connectionId) {
106-
throw new HttpException({ message: Messages.CONNECTION_ID_MISSING }, HttpStatus.BAD_REQUEST);
107-
}
108-
if (!fieldName) {
109-
throw new HttpException({ message: 'Field name is required' }, HttpStatus.BAD_REQUEST);
110-
}
111-
if (!body.filename) {
112-
throw new HttpException({ message: 'Filename is required' }, HttpStatus.BAD_REQUEST);
113-
}
114-
if (!body.contentType) {
115-
throw new HttpException({ message: 'Content type is required' }, HttpStatus.BAD_REQUEST);
116-
}
122+
@UseGuards(ConnectionEditGuard)
123+
@ApiOperation({ summary: "Get pre-signed URL for S3 file upload" })
124+
@ApiResponse({
125+
status: 201,
126+
description: "Pre-signed upload URL generated successfully.",
127+
})
128+
@ApiQuery({ name: "tableName", required: true })
129+
@ApiQuery({ name: "fieldName", required: true })
130+
@ApiBody({
131+
schema: {
132+
type: "object",
133+
properties: {
134+
filename: { type: "string", description: "Name of the file to upload" },
135+
contentType: { type: "string", description: "MIME type of the file" },
136+
},
137+
required: ["filename", "contentType"],
138+
},
139+
})
140+
@Post("/s3/upload-url/:connectionId")
141+
async getUploadUrl(
142+
@SlugUuid("connectionId") connectionId: string,
143+
@UserId() userId: string,
144+
@MasterPassword() masterPwd: string,
145+
@QueryTableName() tableName: string,
146+
@Query("fieldName") fieldName: string,
147+
@Body() body: { filename: string; contentType: string },
148+
): Promise<S3UploadUrlResponseDs> {
149+
if (!connectionId) {
150+
throw new HttpException(
151+
{ message: Messages.CONNECTION_ID_MISSING },
152+
HttpStatus.BAD_REQUEST,
153+
);
154+
}
155+
if (!fieldName) {
156+
throw new HttpException(
157+
{ message: "Field name is required" },
158+
HttpStatus.BAD_REQUEST,
159+
);
160+
}
161+
if (!body.filename) {
162+
throw new HttpException(
163+
{ message: "Filename is required" },
164+
HttpStatus.BAD_REQUEST,
165+
);
166+
}
167+
if (!body.contentType) {
168+
throw new HttpException(
169+
{ message: "Content type is required" },
170+
HttpStatus.BAD_REQUEST,
171+
);
172+
}
117173

118-
return await this.getS3UploadUrlUseCase.execute(
119-
{
120-
connectionId,
121-
tableName,
122-
fieldName,
123-
userId,
124-
masterPwd,
125-
filename: body.filename,
126-
contentType: body.contentType,
127-
},
128-
InTransactionEnum.OFF,
129-
);
130-
}
174+
return await this.getS3UploadUrlUseCase.execute(
175+
{
176+
connectionId,
177+
tableName,
178+
fieldName,
179+
userId,
180+
masterPwd,
181+
filename: body.filename,
182+
contentType: body.contentType,
183+
},
184+
InTransactionEnum.OFF,
185+
);
186+
}
131187
}

0 commit comments

Comments
 (0)