summary refs log tree commit diff
diff options
context:
space:
mode:
authorAlTech98 <altech123159@gmail.com>2021-08-30 21:12:13 +0200
committerAlTech98 <altech123159@gmail.com>2021-08-30 21:12:13 +0200
commit89304ff47ba03df876dc795cdb9fd17f5bca67f2 (patch)
treee67f2873277ea09cec4ba50008c5ab4e891e886c
parentadded first unittests for api endpoints (diff)
downloadserver-89304ff47ba03df876dc795cdb9fd17f5bca67f2.tar.xz
Implemented voice apis #127 and #78
-rw-r--r--api/src/routes/guilds/#guild_id/regions.ts9
-rw-r--r--api/src/routes/voice/regions.ts11
-rw-r--r--api/src/util/Voice.ts32
-rw-r--r--api/src/util/ipAddress.ts17
-rw-r--r--util/src/entities/Config.ts10
-rw-r--r--util/src/entities/Guild.ts2
6 files changed, 76 insertions, 5 deletions
diff --git a/api/src/routes/guilds/#guild_id/regions.ts b/api/src/routes/guilds/#guild_id/regions.ts
index 5ec649ee..212c9bcd 100644
--- a/api/src/routes/guilds/#guild_id/regions.ts
+++ b/api/src/routes/guilds/#guild_id/regions.ts
@@ -1,10 +1,15 @@
-import { Config } from "@fosscord/util";
+import {Config, Guild, Member} from "@fosscord/util";
 import { Request, Response, Router } from "express";
+import {getVoiceRegions} from "../../../util/Voice";
+import {getIpAdress} from "../../../util/ipAddress";
 
 const router = Router();
 
 router.get("/", async (req: Request, res: Response) => {
-	return res.json(Config.get().regions.available);
+	const { guild_id } = req.params;
+	const guild = await Guild.findOneOrFail({ id: guild_id });
+	//TODO we should use an enum for guild's features and not hardcoded strings
+	return res.json(await getVoiceRegions(getIpAdress(req), guild.features.includes("VIP_REGIONS")));
 });
 
 export default router;
diff --git a/api/src/routes/voice/regions.ts b/api/src/routes/voice/regions.ts
new file mode 100644
index 00000000..812aa8f6
--- /dev/null
+++ b/api/src/routes/voice/regions.ts
@@ -0,0 +1,11 @@
+import { Router, Request, Response } from "express";
+import {getIpAdress} from "../../util/ipAddress";
+import {getVoiceRegions} from "../../util/Voice";
+
+const router: Router = Router();
+
+router.get("/", async (req: Request, res: Response) => {
+    res.json(await getVoiceRegions(getIpAdress(req), true))//vip true?
+});
+
+export default router;
diff --git a/api/src/util/Voice.ts b/api/src/util/Voice.ts
new file mode 100644
index 00000000..087bdfa8
--- /dev/null
+++ b/api/src/util/Voice.ts
@@ -0,0 +1,32 @@
+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
+
+    if(!regions.useDefaultAsOptimal) {
+        const clientIpAnalysis = await IPAnalysis(ipAddress)
+
+        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)))
+
+            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
diff --git a/api/src/util/ipAddress.ts b/api/src/util/ipAddress.ts
index 0a724daa..c6239426 100644
--- a/api/src/util/ipAddress.ts
+++ b/api/src/util/ipAddress.ts
@@ -60,6 +60,7 @@ const exampleData = {
 	status: 200
 };
 
+//TODO add function that support both ip and domain names
 export async function IPAnalysis(ip: string): Promise<typeof exampleData> {
 	const { ipdataApiKey } = Config.get().security;
 	if (!ipdataApiKey) return { ...exampleData, ip };
@@ -79,3 +80,19 @@ export function getIpAdress(req: Request): string {
 	// @ts-ignore
 	return req.headers[Config.get().security.forwadedFor] || req.socket.remoteAddress;
 }
+
+
+export function distanceBetweenLocations(loc1: any, loc2: any): number {
+	return distanceBetweenCoords(loc1.latitude, loc1.longitude, loc2.latitude, loc2.longitude);
+}
+
+//Haversine function
+function distanceBetweenCoords(lat1: number, lon1: number, lat2: number, lon2: number) {
+	const p = 0.017453292519943295;    // Math.PI / 180
+	const c = Math.cos;
+	const a = 0.5 - c((lat2 - lat1) * p) / 2 +
+		c(lat1 * p) * c(lat2 * p) *
+		(1 - c((lon2 - lon1) * p)) / 2;
+
+	return 12742 * Math.asin(Math.sqrt(a)); // 2 * R; R = 6371 km
+}
\ No newline at end of file
diff --git a/util/src/entities/Config.ts b/util/src/entities/Config.ts
index 320a729c..f030b167 100644
--- a/util/src/entities/Config.ts
+++ b/util/src/entities/Config.ts
@@ -19,10 +19,14 @@ export interface RateLimitOptions {
 export interface Region {
 	id: string;
 	name: string;
+	endpoint: string;
+	location?: {
+		latitude: number;
+		longitude: number;
+	};
 	vip: boolean;
 	custom: boolean;
 	deprecated: boolean;
-	optimal: boolean;
 }
 
 export interface KafkaBroker {
@@ -128,6 +132,7 @@ export interface ConfigValue {
 	};
 	regions: {
 		default: string;
+		useDefaultAsOptimal: boolean;
 		available: Region[];
 	};
 	rabbitmq: {
@@ -263,7 +268,8 @@ export const DefaultConfigOptions: ConfigValue = {
 	},
 	regions: {
 		default: "fosscord",
-		available: [{ id: "fosscord", name: "Fosscord", vip: false, custom: false, deprecated: false, optimal: false }],
+		useDefaultAsOptimal: true,
+		available: [{ id: "fosscord", name: "Fosscord", endpoint: "127.0.0.1", vip: false, custom: false, deprecated: false }],
 	},
 	rabbitmq: {
 		host: null,
diff --git a/util/src/entities/Guild.ts b/util/src/entities/Guild.ts
index 3e7e8917..21b059a3 100644
--- a/util/src/entities/Guild.ts
+++ b/util/src/entities/Guild.ts
@@ -41,7 +41,7 @@ export class Guild extends BaseClass {
 	explicit_content_filter?: number;
 
 	@Column({ type: "simple-array" })
-	features: string[];
+	features: string[]; //TODO use enum
 
 	@Column({ nullable: true })
 	icon?: string;