-
Notifications
You must be signed in to change notification settings - Fork 120
/
Copy pathsetting.service.ts
202 lines (182 loc) · 6.17 KB
/
setting.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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
/*
* Copyright © 2025 Hexastack. All rights reserved.
*
* Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms:
* 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission.
* 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file).
*/
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { Inject, Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { Cache } from 'cache-manager';
import { config } from '@/config';
import { Config } from '@/config/types';
import { LoggerService } from '@/logger/logger.service';
import {
ALLOWED_ORIGINS_CACHE_KEY,
SETTING_CACHE_KEY,
} from '@/utils/constants/cache';
import { Cacheable } from '@/utils/decorators/cacheable.decorator';
import { BaseService } from '@/utils/generics/base-service';
import { ExtensionName } from '@/utils/types/extension';
import { SettingCreateDto } from '../dto/setting.dto';
import { SettingRepository } from '../repositories/setting.repository';
import { Setting } from '../schemas/setting.schema';
import { TextSetting } from '../schemas/types';
import { SettingSeeder } from '../seeds/setting.seed';
@Injectable()
export class SettingService extends BaseService<Setting> {
constructor(
readonly repository: SettingRepository,
@Inject(CACHE_MANAGER) private readonly cacheManager: Cache,
private readonly logger: LoggerService,
private readonly seeder: SettingSeeder,
) {
super(repository);
}
/**
* Seeds the settings if they don't already exist for the provided group.
*
* @param group - The group of settings to check.
* @param data - The array of settings to seed if none exist.
*/
async seedIfNotExist(group: string, data: SettingCreateDto[]) {
const count = await this.count({ group });
if (count === 0) {
await this.seeder.seed(data);
}
}
/**
* Removes all settings that belong to the provided group.
*
* @param group - The group of settings to remove.
*/
async deleteGroup(group: string) {
await this.repository.deleteMany({ group });
}
/**
* Loads all settings and returns them grouped in ascending order by weight.
*
* @returns A grouped object of settings.
*/
async load() {
const settings = await this.findAll(['weight', 'asc']);
return this.group(settings);
}
/**
* Builds a tree structure from the settings array.
*
* Each setting is grouped by its `group` and returned as a structured object.
*
* @param settings - An array of settings to build into a tree structure.
*
* @returns A `Settings` object organized by group.
*/
public buildTree(settings: Setting[]): Settings {
return settings.reduce((acc: Settings, s: Setting) => {
const groupKey = s.group || 'undefinedGroup';
acc[groupKey] = acc[groupKey] || {};
acc[groupKey][s.label] = s.value;
return acc;
}, {} as Settings);
}
/**
* Groups the settings into a record where the key is the setting group and
* the value is an array of settings in that group.
*
* @param settings - An array of settings to group.
*
* @returns A record where each key is a group and each value is an array of settings.
*/
public group(settings: Setting[]): Record<string, Setting[]> {
return (
settings?.reduce((acc, curr) => {
const group = acc[curr.group] || [];
group.push(curr);
acc[curr.group] = group;
return acc;
}, {}) || {}
);
}
/**
*
* Retrieves a list of all setting groups for a specific extension type.
*
* @param extensionType - The extension type to filter by.
*
* @returns A promise that resolves to a list of setting groups.
*/
async getExtensionSettings(
extensionType: 'channel' | 'plugin' | 'helper',
): Promise<HyphenToUnderscore<ExtensionName>[]> {
const settings = await this.find({
group: { $regex: `_${extensionType}$` },
});
const groups = settings.reduce<string[]>((acc, setting) => {
if (!acc.includes(setting.group)) {
acc.push(setting.group);
}
return acc;
}, []) as HyphenToUnderscore<ExtensionName>[];
return groups;
}
/**
* Retrieves the application configuration object.
*
* @returns The global configuration object.
*/
getConfig(): Config {
return config;
}
/**
* Clears the settings cache
*/
async clearCache() {
this.cacheManager.del(SETTING_CACHE_KEY);
this.cacheManager.del(ALLOWED_ORIGINS_CACHE_KEY);
}
/**
* Event handler for setting updates. Listens to 'hook:setting:*' events
* and invalidates the cache for settings when triggered.
*/
@OnEvent('hook:setting:*')
async handleSettingUpdateEvent() {
this.clearCache();
}
/**
* Retrieves a set of unique allowed origins for CORS configuration.
*
* This method combines all `allowed_domains` settings,
* splits their values (comma-separated), and removes duplicates to produce a
* whitelist of origins. The result is cached for better performance using the
* `Cacheable` decorator with the key `ALLOWED_ORIGINS_CACHE_KEY`.
*
* @returns A promise that resolves to a set of allowed origins
*/
@Cacheable(ALLOWED_ORIGINS_CACHE_KEY)
async getAllowedOrigins() {
const settings = (await this.find({
label: 'allowed_domains',
})) as TextSetting[];
const allowedDomains = settings.flatMap((setting) =>
setting.value.split(',').filter((o) => !!o),
);
const uniqueOrigins = new Set([
...config.security.cors.allowOrigins,
...config.sockets.onlyAllowOrigins,
...allowedDomains,
]);
return uniqueOrigins;
}
/**
* Retrieves settings from the cache if available, or loads them from the
* repository and caches the result.
*
* @returns A promise that resolves to a `Settings` object.
*/
@Cacheable(SETTING_CACHE_KEY)
async getSettings(): Promise<Settings> {
const settings = await this.findAll();
return this.buildTree(settings);
}
}