Skip to content

Commit 8e64154

Browse files
committed
Merge branch 'main' into Feat-add-token-expire-handling
2 parents f208df9 + 44f21d9 commit 8e64154

17 files changed

+17886
-13990
lines changed

backend/.env.example

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,13 @@ GITHUB_CLIENT_ID="GITHUB_CLIENT_ID"
4040
GITHUB_CLIENT_SECRET="GITHUB_CLIENT_SECRET"
4141
GITHUB_WEBHOOK_SECRET="GITHUB_WEBHOOK_SECRET"
4242
CALLBACK="CALLBACK"
43+
44+
# Database Configuration
45+
USE_REMOTE_DB=false
46+
DB_TYPE=
47+
DB_HOST=
48+
DB_PORT=0
49+
DB_USERNAME=
50+
DB_NAME=
51+
DB_REGION=us-east-2
52+

backend/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
"@types/toposort": "^2.0.7",
5050
"archiver": "^7.0.1",
5151
"axios": "^1.8.3",
52+
"aws-sdk": "^2.1692.0",
5253
"bcrypt": "^5.1.1",
5354
"class-transformer": "^0.5.1",
5455
"class-validator": "^0.14.1",
@@ -69,6 +70,7 @@
6970
"octokit": "^4.1.2",
7071
"openai": "^4.77.0",
7172
"p-queue-es5": "^6.0.2",
73+
"pg": "^8.14.1",
7274
"reflect-metadata": "^0.2.2",
7375
"rxjs": "^7.8.1",
7476
"simple-git": "^3.27.0",
@@ -105,4 +107,4 @@
105107
"tsconfig-paths": "^4.2.0",
106108
"typescript": "^5.1.3"
107109
}
108-
}
110+
}

backend/src/app.module.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
22
import { Module } from '@nestjs/common';
3-
import { ConfigModule } from '@nestjs/config';
3+
import { ConfigModule, ConfigService } from '@nestjs/config';
4+
import { EnvironmentVariables } from './config/env.validation';
45
import { GraphQLModule } from '@nestjs/graphql';
56
import { TypeOrmModule } from '@nestjs/typeorm';
67
import { join } from 'path';
@@ -17,14 +18,17 @@ import { LoggingInterceptor } from 'src/interceptor/LoggingInterceptor';
1718
import { PromptToolModule } from './prompt-tool/prompt-tool.module';
1819
import { MailModule } from './mail/mail.module';
1920
import { GitHubModule } from './github/github.module';
21+
import { AppConfigService } from './config/config.service';
22+
import { getDatabaseConfig } from './database.config';
2023

21-
// TODO(Sma1lboy): move to a separate file
22-
function isProduction(): boolean {
23-
return process.env.NODE_ENV === 'production';
24-
}
2524
@Module({
2625
imports: [
27-
ConfigModule.forRoot({ isGlobal: true }),
26+
ConfigModule.forRoot({
27+
isGlobal: true,
28+
validate: (config: Record<string, unknown>) => {
29+
return Object.assign(new EnvironmentVariables(), config);
30+
},
31+
}),
2832
GraphQLModule.forRoot<ApolloDriverConfig>({
2933
driver: ApolloDriver,
3034
autoSchemaFile: join(process.cwd(), '../frontend/src/graphql/schema.gql'),
@@ -36,11 +40,11 @@ function isProduction(): boolean {
3640
},
3741
context: ({ req, res }) => ({ req, res }),
3842
}),
39-
TypeOrmModule.forRoot({
40-
type: 'sqlite',
41-
database: join(process.cwd(), './database.db'),
42-
synchronize: !isProduction(),
43-
entities: [__dirname + '/**/*.model{.ts,.js}'],
43+
TypeOrmModule.forRootAsync({
44+
imports: [ConfigModule],
45+
useFactory: (config: ConfigService<EnvironmentVariables>) =>
46+
getDatabaseConfig(new AppConfigService(config)),
47+
inject: [ConfigService],
4448
}),
4549
InitModule,
4650
UserModule,
@@ -55,10 +59,12 @@ function isProduction(): boolean {
5559
],
5660
providers: [
5761
AppResolver,
62+
AppConfigService,
5863
{
5964
provide: APP_INTERCEPTOR,
6065
useClass: LoggingInterceptor,
6166
},
6267
],
68+
exports: [AppConfigService],
6369
})
6470
export class AppModule {}

backend/src/auth/auth.module.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,26 @@ import { TypeOrmModule } from '@nestjs/typeorm';
33
import { Menu } from './menu/menu.model';
44
import { JwtModule } from '@nestjs/jwt';
55
import { Role } from './role/role.model';
6-
import { ConfigModule, ConfigService } from '@nestjs/config';
76
import { AuthService } from './auth.service';
87
import { User } from 'src/user/user.model';
8+
import { AppConfigService } from 'src/config/config.service';
99
import { AuthResolver } from './auth.resolver';
1010
import { RefreshToken } from './refresh-token/refresh-token.model';
1111
import { JwtCacheModule } from 'src/jwt-cache/jwt-cache.module';
1212
import { MailModule } from 'src/mail/mail.module';
13+
import { AppConfigModule } from 'src/config/config.module';
1314

1415
@Module({
1516
imports: [
16-
ConfigModule,
17+
AppConfigModule,
1718
TypeOrmModule.forFeature([Role, Menu, User, RefreshToken]),
1819
JwtModule.registerAsync({
19-
imports: [ConfigModule],
20-
useFactory: async (configService: ConfigService) => ({
21-
secret: configService.get<string>('JWT_SECRET'),
20+
imports: [AppConfigModule],
21+
useFactory: async (config: AppConfigService) => ({
22+
secret: config.jwtSecret,
2223
signOptions: { expiresIn: '24h' },
2324
}),
24-
inject: [ConfigService],
25+
inject: [AppConfigService],
2526
}),
2627
JwtCacheModule,
2728
MailModule,

backend/src/auth/auth.service.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
NotFoundException,
66
UnauthorizedException,
77
} from '@nestjs/common';
8-
import { ConfigService } from '@nestjs/config';
8+
import { AppConfigService } from 'src/config/config.service';
99
import { JwtService } from '@nestjs/jwt';
1010
import { InjectRepository } from '@nestjs/typeorm';
1111
import { LoginUserInput } from 'src/user/dto/login-user.input';
@@ -34,7 +34,7 @@ export class AuthService {
3434
private userRepository: Repository<User>,
3535
private jwtService: JwtService,
3636
private jwtCacheService: JwtCacheService,
37-
private configService: ConfigService,
37+
private configService: AppConfigService,
3838
private mailService: MailService,
3939
@InjectRepository(Menu)
4040
private menuRepository: Repository<Menu>,
@@ -43,10 +43,7 @@ export class AuthService {
4343
@InjectRepository(RefreshToken)
4444
private refreshTokenRepository: Repository<RefreshToken>,
4545
) {
46-
// Read the MAIL_ENABLED environment variable, default to 'true'
47-
this.isMailEnabled =
48-
this.configService.get<string>('MAIL_ENABLED', 'false').toLowerCase() ===
49-
'true';
46+
this.isMailEnabled = this.configService.isMailEnabled;
5047
}
5148

5249
async confirmEmail(token: string): Promise<EmailConfirmationResponse> {
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { CreateDateColumn, UpdateDateColumn, ColumnType } from 'typeorm';
2+
3+
/**
4+
* Universal date column options
5+
*/
6+
interface UniversalDateOptions {
7+
nullable?: boolean;
8+
update?: boolean;
9+
}
10+
11+
/**
12+
* Get database column type based on environment
13+
* @returns 'timestamp' for remote database, 'datetime' for local
14+
*/
15+
function getDateColumnType(): ColumnType {
16+
// FIXME: optimize this logic to use a config file or environment variable
17+
// const useRemoteDb = process.env.USE_REMOTE_DB === 'true';
18+
// use timestamp for remote database
19+
return 'datetime' as ColumnType;
20+
}
21+
22+
/**
23+
* Universal create date column decorator that handles both remote and local databases
24+
*/
25+
export function UniversalCreateDateColumn(options: UniversalDateOptions = {}) {
26+
return CreateDateColumn({
27+
type: getDateColumnType(),
28+
nullable: options.nullable,
29+
transformer: {
30+
to: (value: Date) => value,
31+
from: (value: any) => (value ? new Date(value) : null),
32+
},
33+
});
34+
}
35+
36+
/**
37+
* Universal update date column decorator that handles both remote and local databases
38+
*/
39+
export function UniversalUpdateDateColumn(options: UniversalDateOptions = {}) {
40+
return UpdateDateColumn({
41+
type: getDateColumnType(),
42+
nullable: options.nullable,
43+
transformer: {
44+
to: (value: Date) => value,
45+
from: (value: any) => (value ? new Date(value) : null),
46+
},
47+
});
48+
}

backend/src/config/config.service.ts

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,4 +68,114 @@ export class AppConfigService {
6868
(config.endpoint || config.accountId)
6969
);
7070
}
71+
72+
/**
73+
* Check if production environment
74+
*/
75+
get isProduction(): boolean {
76+
return this.configService.get('NODE_ENV') === 'production';
77+
}
78+
79+
/**
80+
* Check if development environment
81+
*/
82+
get isDevEnv(): boolean {
83+
const env = this.configService.get('NODE_ENV');
84+
return env?.toUpperCase() === 'DEV';
85+
}
86+
87+
/**
88+
* Check if using remote database
89+
*/
90+
get useRemoteDb(): boolean {
91+
return this.configService.get('USE_REMOTE_DB') === 'true';
92+
}
93+
94+
/**
95+
* Get database configuration
96+
*/
97+
get dbConfig() {
98+
return {
99+
host: this.configService.get('DB_HOST'),
100+
port: parseInt(this.configService.get('DB_PORT'), 10),
101+
username: this.configService.get('DB_USERNAME'),
102+
password: this.configService.get('DB_PASSWORD'),
103+
database: this.configService.get('DB_DATABASE') || 'postgres',
104+
region: this.configService.get('DB_REGION'),
105+
};
106+
}
107+
108+
/**
109+
* Get GitHub configuration
110+
*/
111+
get githubConfig() {
112+
return {
113+
appId: this.configService.get('GITHUB_APP_ID'),
114+
privateKeyPath: this.configService.get('GITHUB_PRIVATE_KEY_PATH'),
115+
clientId: this.configService.get('GITHUB_CLIENT_ID'),
116+
clientSecret: this.configService.get('GITHUB_CLIENT_SECRET'),
117+
webhookSecret: this.configService.get('GITHUB_WEBHOOK_SECRET'),
118+
enabled: !!this.configService.get('GITHUB_ENABLED'),
119+
};
120+
}
121+
122+
get githubEnabled(): boolean {
123+
return this.configService.get('GITHUB_ENABLED') === 'true';
124+
}
125+
126+
get githubWebhookSecret(): string {
127+
return this.configService.get('GITHUB_WEBHOOK_SECRET');
128+
}
129+
130+
/**
131+
* Get mail domain for email links
132+
*/
133+
get mailDomain(): string {
134+
return this.configService.get('MAIL_DOMAIN');
135+
}
136+
137+
/**
138+
* Get frontend URL for email links
139+
*/
140+
get frontendUrl(): string {
141+
return this.configService.get('FRONTEND_URL');
142+
}
143+
144+
/**
145+
* Get mail configuration
146+
*/
147+
get mailConfig() {
148+
return {
149+
host: this.configService.get('MAIL_HOST'),
150+
port: parseInt(this.configService.get('MAIL_PORT'), 10),
151+
user: this.configService.get('MAIL_USER'),
152+
password: this.configService.get('MAIL_PASSWORD'),
153+
from: this.configService.get('MAIL_FROM'),
154+
};
155+
}
156+
157+
/**
158+
* Check if mail service is enabled
159+
*/
160+
get isMailEnabled(): boolean {
161+
return (
162+
this.configService.get('MAIL_ENABLED', 'false').toLowerCase() === 'true'
163+
);
164+
}
165+
166+
get githubAppId(): string {
167+
return this.configService.get('GITHUB_APP_ID');
168+
}
169+
170+
get githubPrivateKeyPath(): string {
171+
return this.configService.get('GITHUB_PRIVATE_KEY_PATH');
172+
}
173+
174+
get githubClientId(): string {
175+
return this.configService.get('GITHUB_CLIENT_ID');
176+
}
177+
178+
get githubClientSecret(): string {
179+
return this.configService.get('GITHUB_CLIENT_SECRET');
180+
}
71181
}

0 commit comments

Comments
 (0)