Merge branch 'master' into fix/claim_accounts
This commit is contained in:
		
						commit
						4304b77987
					
				
							
								
								
									
										45
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										45
									
								
								Dockerfile
									
									
									
									
									
								
							| @ -1,7 +1,40 @@ | ||||
| FROM node:14 | ||||
| WORKDIR /usr/src/fosscord-server/ | ||||
| COPY . . | ||||
| WORKDIR /usr/src/fosscord-server/bundle | ||||
| FROM node:alpine | ||||
| 
 | ||||
| # env vars | ||||
| ENV WORK_DIR="/srv/fosscord-server" | ||||
| ENV DEV_MODE=0 | ||||
| ENV HTTP_PORT=3001 | ||||
| ENV WS_PORT=3002 | ||||
| ENV CDN_PORT=3003 | ||||
| ENV RTC_PORT=3004 | ||||
| ENV ADMIN_PORT=3005 | ||||
| 
 | ||||
| # exposed ports (only for reference, see https://docs.docker.com/engine/reference/builder/#expose) | ||||
| EXPOSE ${HTTP_PORT}/tcp ${WS_PORT}/tcp ${CDN_PORT}/tcp ${RTC_PORT}/tcp ${ADMIN_PORT}/tcp | ||||
| 
 | ||||
| # install required apps | ||||
| RUN apk add --no-cache --update git python2 py-pip make build-base | ||||
| 
 | ||||
| # optionl: packages for debugging/development | ||||
| RUN apk add --no-cache sqlite | ||||
| 
 | ||||
| # download fosscord-server | ||||
| WORKDIR $WORK_DIR/src | ||||
| RUN git clone https://github.com/fosscord/fosscord-server.git . | ||||
| 
 | ||||
| # setup and run | ||||
| WORKDIR $WORK_DIR/src/bundle | ||||
| RUN npm run setup | ||||
| EXPOSE 3001 | ||||
| CMD [ "npm", "run", "start:bundle" ] | ||||
| RUN npm install @yukikaze-bot/erlpack | ||||
| # RUN npm install mysql --save | ||||
| 
 | ||||
| # create update script | ||||
| RUN printf '#!/bin/sh\n\ngit -C $WORK_DIR/src/ checkout master\ngit -C $WORK_DIR/src/ reset --hard HEAD\ngit -C $WORK_DIR/src/ pull\ncd $WORK_DIR/src/bundle/\nnpm run setup\n' > $WORK_DIR/update.sh | ||||
| RUN chmod +x $WORK_DIR/update.sh | ||||
| 
 | ||||
| # configure entrypoint file | ||||
| RUN printf '#!/bin/sh\n\nDEV_MODE=${DEV_MODE:-0}\n\nif [ "$DEV_MODE" -eq 1 ]; then\n    tail -f /dev/null\nelse\n    cd $WORK_DIR/src/bundle/\n    npm run start:bundle\nfi\n' > $WORK_DIR/entrypoint.sh | ||||
| RUN chmod +x $WORK_DIR/entrypoint.sh | ||||
| 
 | ||||
| WORKDIR $WORK_DIR | ||||
| ENTRYPOINT ["./entrypoint.sh"] | ||||
|  | ||||
| @ -30,6 +30,6 @@ This repository contains: | ||||
| 
 | ||||
| -   [Contributing](https://docs.fosscord.com/contributing/server/) | ||||
| 
 | ||||
| ## [Setup](https://docs.fosscord.com/setup/server/) | ||||
| ## [Setup](https://docs.fosscord.com/server/setup/) | ||||
| 
 | ||||
| -   [Download](https://github.com/fosscord/fosscord-server/releases) | ||||
|  | ||||
| @ -2,7 +2,7 @@ | ||||
| 	"openapi": "3.0.0", | ||||
| 	"servers": [ | ||||
| 		{ | ||||
| 			"url": "https://api.fosscord.com/v{version}", | ||||
| 			"url": "https://api.fosscord.com/api/v{version}", | ||||
| 			"description": "Official fosscord instance", | ||||
| 			"variables": { | ||||
| 				"version": { | ||||
| @ -2960,7 +2960,7 @@ | ||||
| 					"type": { | ||||
| 						"type": "string" | ||||
| 					}, | ||||
| 					"verifie": { | ||||
| 					"verified": { | ||||
| 						"type": "boolean" | ||||
| 					}, | ||||
| 					"visibility": { | ||||
| @ -2980,7 +2980,7 @@ | ||||
| 					"type", | ||||
| 					"user", | ||||
| 					"user_id", | ||||
| 					"verifie", | ||||
| 					"verified", | ||||
| 					"visibility" | ||||
| 				] | ||||
| 			}, | ||||
| @ -3119,7 +3119,7 @@ | ||||
| 						"type": "boolean" | ||||
| 					}, | ||||
| 					"status": { | ||||
| 						"enum": ["dnd", "idle", "offline", "online"], | ||||
| 						"enum": ["dnd", "idle", "offline", "online", "invisible"], | ||||
| 						"type": "string" | ||||
| 					}, | ||||
| 					"stream_notifications_enabled": { | ||||
| @ -5677,7 +5677,7 @@ | ||||
| 						"type": "boolean" | ||||
| 					}, | ||||
| 					"status": { | ||||
| 						"enum": ["dnd", "idle", "offline", "online"], | ||||
| 						"enum": ["dnd", "idle", "offline", "online", "invisible"], | ||||
| 						"type": "string" | ||||
| 					}, | ||||
| 					"stream_notifications_enabled": { | ||||
|  | ||||
							
								
								
									
										12
									
								
								api/assets/preload-plugins/fosscord-login.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								api/assets/preload-plugins/fosscord-login.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| // Remove `<link id="logincss" rel="stylesheet" href="/assets/fosscord-login.css" />` from header when we're not accessing `/login` or `/register`
 | ||||
| // fosscord-login.css replaces discord's TOS tooltip with something more fitting for fosscord, which when included in the main app, causes other tooltips
 | ||||
| // to be affected, which is potentially unwanted.
 | ||||
| //
 | ||||
| // This script removes fosscord-login.css when a user reloads the page. From testing, it appears fosscord already properly removes
 | ||||
| // fosscord-login.css after login is successful, but not if you reload the page after logging in. This script is to remove fosscord-login.css in
 | ||||
| // that specific case.
 | ||||
| 
 | ||||
| var token = JSON.parse(localStorage.getItem("token")); | ||||
| if (!token && location.pathname !== "/login" && location.pathname !== "/register") { | ||||
| 	document.getElementById("logincss").remove(); | ||||
| } | ||||
| @ -355,11 +355,11 @@ | ||||
| 					"type": { | ||||
| 						"type": "string" | ||||
| 					}, | ||||
| 					"verifie": { | ||||
| 					"verified": { | ||||
| 						"type": "boolean" | ||||
| 					} | ||||
| 				}, | ||||
| 				"required": ["name", "type", "verifie"] | ||||
| 				"required": ["name", "type", "verified"] | ||||
| 			} | ||||
| 		}, | ||||
| 		"$schema": "http://json-schema.org/draft-07/schema#" | ||||
| @ -7900,7 +7900,7 @@ | ||||
| 				"type": "boolean" | ||||
| 			}, | ||||
| 			"status": { | ||||
| 				"enum": ["dnd", "idle", "offline", "online"], | ||||
| 				"enum": ["dnd", "idle", "offline", "online", "invisible"], | ||||
| 				"type": "string" | ||||
| 			}, | ||||
| 			"stream_notifications_enabled": { | ||||
|  | ||||
| @ -7,12 +7,12 @@ | ||||
| 		"BASE_TYPE_BOOLEAN": "This field must be a boolean", | ||||
| 		"BASE_TYPE_CHOICES": "This field must be one of ({{types}})", | ||||
| 		"BASE_TYPE_CLASS": "This field must be an instance of {{type}}", | ||||
| 		"BASE_TYPE_OBJECT": "This field must be an object", | ||||
| 		"BASE_TYPE_ARRAY": "This field must be an array", | ||||
| 		"UNKOWN_FIELD": "Unknown key: {{key}}", | ||||
| 		"BASE_TYPE_CONSTANT": "This field must be {{value}}", | ||||
| 		"EMAIL_TYPE_INVALID_EMAIL": "Not a well-formed email address", | ||||
| 		"DATE_TYPE_PARSE": "Could not parse {{date}}. Should be ISO8601", | ||||
| 		"BASE_TYPE_BAD_LENGTH": "Must be between {{length}} in length" | ||||
| 		"BASE_TYPE_OBJECT": "שדה זה חייב להיות אובייקט", | ||||
| 		"BASE_TYPE_ARRAY": "שדה זה חייב להיות מערך", | ||||
| 		"UNKOWN_FIELD": "מפתח לא ידוע: {{key}}", | ||||
| 		"BASE_TYPE_CONSTANT": "שדה זה להיות {{value}}", | ||||
| 		"EMAIL_TYPE_INVALID_EMAIL": "כתובת דואר אלקטרוני לא חוקית", | ||||
| 		"DATE_TYPE_PARSE": "לא ניתן לנתח {{date}}. צריך להיות ISO8601", | ||||
| 		"BASE_TYPE_BAD_LENGTH": "האורך חייב להיות בין {{length}}" | ||||
| 	} | ||||
| } | ||||
|  | ||||
							
								
								
									
										6397
									
								
								api/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										6397
									
								
								api/package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -49,7 +49,7 @@ | ||||
| 		"@types/morgan": "^1.9.3", | ||||
| 		"@types/multer": "^1.4.5", | ||||
| 		"@types/node": "^14.17.9", | ||||
| 		"@types/node-fetch": "^2.5.7", | ||||
| 		"@types/node-fetch": "^2.5.5", | ||||
| 		"@types/supertest": "^2.0.11", | ||||
| 		"@zerollup/ts-transform-paths": "^1.7.18", | ||||
| 		"jest": "^27.2.5", | ||||
| @ -86,7 +86,7 @@ | ||||
| 		"missing-native-js-functions": "^1.2.18", | ||||
| 		"morgan": "^1.10.0", | ||||
| 		"multer": "^1.4.2", | ||||
| 		"node-fetch": "^3.1.1", | ||||
| 		"node-fetch": "^2.6.2", | ||||
| 		"patch-package": "^6.4.7", | ||||
| 		"picocolors": "^1.0.0", | ||||
| 		"proxy-agent": "^5.0.0", | ||||
|  | ||||
| @ -26,6 +26,6 @@ DROP TABLE webhooks; | ||||
| DROP TABLE channels; | ||||
| DROP TABLE members; | ||||
| DROP TABLE guilds; | ||||
| DROP TABLE client_relase; | ||||
| DROP TABLE client_release; | ||||
| -- DROP TABLE users; | ||||
| -- DROP TABLE config; | ||||
| @ -15,6 +15,7 @@ export const NO_AUTHORIZATION_ROUTES = [ | ||||
| 	"/experiments", | ||||
| 	"/updates", | ||||
| 	"/downloads/", | ||||
| 	"/scheduled-maintenances/upcoming.json", | ||||
| 	// Public kubernetes integration
 | ||||
| 	"/-/readyz", | ||||
| 	"/-/healthz", | ||||
|  | ||||
| @ -19,7 +19,8 @@ export interface InviteCreateSchema { | ||||
| 	target_user_type?: number; | ||||
| } | ||||
| 
 | ||||
| router.post("/", route({ body: "InviteCreateSchema", permission: "CREATE_INSTANT_INVITE" }), async (req: Request, res: Response) => { | ||||
| router.post("/", route({ body: "InviteCreateSchema", permission: "CREATE_INSTANT_INVITE", right: "CREATE_INVITES" }), | ||||
| 			async (req: Request, res: Response) => { | ||||
| 	const { user_id } = req; | ||||
| 	const { channel_id } = req.params; | ||||
| 	const channel = await Channel.findOneOrFail({ where: { id: channel_id }, select: ["id", "name", "type", "guild_id"] }); | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| import { Channel, emitEvent, getPermission, MessageDeleteEvent, Message, MessageUpdateEvent } from "@fosscord/util"; | ||||
| import { Channel, emitEvent, getPermission, getRights, MessageDeleteEvent, Message, MessageUpdateEvent } from "@fosscord/util"; | ||||
| import { Router, Response, Request } from "express"; | ||||
| import { route } from "@fosscord/api"; | ||||
| import { handleMessage, postHandleMessage } from "@fosscord/api"; | ||||
| @ -7,18 +7,23 @@ import { MessageCreateSchema } from "../index"; | ||||
| const router = Router(); | ||||
| // TODO: message content/embed string length limit
 | ||||
| 
 | ||||
| router.patch("/", route({ body: "MessageCreateSchema", permission: "SEND_MESSAGES" }), async (req: Request, res: Response) => { | ||||
| router.patch("/", route({ body: "MessageCreateSchema", permission: "SEND_MESSAGES", right: "SEND_MESSAGES" }), async (req: Request, res: Response) => { | ||||
| 	const { message_id, channel_id } = req.params; | ||||
| 	var body = req.body as MessageCreateSchema; | ||||
| 
 | ||||
| 	const message = await Message.findOneOrFail({ where: { id: message_id, channel_id }, relations: ["attachments"] }); | ||||
| 
 | ||||
| 	const permissions = await getPermission(req.user_id, undefined, channel_id); | ||||
| 	 | ||||
| 	const rights = await getRights(req.user_id); | ||||
| 
 | ||||
| 	if (req.user_id !== message.author_id) { | ||||
| 		permissions.hasThrow("MANAGE_MESSAGES"); | ||||
| 		body = { flags: body.flags }; // admins can only suppress embeds of other messages
 | ||||
| 	} | ||||
| 	if ((req.user_id !== message.author_id)) { | ||||
| 		if (!rights.has("MANAGE_MESSAGES")) { | ||||
| 			permissions.hasThrow("MANAGE_MESSAGES"); | ||||
| 			body = { flags: body.flags }; | ||||
| // guild admins can only suppress embeds of other messages, no such restriction imposed to instance-wide admins
 | ||||
| 		} | ||||
| 	} else rights.hasThrow("SELF_EDIT_MESSAGES"); | ||||
| 
 | ||||
| 	const new_message = await handleMessage({ | ||||
| 		...message, | ||||
| @ -46,17 +51,20 @@ router.patch("/", route({ body: "MessageCreateSchema", permission: "SEND_MESSAGE | ||||
| 	return res.json(message); | ||||
| }); | ||||
| 
 | ||||
| // permission check only if deletes messagr from other user
 | ||||
| router.delete("/", route({}), async (req: Request, res: Response) => { | ||||
| 	const { message_id, channel_id } = req.params; | ||||
| 
 | ||||
| 	const channel = await Channel.findOneOrFail({ id: channel_id }); | ||||
| 	const message = await Message.findOneOrFail({ id: message_id }); | ||||
| 	 | ||||
| 	const rights = await getRights(req.user_id); | ||||
| 
 | ||||
| 	if (message.author_id !== req.user_id) { | ||||
| 		const permission = await getPermission(req.user_id, channel.guild_id, channel_id); | ||||
| 		permission.hasThrow("MANAGE_MESSAGES"); | ||||
| 	} | ||||
| 	if ((message.author_id !== req.user_id)) { | ||||
| 		if (!rights.has("MANAGE_MESSAGES")) { | ||||
| 			const permission = await getPermission(req.user_id, channel.guild_id, channel_id); | ||||
| 			permission.hasThrow("MANAGE_MESSAGES"); | ||||
| 		} | ||||
| 	} else rights.hasThrow("SELF_DELETE_MESSAGES"); | ||||
| 
 | ||||
| 	await Message.delete({ id: message_id }); | ||||
| 
 | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| import { Router, Response, Request } from "express"; | ||||
| import { route } from "@fosscord/api"; | ||||
| import { Relase, Config } from "@fosscord/util"; | ||||
| import { Release, Config } from "@fosscord/util"; | ||||
| 
 | ||||
| const router = Router(); | ||||
| 
 | ||||
| @ -12,9 +12,9 @@ router.get("/:branch", route({}), async (req: Request, res: Response) => { | ||||
| 
 | ||||
| 	if(!platform || !["linux", "osx", "win"].includes(platform.toString())) return res.status(404) | ||||
| 
 | ||||
| 	const relase = await Relase.findOneOrFail({ name: client.relases.upstreamVersion }); | ||||
| 	const release = await Release.findOneOrFail({ name: client.releases.upstreamVersion }); | ||||
| 
 | ||||
| 	res.redirect(relase[`win_url`]); | ||||
| 	res.redirect(release[`win_url`]); | ||||
| }); | ||||
| 
 | ||||
| export default router; | ||||
|  | ||||
| @ -33,17 +33,32 @@ router.get("/", route({ permission: "BAN_MEMBERS" }), async (req: Request, res: | ||||
| 	const { guild_id } = req.params; | ||||
| 
 | ||||
| 	let bans = await Ban.find({ guild_id: guild_id }); | ||||
| 	let promisesToAwait: object[] = []; | ||||
| 	const bansObj: object[] = []; | ||||
| 
 | ||||
| 	/* Filter secret from database registry.*/ | ||||
| 	bans.filter((ban) => ban.user_id !== ban.executor_id); // pretend self-bans don't exist to prevent victim chasing
 | ||||
| 
 | ||||
| 	bans.filter(ban => ban.user_id !== ban.executor_id); | ||||
| 	// pretend self-bans don't exist to prevent victim chasing
 | ||||
| 	 | ||||
| 	bans.forEach((registry: BanRegistrySchema) => { | ||||
| 	delete registry.ip; | ||||
| 	bans.forEach((ban) => { | ||||
| 		promisesToAwait.push(User.getPublicUser(ban.user_id)); | ||||
| 	}); | ||||
| 	 | ||||
| 	return res.json(bans); | ||||
| 
 | ||||
| 	const bannedUsers: object[] = await Promise.all(promisesToAwait); | ||||
| 
 | ||||
| 	bans.forEach((ban, index) => { | ||||
| 		const user = bannedUsers[index] as User; | ||||
| 		bansObj.push({ | ||||
| 			reason: ban.reason, | ||||
| 			user: { | ||||
| 				username: user.username, | ||||
| 				discriminator: user.discriminator, | ||||
| 				id: user.id, | ||||
| 				avatar: user.avatar, | ||||
| 				public_flags: user.public_flags | ||||
| 			} | ||||
| 		}); | ||||
| 	}); | ||||
| 
 | ||||
| 	return res.json(bansObj); | ||||
| }); | ||||
| 
 | ||||
| router.get("/:user", route({ permission: "BAN_MEMBERS" }), async (req: Request, res: Response) => { | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| import { Request, Response, Router } from "express"; | ||||
| import { emitEvent, getPermission, Guild, GuildUpdateEvent, handleFile, Member } from "@fosscord/util"; | ||||
| import { DiscordApiErrors, emitEvent, getPermission, getRights, Guild, GuildUpdateEvent, handleFile, Member } from "@fosscord/util"; | ||||
| import { HTTPError } from "lambert-server"; | ||||
| import { route } from "@fosscord/api"; | ||||
| import "missing-native-js-functions"; | ||||
| @ -37,9 +37,17 @@ router.get("/", route({}), async (req: Request, res: Response) => { | ||||
| 	return res.send(guild); | ||||
| }); | ||||
| 
 | ||||
| router.patch("/", route({ body: "GuildUpdateSchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { | ||||
| router.patch("/", route({ body: "GuildUpdateSchema"}), async (req: Request, res: Response) => { | ||||
| 	const body = req.body as GuildUpdateSchema; | ||||
| 	const { guild_id } = req.params; | ||||
| 	 | ||||
| 	 | ||||
| 	const rights = await getRights(req.user_id); | ||||
| 	const permission = await getPermission(req.user_id, guild_id); | ||||
| 	 | ||||
| 	if (!rights.has("MANAGE_GUILDS")||!permission.has("MANAGE_GUILD")) | ||||
| 		throw DiscordApiErrors.MISSING_PERMISSIONS.withParams("MANAGE_GUILD"); | ||||
| 	 | ||||
| 	// TODO: guild update check image
 | ||||
| 
 | ||||
| 	if (body.icon) body.icon = await handleFile(`/icons/${guild_id}`, body.icon); | ||||
|  | ||||
| @ -25,13 +25,19 @@ router.patch("/", route({ body: "MemberChangeSchema" }), async (req: Request, re | ||||
| 
 | ||||
| 	const member = await Member.findOneOrFail({ where: { id: member_id, guild_id }, relations: ["roles", "user"] }); | ||||
| 	const permission = await getPermission(req.user_id, guild_id); | ||||
| 	const everyone = await Role.findOneOrFail({ guild_id: guild_id, name: "@everyone", position: 0 }); | ||||
| 
 | ||||
| 	if (body.roles) { | ||||
| 		permission.hasThrow("MANAGE_ROLES"); | ||||
| 
 | ||||
| 		if (body.roles.indexOf(everyone.id) === -1) body.roles.push(everyone.id); | ||||
| 		member.roles = body.roles.map((x) => new Role({ id: x })); // foreign key constraint will fail if role doesn't exist
 | ||||
| 	} | ||||
| 
 | ||||
| 	await member.save(); | ||||
| 
 | ||||
| 	member.roles = member.roles.filter((x) => x.id !== everyone.id); | ||||
| 
 | ||||
| 	// do not use promise.all as we have to first write to db before emitting the event to catch errors
 | ||||
| 	await emitEvent({ | ||||
| 		event: "GUILD_MEMBER_UPDATE", | ||||
|  | ||||
| @ -9,11 +9,19 @@ const InviteRegex = /\W/g; | ||||
| 
 | ||||
| router.get("/", route({ permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { | ||||
| 	const { guild_id } = req.params; | ||||
| 	const guild = await Guild.findOneOrFail({ id: guild_id }); | ||||
| 
 | ||||
| 	const invite = await Invite.findOne({ where: { guild_id: guild_id, vanity_url: true } }); | ||||
| 	if (!invite) return res.json({ code: null }); | ||||
| 	if (!guild.features.includes("ALIASABLE_NAMES")) { | ||||
| 		const invite = await Invite.findOne({ where: { guild_id: guild_id, vanity_url: true } }); | ||||
| 		if (!invite) return res.json({ code: null }); | ||||
| 
 | ||||
| 	return res.json({ code: invite.code, uses: invite.uses }); | ||||
| 		return res.json({ code: invite.code, uses: invite.uses }); | ||||
| 	} else { | ||||
| 		const invite = await Invite.find({ where: { guild_id: guild_id, vanity_url: true } }); | ||||
| 		if (!invite || invite.length == 0) return res.json({ code: null }); | ||||
| 
 | ||||
| 		return res.json(invite.map((x) => ({ code: x.code, uses: x.uses }))); | ||||
| 	} | ||||
| }); | ||||
| 
 | ||||
| export interface VanityUrlSchema { | ||||
| @ -24,18 +32,33 @@ export interface VanityUrlSchema { | ||||
| 	code?: string; | ||||
| } | ||||
| 
 | ||||
| // TODO: check if guild is elgible for vanity url
 | ||||
| router.patch("/", route({ body: "VanityUrlSchema", permission: "MANAGE_GUILD" }), async (req: Request, res: Response) => { | ||||
| 	const { guild_id } = req.params; | ||||
| 	const body = req.body as VanityUrlSchema; | ||||
| 	const code = body.code?.replace(InviteRegex, ""); | ||||
| 
 | ||||
| 	const guild = await Guild.findOneOrFail({ id: guild_id }); | ||||
| 	if (!guild.features.includes("VANITY_URL")) throw new HTTPError("Your guild doesn't support vanity urls"); | ||||
| 
 | ||||
| 	if (!code || code.length === 0) throw new HTTPError("Code cannot be null or empty"); | ||||
| 
 | ||||
| 	const invite = await Invite.findOne({ code }); | ||||
| 	if (invite) throw new HTTPError("Invite already exists"); | ||||
| 
 | ||||
| 	const { id } = await Channel.findOneOrFail({ guild_id, type: ChannelType.GUILD_TEXT }); | ||||
| 
 | ||||
| 	await Invite.update({ vanity_url: true, guild_id }, { code: code, channel_id: id }); | ||||
| 	await new Invite({ | ||||
| 		vanity_url: true, | ||||
| 		code: code, | ||||
| 		temporary: false, | ||||
| 		uses: 0, | ||||
| 		max_uses: 0, | ||||
| 		max_age: 0, | ||||
| 		created_at: new Date(), | ||||
| 		expires_at: new Date(), | ||||
| 		guild_id: guild_id, | ||||
| 		channel_id: id | ||||
| 	}).save(); | ||||
| 
 | ||||
| 	return res.json({ code: code }); | ||||
| }); | ||||
|  | ||||
| @ -1,5 +1,5 @@ | ||||
| import { Router, Request, Response } from "express"; | ||||
| import { Role, Guild, Snowflake, Config, Member, Channel, DiscordApiErrors, handleFile } from "@fosscord/util"; | ||||
| import { Role, Guild, Snowflake, Config, getRights, Member, Channel, DiscordApiErrors, handleFile } from "@fosscord/util"; | ||||
| import { route } from "@fosscord/api"; | ||||
| import { ChannelModifySchema } from "../channels/#channel_id"; | ||||
| 
 | ||||
| @ -20,12 +20,13 @@ export interface GuildCreateSchema { | ||||
| 
 | ||||
| //TODO: create default channel
 | ||||
| 
 | ||||
| router.post("/", route({ body: "GuildCreateSchema" }), async (req: Request, res: Response) => { | ||||
| router.post("/", route({ body: "GuildCreateSchema", right: "CREATE_GUILDS" }), async (req: Request, res: Response) => { | ||||
| 	const body = req.body as GuildCreateSchema; | ||||
| 
 | ||||
| 	const { maxGuilds } = Config.get().limits.user; | ||||
| 	const guild_count = await Member.count({ id: req.user_id }); | ||||
| 	if (guild_count >= maxGuilds) { | ||||
| 	const rights = await getRights(req.user_id); | ||||
| 	if ((guild_count >= maxGuilds)&&!rights.has("MANAGE_GUILDS")) { | ||||
| 		throw DiscordApiErrors.MAXIMUM_GUILDS.withParams(maxGuilds); | ||||
| 	} | ||||
| 
 | ||||
|  | ||||
| @ -13,7 +13,7 @@ router.get("/:code", route({}), async (req: Request, res: Response) => { | ||||
| 	res.status(200).send(invite); | ||||
| }); | ||||
| 
 | ||||
| router.post("/:code", route({}), async (req: Request, res: Response) => { | ||||
| router.post("/:code", route({right: "JOIN_GUILDS"}), async (req: Request, res: Response) => { | ||||
| 	const { code } = req.params; | ||||
|     const { guild_id } = await Invite.findOneOrFail({ code }) | ||||
| 	const { features } = await Guild.findOneOrFail({ id: guild_id}); | ||||
|  | ||||
							
								
								
									
										12
									
								
								api/src/routes/scheduled-maintenances/upcoming_json.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								api/src/routes/scheduled-maintenances/upcoming_json.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| import { Router, Request, Response } from "express"; | ||||
| import { route } from "@fosscord/api"; | ||||
| const router = Router(); | ||||
| 
 | ||||
| router.get("/scheduled-maintenances/upcoming.json",route({}), async (req: Request, res: Response) => { | ||||
| 	res.json({ | ||||
|   "page": {}, | ||||
|   "scheduled_maintenances": {} | ||||
|   }); | ||||
| }); | ||||
| 
 | ||||
| export default router; | ||||
| @ -18,7 +18,7 @@ router.get("/:id", route({}), async (req: Request, res: Response) => { | ||||
| 			access_type: 2, | ||||
| 			name: "", | ||||
| 			features: [], | ||||
| 			relase_date: "", | ||||
| 			release_date: "", | ||||
| 			premium: false, | ||||
| 			slug: "", | ||||
| 			flags: 4, | ||||
|  | ||||
| @ -18,7 +18,7 @@ router.get("/:id", route({}), async (req: Request, res: Response) => { | ||||
| 			access_type: 2, | ||||
| 			name: "", | ||||
| 			features: [], | ||||
| 			relase_date: "", | ||||
| 			release_date: "", | ||||
| 			premium: false, | ||||
| 			slug: "", | ||||
| 			flags: 4, | ||||
|  | ||||
| @ -1,19 +1,19 @@ | ||||
| import { Router, Response, Request } from "express"; | ||||
| import { route } from "@fosscord/api"; | ||||
| import { Config, Relase } from "@fosscord/util"; | ||||
| import { Config, Release } from "@fosscord/util"; | ||||
| 
 | ||||
| const router = Router(); | ||||
| 
 | ||||
| router.get("/", route({}), async (req: Request, res: Response) => { | ||||
| 	const { client } = Config.get(); | ||||
| 
 | ||||
|     const relase = await Relase.findOneOrFail({ name: client.relases.upstreamVersion}) | ||||
|     const release = await Release.findOneOrFail({ name: client.releases.upstreamVersion}) | ||||
| 
 | ||||
| 	res.json({ | ||||
|         name: relase.name, | ||||
|         pub_date: relase.pub_date, | ||||
|         url: relase.url, | ||||
|         notes: relase.notes | ||||
|         name: release.name, | ||||
|         pub_date: release.pub_date, | ||||
|         url: release.url, | ||||
|         notes: release.notes | ||||
|     }); | ||||
| }); | ||||
| 
 | ||||
|  | ||||
| @ -53,8 +53,6 @@ router.patch("/", route({ body: "UserModifySchema" }), async (req: Request, res: | ||||
| 			throw FieldErrors({ email: { message: req.t("auth:register.EMAIL_INVALID"), code: "EMAIL_INVALID" } }); | ||||
| 	} | ||||
| 
 | ||||
| 	user.assign(body); | ||||
| 
 | ||||
| 	if (body.new_password) { | ||||
| 		if (!body.password && !user.email) { | ||||
| 			throw FieldErrors({ | ||||
| @ -64,14 +62,16 @@ router.patch("/", route({ body: "UserModifySchema" }), async (req: Request, res: | ||||
| 		user.data.hash = await bcrypt.hash(body.new_password, 12); | ||||
| 	} | ||||
| 
 | ||||
| 	var check_username = body?.username?.replace(/\s/g, ''); | ||||
| 
 | ||||
| 	if(!check_username && !body?.avatar && !body?.banner) { | ||||
| 		throw FieldErrors({ | ||||
| 			username: { code: "BASE_TYPE_REQUIRED", message: req.t("common:field.BASE_TYPE_REQUIRED") } | ||||
| 		}); | ||||
| 	if (body.username) { | ||||
| 		var check_username = body?.username?.replace(/\s/g, ''); | ||||
| 		if (!check_username) { | ||||
| 			throw FieldErrors({ | ||||
| 				username: { code: "BASE_TYPE_REQUIRED", message: req.t("common:field.BASE_TYPE_REQUIRED") } | ||||
| 			}); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	user.assign(body); | ||||
| 	await user.save(); | ||||
| 
 | ||||
| 	// @ts-ignore
 | ||||
|  | ||||
| @ -1,14 +1,39 @@ | ||||
| import { Request, Response, Router } from "express"; | ||||
| import { route } from "@fosscord/api"; | ||||
| import { User, emitEvent } from "@fosscord/util"; | ||||
| 
 | ||||
| const router: Router = Router(); | ||||
| 
 | ||||
| router.get("/:id", route({}), async (req: Request, res: Response) => { | ||||
| 	const { id } = req.params; | ||||
| 	const user = await User.findOneOrFail({ where: { id: req.user_id }, select: ["notes"] }); | ||||
| 
 | ||||
| 	const note = user.notes[id]; | ||||
| 	return res.json({ | ||||
| 		note: note, | ||||
| 		note_user_id: id, | ||||
| 		user_id: user.id, | ||||
| 	}); | ||||
| }); | ||||
| 
 | ||||
| router.put("/:id", route({}), async (req: Request, res: Response) => { | ||||
| 	//TODO
 | ||||
| 	res.json({ | ||||
| 		message: "400: Bad Request", | ||||
| 		code: 0 | ||||
| 	}).status(400); | ||||
| 	const { id } = req.params; | ||||
| 	const user = await User.findOneOrFail({ where: { id: req.user_id } }); | ||||
| 	const noteUser = await User.findOneOrFail({ where: { id: id }});		//if noted user does not exist throw
 | ||||
| 	const { note } = req.body; | ||||
| 
 | ||||
| 	await User.update({ id: req.user_id }, { notes: { ...user.notes, [noteUser.id]: note } }); | ||||
| 
 | ||||
| 	await emitEvent({ | ||||
| 		event: "USER_NOTE_UPDATE", | ||||
| 		data: { | ||||
| 			note: note, | ||||
| 			id: noteUser.id | ||||
| 		}, | ||||
| 		user_id: user.id, | ||||
| 	}) | ||||
| 
 | ||||
| 	return res.status(204); | ||||
| }); | ||||
| 
 | ||||
| export default router; | ||||
|  | ||||
| @ -7,6 +7,7 @@ import { | ||||
| 	MessageCreateEvent, | ||||
| 	MessageUpdateEvent, | ||||
| 	getPermission, | ||||
| 	getRights, | ||||
| 	CHANNEL_MENTION, | ||||
| 	Snowflake, | ||||
| 	USER_MENTION, | ||||
| @ -61,19 +62,20 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> { | ||||
| 		throw new HTTPError("Content length over max character limit") | ||||
| 	} | ||||
| 
 | ||||
| 	// TODO: are tts messages allowed in dm channels? should permission be checked?
 | ||||
| 	if (opts.author_id) { | ||||
| 		message.author = await User.getPublicUser(opts.author_id); | ||||
| 	} | ||||
| 		const rights = await getRights(opts.author_id); | ||||
| 		rights.hasThrow("SEND_MESSAGES"); | ||||
| 	}	 | ||||
| 	if (opts.application_id) { | ||||
| 		message.application = await Application.findOneOrFail({ id: opts.application_id }); | ||||
| 	} | ||||
| 	if (opts.webhook_id) { | ||||
| 		message.webhook = await Webhook.findOneOrFail({ id: opts.webhook_id }); | ||||
| 	} | ||||
| 
 | ||||
| 	 | ||||
| 	const permission = await getPermission(opts.author_id, channel.guild_id, opts.channel_id); | ||||
| 	permission.hasThrow("SEND_MESSAGES"); // TODO: add the rights check
 | ||||
| 	permission.hasThrow("SEND_MESSAGES"); | ||||
| 	if (permission.cache.member) { | ||||
| 		message.member = permission.cache.member; | ||||
| 	} | ||||
| @ -81,13 +83,15 @@ export async function handleMessage(opts: MessageOptions): Promise<Message> { | ||||
| 	if (opts.tts) permission.hasThrow("SEND_TTS_MESSAGES"); | ||||
| 	if (opts.message_reference) { | ||||
| 		permission.hasThrow("READ_MESSAGE_HISTORY"); | ||||
| 		// code below has to be redone when we add custom message routing and cross-channel replies
 | ||||
| 		const guild = await Guild.findOneOrFail({ id: channel.guild_id }); | ||||
| 		if (!guild.features.includes("CROSS_CHANNEL_REPLIES")) { | ||||
| 			if (opts.message_reference.guild_id !== channel.guild_id) throw new HTTPError("You can only reference messages from this guild"); | ||||
| 			if (opts.message_reference.channel_id !== opts.channel_id) throw new HTTPError("You can only reference messages from this channel"); | ||||
| 		// code below has to be redone when we add custom message routing
 | ||||
| 		if (message.guild_id !== null) { | ||||
| 			const guild = await Guild.findOneOrFail({ id: channel.guild_id }); | ||||
| 			if (!guild.features.includes("CROSS_CHANNEL_REPLIES")) { | ||||
| 				if (opts.message_reference.guild_id !== channel.guild_id) throw new HTTPError("You can only reference messages from this guild"); | ||||
| 				if (opts.message_reference.channel_id !== opts.channel_id) throw new HTTPError("You can only reference messages from this channel"); | ||||
| 			} | ||||
| 		} | ||||
| 		// TODO: should be checked if the referenced message exists?
 | ||||
| 		// Q: should be checked if the referenced message exists? ANSWER: NO
 | ||||
| 		// @ts-ignore
 | ||||
| 		message.type = MessageType.REPLY; | ||||
| 	} | ||||
|  | ||||
| @ -13,6 +13,7 @@ const blocklist: string[] = []; // TODO: update ones passwordblocklist is stored | ||||
|  *  - min <n> numbers | ||||
|  *  - min <n> symbols | ||||
|  *  - min <n> uppercase chars | ||||
|  *  - shannon entropy divided by password entropy | ||||
|  * | ||||
|  * Returns: 0 > pw > 1 | ||||
|  */ | ||||
| @ -22,28 +23,38 @@ export function checkPassword(password: string): number { | ||||
| 
 | ||||
| 	// checks for total password len
 | ||||
| 	if (password.length >= minLength - 1) { | ||||
| 		strength += 0.25; | ||||
| 		strength += 0.05; | ||||
| 	} | ||||
| 
 | ||||
| 	// checks for amount of Numbers
 | ||||
| 	if (password.count(reNUMBER) >= minNumbers - 1) { | ||||
| 		strength += 0.25; | ||||
| 		strength += 0.05; | ||||
| 	} | ||||
| 
 | ||||
| 	// checks for amount of Uppercase Letters
 | ||||
| 	if (password.count(reUPPERCASELETTER) >= minUpperCase - 1) { | ||||
| 		strength += 0.25; | ||||
| 		strength += 0.05; | ||||
| 	} | ||||
| 
 | ||||
| 	// checks for amount of symbols
 | ||||
| 	if (password.replace(reSYMBOLS, "").length >= minSymbols - 1) { | ||||
| 		strength += 0.25; | ||||
| 		strength += 0.05; | ||||
| 	} | ||||
| 
 | ||||
| 	// checks if password only consists of numbers or only consists of chars
 | ||||
| 	if (password.length == password.count(reNUMBER) || password.length === password.count(reUPPERCASELETTER)) { | ||||
| 		strength = 0; | ||||
| 	} | ||||
| 
 | ||||
| 	 | ||||
| 	let entropyMap; | ||||
| 	for (let i = 0; i < password.length; i++) { | ||||
| 		if (entropyMap[password[i]]) entropyMap[password[i]]++; | ||||
| 		else entropyMap[password[i]] = 1; | ||||
| 	} | ||||
| 	 | ||||
| 	let entropies = Array(entropyMap); | ||||
| 		 | ||||
| 	entropies.map(x => (x / entropyMap.length)); | ||||
| 	strength += entropies.reduceRight((a, x), a - (x * Math.log2(x))) / Math.log2(password.length);	 | ||||
| 	return strength; | ||||
| } | ||||
|  | ||||
							
								
								
									
										53
									
								
								bundle/.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										53
									
								
								bundle/.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							| @ -1,18 +1,35 @@ | ||||
| { | ||||
| 	// Use IntelliSense to learn about possible attributes. | ||||
| 	// Hover to view descriptions of existing attributes. | ||||
| 	// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 | ||||
| 	"version": "0.2.0", | ||||
| 	"configurations": [ | ||||
| 		{ | ||||
| 			"sourceMaps": true, | ||||
| 			"type": "node", | ||||
| 			"request": "launch", | ||||
| 			"name": "Launch Server", | ||||
| 			"program": "${workspaceFolder}/dist/bundle/src/start.js", | ||||
| 			"preLaunchTask": "tsc: build - tsconfig.json", | ||||
| 			"outFiles": ["${workspaceFolder}/dist/**/*.js"], | ||||
| 			"envFile": "${workspaceFolder}/.env" | ||||
| 		} | ||||
| 	] | ||||
| } | ||||
| { | ||||
| 	"version": "0.2.0", | ||||
| 	"configurations": [ | ||||
| 		{ | ||||
| 			"sourceMaps": true, | ||||
| 			"name": "ts-node", | ||||
| 			"type": "node", | ||||
| 			"request": "launch", | ||||
| 			"args": [ | ||||
| 				"${workspaceFolder}/src/start.ts" | ||||
| 			], | ||||
| 			"runtimeArgs": [ | ||||
| 				"-r", | ||||
| 				"ts-node/register" | ||||
| 			], | ||||
| 			"protocol": "inspector", | ||||
| 			"internalConsoleOptions": "openOnSessionStart", | ||||
| 			"env": { | ||||
| 				"TS_NODE_PROJECT": "${workspaceFolder}/tsnode.tsconfig.json", | ||||
| 				"TS_NODE_COMPILER": "typescript-cached-transpile" | ||||
| 			}, | ||||
| 			"resolveSourceMapLocations": null, /* allow breakpoints in modules other than bundle */ | ||||
| 		}, | ||||
| 		{ | ||||
| 			"sourceMaps": true, | ||||
| 			"type": "node", | ||||
| 			"request": "launch", | ||||
| 			"name": "Launch Server", | ||||
| 			"program": "${workspaceFolder}/dist/bundle/src/start.js", | ||||
| 			"preLaunchTask": "tsc: build - tsconfig.json", | ||||
| 			"outFiles": ["${workspaceFolder}/dist/**/*.js"], | ||||
| 			"envFile": "${workspaceFolder}/.env", | ||||
| 		} | ||||
| 	] | ||||
| } | ||||
|  | ||||
							
								
								
									
										210
									
								
								bundle/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										210
									
								
								bundle/package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -45,6 +45,7 @@ | ||||
| 				"missing-native-js-functions": "^1.2.18", | ||||
| 				"morgan": "^1.10.0", | ||||
| 				"multer": "^1.4.2", | ||||
| 				"nan": "^2.15.0", | ||||
| 				"nanocolors": "^0.2.12", | ||||
| 				"node-fetch": "^2.6.2", | ||||
| 				"node-os-utils": "^1.3.5", | ||||
| @ -58,6 +59,7 @@ | ||||
| 				"tslib": "^2.3.1", | ||||
| 				"typeorm": "^0.2.37", | ||||
| 				"typescript": "^4.1.2", | ||||
| 				"typescript-cached-transpile": "^0.0.6", | ||||
| 				"typescript-json-schema": "^0.50.1", | ||||
| 				"ws": "^7.4.2" | ||||
| 			}, | ||||
| @ -90,6 +92,7 @@ | ||||
| 				"ts-node": "^10.2.1", | ||||
| 				"ts-node-dev": "^1.1.6", | ||||
| 				"ts-patch": "^1.4.4", | ||||
| 				"tsconfig-paths": "^3.12.0", | ||||
| 				"typescript": "^4.2.3", | ||||
| 				"typescript-json-schema": "0.50.1" | ||||
| 			} | ||||
| @ -124,7 +127,7 @@ | ||||
| 				"missing-native-js-functions": "^1.2.18", | ||||
| 				"morgan": "^1.10.0", | ||||
| 				"multer": "^1.4.2", | ||||
| 				"node-fetch": "^3.1.1", | ||||
| 				"node-fetch": "^2.6.2", | ||||
| 				"patch-package": "^6.4.7", | ||||
| 				"picocolors": "^1.0.0", | ||||
| 				"proxy-agent": "^5.0.0", | ||||
| @ -145,7 +148,7 @@ | ||||
| 				"@types/morgan": "^1.9.3", | ||||
| 				"@types/multer": "^1.4.5", | ||||
| 				"@types/node": "^14.17.9", | ||||
| 				"@types/node-fetch": "^2.5.7", | ||||
| 				"@types/node-fetch": "^2.5.5", | ||||
| 				"@types/supertest": "^2.0.11", | ||||
| 				"@zerollup/ts-transform-paths": "^1.7.18", | ||||
| 				"jest": "^27.2.5", | ||||
| @ -182,7 +185,7 @@ | ||||
| 				"missing-native-js-functions": "^1.2.17", | ||||
| 				"multer": "^1.4.2", | ||||
| 				"nanocolors": "^0.2.12", | ||||
| 				"node-fetch": "^2.6.7", | ||||
| 				"node-fetch": "^2.6.2", | ||||
| 				"supertest": "^6.1.6", | ||||
| 				"typescript": "^4.1.2" | ||||
| 			}, | ||||
| @ -3601,6 +3604,12 @@ | ||||
| 			"integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", | ||||
| 			"dev": true | ||||
| 		}, | ||||
| 		"node_modules/@types/json5": { | ||||
| 			"version": "0.0.29", | ||||
| 			"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", | ||||
| 			"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", | ||||
| 			"dev": true | ||||
| 		}, | ||||
| 		"node_modules/@types/jsonwebtoken": { | ||||
| 			"version": "8.5.5", | ||||
| 			"integrity": "sha512-OGqtHQ7N5/Ap/TUwO6IgHDuLiAoTmHhGpNvgkCm/F4N6pKzx/RBSfr2OXZSwC6vkfnsEdb6+7DNZVtiXiwdwFw==", | ||||
| @ -7325,8 +7334,9 @@ | ||||
| 			} | ||||
| 		}, | ||||
| 		"node_modules/minimist": { | ||||
| 			"version": "1.2.5", | ||||
| 			"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" | ||||
| 			"version": "1.2.6", | ||||
| 			"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", | ||||
| 			"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" | ||||
| 		}, | ||||
| 		"node_modules/minipass": { | ||||
| 			"version": "3.1.5", | ||||
| @ -7610,6 +7620,11 @@ | ||||
| 				"thenify-all": "^1.0.0" | ||||
| 			} | ||||
| 		}, | ||||
| 		"node_modules/nan": { | ||||
| 			"version": "2.15.0", | ||||
| 			"resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", | ||||
| 			"integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==" | ||||
| 		}, | ||||
| 		"node_modules/nanocolors": { | ||||
| 			"version": "0.2.12", | ||||
| 			"integrity": "sha512-SFNdALvzW+rVlzqexid6epYdt8H9Zol7xDoQarioEFcFN0JHo4CYNztAxmtfgGTVRCmFlEOqqhBpoFGKqSAMug==" | ||||
| @ -10157,6 +10172,39 @@ | ||||
| 				"strip-json-comments": "^2.0.0" | ||||
| 			} | ||||
| 		}, | ||||
| 		"node_modules/tsconfig-paths": { | ||||
| 			"version": "3.12.0", | ||||
| 			"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz", | ||||
| 			"integrity": "sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg==", | ||||
| 			"dev": true, | ||||
| 			"dependencies": { | ||||
| 				"@types/json5": "^0.0.29", | ||||
| 				"json5": "^1.0.1", | ||||
| 				"minimist": "^1.2.0", | ||||
| 				"strip-bom": "^3.0.0" | ||||
| 			} | ||||
| 		}, | ||||
| 		"node_modules/tsconfig-paths/node_modules/json5": { | ||||
| 			"version": "1.0.1", | ||||
| 			"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", | ||||
| 			"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", | ||||
| 			"dev": true, | ||||
| 			"dependencies": { | ||||
| 				"minimist": "^1.2.0" | ||||
| 			}, | ||||
| 			"bin": { | ||||
| 				"json5": "lib/cli.js" | ||||
| 			} | ||||
| 		}, | ||||
| 		"node_modules/tsconfig-paths/node_modules/strip-bom": { | ||||
| 			"version": "3.0.0", | ||||
| 			"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", | ||||
| 			"integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", | ||||
| 			"dev": true, | ||||
| 			"engines": { | ||||
| 				"node": ">=4" | ||||
| 			} | ||||
| 		}, | ||||
| 		"node_modules/tsconfig/node_modules/strip-bom": { | ||||
| 			"version": "3.0.0", | ||||
| 			"integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", | ||||
| @ -10418,7 +10466,6 @@ | ||||
| 		"node_modules/typescript": { | ||||
| 			"version": "4.4.3", | ||||
| 			"integrity": "sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==", | ||||
| 			"dev": true, | ||||
| 			"bin": { | ||||
| 				"tsc": "bin/tsc", | ||||
| 				"tsserver": "bin/tsserver" | ||||
| @ -10427,6 +10474,58 @@ | ||||
| 				"node": ">=4.2.0" | ||||
| 			} | ||||
| 		}, | ||||
| 		"node_modules/typescript-cached-transpile": { | ||||
| 			"version": "0.0.6", | ||||
| 			"resolved": "https://registry.npmjs.org/typescript-cached-transpile/-/typescript-cached-transpile-0.0.6.tgz", | ||||
| 			"integrity": "sha512-bfPc7YUW0PrVkQHU0xN0ANRuxdPgoYYXtZEW6PNkH5a97/AOM+kPPxSTMZbpWA3BG1do22JUkfC60KoCKJ9VZQ==", | ||||
| 			"dependencies": { | ||||
| 				"@types/node": "^12.12.7", | ||||
| 				"fs-extra": "^8.1.0", | ||||
| 				"tslib": "^1.10.0" | ||||
| 			}, | ||||
| 			"peerDependencies": { | ||||
| 				"typescript": "*" | ||||
| 			} | ||||
| 		}, | ||||
| 		"node_modules/typescript-cached-transpile/node_modules/@types/node": { | ||||
| 			"version": "12.20.41", | ||||
| 			"resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.41.tgz", | ||||
| 			"integrity": "sha512-f6xOqucbDirG7LOzedpvzjP3UTmHttRou3Mosx3vL9wr9AIQGhcPgVnqa8ihpZYnxyM1rxeNCvTyukPKZtq10Q==" | ||||
| 		}, | ||||
| 		"node_modules/typescript-cached-transpile/node_modules/fs-extra": { | ||||
| 			"version": "8.1.0", | ||||
| 			"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", | ||||
| 			"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", | ||||
| 			"dependencies": { | ||||
| 				"graceful-fs": "^4.2.0", | ||||
| 				"jsonfile": "^4.0.0", | ||||
| 				"universalify": "^0.1.0" | ||||
| 			}, | ||||
| 			"engines": { | ||||
| 				"node": ">=6 <7 || >=8" | ||||
| 			} | ||||
| 		}, | ||||
| 		"node_modules/typescript-cached-transpile/node_modules/jsonfile": { | ||||
| 			"version": "4.0.0", | ||||
| 			"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", | ||||
| 			"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", | ||||
| 			"optionalDependencies": { | ||||
| 				"graceful-fs": "^4.1.6" | ||||
| 			} | ||||
| 		}, | ||||
| 		"node_modules/typescript-cached-transpile/node_modules/tslib": { | ||||
| 			"version": "1.14.1", | ||||
| 			"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", | ||||
| 			"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" | ||||
| 		}, | ||||
| 		"node_modules/typescript-cached-transpile/node_modules/universalify": { | ||||
| 			"version": "0.1.2", | ||||
| 			"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", | ||||
| 			"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", | ||||
| 			"engines": { | ||||
| 				"node": ">= 4.0.0" | ||||
| 			} | ||||
| 		}, | ||||
| 		"node_modules/typescript-json-schema": { | ||||
| 			"version": "0.50.1", | ||||
| 			"integrity": "sha512-GCof/SDoiTDl0qzPonNEV4CHyCsZEIIf+mZtlrjoD8vURCcEzEfa2deRuxYid8Znp/e27eDR7Cjg8jgGrimBCA==", | ||||
| @ -12885,7 +12984,7 @@ | ||||
| 				"@types/morgan": "^1.9.3", | ||||
| 				"@types/multer": "^1.4.5", | ||||
| 				"@types/node": "^14.17.9", | ||||
| 				"@types/node-fetch": "^2.5.7", | ||||
| 				"@types/node-fetch": "^2.5.5", | ||||
| 				"@types/supertest": "^2.0.11", | ||||
| 				"@zerollup/ts-transform-paths": "^1.7.18", | ||||
| 				"ajv": "8.6.2", | ||||
| @ -12910,7 +13009,7 @@ | ||||
| 				"missing-native-js-functions": "^1.2.18", | ||||
| 				"morgan": "^1.10.0", | ||||
| 				"multer": "^1.4.2", | ||||
| 				"node-fetch": "^3.1.1", | ||||
| 				"node-fetch": "^2.6.2", | ||||
| 				"patch-package": "^6.4.7", | ||||
| 				"picocolors": "^1.0.0", | ||||
| 				"proxy-agent": "^5.0.0", | ||||
| @ -12956,7 +13055,7 @@ | ||||
| 				"missing-native-js-functions": "^1.2.17", | ||||
| 				"multer": "^1.4.2", | ||||
| 				"nanocolors": "^0.2.12", | ||||
| 				"node-fetch": "^2.6.7", | ||||
| 				"node-fetch": "^2.6.2", | ||||
| 				"supertest": "^6.1.6", | ||||
| 				"ts-patch": "^1.4.4", | ||||
| 				"typescript": "^4.1.2" | ||||
| @ -13633,6 +13732,12 @@ | ||||
| 			"integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", | ||||
| 			"dev": true | ||||
| 		}, | ||||
| 		"@types/json5": { | ||||
| 			"version": "0.0.29", | ||||
| 			"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", | ||||
| 			"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", | ||||
| 			"dev": true | ||||
| 		}, | ||||
| 		"@types/jsonwebtoken": { | ||||
| 			"version": "8.5.5", | ||||
| 			"integrity": "sha512-OGqtHQ7N5/Ap/TUwO6IgHDuLiAoTmHhGpNvgkCm/F4N6pKzx/RBSfr2OXZSwC6vkfnsEdb6+7DNZVtiXiwdwFw==", | ||||
| @ -16446,8 +16551,9 @@ | ||||
| 			} | ||||
| 		}, | ||||
| 		"minimist": { | ||||
| 			"version": "1.2.5", | ||||
| 			"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" | ||||
| 			"version": "1.2.6", | ||||
| 			"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", | ||||
| 			"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" | ||||
| 		}, | ||||
| 		"minipass": { | ||||
| 			"version": "3.1.5", | ||||
| @ -16633,6 +16739,11 @@ | ||||
| 				"thenify-all": "^1.0.0" | ||||
| 			} | ||||
| 		}, | ||||
| 		"nan": { | ||||
| 			"version": "2.15.0", | ||||
| 			"resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", | ||||
| 			"integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==" | ||||
| 		}, | ||||
| 		"nanocolors": { | ||||
| 			"version": "0.2.12", | ||||
| 			"integrity": "sha512-SFNdALvzW+rVlzqexid6epYdt8H9Zol7xDoQarioEFcFN0JHo4CYNztAxmtfgGTVRCmFlEOqqhBpoFGKqSAMug==" | ||||
| @ -18517,6 +18628,35 @@ | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		"tsconfig-paths": { | ||||
| 			"version": "3.12.0", | ||||
| 			"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz", | ||||
| 			"integrity": "sha512-e5adrnOYT6zqVnWqZu7i/BQ3BnhzvGbjEjejFXO20lKIKpwTaupkCPgEfv4GZK1IBciJUEhYs3J3p75FdaTFVg==", | ||||
| 			"dev": true, | ||||
| 			"requires": { | ||||
| 				"@types/json5": "^0.0.29", | ||||
| 				"json5": "^1.0.1", | ||||
| 				"minimist": "^1.2.0", | ||||
| 				"strip-bom": "^3.0.0" | ||||
| 			}, | ||||
| 			"dependencies": { | ||||
| 				"json5": { | ||||
| 					"version": "1.0.1", | ||||
| 					"resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", | ||||
| 					"integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", | ||||
| 					"dev": true, | ||||
| 					"requires": { | ||||
| 						"minimist": "^1.2.0" | ||||
| 					} | ||||
| 				}, | ||||
| 				"strip-bom": { | ||||
| 					"version": "3.0.0", | ||||
| 					"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", | ||||
| 					"integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", | ||||
| 					"dev": true | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		"tslib": { | ||||
| 			"version": "2.3.1", | ||||
| 			"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==" | ||||
| @ -18649,8 +18789,52 @@ | ||||
| 		}, | ||||
| 		"typescript": { | ||||
| 			"version": "4.4.3", | ||||
| 			"integrity": "sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==", | ||||
| 			"dev": true | ||||
| 			"integrity": "sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==" | ||||
| 		}, | ||||
| 		"typescript-cached-transpile": { | ||||
| 			"version": "0.0.6", | ||||
| 			"resolved": "https://registry.npmjs.org/typescript-cached-transpile/-/typescript-cached-transpile-0.0.6.tgz", | ||||
| 			"integrity": "sha512-bfPc7YUW0PrVkQHU0xN0ANRuxdPgoYYXtZEW6PNkH5a97/AOM+kPPxSTMZbpWA3BG1do22JUkfC60KoCKJ9VZQ==", | ||||
| 			"requires": { | ||||
| 				"@types/node": "^12.12.7", | ||||
| 				"fs-extra": "^8.1.0", | ||||
| 				"tslib": "^1.10.0" | ||||
| 			}, | ||||
| 			"dependencies": { | ||||
| 				"@types/node": { | ||||
| 					"version": "12.20.41", | ||||
| 					"resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.41.tgz", | ||||
| 					"integrity": "sha512-f6xOqucbDirG7LOzedpvzjP3UTmHttRou3Mosx3vL9wr9AIQGhcPgVnqa8ihpZYnxyM1rxeNCvTyukPKZtq10Q==" | ||||
| 				}, | ||||
| 				"fs-extra": { | ||||
| 					"version": "8.1.0", | ||||
| 					"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", | ||||
| 					"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", | ||||
| 					"requires": { | ||||
| 						"graceful-fs": "^4.2.0", | ||||
| 						"jsonfile": "^4.0.0", | ||||
| 						"universalify": "^0.1.0" | ||||
| 					} | ||||
| 				}, | ||||
| 				"jsonfile": { | ||||
| 					"version": "4.0.0", | ||||
| 					"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", | ||||
| 					"integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", | ||||
| 					"requires": { | ||||
| 						"graceful-fs": "^4.1.6" | ||||
| 					} | ||||
| 				}, | ||||
| 				"tslib": { | ||||
| 					"version": "1.14.1", | ||||
| 					"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", | ||||
| 					"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" | ||||
| 				}, | ||||
| 				"universalify": { | ||||
| 					"version": "0.1.2", | ||||
| 					"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", | ||||
| 					"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" | ||||
| 				} | ||||
| 			} | ||||
| 		}, | ||||
| 		"typescript-json-schema": { | ||||
| 			"version": "0.50.1", | ||||
|  | ||||
| @ -9,7 +9,8 @@ | ||||
| 		"start": "node scripts/build.js && node dist/bundle/src/start.js", | ||||
| 		"start:bundle": "node dist/bundle/src/start.js", | ||||
| 		"test": "echo \"Error: no test specified\" && exit 1", | ||||
| 		"migrate": "cd ../util/ && npm i && node --require ts-node/register node_modules/typeorm/cli.js -f ../util/ormconfig.json migration:run" | ||||
| 		"migrate": "cd ../util/ && npm i && node --require ts-node/register node_modules/typeorm/cli.js -f ../util/ormconfig.json migration:run", | ||||
| 		"tsnode": "npx ts-node --transpile-only -P tsnode.tsconfig.json src/start.ts" | ||||
| 	}, | ||||
| 	"repository": { | ||||
| 		"type": "git", | ||||
| @ -51,6 +52,7 @@ | ||||
| 		"ts-node": "^10.2.1", | ||||
| 		"ts-node-dev": "^1.1.6", | ||||
| 		"ts-patch": "^1.4.4", | ||||
| 		"tsconfig-paths": "^3.12.0", | ||||
| 		"typescript": "^4.2.3", | ||||
| 		"typescript-json-schema": "0.50.1" | ||||
| 	}, | ||||
| @ -91,6 +93,7 @@ | ||||
| 		"missing-native-js-functions": "^1.2.18", | ||||
| 		"morgan": "^1.10.0", | ||||
| 		"multer": "^1.4.2", | ||||
| 		"nan": "^2.15.0", | ||||
| 		"nanocolors": "^0.2.12", | ||||
| 		"node-fetch": "^2.6.2", | ||||
| 		"node-os-utils": "^1.3.5", | ||||
| @ -104,8 +107,8 @@ | ||||
| 		"tslib": "^2.3.1", | ||||
| 		"typeorm": "^0.2.37", | ||||
| 		"typescript": "^4.1.2", | ||||
| 		"typescript-cached-transpile": "^0.0.6", | ||||
| 		"typescript-json-schema": "^0.50.1", | ||||
| 		"ws": "^7.4.2", | ||||
| 		"nan": "^2.15.0" | ||||
| 		"ws": "^7.4.2" | ||||
| 	} | ||||
| } | ||||
| } | ||||
| @ -1,86 +1,85 @@ | ||||
| { | ||||
| 	"include": ["dist/**/*.ts"], | ||||
| 	"exclude": [], | ||||
| 	"compilerOptions": { | ||||
| 		/* Visit https://aka.ms/tsconfig.json to read more about this file */ | ||||
| 
 | ||||
| 		/* Basic Options */ | ||||
| 		"incremental": false /* Enable incremental compilation */, | ||||
| 		"target": "ES6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, | ||||
| 		"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, | ||||
| 		"lib": [ | ||||
| 			"ES2021" | ||||
| 		] /* Specify library files to be included in the compilation. */, | ||||
| 		"allowJs": true /* Allow javascript files to be compiled. */, | ||||
| 		"checkJs": true /* Report errors in .js files. */, | ||||
| 		// "jsx": "preserve",                     /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ | ||||
| 		"declaration": false /* Generates corresponding '.d.ts' file. */, | ||||
| 		"declarationMap": false /* Generates a sourcemap for each corresponding '.d.ts' file. */, | ||||
| 		"sourceMap": false /* Generates corresponding '.map' file. */, | ||||
| 		// "outFile": "./",                       /* Concatenate and emit output to single file. */ | ||||
| 		"outDir": "./dist/" /* Redirect output structure to the directory. */, | ||||
| 		"rootDir": "./dist/" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */, | ||||
| 		// "composite": true,                     /* Enable project compilation */ | ||||
| 		// "tsBuildInfoFile": "./",               /* Specify file to store incremental compilation information */ | ||||
| 		// "removeComments": true,                /* Do not emit comments to output. */ | ||||
| 		// "noEmit": true,                        /* Do not emit outputs. */ | ||||
| 		// "importHelpers": true,                 /* Import emit helpers from 'tslib'. */ | ||||
| 		// "downlevelIteration": true,            /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ | ||||
| 		// "isolatedModules": true,               /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ | ||||
| 
 | ||||
| 		/* Strict Type-Checking Options */ | ||||
| 		"strict": true /* Enable all strict type-checking options. */, | ||||
| 		"noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */, | ||||
| 		"strictNullChecks": true /* Enable strict null checks. */, | ||||
| 		// "strictFunctionTypes": true,           /* Enable strict checking of function types. */ | ||||
| 		// "strictBindCallApply": true,           /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ | ||||
| 		"strictPropertyInitialization": false /* Enable strict checking of property initialization in classes. */, | ||||
| 		// "noImplicitThis": true,                /* Raise error on 'this' expressions with an implied 'any' type. */ | ||||
| 		"alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */, | ||||
| 
 | ||||
| 		/* Additional Checks */ | ||||
| 		// "noUnusedLocals": true,                /* Report errors on unused locals. */ | ||||
| 		// "noUnusedParameters": true,            /* Report errors on unused parameters. */ | ||||
| 		// "noImplicitReturns": true,             /* Report error when not all code paths in function return a value. */ | ||||
| 		// "noFallthroughCasesInSwitch": true,    /* Report errors for fallthrough cases in switch statement. */ | ||||
| 
 | ||||
| 		/* Module Resolution Options */ | ||||
| 		"moduleResolution": "node",            /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ | ||||
| 		// "rootDirs": [],                        /* List of root folders whose combined content represents the structure of the project at runtime. */ | ||||
| 		// "typeRoots": [],                       /* List of folders to include type definitions from. */ | ||||
| 		"types": [ | ||||
| 			"node" | ||||
| 		] /* Type declaration files to be included in compilation. */, | ||||
| 		// "allowSyntheticDefaultImports": true,  /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ | ||||
| 		"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, | ||||
| 		// "preserveSymlinks": true,              /* Do not resolve the real path of symlinks. */ | ||||
| 		// "allowUmdGlobalAccess": true,          /* Allow accessing UMD globals from modules. */ | ||||
| 
 | ||||
| 		/* Source Map Options */ | ||||
| 		// "sourceRoot": "",                      /* Specify the location where debugger should locate TypeScript files instead of source locations. */ | ||||
| 		// "mapRoot": "",                         /* Specify the location where debugger should locate map files instead of generated locations. */ | ||||
| 		// "inlineSourceMap": true,               /* Emit a single file with source maps instead of having a separate file. */ | ||||
| 		// "inlineSources": true,                 /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ | ||||
| 
 | ||||
| 		/* Experimental Options */ | ||||
| 		// "experimentalDecorators": true,        /* Enables experimental support for ES7 decorators. */ | ||||
| 		// "emitDecoratorMetadata": true,         /* Enables experimental support for emitting type metadata for decorators. */ | ||||
| 
 | ||||
| 		/* Advanced Options */ | ||||
| 		"skipLibCheck": true /* Skip type checking of declaration files. */, | ||||
| 		"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */, | ||||
| 		"emitDecoratorMetadata": true, | ||||
| 		"experimentalDecorators": true, | ||||
| 		"resolveJsonModule": true, | ||||
| 		"baseUrl": "./dist/", | ||||
| 		"paths": { | ||||
| 			"@fosscord/api": ["api/src/index"], | ||||
| 			"@fosscord/gateway": ["gateway/src/index"], | ||||
| 			"@fosscord/cdn": ["cdn/src/index"], | ||||
| 			"@fosscord/util": ["util/src/index"] | ||||
| 		}, | ||||
| 		"plugins": [{ "transform": "@zerollup/ts-transform-paths" }], | ||||
| 		"noEmitHelpers": true, | ||||
| 		"importHelpers": true | ||||
| 	} | ||||
| } | ||||
| { | ||||
| 	"include": ["dist/**/*.ts"], | ||||
| 	"exclude": [], | ||||
| 	"compilerOptions": { | ||||
| 
 | ||||
| 		/* Basic Options */ | ||||
| 		"incremental": false /* Enable incremental compilation */, | ||||
| 		"target": "ES6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */, | ||||
| 		"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */, | ||||
| 		"lib": [ | ||||
| 			"ES2021" | ||||
| 		] /* Specify library files to be included in the compilation. */, | ||||
| 		"allowJs": true /* Allow javascript files to be compiled. */, | ||||
| 		"checkJs": true /* Report errors in .js files. */, | ||||
| 		// "jsx": "preserve",                     /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ | ||||
| 		"declaration": false /* Generates corresponding '.d.ts' file. */, | ||||
| 		"declarationMap": false /* Generates a sourcemap for each corresponding '.d.ts' file. */, | ||||
| 		"sourceMap": true /* Generates corresponding '.map' file. */, | ||||
| 		// "outFile": "./",                       /* Concatenate and emit output to single file. */ | ||||
| 		"outDir": "./dist/" /* Redirect output structure to the directory. */, | ||||
| 		"rootDir": "./dist/" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */, | ||||
| 		// "composite": true,                     /* Enable project compilation */ | ||||
| 		// "tsBuildInfoFile": "./",               /* Specify file to store incremental compilation information */ | ||||
| 		// "removeComments": true,                /* Do not emit comments to output. */ | ||||
| 		// "noEmit": true,                        /* Do not emit outputs. */ | ||||
| 		// "importHelpers": true,                 /* Import emit helpers from 'tslib'. */ | ||||
| 		// "downlevelIteration": true,            /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ | ||||
| 		// "isolatedModules": true,               /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ | ||||
| 
 | ||||
| 		/* Strict Type-Checking Options */ | ||||
| 		"strict": true /* Enable all strict type-checking options. */, | ||||
| 		"noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */, | ||||
| 		"strictNullChecks": true /* Enable strict null checks. */, | ||||
| 		// "strictFunctionTypes": true,           /* Enable strict checking of function types. */ | ||||
| 		// "strictBindCallApply": true,           /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ | ||||
| 		"strictPropertyInitialization": false /* Enable strict checking of property initialization in classes. */, | ||||
| 		// "noImplicitThis": true,                /* Raise error on 'this' expressions with an implied 'any' type. */ | ||||
| 		"alwaysStrict": true /* Parse in strict mode and emit "use strict" for each source file. */, | ||||
| 
 | ||||
| 		/* Additional Checks */ | ||||
| 		// "noUnusedLocals": true,                /* Report errors on unused locals. */ | ||||
| 		// "noUnusedParameters": true,            /* Report errors on unused parameters. */ | ||||
| 		// "noImplicitReturns": true,             /* Report error when not all code paths in function return a value. */ | ||||
| 		// "noFallthroughCasesInSwitch": true,    /* Report errors for fallthrough cases in switch statement. */ | ||||
| 
 | ||||
| 		/* Module Resolution Options */ | ||||
| 		"moduleResolution": "node",            /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ | ||||
| 		// "rootDirs": [],                        /* List of root folders whose combined content represents the structure of the project at runtime. */ | ||||
| 		// "typeRoots": [],                       /* List of folders to include type definitions from. */ | ||||
| 		"types": [ | ||||
| 			"node" | ||||
| 		] /* Type declaration files to be included in compilation. */, | ||||
| 		// "allowSyntheticDefaultImports": true,  /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ | ||||
| 		"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, | ||||
| 		// "preserveSymlinks": true,              /* Do not resolve the real path of symlinks. */ | ||||
| 		// "allowUmdGlobalAccess": true,          /* Allow accessing UMD globals from modules. */ | ||||
| 
 | ||||
| 		/* Source Map Options */ | ||||
| 		// "sourceRoot": "",                      /* Specify the location where debugger should locate TypeScript files instead of source locations. */ | ||||
| 		// "mapRoot": "",                         /* Specify the location where debugger should locate map files instead of generated locations. */ | ||||
| 		// "inlineSourceMap": true,               /* Emit a single file with source maps instead of having a separate file. */ | ||||
| 		// "inlineSources": true,                 /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ | ||||
| 
 | ||||
| 		/* Experimental Options */ | ||||
| 		// "experimentalDecorators": true,        /* Enables experimental support for ES7 decorators. */ | ||||
| 		// "emitDecoratorMetadata": true,         /* Enables experimental support for emitting type metadata for decorators. */ | ||||
| 
 | ||||
| 		/* Advanced Options */ | ||||
| 		"skipLibCheck": true /* Skip type checking of declaration files. */, | ||||
| 		"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */, | ||||
| 		"emitDecoratorMetadata": true, | ||||
| 		"experimentalDecorators": true, | ||||
| 		"resolveJsonModule": true, | ||||
| 		"baseUrl": "./dist/", | ||||
| 		"paths": { | ||||
| 			"@fosscord/api": ["api/src/index"], | ||||
| 			"@fosscord/gateway": ["gateway/src/index"], | ||||
| 			"@fosscord/cdn": ["cdn/src/index"], | ||||
| 			"@fosscord/util": ["util/src/index"] | ||||
| 		}, | ||||
| 		"plugins": [{ "transform": "@zerollup/ts-transform-paths" }], | ||||
| 		"noEmitHelpers": true, | ||||
| 		"importHelpers": true | ||||
| 	} | ||||
| } | ||||
|  | ||||
							
								
								
									
										15
									
								
								bundle/tsnode.tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								bundle/tsnode.tsconfig.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | ||||
| { | ||||
| 	"extends": "./tsconfig.json", | ||||
| 	"ts-node": { | ||||
| 		"transpileOnly": true, | ||||
| 		"preferTsExts": true, | ||||
| 		"require": ["tsconfig-paths/register"], | ||||
| 		"compiler": "typescript-cached-transpile", | ||||
| 	}, | ||||
| 	"compilerOptions": { | ||||
| 		"rootDir": "../", | ||||
| 		"baseUrl": "../", | ||||
| 		"sourceRoot": "../", | ||||
| 		"sourceMap": true, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										14
									
								
								cdn/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										14
									
								
								cdn/package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -28,7 +28,7 @@ | ||||
| 				"missing-native-js-functions": "^1.2.17", | ||||
| 				"multer": "^1.4.2", | ||||
| 				"nanocolors": "^0.2.12", | ||||
| 				"node-fetch": "^2.6.7", | ||||
| 				"node-fetch": "^2.6.2", | ||||
| 				"supertest": "^6.1.6", | ||||
| 				"typescript": "^4.1.2" | ||||
| 			}, | ||||
| @ -5739,9 +5739,9 @@ | ||||
| 			} | ||||
| 		}, | ||||
| 		"node_modules/minimist": { | ||||
| 			"version": "1.2.5", | ||||
| 			"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", | ||||
| 			"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" | ||||
| 			"version": "1.2.6", | ||||
| 			"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", | ||||
| 			"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" | ||||
| 		}, | ||||
| 		"node_modules/minipass": { | ||||
| 			"version": "3.1.6", | ||||
| @ -12301,9 +12301,9 @@ | ||||
| 			} | ||||
| 		}, | ||||
| 		"minimist": { | ||||
| 			"version": "1.2.5", | ||||
| 			"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", | ||||
| 			"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" | ||||
| 			"version": "1.2.6", | ||||
| 			"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", | ||||
| 			"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" | ||||
| 		}, | ||||
| 		"minipass": { | ||||
| 			"version": "3.1.6", | ||||
|  | ||||
| @ -54,7 +54,7 @@ | ||||
| 		"missing-native-js-functions": "^1.2.17", | ||||
| 		"multer": "^1.4.2", | ||||
| 		"nanocolors": "^0.2.12", | ||||
| 		"node-fetch": "^2.6.7", | ||||
| 		"node-fetch": "^2.6.2", | ||||
| 		"supertest": "^6.1.6", | ||||
| 		"typescript": "^4.1.2" | ||||
| 	}, | ||||
|  | ||||
| @ -1,7 +1,47 @@ | ||||
| version: "3" | ||||
| version: '3.8' | ||||
| 
 | ||||
| services: | ||||
|   server: | ||||
|     image: fosscord/server | ||||
|   fosscord: | ||||
|     container_name: fosscord | ||||
|     image: fosscord | ||||
|     restart: on-failure:5 | ||||
|     # depends_on: mariadb | ||||
|     build: . | ||||
|     ports: | ||||
|       - 3001:3001 | ||||
|       - '3001-3005:3001-3005' | ||||
|     volumes: | ||||
|       # - ./data/:${WORK_DIR:-/srv/fosscord-server}/data/ | ||||
|       - data:${WORK_DIR:-/srv/fosscord-server}/ | ||||
|     environment: | ||||
|       WORK_DIR: ${WORK_DIR:-/srv/fosscord-server} | ||||
|       DEV_MODE: ${DEV_MODE:-0} | ||||
|       THREADS: ${THREADS:-1} | ||||
|       DATABASE: ${DATABASE:-../../data/database.db} | ||||
|       STORAGE_LOCATION: ${STORAGE_LOCATION:-../../data/files/} | ||||
|       HTTP_PORT: 3001 | ||||
|       WS_PORT: 3002 | ||||
|       CDN_PORT: 3003 | ||||
|       RTC_PORT: 3004 | ||||
|       ADMIN_PORT: 3005 | ||||
| 
 | ||||
|   # mariadb: | ||||
|   #   image: mariadb:latest | ||||
|   #   restart: on-failure:5 | ||||
|   #   environment: | ||||
|   #     MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-secr3tpassw0rd} | ||||
|   #     MYSQL_DATABASE: ${MYSQL_DATABASE:-fosscord} | ||||
|   #     MYSQL_USER: ${MYSQL_USER:-fosscord} | ||||
|   #     MYSQL_PASSWORD: ${MYSQL_PASSWORD:-password1} | ||||
|   #   networks: | ||||
|   #     - default | ||||
|   #   volumes: | ||||
|   #     - mariadb:/var/lib/mysql | ||||
| 
 | ||||
| volumes: | ||||
|   data: | ||||
|   # mariadb: | ||||
| 
 | ||||
| networks: | ||||
|   default: | ||||
|     name: fosscord | ||||
|     driver: bridge | ||||
|  | ||||
							
								
								
									
										20
									
								
								gateway/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										20
									
								
								gateway/package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -4479,8 +4479,9 @@ | ||||
| 			} | ||||
| 		}, | ||||
| 		"../util/node_modules/minimist": { | ||||
| 			"version": "1.2.5", | ||||
| 			"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" | ||||
| 			"version": "1.2.6", | ||||
| 			"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", | ||||
| 			"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" | ||||
| 		}, | ||||
| 		"../util/node_modules/minipass": { | ||||
| 			"version": "2.9.0", | ||||
| @ -8768,8 +8769,9 @@ | ||||
| 			} | ||||
| 		}, | ||||
| 		"node_modules/minimist": { | ||||
| 			"version": "1.2.5", | ||||
| 			"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", | ||||
| 			"version": "1.2.6", | ||||
| 			"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", | ||||
| 			"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", | ||||
| 			"dev": true | ||||
| 		}, | ||||
| 		"node_modules/minipass": { | ||||
| @ -13666,8 +13668,9 @@ | ||||
| 					} | ||||
| 				}, | ||||
| 				"minimist": { | ||||
| 					"version": "1.2.5", | ||||
| 					"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" | ||||
| 					"version": "1.2.6", | ||||
| 					"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", | ||||
| 					"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" | ||||
| 				}, | ||||
| 				"minipass": { | ||||
| 					"version": "2.9.0", | ||||
| @ -16870,8 +16873,9 @@ | ||||
| 			} | ||||
| 		}, | ||||
| 		"minimist": { | ||||
| 			"version": "1.2.5", | ||||
| 			"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", | ||||
| 			"version": "1.2.6", | ||||
| 			"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", | ||||
| 			"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", | ||||
| 			"dev": true | ||||
| 		}, | ||||
| 		"minipass": { | ||||
|  | ||||
| @ -240,8 +240,6 @@ export async function onIdentify(this: WebSocket, data: Payload) { | ||||
| 			x.guild_hashes = {}; // @ts-ignore
 | ||||
| 			x.guild_scheduled_events = []; // @ts-ignore
 | ||||
| 			x.threads = []; | ||||
| 			x.premium_subscription_count = 30; | ||||
| 			x.premium_tier = 3; | ||||
| 			return x; | ||||
| 		}), | ||||
| 		guild_experiments: [], // TODO
 | ||||
|  | ||||
							
								
								
									
										12
									
								
								util/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										12
									
								
								util/package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -5003,9 +5003,9 @@ | ||||
| 			} | ||||
| 		}, | ||||
| 		"node_modules/minimist": { | ||||
| 			"version": "1.2.5", | ||||
| 			"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", | ||||
| 			"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" | ||||
| 			"version": "1.2.6", | ||||
| 			"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", | ||||
| 			"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" | ||||
| 		}, | ||||
| 		"node_modules/minipass": { | ||||
| 			"version": "2.9.0", | ||||
| @ -12060,9 +12060,9 @@ | ||||
| 			} | ||||
| 		}, | ||||
| 		"minimist": { | ||||
| 			"version": "1.2.5", | ||||
| 			"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", | ||||
| 			"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" | ||||
| 			"version": "1.2.6", | ||||
| 			"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", | ||||
| 			"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" | ||||
| 		}, | ||||
| 		"minipass": { | ||||
| 			"version": "2.9.0", | ||||
|  | ||||
| @ -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, | ||||
| } | ||||
|  | ||||
| @ -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; | ||||
| 
 | ||||
| @ -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" | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
							
								
								
									
										35
									
								
								util/src/entities/Encryption.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								util/src/entities/Encryption.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||
| 
 | ||||
| } | ||||
| @ -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
 | ||||
|  | ||||
| @ -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, | ||||
|  | ||||
| @ -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, | ||||
| } | ||||
|  | ||||
| @ -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; | ||||
| } | ||||
|  | ||||
| @ -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
 | ||||
|  | ||||
| @ -27,4 +27,4 @@ export * from "./Template"; | ||||
| export * from "./User"; | ||||
| export * from "./VoiceState"; | ||||
| export * from "./Webhook"; | ||||
| export * from "./ClientRelase"; | ||||
| export * from "./ClientRelease"; | ||||
| @ -623,6 +623,7 @@ export type EVENT = | ||||
| 	| "PRESENCE_UPDATE" | ||||
| 	| "TYPING_START" | ||||
| 	| "USER_UPDATE" | ||||
| 	| "USER_NOTE_UPDATE" | ||||
| 	| "WEBHOOKS_UPDATE" | ||||
| 	| "INTERACTION_CREATE" | ||||
| 	| "VOICE_STATE_UPDATE" | ||||
|  | ||||
| @ -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, | ||||
|  | ||||
| @ -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
 | ||||
|  | ||||
							
								
								
									
										16
									
								
								util/src/migrations/1648643945733-ReleaseTypo.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								util/src/migrations/1648643945733-ReleaseTypo.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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"); | ||||
| 	} | ||||
| } | ||||
| @ -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); | ||||
| }  | ||||
|  | ||||
| @ -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( | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Madeline
						Madeline