/
config.service.ts
141 lines (111 loc) · 3.59 KB
/
config.service.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
import { Logger } from '@nestjs/common';
import { classToPlain, Exclude } from 'class-transformer';
import { validateSync } from 'class-validator';
import findRoot from 'find-root';
import { readJSONSync, writeJson } from 'fs-extra';
import { camelCase, get } from 'lodash';
import nconf from 'nconf';
import { join } from 'path';
import SmeeClient from 'smee-client';
import { ConfigValidationError } from '@kb-errors';
import { PackageDetailsDto } from '@kb-models';
import { AchievibitConfig } from './achievibit-config.model';
const appRoot = findRoot(__dirname);
const environment = get(process, 'env.NODE_ENV', 'development');
const eventLogger: Logger = new Logger('SmeeEvents');
(eventLogger as any).info = eventLogger.log;
const configFilePath = join(appRoot, `${ environment }.env.json`);
const packageDetails = new PackageDetailsDto(readJSONSync(join(appRoot, 'package.json')));
nconf
.argv({
parseValues: true
})
.env({
lowerCase: true,
parseValues: true,
transform: transformToLowerCase
})
.file({ file: configFilePath });
let smee: SmeeClient;
let events: any;
let configService: ConfigService;
/**
* This is a **Forced Singleton**.
* This means that even if you try to create
* another ConfigService, you'll always get the
* first one.
*/
@Exclude()
export class ConfigService extends AchievibitConfig {
private readonly logger: Logger = new Logger('ConfigService');
private readonly mode: string = environment;
get smee(): SmeeClient {
return smee;
}
get events(): any {
return events;
}
get packageDetails(): PackageDetailsDto {
return packageDetails;
}
get appRoot(): string {
return appRoot;
}
constructor(passedConfig?: Partial<AchievibitConfig>) {
super();
if (!passedConfig && configService) { return configService; }
const config = passedConfig || nconf.get();
const envConfig = this.validateInput(config);
const passedConfigNodeEnv = get(passedConfig, 'nodeEnv', '');
// attach configuration to this service
Object.assign(this, envConfig);
if (this.mode === 'development' || passedConfigNodeEnv === 'development') {
if (!smee) {
smee = new SmeeClient({
source: this.webhookProxyUrl,
target: `http://localhost:${ this.port }/${ this.webhookDestinationUrl }`,
logger: eventLogger
});
}
if (!events) {
this.logger.log('Starting to listen to events from Proxy');
events = this.smee.start();
}
} else {
this.closeEvents();
smee = undefined;
events = undefined;
}
if (this.saveToFile) {
writeJson(configFilePath, classToPlain(this), { spaces: 2 });
}
configService = this;
}
closeEvents() {
const smeeToDelete = smee;
const eventsToDelete = events;
smee = undefined;
events = undefined;
return eventsToDelete && eventsToDelete.close();
}
toPlainObject() {
return classToPlain(this);
}
/**
* Ensures all needed variables are set, and returns the validated JavaScript object
* including the applied default values.
*/
private validateInput(envConfig: Record<string, any>): Partial<AchievibitConfig> {
const achievibitConfig = new AchievibitConfig(envConfig);
const validationErrors = validateSync(achievibitConfig);
if (validationErrors.length > 0) {
throw new ConfigValidationError(validationErrors);
}
return classToPlain(achievibitConfig);
}
}
function transformToLowerCase(obj: { key: string; value: string }) {
const camelCasedKey = camelCase(obj.key);
obj.key = camelCasedKey;
return camelCasedKey && obj;
}