/[sudobot]/trunk/src/api/controllers/ConfigController.ts
ViewVC logotype

Annotation of /trunk/src/api/controllers/ConfigController.ts

Parent Directory Parent Directory | Revision Log Revision Log


Revision 379 - (hide annotations)
Mon Jul 29 17:29:52 2024 UTC (8 months, 2 weeks ago) by rakin
File MIME type: application/typescript
File size: 11047 byte(s)
fix(api): config input not merging properly
1 rakin 352 import { dot, object } from "dot-object";
2     import { body } from "express-validator";
3 rakin 370 import { z as zod, ZodSchema } from "zod";
4 rakin 352 import KeyValuePair from "../../types/KeyValuePair";
5 rakin 326 import Controller from "../Controller";
6 rakin 349 import RequireAuth from "../middleware/RequireAuth";
7 rakin 353 import ValidatorError from "../middleware/ValidatorError";
8 rakin 349 import Request from "../Request";
9 rakin 370 import merge from 'ts-deepmerge';
10 rakin 379 import { config } from "../../client/Config";
11 rakin 326
12     export default class ConfigController extends Controller {
13 rakin 349 globalMiddleware(): Function[] {
14 rakin 353 return [RequireAuth, ValidatorError];
15 rakin 349 }
16    
17 rakin 352 middleware(): KeyValuePair<Function[]> {
18     return {
19     update: [
20     body(["config"]).isObject()
21     ]
22     };
23     }
24    
25 rakin 379 private zodSchema(id: string) {
26 rakin 370 const snowflake = zod.string().regex(/\d+/, { message: "The given value is not a Snowflake" });
27 rakin 379 const config = this.client.config.props[id];
28 rakin 370
29     const schema = zod.object({
30     "prefix": zod.string().optional(),
31     "debug": zod.boolean().optional(),
32     "mute_role": snowflake.optional(),
33     "gen_role": snowflake.optional(),
34     "logging_channel": snowflake.optional(),
35     "logging_channel_join_leave": snowflake.optional(),
36     "mod_role": snowflake.optional(),
37     "announcement_channel": snowflake.optional(),
38     "admin": snowflake.optional(),
39     "lockall": zod.array(zod.string()).optional(),
40     "warn_notallowed": zod.boolean().optional(),
41     "role_commands": zod.record(
42     snowflake,
43     zod.array(zod.string().min(1))
44 rakin 379 ).optional().default({}),
45 rakin 370 "autoclear": zod.object({
46     "enabled": zod.boolean().optional(),
47 rakin 379 "channels": zod.array(snowflake).optional().default(config.autoclear.channels)
48 rakin 370 }).optional(),
49     "verification": zod.object({
50     "enabled": zod.boolean().optional(),
51     "role": snowflake.optional()
52     }).optional(),
53     "welcomer": zod.object({
54     "enabled": zod.boolean().optional(),
55     "channel": snowflake.optional(),
56     "message": zod.string().min(1).or(zod.null()).optional(),
57     "randomize": zod.boolean().optional()
58     }).optional(),
59     "cooldown": zod.object({
60     "enabled": zod.boolean().optional(),
61     "global": zod.any().optional(),
62     "cmds": zod.object({}).optional()
63     }).optional(),
64     "starboard": zod.object({
65     "enabled": zod.boolean().optional(),
66     "reactions": zod.number().int().optional(),
67     "channel": snowflake.optional()
68     }).optional(),
69     "autorole": zod.object({
70     "enabled": zod.boolean().optional(),
71 rakin 379 "roles": zod.array(snowflake).optional().default(config.autorole.roles)
72 rakin 370 }).optional(),
73     "spam_filter": zod.object({
74     "enabled": zod.boolean().optional(),
75     "limit": zod.number().int().optional(),
76     "time": zod.number().optional(),
77     "diff": zod.number().optional(),
78 rakin 379 "exclude": zod.array(snowflake).optional().default(config.spam_filter.exclude),
79 rakin 370 "samelimit": zod.number().int().optional(),
80     "unmute_in": zod.number().optional()
81     }).optional(),
82     "raid": zod.object({
83     "enabled": zod.boolean().optional(),
84     "max_joins": zod.number().int().optional(),
85     "time": zod.number().optional(),
86 rakin 379 "channels": zod.array(snowflake).optional().default(config.raid.channels),
87 rakin 370 "exclude": zod.boolean().optional()
88     }).optional(),
89 rakin 379 "global_commands": zod.array(zod.string()).optional().default(config.global_commands),
90 rakin 370 "filters": zod.object({
91     "ignore_staff": zod.boolean().optional(),
92     "chars_repeated": zod.number().int().optional(),
93     "words_repeated": zod.number().int().optional(),
94 rakin 379 "words": zod.array(zod.string()).optional().default(config.filters.words),
95     "tokens": zod.array(zod.string()).optional().default(config.filters.tokens),
96 rakin 370 "invite_message": zod.string().optional(),
97 rakin 379 "words_excluded": zod.array(snowflake).optional().default(config.filters.words_excluded),
98     "domain_excluded": zod.array(snowflake).optional().default(config.filters.domain_excluded),
99     "invite_excluded": zod.array(snowflake).optional().default(config.filters.invite_excluded),
100 rakin 370 "words_enabled": zod.boolean().optional(),
101     "invite_enabled": zod.boolean().optional(),
102     "domain_enabled": zod.boolean().optional(),
103     "regex": zod.boolean().optional(),
104 rakin 379 "file_mimes_excluded": zod.array(zod.string()).optional().default(config.filters.file_mimes_excluded),
105     "file_types_excluded": zod.array(zod.string()).optional().default(config.filters.file_types_excluded),
106     "domains": zod.array(zod.string()).optional().default(config.filters.domains),
107     "regex_patterns": zod.array(zod.string()).optional().default(config.filters.regex_patterns),
108 rakin 370 "rickrolls_enabled": zod.boolean().optional(),
109     "pings": zod.number().int().optional()
110     }).optional()
111     });
112    
113     return schema;
114     }
115    
116 rakin 326 public async index(request: Request) {
117 rakin 349 const { id } = request.params;
118    
119     if (!request.user?.guilds.includes(id)) {
120     return this.response({ error: "You don't have permission to access configuration of this guild." }, 403);
121     }
122    
123     if (id === "global" || !this.client.config.props[id]) {
124     return this.response({ error: "No configuration found for the given guild ID" }, 404);
125     }
126    
127     return this.client.config.props[id];
128 rakin 326 }
129    
130     public async update(request: Request) {
131 rakin 352 const { id } = request.params;
132 rakin 379 const { config: origconfig } = request.body;
133 rakin 368
134 rakin 379 console.log(origconfig);
135 rakin 368
136 rakin 370 try {
137 rakin 379 const currentConfigDotObject = this.client.config.props[id];
138    
139     console.log("Current config: ", currentConfigDotObject);
140    
141     const result = this.zodSchema(id).safeParse(object(origconfig));
142    
143     if (!result?.success) {
144     return this.response({ error: "The data schema does not match.", error_type: 'validation', errors: result.error.errors }, 422);
145     }
146    
147     const config = result.data;
148     const newConfig = merge.withOptions({ mergeArrays: false }, currentConfigDotObject, config);
149     const result2 = this.zodSchema(id).safeParse(newConfig);
150    
151     if (!result2?.success) {
152     console.log(result2.error.errors);
153     return this.response({ error: 'Internal Server Error (500)', error_type: 'internal' }, 500);
154     }
155    
156     console.log('Final', newConfig);
157     this.client.config.props[id] = newConfig;
158     this.client.config.write();
159     return { message: "Configuration updated", previous: dot(currentConfigDotObject), new: dot(this.client.config.props[id]) };
160     }
161     catch (e) {
162     console.log(e);
163     return this.response({ error: 'Internal Server Error', error_type: 'internal' }, 500);
164     }
165     }
166    
167     public async update2(request: Request) {
168     const { id } = request.params;
169     const { config: origconfig } = request.body;
170    
171     console.log(origconfig);
172    
173     try {
174 rakin 370 const currentConfigDotObject = dot(this.client.config.props[id]);
175     let newConfigDotObject = {...currentConfigDotObject};
176    
177 rakin 379 console.log("Current config: ", currentConfigDotObject);
178 rakin 352
179 rakin 379 const result = this.zodSchema(id).safeParse(object({...origconfig}));
180 rakin 370
181     if (!result?.success) {
182     return this.response({ error: "The data schema does not match.", error_type: 'validation', errors: result.error.errors }, 422);
183 rakin 352 }
184    
185 rakin 379 const config = result.data;
186    
187     console.log(config);
188    
189     for (const key in config) {
190     const configKey = key as keyof typeof config;
191 rakin 370 const regexMatched = /(.+)\[\d+\]/g.test(configKey);
192 rakin 368
193 rakin 370 if (typeof currentConfigDotObject[configKey] === 'undefined' && !regexMatched) {
194 rakin 379 console.log(configKey, config[configKey]);
195    
196     if (currentConfigDotObject[configKey] instanceof Array && config[configKey] instanceof Array) {
197     console.log('Array');
198     }
199     else
200     return this.response({ error: `The key '${configKey}' is not allowed` }, 422);
201 rakin 370 }
202 rakin 368
203 rakin 370 if (!regexMatched && config[configKey] !== null && config[configKey] !== null && typeof config[configKey] !== typeof currentConfigDotObject[configKey]) {
204     console.log(typeof config[configKey], typeof currentConfigDotObject[configKey]);
205    
206     if (typeof currentConfigDotObject[configKey] === 'number' && typeof config[configKey] === 'string') {
207 rakin 379 const int = parseInt(config[configKey]!.toString());
208 rakin 370
209     if (int !== NaN) {
210     newConfigDotObject[configKey] = int;
211     console.log("Updating: ", configKey, config[configKey], newConfigDotObject[configKey]);
212     continue;
213     }
214     }
215    
216     return this.response({ error: `The key '${configKey}' has incompatible value type '${config[configKey] === null ? 'null' : typeof config[configKey]}'` }, 422);
217 rakin 368 }
218 rakin 370
219     console.log("Updating: ", configKey, config[configKey], newConfigDotObject[configKey]);
220 rakin 352 }
221    
222 rakin 379 const newObj = object({...newConfigDotObject});
223     const configObj = object({...config});
224 rakin 352
225 rakin 379 // console.log("Newobj", newObj);
226     // console.log("Configobj", configObj);
227    
228     newConfigDotObject = merge.withOptions({ mergeArrays: false }, newObj, configObj);
229 rakin 370 console.log("Output: ", newConfigDotObject);
230 rakin 353
231 rakin 370 this.client.config.props[id] = newConfigDotObject;
232     this.client.config.write();
233 rakin 353
234 rakin 370 return { message: "Configuration updated", previous: currentConfigDotObject, new: dot(this.client.config.props[id]) };
235     }
236     catch (e) {
237     console.log(e);
238     return this.response({ error: 'Internal Server Error', error_type: 'internal' }, 500);
239     }
240 rakin 326 }
241     }

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26