mirror of
https://github.com/hknsh/project-knedita.git
synced 2024-11-28 09:31:16 +00:00
feat: safer environment variables, updated packages
This commit is contained in:
parent
2eaa2294da
commit
47b50415ea
11 changed files with 3565 additions and 2215 deletions
26
README.md
26
README.md
|
@ -29,9 +29,10 @@ $ npm run docker:db
|
|||
```
|
||||
|
||||
This will start the following services:
|
||||
- **PostgreSQL**
|
||||
- **Redis**
|
||||
- **MinIO**
|
||||
|
||||
- **PostgreSQL**
|
||||
- **Redis**
|
||||
- **MinIO**
|
||||
|
||||
Apply the migrations to the database with the following command:
|
||||
|
||||
|
@ -58,16 +59,15 @@ This will start all the previous services and the back-end image.
|
|||
## 🗄️ Stack
|
||||
|
||||
This back-end uses the following stack:
|
||||
- **Docker**
|
||||
- **Fastify**
|
||||
- **MinIO**
|
||||
- **NestJS**
|
||||
- **Passport**
|
||||
- **PostgreSQL**
|
||||
- **Prisma**
|
||||
- **Redis**
|
||||
- **Swagger**
|
||||
- **Typescript**
|
||||
|
||||
- **Fastify**
|
||||
- **MinIO**
|
||||
- **NestJS**
|
||||
- **PostgreSQL**
|
||||
- **Prisma**
|
||||
- **Redis**
|
||||
- **Swagger**
|
||||
- **Typescript**
|
||||
|
||||
## License
|
||||
|
||||
|
|
5619
package-lock.json
generated
5619
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -43,6 +43,8 @@
|
|||
"@nestjs/throttler": "^5.1.1",
|
||||
"@prisma/client": "^5.9.1",
|
||||
"bcrypt": "^5.1.1",
|
||||
"dotenv": "^16.4.5",
|
||||
"dotenv-expand": "^11.0.6",
|
||||
"file-type": "^19.0.0",
|
||||
"ioredis": "^5.3.2",
|
||||
"nestjs-s3": "^2.0.1",
|
||||
|
@ -53,7 +55,8 @@
|
|||
"passport-local": "^1.0.0",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.1",
|
||||
"sharp": "^0.33.2"
|
||||
"sharp": "^0.33.2",
|
||||
"tstl": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "1.5.3",
|
||||
|
|
|
@ -8,6 +8,7 @@ import { ThrottlerStorageRedisService } from "nestjs-throttler-storage-redis";
|
|||
import { ZodValidationPipe } from "nestjs-zod";
|
||||
import { AuthModule } from "./auth/auth.module";
|
||||
import { JwtAuthGuard } from "./auth/jwt-auth.guard";
|
||||
import { Configuration } from "./configuration";
|
||||
import { KweeksModule } from "./kweeks/kweeks.module";
|
||||
import { UserModule } from "./users/users.module";
|
||||
|
||||
|
@ -20,20 +21,18 @@ import { UserModule } from "./users/users.module";
|
|||
}),
|
||||
ThrottlerModule.forRoot({
|
||||
throttlers: [{ limit: 10, ttl: 60000 }],
|
||||
storage: new ThrottlerStorageRedisService(
|
||||
`redis://:${process.env.REDIS_PASSWORD}@${process.env.REDIS_HOST}:${process.env.REDIS_PORT}/0`,
|
||||
),
|
||||
storage: new ThrottlerStorageRedisService(Configuration.REDIS_URL()),
|
||||
}),
|
||||
KweeksModule,
|
||||
FastifyMulterModule,
|
||||
S3Module.forRoot({
|
||||
config: {
|
||||
credentials: {
|
||||
accessKeyId: process.env.MINIO_ROOT_USER, // CHANGE WHEN PRODUCTION TO S3
|
||||
secretAccessKey: process.env.MINIO_ROOT_PASSWORD,
|
||||
accessKeyId: Configuration.MINIO_ROOT_USER(),
|
||||
secretAccessKey: Configuration.MINIO_ROOT_PASSWORD(),
|
||||
},
|
||||
region: "us-east-1",
|
||||
endpoint: process.env.MINIO_ENDPOINT,
|
||||
endpoint: Configuration.MINIO_ENDPOINT(),
|
||||
forcePathStyle: true,
|
||||
},
|
||||
}),
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Module } from "@nestjs/common";
|
||||
import { JwtModule } from "@nestjs/jwt";
|
||||
import { PassportModule } from "@nestjs/passport";
|
||||
import { Configuration } from "src/configuration";
|
||||
import { UserModule } from "src/users/users.module";
|
||||
import { AuthController } from "./auth.controller";
|
||||
import { AuthService } from "./auth.service";
|
||||
|
@ -13,7 +14,7 @@ import { LocalStrategy } from "./local.strategy";
|
|||
UserModule,
|
||||
PassportModule,
|
||||
JwtModule.register({
|
||||
secret: process.env.JWT_ACCESS_SECRET,
|
||||
secret: Configuration.JWT_ACCESS_SECRET(),
|
||||
signOptions: { expiresIn: "1d" }, // TODO: add refresh tokens
|
||||
}),
|
||||
],
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Injectable } from "@nestjs/common";
|
||||
import { PassportStrategy } from "@nestjs/passport";
|
||||
import { ExtractJwt, Strategy } from "passport-jwt";
|
||||
import { Configuration } from "src/configuration";
|
||||
|
||||
type Payload = {
|
||||
displayName: string;
|
||||
|
@ -14,7 +15,7 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
|
|||
super({
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
ignoreExpiration: false,
|
||||
secretOrKey: process.env.JWT_ACCESS_SECRET,
|
||||
secretOrKey: Configuration.JWT_ACCESS_SECRET(),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
14
src/configuration.ts
Normal file
14
src/configuration.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { Environment } from "./environment";
|
||||
|
||||
export namespace Configuration {
|
||||
export const NODE_ENV = () => Environment.env.NODE_ENV;
|
||||
export const SERVER_HOST = () => Environment.env.SERVER_HOST;
|
||||
export const SERVER_PORT = () => Environment.env.SERVER_PORT;
|
||||
export const JWT_ACCESS_SECRET = () => Environment.env.JWT_ACCESS_SECRET;
|
||||
export const REDIS_URL = () => Environment.env.REDIS_URL;
|
||||
export const MINIO_ROOT_USER = () => Environment.env.MINIO_ROOT_USER;
|
||||
export const MINIO_ROOT_PASSWORD = () => Environment.env.MINIO_ROOT_PASSWORD;
|
||||
export const MINIO_DEFAULT_BUCKETS = () =>
|
||||
Environment.env.MINIO_DEFAULT_BUCKETS;
|
||||
export const MINIO_ENDPOINT = () => Environment.env.MINIO_ENDPOINT;
|
||||
}
|
78
src/environment.ts
Normal file
78
src/environment.ts
Normal file
|
@ -0,0 +1,78 @@
|
|||
import dotenv from "dotenv";
|
||||
import dotEnvExpand from "dotenv-expand";
|
||||
import { Singleton } from "tstl";
|
||||
|
||||
import { z } from "nestjs-zod/z";
|
||||
|
||||
/**
|
||||
* Global variables of the server.
|
||||
*/
|
||||
export class Environment {
|
||||
public static get env(): IEnvironment {
|
||||
return environments.get();
|
||||
}
|
||||
|
||||
public static get node_env(): NodeEnv {
|
||||
if (nodeEnvWrapper.value === undefined || nodeEnvWrapper.value === null) {
|
||||
nodeEnvWrapper.value = environments.get().NODE_ENV;
|
||||
}
|
||||
return nodeEnvWrapper.value;
|
||||
}
|
||||
|
||||
public setMode(mode: NodeEnv): void {
|
||||
if (!["dev", "prod"].includes(mode)) {
|
||||
throw new Error("Invalid NODE_ENV value, expected 'dev' or 'prod'");
|
||||
}
|
||||
nodeEnvWrapper.value = mode;
|
||||
}
|
||||
}
|
||||
|
||||
const EnvironmentSchema = z.object({
|
||||
NODE_ENV: z.enum(["dev", "prod"]),
|
||||
|
||||
POSTGRES_HOST: z.string(),
|
||||
POSTGRES_DB: z.string(),
|
||||
POSTGRES_USER: z.string(),
|
||||
POSTGRES_PASSWORD: z.string(),
|
||||
POSTGRES_PORT: z.string().regex(/^[0-9]+$/),
|
||||
|
||||
DATABASE_URL: z.string(),
|
||||
|
||||
REDIS_HOST: z.string(),
|
||||
REDIS_PORT: z.string().regex(/^[0-9]+$/),
|
||||
REDIS_PASSWORD: z.string(),
|
||||
REDIS_URL: z.string(),
|
||||
|
||||
SERVER_PORT: z.string().regex(/^[0-9]+$/),
|
||||
SERVER_HOST: z.string(),
|
||||
|
||||
JWT_ACCESS_SECRET: z.string(),
|
||||
|
||||
MINIO_ROOT_USER: z.string(),
|
||||
MINIO_ROOT_PASSWORD: z.string(),
|
||||
MINIO_DEFAULT_BUCKETS: z.string(),
|
||||
MINIO_ENDPOINT: z.string(),
|
||||
});
|
||||
|
||||
type IEnvironment = z.infer<typeof EnvironmentSchema>;
|
||||
type NodeEnv = "dev" | "prod";
|
||||
|
||||
interface INodeEnv {
|
||||
value?: NodeEnv;
|
||||
}
|
||||
|
||||
const nodeEnvWrapper: INodeEnv = {};
|
||||
|
||||
const environments = new Singleton(() => {
|
||||
const env = dotenv.config();
|
||||
dotEnvExpand.expand(env);
|
||||
|
||||
const parsedEnv = EnvironmentSchema.safeParse(process.env);
|
||||
|
||||
if (!parsedEnv.success) {
|
||||
const errors = parsedEnv.error.format();
|
||||
throw new Error(`Environment validation failed: ${JSON.stringify(errors)}`);
|
||||
}
|
||||
|
||||
return parsedEnv.data;
|
||||
});
|
|
@ -1,4 +1,4 @@
|
|||
import * as helmet from "@fastify/helmet";
|
||||
import helmet from "@fastify/helmet";
|
||||
import { NestFactory } from "@nestjs/core";
|
||||
import {
|
||||
FastifyAdapter,
|
||||
|
@ -7,6 +7,7 @@ import {
|
|||
import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger";
|
||||
import { patchNestJsSwagger } from "nestjs-zod";
|
||||
import { AppModule } from "./app.module";
|
||||
import { Configuration } from "./configuration";
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create<NestFastifyApplication>(
|
||||
|
@ -44,6 +45,6 @@ async function bootstrap() {
|
|||
|
||||
await app.register(helmet);
|
||||
|
||||
await app.listen(process.env.SERVER_PORT, process.env.SERVER_HOST);
|
||||
await app.listen(Configuration.SERVER_PORT(), Configuration.SERVER_HOST);
|
||||
}
|
||||
bootstrap();
|
||||
|
|
|
@ -3,6 +3,7 @@ import { File } from "@nest-lab/fastify-multer";
|
|||
import { Injectable, InternalServerErrorException } from "@nestjs/common";
|
||||
import { InjectS3, S3 } from "nestjs-s3";
|
||||
import sharp from "sharp";
|
||||
import { Configuration } from "src/configuration";
|
||||
|
||||
@Injectable()
|
||||
export class S3Service {
|
||||
|
@ -18,7 +19,7 @@ export class S3Service {
|
|||
.toBuffer();
|
||||
|
||||
const params: PutObjectCommandInput = {
|
||||
Bucket: process.env.MINIO_DEFAULT_BUCKETS,
|
||||
Bucket: Configuration.MINIO_DEFAULT_BUCKETS(),
|
||||
Key: `profile_images/${userID}.webp`,
|
||||
Body: compressedBuffer,
|
||||
ContentType: "image/webp",
|
||||
|
@ -29,7 +30,7 @@ export class S3Service {
|
|||
const { ETag } = await this.s3.send(new PutObjectCommand(params));
|
||||
|
||||
if (ETag !== null) {
|
||||
return `${process.env.MINIO_ENDPOINT}/${process.env.MINIO_DEFAULT_BUCKETS}/profile_images/${userID}.webp`;
|
||||
return `${Configuration.MINIO_ENDPOINT}/${Configuration.MINIO_DEFAULT_BUCKETS}/profile_images/${userID}.webp`;
|
||||
}
|
||||
|
||||
throw new InternalServerErrorException(
|
||||
|
@ -64,13 +65,13 @@ export class S3Service {
|
|||
const Key = `posts/${id}/${index}.webp`;
|
||||
|
||||
const params: PutObjectCommandInput = {
|
||||
Bucket: process.env.MINIO_DEFAULT_BUCKETS,
|
||||
Bucket: Configuration.MINIO_DEFAULT_BUCKETS(),
|
||||
Key,
|
||||
Body: buffer,
|
||||
ContentType: "image/webp",
|
||||
};
|
||||
|
||||
await this.s3.send(new PutObjectCommand(params));
|
||||
return `${process.env.MINIO_ENDPOINT}/${process.env.MINIO_DEFAULT_BUCKETS}/${Key}`;
|
||||
return `${Configuration.MINIO_ENDPOINT}/${Configuration.MINIO_DEFAULT_BUCKETS}/${Key}`;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"esModuleInterop": true,
|
||||
"removeComments": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
|
|
Loading…
Reference in a new issue