mirror of
https://github.com/hknsh/project-knedita.git
synced 2024-11-28 17:41:15 +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:
|
This will start the following services:
|
||||||
- **PostgreSQL**
|
|
||||||
- **Redis**
|
- **PostgreSQL**
|
||||||
- **MinIO**
|
- **Redis**
|
||||||
|
- **MinIO**
|
||||||
|
|
||||||
Apply the migrations to the database with the following command:
|
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
|
## 🗄️ Stack
|
||||||
|
|
||||||
This back-end uses the following stack:
|
This back-end uses the following stack:
|
||||||
- **Docker**
|
|
||||||
- **Fastify**
|
- **Fastify**
|
||||||
- **MinIO**
|
- **MinIO**
|
||||||
- **NestJS**
|
- **NestJS**
|
||||||
- **Passport**
|
- **PostgreSQL**
|
||||||
- **PostgreSQL**
|
- **Prisma**
|
||||||
- **Prisma**
|
- **Redis**
|
||||||
- **Redis**
|
- **Swagger**
|
||||||
- **Swagger**
|
- **Typescript**
|
||||||
- **Typescript**
|
|
||||||
|
|
||||||
## License
|
## 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",
|
"@nestjs/throttler": "^5.1.1",
|
||||||
"@prisma/client": "^5.9.1",
|
"@prisma/client": "^5.9.1",
|
||||||
"bcrypt": "^5.1.1",
|
"bcrypt": "^5.1.1",
|
||||||
|
"dotenv": "^16.4.5",
|
||||||
|
"dotenv-expand": "^11.0.6",
|
||||||
"file-type": "^19.0.0",
|
"file-type": "^19.0.0",
|
||||||
"ioredis": "^5.3.2",
|
"ioredis": "^5.3.2",
|
||||||
"nestjs-s3": "^2.0.1",
|
"nestjs-s3": "^2.0.1",
|
||||||
|
@ -53,7 +55,8 @@
|
||||||
"passport-local": "^1.0.0",
|
"passport-local": "^1.0.0",
|
||||||
"reflect-metadata": "^0.2.2",
|
"reflect-metadata": "^0.2.2",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"sharp": "^0.33.2"
|
"sharp": "^0.33.2",
|
||||||
|
"tstl": "^3.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@biomejs/biome": "1.5.3",
|
"@biomejs/biome": "1.5.3",
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { ThrottlerStorageRedisService } from "nestjs-throttler-storage-redis";
|
||||||
import { ZodValidationPipe } from "nestjs-zod";
|
import { ZodValidationPipe } from "nestjs-zod";
|
||||||
import { AuthModule } from "./auth/auth.module";
|
import { AuthModule } from "./auth/auth.module";
|
||||||
import { JwtAuthGuard } from "./auth/jwt-auth.guard";
|
import { JwtAuthGuard } from "./auth/jwt-auth.guard";
|
||||||
|
import { Configuration } from "./configuration";
|
||||||
import { KweeksModule } from "./kweeks/kweeks.module";
|
import { KweeksModule } from "./kweeks/kweeks.module";
|
||||||
import { UserModule } from "./users/users.module";
|
import { UserModule } from "./users/users.module";
|
||||||
|
|
||||||
|
@ -20,20 +21,18 @@ import { UserModule } from "./users/users.module";
|
||||||
}),
|
}),
|
||||||
ThrottlerModule.forRoot({
|
ThrottlerModule.forRoot({
|
||||||
throttlers: [{ limit: 10, ttl: 60000 }],
|
throttlers: [{ limit: 10, ttl: 60000 }],
|
||||||
storage: new ThrottlerStorageRedisService(
|
storage: new ThrottlerStorageRedisService(Configuration.REDIS_URL()),
|
||||||
`redis://:${process.env.REDIS_PASSWORD}@${process.env.REDIS_HOST}:${process.env.REDIS_PORT}/0`,
|
|
||||||
),
|
|
||||||
}),
|
}),
|
||||||
KweeksModule,
|
KweeksModule,
|
||||||
FastifyMulterModule,
|
FastifyMulterModule,
|
||||||
S3Module.forRoot({
|
S3Module.forRoot({
|
||||||
config: {
|
config: {
|
||||||
credentials: {
|
credentials: {
|
||||||
accessKeyId: process.env.MINIO_ROOT_USER, // CHANGE WHEN PRODUCTION TO S3
|
accessKeyId: Configuration.MINIO_ROOT_USER(),
|
||||||
secretAccessKey: process.env.MINIO_ROOT_PASSWORD,
|
secretAccessKey: Configuration.MINIO_ROOT_PASSWORD(),
|
||||||
},
|
},
|
||||||
region: "us-east-1",
|
region: "us-east-1",
|
||||||
endpoint: process.env.MINIO_ENDPOINT,
|
endpoint: Configuration.MINIO_ENDPOINT(),
|
||||||
forcePathStyle: true,
|
forcePathStyle: true,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { Module } from "@nestjs/common";
|
import { Module } from "@nestjs/common";
|
||||||
import { JwtModule } from "@nestjs/jwt";
|
import { JwtModule } from "@nestjs/jwt";
|
||||||
import { PassportModule } from "@nestjs/passport";
|
import { PassportModule } from "@nestjs/passport";
|
||||||
|
import { Configuration } from "src/configuration";
|
||||||
import { UserModule } from "src/users/users.module";
|
import { UserModule } from "src/users/users.module";
|
||||||
import { AuthController } from "./auth.controller";
|
import { AuthController } from "./auth.controller";
|
||||||
import { AuthService } from "./auth.service";
|
import { AuthService } from "./auth.service";
|
||||||
|
@ -13,7 +14,7 @@ import { LocalStrategy } from "./local.strategy";
|
||||||
UserModule,
|
UserModule,
|
||||||
PassportModule,
|
PassportModule,
|
||||||
JwtModule.register({
|
JwtModule.register({
|
||||||
secret: process.env.JWT_ACCESS_SECRET,
|
secret: Configuration.JWT_ACCESS_SECRET(),
|
||||||
signOptions: { expiresIn: "1d" }, // TODO: add refresh tokens
|
signOptions: { expiresIn: "1d" }, // TODO: add refresh tokens
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { Injectable } from "@nestjs/common";
|
import { Injectable } from "@nestjs/common";
|
||||||
import { PassportStrategy } from "@nestjs/passport";
|
import { PassportStrategy } from "@nestjs/passport";
|
||||||
import { ExtractJwt, Strategy } from "passport-jwt";
|
import { ExtractJwt, Strategy } from "passport-jwt";
|
||||||
|
import { Configuration } from "src/configuration";
|
||||||
|
|
||||||
type Payload = {
|
type Payload = {
|
||||||
displayName: string;
|
displayName: string;
|
||||||
|
@ -14,7 +15,7 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
|
||||||
super({
|
super({
|
||||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||||
ignoreExpiration: false,
|
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 { NestFactory } from "@nestjs/core";
|
||||||
import {
|
import {
|
||||||
FastifyAdapter,
|
FastifyAdapter,
|
||||||
|
@ -7,6 +7,7 @@ import {
|
||||||
import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger";
|
import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger";
|
||||||
import { patchNestJsSwagger } from "nestjs-zod";
|
import { patchNestJsSwagger } from "nestjs-zod";
|
||||||
import { AppModule } from "./app.module";
|
import { AppModule } from "./app.module";
|
||||||
|
import { Configuration } from "./configuration";
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create<NestFastifyApplication>(
|
const app = await NestFactory.create<NestFastifyApplication>(
|
||||||
|
@ -44,6 +45,6 @@ async function bootstrap() {
|
||||||
|
|
||||||
await app.register(helmet);
|
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();
|
bootstrap();
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { File } from "@nest-lab/fastify-multer";
|
||||||
import { Injectable, InternalServerErrorException } from "@nestjs/common";
|
import { Injectable, InternalServerErrorException } from "@nestjs/common";
|
||||||
import { InjectS3, S3 } from "nestjs-s3";
|
import { InjectS3, S3 } from "nestjs-s3";
|
||||||
import sharp from "sharp";
|
import sharp from "sharp";
|
||||||
|
import { Configuration } from "src/configuration";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class S3Service {
|
export class S3Service {
|
||||||
|
@ -18,7 +19,7 @@ export class S3Service {
|
||||||
.toBuffer();
|
.toBuffer();
|
||||||
|
|
||||||
const params: PutObjectCommandInput = {
|
const params: PutObjectCommandInput = {
|
||||||
Bucket: process.env.MINIO_DEFAULT_BUCKETS,
|
Bucket: Configuration.MINIO_DEFAULT_BUCKETS(),
|
||||||
Key: `profile_images/${userID}.webp`,
|
Key: `profile_images/${userID}.webp`,
|
||||||
Body: compressedBuffer,
|
Body: compressedBuffer,
|
||||||
ContentType: "image/webp",
|
ContentType: "image/webp",
|
||||||
|
@ -29,7 +30,7 @@ export class S3Service {
|
||||||
const { ETag } = await this.s3.send(new PutObjectCommand(params));
|
const { ETag } = await this.s3.send(new PutObjectCommand(params));
|
||||||
|
|
||||||
if (ETag !== null) {
|
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(
|
throw new InternalServerErrorException(
|
||||||
|
@ -64,13 +65,13 @@ export class S3Service {
|
||||||
const Key = `posts/${id}/${index}.webp`;
|
const Key = `posts/${id}/${index}.webp`;
|
||||||
|
|
||||||
const params: PutObjectCommandInput = {
|
const params: PutObjectCommandInput = {
|
||||||
Bucket: process.env.MINIO_DEFAULT_BUCKETS,
|
Bucket: Configuration.MINIO_DEFAULT_BUCKETS(),
|
||||||
Key,
|
Key,
|
||||||
Body: buffer,
|
Body: buffer,
|
||||||
ContentType: "image/webp",
|
ContentType: "image/webp",
|
||||||
};
|
};
|
||||||
|
|
||||||
await this.s3.send(new PutObjectCommand(params));
|
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": {
|
"compilerOptions": {
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
"emitDecoratorMetadata": true,
|
"emitDecoratorMetadata": true,
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
|
|
Loading…
Reference in a new issue