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

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

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 326 by rakin, Mon Jul 29 17:29:33 2024 UTC revision 379 by rakin, Mon Jul 29 17:29:52 2024 UTC
# Line 1  Line 1 
1  import { Request } from "express";  import { dot, object } from "dot-object";
2    import { body } from "express-validator";
3    import { z as zod, ZodSchema } from "zod";
4    import KeyValuePair from "../../types/KeyValuePair";
5  import Controller from "../Controller";  import Controller from "../Controller";
6    import RequireAuth from "../middleware/RequireAuth";
7    import ValidatorError from "../middleware/ValidatorError";
8    import Request from "../Request";
9    import merge from 'ts-deepmerge';
10    import { config } from "../../client/Config";
11    
12  export default class ConfigController extends Controller {  export default class ConfigController extends Controller {
13        globalMiddleware(): Function[] {
14            return [RequireAuth, ValidatorError];
15        }
16    
17        middleware(): KeyValuePair<Function[]> {
18            return {
19                update: [
20                    body(["config"]).isObject()
21                ]
22            };
23        }
24    
25        private zodSchema(id: string) {
26            const snowflake = zod.string().regex(/\d+/, { message: "The given value is not a Snowflake" });
27            const config = this.client.config.props[id];
28    
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                ).optional().default({}),
45                "autoclear": zod.object({
46                    "enabled": zod.boolean().optional(),
47                    "channels": zod.array(snowflake).optional().default(config.autoclear.channels)
48                }).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                    "roles": zod.array(snowflake).optional().default(config.autorole.roles)
72                }).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                    "exclude": zod.array(snowflake).optional().default(config.spam_filter.exclude),
79                    "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                    "channels": zod.array(snowflake).optional().default(config.raid.channels),
87                    "exclude": zod.boolean().optional()
88                }).optional(),
89                "global_commands": zod.array(zod.string()).optional().default(config.global_commands),
90                "filters": zod.object({
91                    "ignore_staff": zod.boolean().optional(),
92                    "chars_repeated": zod.number().int().optional(),
93                    "words_repeated": zod.number().int().optional(),
94                    "words": zod.array(zod.string()).optional().default(config.filters.words),
95                    "tokens": zod.array(zod.string()).optional().default(config.filters.tokens),
96                    "invite_message": zod.string().optional(),
97                    "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                    "words_enabled": zod.boolean().optional(),
101                    "invite_enabled": zod.boolean().optional(),
102                    "domain_enabled": zod.boolean().optional(),
103                    "regex": zod.boolean().optional(),
104                    "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                    "rickrolls_enabled": zod.boolean().optional(),
109                    "pings": zod.number().int().optional()
110                }).optional()
111            });
112    
113            return schema;
114        }
115    
116      public async index(request: Request) {      public async index(request: Request) {
117                        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      }      }
129    
130      public async update(request: Request) {      public async update(request: Request) {
131                    const { id } = request.params;
132            const { config: origconfig } = request.body;
133    
134            console.log(origconfig);        
135    
136            try {
137                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                const currentConfigDotObject = dot(this.client.config.props[id]);
175                let newConfigDotObject = {...currentConfigDotObject};
176        
177                console.log("Current config: ", currentConfigDotObject);
178    
179                const result = this.zodSchema(id).safeParse(object({...origconfig}));
180    
181                if (!result?.success) {
182                    return this.response({ error: "The data schema does not match.", error_type: 'validation', errors: result.error.errors }, 422);
183                }
184    
185                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                    const regexMatched = /(.+)\[\d+\]/g.test(configKey);
192                    
193                    if (typeof currentConfigDotObject[configKey] === 'undefined' && !regexMatched) {
194                        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                    }
202    
203                    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                            const int = parseInt(config[configKey]!.toString());
208    
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                    }
218    
219                    console.log("Updating: ", configKey, config[configKey], newConfigDotObject[configKey]);
220                }
221    
222                const newObj = object({...newConfigDotObject});
223                const configObj = object({...config});
224    
225                // console.log("Newobj", newObj);
226                // console.log("Configobj", configObj);
227    
228                newConfigDotObject = merge.withOptions({ mergeArrays: false }, newObj, configObj);
229                console.log("Output: ", newConfigDotObject);
230    
231                this.client.config.props[id] = newConfigDotObject;
232                this.client.config.write();
233    
234                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      }      }
241  }  }

Legend:
Removed from v.326  
changed lines
  Added in v.379

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26