Skip to content

Commit a0b3818

Browse files
authored
th-89: Implement file upload flow (#187)
* th-81: + frontend part of file service * th-81: + backend part of file service (only schema, repository and service, no aws yet) * th-89: + env variables for S3 * th-89: + S3 Client service * th-89: + Files backend CRUD * th-89: + Files backend validation and multipart data parsing * th-89: + Files upload form * th-89: + Files shared libs * th-89: + Files API Swagger * th-89: + FastifyFileValidationFunction type * th-89: * fixed not displaying error in case of choosing valid and invalid files together * th-89: * Fixed file validation schemas * th-89: * prepared local migrations for being merged into local development * th-89: * Fixed comments * th-89: + Create `files` table migration * th-89: * resolved issues * th-89: * Removed conflicting local migrations for merging remote ones * th-89: * Prettified migration files * th-89: * Fixed comments, remade file-input component to be part of a form, changed form component to support file-input component * th-89: * Fixed package.json * th-89: * Deleted conflicting local migrations * th-89: * Fixed comments * th-89: * Fixed comments * th-281: * Fixed warning
1 parent 6bb4188 commit a0b3818

File tree

124 files changed

+4917
-177
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

124 files changed

+4917
-177
lines changed

backend/.env.example

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@ JWT_SECRET=very_secret_secret
1111
JWT_ISSUER=jwt_issuer
1212
JWT_ACCESS_LIFETIME=24h
1313

14+
#
15+
# S3
16+
#
17+
AWS_ACCESS_KEY_ID=YOUR_KEY
18+
AWS_SECRET_ACCESS_KEY=YOUR_SECRET_KEY
19+
S3_REGION=REGION
20+
BUCKET_NAME=BUCKET
21+
1422
#
1523
# SENDGRID
1624
#

backend/package.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,19 @@
2121
"start": "node index.js"
2222
},
2323
"devDependencies": {
24+
"@types/mime-types": "2.1.1",
25+
"@types/uuid": "9.0.4",
2426
"nodemon": "3.0.1",
2527
"ts-node": "10.9.1",
2628
"ts-paths-esm-loader": "1.4.3",
2729
"tsconfig-paths": "4.2.0"
2830
},
2931
"dependencies": {
32+
"@aws-sdk/client-s3": "3.400.0",
33+
"@aws-sdk/s3-request-presigner": "3.400.0",
3034
"@fastify/auth": "4.3.0",
3135
"@fastify/cors": "8.3.0",
36+
"@fastify/multipart": "7.7.3",
3237
"@fastify/static": "6.10.2",
3338
"@fastify/swagger": "8.9.0",
3439
"@fastify/swagger-ui": "1.9.3",
@@ -52,6 +57,7 @@
5257
"pino-pretty": "10.2.0",
5358
"postgres": "3.3.5",
5459
"socket.io": "4.7.2",
55-
"swagger-jsdoc": "6.2.8"
60+
"swagger-jsdoc": "6.2.8",
61+
"mime-types": "2.1.35"
5662
}
5763
}

backend/src/index.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
import { type AuthStrategy } from './packages/auth/auth.js';
2+
import { type FastifyFileValidationFunction } from './packages/files/files.js';
3+
import { type FilesValidationStrategy } from './packages/files/libs/enums/enums.js';
4+
import { type MultipartParsedFile } from './packages/files/libs/types/types.js';
25
import { type UserEntityObjectWithGroupT } from './packages/users/users.js';
36

47
declare module 'fastify' {
@@ -7,9 +10,11 @@ declare module 'fastify' {
710
[AuthStrategy.VERIFY_JWT]: FastifyAuthFunction;
811
[AuthStrategy.VERIFY_BUSINESS_GROUP]: FastifyAuthFunction;
912
[AuthStrategy.VERIFY_DRIVER_GROUP]: FastifyAuthFunction;
13+
[FilesValidationStrategy.BASIC]: FastifyFileValidationFunction;
1014
}
1115

1216
interface FastifyRequest {
1317
user: UserEntityObjectWithGroupT;
18+
parsedFiles: MultipartParsedFile[];
1419
}
1520
}

backend/src/libs/exceptions/exceptions.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
export { DatabaseConnectionError } from './database/database.js';
2+
export {
3+
FileTransactionError,
4+
FileValidatorError,
5+
InvalidFileError,
6+
} from './file/file.js';
27
export { MailerConnectionError } from './mailer/mailer.js';
38
export { NotFoundError } from './not-found-error/not-found-error.exception.js';
49
export {
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { FILE_TRANSACTION_ERROR_MESSAGE } from '~/packages/files/libs/constants/constants.js';
2+
3+
import { HttpCode } from '../../enums/enums.js';
4+
import { HttpError } from '../exceptions.js';
5+
6+
type Constructor = {
7+
message?: string;
8+
cause?: unknown;
9+
};
10+
11+
class FileTransactionError extends HttpError {
12+
public constructor({ message, cause }: Constructor) {
13+
super({
14+
message: message ?? FILE_TRANSACTION_ERROR_MESSAGE,
15+
status: HttpCode.BAD_REQUEST,
16+
cause,
17+
});
18+
}
19+
}
20+
21+
export { FileTransactionError };
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { AppErrorMessage, HttpCode } from '../../enums/enums.js';
2+
import { HttpError } from '../exceptions.js';
3+
4+
type Constructor = {
5+
message?: string;
6+
cause?: unknown;
7+
};
8+
9+
class FileValidatorError extends HttpError {
10+
public constructor({ message, cause }: Constructor) {
11+
super({
12+
message: message ?? AppErrorMessage.INVALID_FILE_INPUT_CONFIG,
13+
status: HttpCode.BAD_REQUEST,
14+
cause,
15+
});
16+
}
17+
}
18+
19+
export { FileValidatorError };
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export { FileTransactionError } from './file-transaction.exception.js';
2+
export { FileValidatorError } from './file-validator.exception.js';
3+
export { InvalidFileError } from './invalid-file.exception.js';
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { HttpCode } from '../../enums/enums.js';
2+
import { HttpError } from '../exceptions.js';
3+
4+
type Constructor = {
5+
message: string;
6+
cause?: unknown;
7+
};
8+
9+
class InvalidFileError extends HttpError {
10+
public constructor({ message, cause }: Constructor) {
11+
super({
12+
message,
13+
status: HttpCode.BAD_REQUEST,
14+
cause,
15+
});
16+
}
17+
}
18+
19+
export { InvalidFileError };

backend/src/libs/packages/config/config.package.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,40 @@ class Config implements IConfig {
6767
default: null,
6868
},
6969
},
70+
AWS: {
71+
ACCESS_KEY_ID: {
72+
doc: 'Access key id',
73+
format: String,
74+
env: 'AWS_ACCESS_KEY_ID',
75+
default: null,
76+
},
77+
SECRET_ACCESS_KEY: {
78+
doc: 'Secret access key',
79+
format: String,
80+
env: 'AWS_SECRET_ACCESS_KEY',
81+
default: null,
82+
},
83+
S3: {
84+
BUCKET_NAME: {
85+
doc: 'Bucket name',
86+
format: String,
87+
env: 'BUCKET_NAME',
88+
default: null,
89+
},
90+
REGION: {
91+
doc: 'Service region',
92+
format: String,
93+
env: 'S3_REGION',
94+
default: null,
95+
},
96+
SIGNED_URL_EXPIRES_IN: {
97+
doc: 'Number of seconds a signed URL expires in',
98+
format: String,
99+
env: 'SIGNED_URL_EXPIRES_IN',
100+
default: null,
101+
},
102+
},
103+
},
70104
JWT: {
71105
SECRET: {
72106
doc: 'Secret key for token generation',

backend/src/libs/packages/config/libs/types/environment-schema.type.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,15 @@ type EnvironmentSchema = {
66
PORT: number;
77
ENVIRONMENT: ValueOf<typeof AppEnvironment>;
88
};
9+
AWS: {
10+
SECRET_ACCESS_KEY: string;
11+
ACCESS_KEY_ID: string;
12+
S3: {
13+
BUCKET_NAME: string;
14+
REGION: string;
15+
SIGNED_URL_EXPIRES_IN: number;
16+
};
17+
};
918
JWT: {
1019
SECRET: string;
1120
ISSUER: string;

0 commit comments

Comments
 (0)