Skip to content

Commit 9c98311

Browse files
author
Nevo David
committed
Merge remote-tracking branch 'origin/main'
2 parents 69da648 + d25a33e commit 9c98311

File tree

5 files changed

+170
-2
lines changed

5 files changed

+170
-2
lines changed

apps/backend/src/api/routes/auth.controller.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,9 @@ export class AuthController {
3030
getOrgFromCookie
3131
);
3232

33-
if (body.provider === 'LOCAL') {
33+
const activationRequired = body.provider === 'LOCAL' && !!process.env.RESEND_API_KEY;
34+
35+
if (activationRequired) {
3436
response.header('activate', 'true');
3537
response.status(200).json({ activate: true });
3638
return;

apps/backend/src/main.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { NestFactory } from '@nestjs/core';
88
import { AppModule } from './app.module';
99
import { SubscriptionExceptionFilter } from '@gitroom/backend/services/auth/permissions/subscription.exception';
1010
import { HttpExceptionFilter } from '@gitroom/nestjs-libraries/services/exception.filter';
11+
import { ConfigurationChecker } from '@gitroom/helpers/configuration/configuration.checker';
1112

1213
async function bootstrap() {
1314
const app = await NestFactory.create(AppModule, {
@@ -38,11 +39,29 @@ async function bootstrap() {
3839

3940
try {
4041
await app.listen(port);
42+
43+
checkConfiguration() // Do this last, so that users will see obvious issues at the end of the startup log without having to scroll up.
4144

4245
Logger.log(`🚀 Backend is running on: http://localhost:${port}`);
4346
} catch (e) {
4447
Logger.error(`Backend failed to start on port ${port}`, e);
4548
}
4649
}
4750

51+
function checkConfiguration() {
52+
const checker = new ConfigurationChecker();
53+
checker.readEnvFromProcess()
54+
checker.check()
55+
56+
if (checker.hasIssues()) {
57+
for (const issue of checker.getIssues()) {
58+
Logger.warn(issue, 'Configuration issue')
59+
}
60+
61+
Logger.warn("Configuration issues found: " + checker.getIssuesCount())
62+
} else {
63+
Logger.log("Configuration check completed without any issues.")
64+
}
65+
}
66+
4867
bootstrap();

apps/commands/src/command.module.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ import { CheckStars } from './tasks/check.stars';
44
import { DatabaseModule } from '@gitroom/nestjs-libraries/database/prisma/database.module';
55
import { RefreshTokens } from './tasks/refresh.tokens';
66
import { BullMqModule } from '@gitroom/nestjs-libraries/bull-mq-transport-new/bull.mq.module';
7+
import { ConfigurationTask } from './tasks/configuration';
78

89
@Module({
910
imports: [ExternalCommandModule, DatabaseModule, BullMqModule],
1011
controllers: [],
11-
providers: [CheckStars, RefreshTokens],
12+
providers: [CheckStars, RefreshTokens, ConfigurationTask],
1213
get exports() {
1314
return [...this.imports, ...this.providers];
1415
},
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { Command } from 'nestjs-command';
2+
import { Injectable } from '@nestjs/common';
3+
import { ConfigurationChecker } from '@gitroom/helpers/configuration/configuration.checker';
4+
5+
@Injectable()
6+
export class ConfigurationTask {
7+
@Command({
8+
command: 'config:check',
9+
describe: 'Checks your configuration (.env) file for issues.',
10+
})
11+
create() {
12+
const checker = new ConfigurationChecker();
13+
checker.readEnvFromProcess()
14+
checker.check()
15+
16+
if (checker.hasIssues()) {
17+
for (const issue of checker.getIssues()) {
18+
console.warn("Configuration issue:", issue)
19+
}
20+
21+
console.error("Configuration check complete, issues: ", checker.getIssuesCount())
22+
} else {
23+
console.log("Configuration check complete, no issues found.")
24+
}
25+
26+
console.log("Press Ctrl+C to exit.");
27+
return true
28+
}
29+
}
30+
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { readFileSync, existsSync } from 'fs'
2+
import * as dotenv from 'dotenv'
3+
import { resolve } from 'path'
4+
5+
export class ConfigurationChecker {
6+
cfg: dotenv.DotenvParseOutput
7+
issues: string[] = []
8+
9+
readEnvFromFile () {
10+
const envFile = resolve(__dirname, '../../../.env')
11+
12+
if (!existsSync(envFile)) {
13+
console.error('Env file not found!: ', envFile)
14+
return
15+
}
16+
17+
const handle = readFileSync(envFile, 'utf-8')
18+
19+
this.cfg = dotenv.parse(handle)
20+
}
21+
22+
readEnvFromProcess () {
23+
this.cfg = process.env
24+
}
25+
26+
check () {
27+
this.checkDatabaseServers()
28+
this.checkNonEmpty('JWT_SECRET')
29+
this.checkIsValidUrl('MAIN_URL')
30+
this.checkIsValidUrl('FRONTEND_URL')
31+
this.checkIsValidUrl('NEXT_PUBLIC_BACKEND_URL')
32+
this.checkIsValidUrl('BACKEND_INTERNAL_URL')
33+
this.checkNonEmpty('RESEND_API_KEY', 'Needed to send user activation emails.')
34+
this.checkNonEmpty('CLOUDFLARE_ACCOUNT_ID', 'Needed to setup providers.')
35+
this.checkNonEmpty('CLOUDFLARE_ACCESS_KEY', 'Needed to setup providers.')
36+
this.checkNonEmpty('CLOUDFLARE_SECRET_ACCESS_KEY', 'Needed to setup providers.')
37+
this.checkNonEmpty('CLOUDFLARE_BUCKETNAME', 'Needed to setup providers.')
38+
this.checkNonEmpty('CLOUDFLARE_BUCKET_URL', 'Needed to setup providers.')
39+
this.checkNonEmpty('CLOUDFLARE_REGION', 'Needed to setup providers.')
40+
}
41+
42+
checkNonEmpty (key: string, description?: string): boolean {
43+
const v = this.get(key)
44+
45+
if (!description) {
46+
description = ''
47+
}
48+
49+
if (!v) {
50+
this.issues.push(key + ' not set. ' + description)
51+
return false
52+
}
53+
54+
if (v.length === 0) {
55+
this.issues.push(key + ' is empty.' + description)
56+
return false
57+
}
58+
59+
return true
60+
}
61+
62+
get(key: string): string | undefined {
63+
return this.cfg[key as keyof typeof this.cfg]
64+
}
65+
66+
checkDatabaseServers () {
67+
this.checkRedis()
68+
this.checkIsValidUrl('DATABASE_URL')
69+
}
70+
71+
checkRedis () {
72+
if (!this.cfg.REDIS_URL) {
73+
this.issues.push('REDIS_URL not set')
74+
}
75+
76+
try {
77+
const redisUrl = new URL(this.cfg.REDIS_URL)
78+
79+
if (redisUrl.protocol !== 'redis:') {
80+
this.issues.push('REDIS_URL must start with redis://')
81+
}
82+
} catch (error) {
83+
this.issues.push('REDIS_URL is not a valid URL')
84+
}
85+
}
86+
87+
checkIsValidUrl (key: string) {
88+
if (!this.checkNonEmpty(key)) {
89+
return
90+
}
91+
92+
const urlString = this.get(key)
93+
94+
try {
95+
new URL(urlString)
96+
} catch (error) {
97+
this.issues.push(key + ' is not a valid URL')
98+
}
99+
100+
if (urlString.endsWith('/')) {
101+
this.issues.push(key + ' should not end with /')
102+
}
103+
}
104+
105+
hasIssues() {
106+
return this.issues.length > 0
107+
}
108+
109+
getIssues() {
110+
return this.issues
111+
}
112+
113+
getIssuesCount() {
114+
return this.issues.length
115+
}
116+
}

0 commit comments

Comments
 (0)