diff --git a/api/src/util/Message.ts b/api/src/util/Message.ts
index fea553bc..f8230124 100644
--- a/api/src/util/Message.ts
+++ b/api/src/util/Message.ts
@@ -22,7 +22,7 @@ import {
import { HTTPError } from "lambert-server";
import fetch from "node-fetch";
import cheerio from "cheerio";
-import { MessageCreateSchema } from "../schema/Message";
+import { MessageCreateSchema } from "../routes/channels/#channel_id/messages";
// TODO: check webhook, application, system author
diff --git a/api/src/util/Voice.ts b/api/src/util/Voice.ts
index 087bdfa8..f06b1aaa 100644
--- a/api/src/util/Voice.ts
+++ b/api/src/util/Voice.ts
@@ -1,32 +1,32 @@
-import {Config} from "@fosscord/util";
-import {distanceBetweenLocations, IPAnalysis} from "./ipAddress";
+import { Config } from "@fosscord/util";
+import { distanceBetweenLocations, IPAnalysis } from "./ipAddress";
export async function getVoiceRegions(ipAddress: string, vip: boolean) {
- const regions = Config.get().regions;
- const availableRegions = regions.available.filter(ar => vip ? true : !ar.vip);
- let optimalId = regions.default
+ const regions = Config.get().regions;
+ const availableRegions = regions.available.filter((ar) => (vip ? true : !ar.vip));
+ let optimalId = regions.default;
- if(!regions.useDefaultAsOptimal) {
- const clientIpAnalysis = await IPAnalysis(ipAddress)
+ if (!regions.useDefaultAsOptimal) {
+ const clientIpAnalysis = await IPAnalysis(ipAddress);
- let min = Number.POSITIVE_INFINITY
+ let min = Number.POSITIVE_INFINITY;
- for (let ar of availableRegions) {
- //TODO the endpoint location should be saved in the database if not already present to prevent IPAnalysis call
- const dist = distanceBetweenLocations(clientIpAnalysis, ar.location || (await IPAnalysis(ar.endpoint)))
+ for (let ar of availableRegions) {
+ //TODO the endpoint location should be saved in the database if not already present to prevent IPAnalysis call
+ const dist = distanceBetweenLocations(clientIpAnalysis, ar.location || (await IPAnalysis(ar.endpoint)));
- if(dist < min) {
- min = dist
- optimalId = ar.id
- }
- }
- }
+ if (dist < min) {
+ min = dist;
+ optimalId = ar.id;
+ }
+ }
+ }
- return availableRegions.map(ar => ({
- id: ar.id,
- name: ar.name,
- custom: ar.custom,
- deprecated: ar.deprecated,
- optimal: ar.id === optimalId
- }))
-}
\ No newline at end of file
+ return availableRegions.map((ar) => ({
+ id: ar.id,
+ name: ar.name,
+ custom: ar.custom,
+ deprecated: ar.deprecated,
+ optimal: ar.id === optimalId
+ }));
+}
diff --git a/api/src/util/VoiceState.ts b/api/src/util/VoiceState.ts
deleted file mode 100644
index 07022ec9..00000000
--- a/api/src/util/VoiceState.ts
+++ /dev/null
@@ -1,54 +0,0 @@
-import { Channel, ChannelType, DiscordApiErrors, emitEvent, getPermission, VoiceState, VoiceStateUpdateEvent } from "@fosscord/util";
-import { VoiceStateUpdateSchema } from "../schema";
-
-
-//TODO need more testing when community guild and voice stage channel are working
-export async function updateVoiceState(vsuSchema: VoiceStateUpdateSchema, guildId: string, userId: string, targetUserId?: string) {
- const perms = await getPermission(userId, guildId, vsuSchema.channel_id);
-
- /*
- From https://discord.com/developers/docs/resources/guild#modify-current-user-voice-state
- You must have the MUTE_MEMBERS permission to unsuppress yourself. You can always suppress yourself.
- You must have the REQUEST_TO_SPEAK permission to request to speak. You can always clear your own request to speak.
- */
- if (targetUserId !== undefined || (vsuSchema.suppress !== undefined && !vsuSchema.suppress)) {
- perms.hasThrow("MUTE_MEMBERS");
- }
- if (vsuSchema.request_to_speak_timestamp !== undefined && vsuSchema.request_to_speak_timestamp !== "") {
- perms.hasThrow("REQUEST_TO_SPEAK")
- }
-
- if (!targetUserId) {
- targetUserId = userId;
- } else {
- if (vsuSchema.suppress !== undefined && vsuSchema.suppress)
- vsuSchema.request_to_speak_timestamp = "" //Need to check if empty string is the right value
- }
-
- //TODO assumed that empty string means clean, need to test if it's right
- let voiceState
- try {
- voiceState = await VoiceState.findOneOrFail({
- guild_id: guildId,
- channel_id: vsuSchema.channel_id,
- user_id: targetUserId
- });
- } catch (error) {
- throw DiscordApiErrors.UNKNOWN_VOICE_STATE;
- }
-
- voiceState.assign(vsuSchema);
- const channel = await Channel.findOneOrFail({ guild_id: guildId, id: vsuSchema.channel_id })
- if (channel.type !== ChannelType.GUILD_STAGE_VOICE) {
- throw DiscordApiErrors.CANNOT_EXECUTE_ON_THIS_CHANNEL_TYPE;
- }
-
- await Promise.all([
- voiceState.save(),
- emitEvent({
- event: "VOICE_STATE_UPDATE",
- data: voiceState,
- guild_id: guildId
- } as VoiceStateUpdateEvent)]);
- return;
-}
\ No newline at end of file
diff --git a/api/src/util/index.ts b/api/src/util/index.ts
new file mode 100644
index 00000000..c98784a4
--- /dev/null
+++ b/api/src/util/index.ts
@@ -0,0 +1,10 @@
+export * from "./Base64";
+export * from "./cdn";
+export * from "./instanceOf";
+export * from "./ipAddress";
+export * from "./Message";
+export * from "./passwordStrength";
+export * from "./RandomInviteID";
+export * from "./route";
+export * from "./String";
+export * from "./Voice";
diff --git a/api/src/util/passwordStrength.ts b/api/src/util/passwordStrength.ts
index dfffa2c0..047df008 100644
--- a/api/src/util/passwordStrength.ts
+++ b/api/src/util/passwordStrength.ts
@@ -16,7 +16,7 @@ const blocklist: string[] = []; // TODO: update ones passwordblocklist is stored
*
* Returns: 0 > pw > 1
*/
-export function check(password: string): number {
+export function checkPassword(password: string): number {
const { minLength, minNumbers, minUpperCase, minSymbols } = Config.get().register.password;
var strength = 0;
diff --git a/api/src/util/route.ts b/api/src/util/route.ts
new file mode 100644
index 00000000..f618a630
--- /dev/null
+++ b/api/src/util/route.ts
@@ -0,0 +1,76 @@
+import { DiscordApiErrors, Event, EventData, getPermission, PermissionResolvable, Permissions } from "@fosscord/util";
+import { NextFunction, Request, Response } from "express";
+import fs from "fs";
+import path from "path";
+import Ajv from "ajv";
+import { AnyValidateFunction } from "ajv/dist/core";
+import { FieldErrors } from "..";
+import addFormats from "ajv-formats";
+
+const SchemaPath = path.join(__dirname, "..", "..", "assets", "schemas.json");
+const schemas = JSON.parse(fs.readFileSync(SchemaPath, { encoding: "utf8" }));
+export const ajv = new Ajv({
+ allErrors: true,
+ parseDate: true,
+ allowDate: true,
+ schemas,
+ messages: true,
+ strict: true,
+ strictRequired: true
+});
+addFormats(ajv);
+
+declare global {
+ namespace Express {
+ interface Request {
+ permission?: Permissions;
+ }
+ }
+}
+
+export type RouteSchema = string; // typescript interface name
+export type RouteResponse = { status?: number; body?: RouteSchema; headers?: Record<string, string> };
+
+export interface RouteOptions {
+ permission?: PermissionResolvable;
+ body?: RouteSchema;
+ response?: RouteResponse;
+ example?: {
+ body?: any;
+ path?: string;
+ event?: EventData;
+ headers?: Record<string, string>;
+ };
+}
+
+export function route(opts: RouteOptions) {
+ var validate: AnyValidateFunction<any>;
+ if (opts.body) {
+ // @ts-ignore
+ validate = ajv.getSchema(opts.body);
+ if (!validate) throw new Error(`Body schema ${opts.body} not found`);
+ }
+
+ return async (req: Request, res: Response, next: NextFunction) => {
+ if (opts.permission) {
+ const required = new Permissions(opts.permission);
+ const permission = await getPermission(req.user_id, req.params.guild_id, req.params.channel_id);
+ console.log(required.bitfield, permission.bitfield, permission.bitfield & required.bitfield);
+
+ // bitfield comparison: check if user lacks certain permission
+ if (!permission.has(required)) {
+ throw DiscordApiErrors.MISSING_PERMISSIONS.withParams(opts.permission as string);
+ }
+
+ if (validate) {
+ const valid = validate(req.body);
+ if (!valid) {
+ const fields: Record<string, { code?: string; message: string }> = {};
+ validate.errors?.forEach((x) => (fields[x.instancePath] = { code: x.keyword, message: x.message || "" }));
+ throw FieldErrors(fields);
+ }
+ }
+ }
+ next();
+ };
+}
|