summary refs log tree commit diff
path: root/util/src
diff options
context:
space:
mode:
authorMadeline <46743919+MaddyUnderStars@users.noreply.github.com>2022-04-11 00:25:53 +1000
committerMadeline <46743919+MaddyUnderStars@users.noreply.github.com>2022-04-11 00:26:33 +1000
commitcf9a92383806561ed311634c41a5a1376304d2de (patch)
tree076c5db27a6c51ae7959c2b9dd4d5dc220981d28 /util/src
parentMerge branch 'master' into fix/claim_accounts (diff)
parentUpdate Guild.ts (diff)
downloadserver-cf9a92383806561ed311634c41a5a1376304d2de.tar.xz
Merge branch 'master' into fix/claim_accounts
Diffstat (limited to 'util/src')
-rw-r--r--util/src/entities/Channel.ts37
-rw-r--r--util/src/entities/ClientRelease.ts (renamed from util/src/entities/ClientRelase.ts)4
-rw-r--r--util/src/entities/Config.ts10
-rw-r--r--util/src/entities/ConnectedAccount.ts4
-rw-r--r--util/src/entities/Encryption.ts35
-rw-r--r--util/src/entities/Guild.ts12
-rw-r--r--util/src/entities/Member.ts19
-rw-r--r--util/src/entities/Message.ts9
-rw-r--r--util/src/entities/ReadState.ts3
-rw-r--r--util/src/entities/User.ts22
-rw-r--r--util/src/entities/index.ts2
-rw-r--r--util/src/interfaces/Event.ts1
-rw-r--r--util/src/interfaces/Interaction.ts2
-rw-r--r--util/src/interfaces/Status.ts2
-rw-r--r--util/src/migrations/1648643945733-ReleaseTypo.ts16
-rw-r--r--util/src/util/Rights.ts12
-rw-r--r--util/src/util/TraverseDirectory.ts5
17 files changed, 150 insertions, 45 deletions
diff --git a/util/src/entities/Channel.ts b/util/src/entities/Channel.ts
index 1cc4a538..4bf81901 100644
--- a/util/src/entities/Channel.ts
+++ b/util/src/entities/Channel.ts
@@ -14,19 +14,23 @@ import { Webhook } from "./Webhook";
 import { DmChannelDTO } from "../dtos";

 

 export enum ChannelType {

-	GUILD_TEXT = 0, // a text channel within a server

+	GUILD_TEXT = 0, // a text channel within a guild

 	DM = 1, // a direct message between users

-	GUILD_VOICE = 2, // a voice channel within a server

+	GUILD_VOICE = 2, // a voice channel within a guild

 	GROUP_DM = 3, // a direct message between multiple users

-	GUILD_CATEGORY = 4, // an organizational category that contains up to 50 channels

-	GUILD_NEWS = 5, // a channel that users can follow and crosspost into their own server

-	GUILD_STORE = 6, // a channel in which game developers can sell their game on Discord

+	GUILD_CATEGORY = 4, // an organizational category that contains zero or more channels

+	GUILD_NEWS = 5, // a channel that users can follow and crosspost into a guild or route

+	GUILD_STORE = 6, // a channel in which game developers can sell their things

 	ENCRYPTED = 7, // end-to-end encrypted channel

 	ENCRYPTED_THREAD = 8, // end-to-end encrypted thread channel

+	TRANSACTIONAL = 9, // event chain style transactional channel

 	GUILD_NEWS_THREAD = 10, // a temporary sub-channel within a GUILD_NEWS channel

 	GUILD_PUBLIC_THREAD = 11, // a temporary sub-channel within a GUILD_TEXT channel

 	GUILD_PRIVATE_THREAD = 12, // a temporary sub-channel within a GUILD_TEXT channel that is only viewable by those invited and those with the MANAGE_THREADS permission

 	GUILD_STAGE_VOICE = 13, // a voice channel for hosting events with an audience

+	TICKET_TRACKER = 33, // ticket tracker, individual ticket items shall have type 12

+	KANBAN = 34, // confluence like kanban board

+	VOICELESS_WHITEBOARD = 35, // whiteboard but without voice (whiteboard + voice is the same as stage)

 	CUSTOM_START = 64, // start custom channel types from here

 	UNHANDLED = 255 // unhandled unowned pass-through channel type

 }

@@ -72,7 +76,7 @@ export class Channel extends BaseClass {
 	@ManyToOne(() => Channel)

 	parent?: Channel;

 

-	// only for group dms

+	// for group DMs and owned custom channel types

 	@Column({ nullable: true })

 	@RelationId((channel: Channel) => channel.owner)

 	owner_id: string;

@@ -117,6 +121,9 @@ export class Channel extends BaseClass {
 	})

 	invites?: Invite[];

 

+	@Column({ nullable: true })

+	retention_policy_id?: string;

+

 	@OneToMany(() => Message, (message: Message) => message.channel, {

 		cascade: true,

 		orphanedRowAction: "delete",

@@ -140,7 +147,7 @@ export class Channel extends BaseClass {
 		orphanedRowAction: "delete",

 	})

 	webhooks?: Webhook[];

-

+	

 	// TODO: DM channel

 	static async createChannel(

 		channel: Partial<Channel>,

@@ -182,6 +189,7 @@ export class Channel extends BaseClass {
 

 		switch (channel.type) {

 			case ChannelType.GUILD_TEXT:

+			case ChannelType.GUILD_NEWS:

 			case ChannelType.GUILD_VOICE:

 				if (channel.parent_id && !opts?.skipExistsCheck) {

 					const exists = await Channel.findOneOrFail({ id: channel.parent_id });

@@ -191,25 +199,24 @@ export class Channel extends BaseClass {
 				}

 				break;

 			case ChannelType.GUILD_CATEGORY:

+			case ChannelType.UNHANDLED:

 				break;

 			case ChannelType.DM:

 			case ChannelType.GROUP_DM:

 				throw new HTTPError("You can't create a dm channel in a guild");

-			// TODO: check if guild is community server

 			case ChannelType.GUILD_STORE:

-			case ChannelType.GUILD_NEWS:

 			default:

 				throw new HTTPError("Not yet supported");

 		}

 

 		if (!channel.permission_overwrites) channel.permission_overwrites = [];

-		// TODO: auto generate position

+		// TODO: eagerly auto generate position of all guild channels

 

 		channel = {

 			...channel,

 			...(!opts?.keepId && { id: Snowflake.generate() }),

 			created_at: new Date(),

-			position: channel.position || 0,

+			position: (channel.type === ChannelType.UNHANDLED ? 0 : channel.position) || 0,

 		};

 

 		await Promise.all([

@@ -231,11 +238,13 @@ export class Channel extends BaseClass {
 		const otherRecipientsUsers = await User.find({ where: recipients.map((x) => ({ id: x })) });

 

 		// TODO: check config for max number of recipients

+		/** if you want to disallow note to self channels, uncomment the conditional below

 		if (otherRecipientsUsers.length !== recipients.length) {

 			throw new HTTPError("Recipient/s not found");

 		}

+		**/

 

-		const type = recipients.length === 1 ? ChannelType.DM : ChannelType.GROUP_DM;

+		const type = recipients.length > 1 ? ChannelType.DM : ChannelType.GROUP_DM;

 

 		let channel = null;

 

@@ -288,7 +297,8 @@ export class Channel extends BaseClass {
 			await emitEvent({ event: "CHANNEL_CREATE", data: channel_dto, user_id: creator_user_id });

 		}

 

-		return channel_dto.excludedRecipients([creator_user_id]);

+		if (recipients.length === 1) return channel_dto; 

+		else return channel_dto.excludedRecipients([creator_user_id]);

 	}

 

 	static async removeRecipientFromChannel(channel: Channel, user_id: string) {

@@ -354,4 +364,5 @@ export interface ChannelPermissionOverwrite {
 export enum ChannelPermissionOverwriteType {

 	role = 0,

 	member = 1,

+	group = 2,

 }

diff --git a/util/src/entities/ClientRelase.ts b/util/src/entities/ClientRelease.ts
index e021b82b..c5afd307 100644
--- a/util/src/entities/ClientRelase.ts
+++ b/util/src/entities/ClientRelease.ts
@@ -1,8 +1,8 @@
 import { Column, Entity} from "typeorm";
 import { BaseClass } from "./BaseClass";
 
-@Entity("client_relase")
-export class Relase extends BaseClass {
+@Entity("client_release")
+export class Release extends BaseClass {
 	@Column()
 	name: string;
 
diff --git a/util/src/entities/Config.ts b/util/src/entities/Config.ts
index f4a266dc..8d29b387 100644
--- a/util/src/entities/Config.ts
+++ b/util/src/entities/Config.ts
@@ -188,8 +188,8 @@ export interface ConfigValue {
 	},
 	client: {
 		useTestClient: Boolean;
-		relases: {
-			useLocalRelases: Boolean; //TODO
+		releases: {
+			useLocalRelease: Boolean; //TODO
 			upstreamVersion: string;
 		}
 	},
@@ -222,7 +222,7 @@ export const DefaultConfigOptions: ConfigValue = {
 	},
 	general: {
 		instanceName: "Fosscord Instance",
-		instanceDescription: "This is a Fosscord instance made in pre-relase days",
+		instanceDescription: "This is a Fosscord instance made in pre-release days",
 		frontPage: null,
 		tosPage: null,
 		correspondenceEmail: "noreply@localhost.local",
@@ -389,8 +389,8 @@ export const DefaultConfigOptions: ConfigValue = {
 	},
 	client: {
 		useTestClient: true,
-		relases: {
-			useLocalRelases: true,
+		releases: {
+			useLocalRelease: true,
 			upstreamVersion: "0.0.264"
 		}
 	},
diff --git a/util/src/entities/ConnectedAccount.ts b/util/src/entities/ConnectedAccount.ts
index b8aa2889..09ae30ab 100644
--- a/util/src/entities/ConnectedAccount.ts
+++ b/util/src/entities/ConnectedAccount.ts
@@ -2,7 +2,7 @@ import { Column, Entity, JoinColumn, ManyToOne, RelationId } from "typeorm";
 import { BaseClass } from "./BaseClass";
 import { User } from "./User";
 
-export interface PublicConnectedAccount extends Pick<ConnectedAccount, "name" | "type" | "verifie"> {}
+export interface PublicConnectedAccount extends Pick<ConnectedAccount, "name" | "type" | "verified"> {}
 
 @Entity("connected_accounts")
 export class ConnectedAccount extends BaseClass {
@@ -35,7 +35,7 @@ export class ConnectedAccount extends BaseClass {
 	type: string;
 
 	@Column()
-	verifie: boolean;
+	verified: boolean;
 
 	@Column({ select: false })
 	visibility: number;
diff --git a/util/src/entities/Encryption.ts b/util/src/entities/Encryption.ts
new file mode 100644
index 00000000..3b82ff84
--- /dev/null
+++ b/util/src/entities/Encryption.ts
@@ -0,0 +1,35 @@
+import { Column, Entity, JoinColumn, ManyToOne, OneToMany, RelationId } from "typeorm";
+import { BaseClass } from "./BaseClass";
+import { Guild } from "./Guild";
+import { PublicUserProjection, User } from "./User";
+import { HTTPError } from "lambert-server";
+import { containsAll, emitEvent, getPermission, Snowflake, trimSpecial, InvisibleCharacters } from "../util";
+import { BitField, BitFieldResolvable, BitFlag } from "../util/BitField";
+import { Recipient } from "./Recipient";
+import { Message } from "./Message";
+import { ReadState } from "./ReadState";
+import { Invite } from "./Invite";
+import { DmChannelDTO } from "../dtos";
+
+@Entity("security_settings")
+export class SecuritySettings extends BaseClass {
+
+  @Column({nullable: true})
+  guild_id: Snowflake;
+
+  @Column({nullable: true})
+  channel_id: Snowflake;
+
+  @Column()
+  encryption_permission_mask: BitField;
+
+  @Column()
+  allowed_algorithms: string[];
+
+  @Column()
+  current_algorithm: string;
+
+  @Column({nullable: true})
+  used_since_message: Snowflake;
+
+}
diff --git a/util/src/entities/Guild.ts b/util/src/entities/Guild.ts
index 9ac148ee..70bb41c5 100644
--- a/util/src/entities/Guild.ts
+++ b/util/src/entities/Guild.ts
@@ -187,11 +187,11 @@ export class Guild extends BaseClass {
 
 	@Column({ nullable: true })
 	@RelationId((guild: Guild) => guild.owner)
-	owner_id: string;
+	owner_id?: string; // optional to allow for ownerless guilds
 
 	@JoinColumn({ name: "owner_id", referencedColumnName: "id" })
 	@ManyToOne(() => User)
-	owner: User;
+	owner?: User; // optional to allow for ownerless guilds
 
 	@Column({ nullable: true })
 	preferred_locale?: string;
@@ -200,7 +200,7 @@ export class Guild extends BaseClass {
 	premium_subscription_count?: number;
 
 	@Column({ nullable: true })
-	premium_tier?: number; // nitro boost level
+	premium_tier?: number; // crowd premium level
 
 	@Column({ nullable: true })
 	@RelationId((guild: Guild) => guild.public_updates_channel)
@@ -269,6 +269,10 @@ export class Guild extends BaseClass {
 
 	@Column({ nullable: true })
 	nsfw?: boolean;
+	
+	// TODO: nested guilds
+	@Column({ nullable: true })
+	parent?: string;
 
 	// only for developer portal
 	permissions?: number;
@@ -308,7 +312,7 @@ export class Guild extends BaseClass {
 			verification_level: 0,
 			welcome_screen: {
 				enabled: false,
-				description: "No description",
+				description: "Fill in your description",
 				welcome_channels: [],
 			},
 			widget_enabled: true, // NB: don't set it as false to prevent artificial restrictions
diff --git a/util/src/entities/Member.ts b/util/src/entities/Member.ts
index 3c5f9db0..fe2d5590 100644
--- a/util/src/entities/Member.ts
+++ b/util/src/entities/Member.ts
@@ -70,7 +70,7 @@ export class Member extends BaseClassWithoutId {
 
 	@Column({ nullable: true })
 	nick?: string;
-
+	
 	@JoinTable({
 		name: "member_roles",
 		joinColumn: { name: "index", referencedColumnName: "index" },
@@ -85,8 +85,8 @@ export class Member extends BaseClassWithoutId {
 	@Column()
 	joined_at: Date;
 
-	@Column({ nullable: true })
-	premium_since?: Date;
+	@Column({ type: "bigint", nullable: true })
+	premium_since?: number;
 
 	@Column()
 	deaf: boolean;
@@ -102,8 +102,17 @@ export class Member extends BaseClassWithoutId {
 
 	@Column({ nullable: true })
 	last_message_id?: string;
+	
+	/**
+	@JoinColumn({ name: "id" })
+	@ManyToOne(() => User, {
+		onDelete: "DO NOTHING",
+	// do not auto-kick force-joined members just because their joiners left the server
+	}) **/
+	@Column({ nullable: true})
+	joined_by?: string;
 
-	// TODO: update
+	// TODO: add this when we have proper read receipts
 	// @Column({ type: "simple-json" })
 	// read_state: ReadState;
 
@@ -245,7 +254,7 @@ export class Member extends BaseClassWithoutId {
 			nick: undefined,
 			roles: [guild_id], // @everyone role
 			joined_at: new Date(),
-			premium_since: new Date(),
+			premium_since: (new Date()).getTime(),
 			deaf: false,
 			mute: false,
 			pending: false,
diff --git a/util/src/entities/Message.ts b/util/src/entities/Message.ts
index e577d5df..b32bbd94 100644
--- a/util/src/entities/Message.ts
+++ b/util/src/entities/Message.ts
@@ -41,8 +41,14 @@ export enum MessageType {
 	CHANNEL_FOLLOW_ADD = 12,
 	GUILD_DISCOVERY_DISQUALIFIED = 14,
 	GUILD_DISCOVERY_REQUALIFIED = 15,
+	ENCRYPTED = 16,
 	REPLY = 19,
 	APPLICATION_COMMAND = 20,
+	ROUTE_ADDED = 41, // custom message routing: new route affecting that channel
+	ROUTE_DISABLED = 42, // custom message routing: given route no longer affecting that channel
+	ENCRYPTION = 50,
+	CUSTOM_START = 63,
+	UNHANDLED = 255
 }
 
 @Entity("messages")
@@ -84,7 +90,7 @@ export class Message extends BaseClass {
 	@RelationId((message: Message) => message.member)
 	member_id: string;
 
-	@JoinColumn({ name: "author_id", referencedColumnName: "id" })
+	@JoinColumn({ name: "member_id", referencedColumnName: "id" })
 	@ManyToOne(() => User, {
 		onDelete: "CASCADE",
 	})
@@ -203,6 +209,7 @@ export interface MessageComponent {
 }
 
 export enum MessageComponentType {
+	Script = 0, // self command script
 	ActionRow = 1,
 	Button = 2,
 }
diff --git a/util/src/entities/ReadState.ts b/util/src/entities/ReadState.ts
index e6d73105..b915573b 100644
--- a/util/src/entities/ReadState.ts
+++ b/util/src/entities/ReadState.ts
@@ -49,6 +49,7 @@ export class ReadState extends BaseClass {
 	@Column({ nullable: true })
 	mention_count: number;
 
-	@Column({ nullable: true })
+	// @Column({ nullable: true })
+	// TODO: derive this from (last_message_id=notifications_cursor=public_ack)=true
 	manual: boolean;
 }
diff --git a/util/src/entities/User.ts b/util/src/entities/User.ts
index 1d18c838..a5c4c136 100644
--- a/util/src/entities/User.ts
+++ b/util/src/entities/User.ts
@@ -60,7 +60,7 @@ export class User extends BaseClass {
 	username: string; // username max length 32, min 2 (should be configurable)
 
 	@Column()
-	discriminator: string; // #0001 4 digit long string from #0001 - #9999
+	discriminator: string; // opaque string: 4 digits on discord.com
 
 	setDiscriminator(val: string) {
 		const number = Number(val);
@@ -88,10 +88,10 @@ export class User extends BaseClass {
 	mobile: boolean; // if the user has mobile app installed
 
 	@Column()
-	premium: boolean; // if user bought nitro
-
+	premium: boolean; // if user bought individual premium
+	
 	@Column()
-	premium_type: number; // nitro level
+	premium_type: number; // individual premium level
 
 	@Column()
 	bot: boolean; // if user is bot
@@ -100,11 +100,11 @@ export class User extends BaseClass {
 	bio: string; // short description of the user (max 190 chars -> should be configurable)
 
 	@Column()
-	system: boolean; // shouldn't be used, the api sents this field type true, if the generated message comes from a system generated author
+	system: boolean; // shouldn't be used, the api sends this field type true, if the generated message comes from a system generated author
 
 	@Column({ select: false })
-	nsfw_allowed: boolean; // if the user is older than 18 (resp. Config)
-
+	nsfw_allowed: boolean; // if the user can do age-restricted actions (NSFW channels/guilds/commands)
+	
 	@Column({ select: false })
 	mfa_enabled: boolean; // if multi factor authentication is enabled
 
@@ -132,7 +132,7 @@ export class User extends BaseClass {
 	@Column()
 	public_flags: number;
 
-	@Column()
+	@Column({ type: "bigint" })
 	rights: string; // Rights
 
 	@OneToMany(() => Session, (session: Session) => session.user)
@@ -164,6 +164,9 @@ export class User extends BaseClass {
 	@Column({ type: "simple-json", select: false })
 	settings: UserSettings;
 
+	@Column({ type: "simple-json" })
+	notes: { [key: string]: string };	//key is ID of user
+
 	toPublicUser() {
 		const user: any = {};
 		PublicUserProjection.forEach((x) => {
@@ -271,6 +274,7 @@ export class User extends BaseClass {
 			},
 			settings: { ...defaultSettings, locale: language },
 			fingerprints: [],
+			notes: {},
 		});
 
 		await user.save();
@@ -360,7 +364,7 @@ export interface UserSettings {
 	render_reactions: boolean;
 	restricted_guilds: string[];
 	show_current_game: boolean;
-	status: "online" | "offline" | "dnd" | "idle";
+	status: "online" | "offline" | "dnd" | "idle" | "invisible";
 	stream_notifications_enabled: boolean;
 	theme: "dark" | "white"; // dark
 	timezone_offset: number; // e.g -60
diff --git a/util/src/entities/index.ts b/util/src/entities/index.ts
index fc18d422..f023d5a6 100644
--- a/util/src/entities/index.ts
+++ b/util/src/entities/index.ts
@@ -27,4 +27,4 @@ export * from "./Template";
 export * from "./User";
 export * from "./VoiceState";
 export * from "./Webhook";
-export * from "./ClientRelase";
\ No newline at end of file
+export * from "./ClientRelease";
\ No newline at end of file
diff --git a/util/src/interfaces/Event.ts b/util/src/interfaces/Event.ts
index a5253c09..416082ed 100644
--- a/util/src/interfaces/Event.ts
+++ b/util/src/interfaces/Event.ts
@@ -623,6 +623,7 @@ export type EVENT =
 	| "PRESENCE_UPDATE"
 	| "TYPING_START"
 	| "USER_UPDATE"
+	| "USER_NOTE_UPDATE"
 	| "WEBHOOKS_UPDATE"
 	| "INTERACTION_CREATE"
 	| "VOICE_STATE_UPDATE"
diff --git a/util/src/interfaces/Interaction.ts b/util/src/interfaces/Interaction.ts
index 3cafb2d5..5d3aae24 100644
--- a/util/src/interfaces/Interaction.ts
+++ b/util/src/interfaces/Interaction.ts
@@ -12,11 +12,13 @@ export interface Interaction {
 }
 
 export enum InteractionType {
+	SelfCommand = 0,
 	Ping = 1,
 	ApplicationCommand = 2,
 }
 
 export enum InteractionResponseType {
+	SelfCommandResponse = 0,
 	Pong = 1,
 	Acknowledge = 2,
 	ChannelMessage = 3,
diff --git a/util/src/interfaces/Status.ts b/util/src/interfaces/Status.ts
index c4dab586..5d2e1bba 100644
--- a/util/src/interfaces/Status.ts
+++ b/util/src/interfaces/Status.ts
@@ -1,4 +1,4 @@
-export type Status = "idle" | "dnd" | "online" | "offline";
+export type Status = "idle" | "dnd" | "online" | "offline" | "invisible";
 
 export interface ClientStatus {
 	desktop?: string; // e.g. Windows/Linux/Mac
diff --git a/util/src/migrations/1648643945733-ReleaseTypo.ts b/util/src/migrations/1648643945733-ReleaseTypo.ts
new file mode 100644
index 00000000..944b9dd9
--- /dev/null
+++ b/util/src/migrations/1648643945733-ReleaseTypo.ts
@@ -0,0 +1,16 @@
+import { MigrationInterface, QueryRunner } from "typeorm";
+
+export class ReleaseTypo1648643945733 implements MigrationInterface {
+	name = "ReleaseTypo1648643945733";
+
+	public async up(queryRunner: QueryRunner): Promise<void> {
+		//drop table first because typeorm creates it before migrations run
+		await queryRunner.dropTable("client_release", true);
+		await queryRunner.renameTable("client_relase", "client_release");
+	}
+
+	public async down(queryRunner: QueryRunner): Promise<void> {
+		await queryRunner.dropTable("client_relase", true);
+		await queryRunner.renameTable("client_release", "client_relase");
+	}
+}
diff --git a/util/src/util/Rights.ts b/util/src/util/Rights.ts
index 9a99d393..35ad9514 100644
--- a/util/src/util/Rights.ts
+++ b/util/src/util/Rights.ts
@@ -1,6 +1,7 @@
 import { BitField } from "./BitField";
 import "missing-native-js-functions";
 import { BitFieldResolvable, BitFlag } from "./BitField";
+import { User } from "../entities";
 
 var HTTPError: any;
 
@@ -65,6 +66,8 @@ export class Rights extends BitField {
 		// inverts the presence confidentiality default (OPERATOR's presence is not routed by default, others' are) for a given user
 		SELF_ADD_DISCOVERABLE: BitFlag(36), // can mark discoverable guilds that they have permissions to mark as discoverable
 		MANAGE_GUILD_DIRECTORY: BitFlag(37), // can change anything in the primary guild directory
+		POGGERS: BitFlag(38), // can send confetti, screenshake, random user mention (@someone)
+		USE_ACHIEVEMENTS: BitFlag(39), // can use achievements and cheers
 		INITIATE_INTERACTIONS: BitFlag(40), // can initiate interactions
 		RESPOND_TO_INTERACTIONS: BitFlag(41), // can respond to interactions
 		SEND_BACKDATED_EVENTS: BitFlag(42), // can send backdated events
@@ -83,6 +86,15 @@ export class Rights extends BitField {
 		// @ts-ignore
 		throw new HTTPError(`You are missing the following rights ${permission}`, 403);
 	}
+	
 }
 
 const ALL_RIGHTS = Object.values(Rights.FLAGS).reduce((total, val) => total | val, BigInt(0));
+
+export async function getRights(	user_id: string
+	/**, opts: {
+		in_behalf?: (keyof User)[];
+	} = {} **/) {
+	let user = await User.findOneOrFail({ where: { id: user_id } });
+	return new Rights(user.rights);
+} 
diff --git a/util/src/util/TraverseDirectory.ts b/util/src/util/TraverseDirectory.ts
index 275b7dcc..3d0d6279 100644
--- a/util/src/util/TraverseDirectory.ts
+++ b/util/src/util/TraverseDirectory.ts
@@ -1,6 +1,9 @@
 import { Server, traverseDirectory } from "lambert-server";
 
-const DEFAULT_FILTER = /^([^\.].*)(?<!\.d)\.(js)$/;
+//if we're using ts-node, use ts files instead of js
+const extension = Symbol.for("ts-node.register.instance") in process ? "ts" : "js"
+
+const DEFAULT_FILTER = new RegExp("^([^\.].*)(?<!\.d)\.(" + extension + ")$");
 
 export function registerRoutes(server: Server, root: string) {
 	return traverseDirectory(