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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26