Prettier u4

This commit is contained in:
Rory&
2025-12-17 08:25:23 +01:00
parent 35166f3b82
commit 9613ffa834
18 changed files with 130 additions and 486 deletions

View File

@@ -17,19 +17,9 @@
*/
import { route } from "@spacebar/api";
import {
Config,
DiscordApiErrors,
Emoji,
GuildEmojisUpdateEvent,
Member,
Snowflake,
User,
emitEvent,
handleFile,
} from "@spacebar/util";
import { Config, DiscordApiErrors, Emoji, GuildEmojisUpdateEvent, Member, Snowflake, User, emitEvent, handleFile } from "@spacebar/util";
import { Request, Response, Router } from "express";
import { EmojiCreateSchema, EmojiModifySchema } from "@spacebar/schemas"
import { EmojiCreateSchema, EmojiModifySchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
@@ -115,10 +105,7 @@ router.post(
});
const { maxEmojis } = Config.get().limits.guild;
if (emoji_count >= maxEmojis)
throw DiscordApiErrors.MAXIMUM_NUMBER_OF_EMOJIS_REACHED.withParams(
maxEmojis,
);
if (emoji_count >= maxEmojis) throw DiscordApiErrors.MAXIMUM_NUMBER_OF_EMOJIS_REACHED.withParams(maxEmojis);
if (body.require_colons == null) body.require_colons = true;
const user = await User.findOneOrFail({ where: { id: req.user_id } });
@@ -132,10 +119,7 @@ router.post(
require_colons: body.require_colons ?? undefined, // schema allows nulls, db does not
user: user,
managed: false,
animated:
mimeType == "image/gif" ||
mimeType == "image/apng" ||
mimeType == "video/webm",
animated: mimeType == "image/gif" || mimeType == "image/apng" || mimeType == "video/webm",
available: true,
roles: [],
}).save();

View File

@@ -17,22 +17,10 @@
*/
import { route } from "@spacebar/api";
import {
Channel,
DiscordApiErrors,
Guild,
GuildUpdateEvent,
Member,
Permissions,
SpacebarApiErrors,
emitEvent,
getPermission,
getRights,
handleFile,
} from "@spacebar/util";
import { Channel, DiscordApiErrors, Guild, GuildUpdateEvent, Member, Permissions, SpacebarApiErrors, emitEvent, getPermission, getRights, handleFile } from "@spacebar/util";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
import { GuildUpdateSchema } from "@spacebar/schemas"
import { GuildUpdateSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
@@ -54,15 +42,8 @@ router.get(
async (req: Request, res: Response) => {
const { guild_id } = req.params;
const [guild, member] = await Promise.all([
Guild.findOneOrFail({ where: { id: guild_id } }),
Member.findOne({ where: { guild_id: guild_id, id: req.user_id } }),
]);
if (!member)
throw new HTTPError(
"You are not a member of the guild you are trying to access",
401,
);
const [guild, member] = await Promise.all([Guild.findOneOrFail({ where: { id: guild_id } }), Member.findOne({ where: { guild_id: guild_id, id: req.user_id } })]);
if (!member) throw new HTTPError("You are not a member of the guild you are trying to access", 401);
return res.send({
...guild,
@@ -98,10 +79,7 @@ router.patch(
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_GUILDS",
);
if (!rights.has("MANAGE_GUILDS") && !permission.has("MANAGE_GUILD")) throw DiscordApiErrors.MISSING_PERMISSIONS.withParams("MANAGE_GUILDS");
const guild = await Guild.findOneOrFail({
where: { id: guild_id },
@@ -118,47 +96,25 @@ router.patch(
// TODO: guild update check image
if (body.icon && body.icon != guild.icon)
body.icon = await handleFile(`/icons/${guild_id}`, body.icon);
if (body.icon && body.icon != guild.icon) body.icon = await handleFile(`/icons/${guild_id}`, body.icon);
if (body.banner && body.banner !== guild.banner)
body.banner = await handleFile(`/banners/${guild_id}`, body.banner);
if (body.banner && body.banner !== guild.banner) body.banner = await handleFile(`/banners/${guild_id}`, body.banner);
if (body.splash && body.splash !== guild.splash)
body.splash = await handleFile(
`/splashes/${guild_id}`,
body.splash,
);
if (body.splash && body.splash !== guild.splash) body.splash = await handleFile(`/splashes/${guild_id}`, body.splash);
if (
body.discovery_splash &&
body.discovery_splash !== guild.discovery_splash
)
body.discovery_splash = await handleFile(
`/discovery-splashes/${guild_id}`,
body.discovery_splash,
);
if (body.discovery_splash && body.discovery_splash !== guild.discovery_splash)
body.discovery_splash = await handleFile(`/discovery-splashes/${guild_id}`, body.discovery_splash);
if (body.features) {
const diff = guild.features
.filter((x) => !body.features?.includes(x))
.concat(
body.features.filter((x) => !guild.features.includes(x)),
);
const diff = guild.features.filter((x) => !body.features?.includes(x)).concat(body.features.filter((x) => !guild.features.includes(x)));
// TODO move these
const MUTABLE_FEATURES = [
"COMMUNITY",
"INVITES_DISABLED",
"DISCOVERABLE",
];
const MUTABLE_FEATURES = ["COMMUNITY", "INVITES_DISABLED", "DISCOVERABLE"];
for (const feature of diff) {
if (MUTABLE_FEATURES.includes(feature)) continue;
throw SpacebarApiErrors.FEATURE_IS_IMMUTABLE.withParams(
feature,
);
throw SpacebarApiErrors.FEATURE_IS_IMMUTABLE.withParams(feature);
}
// for some reason, they don't update in the assign.

View File

@@ -17,15 +17,9 @@
*/
import { route } from "@spacebar/api";
import {
emitEvent,
GuildMemberUpdateEvent,
handleFile,
Member,
OrmUtils,
} from "@spacebar/util";
import { emitEvent, GuildMemberUpdateEvent, handleFile, Member, OrmUtils } from "@spacebar/util";
import { Request, Response, Router } from "express";
import { MemberChangeProfileSchema } from "@spacebar/schemas"
import { MemberChangeProfileSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
@@ -56,11 +50,7 @@ router.patch(
relations: ["roles", "user"],
});
if (body.banner)
body.banner = await handleFile(
`/guilds/${guild_id}/users/${req.user_id}/avatars`,
body.banner as string,
);
if (body.banner) body.banner = await handleFile(`/guilds/${guild_id}/users/${req.user_id}/avatars`, body.banner as string);
member = await OrmUtils.mergeDeep(member, body);

View File

@@ -23,12 +23,7 @@ import { IsNull, LessThan } from "typeorm";
const router = Router({ mergeParams: true });
//Returns all inactive members, respecting role hierarchy
const inactiveMembers = async (
guild_id: string,
user_id: string,
days: number,
roles: string[] = [],
) => {
const inactiveMembers = async (guild_id: string, user_id: string, days: number, roles: string[] = []) => {
const date = new Date();
date.setDate(date.getDate() - days);
//Snowflake should have `generateFromTime` method? Or similar?
@@ -54,10 +49,7 @@ const inactiveMembers = async (
if (!members.length) return [];
//I'm sure I can do this in the above db query ( and it would probably be better to do so ), but oh well.
if (roles.length && members.length)
members = members.filter((user) =>
user.roles?.some((role) => roles.includes(role.id)),
);
if (roles.length && members.length) members = members.filter((user) => user.roles?.some((role) => roles.includes(role.id)));
const me = await Member.findOneOrFail({
where: { id: user_id, guild_id },
@@ -95,12 +87,7 @@ router.get(
let roles = req.query.include_roles;
if (typeof roles === "string") roles = [roles]; //express will return array otherwise
const members = await inactiveMembers(
req.params.guild_id,
req.user_id,
days,
roles as string[],
);
const members = await inactiveMembers(req.params.guild_id, req.user_id, days, roles as string[]);
res.send({ pruned: members.length });
},
@@ -127,16 +114,9 @@ router.post(
if (typeof roles === "string") roles = [roles];
const { guild_id } = req.params;
const members = await inactiveMembers(
guild_id,
req.user_id,
days,
roles as string[],
);
const members = await inactiveMembers(guild_id, req.user_id, days, roles as string[]);
await Promise.all(
members.map((x) => Member.removeFromGuild(x.id, guild_id)),
);
await Promise.all(members.map((x) => Member.removeFromGuild(x.id, guild_id)));
res.send({ purged: members.length });
},

View File

@@ -43,28 +43,20 @@ router.get(
limits_user_maxBio: general.limits.user.maxBio,
limits_guild_maxEmojis: general.limits.guild.maxEmojis,
limits_guild_maxRoles: general.limits.guild.maxRoles,
limits_message_maxCharacters:
general.limits.message.maxCharacters,
limits_message_maxAttachmentSize:
general.limits.message.maxAttachmentSize,
limits_message_maxEmbedDownloadSize:
general.limits.message.maxEmbedDownloadSize,
limits_message_maxCharacters: general.limits.message.maxCharacters,
limits_message_maxAttachmentSize: general.limits.message.maxAttachmentSize,
limits_message_maxEmbedDownloadSize: general.limits.message.maxEmbedDownloadSize,
limits_channel_maxWebhooks: general.limits.channel.maxWebhooks,
register_dateOfBirth_requiredc:
general.register.dateOfBirth.required,
register_dateOfBirth_requiredc: general.register.dateOfBirth.required,
register_password_required: general.register.password.required,
register_disabled: general.register.disabled,
register_requireInvite: general.register.requireInvite,
register_allowNewRegistration:
general.register.allowNewRegistration,
register_allowMultipleAccounts:
general.register.allowMultipleAccounts,
register_allowNewRegistration: general.register.allowNewRegistration,
register_allowMultipleAccounts: general.register.allowMultipleAccounts,
guild_autoJoin_canLeave: general.guild.autoJoin.canLeave,
guild_autoJoin_guilds_x: general.guild.autoJoin.guilds,
register_email_required: general.register.email.required,
can_recover_account:
general.email.provider != null &&
general.general.frontPage != null,
can_recover_account: general.email.provider != null && general.general.frontPage != null,
};
}
res.send(outputtedConfig);

View File

@@ -17,14 +17,7 @@
*/
import { route } from "@spacebar/api";
import {
Config,
getRights,
Guild,
Member,
Message,
User,
} from "@spacebar/util";
import { Config, getRights, Guild, Member, Message, User } from "@spacebar/util";
import { Request, Response, Router } from "express";
const router = Router({ mergeParams: true });

View File

@@ -19,7 +19,7 @@
import { route } from "@spacebar/api";
import { Channel, DmChannelDTO, Recipient } from "@spacebar/util";
import { Request, Response, Router } from "express";
import { DmChannelCreateSchema } from "@spacebar/schemas"
import { DmChannelCreateSchema } from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
@@ -37,13 +37,7 @@ router.get(
where: { user_id: req.user_id, closed: false },
relations: ["channel", "channel.recipients"],
});
res.json(
await Promise.all(
recipients.map((r) =>
DmChannelDTO.from(r.channel, [req.user_id]),
),
),
);
res.json(await Promise.all(recipients.map((r) => DmChannelDTO.from(r.channel, [req.user_id]))));
},
);
@@ -59,13 +53,7 @@ router.post(
}),
async (req: Request, res: Response) => {
const body = req.body as DmChannelCreateSchema;
res.json(
await Channel.createDMChannel(
body.recipients,
req.user_id,
body.name,
),
);
res.json(await Channel.createDMChannel(body.recipients, req.user_id, body.name));
},
);

View File

@@ -27,18 +27,7 @@ router.get("/", route({}), async (req: Request, res: Response) => {
where: {
user_id: req.user_id,
},
select: [
"external_id",
"type",
"name",
"verified",
"visibility",
"show_activity",
"revoked",
"token_data",
"friend_sync",
"integrations",
],
select: ["external_id", "type", "name", "verified", "visibility", "show_activity", "revoked", "token_data", "friend_sync", "integrations"],
});
res.json(connections.map((x) => new ConnectedAccountDTO(x, true)));

View File

@@ -17,15 +17,7 @@
*/
import { route } from "@spacebar/api";
import {
Config,
emitEvent,
FieldErrors,
generateToken,
handleFile,
User,
UserUpdateEvent,
} from "@spacebar/util";
import { Config, emitEvent, FieldErrors, generateToken, handleFile, User, UserUpdateEvent } from "@spacebar/util";
import bcrypt from "bcrypt";
import { Request, Response, Router } from "express";
import { PrivateUserProjection, UserModifySchema } from "@spacebar/schemas";
@@ -78,23 +70,12 @@ router.patch(
// Populated on password change
let newToken: string | undefined;
if (body.avatar)
body.avatar = await handleFile(
`/avatars/${req.user_id}`,
body.avatar as string,
);
if (body.banner)
body.banner = await handleFile(
`/banners/${req.user_id}`,
body.banner as string,
);
if (body.avatar) body.avatar = await handleFile(`/avatars/${req.user_id}`, body.avatar as string);
if (body.banner) body.banner = await handleFile(`/banners/${req.user_id}`, body.banner as string);
if (body.password) {
if (user.data?.hash) {
const same_password = await bcrypt.compare(
body.password,
user.data.hash || "",
);
const same_password = await bcrypt.compare(body.password, user.data.hash || "");
if (!same_password) {
throw FieldErrors({
password: {
@@ -151,10 +132,7 @@ router.patch(
}
const { maxUsername } = Config.get().limits.user;
if (
check_username.length > maxUsername ||
check_username.length < 2
) {
if (check_username.length > maxUsername || check_username.length < 2) {
throw FieldErrors({
username: {
code: "BASE_TYPE_BAD_LENGTH",

View File

@@ -17,14 +17,9 @@
*/
import { route } from "@spacebar/api";
import {
BackupCode,
DiscordApiErrors,
User,
generateMfaBackupCodes,
} from "@spacebar/util";
import { BackupCode, DiscordApiErrors, User, generateMfaBackupCodes } from "@spacebar/util";
import { Request, Response, Router } from "express";
import { CodesVerificationSchema } from "@spacebar/schemas"
import { CodesVerificationSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
@@ -52,15 +47,11 @@ router.post(
// Once that's done, this route can verify `key`
// const user = await User.findOneOrFail({ where: { id: req.user_id } });
if ((await User.count({ where: { id: req.user_id } })) === 0)
throw DiscordApiErrors.UNKNOWN_USER;
if ((await User.count({ where: { id: req.user_id } })) === 0) throw DiscordApiErrors.UNKNOWN_USER;
let codes: BackupCode[];
if (regenerate) {
await BackupCode.update(
{ user: { id: req.user_id } },
{ expired: true },
);
await BackupCode.update({ user: { id: req.user_id } }, { expired: true });
codes = generateMfaBackupCodes(req.user_id);
await Promise.all(codes.map((x) => x.save()));

View File

@@ -17,15 +17,10 @@
*/
import { route } from "@spacebar/api";
import {
BackupCode,
FieldErrors,
generateMfaBackupCodes,
User,
} from "@spacebar/util";
import { BackupCode, FieldErrors, generateMfaBackupCodes, User } from "@spacebar/util";
import bcrypt from "bcrypt";
import { Request, Response, Router } from "express";
import { MfaCodesSchema } from "@spacebar/schemas"
import { MfaCodesSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
@@ -36,8 +31,7 @@ router.post(
route({
requestBody: "MfaCodesSchema",
deprecated: true,
description:
"This route is replaced with users/@me/mfa/codes-verification in newer clients",
description: "This route is replaced with users/@me/mfa/codes-verification in newer clients",
responses: {
200: {
body: "APIBackupCodeArray",
@@ -69,10 +63,7 @@ router.post(
let codes: BackupCode[];
if (regenerate) {
await BackupCode.update(
{ user: { id: req.user_id } },
{ expired: true },
);
await BackupCode.update({ user: { id: req.user_id } }, { expired: true });
codes = generateMfaBackupCodes(req.user_id);
await Promise.all(codes.map((x) => x.save()));

View File

@@ -17,15 +17,11 @@
*/
import { route } from "@spacebar/api";
import {
BackupCode,
User,
generateToken,
} from "@spacebar/util";
import { BackupCode, User, generateToken } from "@spacebar/util";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
import { verifyToken } from "node-2fa";
import { TotpDisableSchema } from "@spacebar/schemas"
import { TotpDisableSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
@@ -53,11 +49,7 @@ router.post(
const backup = await BackupCode.findOne({ where: { code: body.code } });
if (!backup) {
const ret = verifyToken(user.totp_secret || "", body.code);
if (!ret || ret.delta != 0)
throw new HTTPError(
req.t("auth:login.INVALID_TOTP_CODE"),
60008,
);
if (!ret || ret.delta != 0) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
}
await User.update(

View File

@@ -17,16 +17,12 @@
*/
import { route } from "@spacebar/api";
import {
User,
generateMfaBackupCodes,
generateToken,
} from "@spacebar/util";
import { User, generateMfaBackupCodes, generateToken } from "@spacebar/util";
import bcrypt from "bcrypt";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
import { verifyToken } from "node-2fa";
import { TotpEnableSchema } from "@spacebar/schemas"
import { TotpEnableSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
@@ -61,21 +57,15 @@ router.post(
}
}
if (!body.secret)
throw new HTTPError(req.t("auth:login.INVALID_TOTP_SECRET"), 60005);
if (!body.secret) throw new HTTPError(req.t("auth:login.INVALID_TOTP_SECRET"), 60005);
if (!body.code)
throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
if (!body.code) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
if (verifyToken(body.secret, body.code)?.delta != 0)
throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
if (verifyToken(body.secret, body.code)?.delta != 0) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008);
const backup_codes = generateMfaBackupCodes(req.user_id);
await Promise.all(backup_codes.map((x) => x.save()));
await User.update(
{ id: req.user_id },
{ mfa_enabled: true, totp_secret: body.secret },
);
await User.update({ id: req.user_id }, { mfa_enabled: true, totp_secret: body.secret });
res.send({
token: await generateToken(user.id),

View File

@@ -17,31 +17,19 @@
*/
import { route } from "@spacebar/api";
import {
DiscordApiErrors,
FieldErrors,
generateWebAuthnTicket,
SecurityKey,
User,
verifyWebAuthnToken,
WebAuthn,
} from "@spacebar/util";
import { DiscordApiErrors, FieldErrors, generateWebAuthnTicket, SecurityKey, User, verifyWebAuthnToken, WebAuthn } from "@spacebar/util";
import bcrypt from "bcrypt";
import { Request, Response, Router } from "express";
import { ExpectedAttestationResult } from "fido2-lib";
import { HTTPError } from "lambert-server";
import { CreateWebAuthnCredentialSchema, GenerateWebAuthnCredentialsSchema, WebAuthnPostSchema } from "@spacebar/schemas"
import { CreateWebAuthnCredentialSchema, GenerateWebAuthnCredentialsSchema, WebAuthnPostSchema } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
const isGenerateSchema = (
body: WebAuthnPostSchema,
): body is GenerateWebAuthnCredentialsSchema => {
const isGenerateSchema = (body: WebAuthnPostSchema): body is GenerateWebAuthnCredentialsSchema => {
return "password" in body;
};
const isCreateSchema = (
body: WebAuthnPostSchema,
): body is CreateWebAuthnCredentialSchema => {
const isCreateSchema = (body: WebAuthnPostSchema): body is CreateWebAuthnCredentialSchema => {
return "credential" in body;
};
@@ -92,24 +80,13 @@ router.post(
where: {
id: req.user_id,
},
select: [
"data",
"id",
"disabled",
"deleted",
"totp_secret",
"mfa_enabled",
"username",
],
select: ["data", "id", "disabled", "deleted", "totp_secret", "mfa_enabled", "username"],
relations: ["settings"],
});
if (isGenerateSchema(req.body)) {
const { password } = req.body;
const same_password = await bcrypt.compare(
password,
user.data.hash || "",
);
const same_password = await bcrypt.compare(password, user.data.hash || "");
if (!same_password) {
throw FieldErrors({
password: {
@@ -119,14 +96,11 @@ router.post(
});
}
const registrationOptions =
await WebAuthn.fido2.attestationOptions();
const registrationOptions = await WebAuthn.fido2.attestationOptions();
const challenge = JSON.stringify({
publicKey: {
...registrationOptions,
challenge: Buffer.from(
registrationOptions.challenge,
).toString("base64"),
challenge: Buffer.from(registrationOptions.challenge).toString("base64"),
user: {
id: user.id,
name: user.username,
@@ -149,35 +123,20 @@ router.post(
const clientAttestationResponse = JSON.parse(credential);
if (!clientAttestationResponse.rawId)
throw new HTTPError("Missing rawId", 400);
if (!clientAttestationResponse.rawId) throw new HTTPError("Missing rawId", 400);
const rawIdBuffer = Buffer.from(
clientAttestationResponse.rawId,
"base64",
);
const rawIdBuffer = Buffer.from(clientAttestationResponse.rawId, "base64");
clientAttestationResponse.rawId = toArrayBuffer(rawIdBuffer);
const attestationExpectations: ExpectedAttestationResult =
JSON.parse(
Buffer.from(
clientAttestationResponse.response.clientDataJSON,
"base64",
).toString(),
);
const attestationExpectations: ExpectedAttestationResult = JSON.parse(Buffer.from(clientAttestationResponse.response.clientDataJSON, "base64").toString());
const regResult = await WebAuthn.fido2.attestationResult(
clientAttestationResponse,
{
...attestationExpectations,
factor: "second",
},
);
const regResult = await WebAuthn.fido2.attestationResult(clientAttestationResponse, {
...attestationExpectations,
factor: "second",
});
const authnrData = regResult.authnrData;
const keyId = Buffer.from(authnrData.get("credId")).toString(
"base64",
);
const keyId = Buffer.from(authnrData.get("credId")).toString("base64");
const counter = authnrData.get("counter");
const publicKey = authnrData.get("credentialPublicKeyPem");
@@ -189,10 +148,7 @@ router.post(
key_id: keyId,
});
await Promise.all([
securityKey.save(),
User.update({ id: req.user_id }, { webauthn_enabled: true }),
]);
await Promise.all([securityKey.save(), User.update({ id: req.user_id }, { webauthn_enabled: true })]);
return res.json({
name,

View File

@@ -17,25 +17,14 @@
*/
import { route } from "@spacebar/api";
import {
Config,
DiscordApiErrors,
Relationship,
RelationshipAddEvent,
RelationshipRemoveEvent,
User,
emitEvent,
} from "@spacebar/util";
import { Config, DiscordApiErrors, Relationship, RelationshipAddEvent, RelationshipRemoveEvent, User, emitEvent } from "@spacebar/util";
import { Request, Response, Router } from "express";
import { HTTPError } from "lambert-server";
import { PublicUserProjection, RelationshipType } from "@spacebar/schemas";
const router = Router({ mergeParams: true });
const userProjection: (keyof User)[] = [
"relationships",
...PublicUserProjection,
];
const userProjection: (keyof User)[] = ["relationships", ...PublicUserProjection];
router.get(
"/",
@@ -111,10 +100,7 @@ router.post(
relations: ["relationships", "relationships.to"],
select: userProjection,
where: {
discriminator: String(req.body.discriminator).padStart(
4,
"0",
), //Discord send the discriminator as integer, we need to add leading zeroes
discriminator: String(req.body.discriminator).padStart(4, "0"), //Discord send the discriminator as integer, we need to add leading zeroes
username: req.body.username,
},
}),
@@ -138,8 +124,7 @@ router.delete(
}),
async (req: Request, res: Response) => {
const { user_id } = req.params;
if (user_id === req.user_id)
throw new HTTPError("You can't remove yourself as a friend");
if (user_id === req.user_id) throw new HTTPError("You can't remove yourself as a friend");
const user = await User.findOneOrFail({
where: { id: req.user_id },
@@ -153,12 +138,9 @@ router.delete(
});
const relationship = user.relationships.find((x) => x.to_id === user_id);
const friendRequest = friend.relationships.find(
(x) => x.to_id === req.user_id,
);
const friendRequest = friend.relationships.find((x) => x.to_id === req.user_id);
if (!relationship)
throw new HTTPError("You are not friends with the user", 404);
if (!relationship) throw new HTTPError("You are not friends with the user", 404);
if (relationship?.type === RelationshipType.blocked) {
// unblock user
@@ -198,15 +180,9 @@ router.delete(
export default router;
async function updateRelationship(
req: Request,
res: Response,
friend: User,
type: RelationshipType,
) {
async function updateRelationship(req: Request, res: Response, friend: User, type: RelationshipType) {
const id = friend.id;
if (id === req.user_id)
throw new HTTPError("You can't add yourself as a friend");
if (id === req.user_id) throw new HTTPError("You can't add yourself as a friend");
const user = await User.findOneOrFail({
where: { id: req.user_id },
@@ -215,15 +191,12 @@ async function updateRelationship(
});
let relationship = user.relationships.find((x) => x.to_id === id);
const friendRequest = friend.relationships.find(
(x) => x.to_id === req.user_id,
);
const friendRequest = friend.relationships.find((x) => x.to_id === req.user_id);
// TODO: you can add infinitely many blocked users (should this be prevented?)
if (type === RelationshipType.blocked) {
if (relationship) {
if (relationship.type === RelationshipType.blocked)
throw new HTTPError("You already blocked the user");
if (relationship.type === RelationshipType.blocked) throw new HTTPError("You already blocked the user");
relationship.type = RelationshipType.blocked;
await relationship.save();
} else {
@@ -255,8 +228,7 @@ async function updateRelationship(
}
const { maxFriends } = Config.get().limits.user;
if (user.relationships.length >= maxFriends)
throw DiscordApiErrors.MAXIMUM_FRIENDS.withParams(maxFriends);
if (user.relationships.length >= maxFriends) throw DiscordApiErrors.MAXIMUM_FRIENDS.withParams(maxFriends);
let incoming_relationship = Relationship.create({
nickname: undefined,
@@ -272,24 +244,17 @@ async function updateRelationship(
});
if (friendRequest) {
if (friendRequest.type === RelationshipType.blocked)
throw new HTTPError("The user blocked you");
if (friendRequest.type === RelationshipType.friends)
throw new HTTPError("You are already friends with the user");
if (friendRequest.type === RelationshipType.blocked) throw new HTTPError("The user blocked you");
if (friendRequest.type === RelationshipType.friends) throw new HTTPError("You are already friends with the user");
// accept friend request
incoming_relationship = friendRequest;
incoming_relationship.type = RelationshipType.friends;
}
if (relationship) {
if (relationship.type === RelationshipType.outgoing)
throw new HTTPError("You already sent a friend request");
if (relationship.type === RelationshipType.blocked)
throw new HTTPError(
"Unblock the user before sending a friend request",
);
if (relationship.type === RelationshipType.friends)
throw new HTTPError("You are already friends with the user");
if (relationship.type === RelationshipType.outgoing) throw new HTTPError("You already sent a friend request");
if (relationship.type === RelationshipType.blocked) throw new HTTPError("Unblock the user before sending a friend request");
if (relationship.type === RelationshipType.friends) throw new HTTPError("You are already friends with the user");
outgoing_relationship = relationship;
outgoing_relationship.type = RelationshipType.friends;
}

View File

@@ -18,14 +18,10 @@
import { route } from "@spacebar/api";
import { Request, Response, Router } from "express";
import {
emitEvent,
OrmUtils,
UserSettingsProtos,
} from "@spacebar/util";
import { emitEvent, OrmUtils, UserSettingsProtos } from "@spacebar/util";
import { PreloadedUserSettings } from "discord-protos";
import { JsonValue } from "@protobuf-ts/runtime";
import { SettingsProtoJsonResponse, SettingsProtoResponse, SettingsProtoUpdateJsonSchema, SettingsProtoUpdateSchema } from "@spacebar/schemas"
import { SettingsProtoJsonResponse, SettingsProtoResponse, SettingsProtoUpdateJsonSchema, SettingsProtoUpdateSchema } from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
@@ -41,8 +37,7 @@ router.get(
query: {
atomic: {
type: "boolean",
description:
"Whether to try to apply the settings update atomically (default false)",
description: "Whether to try to apply the settings update atomically (default false)",
},
},
}),
@@ -50,9 +45,7 @@ router.get(
const userSettings = await UserSettingsProtos.getOrDefault(req.user_id);
res.json({
settings: PreloadedUserSettings.toBase64(
userSettings.userSettings!,
),
settings: PreloadedUserSettings.toBase64(userSettings.userSettings!),
} as SettingsProtoResponse);
},
);
@@ -68,17 +61,11 @@ router.patch(
},
}),
async (req: Request, res: Response) => {
const { settings, required_data_version } =
req.body as SettingsProtoUpdateSchema;
const { settings, required_data_version } = req.body as SettingsProtoUpdateSchema;
const { atomic } = req.query;
const updatedSettings = PreloadedUserSettings.fromBase64(settings);
const resultObj = await patchUserSettings(
req.user_id,
updatedSettings,
required_data_version,
atomic == "true",
);
const resultObj = await patchUserSettings(req.user_id, updatedSettings, required_data_version, atomic == "true");
res.json({
settings: PreloadedUserSettings.toBase64(resultObj.settings),
@@ -119,23 +106,16 @@ router.patch(
query: {
atomic: {
type: "boolean",
description:
"Whether to try to apply the settings update atomically (default false)",
description: "Whether to try to apply the settings update atomically (default false)",
},
},
}),
async (req: Request, res: Response) => {
const { settings, required_data_version } =
req.body as SettingsProtoUpdateJsonSchema;
const { settings, required_data_version } = req.body as SettingsProtoUpdateJsonSchema;
const { atomic } = req.query;
const updatedSettings = PreloadedUserSettings.fromJson(settings);
const resultObj = await patchUserSettings(
req.user_id,
updatedSettings,
required_data_version,
atomic == "true",
);
const resultObj = await patchUserSettings(req.user_id, updatedSettings, required_data_version, atomic == "true");
res.json({
settings: PreloadedUserSettings.toJson(resultObj.settings),
@@ -146,20 +126,11 @@ router.patch(
//#endregion
async function patchUserSettings(
userId: string,
updatedSettings: PreloadedUserSettings,
required_data_version: number | undefined,
atomic: boolean = false,
) {
async function patchUserSettings(userId: string, updatedSettings: PreloadedUserSettings, required_data_version: number | undefined, atomic: boolean = false) {
const userSettings = await UserSettingsProtos.getOrDefault(userId);
let settings = userSettings.userSettings!;
if (
required_data_version &&
settings.versions &&
settings.versions.dataVersion > required_data_version
) {
if (required_data_version && settings.versions && settings.versions.dataVersion > required_data_version) {
return {
settings: settings,
out_of_date: true,
@@ -167,24 +138,15 @@ async function patchUserSettings(
}
if ((process.env.LOG_PROTO_UPDATES || process.env.LOG_PROTO_SETTINGS_UPDATES) && process.env.LOG_PROTO_SETTINGS_UPDATES !== "false")
console.log(
`Updating user settings for user ${userId} with atomic=${atomic}:`,
updatedSettings,
);
console.log(`Updating user settings for user ${userId} with atomic=${atomic}:`, updatedSettings);
if (!atomic) {
settings = PreloadedUserSettings.fromJson(
Object.assign(
PreloadedUserSettings.toJson(settings) as object,
PreloadedUserSettings.toJson(updatedSettings) as object,
) as JsonValue,
Object.assign(PreloadedUserSettings.toJson(settings) as object, PreloadedUserSettings.toJson(updatedSettings) as object) as JsonValue,
);
} else {
settings = PreloadedUserSettings.fromJson(
OrmUtils.mergeDeep(
PreloadedUserSettings.toJson(settings) as object,
PreloadedUserSettings.toJson(updatedSettings) as object,
) as JsonValue,
OrmUtils.mergeDeep(PreloadedUserSettings.toJson(settings) as object, PreloadedUserSettings.toJson(updatedSettings) as object) as JsonValue,
);
}
@@ -202,14 +164,14 @@ async function patchUserSettings(
data: {
settings: {
proto: PreloadedUserSettings.toBase64(settings),
type: 1
type: 1,
},
json_settings: {
proto: PreloadedUserSettings.toJson(settings),
type: "user_settings"
type: "user_settings",
},
partial: false, // Unsure how this should behave
}
},
});
// This should also send a USER_SETTINGS_UPDATE event, but that isn't sent
// when using the USER_SETTINGS_PROTOS capability, so we ignore it for now.

View File

@@ -18,14 +18,10 @@
import { route } from "@spacebar/api";
import { Request, Response, Router } from "express";
import {
emitEvent,
OrmUtils,
UserSettingsProtos,
} from "@spacebar/util";
import { emitEvent, OrmUtils, UserSettingsProtos } from "@spacebar/util";
import { FrecencyUserSettings } from "discord-protos";
import { JsonValue } from "@protobuf-ts/runtime";
import { SettingsProtoJsonResponse, SettingsProtoResponse, SettingsProtoUpdateJsonSchema, SettingsProtoUpdateSchema } from "@spacebar/schemas"
import { SettingsProtoJsonResponse, SettingsProtoResponse, SettingsProtoUpdateJsonSchema, SettingsProtoUpdateSchema } from "@spacebar/schemas";
const router: Router = Router({ mergeParams: true });
@@ -41,8 +37,7 @@ router.get(
query: {
atomic: {
type: "boolean",
description:
"Whether to try to apply the settings update atomically (default false)",
description: "Whether to try to apply the settings update atomically (default false)",
},
},
}),
@@ -50,9 +45,7 @@ router.get(
const userSettings = await UserSettingsProtos.getOrDefault(req.user_id);
res.json({
settings: FrecencyUserSettings.toBase64(
userSettings.frecencySettings!,
),
settings: FrecencyUserSettings.toBase64(userSettings.frecencySettings!),
} as SettingsProtoResponse);
},
);
@@ -68,17 +61,11 @@ router.patch(
},
}),
async (req: Request, res: Response) => {
const { settings, required_data_version } =
req.body as SettingsProtoUpdateSchema;
const { settings, required_data_version } = req.body as SettingsProtoUpdateSchema;
const { atomic } = req.query;
const updatedSettings = FrecencyUserSettings.fromBase64(settings);
const resultObj = await patchUserSettings(
req.user_id,
updatedSettings,
required_data_version,
atomic == "true",
);
const resultObj = await patchUserSettings(req.user_id, updatedSettings, required_data_version, atomic == "true");
res.json({
settings: FrecencyUserSettings.toBase64(resultObj.settings),
@@ -102,9 +89,7 @@ router.get(
const userSettings = await UserSettingsProtos.getOrDefault(req.user_id);
res.json({
settings: FrecencyUserSettings.toJson(
userSettings.frecencySettings!,
),
settings: FrecencyUserSettings.toJson(userSettings.frecencySettings!),
} as SettingsProtoJsonResponse);
},
);
@@ -121,23 +106,16 @@ router.patch(
query: {
atomic: {
type: "boolean",
description:
"Whether to try to apply the settings update atomically (default false)",
description: "Whether to try to apply the settings update atomically (default false)",
},
},
}),
async (req: Request, res: Response) => {
const { settings, required_data_version } =
req.body as SettingsProtoUpdateJsonSchema;
const { settings, required_data_version } = req.body as SettingsProtoUpdateJsonSchema;
const { atomic } = req.query;
const updatedSettings = FrecencyUserSettings.fromJson(settings);
const resultObj = await patchUserSettings(
req.user_id,
updatedSettings,
required_data_version,
atomic == "true",
);
const resultObj = await patchUserSettings(req.user_id, updatedSettings, required_data_version, atomic == "true");
res.json({
settings: FrecencyUserSettings.toJson(resultObj.settings),
@@ -148,20 +126,11 @@ router.patch(
//#endregion
async function patchUserSettings(
userId: string,
updatedSettings: FrecencyUserSettings,
required_data_version: number | undefined,
atomic: boolean = false,
) {
async function patchUserSettings(userId: string, updatedSettings: FrecencyUserSettings, required_data_version: number | undefined, atomic: boolean = false) {
const userSettings = await UserSettingsProtos.getOrDefault(userId);
let settings = userSettings.frecencySettings!;
if (
required_data_version &&
settings.versions &&
settings.versions.dataVersion > required_data_version
) {
if (required_data_version && settings.versions && settings.versions.dataVersion > required_data_version) {
return {
settings: settings,
out_of_date: true,
@@ -169,24 +138,15 @@ async function patchUserSettings(
}
if ((process.env.LOG_PROTO_UPDATES || process.env.LOG_PROTO_FRECENCY_UPDATES) && process.env.LOG_PROTO_FRECENCY_UPDATES !== "false")
console.log(
`Updating frecency settings for user ${userId} with atomic=${atomic}:`,
updatedSettings,
);
console.log(`Updating frecency settings for user ${userId} with atomic=${atomic}:`, updatedSettings);
if (!atomic) {
settings = FrecencyUserSettings.fromJson(
Object.assign(
FrecencyUserSettings.toJson(settings) as object,
FrecencyUserSettings.toJson(updatedSettings) as object,
) as JsonValue,
Object.assign(FrecencyUserSettings.toJson(settings) as object, FrecencyUserSettings.toJson(updatedSettings) as object) as JsonValue,
);
} else {
settings = FrecencyUserSettings.fromJson(
OrmUtils.mergeDeep(
FrecencyUserSettings.toJson(settings) as object,
FrecencyUserSettings.toJson(updatedSettings) as object,
) as JsonValue,
OrmUtils.mergeDeep(FrecencyUserSettings.toJson(settings) as object, FrecencyUserSettings.toJson(updatedSettings) as object) as JsonValue,
);
}
@@ -204,14 +164,14 @@ async function patchUserSettings(
data: {
settings: {
proto: FrecencyUserSettings.toBase64(settings),
type: 2
type: 2,
},
json_settings: {
proto: FrecencyUserSettings.toJson(settings),
type: "frecency_settings"
type: "frecency_settings",
},
partial: false, // Unsure how this should behave
}
},
});
// This should also send a USER_SETTINGS_UPDATE event, but that isn't sent
// when using the USER_SETTINGS_PROTOS capability, so we ignore it for now.

View File

@@ -18,12 +18,7 @@
import dotenv from "dotenv";
dotenv.config({ quiet: true });
import {
closeDatabase,
Config,
initDatabase,
initEvent,
} from "@spacebar/util";
import { closeDatabase, Config, initDatabase, initEvent } from "@spacebar/util";
import ws from "ws";
import { Connection } from "./events/Connection";
import http from "http";
@@ -36,22 +31,14 @@ export class Server {
public server: http.Server;
public production: boolean;
constructor({
port,
server,
production,
}: {
port: number;
server?: http.Server;
production?: boolean;
}) {
constructor({ port, server, production }: { port: number; server?: http.Server; production?: boolean }) {
this.port = port;
this.production = production || false;
if (server) this.server = server;
else {
this.server = http.createServer(function (req, res) {
if(!req.headers.cookie?.split("; ").find(x => x.startsWith("__sb_sessid="))) {
if (!req.headers.cookie?.split("; ").find((x) => x.startsWith("__sb_sessid="))) {
res.setHeader("Set-Cookie", `__sb_sessid=${randomString(32)}; Secure; HttpOnly; SameSite=None; Path=/`);
}