summary refs log tree commit diff
path: root/src/api/routes/users/@me
diff options
context:
space:
mode:
authorRory& <root@rory.gay>2025-12-17 11:00:51 +0100
committerRory& <root@rory.gay>2025-12-17 11:01:27 +0100
commit491c0de845a886954262e3ddb24959ba37a014b6 (patch)
treec0251f3779a5cd2842320e1278232b48fbea0390 /src/api/routes/users/@me
parentPre-commit: use spaces for formatting, regenerate schemas if changed (diff)
downloadserver-ts-491c0de845a886954262e3ddb24959ba37a014b6.tar.xz
lintstagedrc: regenerate schemas/openapi if schemas changed
Diffstat (limited to 'src/api/routes/users/@me')
-rw-r--r--src/api/routes/users/@me/activities/statistics/applications.ts4
-rw-r--r--src/api/routes/users/@me/affinities/guilds.ts4
-rw-r--r--src/api/routes/users/@me/affinities/users.ts4
-rw-r--r--src/api/routes/users/@me/applications/#application_id/entitlements.ts4
-rw-r--r--src/api/routes/users/@me/billing/country-code.ts4
-rw-r--r--src/api/routes/users/@me/billing/location-info.ts6
-rw-r--r--src/api/routes/users/@me/billing/payment-sources.ts76
-rw-r--r--src/api/routes/users/@me/billing/subscriptions.ts4
-rw-r--r--src/api/routes/users/@me/channels.ts56
-rw-r--r--src/api/routes/users/@me/collectibles-marketing.ts40
-rw-r--r--src/api/routes/users/@me/collectibles-purchases.ts36
-rw-r--r--src/api/routes/users/@me/connections/#connection_name/#connection_id/access-token.ts72
-rw-r--r--src/api/routes/users/@me/connections/#connection_name/#connection_id/index.ts96
-rw-r--r--src/api/routes/users/@me/connections/index.ts14
-rw-r--r--src/api/routes/users/@me/delete.ts68
-rw-r--r--src/api/routes/users/@me/devices.ts4
-rw-r--r--src/api/routes/users/@me/disable.ts64
-rw-r--r--src/api/routes/users/@me/email-settings.ts22
-rw-r--r--src/api/routes/users/@me/entitlements.ts4
-rw-r--r--src/api/routes/users/@me/guilds.ts92
-rw-r--r--src/api/routes/users/@me/guilds/#guild_id/settings.ts84
-rw-r--r--src/api/routes/users/@me/guilds/premium/subscription-slots.ts4
-rw-r--r--src/api/routes/users/@me/index.ts316
-rw-r--r--src/api/routes/users/@me/library.ts4
-rw-r--r--src/api/routes/users/@me/mentions.ts248
-rw-r--r--src/api/routes/users/@me/mfa/codes-verification.ts82
-rw-r--r--src/api/routes/users/@me/mfa/codes.ts100
-rw-r--r--src/api/routes/users/@me/mfa/totp/disable.ts80
-rw-r--r--src/api/routes/users/@me/mfa/totp/enable.ts82
-rw-r--r--src/api/routes/users/@me/mfa/webauthn/credentials/#key_id/index.ts38
-rw-r--r--src/api/routes/users/@me/mfa/webauthn/credentials/index.ts208
-rw-r--r--src/api/routes/users/@me/notes.ts150
-rw-r--r--src/api/routes/users/@me/relationships.ts436
-rw-r--r--src/api/routes/users/@me/settings-proto/1.ts250
-rw-r--r--src/api/routes/users/@me/settings-proto/2.ts250
-rw-r--r--src/api/routes/users/@me/settings.ts132
36 files changed, 1569 insertions, 1569 deletions
diff --git a/src/api/routes/users/@me/activities/statistics/applications.ts b/src/api/routes/users/@me/activities/statistics/applications.ts

index a9bc5961..b0119161 100644 --- a/src/api/routes/users/@me/activities/statistics/applications.ts +++ b/src/api/routes/users/@me/activities/statistics/applications.ts
@@ -22,8 +22,8 @@ import { route } from "@spacebar/api"; const router = Router({ mergeParams: true }); router.get("/", route({}), (req: Request, res: Response) => { - // TODO: - res.json([]).status(200); + // TODO: + res.json([]).status(200); }); export default router; diff --git a/src/api/routes/users/@me/affinities/guilds.ts b/src/api/routes/users/@me/affinities/guilds.ts
index 30fe8879..8de9aa58 100644 --- a/src/api/routes/users/@me/affinities/guilds.ts +++ b/src/api/routes/users/@me/affinities/guilds.ts
@@ -22,8 +22,8 @@ import { route } from "@spacebar/api"; const router = Router({ mergeParams: true }); router.get("/", route({}), (req: Request, res: Response) => { - // TODO: - res.status(200).send({ guild_affinities: [] }); + // TODO: + res.status(200).send({ guild_affinities: [] }); }); export default router; diff --git a/src/api/routes/users/@me/affinities/users.ts b/src/api/routes/users/@me/affinities/users.ts
index 038f4108..2f666252 100644 --- a/src/api/routes/users/@me/affinities/users.ts +++ b/src/api/routes/users/@me/affinities/users.ts
@@ -22,8 +22,8 @@ import { route } from "@spacebar/api"; const router = Router({ mergeParams: true }); router.get("/", route({}), (req: Request, res: Response) => { - // TODO: - res.status(200).send({ user_affinities: [], inverse_user_affinities: [] }); + // TODO: + res.status(200).send({ user_affinities: [], inverse_user_affinities: [] }); }); export default router; diff --git a/src/api/routes/users/@me/applications/#application_id/entitlements.ts b/src/api/routes/users/@me/applications/#application_id/entitlements.ts
index f1f9a292..e8e6f657 100644 --- a/src/api/routes/users/@me/applications/#application_id/entitlements.ts +++ b/src/api/routes/users/@me/applications/#application_id/entitlements.ts
@@ -22,8 +22,8 @@ import { route } from "@spacebar/api"; const router: Router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - //TODO - res.json([]).status(200); + //TODO + res.json([]).status(200); }); export default router; diff --git a/src/api/routes/users/@me/billing/country-code.ts b/src/api/routes/users/@me/billing/country-code.ts
index 1907536a..8bddafc3 100644 --- a/src/api/routes/users/@me/billing/country-code.ts +++ b/src/api/routes/users/@me/billing/country-code.ts
@@ -22,8 +22,8 @@ import { route } from "@spacebar/api"; const router: Router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - //TODO - res.json({ country_code: "US" }).status(200); + //TODO + res.json({ country_code: "US" }).status(200); }); export default router; diff --git a/src/api/routes/users/@me/billing/location-info.ts b/src/api/routes/users/@me/billing/location-info.ts
index 3fe7f679..918ea913 100644 --- a/src/api/routes/users/@me/billing/location-info.ts +++ b/src/api/routes/users/@me/billing/location-info.ts
@@ -22,9 +22,9 @@ import { route } from "@spacebar/api"; const router: Router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - //TODO - // TODO: subdivision_code (optional) - res.json({ country_code: "US" }).status(200); + //TODO + // TODO: subdivision_code (optional) + res.json({ country_code: "US" }).status(200); }); export default router; diff --git a/src/api/routes/users/@me/billing/payment-sources.ts b/src/api/routes/users/@me/billing/payment-sources.ts
index c2ebd3fe..1380ed52 100644 --- a/src/api/routes/users/@me/billing/payment-sources.ts +++ b/src/api/routes/users/@me/billing/payment-sources.ts
@@ -23,59 +23,59 @@ const router = Router({ mergeParams: true }); // https://docs.discord.food/resources/billing#example-payment-source const example = { - id: "1422548914485198869", - type: 1, - invalid: false, - flags: 2, - deleted_at: null, - brand: "visa", - last_4: "4242", - expires_month: 9, - expires_year: 2077, - billing_address: { - name: "John Doe", - line_1: "123 Main Street", - line_2: "Apt 4B", - city: "San Francisco", - state: "CA", - country: "US", - postal_code: "94105", - }, - country: "US", - payment_gateway: 1, - payment_gateway_source_id: "pm_DwiVlGlYwe1qxLzy4QWChQeo", - default: false, + id: "1422548914485198869", + type: 1, + invalid: false, + flags: 2, + deleted_at: null, + brand: "visa", + last_4: "4242", + expires_month: 9, + expires_year: 2077, + billing_address: { + name: "John Doe", + line_1: "123 Main Street", + line_2: "Apt 4B", + city: "San Francisco", + state: "CA", + country: "US", + postal_code: "94105", + }, + country: "US", + payment_gateway: 1, + payment_gateway_source_id: "pm_DwiVlGlYwe1qxLzy4QWChQeo", + default: false, }; router.get("/", route({}), (req: Request, res: Response) => { - // TODO: schema - res.json([example]).status(200); + // TODO: schema + res.json([example]).status(200); }); router.post("/", route({}), (req: Request, res: Response) => { - // TODO: schema - res.json([example]).status(200); + // TODO: schema + res.json([example]).status(200); }); router.get("/:payment_source_id", route({}), (req: Request, res: Response) => { - // TODO: schema - res.json({ - ...example, - id: req.route.payment_source_id, - }).status(200); + // TODO: schema + res.json({ + ...example, + id: req.route.payment_source_id, + }).status(200); }); router.patch("/:payment_source_id", route({}), (req: Request, res: Response) => { - // TODO: schema - res.json({ - ...example, - id: req.route.payment_source_id, - }).status(200); + // TODO: schema + res.json({ + ...example, + id: req.route.payment_source_id, + }).status(200); }); router.delete("/:payment_source_id", route({}), (req: Request, res: Response) => { - // TODO: schema - res.status(204); + // TODO: schema + res.status(204); }); export default router; diff --git a/src/api/routes/users/@me/billing/subscriptions.ts b/src/api/routes/users/@me/billing/subscriptions.ts
index f1f9a292..e8e6f657 100644 --- a/src/api/routes/users/@me/billing/subscriptions.ts +++ b/src/api/routes/users/@me/billing/subscriptions.ts
@@ -22,8 +22,8 @@ import { route } from "@spacebar/api"; const router: Router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - //TODO - res.json([]).status(200); + //TODO + res.json([]).status(200); }); export default router; diff --git a/src/api/routes/users/@me/channels.ts b/src/api/routes/users/@me/channels.ts
index 9411e72e..e13b874a 100644 --- a/src/api/routes/users/@me/channels.ts +++ b/src/api/routes/users/@me/channels.ts
@@ -24,37 +24,37 @@ import { DmChannelCreateSchema } from "@spacebar/schemas"; const router: Router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "APIDMChannelArray", - }, - }, - }), - async (req: Request, res: Response) => { - const recipients = await Recipient.find({ - 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])))); - }, + "/", + route({ + responses: { + 200: { + body: "APIDMChannelArray", + }, + }, + }), + async (req: Request, res: Response) => { + const recipients = await Recipient.find({ + 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])))); + }, ); router.post( - "/", - route({ - requestBody: "DmChannelCreateSchema", - responses: { - 200: { - body: "DmChannelDTO", - }, - }, - }), - async (req: Request, res: Response) => { - const body = req.body as DmChannelCreateSchema; - res.json(await Channel.createDMChannel(body.recipients, req.user_id, body.name)); - }, + "/", + route({ + requestBody: "DmChannelCreateSchema", + responses: { + 200: { + body: "DmChannelDTO", + }, + }, + }), + async (req: Request, res: Response) => { + const body = req.body as DmChannelCreateSchema; + res.json(await Channel.createDMChannel(body.recipients, req.user_id, body.name)); + }, ); export default router; diff --git a/src/api/routes/users/@me/collectibles-marketing.ts b/src/api/routes/users/@me/collectibles-marketing.ts
index c9f1a1d1..a043a146 100644 --- a/src/api/routes/users/@me/collectibles-marketing.ts +++ b/src/api/routes/users/@me/collectibles-marketing.ts
@@ -24,26 +24,26 @@ const router = Router({ mergeParams: true }); // Unsure what this endpoint does, it seems to only affect the visual style of the shop tab in home router.get( - "/", - route({ - responses: { - 200: { - body: "CollectiblesMarketingResponse", - }, - 204: {}, - 401: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - res.send({ - marketings: {}, - } as CollectiblesMarketingResponse); - }, + "/", + route({ + responses: { + 200: { + body: "CollectiblesMarketingResponse", + }, + 204: {}, + 401: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + res.send({ + marketings: {}, + } as CollectiblesMarketingResponse); + }, ); export default router; diff --git a/src/api/routes/users/@me/collectibles-purchases.ts b/src/api/routes/users/@me/collectibles-purchases.ts
index 9491e30a..1e6a08d0 100644 --- a/src/api/routes/users/@me/collectibles-purchases.ts +++ b/src/api/routes/users/@me/collectibles-purchases.ts
@@ -23,24 +23,24 @@ const router = Router({ mergeParams: true }); // Unsure what this endpoint does, it seems to only affect the visual style of the shop tab in home router.get( - "/", - route({ - responses: { - 200: { - // body: "CollectiblesPurchasesResponse", - }, - 204: {}, - 401: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - res.send([]); - }, + "/", + route({ + responses: { + 200: { + // body: "CollectiblesPurchasesResponse", + }, + 204: {}, + 401: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + res.send([]); + }, ); export default router; diff --git a/src/api/routes/users/@me/connections/#connection_name/#connection_id/access-token.ts b/src/api/routes/users/@me/connections/#connection_name/#connection_id/access-token.ts
index fcab4321..8846efe4 100644 --- a/src/api/routes/users/@me/connections/#connection_name/#connection_id/access-token.ts +++ b/src/api/routes/users/@me/connections/#connection_name/#connection_id/access-token.ts
@@ -28,49 +28,49 @@ const ALLOWED_CONNECTIONS = ["twitch", "youtube"]; // NOTE: this route has not been extensively tested, as the required connections are not implemented as of writing router.get("/", route({}), async (req: Request, res: Response) => { - const { connection_name, connection_id } = req.params; + const { connection_name, connection_id } = req.params; - const connection = ConnectionStore.connections.get(connection_name); + const connection = ConnectionStore.connections.get(connection_name); - if (!ALLOWED_CONNECTIONS.includes(connection_name) || !connection) - throw FieldErrors({ - provider_id: { - code: "BASE_TYPE_CHOICES", - message: req.t("common:field.BASE_TYPE_CHOICES", { - types: ALLOWED_CONNECTIONS.join(", "), - }), - }, - }); + if (!ALLOWED_CONNECTIONS.includes(connection_name) || !connection) + throw FieldErrors({ + provider_id: { + code: "BASE_TYPE_CHOICES", + message: req.t("common:field.BASE_TYPE_CHOICES", { + types: ALLOWED_CONNECTIONS.join(", "), + }), + }, + }); - if (!connection.settings.enabled) - throw FieldErrors({ - provider_id: { - message: "This connection has been disabled server-side.", - }, - }); + if (!connection.settings.enabled) + throw FieldErrors({ + provider_id: { + message: "This connection has been disabled server-side.", + }, + }); - const connectedAccount = await ConnectedAccount.findOne({ - where: { - type: connection_name, - external_id: connection_id, - user_id: req.user_id, - }, - select: ["external_id", "type", "name", "verified", "visibility", "show_activity", "revoked", "token_data", "friend_sync", "integrations"], - }); - if (!connectedAccount) throw DiscordApiErrors.UNKNOWN_CONNECTION; - if (connectedAccount.revoked) throw DiscordApiErrors.CONNECTION_REVOKED; - if (!connectedAccount.token_data) throw new ApiError("No token data", 0, 400); + const connectedAccount = await ConnectedAccount.findOne({ + where: { + type: connection_name, + external_id: connection_id, + user_id: req.user_id, + }, + select: ["external_id", "type", "name", "verified", "visibility", "show_activity", "revoked", "token_data", "friend_sync", "integrations"], + }); + if (!connectedAccount) throw DiscordApiErrors.UNKNOWN_CONNECTION; + if (connectedAccount.revoked) throw DiscordApiErrors.CONNECTION_REVOKED; + if (!connectedAccount.token_data) throw new ApiError("No token data", 0, 400); - let access_token = connectedAccount.token_data.access_token; - const { expires_at, expires_in, fetched_at } = connectedAccount.token_data; + let access_token = connectedAccount.token_data.access_token; + const { expires_at, expires_in, fetched_at } = connectedAccount.token_data; - if ((expires_at && expires_at < Date.now()) || (expires_in && fetched_at + expires_in * 1000 < Date.now())) { - if (!(connection instanceof RefreshableConnection)) throw new ApiError("Access token expired", 0, 400); - const tokenData = await connection.refresh(connectedAccount); - access_token = tokenData.access_token; - } + if ((expires_at && expires_at < Date.now()) || (expires_in && fetched_at + expires_in * 1000 < Date.now())) { + if (!(connection instanceof RefreshableConnection)) throw new ApiError("Access token expired", 0, 400); + const tokenData = await connection.refresh(connectedAccount); + access_token = tokenData.access_token; + } - res.json({ access_token }); + res.json({ access_token }); }); export default router; diff --git a/src/api/routes/users/@me/connections/#connection_name/#connection_id/index.ts b/src/api/routes/users/@me/connections/#connection_name/#connection_id/index.ts
index 37deee38..111312ae 100644 --- a/src/api/routes/users/@me/connections/#connection_name/#connection_id/index.ts +++ b/src/api/routes/users/@me/connections/#connection_name/#connection_id/index.ts
@@ -24,65 +24,65 @@ const router = Router({ mergeParams: true }); // TODO: connection update schema router.patch("/", route({ requestBody: "ConnectionUpdateSchema" }), async (req: Request, res: Response) => { - const { connection_name, connection_id } = req.params; - const body = req.body as ConnectionUpdateSchema; + const { connection_name, connection_id } = req.params; + const body = req.body as ConnectionUpdateSchema; - const connection = await ConnectedAccount.findOne({ - where: { - user_id: req.user_id, - external_id: connection_id, - type: connection_name, - }, - select: ["external_id", "type", "name", "verified", "visibility", "show_activity", "revoked", "friend_sync", "integrations"], - }); + const connection = await ConnectedAccount.findOne({ + where: { + user_id: req.user_id, + external_id: connection_id, + type: connection_name, + }, + select: ["external_id", "type", "name", "verified", "visibility", "show_activity", "revoked", "friend_sync", "integrations"], + }); - if (!connection) return DiscordApiErrors.UNKNOWN_CONNECTION; - // TODO: do we need to do anything if the connection is revoked? + if (!connection) return DiscordApiErrors.UNKNOWN_CONNECTION; + // TODO: do we need to do anything if the connection is revoked? - if (typeof body.visibility === "boolean") - //@ts-expect-error For some reason the client sends this as a boolean, even tho docs say its a number? - body.visibility = body.visibility ? 1 : 0; - if (typeof body.show_activity === "boolean") - //@ts-expect-error For some reason the client sends this as a boolean, even tho docs say its a number? - body.show_activity = body.show_activity ? 1 : 0; - if (typeof body.metadata_visibility === "boolean") - //@ts-expect-error For some reason the client sends this as a boolean, even tho docs say its a number? - body.metadata_visibility = body.metadata_visibility ? 1 : 0; + if (typeof body.visibility === "boolean") + //@ts-expect-error For some reason the client sends this as a boolean, even tho docs say its a number? + body.visibility = body.visibility ? 1 : 0; + if (typeof body.show_activity === "boolean") + //@ts-expect-error For some reason the client sends this as a boolean, even tho docs say its a number? + body.show_activity = body.show_activity ? 1 : 0; + if (typeof body.metadata_visibility === "boolean") + //@ts-expect-error For some reason the client sends this as a boolean, even tho docs say its a number? + body.metadata_visibility = body.metadata_visibility ? 1 : 0; - connection.assign(req.body); + connection.assign(req.body); - await ConnectedAccount.update( - { - user_id: req.user_id, - external_id: connection_id, - type: connection_name, - }, - connection, - ); - res.json(connection.toJSON()); + await ConnectedAccount.update( + { + user_id: req.user_id, + external_id: connection_id, + type: connection_name, + }, + connection, + ); + res.json(connection.toJSON()); }); router.delete("/", route({}), async (req: Request, res: Response) => { - const { connection_name, connection_id } = req.params; + const { connection_name, connection_id } = req.params; - const account = await ConnectedAccount.findOneOrFail({ - where: { - user_id: req.user_id, - external_id: connection_id, - type: connection_name, - }, - }); + const account = await ConnectedAccount.findOneOrFail({ + where: { + user_id: req.user_id, + external_id: connection_id, + type: connection_name, + }, + }); - await Promise.all([ - ConnectedAccount.remove(account), - emitEvent({ - event: "USER_CONNECTIONS_UPDATE", - data: account, - user_id: req.user_id, - }), - ]); + await Promise.all([ + ConnectedAccount.remove(account), + emitEvent({ + event: "USER_CONNECTIONS_UPDATE", + data: account, + user_id: req.user_id, + }), + ]); - return res.sendStatus(200); + return res.sendStatus(200); }); export default router; diff --git a/src/api/routes/users/@me/connections/index.ts b/src/api/routes/users/@me/connections/index.ts
index 00625eea..a2d8b82a 100644 --- a/src/api/routes/users/@me/connections/index.ts +++ b/src/api/routes/users/@me/connections/index.ts
@@ -23,14 +23,14 @@ import { ConnectedAccount, ConnectedAccountDTO } from "@spacebar/util"; const router: Router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - const connections = await ConnectedAccount.find({ - where: { - user_id: req.user_id, - }, - select: ["external_id", "type", "name", "verified", "visibility", "show_activity", "revoked", "token_data", "friend_sync", "integrations"], - }); + const connections = await ConnectedAccount.find({ + where: { + user_id: req.user_id, + }, + select: ["external_id", "type", "name", "verified", "visibility", "show_activity", "revoked", "token_data", "friend_sync", "integrations"], + }); - res.json(connections.map((x) => new ConnectedAccountDTO(x, true))); + res.json(connections.map((x) => new ConnectedAccountDTO(x, true))); }); export default router; diff --git a/src/api/routes/users/@me/delete.ts b/src/api/routes/users/@me/delete.ts
index 43861181..668f7c19 100644 --- a/src/api/routes/users/@me/delete.ts +++ b/src/api/routes/users/@me/delete.ts
@@ -25,44 +25,44 @@ import { HTTPError } from "lambert-server"; const router = Router({ mergeParams: true }); router.post( - "/", - route({ - responses: { - 204: {}, - 401: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const user = await User.findOneOrFail({ - where: { id: req.user_id }, - select: ["data"], - }); //User object - let correctpass = true; + "/", + route({ + responses: { + 204: {}, + 401: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const user = await User.findOneOrFail({ + where: { id: req.user_id }, + select: ["data"], + }); //User object + let correctpass = true; - if (user.data.hash) { - // guest accounts can delete accounts without password - correctpass = await bcrypt.compare(req.body.password, user.data.hash); - if (!correctpass) { - throw new HTTPError(req.t("auth:login.INVALID_PASSWORD")); - } - } + if (user.data.hash) { + // guest accounts can delete accounts without password + correctpass = await bcrypt.compare(req.body.password, user.data.hash); + if (!correctpass) { + throw new HTTPError(req.t("auth:login.INVALID_PASSWORD")); + } + } - // TODO: decrement guild member count + // TODO: decrement guild member count - if (correctpass) { - const members = await Member.find({ where: { id: req.user_id } }); - await Promise.all([User.delete({ id: req.user_id }), ...members.map((member) => Member.removeFromGuild(member.id, member.guild_id))]); + if (correctpass) { + const members = await Member.find({ where: { id: req.user_id } }); + await Promise.all([User.delete({ id: req.user_id }), ...members.map((member) => Member.removeFromGuild(member.id, member.guild_id))]); - res.sendStatus(204); - } else { - res.sendStatus(401); - } - }, + res.sendStatus(204); + } else { + res.sendStatus(401); + } + }, ); export default router; diff --git a/src/api/routes/users/@me/devices.ts b/src/api/routes/users/@me/devices.ts
index 644302cd..d3206138 100644 --- a/src/api/routes/users/@me/devices.ts +++ b/src/api/routes/users/@me/devices.ts
@@ -22,8 +22,8 @@ import { route } from "@spacebar/api"; const router = Router({ mergeParams: true }); router.post("/", route({}), (req: Request, res: Response) => { - // TODO: - res.sendStatus(204); + // TODO: + res.sendStatus(204); }); export default router; diff --git a/src/api/routes/users/@me/disable.ts b/src/api/routes/users/@me/disable.ts
index 73de8395..95995e4b 100644 --- a/src/api/routes/users/@me/disable.ts +++ b/src/api/routes/users/@me/disable.ts
@@ -24,41 +24,41 @@ import { Request, Response, Router } from "express"; const router = Router({ mergeParams: true }); router.post( - "/", - route({ - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const user = await User.findOneOrFail({ - where: { id: req.user_id }, - select: ["data"], - }); //User object - let correctpass = true; + "/", + route({ + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const user = await User.findOneOrFail({ + where: { id: req.user_id }, + select: ["data"], + }); //User object + let correctpass = true; - if (user.data.hash) { - // guest accounts can delete accounts without password - correctpass = await bcrypt.compare(req.body.password, user.data.hash); //Not sure if user typed right password :/ - } + if (user.data.hash) { + // guest accounts can delete accounts without password + correctpass = await bcrypt.compare(req.body.password, user.data.hash); //Not sure if user typed right password :/ + } - if (correctpass) { - await User.update({ id: req.user_id }, { disabled: true }); + if (correctpass) { + await User.update({ id: req.user_id }, { disabled: true }); - res.sendStatus(204); - } else { - res.status(400).json({ - message: "Password does not match", - code: 50018, - }); - } - }, + res.sendStatus(204); + } else { + res.status(400).json({ + message: "Password does not match", + code: 50018, + }); + } + }, ); export default router; diff --git a/src/api/routes/users/@me/email-settings.ts b/src/api/routes/users/@me/email-settings.ts
index c5575009..ede73532 100644 --- a/src/api/routes/users/@me/email-settings.ts +++ b/src/api/routes/users/@me/email-settings.ts
@@ -22,17 +22,17 @@ import { route } from "@spacebar/api"; const router = Router({ mergeParams: true }); router.get("/", route({}), (req: Request, res: Response) => { - // TODO: - res.json({ - categories: { - social: true, - communication: true, - tips: false, - updates_and_announcements: false, - recommendations_and_events: false, - }, - initialized: false, - }).status(200); + // TODO: + res.json({ + categories: { + social: true, + communication: true, + tips: false, + updates_and_announcements: false, + recommendations_and_events: false, + }, + initialized: false, + }).status(200); }); export default router; diff --git a/src/api/routes/users/@me/entitlements.ts b/src/api/routes/users/@me/entitlements.ts
index fd953eee..ac73a12c 100644 --- a/src/api/routes/users/@me/entitlements.ts +++ b/src/api/routes/users/@me/entitlements.ts
@@ -22,8 +22,8 @@ import { route } from "@spacebar/api"; const router = Router({ mergeParams: true }); router.get("/gifts", route({}), (req: Request, res: Response) => { - // TODO: - res.json([]).status(200); + // TODO: + res.json([]).status(200); }); export default router; diff --git a/src/api/routes/users/@me/guilds.ts b/src/api/routes/users/@me/guilds.ts
index df71f46e..c06c39c4 100644 --- a/src/api/routes/users/@me/guilds.ts +++ b/src/api/routes/users/@me/guilds.ts
@@ -24,62 +24,62 @@ import { HTTPError } from "lambert-server"; const router: Router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "APIGuildArray", - }, - }, - }), - async (req: Request, res: Response) => { - const members = await Member.find({ - relations: ["guild"], - where: { id: req.user_id }, - }); + "/", + route({ + responses: { + 200: { + body: "APIGuildArray", + }, + }, + }), + async (req: Request, res: Response) => { + const members = await Member.find({ + relations: ["guild"], + where: { id: req.user_id }, + }); - let guild = members.map((x) => x.guild); + let guild = members.map((x) => x.guild); - if ("with_counts" in req.query && req.query.with_counts == "true") { - guild = []; // TODO: Load guilds with user role permissions number - } + if ("with_counts" in req.query && req.query.with_counts == "true") { + guild = []; // TODO: Load guilds with user role permissions number + } - res.json(guild); - }, + res.json(guild); + }, ); // user send to leave a certain guild router.delete( - "/:guild_id", - route({ - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { autoJoin } = Config.get().guild; - const { guild_id } = req.params; - const guild = await Guild.findOneOrFail({ - where: { id: guild_id }, - select: ["owner_id"], - }); + "/:guild_id", + route({ + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { autoJoin } = Config.get().guild; + const { guild_id } = req.params; + const guild = await Guild.findOneOrFail({ + where: { id: guild_id }, + select: ["owner_id"], + }); - if (!guild) throw new HTTPError("Guild doesn't exist", 404); - if (guild.owner_id === req.user_id) throw new HTTPError("You can't leave your own guild", 400); - if (autoJoin.enabled && autoJoin.guilds.includes(guild_id) && !autoJoin.canLeave) { - throw new HTTPError("You can't leave instance auto join guilds", 400); - } + if (!guild) throw new HTTPError("Guild doesn't exist", 404); + if (guild.owner_id === req.user_id) throw new HTTPError("You can't leave your own guild", 400); + if (autoJoin.enabled && autoJoin.guilds.includes(guild_id) && !autoJoin.canLeave) { + throw new HTTPError("You can't leave instance auto join guilds", 400); + } - await Member.removeFromGuild(req.user_id, guild_id); + await Member.removeFromGuild(req.user_id, guild_id); - return res.sendStatus(204); - }, + return res.sendStatus(204); + }, ); export default router; diff --git a/src/api/routes/users/@me/guilds/#guild_id/settings.ts b/src/api/routes/users/@me/guilds/#guild_id/settings.ts
index 7fda9a90..16b64ec5 100644 --- a/src/api/routes/users/@me/guilds/#guild_id/settings.ts +++ b/src/api/routes/users/@me/guilds/#guild_id/settings.ts
@@ -25,54 +25,54 @@ const router = Router({ mergeParams: true }); // GET doesn't exist on discord.com router.get( - "/", - route({ - responses: { - 200: {}, - 404: {}, - }, - }), - async (req: Request, res: Response) => { - const user = await Member.findOneOrFail({ - where: { id: req.user_id, guild_id: req.params.guild_id }, - select: ["settings"], - }); - return res.json(user.settings); - }, + "/", + route({ + responses: { + 200: {}, + 404: {}, + }, + }), + async (req: Request, res: Response) => { + const user = await Member.findOneOrFail({ + where: { id: req.user_id, guild_id: req.params.guild_id }, + select: ["settings"], + }); + return res.json(user.settings); + }, ); router.patch( - "/", - route({ - requestBody: "UserGuildSettingsSchema", - responses: { - 200: {}, - 400: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const body = req.body as UserGuildSettingsSchema; + "/", + route({ + requestBody: "UserGuildSettingsSchema", + responses: { + 200: {}, + 400: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const body = req.body as UserGuildSettingsSchema; - if (body.channel_overrides) { - for (const channel in body.channel_overrides) { - Channel.findOneOrFail({ where: { id: channel } }); - } - } + if (body.channel_overrides) { + for (const channel in body.channel_overrides) { + Channel.findOneOrFail({ where: { id: channel } }); + } + } - const user = await Member.findOneOrFail({ - where: { id: req.user_id, guild_id: req.params.guild_id }, - select: ["settings"], - }); - OrmUtils.mergeDeep(user.settings || {}, body); - Member.update({ id: req.user_id, guild_id: req.params.guild_id }, user); + const user = await Member.findOneOrFail({ + where: { id: req.user_id, guild_id: req.params.guild_id }, + select: ["settings"], + }); + OrmUtils.mergeDeep(user.settings || {}, body); + Member.update({ id: req.user_id, guild_id: req.params.guild_id }, user); - res.json(user.settings); - }, + res.json(user.settings); + }, ); export default router; diff --git a/src/api/routes/users/@me/guilds/premium/subscription-slots.ts b/src/api/routes/users/@me/guilds/premium/subscription-slots.ts
index a9bc5961..b0119161 100644 --- a/src/api/routes/users/@me/guilds/premium/subscription-slots.ts +++ b/src/api/routes/users/@me/guilds/premium/subscription-slots.ts
@@ -22,8 +22,8 @@ import { route } from "@spacebar/api"; const router = Router({ mergeParams: true }); router.get("/", route({}), (req: Request, res: Response) => { - // TODO: - res.json([]).status(200); + // TODO: + res.json([]).status(200); }); export default router; diff --git a/src/api/routes/users/@me/index.ts b/src/api/routes/users/@me/index.ts
index aba9f928..bd1fa077 100644 --- a/src/api/routes/users/@me/index.ts +++ b/src/api/routes/users/@me/index.ts
@@ -25,182 +25,182 @@ import { PrivateUserProjection, UserModifySchema } from "@spacebar/schemas"; const router: Router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "APIPrivateUser", - }, - }, - }), - async (req: Request, res: Response) => { - res.json( - await User.findOne({ - select: PrivateUserProjection, - where: { id: req.user_id }, - }), - ); - }, + "/", + route({ + responses: { + 200: { + body: "APIPrivateUser", + }, + }, + }), + async (req: Request, res: Response) => { + res.json( + await User.findOne({ + select: PrivateUserProjection, + where: { id: req.user_id }, + }), + ); + }, ); router.patch( - "/", - route({ - requestBody: "UserModifySchema", - responses: { - 200: { - body: "UserUpdateResponse", - }, - 400: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const body = req.body as UserModifySchema; + "/", + route({ + requestBody: "UserModifySchema", + responses: { + 200: { + body: "UserUpdateResponse", + }, + 400: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const body = req.body as UserModifySchema; - const user = await User.findOneOrFail({ - where: { id: req.user_id }, - select: [...PrivateUserProjection, "data"], - }); + const user = await User.findOneOrFail({ + where: { id: req.user_id }, + select: [...PrivateUserProjection, "data"], + }); - // Populated on password change - let newToken: string | undefined; + // 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 || ""); - if (!same_password) { - throw FieldErrors({ - password: { - message: req.t("auth:login.INVALID_PASSWORD"), - code: "INVALID_PASSWORD", - }, - }); - } - } else { - user.data.hash = await bcrypt.hash(body.password, 12); - } - } + if (body.password) { + if (user.data?.hash) { + const same_password = await bcrypt.compare(body.password, user.data.hash || ""); + if (!same_password) { + throw FieldErrors({ + password: { + message: req.t("auth:login.INVALID_PASSWORD"), + code: "INVALID_PASSWORD", + }, + }); + } + } else { + user.data.hash = await bcrypt.hash(body.password, 12); + } + } - if (body.email) { - if (!body.email && Config.get().register.email.required) - throw FieldErrors({ - email: { - message: req.t("auth:register.EMAIL_INVALID"), - code: "EMAIL_INVALID", - }, - }); - if (!body.password) - throw FieldErrors({ - password: { - message: req.t("auth:login.INVALID_PASSWORD"), - code: "INVALID_PASSWORD", - }, - }); - } + if (body.email) { + if (!body.email && Config.get().register.email.required) + throw FieldErrors({ + email: { + message: req.t("auth:register.EMAIL_INVALID"), + code: "EMAIL_INVALID", + }, + }); + if (!body.password) + throw FieldErrors({ + password: { + message: req.t("auth:login.INVALID_PASSWORD"), + code: "INVALID_PASSWORD", + }, + }); + } - if (body.new_password) { - if (!body.password && user.email) { - throw FieldErrors({ - password: { - code: "BASE_TYPE_REQUIRED", - message: req.t("common:field.BASE_TYPE_REQUIRED"), - }, - }); - } - user.data.hash = await bcrypt.hash(body.new_password, 12); - user.data.valid_tokens_since = new Date(); - newToken = (await generateToken(user.id)) as string; - } + if (body.new_password) { + if (!body.password && user.email) { + throw FieldErrors({ + password: { + code: "BASE_TYPE_REQUIRED", + message: req.t("common:field.BASE_TYPE_REQUIRED"), + }, + }); + } + user.data.hash = await bcrypt.hash(body.new_password, 12); + user.data.valid_tokens_since = new Date(); + newToken = (await generateToken(user.id)) as string; + } - if (body.username) { - const check_username = body?.username?.replace(/\s/g, "").trim(); - if (!check_username) { - throw FieldErrors({ - username: { - code: "BASE_TYPE_REQUIRED", - message: req.t("common:field.BASE_TYPE_REQUIRED"), - }, - }); - } + if (body.username) { + const check_username = body?.username?.replace(/\s/g, "").trim(); + if (!check_username) { + throw FieldErrors({ + username: { + code: "BASE_TYPE_REQUIRED", + message: req.t("common:field.BASE_TYPE_REQUIRED"), + }, + }); + } - const { maxUsername } = Config.get().limits.user; - if (check_username.length > maxUsername || check_username.length < 2) { - throw FieldErrors({ - username: { - code: "BASE_TYPE_BAD_LENGTH", - message: `Must be between 2 and ${maxUsername} in length.`, - }, - }); - } + const { maxUsername } = Config.get().limits.user; + if (check_username.length > maxUsername || check_username.length < 2) { + throw FieldErrors({ + username: { + code: "BASE_TYPE_BAD_LENGTH", + message: `Must be between 2 and ${maxUsername} in length.`, + }, + }); + } - if (!body.password) { - throw FieldErrors({ - password: { - message: req.t("auth:login.INVALID_PASSWORD"), - code: "INVALID_PASSWORD", - }, - }); - } - } + if (!body.password) { + throw FieldErrors({ + password: { + message: req.t("auth:login.INVALID_PASSWORD"), + code: "INVALID_PASSWORD", + }, + }); + } + } - if (body.discriminator) { - if ( - await User.findOne({ - where: { - discriminator: body.discriminator, - username: body.username || user.username, - }, - }) - ) { - throw FieldErrors({ - discriminator: { - code: "INVALID_DISCRIMINATOR", - message: "This discriminator is already in use.", - }, - }); - } - } + if (body.discriminator) { + if ( + await User.findOne({ + where: { + discriminator: body.discriminator, + username: body.username || user.username, + }, + }) + ) { + throw FieldErrors({ + discriminator: { + code: "INVALID_DISCRIMINATOR", + message: "This discriminator is already in use.", + }, + }); + } + } - if (body.bio) { - const { maxBio } = Config.get().limits.user; - if (body.bio.length > maxBio) { - throw FieldErrors({ - bio: { - code: "BIO_INVALID", - message: `Bio must be less than ${maxBio} in length`, - }, - }); - } - } + if (body.bio) { + const { maxBio } = Config.get().limits.user; + if (body.bio.length > maxBio) { + throw FieldErrors({ + bio: { + code: "BIO_INVALID", + message: `Bio must be less than ${maxBio} in length`, + }, + }); + } + } - user.assign(body); - user.validate(); - await user.save(); + user.assign(body); + user.validate(); + await user.save(); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - //@ts-ignore - delete user.data; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + delete user.data; - // TODO: send update member list event in gateway - await emitEvent({ - event: "USER_UPDATE", - user_id: req.user_id, - data: user, - } as UserUpdateEvent); + // TODO: send update member list event in gateway + await emitEvent({ + event: "USER_UPDATE", + user_id: req.user_id, + data: user, + } as UserUpdateEvent); - res.json({ - ...user, - newToken, - }); - }, + res.json({ + ...user, + newToken, + }); + }, ); export default router; diff --git a/src/api/routes/users/@me/library.ts b/src/api/routes/users/@me/library.ts
index 845cd171..8d09b38d 100644 --- a/src/api/routes/users/@me/library.ts +++ b/src/api/routes/users/@me/library.ts
@@ -22,8 +22,8 @@ import { route } from "@spacebar/api"; const router = Router({ mergeParams: true }); router.get("/", route({}), (req: Request, res: Response) => { - // TODO: - res.status(200).send([]); + // TODO: + res.status(200).send([]); }); export default router; diff --git a/src/api/routes/users/@me/mentions.ts b/src/api/routes/users/@me/mentions.ts
index 37a7abe1..2d631e90 100644 --- a/src/api/routes/users/@me/mentions.ts +++ b/src/api/routes/users/@me/mentions.ts
@@ -24,139 +24,139 @@ import { In, LessThan, FindOptionsWhere } from "typeorm"; const router: Router = Router({ mergeParams: true }); router.get( - "", - route({ - responses: { - 200: { - body: "MessageListResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - // AFAICT this endpoint doesn't list DMs - async (req: Request, res: Response) => { - const limit = req.query.limit && !isNaN(Number(req.query.limit)) ? Number(req.query.limit) : 25; - const everyone = req.query.everyone !== undefined ? Boolean(req.query.everyone) : true; - const roles = req.query.roles !== undefined ? Boolean(req.query.roles) : true; - const before = req.query.before !== undefined ? String(req.query.before as string) : undefined; - const guild_id = req.query.guild_id !== undefined ? req.query.guild_id : undefined; + "", + route({ + responses: { + 200: { + body: "MessageListResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + // AFAICT this endpoint doesn't list DMs + async (req: Request, res: Response) => { + const limit = req.query.limit && !isNaN(Number(req.query.limit)) ? Number(req.query.limit) : 25; + const everyone = req.query.everyone !== undefined ? Boolean(req.query.everyone) : true; + const roles = req.query.roles !== undefined ? Boolean(req.query.roles) : true; + const before = req.query.before !== undefined ? String(req.query.before as string) : undefined; + const guild_id = req.query.guild_id !== undefined ? req.query.guild_id : undefined; - const user = await User.findOneOrFail({ - where: { id: req.user_id }, - }); + const user = await User.findOneOrFail({ + where: { id: req.user_id }, + }); - const memberships = await Member.find({ - where: { id: req.user_id, ...(guild_id === undefined ? {} : { guild_id: String(guild_id) }) }, - select: { - guild_id: true, - id: true, - communication_disabled_until: true, - roles: { - // We don't want to include all guild roles, as this could cause a lot more explosive behavior - id: true, - position: true, - permissions: true, - mentionable: true, // cause we can skip querying for unmentionable roles - }, - guild: { - id: true, - owner_id: true, - }, - }, - relations: ["guild", "roles"], - }); + const memberships = await Member.find({ + where: { id: req.user_id, ...(guild_id === undefined ? {} : { guild_id: String(guild_id) }) }, + select: { + guild_id: true, + id: true, + communication_disabled_until: true, + roles: { + // We don't want to include all guild roles, as this could cause a lot more explosive behavior + id: true, + position: true, + permissions: true, + mentionable: true, // cause we can skip querying for unmentionable roles + }, + guild: { + id: true, + owner_id: true, + }, + }, + relations: ["guild", "roles"], + }); - const channels = await Channel.find({ - where: { - guild_id: In(memberships.map((m) => m.guild_id)), - }, - select: { id: true, guild_id: true, permission_overwrites: true }, - }); + const channels = await Channel.find({ + where: { + guild_id: In(memberships.map((m) => m.guild_id)), + }, + select: { id: true, guild_id: true, permission_overwrites: true }, + }); - const visibleChannels = channels.filter((c) => { - const member = memberships.find((m) => m.guild_id === c.guild_id)!; - return Permissions.finalPermission({ - user: { id: member.id, roles: member.roles.map((r) => r.id), communication_disabled_until: member.communication_disabled_until, flags: 0 }, - guild: { id: member.guild.id, owner_id: member.guild.owner_id!, roles: member.roles }, - channel: c, - }).has("VIEW_CHANNEL"); - }); + const visibleChannels = channels.filter((c) => { + const member = memberships.find((m) => m.guild_id === c.guild_id)!; + return Permissions.finalPermission({ + user: { id: member.id, roles: member.roles.map((r) => r.id), communication_disabled_until: member.communication_disabled_until, flags: 0 }, + guild: { id: member.guild.id, owner_id: member.guild.owner_id!, roles: member.roles }, + channel: c, + }).has("VIEW_CHANNEL"); + }); - const visibleChannelIds = visibleChannels.map((c) => c.id); - const ownedMentionableRoleIds = memberships.reduce((acc, m) => { - acc.push(...m.roles.filter((r) => r.mentionable).map((r) => r.id)); - return acc; - }, [] as Snowflake[]); + const visibleChannelIds = visibleChannels.map((c) => c.id); + const ownedMentionableRoleIds = memberships.reduce((acc, m) => { + acc.push(...m.roles.filter((r) => r.mentionable).map((r) => r.id)); + return acc; + }, [] as Snowflake[]); - const whereQuery: FindOptionsWhere<Message>[] = [ - { - channel_id: In(visibleChannelIds), - mentions: { id: user.id }, - id: before ? LessThan(before) : undefined, - }, - ]; - if (everyone) { - whereQuery.push({ - channel_id: In(visibleChannelIds), - mention_everyone: true, - id: before ? LessThan(before) : undefined, - }); - } - if (roles) { - whereQuery.push({ - channel_id: In(visibleChannelIds), - mention_roles: { id: In(ownedMentionableRoleIds) }, - id: before ? LessThan(before) : undefined, - }); - } + const whereQuery: FindOptionsWhere<Message>[] = [ + { + channel_id: In(visibleChannelIds), + mentions: { id: user.id }, + id: before ? LessThan(before) : undefined, + }, + ]; + if (everyone) { + whereQuery.push({ + channel_id: In(visibleChannelIds), + mention_everyone: true, + id: before ? LessThan(before) : undefined, + }); + } + if (roles) { + whereQuery.push({ + channel_id: In(visibleChannelIds), + mention_roles: { id: In(ownedMentionableRoleIds) }, + id: before ? LessThan(before) : undefined, + }); + } - const sw = Stopwatch.startNew(); - const finalMessages = ( - await Message.find({ - where: whereQuery, - order: { timestamp: "DESC" }, - relations: [ - "author", - "webhook", - "application", - "mentions", - "mention_roles", - "mention_channels", - "sticker_items", - "attachments", - "referenced_message", - "referenced_message.author", - "referenced_message.webhook", - "referenced_message.application", - "referenced_message.mentions", - "referenced_message.mention_roles", - "referenced_message.mention_channels", - "referenced_message.sticker_items", - "referenced_message.attachments", - ], - take: limit, - }) - ).map((m) => { - return { - ...m.toJSON(), - attachments: m.attachments?.map((attachment: Attachment) => - Attachment.prototype.signUrls.call( - attachment, - new NewUrlUserSignatureData({ - ip: req.ip, - userAgent: req.headers["user-agent"] as string, - }), - ), - ), - }; - }); + const sw = Stopwatch.startNew(); + const finalMessages = ( + await Message.find({ + where: whereQuery, + order: { timestamp: "DESC" }, + relations: [ + "author", + "webhook", + "application", + "mentions", + "mention_roles", + "mention_channels", + "sticker_items", + "attachments", + "referenced_message", + "referenced_message.author", + "referenced_message.webhook", + "referenced_message.application", + "referenced_message.mentions", + "referenced_message.mention_roles", + "referenced_message.mention_channels", + "referenced_message.sticker_items", + "referenced_message.attachments", + ], + take: limit, + }) + ).map((m) => { + return { + ...m.toJSON(), + attachments: m.attachments?.map((attachment: Attachment) => + Attachment.prototype.signUrls.call( + attachment, + new NewUrlUserSignatureData({ + ip: req.ip, + userAgent: req.headers["user-agent"] as string, + }), + ), + ), + }; + }); - console.log(`[Inbox/mentions] User ${user.id} fetched full message data for ${finalMessages.length} messages in ${sw.elapsed().totalMilliseconds}ms`); + console.log(`[Inbox/mentions] User ${user.id} fetched full message data for ${finalMessages.length} messages in ${sw.elapsed().totalMilliseconds}ms`); - return res.json(finalMessages); - }, + return res.json(finalMessages); + }, ); export default router; diff --git a/src/api/routes/users/@me/mfa/codes-verification.ts b/src/api/routes/users/@me/mfa/codes-verification.ts
index c8995d83..4cc4c072 100644 --- a/src/api/routes/users/@me/mfa/codes-verification.ts +++ b/src/api/routes/users/@me/mfa/codes-verification.ts
@@ -24,52 +24,52 @@ import { CodesVerificationSchema } from "@spacebar/schemas"; const router = Router({ mergeParams: true }); router.post( - "/", - route({ - requestBody: "CodesVerificationSchema", - responses: { - 200: { - body: "APIBackupCodeArray", - }, - 400: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - // const { key, nonce, regenerate } = req.body as CodesVerificationSchema; - const { regenerate } = req.body as CodesVerificationSchema; + "/", + route({ + requestBody: "CodesVerificationSchema", + responses: { + 200: { + body: "APIBackupCodeArray", + }, + 400: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + // const { key, nonce, regenerate } = req.body as CodesVerificationSchema; + const { regenerate } = req.body as CodesVerificationSchema; - // TODO: We don't have email/etc etc, so can't send a verification code. - // Once that's done, this route can verify `key` + // TODO: We don't have email/etc etc, so can't send a verification code. + // 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; + // const user = await User.findOneOrFail({ where: { id: req.user_id } }); + 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 }); + let codes: BackupCode[]; + if (regenerate) { + await BackupCode.update({ user: { id: req.user_id } }, { expired: true }); - codes = generateMfaBackupCodes(req.user_id); - await Promise.all(codes.map((x) => x.save())); - } else { - codes = await BackupCode.find({ - where: { - user: { - id: req.user_id, - }, - expired: false, - }, - }); - } + codes = generateMfaBackupCodes(req.user_id); + await Promise.all(codes.map((x) => x.save())); + } else { + codes = await BackupCode.find({ + where: { + user: { + id: req.user_id, + }, + expired: false, + }, + }); + } - return res.json({ - backup_codes: codes.map((x) => ({ ...x, expired: undefined })), - }); - }, + return res.json({ + backup_codes: codes.map((x) => ({ ...x, expired: undefined })), + }); + }, ); export default router; diff --git a/src/api/routes/users/@me/mfa/codes.ts b/src/api/routes/users/@me/mfa/codes.ts
index 579a106e..2eb1a3e3 100644 --- a/src/api/routes/users/@me/mfa/codes.ts +++ b/src/api/routes/users/@me/mfa/codes.ts
@@ -27,61 +27,61 @@ const router = Router({ mergeParams: true }); // TODO: This route is replaced with users/@me/mfa/codes-verification in newer clients router.post( - "/", - route({ - requestBody: "MfaCodesSchema", - deprecated: true, - description: "This route is replaced with users/@me/mfa/codes-verification in newer clients", - responses: { - 200: { - body: "APIBackupCodeArray", - }, - 400: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { password, regenerate } = req.body as MfaCodesSchema; + "/", + route({ + requestBody: "MfaCodesSchema", + deprecated: true, + description: "This route is replaced with users/@me/mfa/codes-verification in newer clients", + responses: { + 200: { + body: "APIBackupCodeArray", + }, + 400: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { password, regenerate } = req.body as MfaCodesSchema; - const user = await User.findOneOrFail({ - where: { id: req.user_id }, - select: ["data"], - }); + const user = await User.findOneOrFail({ + where: { id: req.user_id }, + select: ["data"], + }); - if (!(await bcrypt.compare(password, user.data.hash || ""))) { - throw FieldErrors({ - password: { - message: req.t("auth:login.INVALID_PASSWORD"), - code: "INVALID_PASSWORD", - }, - }); - } + if (!(await bcrypt.compare(password, user.data.hash || ""))) { + throw FieldErrors({ + password: { + message: req.t("auth:login.INVALID_PASSWORD"), + code: "INVALID_PASSWORD", + }, + }); + } - let codes: BackupCode[]; - if (regenerate) { - await BackupCode.update({ user: { id: req.user_id } }, { expired: true }); + let codes: BackupCode[]; + if (regenerate) { + await BackupCode.update({ user: { id: req.user_id } }, { expired: true }); - codes = generateMfaBackupCodes(req.user_id); - await Promise.all(codes.map((x) => x.save())); - } else { - codes = await BackupCode.find({ - where: { - user: { - id: req.user_id, - }, - expired: false, - }, - }); - } + codes = generateMfaBackupCodes(req.user_id); + await Promise.all(codes.map((x) => x.save())); + } else { + codes = await BackupCode.find({ + where: { + user: { + id: req.user_id, + }, + expired: false, + }, + }); + } - return res.json({ - backup_codes: codes.map((x) => ({ ...x, expired: undefined })), - }); - }, + return res.json({ + backup_codes: codes.map((x) => ({ ...x, expired: undefined })), + }); + }, ); export default router; diff --git a/src/api/routes/users/@me/mfa/totp/disable.ts b/src/api/routes/users/@me/mfa/totp/disable.ts
index 003dcb74..11c927e4 100644 --- a/src/api/routes/users/@me/mfa/totp/disable.ts +++ b/src/api/routes/users/@me/mfa/totp/disable.ts
@@ -26,51 +26,51 @@ import { TotpDisableSchema } from "@spacebar/schemas"; const router = Router({ mergeParams: true }); router.post( - "/", - route({ - requestBody: "TotpDisableSchema", - responses: { - 200: { - body: "TokenOnlyResponse", - }, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const body = req.body as TotpDisableSchema; + "/", + route({ + requestBody: "TotpDisableSchema", + responses: { + 200: { + body: "TokenOnlyResponse", + }, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const body = req.body as TotpDisableSchema; - const user = await User.findOneOrFail({ - where: { id: req.user_id }, - select: ["totp_secret"], - }); + const user = await User.findOneOrFail({ + where: { id: req.user_id }, + select: ["totp_secret"], + }); - 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); - } + 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); + } - await User.update( - { id: req.user_id }, - { - mfa_enabled: false, - totp_secret: "", - }, - ); + await User.update( + { id: req.user_id }, + { + mfa_enabled: false, + totp_secret: "", + }, + ); - await BackupCode.update( - { user: { id: req.user_id } }, - { - expired: true, - }, - ); + await BackupCode.update( + { user: { id: req.user_id } }, + { + expired: true, + }, + ); - return res.json({ - token: await generateToken(user.id), - }); - }, + return res.json({ + token: await generateToken(user.id), + }); + }, ); export default router; diff --git a/src/api/routes/users/@me/mfa/totp/enable.ts b/src/api/routes/users/@me/mfa/totp/enable.ts
index f0563e30..a400c628 100644 --- a/src/api/routes/users/@me/mfa/totp/enable.ts +++ b/src/api/routes/users/@me/mfa/totp/enable.ts
@@ -27,54 +27,54 @@ import { TotpEnableSchema } from "@spacebar/schemas"; const router = Router({ mergeParams: true }); router.post( - "/", - route({ - requestBody: "TotpEnableSchema", - responses: { - 200: { - body: "TokenWithBackupCodesResponse", - }, - 400: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const body = req.body as TotpEnableSchema; + "/", + route({ + requestBody: "TotpEnableSchema", + responses: { + 200: { + body: "TokenWithBackupCodesResponse", + }, + 400: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const body = req.body as TotpEnableSchema; - const user = await User.findOneOrFail({ - where: { id: req.user_id }, - select: ["data", "email"], - }); + const user = await User.findOneOrFail({ + where: { id: req.user_id }, + select: ["data", "email"], + }); - // TODO: Are guests allowed to enable 2fa? - if (user.data.hash) { - if (!(await bcrypt.compare(body.password, user.data.hash))) { - throw new HTTPError(req.t("auth:login.INVALID_PASSWORD")); - } - } + // TODO: Are guests allowed to enable 2fa? + if (user.data.hash) { + if (!(await bcrypt.compare(body.password, user.data.hash))) { + throw new HTTPError(req.t("auth:login.INVALID_PASSWORD")); + } + } - 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 }); + 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 }); - res.send({ - token: await generateToken(user.id), - backup_codes: backup_codes.map((x) => ({ - ...x, - expired: undefined, - })), - }); - }, + res.send({ + token: await generateToken(user.id), + backup_codes: backup_codes.map((x) => ({ + ...x, + expired: undefined, + })), + }); + }, ); export default router; diff --git a/src/api/routes/users/@me/mfa/webauthn/credentials/#key_id/index.ts b/src/api/routes/users/@me/mfa/webauthn/credentials/#key_id/index.ts
index 6dc06563..77180cac 100644 --- a/src/api/routes/users/@me/mfa/webauthn/credentials/#key_id/index.ts +++ b/src/api/routes/users/@me/mfa/webauthn/credentials/#key_id/index.ts
@@ -22,29 +22,29 @@ import { Request, Response, Router } from "express"; const router = Router({ mergeParams: true }); router.delete( - "/", - route({ - responses: { - 204: {}, - }, - }), - async (req: Request, res: Response) => { - const { key_id } = req.params; + "/", + route({ + responses: { + 204: {}, + }, + }), + async (req: Request, res: Response) => { + const { key_id } = req.params; - await SecurityKey.delete({ - id: key_id, - user_id: req.user_id, - }); + await SecurityKey.delete({ + id: key_id, + user_id: req.user_id, + }); - const keys = await SecurityKey.count({ - where: { user_id: req.user_id }, - }); + const keys = await SecurityKey.count({ + where: { user_id: req.user_id }, + }); - // disable webauthn if there are no keys left - if (keys === 0) await User.update({ id: req.user_id }, { webauthn_enabled: false }); + // disable webauthn if there are no keys left + if (keys === 0) await User.update({ id: req.user_id }, { webauthn_enabled: false }); - res.sendStatus(204); - }, + res.sendStatus(204); + }, ); export default router; diff --git a/src/api/routes/users/@me/mfa/webauthn/credentials/index.ts b/src/api/routes/users/@me/mfa/webauthn/credentials/index.ts
index 0c4b733d..7bd29a57 100644 --- a/src/api/routes/users/@me/mfa/webauthn/credentials/index.ts +++ b/src/api/routes/users/@me/mfa/webauthn/credentials/index.ts
@@ -26,138 +26,138 @@ import { CreateWebAuthnCredentialSchema, GenerateWebAuthnCredentialsSchema, WebA const router = Router({ mergeParams: true }); const isGenerateSchema = (body: WebAuthnPostSchema): body is GenerateWebAuthnCredentialsSchema => { - return "password" in body; + return "password" in body; }; const isCreateSchema = (body: WebAuthnPostSchema): body is CreateWebAuthnCredentialSchema => { - return "credential" in body; + return "credential" in body; }; function toArrayBuffer(buf: Buffer) { - const ab = new ArrayBuffer(buf.length); - const view = new Uint8Array(ab); - for (let i = 0; i < buf.length; ++i) { - view[i] = buf[i]; - } - return ab; + const ab = new ArrayBuffer(buf.length); + const view = new Uint8Array(ab); + for (let i = 0; i < buf.length; ++i) { + view[i] = buf[i]; + } + return ab; } router.get("/", route({}), async (req: Request, res: Response) => { - const securityKeys = await SecurityKey.find({ - where: { - user_id: req.user_id, - }, - }); + const securityKeys = await SecurityKey.find({ + where: { + user_id: req.user_id, + }, + }); - return res.json( - securityKeys.map((key) => ({ - id: key.id, - name: key.name, - })), - ); + return res.json( + securityKeys.map((key) => ({ + id: key.id, + name: key.name, + })), + ); }); router.post( - "/", - route({ - requestBody: "WebAuthnPostSchema", - responses: { - 200: { - body: "WebAuthnCreateResponse", - }, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - if (!WebAuthn.fido2) { - // TODO: I did this for typescript and I can't use ! - throw new Error("WebAuthn not enabled"); - } + "/", + route({ + requestBody: "WebAuthnPostSchema", + responses: { + 200: { + body: "WebAuthnCreateResponse", + }, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + if (!WebAuthn.fido2) { + // TODO: I did this for typescript and I can't use ! + throw new Error("WebAuthn not enabled"); + } - const user = await User.findOneOrFail({ - where: { - id: req.user_id, - }, - select: ["data", "id", "disabled", "deleted", "totp_secret", "mfa_enabled", "username"], - relations: ["settings"], - }); + const user = await User.findOneOrFail({ + where: { + id: req.user_id, + }, + 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 || ""); - if (!same_password) { - throw FieldErrors({ - password: { - message: req.t("auth:login.INVALID_PASSWORD"), - code: "INVALID_PASSWORD", - }, - }); - } + if (isGenerateSchema(req.body)) { + const { password } = req.body; + const same_password = await bcrypt.compare(password, user.data.hash || ""); + if (!same_password) { + throw FieldErrors({ + password: { + message: req.t("auth:login.INVALID_PASSWORD"), + code: "INVALID_PASSWORD", + }, + }); + } - const registrationOptions = await WebAuthn.fido2.attestationOptions(); - const challenge = JSON.stringify({ - publicKey: { - ...registrationOptions, - challenge: Buffer.from(registrationOptions.challenge).toString("base64"), - user: { - id: user.id, - name: user.username, - displayName: user.username, - }, - }, - }); + const registrationOptions = await WebAuthn.fido2.attestationOptions(); + const challenge = JSON.stringify({ + publicKey: { + ...registrationOptions, + challenge: Buffer.from(registrationOptions.challenge).toString("base64"), + user: { + id: user.id, + name: user.username, + displayName: user.username, + }, + }, + }); - const ticket = await generateWebAuthnTicket(challenge); + const ticket = await generateWebAuthnTicket(challenge); - return res.json({ - ticket: ticket, - challenge, - }); - } else if (isCreateSchema(req.body)) { - const { credential, name, ticket } = req.body; + return res.json({ + ticket: ticket, + challenge, + }); + } else if (isCreateSchema(req.body)) { + const { credential, name, ticket } = req.body; - const verified = await verifyWebAuthnToken(ticket); - if (!verified) throw new HTTPError("Invalid ticket", 400); + const verified = await verifyWebAuthnToken(ticket); + if (!verified) throw new HTTPError("Invalid ticket", 400); - const clientAttestationResponse = JSON.parse(credential); + 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"); - clientAttestationResponse.rawId = toArrayBuffer(rawIdBuffer); + 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 counter = authnrData.get("counter"); - const publicKey = authnrData.get("credentialPublicKeyPem"); + const authnrData = regResult.authnrData; + const keyId = Buffer.from(authnrData.get("credId")).toString("base64"); + const counter = authnrData.get("counter"); + const publicKey = authnrData.get("credentialPublicKeyPem"); - const securityKey = SecurityKey.create({ - name, - counter, - public_key: publicKey, - user_id: req.user_id, - key_id: keyId, - }); + const securityKey = SecurityKey.create({ + name, + counter, + public_key: publicKey, + user_id: req.user_id, + 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, - id: securityKey.id, - }); - } else { - throw DiscordApiErrors.INVALID_AUTHENTICATION_TOKEN; - } - }, + return res.json({ + name, + id: securityKey.id, + }); + } else { + throw DiscordApiErrors.INVALID_AUTHENTICATION_TOKEN; + } + }, ); export default router; diff --git a/src/api/routes/users/@me/notes.ts b/src/api/routes/users/@me/notes.ts
index 4969fa75..8cd33304 100644 --- a/src/api/routes/users/@me/notes.ts +++ b/src/api/routes/users/@me/notes.ts
@@ -23,89 +23,89 @@ import { Request, Response, Router } from "express"; const router: Router = Router({ mergeParams: true }); router.get( - "/:user_id", - route({ - responses: { - 200: { - body: "UserNoteResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { user_id } = req.params; + "/:user_id", + route({ + responses: { + 200: { + body: "UserNoteResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { user_id } = req.params; - const note = await Note.findOneOrFail({ - where: { - owner: { id: req.user_id }, - target: { id: user_id }, - }, - }); + const note = await Note.findOneOrFail({ + where: { + owner: { id: req.user_id }, + target: { id: user_id }, + }, + }); - return res.json({ - note: note?.content, - note_user_id: user_id, - user_id: req.user_id, - }); - }, + return res.json({ + note: note?.content, + note_user_id: user_id, + user_id: req.user_id, + }); + }, ); router.put( - "/:user_id", - route({ - requestBody: "UserNoteUpdateSchema", - responses: { - 204: {}, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { user_id } = req.params; - const owner = await User.findOneOrFail({ where: { id: req.user_id } }); - const target = await User.findOneOrFail({ where: { id: user_id } }); //if noted user does not exist throw - const { note } = req.body; + "/:user_id", + route({ + requestBody: "UserNoteUpdateSchema", + responses: { + 204: {}, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { user_id } = req.params; + const owner = await User.findOneOrFail({ where: { id: req.user_id } }); + const target = await User.findOneOrFail({ where: { id: user_id } }); //if noted user does not exist throw + const { note } = req.body; - if (note && note.length) { - // upsert a note - if ( - await Note.findOne({ - where: { - owner: { id: owner.id }, - target: { id: target.id }, - }, - }) - ) { - Note.update({ owner: { id: owner.id }, target: { id: target.id } }, { owner, target, content: note }); - } else { - Note.insert({ - id: Snowflake.generate(), - owner, - target, - content: note, - }); - } - } else { - await Note.delete({ - owner: { id: owner.id }, - target: { id: target.id }, - }); - } + if (note && note.length) { + // upsert a note + if ( + await Note.findOne({ + where: { + owner: { id: owner.id }, + target: { id: target.id }, + }, + }) + ) { + Note.update({ owner: { id: owner.id }, target: { id: target.id } }, { owner, target, content: note }); + } else { + Note.insert({ + id: Snowflake.generate(), + owner, + target, + content: note, + }); + } + } else { + await Note.delete({ + owner: { id: owner.id }, + target: { id: target.id }, + }); + } - await emitEvent({ - event: "USER_NOTE_UPDATE", - data: { - note: note, - id: target.id, - }, - user_id: owner.id, - }); + await emitEvent({ + event: "USER_NOTE_UPDATE", + data: { + note: note, + id: target.id, + }, + user_id: owner.id, + }); - return res.sendStatus(204); - }, + return res.sendStatus(204); + }, ); export default router; diff --git a/src/api/routes/users/@me/relationships.ts b/src/api/routes/users/@me/relationships.ts
index d3df9eb0..ef6c7798 100644 --- a/src/api/routes/users/@me/relationships.ts +++ b/src/api/routes/users/@me/relationships.ts
@@ -27,255 +27,255 @@ const router = Router({ mergeParams: true }); const userProjection: (keyof User)[] = ["relationships", ...PublicUserProjection]; router.get( - "/", - route({ - responses: { - 200: { - body: "UserRelationshipsResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const user = await User.findOneOrFail({ - where: { id: req.user_id }, - relations: ["relationships", "relationships.to"], - select: ["id", "relationships"], - }); + "/", + route({ + responses: { + 200: { + body: "UserRelationshipsResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const user = await User.findOneOrFail({ + where: { id: req.user_id }, + relations: ["relationships", "relationships.to"], + select: ["id", "relationships"], + }); - const related_users = user.relationships.map((r) => r.toPublicRelationship()); - return res.json(related_users); - }, + const related_users = user.relationships.map((r) => r.toPublicRelationship()); + return res.json(related_users); + }, ); router.put( - "/:user_id", - route({ - requestBody: "RelationshipPutSchema", - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - return await updateRelationship( - req, - res, - await User.findOneOrFail({ - where: { id: req.params.user_id }, - relations: ["relationships", "relationships.to"], - select: userProjection, - }), - req.body.type ?? RelationshipType.friends, - ); - }, + "/:user_id", + route({ + requestBody: "RelationshipPutSchema", + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + return await updateRelationship( + req, + res, + await User.findOneOrFail({ + where: { id: req.params.user_id }, + relations: ["relationships", "relationships.to"], + select: userProjection, + }), + req.body.type ?? RelationshipType.friends, + ); + }, ); router.post( - "/", - route({ - requestBody: "RelationshipPostSchema", - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - return await updateRelationship( - req, - res, - await User.findOneOrFail({ - 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 - username: req.body.username, - }, - }), - req.body.type, - ); - }, + "/", + route({ + requestBody: "RelationshipPostSchema", + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + return await updateRelationship( + req, + res, + await User.findOneOrFail({ + 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 + username: req.body.username, + }, + }), + req.body.type, + ); + }, ); router.delete( - "/:user_id", - route({ - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - 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"); + "/:user_id", + route({ + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + 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"); - const user = await User.findOneOrFail({ - where: { id: req.user_id }, - select: userProjection, - relations: ["relationships"], - }); - const friend = await User.findOneOrFail({ - where: { id: user_id }, - select: userProjection, - relations: ["relationships"], - }); + const user = await User.findOneOrFail({ + where: { id: req.user_id }, + select: userProjection, + relations: ["relationships"], + }); + const friend = await User.findOneOrFail({ + where: { id: user_id }, + select: userProjection, + relations: ["relationships"], + }); - const relationship = user.relationships.find((x) => x.to_id === user_id); - const friendRequest = friend.relationships.find((x) => x.to_id === req.user_id); + const relationship = user.relationships.find((x) => x.to_id === 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 - await Promise.all([ - Relationship.delete({ id: relationship.id }), - emitEvent({ - event: "RELATIONSHIP_REMOVE", - user_id: req.user_id, - data: relationship.toPublicRelationship(), - } as RelationshipRemoveEvent), - ]); - return res.sendStatus(204); - } - if (friendRequest && friendRequest.type !== RelationshipType.blocked) { - await Promise.all([ - Relationship.delete({ id: friendRequest.id }), - await emitEvent({ - event: "RELATIONSHIP_REMOVE", - data: friendRequest.toPublicRelationship(), - user_id: user_id, - } as RelationshipRemoveEvent), - ]); - } + if (relationship?.type === RelationshipType.blocked) { + // unblock user + await Promise.all([ + Relationship.delete({ id: relationship.id }), + emitEvent({ + event: "RELATIONSHIP_REMOVE", + user_id: req.user_id, + data: relationship.toPublicRelationship(), + } as RelationshipRemoveEvent), + ]); + return res.sendStatus(204); + } + if (friendRequest && friendRequest.type !== RelationshipType.blocked) { + await Promise.all([ + Relationship.delete({ id: friendRequest.id }), + await emitEvent({ + event: "RELATIONSHIP_REMOVE", + data: friendRequest.toPublicRelationship(), + user_id: user_id, + } as RelationshipRemoveEvent), + ]); + } - await Promise.all([ - Relationship.delete({ id: relationship.id }), - emitEvent({ - event: "RELATIONSHIP_REMOVE", - data: relationship.toPublicRelationship(), - user_id: req.user_id, - } as RelationshipRemoveEvent), - ]); + await Promise.all([ + Relationship.delete({ id: relationship.id }), + emitEvent({ + event: "RELATIONSHIP_REMOVE", + data: relationship.toPublicRelationship(), + user_id: req.user_id, + } as RelationshipRemoveEvent), + ]); - return res.sendStatus(204); - }, + return res.sendStatus(204); + }, ); export default router; 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"); + const id = friend.id; + 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 }, - relations: ["relationships", "relationships.to"], - select: userProjection, - }); + const user = await User.findOneOrFail({ + where: { id: req.user_id }, + relations: ["relationships", "relationships.to"], + select: userProjection, + }); - let relationship = user.relationships.find((x) => x.to_id === id); - const friendRequest = friend.relationships.find((x) => x.to_id === req.user_id); + let relationship = user.relationships.find((x) => x.to_id === 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"); - relationship.type = RelationshipType.blocked; - await relationship.save(); - } else { - relationship = await Relationship.create({ - to_id: id, - type: RelationshipType.blocked, - from_id: req.user_id, - }).save(); - } + // 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"); + relationship.type = RelationshipType.blocked; + await relationship.save(); + } else { + relationship = await Relationship.create({ + to_id: id, + type: RelationshipType.blocked, + from_id: req.user_id, + }).save(); + } - if (friendRequest && friendRequest.type !== RelationshipType.blocked) { - await Promise.all([ - Relationship.delete({ id: friendRequest.id }), - emitEvent({ - event: "RELATIONSHIP_REMOVE", - data: friendRequest.toPublicRelationship(), - user_id: id, - } as RelationshipRemoveEvent), - ]); - } + if (friendRequest && friendRequest.type !== RelationshipType.blocked) { + await Promise.all([ + Relationship.delete({ id: friendRequest.id }), + emitEvent({ + event: "RELATIONSHIP_REMOVE", + data: friendRequest.toPublicRelationship(), + user_id: id, + } as RelationshipRemoveEvent), + ]); + } - await emitEvent({ - event: "RELATIONSHIP_ADD", - data: relationship.toPublicRelationship(), - user_id: req.user_id, - } as RelationshipAddEvent); + await emitEvent({ + event: "RELATIONSHIP_ADD", + data: relationship.toPublicRelationship(), + user_id: req.user_id, + } as RelationshipAddEvent); - return res.sendStatus(204); - } + return res.sendStatus(204); + } - const { maxFriends } = Config.get().limits.user; - if (user.relationships.length >= maxFriends) throw DiscordApiErrors.MAXIMUM_FRIENDS.withParams(maxFriends); + const { maxFriends } = Config.get().limits.user; + if (user.relationships.length >= maxFriends) throw DiscordApiErrors.MAXIMUM_FRIENDS.withParams(maxFriends); - let incoming_relationship = Relationship.create({ - nickname: undefined, - type: RelationshipType.incoming, - to: user, - from: friend, - }); - let outgoing_relationship = Relationship.create({ - nickname: undefined, - type: RelationshipType.outgoing, - to: friend, - from: user, - }); + let incoming_relationship = Relationship.create({ + nickname: undefined, + type: RelationshipType.incoming, + to: user, + from: friend, + }); + let outgoing_relationship = Relationship.create({ + nickname: undefined, + type: RelationshipType.outgoing, + to: friend, + from: user, + }); - 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"); - // accept friend request - incoming_relationship = friendRequest; - incoming_relationship.type = RelationshipType.friends; - } + 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"); + // 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"); - outgoing_relationship = relationship; - outgoing_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"); + outgoing_relationship = relationship; + outgoing_relationship.type = RelationshipType.friends; + } - await Promise.all([ - incoming_relationship.save(), - outgoing_relationship.save(), - emitEvent({ - event: "RELATIONSHIP_ADD", - data: outgoing_relationship.toPublicRelationship(), - user_id: req.user_id, - } as RelationshipAddEvent), - emitEvent({ - event: "RELATIONSHIP_ADD", - data: { - ...incoming_relationship.toPublicRelationship(), - should_notify: true, - }, - user_id: id, - } as RelationshipAddEvent), - ]); + await Promise.all([ + incoming_relationship.save(), + outgoing_relationship.save(), + emitEvent({ + event: "RELATIONSHIP_ADD", + data: outgoing_relationship.toPublicRelationship(), + user_id: req.user_id, + } as RelationshipAddEvent), + emitEvent({ + event: "RELATIONSHIP_ADD", + data: { + ...incoming_relationship.toPublicRelationship(), + should_notify: true, + }, + user_id: id, + } as RelationshipAddEvent), + ]); - return res.sendStatus(204); + return res.sendStatus(204); } diff --git a/src/api/routes/users/@me/settings-proto/1.ts b/src/api/routes/users/@me/settings-proto/1.ts
index 6f58b422..d54bdf36 100644 --- a/src/api/routes/users/@me/settings-proto/1.ts +++ b/src/api/routes/users/@me/settings-proto/1.ts
@@ -27,159 +27,159 @@ const router: Router = Router({ mergeParams: true }); //#region Protobuf router.get( - "/", - route({ - responses: { - 200: { - body: "SettingsProtoResponse", - }, - }, - query: { - atomic: { - type: "boolean", - description: "Whether to try to apply the settings update atomically (default false)", - }, - }, - }), - async (req: Request, res: Response) => { - const userSettings = await UserSettingsProtos.getOrDefault(req.user_id); + "/", + route({ + responses: { + 200: { + body: "SettingsProtoResponse", + }, + }, + query: { + atomic: { + type: "boolean", + description: "Whether to try to apply the settings update atomically (default false)", + }, + }, + }), + async (req: Request, res: Response) => { + const userSettings = await UserSettingsProtos.getOrDefault(req.user_id); - res.json({ - settings: PreloadedUserSettings.toBase64(userSettings.userSettings!), - } as SettingsProtoResponse); - }, + res.json({ + settings: PreloadedUserSettings.toBase64(userSettings.userSettings!), + } as SettingsProtoResponse); + }, ); router.patch( - "/", - route({ - requestBody: "SettingsProtoUpdateSchema", - responses: { - 200: { - body: "SettingsProtoUpdateResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { settings, required_data_version } = req.body as SettingsProtoUpdateSchema; - const { atomic } = req.query; - const updatedSettings = PreloadedUserSettings.fromBase64(settings); + "/", + route({ + requestBody: "SettingsProtoUpdateSchema", + responses: { + 200: { + body: "SettingsProtoUpdateResponse", + }, + }, + }), + async (req: Request, res: Response) => { + 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), - out_of_date: resultObj.out_of_date, - }); - }, + res.json({ + settings: PreloadedUserSettings.toBase64(resultObj.settings), + out_of_date: resultObj.out_of_date, + }); + }, ); //#endregion //#region JSON router.get( - "/json", - route({ - responses: { - 200: { - body: "SettingsProtoJsonResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const userSettings = await UserSettingsProtos.getOrDefault(req.user_id); + "/json", + route({ + responses: { + 200: { + body: "SettingsProtoJsonResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const userSettings = await UserSettingsProtos.getOrDefault(req.user_id); - res.json({ - settings: PreloadedUserSettings.toJson(userSettings.userSettings!), - } as SettingsProtoJsonResponse); - }, + res.json({ + settings: PreloadedUserSettings.toJson(userSettings.userSettings!), + } as SettingsProtoJsonResponse); + }, ); router.patch( - "/json", - route({ - requestBody: "SettingsProtoUpdateJsonSchema", - responses: { - 200: { - body: "SettingsProtoUpdateJsonResponse", - }, - }, - query: { - atomic: { - type: "boolean", - 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 { atomic } = req.query; - const updatedSettings = PreloadedUserSettings.fromJson(settings); + "/json", + route({ + requestBody: "SettingsProtoUpdateJsonSchema", + responses: { + 200: { + body: "SettingsProtoUpdateJsonResponse", + }, + }, + query: { + atomic: { + type: "boolean", + 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 { 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), - out_of_date: resultObj.out_of_date, - }); - }, + res.json({ + settings: PreloadedUserSettings.toJson(resultObj.settings), + out_of_date: resultObj.out_of_date, + }); + }, ); //#endregion 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!; + const userSettings = await UserSettingsProtos.getOrDefault(userId); + let settings = userSettings.userSettings!; - if (required_data_version && settings.versions && settings.versions.dataVersion > required_data_version) { - return { - settings: settings, - out_of_date: true, - }; - } + if (required_data_version && settings.versions && settings.versions.dataVersion > required_data_version) { + return { + settings: settings, + out_of_date: true, + }; + } - 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); + 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); - if (!atomic) { - settings = PreloadedUserSettings.fromJson( - 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, - ); - } + if (!atomic) { + settings = PreloadedUserSettings.fromJson( + 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, + ); + } - settings.versions = { - clientVersion: updatedSettings.versions?.clientVersion ?? settings.versions?.clientVersion ?? 0, - serverVersion: settings.versions?.serverVersion ?? 0, - dataVersion: (settings.versions?.dataVersion ?? 0) + 1, - }; - userSettings.userSettings = settings; - await userSettings.save(); + settings.versions = { + clientVersion: updatedSettings.versions?.clientVersion ?? settings.versions?.clientVersion ?? 0, + serverVersion: settings.versions?.serverVersion ?? 0, + dataVersion: (settings.versions?.dataVersion ?? 0) + 1, + }; + userSettings.userSettings = settings; + await userSettings.save(); - await emitEvent({ - user_id: userId, - event: "USER_SETTINGS_PROTO_UPDATE", - data: { - settings: { - proto: PreloadedUserSettings.toBase64(settings), - type: 1, - }, - json_settings: { - proto: PreloadedUserSettings.toJson(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. + await emitEvent({ + user_id: userId, + event: "USER_SETTINGS_PROTO_UPDATE", + data: { + settings: { + proto: PreloadedUserSettings.toBase64(settings), + type: 1, + }, + json_settings: { + proto: PreloadedUserSettings.toJson(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. - return { - settings: settings, - out_of_date: false, - }; + return { + settings: settings, + out_of_date: false, + }; } export default router; diff --git a/src/api/routes/users/@me/settings-proto/2.ts b/src/api/routes/users/@me/settings-proto/2.ts
index b37805fd..809db848 100644 --- a/src/api/routes/users/@me/settings-proto/2.ts +++ b/src/api/routes/users/@me/settings-proto/2.ts
@@ -27,159 +27,159 @@ const router: Router = Router({ mergeParams: true }); //#region Protobuf router.get( - "/", - route({ - responses: { - 200: { - body: "SettingsProtoResponse", - }, - }, - query: { - atomic: { - type: "boolean", - description: "Whether to try to apply the settings update atomically (default false)", - }, - }, - }), - async (req: Request, res: Response) => { - const userSettings = await UserSettingsProtos.getOrDefault(req.user_id); + "/", + route({ + responses: { + 200: { + body: "SettingsProtoResponse", + }, + }, + query: { + atomic: { + type: "boolean", + description: "Whether to try to apply the settings update atomically (default false)", + }, + }, + }), + async (req: Request, res: Response) => { + const userSettings = await UserSettingsProtos.getOrDefault(req.user_id); - res.json({ - settings: FrecencyUserSettings.toBase64(userSettings.frecencySettings!), - } as SettingsProtoResponse); - }, + res.json({ + settings: FrecencyUserSettings.toBase64(userSettings.frecencySettings!), + } as SettingsProtoResponse); + }, ); router.patch( - "/", - route({ - requestBody: "SettingsProtoUpdateSchema", - responses: { - 200: { - body: "SettingsProtoUpdateResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { settings, required_data_version } = req.body as SettingsProtoUpdateSchema; - const { atomic } = req.query; - const updatedSettings = FrecencyUserSettings.fromBase64(settings); + "/", + route({ + requestBody: "SettingsProtoUpdateSchema", + responses: { + 200: { + body: "SettingsProtoUpdateResponse", + }, + }, + }), + async (req: Request, res: Response) => { + 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), - out_of_date: resultObj.out_of_date, - }); - }, + res.json({ + settings: FrecencyUserSettings.toBase64(resultObj.settings), + out_of_date: resultObj.out_of_date, + }); + }, ); //#endregion //#region JSON router.get( - "/json", - route({ - responses: { - 200: { - body: "SettingsProtoJsonResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const userSettings = await UserSettingsProtos.getOrDefault(req.user_id); + "/json", + route({ + responses: { + 200: { + body: "SettingsProtoJsonResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const userSettings = await UserSettingsProtos.getOrDefault(req.user_id); - res.json({ - settings: FrecencyUserSettings.toJson(userSettings.frecencySettings!), - } as SettingsProtoJsonResponse); - }, + res.json({ + settings: FrecencyUserSettings.toJson(userSettings.frecencySettings!), + } as SettingsProtoJsonResponse); + }, ); router.patch( - "/json", - route({ - requestBody: "SettingsProtoUpdateJsonSchema", - responses: { - 200: { - body: "SettingsProtoUpdateJsonResponse", - }, - }, - query: { - atomic: { - type: "boolean", - 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 { atomic } = req.query; - const updatedSettings = FrecencyUserSettings.fromJson(settings); + "/json", + route({ + requestBody: "SettingsProtoUpdateJsonSchema", + responses: { + 200: { + body: "SettingsProtoUpdateJsonResponse", + }, + }, + query: { + atomic: { + type: "boolean", + 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 { 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), - out_of_date: resultObj.out_of_date, - }); - }, + res.json({ + settings: FrecencyUserSettings.toJson(resultObj.settings), + out_of_date: resultObj.out_of_date, + }); + }, ); //#endregion 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!; + const userSettings = await UserSettingsProtos.getOrDefault(userId); + let settings = userSettings.frecencySettings!; - if (required_data_version && settings.versions && settings.versions.dataVersion > required_data_version) { - return { - settings: settings, - out_of_date: true, - }; - } + if (required_data_version && settings.versions && settings.versions.dataVersion > required_data_version) { + return { + settings: settings, + out_of_date: true, + }; + } - 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); + 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); - if (!atomic) { - settings = FrecencyUserSettings.fromJson( - 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, - ); - } + if (!atomic) { + settings = FrecencyUserSettings.fromJson( + 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, + ); + } - settings.versions = { - clientVersion: updatedSettings.versions?.clientVersion ?? settings.versions?.clientVersion ?? 0, - serverVersion: settings.versions?.serverVersion ?? 0, - dataVersion: (settings.versions?.dataVersion ?? 0) + 1, - }; - userSettings.frecencySettings = settings; - await userSettings.save(); + settings.versions = { + clientVersion: updatedSettings.versions?.clientVersion ?? settings.versions?.clientVersion ?? 0, + serverVersion: settings.versions?.serverVersion ?? 0, + dataVersion: (settings.versions?.dataVersion ?? 0) + 1, + }; + userSettings.frecencySettings = settings; + await userSettings.save(); - await emitEvent({ - user_id: userId, - event: "USER_SETTINGS_PROTO_UPDATE", - data: { - settings: { - proto: FrecencyUserSettings.toBase64(settings), - type: 2, - }, - json_settings: { - proto: FrecencyUserSettings.toJson(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. + await emitEvent({ + user_id: userId, + event: "USER_SETTINGS_PROTO_UPDATE", + data: { + settings: { + proto: FrecencyUserSettings.toBase64(settings), + type: 2, + }, + json_settings: { + proto: FrecencyUserSettings.toJson(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. - return { - settings: settings, - out_of_date: false, - }; + return { + settings: settings, + out_of_date: false, + }; } export default router; diff --git a/src/api/routes/users/@me/settings.ts b/src/api/routes/users/@me/settings.ts
index d220b859..57ffba14 100644 --- a/src/api/routes/users/@me/settings.ts +++ b/src/api/routes/users/@me/settings.ts
@@ -24,81 +24,81 @@ import { UserSettingsUpdateSchema } from "@spacebar/schemas"; const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "UserSettings", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const settings = await UserSettings.getOrDefault(req.user_id); - return res.json(settings); - }, + "/", + route({ + responses: { + 200: { + body: "UserSettings", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const settings = await UserSettings.getOrDefault(req.user_id); + return res.json(settings); + }, ); router.patch( - "/", - route({ - requestBody: "UserSettingsUpdateSchema", - responses: { - 200: { - body: "UserSettings", - }, - 400: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const body = req.body as UserSettingsUpdateSchema; - if (!body) return res.status(400).json({ code: 400, message: "Invalid request body" }); - if (body.locale === "en") body.locale = "en-US"; // fix discord client crash on unknown locale + "/", + route({ + requestBody: "UserSettingsUpdateSchema", + responses: { + 200: { + body: "UserSettings", + }, + 400: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const body = req.body as UserSettingsUpdateSchema; + if (!body) return res.status(400).json({ code: 400, message: "Invalid request body" }); + if (body.locale === "en") body.locale = "en-US"; // fix discord client crash on unknown locale - const user = await User.findOneOrFail({ - where: { id: req.user_id, bot: false }, - relations: ["settings"], - }); + const user = await User.findOneOrFail({ + where: { id: req.user_id, bot: false }, + relations: ["settings"], + }); - if (!user.settings) user.settings = UserSettings.create<UserSettings>(body); - else user.settings.assign(body); + if (!user.settings) user.settings = UserSettings.create<UserSettings>(body); + else user.settings.assign(body); - if (body.guild_folders) user.settings.guild_folders = body.guild_folders; + if (body.guild_folders) user.settings.guild_folders = body.guild_folders; - await user.settings.save(); - await user.save(); - if (body.status) { - const [session] = (await Session.find({ - where: { user_id: user.id }, - })) as [Session | undefined]; - if (session) { - session.status = body.status; + await user.settings.save(); + await user.save(); + if (body.status) { + const [session] = (await Session.find({ + where: { user_id: user.id }, + })) as [Session | undefined]; + if (session) { + session.status = body.status; - await Promise.all([ - emitEvent({ - event: "PRESENCE_UPDATE", - user_id: user.id, - data: { - user: user.toPublicUser(), - activities: session.activities, - client_status: session?.client_status, - status: session.getPublicStatus(), - }, - } as PresenceUpdateEvent), - session.save(), - ]); - } - } + await Promise.all([ + emitEvent({ + event: "PRESENCE_UPDATE", + user_id: user.id, + data: { + user: user.toPublicUser(), + activities: session.activities, + client_status: session?.client_status, + status: session.getPublicStatus(), + }, + } as PresenceUpdateEvent), + session.save(), + ]); + } + } - res.json({ ...user.settings, index: undefined }); - }, + res.json({ ...user.settings, index: undefined }); + }, ); export default router;