summary refs log tree commit diff
path: root/src/api
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
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')
-rw-r--r--src/api/Server.ts236
-rw-r--r--src/api/middlewares/Authentication.ts182
-rw-r--r--src/api/middlewares/BodyParser.ts44
-rw-r--r--src/api/middlewares/CORS.ts30
-rw-r--r--src/api/middlewares/ErrorHandler.ts78
-rw-r--r--src/api/middlewares/ImageProxy.ts168
-rw-r--r--src/api/middlewares/RateLimit.ts302
-rw-r--r--src/api/middlewares/Translation.ts34
-rw-r--r--src/api/routes/-/healthz.ts4
-rw-r--r--src/api/routes/-/readyz.ts4
-rw-r--r--src/api/routes/applications/#application_id/bot/index.ts140
-rw-r--r--src/api/routes/applications/#application_id/commands/#command_id/index.ts142
-rw-r--r--src/api/routes/applications/#application_id/commands/index.ts228
-rw-r--r--src/api/routes/applications/#application_id/entitlements.ts26
-rw-r--r--src/api/routes/applications/#application_id/guilds/#guild_id/commands/#command_id/index.ts206
-rw-r--r--src/api/routes/applications/#application_id/guilds/#guild_id/commands/index.ts286
-rw-r--r--src/api/routes/applications/#application_id/index.ts164
-rw-r--r--src/api/routes/applications/#application_id/skus.ts22
-rw-r--r--src/api/routes/applications/@me.ts114
-rw-r--r--src/api/routes/applications/detectable.ts44
-rw-r--r--src/api/routes/applications/index.ts84
-rw-r--r--src/api/routes/attachments/refresh-urls.ts58
-rw-r--r--src/api/routes/auth/fingerprint.ts8
-rw-r--r--src/api/routes/auth/forgot.ts86
-rw-r--r--src/api/routes/auth/generate-registration-tokens.ts72
-rw-r--r--src/api/routes/auth/location-metadata.ts36
-rw-r--r--src/api/routes/auth/login.ts268
-rw-r--r--src/api/routes/auth/logout.ts38
-rw-r--r--src/api/routes/auth/mfa/totp.ts86
-rw-r--r--src/api/routes/auth/mfa/webauthn.ts108
-rw-r--r--src/api/routes/auth/register.ts516
-rw-r--r--src/api/routes/auth/reset.ts86
-rw-r--r--src/api/routes/auth/sessions.ts84
-rw-r--r--src/api/routes/auth/verify/index.ts116
-rw-r--r--src/api/routes/auth/verify/resend.ts68
-rw-r--r--src/api/routes/auth/verify/view-backup-codes-challenge.ts54
-rw-r--r--src/api/routes/beaker.ts20
-rw-r--r--src/api/routes/channels/#channel_id/attachments.ts180
-rw-r--r--src/api/routes/channels/#channel_id/directory-entries.ts28
-rw-r--r--src/api/routes/channels/#channel_id/greet.ts132
-rw-r--r--src/api/routes/channels/#channel_id/index.ts220
-rw-r--r--src/api/routes/channels/#channel_id/invites.ts156
-rw-r--r--src/api/routes/channels/#channel_id/messages/#message_id/ack.ts62
-rw-r--r--src/api/routes/channels/#channel_id/messages/#message_id/crosspost.ts74
-rw-r--r--src/api/routes/channels/#channel_id/messages/#message_id/index.ts498
-rw-r--r--src/api/routes/channels/#channel_id/messages/#message_id/reactions.ts552
-rw-r--r--src/api/routes/channels/#channel_id/messages/bulk-delete.ts74
-rw-r--r--src/api/routes/channels/#channel_id/messages/index.ts808
-rw-r--r--src/api/routes/channels/#channel_id/messages/pins/index.ts280
-rw-r--r--src/api/routes/channels/#channel_id/messages/search.ts204
-rw-r--r--src/api/routes/channels/#channel_id/permissions.ts122
-rw-r--r--src/api/routes/channels/#channel_id/pins.ts266
-rw-r--r--src/api/routes/channels/#channel_id/purge.ts114
-rw-r--r--src/api/routes/channels/#channel_id/recipients.ts124
-rw-r--r--src/api/routes/channels/#channel_id/typing.ts80
-rw-r--r--src/api/routes/channels/#channel_id/webhooks.ts138
-rw-r--r--src/api/routes/channels/preload-messages.ts80
-rw-r--r--src/api/routes/collectibles-categories.ts24
-rw-r--r--src/api/routes/collectibles-shop.ts30
-rw-r--r--src/api/routes/connections/#connection_name/#connection_id/refresh.ts6
-rw-r--r--src/api/routes/connections/#connection_name/authorize.ts40
-rw-r--r--src/api/routes/connections/#connection_name/callback.ts56
-rw-r--r--src/api/routes/connections/index.ts32
-rw-r--r--src/api/routes/discoverable-guilds.ts88
-rw-r--r--src/api/routes/discovery.ts32
-rw-r--r--src/api/routes/download.ts54
-rw-r--r--src/api/routes/emojis/#emoji_id/source.ts110
-rw-r--r--src/api/routes/experiments.ts4
-rw-r--r--src/api/routes/games/detectable.ts44
-rw-r--r--src/api/routes/gateway/bot.ts42
-rw-r--r--src/api/routes/gateway/index.ts28
-rw-r--r--src/api/routes/gifs/search.ts70
-rw-r--r--src/api/routes/gifs/trending-gifs.ts60
-rw-r--r--src/api/routes/gifs/trending.ts80
-rw-r--r--src/api/routes/guild-recommendations.ts48
-rw-r--r--src/api/routes/guilds/#guild_id/application-command-index.ts104
-rw-r--r--src/api/routes/guilds/#guild_id/audit-logs.ts18
-rw-r--r--src/api/routes/guilds/#guild_id/auto-moderation/rules.ts188
-rw-r--r--src/api/routes/guilds/#guild_id/bans.ts434
-rw-r--r--src/api/routes/guilds/#guild_id/bulk-ban.ts156
-rw-r--r--src/api/routes/guilds/#guild_id/channels.ts244
-rw-r--r--src/api/routes/guilds/#guild_id/delete.ts62
-rw-r--r--src/api/routes/guilds/#guild_id/discovery-requirements.ts74
-rw-r--r--src/api/routes/guilds/#guild_id/emojis.ts300
-rw-r--r--src/api/routes/guilds/#guild_id/index.ts306
-rw-r--r--src/api/routes/guilds/#guild_id/integrations.ts2
-rw-r--r--src/api/routes/guilds/#guild_id/invites.ts48
-rw-r--r--src/api/routes/guilds/#guild_id/member-verification.ts30
-rw-r--r--src/api/routes/guilds/#guild_id/members/#member_id/index.ts342
-rw-r--r--src/api/routes/guilds/#guild_id/members/#member_id/nick.ts56
-rw-r--r--src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts56
-rw-r--r--src/api/routes/guilds/#guild_id/members/index.ts70
-rw-r--r--src/api/routes/guilds/#guild_id/messages/search.ts220
-rw-r--r--src/api/routes/guilds/#guild_id/premium.ts4
-rw-r--r--src/api/routes/guilds/#guild_id/profile.ts72
-rw-r--r--src/api/routes/guilds/#guild_id/profile/index.ts70
-rw-r--r--src/api/routes/guilds/#guild_id/prune.ts148
-rw-r--r--src/api/routes/guilds/#guild_id/regions.ts34
-rw-r--r--src/api/routes/guilds/#guild_id/roles/#role_id/index.ts202
-rw-r--r--src/api/routes/guilds/#guild_id/roles/#role_id/member-ids.ts24
-rw-r--r--src/api/routes/guilds/#guild_id/roles/#role_id/members.ts26
-rw-r--r--src/api/routes/guilds/#guild_id/roles/index.ts210
-rw-r--r--src/api/routes/guilds/#guild_id/roles/member-counts.ts16
-rw-r--r--src/api/routes/guilds/#guild_id/stickers.ts292
-rw-r--r--src/api/routes/guilds/#guild_id/templates.ts254
-rw-r--r--src/api/routes/guilds/#guild_id/vanity-url.ts154
-rw-r--r--src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts104
-rw-r--r--src/api/routes/guilds/#guild_id/webhooks.ts48
-rw-r--r--src/api/routes/guilds/#guild_id/welcome-screen.ts104
-rw-r--r--src/api/routes/guilds/#guild_id/widget.json.ts146
-rw-r--r--src/api/routes/guilds/#guild_id/widget.png.ts176
-rw-r--r--src/api/routes/guilds/#guild_id/widget.ts96
-rw-r--r--src/api/routes/guilds/automations/email-domain-lookup.ts116
-rw-r--r--src/api/routes/guilds/index.ts76
-rw-r--r--src/api/routes/guilds/templates/index.ts104
-rw-r--r--src/api/routes/hub-waitlist.ts42
-rw-r--r--src/api/routes/interactions/#interaction_id/#interaction_token/callback.ts250
-rw-r--r--src/api/routes/interactions/index.ts164
-rw-r--r--src/api/routes/invites/index.ts216
-rw-r--r--src/api/routes/oauth2/applications/@me.ts46
-rw-r--r--src/api/routes/oauth2/authorize.ts348
-rw-r--r--src/api/routes/oauth2/tokens.ts4
-rw-r--r--src/api/routes/outbound-promotions.ts4
-rw-r--r--src/api/routes/partners/#guild_id/requirements.ts54
-rw-r--r--src/api/routes/ping.ts48
-rwxr-xr-xsrc/api/routes/policies/instance/config.ts76
-rw-r--r--src/api/routes/policies/instance/domains.ts38
-rw-r--r--src/api/routes/policies/instance/index.ts24
-rw-r--r--src/api/routes/policies/instance/limits.ts24
-rw-r--r--src/api/routes/policies/stats.ts50
-rw-r--r--src/api/routes/read-states/ack-bulk.ts74
-rw-r--r--src/api/routes/reporting/menu/message.ts26
-rw-r--r--src/api/routes/safety-hub/@me/index.ts58
-rw-r--r--src/api/routes/safety-hub/suspended/@me.ts58
-rw-r--r--src/api/routes/scheduled-maintenances/upcoming.json.ts8
-rw-r--r--src/api/routes/science.ts20
-rw-r--r--src/api/routes/stage-instances.ts4
-rw-r--r--src/api/routes/sticker-packs/index.ts28
-rw-r--r--src/api/routes/stickers/#sticker_id/index.ts24
-rw-r--r--src/api/routes/stop.ts30
-rw-r--r--src/api/routes/store/published-listings/applications/#application_id/index.ts140
-rw-r--r--src/api/routes/store/published-listings/applications/#application_id/subscription-plans.ts32
-rw-r--r--src/api/routes/store/published-listings/skus.ts140
-rw-r--r--src/api/routes/store/published-listings/skus/#sku_id/subscription-plans.ts600
-rw-r--r--src/api/routes/teams.ts106
-rw-r--r--src/api/routes/track.ts4
-rw-r--r--src/api/routes/updates.ts74
-rw-r--r--src/api/routes/users/#user_id/delete.ts310
-rw-r--r--src/api/routes/users/#user_id/index.ts24
-rw-r--r--src/api/routes/users/#user_id/messages.ts48
-rw-r--r--src/api/routes/users/#user_id/profile.ts236
-rw-r--r--src/api/routes/users/#user_id/relationships.ts68
-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
-rw-r--r--src/api/routes/voice/regions.ts22
-rw-r--r--src/api/routes/webhooks/#webhook_id/#token/github.ts746
-rw-r--r--src/api/routes/webhooks/#webhook_id/#token/index.ts290
-rw-r--r--src/api/routes/webhooks/#webhook_id/index.ts220
-rw-r--r--src/api/start.ts34
-rw-r--r--src/api/util/handlers/Instance.ts54
-rw-r--r--src/api/util/handlers/Message.ts932
-rw-r--r--src/api/util/handlers/Voice.ts44
-rw-r--r--src/api/util/handlers/Webhook.ts192
-rw-r--r--src/api/util/handlers/route.ts170
-rw-r--r--src/api/util/utility/Base64.ts46
-rw-r--r--src/api/util/utility/EmbedHandlers.ts826
-rw-r--r--src/api/util/utility/RandomInviteID.ts52
-rw-r--r--src/api/util/utility/String.ts22
-rw-r--r--src/api/util/utility/captcha.ts60
-rw-r--r--src/api/util/utility/ipAddress.ts10
-rw-r--r--src/api/util/utility/passwordStrength.ts62
205 files changed, 12444 insertions, 12444 deletions
diff --git a/src/api/Server.ts b/src/api/Server.ts

index 0969e5e8..f81898e8 100644 --- a/src/api/Server.ts +++ b/src/api/Server.ts
@@ -31,149 +31,149 @@ const PUBLIC_ASSETS_FOLDER = path.join(ASSETS_FOLDER, "public"); export type SpacebarServerOptions = ServerOptions; declare global { - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace Express { - interface Request { - server: SpacebarServer; - } - } + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Express { + interface Request { + server: SpacebarServer; + } + } } export class SpacebarServer extends Server { - declare public options: SpacebarServerOptions; + declare public options: SpacebarServerOptions; - constructor(opts?: Partial<SpacebarServerOptions>) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - super({ ...opts, errorHandler: false, jsonBody: false }); - } + constructor(opts?: Partial<SpacebarServerOptions>) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + super({ ...opts, errorHandler: false, jsonBody: false }); + } - async start() { - await initDatabase(); - await Config.init(); - await initEvent(); - await Email.init(); - await ConnectionConfig.init(); - await initInstance(); - WebAuthn.init(); + async start() { + await initDatabase(); + await Config.init(); + await initEvent(); + await Email.init(); + await ConnectionConfig.init(); + await initInstance(); + WebAuthn.init(); - const logRequests = process.env["LOG_REQUESTS"] != undefined; - if (logRequests) { - this.app.use( - morgan("combined", { - skip: (req, res) => { - let skip = !(process.env["LOG_REQUESTS"]?.includes(res.statusCode.toString()) ?? false); - if (process.env["LOG_REQUESTS"]?.charAt(0) == "-") skip = !skip; - return skip; - }, - }), - ); - } + const logRequests = process.env["LOG_REQUESTS"] != undefined; + if (logRequests) { + this.app.use( + morgan("combined", { + skip: (req, res) => { + let skip = !(process.env["LOG_REQUESTS"]?.includes(res.statusCode.toString()) ?? false); + if (process.env["LOG_REQUESTS"]?.charAt(0) == "-") skip = !skip; + return skip; + }, + }), + ); + } - this.app.set("json replacer", JSONReplacer); - this.app.disable("x-powered-by"); + this.app.set("json replacer", JSONReplacer); + this.app.disable("x-powered-by"); - const trustedProxies = Config.get().security.trustedProxies; - if (trustedProxies) this.app.set("trust proxy", trustedProxies); + const trustedProxies = Config.get().security.trustedProxies; + if (trustedProxies) this.app.set("trust proxy", trustedProxies); - this.app.use(CORS); - this.app.use(BodyParser({ inflate: true, limit: "10mb" })); + this.app.use(CORS); + this.app.use(BodyParser({ inflate: true, limit: "10mb" })); - const app = this.app; - const api = Router({ mergeParams: true }); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - this.app = api; + const app = this.app; + const api = Router({ mergeParams: true }); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + this.app = api; - api.use(Authentication); - await initRateLimits(api); - await initTranslation(api); + api.use(Authentication); + await initRateLimits(api); + await initTranslation(api); - this.routes = (await registerRoutes(this, path.join(__dirname, "routes", "/"))).filter((r) => !!r); + this.routes = (await registerRoutes(this, path.join(__dirname, "routes", "/"))).filter((r) => !!r); - // 404 is not an error in express, so this should not be an error middleware - // this is a fine place to put the 404 handler because its after we register the routes - // and since its not an error middleware, our error handler below still works. - // Emma [it/its] @ Rory& - the _ is required now, as pillarjs throw an error if you don't pass a param name now - api.use("*_", (req: Request, res: Response) => { - res.status(404).json({ - message: "404 endpoint not found", - code: 0, - }); - }); + // 404 is not an error in express, so this should not be an error middleware + // this is a fine place to put the 404 handler because its after we register the routes + // and since its not an error middleware, our error handler below still works. + // Emma [it/its] @ Rory& - the _ is required now, as pillarjs throw an error if you don't pass a param name now + api.use("*_", (req: Request, res: Response) => { + res.status(404).json({ + message: "404 endpoint not found", + code: 0, + }); + }); - this.app = app; + this.app = app; - //app.use("/__development", ) - //app.use("/__internals", ) - app.use("/api/v6", api); - app.use("/api/v7", api); - app.use("/api/v8", api); - app.use("/api/v9", api); - app.use("/api", api); // allow unversioned requests + //app.use("/__development", ) + //app.use("/__internals", ) + app.use("/api/v6", api); + app.use("/api/v7", api); + app.use("/api/v8", api); + app.use("/api/v9", api); + app.use("/api", api); // allow unversioned requests - app.use("/imageproxy/:hash/:size/:url", ImageProxy); + app.use("/imageproxy/:hash/:size/:url", ImageProxy); - app.get("/", (req, res) => res.sendFile(path.join(PUBLIC_ASSETS_FOLDER, "index.html"))); + app.get("/", (req, res) => res.sendFile(path.join(PUBLIC_ASSETS_FOLDER, "index.html"))); - app.get("/verify-email", (req, res) => res.sendFile(path.join(PUBLIC_ASSETS_FOLDER, "verify.html"))); + app.get("/verify-email", (req, res) => res.sendFile(path.join(PUBLIC_ASSETS_FOLDER, "verify.html"))); - app.get("/_spacebar/api/schemas.json", (req, res) => { - res.sendFile(path.join(ASSETS_FOLDER, "schemas.json")); - }); + app.get("/_spacebar/api/schemas.json", (req, res) => { + res.sendFile(path.join(ASSETS_FOLDER, "schemas.json")); + }); - app.get("/_spacebar/api/openapi.json", (req, res) => { - res.sendFile(path.join(ASSETS_FOLDER, "openapi.json")); - }); + app.get("/_spacebar/api/openapi.json", (req, res) => { + res.sendFile(path.join(ASSETS_FOLDER, "openapi.json")); + }); - // current well-known location - app.get("/.well-known/spacebar", (req, res) => { - res.json({ - api: Config.get().api.endpointPublic, - }); - }); + // current well-known location + app.get("/.well-known/spacebar", (req, res) => { + res.json({ + api: Config.get().api.endpointPublic, + }); + }); - // new well-known location - app.get("/.well-known/spacebar/client", (req, res) => { - let erlpackSupported = false; - try { - require("@yukikaze-bot/erlpack"); - erlpackSupported = true; - } catch (e) { - // empty - } + // new well-known location + app.get("/.well-known/spacebar/client", (req, res) => { + let erlpackSupported = false; + try { + require("@yukikaze-bot/erlpack"); + erlpackSupported = true; + } catch (e) { + // empty + } - res.json({ - api: { - baseUrl: Config.get().api.endpointPublic?.split("/api")[0] || "", // TODO: migrate database values to not include /api/v9 - apiVersions: { - default: Config.get().api.defaultVersion, - active: Config.get().api.activeVersions, - }, - }, - cdn: { - baseUrl: Config.get().cdn.endpointPublic, - }, - gateway: { - baseUrl: Config.get().gateway.endpointPublic, - encoding: [...(erlpackSupported ? ["etf"] : []), "json"], - compression: ["zstd-stream", "zlib-stream", null], - }, - admin: - Config.get().admin.endpointPublic === null - ? undefined - : { - baseUrl: Config.get().admin.endpointPublic, - }, - }); - }); + res.json({ + api: { + baseUrl: Config.get().api.endpointPublic?.split("/api")[0] || "", // TODO: migrate database values to not include /api/v9 + apiVersions: { + default: Config.get().api.defaultVersion, + active: Config.get().api.activeVersions, + }, + }, + cdn: { + baseUrl: Config.get().cdn.endpointPublic, + }, + gateway: { + baseUrl: Config.get().gateway.endpointPublic, + encoding: [...(erlpackSupported ? ["etf"] : []), "json"], + compression: ["zstd-stream", "zlib-stream", null], + }, + admin: + Config.get().admin.endpointPublic === null + ? undefined + : { + baseUrl: Config.get().admin.endpointPublic, + }, + }); + }); - this.app.use(ErrorHandler); + this.app.use(ErrorHandler); - ConnectionLoader.loadConnections(); + ConnectionLoader.loadConnections(); - if (logRequests) console.log(red(`Warning: Request logging is enabled! This will spam your console!\nTo disable this, unset the 'LOG_REQUESTS' environment variable!`)); + if (logRequests) console.log(red(`Warning: Request logging is enabled! This will spam your console!\nTo disable this, unset the 'LOG_REQUESTS' environment variable!`)); - return super.start(); - } + return super.start(); + } } diff --git a/src/api/middlewares/Authentication.ts b/src/api/middlewares/Authentication.ts
index 47f6ab37..078a566b 100644 --- a/src/api/middlewares/Authentication.ts +++ b/src/api/middlewares/Authentication.ts
@@ -21,112 +21,112 @@ import { NextFunction, Request, Response } from "express"; import { HTTPError } from "lambert-server"; export const NO_AUTHORIZATION_ROUTES = [ - // Authentication routes - "POST /auth/login", - "POST /auth/register", - "GET /auth/location-metadata", - "POST /auth/mfa/", - "POST /auth/verify", - "POST /auth/forgot", - "POST /auth/reset", - "POST /auth/fingerprint", - "GET /invites/", - // Routes with a seperate auth system - /^(POST|HEAD|GET|PATCH|DELETE) \/webhooks\/\d+\/\w+\/?/, // no token requires auth - /^POST \/interactions\/\d+\/[A-Za-z0-9_-]+\/callback/, - // Public information endpoints - "GET /ping", - "GET /gateway", - "GET /experiments", - "GET /updates", - "GET /download", - "GET /scheduled-maintenances/upcoming.json", - // Public kubernetes integration - "GET /-/readyz", - "GET /-/healthz", - // Client analytics - "POST /science", - "POST /track", - // Public policy pages - "GET /policies/instance/", - // Oauth callback - "/oauth2/callback", - // Asset delivery - /^(GET|HEAD) \/guilds\/\d+\/widget\.(json|png)/, - // Connections - /^(POST|HEAD) \/connections\/\w+\/callback/, - // Image proxy - /^(GET|HEAD) \/imageproxy\/[A-Za-z0-9+/]\/\d+x\d+\/.+/, + // Authentication routes + "POST /auth/login", + "POST /auth/register", + "GET /auth/location-metadata", + "POST /auth/mfa/", + "POST /auth/verify", + "POST /auth/forgot", + "POST /auth/reset", + "POST /auth/fingerprint", + "GET /invites/", + // Routes with a seperate auth system + /^(POST|HEAD|GET|PATCH|DELETE) \/webhooks\/\d+\/\w+\/?/, // no token requires auth + /^POST \/interactions\/\d+\/[A-Za-z0-9_-]+\/callback/, + // Public information endpoints + "GET /ping", + "GET /gateway", + "GET /experiments", + "GET /updates", + "GET /download", + "GET /scheduled-maintenances/upcoming.json", + // Public kubernetes integration + "GET /-/readyz", + "GET /-/healthz", + // Client analytics + "POST /science", + "POST /track", + // Public policy pages + "GET /policies/instance/", + // Oauth callback + "/oauth2/callback", + // Asset delivery + /^(GET|HEAD) \/guilds\/\d+\/widget\.(json|png)/, + // Connections + /^(POST|HEAD) \/connections\/\w+\/callback/, + // Image proxy + /^(GET|HEAD) \/imageproxy\/[A-Za-z0-9+/]\/\d+x\d+\/.+/, ]; export const API_PREFIX = /^\/api(\/v\d+)?/; export const API_PREFIX_TRAILING_SLASH = /^\/api(\/v\d+)?\//; declare global { - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace Express { - interface Request { - user_id: string; - user_bot: boolean; - token: { id: string; iat: number; ver?: number; did?: string }; - rights: Rights; - fingerprint?: string; - } - } + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Express { + interface Request { + user_id: string; + user_bot: boolean; + token: { id: string; iat: number; ver?: number; did?: string }; + rights: Rights; + fingerprint?: string; + } + } } export async function Authentication(req: Request, res: Response, next: NextFunction) { - if (req.method === "OPTIONS") return res.sendStatus(204); - const url = req.url.replace(API_PREFIX, ""); + if (req.method === "OPTIONS") return res.sendStatus(204); + const url = req.url.replace(API_PREFIX, ""); - if (req.headers.cookie?.split("; ").find((x) => x.startsWith("__sb_sessid="))) - req.fingerprint = req.headers.cookie - .split("; ") - .find((x) => x.startsWith("__sb_sessid="))! - .split("=")[1]; - // for some reason we need to require here, else the openapi generator fails with "route is not a function" - else res.setHeader("Set-Cookie", `__sb_sessid=${(req.fingerprint = (await require("../util")).randomString(32))}; Secure; HttpOnly; SameSite=None; Path=/`); + if (req.headers.cookie?.split("; ").find((x) => x.startsWith("__sb_sessid="))) + req.fingerprint = req.headers.cookie + .split("; ") + .find((x) => x.startsWith("__sb_sessid="))! + .split("=")[1]; + // for some reason we need to require here, else the openapi generator fails with "route is not a function" + else res.setHeader("Set-Cookie", `__sb_sessid=${(req.fingerprint = (await require("../util")).randomString(32))}; Secure; HttpOnly; SameSite=None; Path=/`); - if ( - NO_AUTHORIZATION_ROUTES.some((x) => { - if (typeof x !== "string") { - return x.test(req.method + " " + url); - } + if ( + NO_AUTHORIZATION_ROUTES.some((x) => { + if (typeof x !== "string") { + return x.test(req.method + " " + url); + } - const fullRoute = req.method + " " + url; + const fullRoute = req.method + " " + url; - if (req.method === "HEAD") { - const urlPart = x.split(" ").slice(1).join(" "); - if (urlPart.endsWith("/")) { - return url.startsWith(urlPart); - } else { - return url === urlPart; - } - } + if (req.method === "HEAD") { + const urlPart = x.split(" ").slice(1).join(" "); + if (urlPart.endsWith("/")) { + return url.startsWith(urlPart); + } else { + return url === urlPart; + } + } - if (x.endsWith("/")) { - return fullRoute.startsWith(x); - } else { - return fullRoute === x; - } - }) - ) - return next(); + if (x.endsWith("/")) { + return fullRoute.startsWith(x); + } else { + return fullRoute === x; + } + }) + ) + return next(); - if (!req.headers.authorization) return next(new HTTPError("Missing Authorization Header", 401)); + if (!req.headers.authorization) return next(new HTTPError("Missing Authorization Header", 401)); - try { - const { decoded, user, session, tokenVersion } = await checkToken(req.headers.authorization, { - ipAddress: req.ip, - fingerprint: req.fingerprint, - }); + try { + const { decoded, user, session, tokenVersion } = await checkToken(req.headers.authorization, { + ipAddress: req.ip, + fingerprint: req.fingerprint, + }); - req.token = decoded; - req.user_id = decoded.id; - req.user_bot = user.bot; - req.rights = new Rights(Number(user.rights)); - return next(); - } catch (error) { - return next(new HTTPError(error!.toString(), 400)); - } + req.token = decoded; + req.user_id = decoded.id; + req.user_bot = user.bot; + req.rights = new Rights(Number(user.rights)); + return next(); + } catch (error) { + return next(new HTTPError(error!.toString(), 400)); + } } diff --git a/src/api/middlewares/BodyParser.ts b/src/api/middlewares/BodyParser.ts
index e08aad49..a579abb3 100644 --- a/src/api/middlewares/BodyParser.ts +++ b/src/api/middlewares/BodyParser.ts
@@ -21,31 +21,31 @@ import { NextFunction, Request, Response } from "express"; import { HTTPError } from "lambert-server"; const errorMessages: { [key: string]: [string, number] } = { - "entity.too.large": ["Request body too large", 413], - "entity.parse.failed": ["Invalid JSON body", 400], - "entity.verify.failed": ["Entity verification failed", 403], - "request.aborted": ["Request aborted", 400], - "request.size.invalid": ["Request size did not match content length", 400], - "stream.encoding.set": ["Stream encoding should not be set", 500], - "stream.not.readable": ["Stream is not readable", 500], - "parameters.too.many": ["Too many parameters", 413], - "charset.unsupported": ["Unsupported charset", 415], - "encoding.unsupported": ["Unsupported content encoding", 415], + "entity.too.large": ["Request body too large", 413], + "entity.parse.failed": ["Invalid JSON body", 400], + "entity.verify.failed": ["Entity verification failed", 403], + "request.aborted": ["Request aborted", 400], + "request.size.invalid": ["Request size did not match content length", 400], + "stream.encoding.set": ["Stream encoding should not be set", 500], + "stream.not.readable": ["Stream is not readable", 500], + "parameters.too.many": ["Too many parameters", 413], + "charset.unsupported": ["Unsupported charset", 415], + "encoding.unsupported": ["Unsupported content encoding", 415], }; export function BodyParser(opts?: OptionsJson) { - const jsonParser = bodyParser.json(opts); + const jsonParser = bodyParser.json(opts); - return (req: Request, res: Response, next: NextFunction) => { - if (!req.headers["content-type"]) req.headers["content-type"] = "application/json"; + return (req: Request, res: Response, next: NextFunction) => { + if (!req.headers["content-type"]) req.headers["content-type"] = "application/json"; - jsonParser(req, res, (err) => { - if (err) { - const [message, status] = errorMessages[err.type] || ["Invalid Body", 400]; - const errorMessage = message.includes("charset") || message.includes("encoding") ? `${message} "${err.charset || err.encoding}"` : message; - return next(new HTTPError(errorMessage, status)); - } - next(); - }); - }; + jsonParser(req, res, (err) => { + if (err) { + const [message, status] = errorMessages[err.type] || ["Invalid Body", 400]; + const errorMessage = message.includes("charset") || message.includes("encoding") ? `${message} "${err.charset || err.encoding}"` : message; + return next(new HTTPError(errorMessage, status)); + } + next(); + }); + }; } diff --git a/src/api/middlewares/CORS.ts b/src/api/middlewares/CORS.ts
index 34532d3f..23186250 100644 --- a/src/api/middlewares/CORS.ts +++ b/src/api/middlewares/CORS.ts
@@ -21,20 +21,20 @@ import { NextFunction, Request, Response } from "express"; // TODO: config settings export function CORS(req: Request, res: Response, next: NextFunction) { - res.set("Access-Control-Allow-Credentials", "true"); - res.set("Access-Control-Allow-Headers", req.header("Access-Control-Request-Headers") || "*"); - res.set("Access-Control-Allow-Methods", req.header("Access-Control-Request-Method") || "*"); - res.set("Access-Control-Allow-Origin", req.header("Origin") ?? "*"); - res.set("Access-Control-Max-Age", "5"); // dont make it too long so we can change it dynamically - // TODO: use better CSP - res.set( - "Content-security-policy", - "default-src * data: blob: filesystem: about: ws: wss: 'unsafe-inline' 'unsafe-eval'; script-src * data: blob: 'unsafe-inline' 'unsafe-eval'; connect-src * data: blob: 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src * data: blob: ; style-src * data: blob: 'unsafe-inline'; font-src * data: blob: 'unsafe-inline';", - ); + res.set("Access-Control-Allow-Credentials", "true"); + res.set("Access-Control-Allow-Headers", req.header("Access-Control-Request-Headers") || "*"); + res.set("Access-Control-Allow-Methods", req.header("Access-Control-Request-Method") || "*"); + res.set("Access-Control-Allow-Origin", req.header("Origin") ?? "*"); + res.set("Access-Control-Max-Age", "5"); // dont make it too long so we can change it dynamically + // TODO: use better CSP + res.set( + "Content-security-policy", + "default-src * data: blob: filesystem: about: ws: wss: 'unsafe-inline' 'unsafe-eval'; script-src * data: blob: 'unsafe-inline' 'unsafe-eval'; connect-src * data: blob: 'unsafe-inline'; img-src * data: blob: 'unsafe-inline'; frame-src * data: blob: ; style-src * data: blob: 'unsafe-inline'; font-src * data: blob: 'unsafe-inline';", + ); - if (req.method === "OPTIONS") { - res.status(204).end(); - return; - } - next(); + if (req.method === "OPTIONS") { + res.status(204).end(); + return; + } + next(); } diff --git a/src/api/middlewares/ErrorHandler.ts b/src/api/middlewares/ErrorHandler.ts
index cd306c9c..1a9a18a8 100644 --- a/src/api/middlewares/ErrorHandler.ts +++ b/src/api/middlewares/ErrorHandler.ts
@@ -22,48 +22,48 @@ import { ApiError, FieldError } from "@spacebar/util"; const EntityNotFoundErrorRegex = /"(\w+)"/; export function ErrorHandler(error: Error & { type?: string }, req: Request, res: Response, next: NextFunction) { - if (!error) return next(); + if (!error) return next(); - try { - let code = 400; - let httpcode = code; - let message = error?.toString(); - let errors = undefined; - let _ajvErrors = undefined; + try { + let code = 400; + let httpcode = code; + let message = error?.toString(); + let errors = undefined; + let _ajvErrors = undefined; - if (error instanceof HTTPError && error.code) code = httpcode = error.code; - else if (error instanceof ApiError) { - code = error.code; - message = error.message; - httpcode = error.httpStatus; - } else if (error.name === "EntityNotFoundError") { - message = `${error.message.match(EntityNotFoundErrorRegex)?.[1] || "Item"} could not be found`; - code = httpcode = 404; - } else if (error instanceof FieldError) { - code = Number(error.code); - message = error.message; - errors = error.errors; - _ajvErrors = error._ajvErrors; - } else if (error?.type == "entity.parse.failed") { - // body-parser failed - httpcode = 400; - code = 50109; - message = "The request body contains invalid JSON."; - } else { - console.error(`[Error] ${code} ${req.url}\n`, errors || error, "\nbody:", req.body); + if (error instanceof HTTPError && error.code) code = httpcode = error.code; + else if (error instanceof ApiError) { + code = error.code; + message = error.message; + httpcode = error.httpStatus; + } else if (error.name === "EntityNotFoundError") { + message = `${error.message.match(EntityNotFoundErrorRegex)?.[1] || "Item"} could not be found`; + code = httpcode = 404; + } else if (error instanceof FieldError) { + code = Number(error.code); + message = error.message; + errors = error.errors; + _ajvErrors = error._ajvErrors; + } else if (error?.type == "entity.parse.failed") { + // body-parser failed + httpcode = 400; + code = 50109; + message = "The request body contains invalid JSON."; + } else { + console.error(`[Error] ${code} ${req.url}\n`, errors || error, "\nbody:", req.body); - if (req.server?.options?.production) { - // don't expose internal errors to the user, instead human errors should be thrown as HTTPError - message = "Internal Server Error"; - } - code = httpcode = 500; - } + if (req.server?.options?.production) { + // don't expose internal errors to the user, instead human errors should be thrown as HTTPError + message = "Internal Server Error"; + } + code = httpcode = 500; + } - if (httpcode > 511) httpcode = 400; + if (httpcode > 511) httpcode = 400; - res.status(httpcode).json({ code: code, message, errors, _ajvErrors }); - } catch (error) { - console.error(`[Internal Server Error] 500`, error); - return res.status(500).json({ code: 500, message: "Internal Server Error" }); - } + res.status(httpcode).json({ code: code, message, errors, _ajvErrors }); + } catch (error) { + console.error(`[Internal Server Error] 500`, error); + return res.status(500).json({ code: 500, message: "Internal Server Error" }); + } } diff --git a/src/api/middlewares/ImageProxy.ts b/src/api/middlewares/ImageProxy.ts
index 78690129..e950ea85 100644 --- a/src/api/middlewares/ImageProxy.ts +++ b/src/api/middlewares/ImageProxy.ts
@@ -25,9 +25,9 @@ let sharp: undefined | false | { default: typeof import("sharp") } = undefined; let Jimp: JimpType | undefined = undefined; try { - Jimp = require("jimp") as JimpType; + Jimp = require("jimp") as JimpType; } catch { - // empty + // empty } let sentImageProxyWarning = false; @@ -37,101 +37,101 @@ const jimpSupported = new Set(["image/jpeg", "image/png", "image/bmp", "image/ti const resizeSupported = new Set([...sharpSupported, ...jimpSupported]); export async function ImageProxy(req: Request, res: Response) { - const path = req.originalUrl.split("/").slice(2); + const path = req.originalUrl.split("/").slice(2); - // src/api/util/utility/EmbedHandlers.ts getProxyUrl - const hash = crypto.createHmac("sha1", Config.get().security.requestSignature).update(path.slice(1).join("/")).digest("base64").replace(/\+/g, "-").replace(/\//g, "_"); + // src/api/util/utility/EmbedHandlers.ts getProxyUrl + const hash = crypto.createHmac("sha1", Config.get().security.requestSignature).update(path.slice(1).join("/")).digest("base64").replace(/\+/g, "-").replace(/\//g, "_"); - try { - if (!crypto.timingSafeEqual(Buffer.from(hash), Buffer.from(path[0]))) throw new Error("Invalid signature"); - } catch { - console.log("[ImageProxy] Invalid signature, expected " + hash + " but got " + path[0]); - res.status(403).send("Invalid signature"); - return; - } + try { + if (!crypto.timingSafeEqual(Buffer.from(hash), Buffer.from(path[0]))) throw new Error("Invalid signature"); + } catch { + console.log("[ImageProxy] Invalid signature, expected " + hash + " but got " + path[0]); + res.status(403).send("Invalid signature"); + return; + } - const abort = new AbortController(); - setTimeout(() => abort.abort(), 5000); + const abort = new AbortController(); + setTimeout(() => abort.abort(), 5000); - const request = await fetch("https://" + path.slice(2).join("/"), { - headers: { - "User-Agent": "SpacebarImageProxy/1.0.0 (https://spacebar.chat)", - }, - signal: abort.signal, - }).catch((e) => { - if (e.name === "AbortError") res.status(504).send("Request timed out"); - else res.status(500).send("Unable to proxy origin: " + e.message); - }); - if (!request) return; + const request = await fetch("https://" + path.slice(2).join("/"), { + headers: { + "User-Agent": "SpacebarImageProxy/1.0.0 (https://spacebar.chat)", + }, + signal: abort.signal, + }).catch((e) => { + if (e.name === "AbortError") res.status(504).send("Request timed out"); + else res.status(500).send("Unable to proxy origin: " + e.message); + }); + if (!request) return; - if (request.status !== 200) { - res.status(request.status).send("Origin failed to respond: " + request.status + " " + request.statusText); - return; - } + if (request.status !== 200) { + res.status(request.status).send("Origin failed to respond: " + request.status + " " + request.statusText); + return; + } - if (!request.headers.get("Content-Type") || !request.headers.get("Content-Length")) { - res.status(500).send("Origin did not provide a Content-Type or Content-Length header"); - return; - } + if (!request.headers.get("Content-Type") || !request.headers.get("Content-Length")) { + res.status(500).send("Origin did not provide a Content-Type or Content-Length header"); + return; + } - // @ts-expect-error TS doesn't believe that the header cannot be null (it's checked for falsiness above) - if (parseInt(request.headers.get("Content-Length")) > 1024 * 1024 * 10) { - res.status(500).send("Origin provided a Content-Length header that is too large"); - return; - } + // @ts-expect-error TS doesn't believe that the header cannot be null (it's checked for falsiness above) + if (parseInt(request.headers.get("Content-Length")) > 1024 * 1024 * 10) { + res.status(500).send("Origin provided a Content-Length header that is too large"); + return; + } - // @ts-expect-error TS doesn't believe that the header cannot be null (it's checked for falsiness above) - let contentType: string = request.headers.get("Content-Type"); + // @ts-expect-error TS doesn't believe that the header cannot be null (it's checked for falsiness above) + let contentType: string = request.headers.get("Content-Type"); - const arrayBuffer = await request.arrayBuffer(); - let resultBuffer = Buffer.from(arrayBuffer); + const arrayBuffer = await request.arrayBuffer(); + let resultBuffer = Buffer.from(arrayBuffer); - if (!sentImageProxyWarning && resizeSupported.has(contentType) && /^\d+x\d+$/.test(path[1])) { - if (sharp !== false) { - try { - sharp = await import("sharp"); - } catch { - sharp = false; - } - } + if (!sentImageProxyWarning && resizeSupported.has(contentType) && /^\d+x\d+$/.test(path[1])) { + if (sharp !== false) { + try { + sharp = await import("sharp"); + } catch { + sharp = false; + } + } - if (sharp === false && !Jimp) { - try { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore Typings don't fit - Jimp = await import("jimp"); - } catch { - sentImageProxyWarning = true; - console.log(`[ImageProxy] ${yellow('Neither "sharp" or "jimp" NPM packages are installed, image resizing will be disabled')}`); - } - } + if (sharp === false && !Jimp) { + try { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore Typings don't fit + Jimp = await import("jimp"); + } catch { + sentImageProxyWarning = true; + console.log(`[ImageProxy] ${yellow('Neither "sharp" or "jimp" NPM packages are installed, image resizing will be disabled')}`); + } + } - const [width, height] = path[1].split("x").map((x) => parseInt(x)); + const [width, height] = path[1].split("x").map((x) => parseInt(x)); - const buffer = Buffer.from(arrayBuffer); - if (sharp && sharpSupported.has(contentType)) { - resultBuffer = Buffer.from( - await sharp - .default(buffer) - // Sharp doesn't support "scaleToFit" - .resize(width) - .toBuffer(), - ); - } else if (Jimp && jimpSupported.has(contentType)) { - resultBuffer = await Jimp.read(buffer).then((image) => { - contentType = image.getMIME(); - return ( - image - .scaleToFit(width, height) - // @ts-expect-error Jimp is defined at this point - .getBufferAsync(Jimp.AUTO) - ); - }); - } - } + const buffer = Buffer.from(arrayBuffer); + if (sharp && sharpSupported.has(contentType)) { + resultBuffer = Buffer.from( + await sharp + .default(buffer) + // Sharp doesn't support "scaleToFit" + .resize(width) + .toBuffer(), + ); + } else if (Jimp && jimpSupported.has(contentType)) { + resultBuffer = await Jimp.read(buffer).then((image) => { + contentType = image.getMIME(); + return ( + image + .scaleToFit(width, height) + // @ts-expect-error Jimp is defined at this point + .getBufferAsync(Jimp.AUTO) + ); + }); + } + } - res.header("Content-Type", contentType); - res.setHeader("Cache-Control", "public, max-age=" + Config.get().cdn.proxyCacheHeaderSeconds); + res.header("Content-Type", contentType); + res.setHeader("Cache-Control", "public, max-age=" + Config.get().cdn.proxyCacheHeaderSeconds); - res.send(resultBuffer); + res.send(resultBuffer); } diff --git a/src/api/middlewares/RateLimit.ts b/src/api/middlewares/RateLimit.ts
index 1a41ad4f..c749df6a 100644 --- a/src/api/middlewares/RateLimit.ts +++ b/src/api/middlewares/RateLimit.ts
@@ -34,188 +34,188 @@ TODO: different for methods (GET/POST) */ type RateLimit = { - id: "global" | "error" | string; - executor_id: string; - hits: number; - blocked: boolean; - expires_at: Date; + id: "global" | "error" | string; + executor_id: string; + hits: number; + blocked: boolean; + expires_at: Date; }; const Cache = new Map<string, RateLimit>(); const EventRateLimit = "RATELIMIT"; export default function rateLimit(opts: { - bucket?: string; - window: number; - count: number; - bot?: number; - webhook?: number; - oauth?: number; - GET?: number; - MODIFY?: number; - error?: boolean; - success?: boolean; - onlyIp?: boolean; + bucket?: string; + window: number; + count: number; + bot?: number; + webhook?: number; + oauth?: number; + GET?: number; + MODIFY?: number; + error?: boolean; + success?: boolean; + onlyIp?: boolean; }) { - return async (req: Request, res: Response, next: NextFunction) => { - // exempt user? if so, immediately short circuit - if (req.user_id) { - const rights = await getRights(req.user_id); - if (rights.has("BYPASS_RATE_LIMITS")) return next(); - } + return async (req: Request, res: Response, next: NextFunction) => { + // exempt user? if so, immediately short circuit + if (req.user_id) { + const rights = await getRights(req.user_id); + if (rights.has("BYPASS_RATE_LIMITS")) return next(); + } - const bucket_id = opts.bucket || req.originalUrl.replace(API_PREFIX_TRAILING_SLASH, ""); - let executor_id = req.ip || "127.0.0.1"; - if (!opts.onlyIp && req.user_id) executor_id = req.user_id; + const bucket_id = opts.bucket || req.originalUrl.replace(API_PREFIX_TRAILING_SLASH, ""); + let executor_id = req.ip || "127.0.0.1"; + if (!opts.onlyIp && req.user_id) executor_id = req.user_id; - let max_hits = opts.count; - if (opts.bot && req.user_bot) max_hits = opts.bot; - if (opts.GET && ["GET", "OPTIONS", "HEAD"].includes(req.method)) max_hits = opts.GET; - else if (opts.MODIFY && ["POST", "DELETE", "PATCH", "PUT"].includes(req.method)) max_hits = opts.MODIFY; + let max_hits = opts.count; + if (opts.bot && req.user_bot) max_hits = opts.bot; + if (opts.GET && ["GET", "OPTIONS", "HEAD"].includes(req.method)) max_hits = opts.GET; + else if (opts.MODIFY && ["POST", "DELETE", "PATCH", "PUT"].includes(req.method)) max_hits = opts.MODIFY; - const offender = Cache.get(executor_id + bucket_id); + const offender = Cache.get(executor_id + bucket_id); - res.set("X-RateLimit-Limit", `${max_hits}`) - .set("X-RateLimit-Remaining", `${max_hits - (offender?.hits || 0)}`) - .set("X-RateLimit-Bucket", `${bucket_id}`) - // assuming we aren't blocked, a new window will start after this request - .set("X-RateLimit-Reset", `${Date.now() + opts.window}`) - .set("X-RateLimit-Reset-After", `${opts.window}`); + res.set("X-RateLimit-Limit", `${max_hits}`) + .set("X-RateLimit-Remaining", `${max_hits - (offender?.hits || 0)}`) + .set("X-RateLimit-Bucket", `${bucket_id}`) + // assuming we aren't blocked, a new window will start after this request + .set("X-RateLimit-Reset", `${Date.now() + opts.window}`) + .set("X-RateLimit-Reset-After", `${opts.window}`); - if (offender) { - let reset = offender.expires_at.getTime(); - let resetAfterMs = reset - Date.now(); - let resetAfterSec = Math.ceil(resetAfterMs / 1000); + if (offender) { + let reset = offender.expires_at.getTime(); + let resetAfterMs = reset - Date.now(); + let resetAfterSec = Math.ceil(resetAfterMs / 1000); - if (resetAfterMs <= 0) { - offender.hits = 0; - offender.expires_at = new Date(Date.now() + opts.window * 1000); - offender.blocked = false; + if (resetAfterMs <= 0) { + offender.hits = 0; + offender.expires_at = new Date(Date.now() + opts.window * 1000); + offender.blocked = false; - Cache.delete(executor_id + bucket_id); - } + Cache.delete(executor_id + bucket_id); + } - res.set("X-RateLimit-Reset", `${reset}`); - res.set("X-RateLimit-Reset-After", `${Math.max(0, Math.ceil(resetAfterSec))}`); + res.set("X-RateLimit-Reset", `${reset}`); + res.set("X-RateLimit-Reset-After", `${Math.max(0, Math.ceil(resetAfterSec))}`); - if (offender.blocked) { - const global = bucket_id === "global"; - // each block violation pushes the expiry one full window further - reset += opts.window * 1000; - offender.expires_at = new Date(offender.expires_at.getTime() + opts.window * 1000); - resetAfterMs = reset - Date.now(); - resetAfterSec = Math.ceil(resetAfterMs / 1000); + if (offender.blocked) { + const global = bucket_id === "global"; + // each block violation pushes the expiry one full window further + reset += opts.window * 1000; + offender.expires_at = new Date(offender.expires_at.getTime() + opts.window * 1000); + resetAfterMs = reset - Date.now(); + resetAfterSec = Math.ceil(resetAfterMs / 1000); - console.log(`blocked bucket: ${bucket_id} ${executor_id}`, { - resetAfterMs, - }); + console.log(`blocked bucket: ${bucket_id} ${executor_id}`, { + resetAfterMs, + }); - if (global) res.set("X-RateLimit-Global", "true"); + if (global) res.set("X-RateLimit-Global", "true"); - return ( - res - .status(429) - .set("X-RateLimit-Remaining", "0") - .set("Retry-After", `${Math.max(0, Math.ceil(resetAfterSec))}`) - // TODO: error rate limit message translation - .send({ - message: "You are being rate limited.", - retry_after: resetAfterSec, - global, - }) - ); - } - } + return ( + res + .status(429) + .set("X-RateLimit-Remaining", "0") + .set("Retry-After", `${Math.max(0, Math.ceil(resetAfterSec))}`) + // TODO: error rate limit message translation + .send({ + message: "You are being rate limited.", + retry_after: resetAfterSec, + global, + }) + ); + } + } - next(); - const hitRouteOpts = { - bucket_id, - executor_id, - max_hits, - window: opts.window, - }; + next(); + const hitRouteOpts = { + bucket_id, + executor_id, + max_hits, + window: opts.window, + }; - if (opts.error || opts.success) { - res.once("finish", () => { - // check if error and increment error rate limit - if (res.statusCode >= 400 && opts.error) { - return hitRoute(hitRouteOpts); - } else if (res.statusCode >= 200 && res.statusCode < 300 && opts.success) { - return hitRoute(hitRouteOpts); - } - }); - } else { - return hitRoute(hitRouteOpts); - } - }; + if (opts.error || opts.success) { + res.once("finish", () => { + // check if error and increment error rate limit + if (res.statusCode >= 400 && opts.error) { + return hitRoute(hitRouteOpts); + } else if (res.statusCode >= 200 && res.statusCode < 300 && opts.success) { + return hitRoute(hitRouteOpts); + } + }); + } else { + return hitRoute(hitRouteOpts); + } + }; } export async function initRateLimits(app: Router) { - const { routes, global, ip, error, enabled } = Config.get().limits.rate; - if (!enabled) return; - console.log("Enabling rate limits..."); - await listenEvent(EventRateLimit, (event) => { - Cache.set(event.channel_id as string, event.data); - event.acknowledge?.(); - }); - // await RateLimit.delete({ expires_at: LessThan(new Date().toISOString()) }); // cleans up if not already deleted, morethan -> older date - // const limits = await RateLimit.find({ blocked: true }); - // limits.forEach((limit) => { - // Cache.set(limit.executor_id, limit); - // }); + const { routes, global, ip, error, enabled } = Config.get().limits.rate; + if (!enabled) return; + console.log("Enabling rate limits..."); + await listenEvent(EventRateLimit, (event) => { + Cache.set(event.channel_id as string, event.data); + event.acknowledge?.(); + }); + // await RateLimit.delete({ expires_at: LessThan(new Date().toISOString()) }); // cleans up if not already deleted, morethan -> older date + // const limits = await RateLimit.find({ blocked: true }); + // limits.forEach((limit) => { + // Cache.set(limit.executor_id, limit); + // }); - setInterval(() => { - Cache.forEach((x, key) => { - if (new Date() > x.expires_at) { - Cache.delete(key); - // RateLimit.delete({ executor_id: key }); - } - }); - }, 1000 * 60); + setInterval(() => { + Cache.forEach((x, key) => { + if (new Date() > x.expires_at) { + Cache.delete(key); + // RateLimit.delete({ executor_id: key }); + } + }); + }, 1000 * 60); - app.use( - rateLimit({ - bucket: "global", - onlyIp: true, - ...ip, - }), - ); - app.use(rateLimit({ bucket: "global", ...global })); - app.use( - rateLimit({ - bucket: "error", - error: true, - onlyIp: true, - ...error, - }), - ); - app.use("/guilds/:guild_id", rateLimit(routes.guild)); - app.use("/webhooks/:webhook_id", rateLimit(routes.webhook)); - app.use("/channels/:channel_id", rateLimit(routes.channel)); - app.use("/auth/login", rateLimit(routes.auth.login)); - app.use("/auth/register", rateLimit({ onlyIp: true, success: true, ...routes.auth.register })); + app.use( + rateLimit({ + bucket: "global", + onlyIp: true, + ...ip, + }), + ); + app.use(rateLimit({ bucket: "global", ...global })); + app.use( + rateLimit({ + bucket: "error", + error: true, + onlyIp: true, + ...error, + }), + ); + app.use("/guilds/:guild_id", rateLimit(routes.guild)); + app.use("/webhooks/:webhook_id", rateLimit(routes.webhook)); + app.use("/channels/:channel_id", rateLimit(routes.channel)); + app.use("/auth/login", rateLimit(routes.auth.login)); + app.use("/auth/register", rateLimit({ onlyIp: true, success: true, ...routes.auth.register })); } async function hitRoute(opts: { executor_id: string; bucket_id: string; max_hits: number; window: number }) { - const id = opts.executor_id + opts.bucket_id; - let limit = Cache.get(id); - if (!limit) { - limit = { - id: opts.bucket_id, - executor_id: opts.executor_id, - expires_at: new Date(Date.now() + opts.window * 1000), - hits: 0, - blocked: false, - }; - Cache.set(id, limit); - } + const id = opts.executor_id + opts.bucket_id; + let limit = Cache.get(id); + if (!limit) { + limit = { + id: opts.bucket_id, + executor_id: opts.executor_id, + expires_at: new Date(Date.now() + opts.window * 1000), + hits: 0, + blocked: false, + }; + Cache.set(id, limit); + } - limit.hits++; - if (limit.hits >= opts.max_hits) { - limit.blocked = true; - } + limit.hits++; + if (limit.hits >= opts.max_hits) { + limit.blocked = true; + } - /* + /* let ratelimit = await RateLimit.findOne({ where: { id: opts.bucket_id, executor_id: opts.executor_id } }); if (!ratelimit) { ratelimit = new RateLimit({ diff --git a/src/api/middlewares/Translation.ts b/src/api/middlewares/Translation.ts
index e089407e..3ca204b6 100644 --- a/src/api/middlewares/Translation.ts +++ b/src/api/middlewares/Translation.ts
@@ -26,23 +26,23 @@ import { Router } from "express"; const ASSET_FOLDER_PATH = path.join(__dirname, "..", "..", "..", "assets"); export async function initTranslation(router: Router) { - const languages = fs.readdirSync(path.join(ASSET_FOLDER_PATH, "locales")); - const namespaces = fs.readdirSync(path.join(ASSET_FOLDER_PATH, "locales", "en")); - const ns = namespaces.filter((x) => x.endsWith(".json")).map((x) => x.slice(0, x.length - 5)); + const languages = fs.readdirSync(path.join(ASSET_FOLDER_PATH, "locales")); + const namespaces = fs.readdirSync(path.join(ASSET_FOLDER_PATH, "locales", "en")); + const ns = namespaces.filter((x) => x.endsWith(".json")).map((x) => x.slice(0, x.length - 5)); - await i18next - .use(i18nextBackend) - .use(i18nextMiddleware.LanguageDetector) - .init({ - preload: languages, - // debug: true, - fallbackLng: "en", - ns, - backend: { - loadPath: path.join(ASSET_FOLDER_PATH, "locales") + "/{{lng}}/{{ns}}.json", - }, - load: "all", - }); + await i18next + .use(i18nextBackend) + .use(i18nextMiddleware.LanguageDetector) + .init({ + preload: languages, + // debug: true, + fallbackLng: "en", + ns, + backend: { + loadPath: path.join(ASSET_FOLDER_PATH, "locales") + "/{{lng}}/{{ns}}.json", + }, + load: "all", + }); - router.use(i18nextMiddleware.handle(i18next, {})); + router.use(i18nextMiddleware.handle(i18next, {})); } diff --git a/src/api/routes/-/healthz.ts b/src/api/routes/-/healthz.ts
index 5acd8c8c..886473cf 100644 --- a/src/api/routes/-/healthz.ts +++ b/src/api/routes/-/healthz.ts
@@ -23,9 +23,9 @@ import { getDatabase } from "@spacebar/util"; const router = Router({ mergeParams: true }); router.get("/", route({}), (req: Request, res: Response) => { - if (!getDatabase()) return res.sendStatus(503); + if (!getDatabase()) return res.sendStatus(503); - return res.sendStatus(200); + return res.sendStatus(200); }); export default router; diff --git a/src/api/routes/-/readyz.ts b/src/api/routes/-/readyz.ts
index 5acd8c8c..886473cf 100644 --- a/src/api/routes/-/readyz.ts +++ b/src/api/routes/-/readyz.ts
@@ -23,9 +23,9 @@ import { getDatabase } from "@spacebar/util"; const router = Router({ mergeParams: true }); router.get("/", route({}), (req: Request, res: Response) => { - if (!getDatabase()) return res.sendStatus(503); + if (!getDatabase()) return res.sendStatus(503); - return res.sendStatus(200); + return res.sendStatus(200); }); export default router; diff --git a/src/api/routes/applications/#application_id/bot/index.ts b/src/api/routes/applications/#application_id/bot/index.ts
index ad491840..83b4fc61 100644 --- a/src/api/routes/applications/#application_id/bot/index.ts +++ b/src/api/routes/applications/#application_id/bot/index.ts
@@ -26,98 +26,98 @@ import { BotModifySchema } from "@spacebar/schemas"; const router: Router = Router({ mergeParams: true }); router.post( - "/", - route({ - responses: { - 204: { - body: "TokenOnlyResponse", - }, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const app = await Application.findOneOrFail({ - where: { id: req.params.application_id }, - relations: ["owner"], - }); + "/", + route({ + responses: { + 204: { + body: "TokenOnlyResponse", + }, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const app = await Application.findOneOrFail({ + where: { id: req.params.application_id }, + relations: ["owner"], + }); - if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION; + if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION; - const user = await createAppBotUser(app, req); + const user = await createAppBotUser(app, req); - res.send({ - token: await generateToken(user.id), - }); - }, + res.send({ + token: await generateToken(user.id), + }); + }, ); router.post( - "/reset", - route({ - responses: { - 200: { - body: "TokenResponse", - }, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const bot = await User.findOneOrFail({ where: { id: req.params.application_id } }); - const owner = await User.findOneOrFail({ where: { id: req.user_id } }); + "/reset", + route({ + responses: { + 200: { + body: "TokenResponse", + }, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const bot = await User.findOneOrFail({ where: { id: req.params.application_id } }); + const owner = await User.findOneOrFail({ where: { id: req.user_id } }); - if (owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION; + if (owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION; - if (owner.totp_secret && (!req.body.code || verifyToken(owner.totp_secret, req.body.code))) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); + if (owner.totp_secret && (!req.body.code || verifyToken(owner.totp_secret, req.body.code))) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); - bot.data = { hash: undefined, valid_tokens_since: new Date() }; + bot.data = { hash: undefined, valid_tokens_since: new Date() }; - await bot.save(); + await bot.save(); - const token = await generateToken(bot.id); + const token = await generateToken(bot.id); - res.json({ token }).status(200); - }, + res.json({ token }).status(200); + }, ); router.patch( - "/", - route({ - requestBody: "BotModifySchema", - responses: { - 200: { - body: "Application", - }, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const body = req.body as BotModifySchema; - if (!body.avatar?.trim()) delete body.avatar; + "/", + route({ + requestBody: "BotModifySchema", + responses: { + 200: { + body: "Application", + }, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const body = req.body as BotModifySchema; + if (!body.avatar?.trim()) delete body.avatar; - const app = await Application.findOneOrFail({ - where: { id: req.params.application_id }, - relations: ["bot", "owner"], - }); + const app = await Application.findOneOrFail({ + where: { id: req.params.application_id }, + relations: ["bot", "owner"], + }); - if (!app.bot) throw DiscordApiErrors.BOT_ONLY_ENDPOINT; + if (!app.bot) throw DiscordApiErrors.BOT_ONLY_ENDPOINT; - if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION; + if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION; - if (body.avatar) body.avatar = await handleFile(`/avatars/${app.id}`, body.avatar as string); + if (body.avatar) body.avatar = await handleFile(`/avatars/${app.id}`, body.avatar as string); - app.bot.assign(body); + app.bot.assign(body); - app.bot.save(); + app.bot.save(); - await app.save(); - res.json(app).status(200); - }, + await app.save(); + res.json(app).status(200); + }, ); export default router; diff --git a/src/api/routes/applications/#application_id/commands/#command_id/index.ts b/src/api/routes/applications/#application_id/commands/#command_id/index.ts
index 7907c1ba..8e4c3ce9 100644 --- a/src/api/routes/applications/#application_id/commands/#command_id/index.ts +++ b/src/api/routes/applications/#application_id/commands/#command_id/index.ts
@@ -24,99 +24,99 @@ import { Application, ApplicationCommand, FieldErrors, Snowflake } from "@spaceb const router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); + const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); - if (!applicationExists) { - res.status(404).send({ code: 404, message: "Unknown application" }); - return; - } + if (!applicationExists) { + res.status(404).send({ code: 404, message: "Unknown application" }); + return; + } - const command = await ApplicationCommand.findOne({ where: { application_id: req.params.application_id, id: req.params.command_id } }); + const command = await ApplicationCommand.findOne({ where: { application_id: req.params.application_id, id: req.params.command_id } }); - if (!command) { - res.status(404).send({ code: 404, message: "Unknown application command" }); - return; - } + if (!command) { + res.status(404).send({ code: 404, message: "Unknown application command" }); + return; + } - res.send(command); + res.send(command); }); router.patch( - "/", - route({ - requestBody: "ApplicationCommandCreateSchema", - }), - async (req: Request, res: Response) => { - const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); + "/", + route({ + requestBody: "ApplicationCommandCreateSchema", + }), + async (req: Request, res: Response) => { + const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); - if (!applicationExists) { - res.status(404).send({ code: 404, message: "Unknown application" }); - return; - } + if (!applicationExists) { + res.status(404).send({ code: 404, message: "Unknown application" }); + return; + } - const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, id: req.params.command_id } }); + const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, id: req.params.command_id } }); - if (!commandExists) { - res.status(404).send({ code: 404, message: "Unknown application command" }); - return; - } + if (!commandExists) { + res.status(404).send({ code: 404, message: "Unknown application command" }); + return; + } - const body = req.body as ApplicationCommandCreateSchema; + const body = req.body as ApplicationCommandCreateSchema; - if (!body.type) { - body.type = 1; - } + if (!body.type) { + body.type = 1; + } - if (body.name.trim().length < 1 || body.name.trim().length > 32) { - // TODO: configurable? - throw FieldErrors({ - name: { - code: "BASE_TYPE_BAD_LENGTH", - message: `Must be between 1 and 32 in length.`, - }, - }); - } + if (body.name.trim().length < 1 || body.name.trim().length > 32) { + // TODO: configurable? + throw FieldErrors({ + name: { + code: "BASE_TYPE_BAD_LENGTH", + message: `Must be between 1 and 32 in length.`, + }, + }); + } - const commandForDb: ApplicationCommandSchema = { - application_id: req.params.application_id, - name: body.name.trim(), - name_localizations: body.name_localizations, - description: body.description?.trim() || "", - description_localizations: body.description_localizations, - default_member_permissions: body.default_member_permissions || null, - contexts: body.contexts, - dm_permission: body.dm_permission || true, - global_popularity_rank: 1, - handler: body.handler, - integration_types: body.integration_types, - nsfw: body.nsfw, - options: body.options, - type: body.type, - version: Snowflake.generate(), - }; + const commandForDb: ApplicationCommandSchema = { + application_id: req.params.application_id, + name: body.name.trim(), + name_localizations: body.name_localizations, + description: body.description?.trim() || "", + description_localizations: body.description_localizations, + default_member_permissions: body.default_member_permissions || null, + contexts: body.contexts, + dm_permission: body.dm_permission || true, + global_popularity_rank: 1, + handler: body.handler, + integration_types: body.integration_types, + nsfw: body.nsfw, + options: body.options, + type: body.type, + version: Snowflake.generate(), + }; - await ApplicationCommand.update({ name: body.name.trim() }, commandForDb); - res.send(commandForDb); - }, + await ApplicationCommand.update({ name: body.name.trim() }, commandForDb); + res.send(commandForDb); + }, ); router.delete("/", async (req: Request, res: Response) => { - const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); + const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); - if (!applicationExists) { - res.status(404).send({ code: 404, message: "Unknown application" }); - return; - } + if (!applicationExists) { + res.status(404).send({ code: 404, message: "Unknown application" }); + return; + } - const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, id: req.params.command_id } }); + const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, id: req.params.command_id } }); - if (!commandExists) { - res.status(404).send({ code: 404, message: "Unknown application command" }); - return; - } + if (!commandExists) { + res.status(404).send({ code: 404, message: "Unknown application command" }); + return; + } - await ApplicationCommand.delete({ application_id: req.params.application_id, id: req.params.command_id }); - res.sendStatus(204); + await ApplicationCommand.delete({ application_id: req.params.application_id, id: req.params.command_id }); + res.sendStatus(204); }); export default router; diff --git a/src/api/routes/applications/#application_id/commands/index.ts b/src/api/routes/applications/#application_id/commands/index.ts
index 97753f00..91824be8 100644 --- a/src/api/routes/applications/#application_id/commands/index.ts +++ b/src/api/routes/applications/#application_id/commands/index.ts
@@ -25,147 +25,147 @@ import { IsNull } from "typeorm"; const router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); + const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); - if (!applicationExists) { - res.status(404).send({ code: 404, message: "Unknown application" }); - return; - } + if (!applicationExists) { + res.status(404).send({ code: 404, message: "Unknown application" }); + return; + } - const command = await ApplicationCommand.find({ where: { application_id: req.params.application_id } }); - res.send(command); + const command = await ApplicationCommand.find({ where: { application_id: req.params.application_id } }); + res.send(command); }); router.post( - "/", - route({ - requestBody: "ApplicationCommandCreateSchema", - }), - async (req: Request, res: Response) => { - const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); + "/", + route({ + requestBody: "ApplicationCommandCreateSchema", + }), + async (req: Request, res: Response) => { + const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); - if (!applicationExists) { - res.status(404).send({ code: 404, message: "Unknown application" }); - return; - } + if (!applicationExists) { + res.status(404).send({ code: 404, message: "Unknown application" }); + return; + } - const body = req.body as ApplicationCommandCreateSchema; + const body = req.body as ApplicationCommandCreateSchema; - if (!body.type) { - body.type = 1; - } + if (!body.type) { + body.type = 1; + } - if (body.name.trim().length < 1 || body.name.trim().length > 32) { - // TODO: configurable? - throw FieldErrors({ - name: { - code: "BASE_TYPE_BAD_LENGTH", - message: `Must be between 1 and 32 in length.`, - }, - }); - } + if (body.name.trim().length < 1 || body.name.trim().length > 32) { + // TODO: configurable? + throw FieldErrors({ + name: { + code: "BASE_TYPE_BAD_LENGTH", + message: `Must be between 1 and 32 in length.`, + }, + }); + } - const commandForDb: ApplicationCommandSchema = { - application_id: req.params.application_id, - name: body.name.trim(), - name_localizations: body.name_localizations, - description: body.description?.trim() || "", - description_localizations: body.description_localizations, - default_member_permissions: body.default_member_permissions || null, - contexts: body.contexts, - dm_permission: body.dm_permission || true, - global_popularity_rank: 1, - handler: body.handler, - integration_types: body.integration_types, - nsfw: body.nsfw, - options: body.options, - type: body.type, - version: Snowflake.generate(), - }; + const commandForDb: ApplicationCommandSchema = { + application_id: req.params.application_id, + name: body.name.trim(), + name_localizations: body.name_localizations, + description: body.description?.trim() || "", + description_localizations: body.description_localizations, + default_member_permissions: body.default_member_permissions || null, + contexts: body.contexts, + dm_permission: body.dm_permission || true, + global_popularity_rank: 1, + handler: body.handler, + integration_types: body.integration_types, + nsfw: body.nsfw, + options: body.options, + type: body.type, + version: Snowflake.generate(), + }; - const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, name: body.name.trim() } }); + const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, name: body.name.trim() } }); - if (commandExists) { - await ApplicationCommand.update({ application_id: req.params.application_id, name: body.name.trim() }, commandForDb); - } else { - commandForDb.id = Snowflake.generate(); // Have to be done that way so the id doesn't change - await ApplicationCommand.save(commandForDb); - } + if (commandExists) { + await ApplicationCommand.update({ application_id: req.params.application_id, name: body.name.trim() }, commandForDb); + } else { + commandForDb.id = Snowflake.generate(); // Have to be done that way so the id doesn't change + await ApplicationCommand.save(commandForDb); + } - res.send(body); - }, + res.send(body); + }, ); router.put( - "/", - route({ - requestBody: "BulkApplicationCommandCreateSchema", - }), - async (req: Request, res: Response) => { - const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); + "/", + route({ + requestBody: "BulkApplicationCommandCreateSchema", + }), + async (req: Request, res: Response) => { + const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); - if (!applicationExists) { - res.status(404).send({ code: 404, message: "Unknown application" }); - return; - } + if (!applicationExists) { + res.status(404).send({ code: 404, message: "Unknown application" }); + return; + } - const body = req.body as ApplicationCommandCreateSchema[]; + const body = req.body as ApplicationCommandCreateSchema[]; - // Remove commands not present in array - const applicationCommands = await ApplicationCommand.find({ where: { application_id: req.params.application_id, guild_id: IsNull() } }); + // Remove commands not present in array + const applicationCommands = await ApplicationCommand.find({ where: { application_id: req.params.application_id, guild_id: IsNull() } }); - const commandNamesInArray = body.map((c) => c.name); - const commandsNotInArray = applicationCommands.filter((c) => !commandNamesInArray.includes(c.name)); + const commandNamesInArray = body.map((c) => c.name); + const commandsNotInArray = applicationCommands.filter((c) => !commandNamesInArray.includes(c.name)); - for (const command of commandsNotInArray) { - await ApplicationCommand.delete({ application_id: req.params.application_id, guild_id: IsNull(), id: command.id }); - } + for (const command of commandsNotInArray) { + await ApplicationCommand.delete({ application_id: req.params.application_id, guild_id: IsNull(), id: command.id }); + } - for (const command of body) { - if (!command.type) { - command.type = 1; - } + for (const command of body) { + if (!command.type) { + command.type = 1; + } - if (command.name.trim().length < 1 || command.name.trim().length > 32) { - // TODO: configurable? - throw FieldErrors({ - name: { - code: "BASE_TYPE_BAD_LENGTH", - message: `Must be between 1 and 32 in length.`, - }, - }); - } + if (command.name.trim().length < 1 || command.name.trim().length > 32) { + // TODO: configurable? + throw FieldErrors({ + name: { + code: "BASE_TYPE_BAD_LENGTH", + message: `Must be between 1 and 32 in length.`, + }, + }); + } - const commandForDb: ApplicationCommandSchema = { - application_id: req.params.application_id, - name: command.name.trim(), - name_localizations: command.name_localizations, - description: command.description?.trim() || "", - description_localizations: command.description_localizations, - default_member_permissions: command.default_member_permissions || null, - contexts: command.contexts, - dm_permission: command.dm_permission || true, - global_popularity_rank: 1, - handler: command.handler, - integration_types: command.integration_types, - nsfw: command.nsfw, - options: command.options, - type: command.type, - version: Snowflake.generate(), - }; + const commandForDb: ApplicationCommandSchema = { + application_id: req.params.application_id, + name: command.name.trim(), + name_localizations: command.name_localizations, + description: command.description?.trim() || "", + description_localizations: command.description_localizations, + default_member_permissions: command.default_member_permissions || null, + contexts: command.contexts, + dm_permission: command.dm_permission || true, + global_popularity_rank: 1, + handler: command.handler, + integration_types: command.integration_types, + nsfw: command.nsfw, + options: command.options, + type: command.type, + version: Snowflake.generate(), + }; - const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, name: command.name.trim() } }); + const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, name: command.name.trim() } }); - if (commandExists) { - await ApplicationCommand.update({ application_id: req.params.application_id, name: command.name.trim() }, commandForDb); - } else { - commandForDb.id = Snowflake.generate(); // Have to be done that way so the id doesn't change - await ApplicationCommand.save(commandForDb); - } - } + if (commandExists) { + await ApplicationCommand.update({ application_id: req.params.application_id, name: command.name.trim() }, commandForDb); + } else { + commandForDb.id = Snowflake.generate(); // Have to be done that way so the id doesn't change + await ApplicationCommand.save(commandForDb); + } + } - res.send(body); - }, + res.send(body); + }, ); export default router; diff --git a/src/api/routes/applications/#application_id/entitlements.ts b/src/api/routes/applications/#application_id/entitlements.ts
index 63a7e7b9..4cd13b93 100644 --- a/src/api/routes/applications/#application_id/entitlements.ts +++ b/src/api/routes/applications/#application_id/entitlements.ts
@@ -22,19 +22,19 @@ import { Request, Response, Router } from "express"; const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "ApplicationEntitlementsResponse", - }, - }, - }), - (req: Request, res: Response) => { - // TODO: - //const { exclude_consumed } = req.query; - res.status(200).send([]); - }, + "/", + route({ + responses: { + 200: { + body: "ApplicationEntitlementsResponse", + }, + }, + }), + (req: Request, res: Response) => { + // TODO: + //const { exclude_consumed } = req.query; + res.status(200).send([]); + }, ); export default router; diff --git a/src/api/routes/applications/#application_id/guilds/#guild_id/commands/#command_id/index.ts b/src/api/routes/applications/#application_id/guilds/#guild_id/commands/#command_id/index.ts
index 2570358d..be2d62e7 100644 --- a/src/api/routes/applications/#application_id/guilds/#guild_id/commands/#command_id/index.ts +++ b/src/api/routes/applications/#application_id/guilds/#guild_id/commands/#command_id/index.ts
@@ -24,140 +24,140 @@ import { Application, ApplicationCommand, FieldErrors, Guild, Member, Snowflake const router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); + const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); - if (!applicationExists) { - res.status(404).send({ code: 404, message: "Unknown application" }); - return; - } + if (!applicationExists) { + res.status(404).send({ code: 404, message: "Unknown application" }); + return; + } - const guildExists = await Guild.exists({ where: { id: req.params.guild_id } }); + const guildExists = await Guild.exists({ where: { id: req.params.guild_id } }); - if (!guildExists) { - res.status(404).send({ code: 404, message: "Unknown Server" }); - return; - } + if (!guildExists) { + res.status(404).send({ code: 404, message: "Unknown Server" }); + return; + } - if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) { - res.status(401).send({ code: 401, message: "Missing Access" }); - return; - } + if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) { + res.status(401).send({ code: 401, message: "Missing Access" }); + return; + } - const command = await ApplicationCommand.findOne({ where: { application_id: req.params.application_id, id: req.params.command_id, guild_id: req.params.guild_id } }); + const command = await ApplicationCommand.findOne({ where: { application_id: req.params.application_id, id: req.params.command_id, guild_id: req.params.guild_id } }); - if (!command) { - res.status(404).send({ code: 404, message: "Unknown application command" }); - return; - } + if (!command) { + res.status(404).send({ code: 404, message: "Unknown application command" }); + return; + } - res.send(command); + res.send(command); }); router.patch( - "/", - route({ - requestBody: "ApplicationCommandCreateSchema", - }), - async (req: Request, res: Response) => { - const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); + "/", + route({ + requestBody: "ApplicationCommandCreateSchema", + }), + async (req: Request, res: Response) => { + const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); - if (!applicationExists) { - res.status(404).send({ code: 404, message: "Unknown application" }); - return; - } + if (!applicationExists) { + res.status(404).send({ code: 404, message: "Unknown application" }); + return; + } - const guildExists = await Guild.exists({ where: { id: req.params.guild_id } }); + const guildExists = await Guild.exists({ where: { id: req.params.guild_id } }); - if (!guildExists) { - res.status(404).send({ code: 404, message: "Unknown Server" }); - return; - } + if (!guildExists) { + res.status(404).send({ code: 404, message: "Unknown Server" }); + return; + } - if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) { - res.status(401).send({ code: 401, message: "Missing Access" }); - return; - } + if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) { + res.status(401).send({ code: 401, message: "Missing Access" }); + return; + } - const body = req.body as ApplicationCommandCreateSchema; + const body = req.body as ApplicationCommandCreateSchema; - if (!body.type) { - body.type = 1; - } + if (!body.type) { + body.type = 1; + } - if (body.name.trim().length < 1 || body.name.trim().length > 32) { - // TODO: configurable? - throw FieldErrors({ - name: { - code: "BASE_TYPE_BAD_LENGTH", - message: `Must be between 1 and 32 in length.`, - }, - }); - } + if (body.name.trim().length < 1 || body.name.trim().length > 32) { + // TODO: configurable? + throw FieldErrors({ + name: { + code: "BASE_TYPE_BAD_LENGTH", + message: `Must be between 1 and 32 in length.`, + }, + }); + } - const commandForDb: ApplicationCommandSchema = { - application_id: req.params.application_id, - name: body.name.trim(), - name_localizations: body.name_localizations, - description: body.description?.trim() || "", - description_localizations: body.description_localizations, - default_member_permissions: body.default_member_permissions || null, - contexts: body.contexts, - dm_permission: body.dm_permission || true, - global_popularity_rank: 1, - handler: body.handler, - integration_types: body.integration_types, - nsfw: body.nsfw, - options: body.options, - type: body.type, - version: Snowflake.generate(), - }; + const commandForDb: ApplicationCommandSchema = { + application_id: req.params.application_id, + name: body.name.trim(), + name_localizations: body.name_localizations, + description: body.description?.trim() || "", + description_localizations: body.description_localizations, + default_member_permissions: body.default_member_permissions || null, + contexts: body.contexts, + dm_permission: body.dm_permission || true, + global_popularity_rank: 1, + handler: body.handler, + integration_types: body.integration_types, + nsfw: body.nsfw, + options: body.options, + type: body.type, + version: Snowflake.generate(), + }; - const commandExists = await ApplicationCommand.exists({ - where: { application_id: req.params.application_id, guild_id: req.params.guild_id, id: req.params.command_id, name: body.name.trim() }, - }); + const commandExists = await ApplicationCommand.exists({ + where: { application_id: req.params.application_id, guild_id: req.params.guild_id, id: req.params.command_id, name: body.name.trim() }, + }); - if (!commandExists) { - res.status(404).send({ code: 404, message: "Unknown application command" }); - return; - } + if (!commandExists) { + res.status(404).send({ code: 404, message: "Unknown application command" }); + return; + } - await ApplicationCommand.update( - { application_id: req.params.application_id, guild_id: req.params.guild_id, id: req.params.command_id, name: body.name.trim() }, - commandForDb, - ); - res.send(commandForDb); - }, + await ApplicationCommand.update( + { application_id: req.params.application_id, guild_id: req.params.guild_id, id: req.params.command_id, name: body.name.trim() }, + commandForDb, + ); + res.send(commandForDb); + }, ); router.delete("/", async (req: Request, res: Response) => { - const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); + const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); - if (!applicationExists) { - res.status(404).send({ code: 404, message: "Unknown application" }); - return; - } + if (!applicationExists) { + res.status(404).send({ code: 404, message: "Unknown application" }); + return; + } - const guildExists = await Guild.exists({ where: { id: req.params.guild_id } }); + const guildExists = await Guild.exists({ where: { id: req.params.guild_id } }); - if (!guildExists) { - res.status(404).send({ code: 404, message: "Unknown Server" }); - return; - } + if (!guildExists) { + res.status(404).send({ code: 404, message: "Unknown Server" }); + return; + } - if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) { - res.status(401).send({ code: 401, message: "Missing Access" }); - return; - } + if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) { + res.status(401).send({ code: 401, message: "Missing Access" }); + return; + } - const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id, id: req.params.command_id } }); + const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id, id: req.params.command_id } }); - if (!commandExists) { - res.status(404).send({ code: 404, message: "Unknown application command" }); - return; - } + if (!commandExists) { + res.status(404).send({ code: 404, message: "Unknown application command" }); + return; + } - await ApplicationCommand.delete({ application_id: req.params.application_id, guild_id: req.params.guild_id, id: req.params.command_id }); - res.sendStatus(204); + await ApplicationCommand.delete({ application_id: req.params.application_id, guild_id: req.params.guild_id, id: req.params.command_id }); + res.sendStatus(204); }); export default router; diff --git a/src/api/routes/applications/#application_id/guilds/#guild_id/commands/index.ts b/src/api/routes/applications/#application_id/guilds/#guild_id/commands/index.ts
index 1639e7be..607c846c 100644 --- a/src/api/routes/applications/#application_id/guilds/#guild_id/commands/index.ts +++ b/src/api/routes/applications/#application_id/guilds/#guild_id/commands/index.ts
@@ -24,185 +24,185 @@ import { Application, ApplicationCommand, FieldErrors, Guild, Member, Snowflake const router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); + const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); - if (!applicationExists) { - res.status(404).send({ code: 404, message: "Unknown application" }); - return; - } + if (!applicationExists) { + res.status(404).send({ code: 404, message: "Unknown application" }); + return; + } - const guildExists = await Guild.exists({ where: { id: req.params.guild_id } }); + const guildExists = await Guild.exists({ where: { id: req.params.guild_id } }); - if (!guildExists) { - res.status(404).send({ code: 404, message: "Unknown Server" }); - return; - } + if (!guildExists) { + res.status(404).send({ code: 404, message: "Unknown Server" }); + return; + } - if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) { - res.status(401).send({ code: 401, message: "Missing Access" }); - return; - } + if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) { + res.status(401).send({ code: 401, message: "Missing Access" }); + return; + } - const command = await ApplicationCommand.find({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id } }); - res.send(command); + const command = await ApplicationCommand.find({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id } }); + res.send(command); }); router.post( - "/", - route({ - requestBody: "ApplicationCommandCreateSchema", - }), - async (req: Request, res: Response) => { - const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); + "/", + route({ + requestBody: "ApplicationCommandCreateSchema", + }), + async (req: Request, res: Response) => { + const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); - if (!applicationExists) { - res.status(404).send({ code: 404, message: "Unknown application" }); - return; - } + if (!applicationExists) { + res.status(404).send({ code: 404, message: "Unknown application" }); + return; + } - const guildExists = await Guild.exists({ where: { id: req.params.guild_id } }); + const guildExists = await Guild.exists({ where: { id: req.params.guild_id } }); - if (!guildExists) { - res.status(404).send({ code: 404, message: "Unknown Server" }); - return; - } + if (!guildExists) { + res.status(404).send({ code: 404, message: "Unknown Server" }); + return; + } - if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) { - res.status(401).send({ code: 401, message: "Missing Access" }); - return; - } + if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) { + res.status(401).send({ code: 401, message: "Missing Access" }); + return; + } - const body = req.body as ApplicationCommandCreateSchema; + const body = req.body as ApplicationCommandCreateSchema; - if (!body.type) { - body.type = 1; - } + if (!body.type) { + body.type = 1; + } - if (body.name.trim().length < 1 || body.name.trim().length > 32) { - // TODO: configurable? - throw FieldErrors({ - name: { - code: "BASE_TYPE_BAD_LENGTH", - message: `Must be between 1 and 32 in length.`, - }, - }); - } + if (body.name.trim().length < 1 || body.name.trim().length > 32) { + // TODO: configurable? + throw FieldErrors({ + name: { + code: "BASE_TYPE_BAD_LENGTH", + message: `Must be between 1 and 32 in length.`, + }, + }); + } - const commandForDb: ApplicationCommandSchema = { - application_id: req.params.application_id, - guild_id: req.params.guild_id, - name: body.name.trim(), - name_localizations: body.name_localizations, - description: body.description?.trim() || "", - description_localizations: body.description_localizations, - default_member_permissions: body.default_member_permissions || null, - contexts: body.contexts, - dm_permission: body.dm_permission || true, - global_popularity_rank: 1, - handler: body.handler, - integration_types: body.integration_types, - nsfw: body.nsfw, - options: body.options, - type: body.type, - version: Snowflake.generate(), - }; + const commandForDb: ApplicationCommandSchema = { + application_id: req.params.application_id, + guild_id: req.params.guild_id, + name: body.name.trim(), + name_localizations: body.name_localizations, + description: body.description?.trim() || "", + description_localizations: body.description_localizations, + default_member_permissions: body.default_member_permissions || null, + contexts: body.contexts, + dm_permission: body.dm_permission || true, + global_popularity_rank: 1, + handler: body.handler, + integration_types: body.integration_types, + nsfw: body.nsfw, + options: body.options, + type: body.type, + version: Snowflake.generate(), + }; - const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id, name: body.name.trim() } }); + const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id, name: body.name.trim() } }); - if (commandExists) { - await ApplicationCommand.update({ application_id: req.params.application_id, guild_id: req.params.guild_id, name: body.name.trim() }, commandForDb); - } else { - commandForDb.id = Snowflake.generate(); // Have to be done that way so the id doesn't change - await ApplicationCommand.save(commandForDb); - } + if (commandExists) { + await ApplicationCommand.update({ application_id: req.params.application_id, guild_id: req.params.guild_id, name: body.name.trim() }, commandForDb); + } else { + commandForDb.id = Snowflake.generate(); // Have to be done that way so the id doesn't change + await ApplicationCommand.save(commandForDb); + } - res.send(body); - }, + res.send(body); + }, ); router.put( - "/", - route({ - requestBody: "BulkApplicationCommandCreateSchema", - }), - async (req: Request, res: Response) => { - const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); + "/", + route({ + requestBody: "BulkApplicationCommandCreateSchema", + }), + async (req: Request, res: Response) => { + const applicationExists = await Application.exists({ where: { id: req.params.application_id } }); - if (!applicationExists) { - res.status(404).send({ code: 404, message: "Unknown application" }); - return; - } + if (!applicationExists) { + res.status(404).send({ code: 404, message: "Unknown application" }); + return; + } - const guildExists = await Guild.exists({ where: { id: req.params.guild_id } }); + const guildExists = await Guild.exists({ where: { id: req.params.guild_id } }); - if (!guildExists) { - res.status(404).send({ code: 404, message: "Unknown Server" }); - return; - } + if (!guildExists) { + res.status(404).send({ code: 404, message: "Unknown Server" }); + return; + } - if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) { - res.status(401).send({ code: 401, message: "Missing Access" }); - return; - } + if (!(await Member.exists({ where: { id: req.params.application_id, guild_id: req.params.guild_id } }))) { + res.status(401).send({ code: 401, message: "Missing Access" }); + return; + } - const body = req.body as ApplicationCommandCreateSchema[]; + const body = req.body as ApplicationCommandCreateSchema[]; - // Remove commands not present in array - const applicationCommands = await ApplicationCommand.find({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id } }); + // Remove commands not present in array + const applicationCommands = await ApplicationCommand.find({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id } }); - const commandNamesInArray = body.map((c) => c.name); - const commandsNotInArray = applicationCommands.filter((c) => !commandNamesInArray.includes(c.name)); + const commandNamesInArray = body.map((c) => c.name); + const commandsNotInArray = applicationCommands.filter((c) => !commandNamesInArray.includes(c.name)); - for (const command of commandsNotInArray) { - await ApplicationCommand.delete({ application_id: req.params.application_id, guild_id: req.params.guild_id, id: command.id }); - } + for (const command of commandsNotInArray) { + await ApplicationCommand.delete({ application_id: req.params.application_id, guild_id: req.params.guild_id, id: command.id }); + } - for (const command of body) { - if (!command.type) { - command.type = 1; - } + for (const command of body) { + if (!command.type) { + command.type = 1; + } - if (command.name.trim().length < 1 || command.name.trim().length > 32) { - // TODO: configurable? - throw FieldErrors({ - name: { - code: "BASE_TYPE_BAD_LENGTH", - message: `Must be between 1 and 32 in length.`, - }, - }); - } + if (command.name.trim().length < 1 || command.name.trim().length > 32) { + // TODO: configurable? + throw FieldErrors({ + name: { + code: "BASE_TYPE_BAD_LENGTH", + message: `Must be between 1 and 32 in length.`, + }, + }); + } - const commandForDb: ApplicationCommandSchema = { - application_id: req.params.application_id, - guild_id: req.params.guild_id, - name: command.name.trim(), - name_localizations: command.name_localizations, - description: command.description?.trim() || "", - description_localizations: command.description_localizations, - default_member_permissions: command.default_member_permissions || null, - contexts: command.contexts, - dm_permission: command.dm_permission || true, - global_popularity_rank: 1, - handler: command.handler, - integration_types: command.integration_types, - nsfw: command.nsfw, - options: command.options, - type: command.type, - version: Snowflake.generate(), - }; + const commandForDb: ApplicationCommandSchema = { + application_id: req.params.application_id, + guild_id: req.params.guild_id, + name: command.name.trim(), + name_localizations: command.name_localizations, + description: command.description?.trim() || "", + description_localizations: command.description_localizations, + default_member_permissions: command.default_member_permissions || null, + contexts: command.contexts, + dm_permission: command.dm_permission || true, + global_popularity_rank: 1, + handler: command.handler, + integration_types: command.integration_types, + nsfw: command.nsfw, + options: command.options, + type: command.type, + version: Snowflake.generate(), + }; - const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id, name: command.name } }); + const commandExists = await ApplicationCommand.exists({ where: { application_id: req.params.application_id, guild_id: req.params.guild_id, name: command.name } }); - if (commandExists) { - await ApplicationCommand.update({ application_id: req.params.application_id, guild_id: req.params.guild_id, name: command.name }, commandForDb); - } else { - commandForDb.id = Snowflake.generate(); // Have to be done that way so the id doesn't change - await ApplicationCommand.save(commandForDb); - } - } + if (commandExists) { + await ApplicationCommand.update({ application_id: req.params.application_id, guild_id: req.params.guild_id, name: command.name }, commandForDb); + } else { + commandForDb.id = Snowflake.generate(); // Have to be done that way so the id doesn't change + await ApplicationCommand.save(commandForDb); + } + } - res.send(body); - }, + res.send(body); + }, ); export default router; diff --git a/src/api/routes/applications/#application_id/index.ts b/src/api/routes/applications/#application_id/index.ts
index c38895d2..bf29f404 100644 --- a/src/api/routes/applications/#application_id/index.ts +++ b/src/api/routes/applications/#application_id/index.ts
@@ -26,106 +26,106 @@ import { ApplicationModifySchema } from "@spacebar/schemas"; const router: Router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "Application", - }, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const app = await Application.findOneOrFail({ - where: { id: req.params.application_id }, - relations: ["owner", "bot"], - }); - if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION; + "/", + route({ + responses: { + 200: { + body: "Application", + }, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const app = await Application.findOneOrFail({ + where: { id: req.params.application_id }, + relations: ["owner", "bot"], + }); + if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION; - return res.json(app); - }, + return res.json(app); + }, ); router.patch( - "/", - route({ - requestBody: "ApplicationModifySchema", - responses: { - 200: { - body: "Application", - }, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const body = req.body as ApplicationModifySchema; + "/", + route({ + requestBody: "ApplicationModifySchema", + responses: { + 200: { + body: "Application", + }, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const body = req.body as ApplicationModifySchema; - const app = await Application.findOneOrFail({ - where: { id: req.params.application_id }, - relations: ["owner", "bot"], - }); + const app = await Application.findOneOrFail({ + where: { id: req.params.application_id }, + relations: ["owner", "bot"], + }); - if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION; + if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION; - if (app.owner.totp_secret && (!req.body.code || verifyToken(app.owner.totp_secret, req.body.code))) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); + if (app.owner.totp_secret && (!req.body.code || verifyToken(app.owner.totp_secret, req.body.code))) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); - if (body.icon) { - body.icon = await handleFile(`/app-icons/${app.id}`, body.icon as string); - } - if (body.cover_image) { - body.cover_image = await handleFile(`/app-icons/${app.id}`, body.cover_image as string); - } + if (body.icon) { + body.icon = await handleFile(`/app-icons/${app.id}`, body.icon as string); + } + if (body.cover_image) { + body.cover_image = await handleFile(`/app-icons/${app.id}`, body.cover_image as string); + } - if (body.guild_id) { - const guild = await Guild.findOneOrFail({ - where: { id: body.guild_id }, - select: ["owner_id"], - }); - if (guild.owner_id != req.user_id) throw new HTTPError("You must be the owner of the guild to link it to an application", 400); - } + if (body.guild_id) { + const guild = await Guild.findOneOrFail({ + where: { id: body.guild_id }, + select: ["owner_id"], + }); + if (guild.owner_id != req.user_id) throw new HTTPError("You must be the owner of the guild to link it to an application", 400); + } - if (app.bot) { - app.bot.assign({ bio: body.description }); - await app.bot.save(); - } + if (app.bot) { + app.bot.assign({ bio: body.description }); + await app.bot.save(); + } - app.assign(body); + app.assign(body); - await app.save(); + await app.save(); - return res.json(app); - }, + return res.json(app); + }, ); router.post( - "/delete", - route({ - responses: { - 200: {}, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const app = await Application.findOneOrFail({ - where: { id: req.params.application_id }, - relations: ["bot", "owner"], - }); - if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION; + "/delete", + route({ + responses: { + 200: {}, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const app = await Application.findOneOrFail({ + where: { id: req.params.application_id }, + relations: ["bot", "owner"], + }); + if (app.owner.id != req.user_id) throw DiscordApiErrors.ACTION_NOT_AUTHORIZED_ON_APPLICATION; - if (app.owner.totp_secret && (!req.body.code || verifyToken(app.owner.totp_secret, req.body.code))) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); - if (app.bot) { - await User.delete({ id: app.id }); - } - await Application.delete({ id: app.id }); + if (app.owner.totp_secret && (!req.body.code || verifyToken(app.owner.totp_secret, req.body.code))) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); + if (app.bot) { + await User.delete({ id: app.id }); + } + await Application.delete({ id: app.id }); - res.send().status(200); - }, + res.send().status(200); + }, ); export default router; diff --git a/src/api/routes/applications/#application_id/skus.ts b/src/api/routes/applications/#application_id/skus.ts
index 0877f4fb..9b9e3387 100644 --- a/src/api/routes/applications/#application_id/skus.ts +++ b/src/api/routes/applications/#application_id/skus.ts
@@ -22,17 +22,17 @@ import { Request, Response, Router } from "express"; const router: Router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "ApplicationSkusResponse", - }, - }, - }), - async (req: Request, res: Response) => { - res.json([]).status(200); - }, + "/", + route({ + responses: { + 200: { + body: "ApplicationSkusResponse", + }, + }, + }), + async (req: Request, res: Response) => { + res.json([]).status(200); + }, ); export default router; diff --git a/src/api/routes/applications/@me.ts b/src/api/routes/applications/@me.ts
index f4a90976..c1cf0db0 100644 --- a/src/api/routes/applications/@me.ts +++ b/src/api/routes/applications/@me.ts
@@ -27,74 +27,74 @@ const router: Router = Router({ mergeParams: true }); // TODO: actually make this be correct - this is just a copy paste of /applications/:id/index.ts minus delete router.get( - "/", - route({ - responses: { - 200: { - body: "Application", - }, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const app = await Application.findOneOrFail({ - where: { id: req.user_id }, - relations: ["owner", "bot"], - }); + "/", + route({ + responses: { + 200: { + body: "Application", + }, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const app = await Application.findOneOrFail({ + where: { id: req.user_id }, + relations: ["owner", "bot"], + }); - return res.json(app); - }, + return res.json(app); + }, ); router.patch( - "/", - route({ - requestBody: "ApplicationModifySchema", - responses: { - 200: { - body: "Application", - }, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const body = req.body as ApplicationModifySchema; + "/", + route({ + requestBody: "ApplicationModifySchema", + responses: { + 200: { + body: "Application", + }, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const body = req.body as ApplicationModifySchema; - const app = await Application.findOneOrFail({ - where: { id: req.user_id }, - relations: ["owner", "bot"], - }); + const app = await Application.findOneOrFail({ + where: { id: req.user_id }, + relations: ["owner", "bot"], + }); - if (body.icon) { - body.icon = await handleFile(`/app-icons/${app.id}`, body.icon as string); - } - if (body.cover_image) { - body.cover_image = await handleFile(`/app-icons/${app.id}`, body.cover_image as string); - } + if (body.icon) { + body.icon = await handleFile(`/app-icons/${app.id}`, body.icon as string); + } + if (body.cover_image) { + body.cover_image = await handleFile(`/app-icons/${app.id}`, body.cover_image as string); + } - if (body.guild_id) { - const guild = await Guild.findOneOrFail({ - where: { id: body.guild_id }, - select: ["owner_id"], - }); - if (guild.owner_id != req.user_id) throw new HTTPError("You must be the owner of the guild to link it to an application", 400); - } + if (body.guild_id) { + const guild = await Guild.findOneOrFail({ + where: { id: body.guild_id }, + select: ["owner_id"], + }); + if (guild.owner_id != req.user_id) throw new HTTPError("You must be the owner of the guild to link it to an application", 400); + } - if (app.bot) { - app.bot.assign({ bio: body.description }); - await app.bot.save(); - } + if (app.bot) { + app.bot.assign({ bio: body.description }); + await app.bot.save(); + } - app.assign(body); + app.assign(body); - await app.save(); + await app.save(); - return res.json(app); - }, + return res.json(app); + }, ); export default router; diff --git a/src/api/routes/applications/detectable.ts b/src/api/routes/applications/detectable.ts
index 4de8a78d..83122e76 100644 --- a/src/api/routes/applications/detectable.ts +++ b/src/api/routes/applications/detectable.ts
@@ -22,32 +22,32 @@ import { ApplicationDetectableResponse } from "@spacebar/schemas"; const router: Router = Router({ mergeParams: true }); const cache = { - data: {}, - expires: 0, + data: {}, + expires: 0, }; router.get( - "/", - route({ - responses: { - 200: { - body: "ApplicationDetectableResponse", - }, - }, - }), - async (req: Request, res: Response) => { - // cache for 6 hours - if (Date.now() > cache.expires) { - const response = await fetch("https://discord.com/api/v10/games/detectable"); // because, well, it's unauthenticated anyways - const data = await response.json(); - cache.data = data as ApplicationDetectableResponse; - cache.expires = Date.now() + 6 * 60 * 60 * 1000; - } + "/", + route({ + responses: { + 200: { + body: "ApplicationDetectableResponse", + }, + }, + }), + async (req: Request, res: Response) => { + // cache for 6 hours + if (Date.now() > cache.expires) { + const response = await fetch("https://discord.com/api/v10/games/detectable"); // because, well, it's unauthenticated anyways + const data = await response.json(); + cache.data = data as ApplicationDetectableResponse; + cache.expires = Date.now() + 6 * 60 * 60 * 1000; + } - res.set("Cache-Control", `public, max-age=${Math.floor((cache.expires - Date.now()) / 1000)}, s-maxage=${Math.floor((cache.expires - Date.now()) / 1000)}, immutable`) - .status(200) - .json(cache.data); - }, + res.set("Cache-Control", `public, max-age=${Math.floor((cache.expires - Date.now()) / 1000)}, s-maxage=${Math.floor((cache.expires - Date.now()) / 1000)}, immutable`) + .status(200) + .json(cache.data); + }, ); export default router; diff --git a/src/api/routes/applications/index.ts b/src/api/routes/applications/index.ts
index e3b270c3..ff89f03d 100644 --- a/src/api/routes/applications/index.ts +++ b/src/api/routes/applications/index.ts
@@ -24,54 +24,54 @@ import { ApplicationCreateSchema } from "@spacebar/schemas"; const router: Router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "APIApplicationArray", - }, - }, - }), - async (req: Request, res: Response) => { - const results = await Application.find({ - where: { owner: { id: req.user_id } }, - relations: ["owner", "bot"], - }); - res.json(results).status(200); - }, + "/", + route({ + responses: { + 200: { + body: "APIApplicationArray", + }, + }, + }), + async (req: Request, res: Response) => { + const results = await Application.find({ + where: { owner: { id: req.user_id } }, + relations: ["owner", "bot"], + }); + res.json(results).status(200); + }, ); router.post( - "/", - route({ - requestBody: "ApplicationCreateSchema", - responses: { - 200: { - body: "Application", - }, - }, - }), - async (req: Request, res: Response) => { - const body = req.body as ApplicationCreateSchema; - const user = await User.findOneOrFail({ where: { id: req.user_id } }); + "/", + route({ + requestBody: "ApplicationCreateSchema", + responses: { + 200: { + body: "Application", + }, + }, + }), + async (req: Request, res: Response) => { + const body = req.body as ApplicationCreateSchema; + const user = await User.findOneOrFail({ where: { id: req.user_id } }); - const app = Application.create({ - name: trimSpecial(body.name), - description: "", - bot_public: true, - owner: user, - verify_key: "IMPLEMENTME", - flags: 0, - }); + const app = Application.create({ + name: trimSpecial(body.name), + description: "", + bot_public: true, + owner: user, + verify_key: "IMPLEMENTME", + flags: 0, + }); - // april 14, 2023: discord made bot users be automatically added to all new apps - const { autoCreateBotUsers } = Config.get().general; - if (autoCreateBotUsers) { - await createAppBotUser(app, req); - } else await app.save(); + // april 14, 2023: discord made bot users be automatically added to all new apps + const { autoCreateBotUsers } = Config.get().general; + if (autoCreateBotUsers) { + await createAppBotUser(app, req); + } else await app.save(); - res.json(app); - }, + res.json(app); + }, ); export default router; diff --git a/src/api/routes/attachments/refresh-urls.ts b/src/api/routes/attachments/refresh-urls.ts
index 92a7dd16..f1530914 100644 --- a/src/api/routes/attachments/refresh-urls.ts +++ b/src/api/routes/attachments/refresh-urls.ts
@@ -23,37 +23,37 @@ import { RefreshUrlsRequestSchema } from "@spacebar/schemas"; const router = Router({ mergeParams: true }); router.post( - "/", - route({ - requestBody: "RefreshUrlsRequestSchema", - responses: { - 200: { - body: "RefreshUrlsResponse", - }, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { attachment_urls } = req.body as RefreshUrlsRequestSchema; + "/", + route({ + requestBody: "RefreshUrlsRequestSchema", + responses: { + 200: { + body: "RefreshUrlsResponse", + }, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { attachment_urls } = req.body as RefreshUrlsRequestSchema; - const refreshed_urls = attachment_urls.map((url) => { - return getUrlSignature( - new NewUrlSignatureData({ - url: url, - ip: req.ip, - userAgent: req.headers["user-agent"] as string, - }), - ) - .applyToUrl(url) - .toString(); - }); + const refreshed_urls = attachment_urls.map((url) => { + return getUrlSignature( + new NewUrlSignatureData({ + url: url, + ip: req.ip, + userAgent: req.headers["user-agent"] as string, + }), + ) + .applyToUrl(url) + .toString(); + }); - return res.status(200).json({ - refreshed_urls, - }); - }, + return res.status(200).json({ + refreshed_urls, + }); + }, ); export default router; diff --git a/src/api/routes/auth/fingerprint.ts b/src/api/routes/auth/fingerprint.ts
index d7ef34b4..a4a49d07 100644 --- a/src/api/routes/auth/fingerprint.ts +++ b/src/api/routes/auth/fingerprint.ts
@@ -21,9 +21,9 @@ import { Snowflake } from "@spacebar/util"; import { Request, Response, Router } from "express"; const router = Router({ mergeParams: true }); router.post("/", route({ responses: { 200: { body: "CreateFingerprintResponse" } } }), (req: Request, res: Response) => { - const snowflake = Snowflake.generate(); - return res.json({ - fingerprint: `${snowflake}.${createHash("sha512").update(snowflake).digest("base64")}`, - }); + const snowflake = Snowflake.generate(); + return res.json({ + fingerprint: `${snowflake}.${createHash("sha512").update(snowflake).digest("base64")}`, + }); }); export default router; diff --git a/src/api/routes/auth/forgot.ts b/src/api/routes/auth/forgot.ts
index 6e613820..db16c3be 100644 --- a/src/api/routes/auth/forgot.ts +++ b/src/api/routes/auth/forgot.ts
@@ -23,55 +23,55 @@ import { ForgotPasswordSchema } from "@spacebar/schemas"; const router = Router({ mergeParams: true }); router.post( - "/", - route({ - requestBody: "ForgotPasswordSchema", - responses: { - 204: {}, - 400: { - body: "APIErrorOrCaptchaResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { login, captcha_key } = req.body as ForgotPasswordSchema; + "/", + route({ + requestBody: "ForgotPasswordSchema", + responses: { + 204: {}, + 400: { + body: "APIErrorOrCaptchaResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { login, captcha_key } = req.body as ForgotPasswordSchema; - const config = Config.get(); + const config = Config.get(); - if (config.passwordReset.requireCaptcha && config.security.captcha.enabled) { - const { sitekey, service } = config.security.captcha; - if (!captcha_key) { - return res.status(400).json({ - captcha_key: ["captcha-required"], - captcha_sitekey: sitekey, - captcha_service: service, - }); - } + if (config.passwordReset.requireCaptcha && config.security.captcha.enabled) { + const { sitekey, service } = config.security.captcha; + if (!captcha_key) { + return res.status(400).json({ + captcha_key: ["captcha-required"], + captcha_sitekey: sitekey, + captcha_service: service, + }); + } - const ip = req.ip; - const verify = await verifyCaptcha(captcha_key, ip); - if (!verify.success) { - return res.status(400).json({ - captcha_key: verify["error-codes"], - captcha_sitekey: sitekey, - captcha_service: service, - }); - } - } + const ip = req.ip; + const verify = await verifyCaptcha(captcha_key, ip); + if (!verify.success) { + return res.status(400).json({ + captcha_key: verify["error-codes"], + captcha_sitekey: sitekey, + captcha_service: service, + }); + } + } - res.sendStatus(204); + res.sendStatus(204); - const user = await User.findOne({ - where: [{ phone: login }, { email: login }], - select: ["username", "id", "email"], - }).catch(() => {}); + const user = await User.findOne({ + where: [{ phone: login }, { email: login }], + select: ["username", "id", "email"], + }).catch(() => {}); - if (user && user.email) { - Email.sendResetPassword(user, user.email).catch((e) => { - console.error(`Failed to send password reset email to ${user.username}#${user.discriminator} (${user.id}): ${e}`); - }); - } - }, + if (user && user.email) { + Email.sendResetPassword(user, user.email).catch((e) => { + console.error(`Failed to send password reset email to ${user.username}#${user.discriminator} (${user.id}): ${e}`); + }); + } + }, ); export default router; diff --git a/src/api/routes/auth/generate-registration-tokens.ts b/src/api/routes/auth/generate-registration-tokens.ts
index be932953..a6b70c65 100644 --- a/src/api/routes/auth/generate-registration-tokens.ts +++ b/src/api/routes/auth/generate-registration-tokens.ts
@@ -24,46 +24,46 @@ const router: Router = Router({ mergeParams: true }); export default router; router.get( - "/", - route({ - query: { - count: { - type: "number", - description: "The number of registration tokens to generate. Defaults to 1.", - }, - length: { - type: "number", - description: "The length of each registration token. Defaults to 255.", - }, - }, - right: "CREATE_REGISTRATION_TOKENS", - responses: { 200: { body: "GenerateRegistrationTokensResponse" } }, - }), - async (req: Request, res: Response) => { - const count = req.query.count ? parseInt(req.query.count as string) : 1; - const length = req.query.length ? parseInt(req.query.length as string) : 255; + "/", + route({ + query: { + count: { + type: "number", + description: "The number of registration tokens to generate. Defaults to 1.", + }, + length: { + type: "number", + description: "The length of each registration token. Defaults to 255.", + }, + }, + right: "CREATE_REGISTRATION_TOKENS", + responses: { 200: { body: "GenerateRegistrationTokensResponse" } }, + }), + async (req: Request, res: Response) => { + const count = req.query.count ? parseInt(req.query.count as string) : 1; + const length = req.query.length ? parseInt(req.query.length as string) : 255; - const tokens: ValidRegistrationToken[] = []; + const tokens: ValidRegistrationToken[] = []; - for (let i = 0; i < count; i++) { - const token = ValidRegistrationToken.create({ - token: randomString(length), - expires_at: new Date(Date.now() + Config.get().security.defaultRegistrationTokenExpiration), - }); - tokens.push(token); - } + for (let i = 0; i < count; i++) { + const token = ValidRegistrationToken.create({ + token: randomString(length), + expires_at: new Date(Date.now() + Config.get().security.defaultRegistrationTokenExpiration), + }); + tokens.push(token); + } - // Why are these options used, exactly? - await ValidRegistrationToken.save(tokens, { - chunk: 1000, - reload: false, - transaction: false, - }); + // Why are these options used, exactly? + await ValidRegistrationToken.save(tokens, { + chunk: 1000, + reload: false, + transaction: false, + }); - const ret = req.query.include_url ? tokens.map((x) => `${Config.get().general.frontPage}/register?token=${x.token}`) : tokens.map((x) => x.token); + const ret = req.query.include_url ? tokens.map((x) => `${Config.get().general.frontPage}/register?token=${x.token}`) : tokens.map((x) => x.token); - if (req.query.plain) return res.send(ret.join("\n")); + if (req.query.plain) return res.send(ret.join("\n")); - return res.json({ tokens: ret }); - }, + return res.json({ tokens: ret }); + }, ); diff --git a/src/api/routes/auth/location-metadata.ts b/src/api/routes/auth/location-metadata.ts
index 92f47909..6f2f26af 100644 --- a/src/api/routes/auth/location-metadata.ts +++ b/src/api/routes/auth/location-metadata.ts
@@ -22,24 +22,24 @@ import { Request, Response, Router } from "express"; const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "LocationMetadataResponse", - }, - }, - }), - async (req: Request, res: Response) => { - //TODO - //Note: It's most likely related to legal. At the moment Discord hasn't finished this too - const country_code = (await IpDataClient.getIpInfo(req.ip!))?.country_code; - res.json({ - consent_required: false, - country_code: country_code, - promotional_email_opt_in: { required: true, pre_checked: false }, - }); - }, + "/", + route({ + responses: { + 200: { + body: "LocationMetadataResponse", + }, + }, + }), + async (req: Request, res: Response) => { + //TODO + //Note: It's most likely related to legal. At the moment Discord hasn't finished this too + const country_code = (await IpDataClient.getIpInfo(req.ip!))?.country_code; + res.json({ + consent_required: false, + country_code: country_code, + promotional_email_opt_in: { required: true, pre_checked: false }, + }); + }, ); export default router; diff --git a/src/api/routes/auth/login.ts b/src/api/routes/auth/login.ts
index c8ef6273..911ad4a9 100644 --- a/src/api/routes/auth/login.ts +++ b/src/api/routes/auth/login.ts
@@ -27,157 +27,157 @@ const router: Router = Router({ mergeParams: true }); export default router; router.post( - "/", - route({ - requestBody: "LoginSchema", - responses: { - 200: { - body: "LoginResponse", - }, - 400: { - body: "APIErrorOrCaptchaResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { login, password, captcha_key, undelete } = req.body as LoginSchema; + "/", + route({ + requestBody: "LoginSchema", + responses: { + 200: { + body: "LoginResponse", + }, + 400: { + body: "APIErrorOrCaptchaResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { login, password, captcha_key, undelete } = req.body as LoginSchema; - const config = Config.get(); + const config = Config.get(); - if (config.login.requireCaptcha && config.security.captcha.enabled) { - const { sitekey, service } = config.security.captcha; - if (!captcha_key) { - return res.status(400).json({ - captcha_key: ["captcha-required"], - captcha_sitekey: sitekey, - captcha_service: service, - }); - } + if (config.login.requireCaptcha && config.security.captcha.enabled) { + const { sitekey, service } = config.security.captcha; + if (!captcha_key) { + return res.status(400).json({ + captcha_key: ["captcha-required"], + captcha_sitekey: sitekey, + captcha_service: service, + }); + } - const ip = req.ip; - const verify = await verifyCaptcha(captcha_key, ip); - if (!verify.success) { - return res.status(400).json({ - captcha_key: verify["error-codes"], - captcha_sitekey: sitekey, - captcha_service: service, - }); - } - } + const ip = req.ip; + const verify = await verifyCaptcha(captcha_key, ip); + if (!verify.success) { + return res.status(400).json({ + captcha_key: verify["error-codes"], + captcha_sitekey: sitekey, + captcha_service: service, + }); + } + } - const user = await User.findOneOrFail({ - where: [{ phone: login }, { email: login }], - select: ["data", "id", "disabled", "deleted", "totp_secret", "mfa_enabled", "webauthn_enabled", "security_keys", "verified"], - relations: ["security_keys", "settings"], - }).catch(() => { - throw FieldErrors({ - login: { - message: req.t("auth:login.INVALID_LOGIN"), - code: "INVALID_LOGIN", - }, - password: { - message: req.t("auth:login.INVALID_LOGIN"), - code: "INVALID_LOGIN", - }, - }); - }); + const user = await User.findOneOrFail({ + where: [{ phone: login }, { email: login }], + select: ["data", "id", "disabled", "deleted", "totp_secret", "mfa_enabled", "webauthn_enabled", "security_keys", "verified"], + relations: ["security_keys", "settings"], + }).catch(() => { + throw FieldErrors({ + login: { + message: req.t("auth:login.INVALID_LOGIN"), + code: "INVALID_LOGIN", + }, + password: { + message: req.t("auth:login.INVALID_LOGIN"), + code: "INVALID_LOGIN", + }, + }); + }); - // the salt is saved in the password refer to bcrypt docs - const same_password = await bcrypt.compare(password, user.data.hash || ""); - if (!same_password) { - throw FieldErrors({ - login: { - message: req.t("auth:login.INVALID_LOGIN"), - code: "INVALID_LOGIN", - }, - password: { - message: req.t("auth:login.INVALID_LOGIN"), - code: "INVALID_LOGIN", - }, - }); - } + // the salt is saved in the password refer to bcrypt docs + const same_password = await bcrypt.compare(password, user.data.hash || ""); + if (!same_password) { + throw FieldErrors({ + login: { + message: req.t("auth:login.INVALID_LOGIN"), + code: "INVALID_LOGIN", + }, + password: { + message: req.t("auth:login.INVALID_LOGIN"), + code: "INVALID_LOGIN", + }, + }); + } - // return an error for unverified accounts if verification is required - if (config.login.requireVerification && !user.verified) { - throw FieldErrors({ - login: { - code: "ACCOUNT_LOGIN_VERIFICATION_EMAIL", - message: "Email verification is required, please check your email.", - }, - }); - } + // return an error for unverified accounts if verification is required + if (config.login.requireVerification && !user.verified) { + throw FieldErrors({ + login: { + code: "ACCOUNT_LOGIN_VERIFICATION_EMAIL", + message: "Email verification is required, please check your email.", + }, + }); + } - if (user.mfa_enabled && !user.webauthn_enabled) { - // TODO: This is not a discord.com ticket. I'm not sure what it is but I'm lazy - const ticket = crypto.randomBytes(40).toString("hex"); + if (user.mfa_enabled && !user.webauthn_enabled) { + // TODO: This is not a discord.com ticket. I'm not sure what it is but I'm lazy + const ticket = crypto.randomBytes(40).toString("hex"); - await User.update({ id: user.id }, { totp_last_ticket: ticket }); + await User.update({ id: user.id }, { totp_last_ticket: ticket }); - return res.json({ - ticket: ticket, - mfa: true, - sms: false, // TODO - token: null, - }); - } + return res.json({ + ticket: ticket, + mfa: true, + sms: false, // TODO + token: null, + }); + } - if (user.mfa_enabled && user.webauthn_enabled) { - if (!WebAuthn.fido2) { - // TODO: I did this for typescript and I can't use ! - throw new Error("WebAuthn not enabled"); - } + if (user.mfa_enabled && user.webauthn_enabled) { + if (!WebAuthn.fido2) { + // TODO: I did this for typescript and I can't use ! + throw new Error("WebAuthn not enabled"); + } - const options = await WebAuthn.fido2.assertionOptions(); - const challenge = JSON.stringify({ - publicKey: { - ...options, - challenge: Buffer.from(options.challenge).toString("base64"), - allowCredentials: user.security_keys.map((x) => ({ - id: x.key_id, - type: "public-key", - })), - transports: ["usb", "ble", "nfc"], - timeout: 60000, - }, - }); + const options = await WebAuthn.fido2.assertionOptions(); + const challenge = JSON.stringify({ + publicKey: { + ...options, + challenge: Buffer.from(options.challenge).toString("base64"), + allowCredentials: user.security_keys.map((x) => ({ + id: x.key_id, + type: "public-key", + })), + transports: ["usb", "ble", "nfc"], + timeout: 60000, + }, + }); - const ticket = await generateWebAuthnTicket(challenge); - await User.update({ id: user.id }, { totp_last_ticket: ticket }); + const ticket = await generateWebAuthnTicket(challenge); + await User.update({ id: user.id }, { totp_last_ticket: ticket }); - return res.json({ - ticket: ticket, - mfa: true, - sms: false, // TODO - token: null, - webauthn: challenge, - }); - } + return res.json({ + ticket: ticket, + mfa: true, + sms: false, // TODO + token: null, + webauthn: challenge, + }); + } - if (undelete) { - // undelete refers to un'disable' here - if (user.disabled) await User.update({ id: user.id }, { disabled: false }); - if (user.deleted) await User.update({ id: user.id }, { deleted: false }); - } else { - if (user.deleted) - return res.status(400).json({ - message: "This account is scheduled for deletion.", - code: 20011, - }); - if (user.disabled) - return res.status(400).json({ - message: req.t("auth:login.ACCOUNT_DISABLED"), - code: 20013, - }); - } + if (undelete) { + // undelete refers to un'disable' here + if (user.disabled) await User.update({ id: user.id }, { disabled: false }); + if (user.deleted) await User.update({ id: user.id }, { deleted: false }); + } else { + if (user.deleted) + return res.status(400).json({ + message: "This account is scheduled for deletion.", + code: 20011, + }); + if (user.disabled) + return res.status(400).json({ + message: req.t("auth:login.ACCOUNT_DISABLED"), + code: 20013, + }); + } - const token = await generateToken(user.id); + const token = await generateToken(user.id); - // Notice this will have a different token structure, than discord - // Discord header is just the user id as string, which is not possible with npm-jsonwebtoken package - // https://user-images.githubusercontent.com/6506416/81051916-dd8c9900-8ec2-11ea-8794-daf12d6f31f0.png + // Notice this will have a different token structure, than discord + // Discord header is just the user id as string, which is not possible with npm-jsonwebtoken package + // https://user-images.githubusercontent.com/6506416/81051916-dd8c9900-8ec2-11ea-8794-daf12d6f31f0.png - res.json({ token, settings: { ...user.settings, index: undefined } }); - }, + res.json({ token, settings: { ...user.settings, index: undefined } }); + }, ); /** diff --git a/src/api/routes/auth/logout.ts b/src/api/routes/auth/logout.ts
index f20a1994..b460e31a 100644 --- a/src/api/routes/auth/logout.ts +++ b/src/api/routes/auth/logout.ts
@@ -24,25 +24,25 @@ const router: Router = Router({ mergeParams: true }); export default router; router.post( - "/", - route({ - responses: { - 204: {}, - }, - }), - async (req: Request, res: Response) => { - if (req.body.provider != null || req.body.voip_provider != null) { - console.log(`[LOGOUT]: provider or voip provider not null!`, req.body); - } else { - delete req.body.provider; - delete req.body.voip_provider; - if (Object.keys(req.body).length != 0) console.log(`[LOGOUT]: Extra fields sent in logout!`, req.body); - } + "/", + route({ + responses: { + 204: {}, + }, + }), + async (req: Request, res: Response) => { + if (req.body.provider != null || req.body.voip_provider != null) { + console.log(`[LOGOUT]: provider or voip provider not null!`, req.body); + } else { + delete req.body.provider; + delete req.body.voip_provider; + if (Object.keys(req.body).length != 0) console.log(`[LOGOUT]: Extra fields sent in logout!`, req.body); + } - if (req.token.did) { - await Session.delete({ user_id: req.user_id, session_id: req.token.did }); - } + if (req.token.did) { + await Session.delete({ user_id: req.user_id, session_id: req.token.did }); + } - res.status(204).send(); - }, + res.status(204).send(); + }, ); diff --git a/src/api/routes/auth/mfa/totp.ts b/src/api/routes/auth/mfa/totp.ts
index edf307cc..b9f2fa85 100644 --- a/src/api/routes/auth/mfa/totp.ts +++ b/src/api/routes/auth/mfa/totp.ts
@@ -25,54 +25,54 @@ import { TotpSchema } from "@spacebar/schemas"; const router = Router({ mergeParams: true }); router.post( - "/", - route({ - requestBody: "TotpSchema", - responses: { - 200: { - body: "TokenResponse", - }, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - // const { code, ticket, gift_code_sku_id, login_source } = - const { code, ticket } = req.body as TotpSchema; + "/", + route({ + requestBody: "TotpSchema", + responses: { + 200: { + body: "TokenResponse", + }, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + // const { code, ticket, gift_code_sku_id, login_source } = + const { code, ticket } = req.body as TotpSchema; - const user = await User.findOneOrFail({ - where: { - totp_last_ticket: ticket, - }, - select: ["id", "totp_secret"], - relations: ["settings"], - }); + const user = await User.findOneOrFail({ + where: { + totp_last_ticket: ticket, + }, + select: ["id", "totp_secret"], + relations: ["settings"], + }); - const backup = await BackupCode.findOne({ - where: { - code: code, - expired: false, - consumed: false, - user: { id: user.id }, - }, - }); + const backup = await BackupCode.findOne({ + where: { + code: code, + expired: false, + consumed: false, + user: { id: user.id }, + }, + }); - if (!backup) { - const ret = verifyToken(user.totp_secret || "", code); - if (!ret || ret.delta != 0) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); - } else { - backup.consumed = true; - await backup.save(); - } + if (!backup) { + const ret = verifyToken(user.totp_secret || "", code); + if (!ret || ret.delta != 0) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); + } else { + backup.consumed = true; + await backup.save(); + } - await User.update({ id: user.id }, { totp_last_ticket: "" }); + await User.update({ id: user.id }, { totp_last_ticket: "" }); - return res.json({ - token: await generateToken(user.id), - settings: { ...user.settings, index: undefined }, - }); - }, + return res.json({ + token: await generateToken(user.id), + settings: { ...user.settings, index: undefined }, + }); + }, ); export default router; diff --git a/src/api/routes/auth/mfa/webauthn.ts b/src/api/routes/auth/mfa/webauthn.ts
index 63b1335d..da476ab4 100644 --- a/src/api/routes/auth/mfa/webauthn.ts +++ b/src/api/routes/auth/mfa/webauthn.ts
@@ -25,77 +25,77 @@ import { WebAuthnTotpSchema } from "@spacebar/schemas"; const router = Router({ mergeParams: true }); 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.post( - "/", - route({ - requestBody: "WebAuthnTotpSchema", - responses: { - 200: { body: "TokenResponse" }, - 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: "WebAuthnTotpSchema", + responses: { + 200: { body: "TokenResponse" }, + 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 { code, ticket } = req.body as WebAuthnTotpSchema; + const { code, ticket } = req.body as WebAuthnTotpSchema; - const user = await User.findOneOrFail({ - where: { - totp_last_ticket: ticket, - }, - select: ["id"], - relations: ["settings"], - }); + const user = await User.findOneOrFail({ + where: { + totp_last_ticket: ticket, + }, + select: ["id"], + relations: ["settings"], + }); - const ret = await verifyWebAuthnToken(ticket); - if (!ret) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); + const ret = await verifyWebAuthnToken(ticket); + if (!ret) throw new HTTPError(req.t("auth:login.INVALID_TOTP_CODE"), 60008); - await User.update({ id: user.id }, { totp_last_ticket: "" }); + await User.update({ id: user.id }, { totp_last_ticket: "" }); - const clientAttestationResponse = JSON.parse(code); + const clientAttestationResponse = JSON.parse(code); - if (!clientAttestationResponse.rawId) throw new HTTPError("Missing rawId", 400); + if (!clientAttestationResponse.rawId) throw new HTTPError("Missing rawId", 400); - clientAttestationResponse.rawId = toArrayBuffer(Buffer.from(clientAttestationResponse.rawId, "base64url")); + clientAttestationResponse.rawId = toArrayBuffer(Buffer.from(clientAttestationResponse.rawId, "base64url")); - const securityKey = await SecurityKey.findOneOrFail({ - where: { - key_id: Buffer.from(clientAttestationResponse.rawId, "base64url").toString("base64"), - }, - }); + const securityKey = await SecurityKey.findOneOrFail({ + where: { + key_id: Buffer.from(clientAttestationResponse.rawId, "base64url").toString("base64"), + }, + }); - const assertionExpectations: ExpectedAssertionResult = JSON.parse(Buffer.from(clientAttestationResponse.response.clientDataJSON, "base64").toString()); + const assertionExpectations: ExpectedAssertionResult = JSON.parse(Buffer.from(clientAttestationResponse.response.clientDataJSON, "base64").toString()); - const authnResult = await WebAuthn.fido2.assertionResult(clientAttestationResponse, { - ...assertionExpectations, - factor: "second", - publicKey: securityKey.public_key, - prevCounter: securityKey.counter, - userHandle: securityKey.key_id, - }); + const authnResult = await WebAuthn.fido2.assertionResult(clientAttestationResponse, { + ...assertionExpectations, + factor: "second", + publicKey: securityKey.public_key, + prevCounter: securityKey.counter, + userHandle: securityKey.key_id, + }); - const counter = authnResult.authnrData.get("counter"); + const counter = authnResult.authnrData.get("counter"); - securityKey.counter = counter; + securityKey.counter = counter; - await securityKey.save(); + await securityKey.save(); - return res.json({ - token: await generateToken(user.id), - user_settings: user.settings, - }); - }, + return res.json({ + token: await generateToken(user.id), + user_settings: user.settings, + }); + }, ); export default router; diff --git a/src/api/routes/auth/register.ts b/src/api/routes/auth/register.ts
index d4b061be..6aa52bff 100644 --- a/src/api/routes/auth/register.ts +++ b/src/api/routes/auth/register.ts
@@ -27,294 +27,294 @@ import { RegisterSchema } from "@spacebar/schemas"; const router: Router = Router({ mergeParams: true }); router.post( - "/", - route({ - requestBody: "RegisterSchema", - responses: { - 200: { body: "TokenOnlyResponse" }, - 400: { body: "APIErrorOrCaptchaResponse" }, - }, - }), - async (req: Request, res: Response) => { - const body = req.body as RegisterSchema; - const { register, security, limits } = Config.get(); - const ip = req.ip!; + "/", + route({ + requestBody: "RegisterSchema", + responses: { + 200: { body: "TokenOnlyResponse" }, + 400: { body: "APIErrorOrCaptchaResponse" }, + }, + }), + async (req: Request, res: Response) => { + const body = req.body as RegisterSchema; + const { register, security, limits } = Config.get(); + const ip = req.ip!; - // Reg tokens - // They're a one time use token that bypasses registration limits ( rates, disabled reg, etc ) - let regTokenUsed = false; - if (req.get("Referrer") && req.get("Referrer")?.includes("token=")) { - // eg theyre on https://staging.spacebar.chat/register?token=whatever - const token = req.get("Referrer")?.split("token=")[1].split("&")[0]; - if (token) { - const regToken = await ValidRegistrationToken.findOneOrFail({ - where: { token, expires_at: MoreThan(new Date()) }, - }); - await regToken.remove(); - regTokenUsed = true; - console.log(`[REGISTER] Registration token ${token} used for registration!`); - } else { - console.log(`[REGISTER] Invalid registration token ${token} used for registration by ${ip}!`); - } - } + // Reg tokens + // They're a one time use token that bypasses registration limits ( rates, disabled reg, etc ) + let regTokenUsed = false; + if (req.get("Referrer") && req.get("Referrer")?.includes("token=")) { + // eg theyre on https://staging.spacebar.chat/register?token=whatever + const token = req.get("Referrer")?.split("token=")[1].split("&")[0]; + if (token) { + const regToken = await ValidRegistrationToken.findOneOrFail({ + where: { token, expires_at: MoreThan(new Date()) }, + }); + await regToken.remove(); + regTokenUsed = true; + console.log(`[REGISTER] Registration token ${token} used for registration!`); + } else { + console.log(`[REGISTER] Invalid registration token ${token} used for registration by ${ip}!`); + } + } - // check if registration is allowed - if (!regTokenUsed && !register.allowNewRegistration) { - throw FieldErrors({ - email: { - code: "REGISTRATION_DISABLED", - message: req.t("auth:register.REGISTRATION_DISABLED"), - }, - }); - } + // check if registration is allowed + if (!regTokenUsed && !register.allowNewRegistration) { + throw FieldErrors({ + email: { + code: "REGISTRATION_DISABLED", + message: req.t("auth:register.REGISTRATION_DISABLED"), + }, + }); + } - // check if the user agreed to the Terms of Service - if (!body.consent) { - throw FieldErrors({ - consent: { - code: "CONSENT_REQUIRED", - message: req.t("auth:register.CONSENT_REQUIRED"), - }, - }); - } + // check if the user agreed to the Terms of Service + if (!body.consent) { + throw FieldErrors({ + consent: { + code: "CONSENT_REQUIRED", + message: req.t("auth:register.CONSENT_REQUIRED"), + }, + }); + } - if (!regTokenUsed && register.disabled) { - throw FieldErrors({ - email: { - code: "DISABLED", - message: "registration is disabled on this instance", - }, - }); - } + if (!regTokenUsed && register.disabled) { + throw FieldErrors({ + email: { + code: "DISABLED", + message: "registration is disabled on this instance", + }, + }); + } - if (!regTokenUsed && register.requireCaptcha && security.captcha.enabled) { - const { sitekey, service } = security.captcha; - if (!body.captcha_key) { - return res?.status(400).json({ - captcha_key: ["captcha-required"], - captcha_sitekey: sitekey, - captcha_service: service, - }); - } + if (!regTokenUsed && register.requireCaptcha && security.captcha.enabled) { + const { sitekey, service } = security.captcha; + if (!body.captcha_key) { + return res?.status(400).json({ + captcha_key: ["captcha-required"], + captcha_sitekey: sitekey, + captcha_service: service, + }); + } - const verify = await verifyCaptcha(body.captcha_key, ip); - if (!verify.success) { - return res.status(400).json({ - captcha_key: verify["error-codes"], - captcha_sitekey: sitekey, - captcha_service: service, - }); - } - } + const verify = await verifyCaptcha(body.captcha_key, ip); + if (!verify.success) { + return res.status(400).json({ + captcha_key: verify["error-codes"], + captcha_sitekey: sitekey, + captcha_service: service, + }); + } + } - if (!regTokenUsed && !register.allowMultipleAccounts) { - // TODO: check if fingerprint was eligible generated - const exists = await User.findOne({ - where: { fingerprints: body.fingerprint }, - select: ["id"], - }); + if (!regTokenUsed && !register.allowMultipleAccounts) { + // TODO: check if fingerprint was eligible generated + const exists = await User.findOne({ + where: { fingerprints: body.fingerprint }, + select: ["id"], + }); - if (exists) { - throw FieldErrors({ - email: { - code: "EMAIL_ALREADY_REGISTERED", - message: req.t("auth:register.EMAIL_ALREADY_REGISTERED"), - }, - }); - } - } + if (exists) { + throw FieldErrors({ + email: { + code: "EMAIL_ALREADY_REGISTERED", + message: req.t("auth:register.EMAIL_ALREADY_REGISTERED"), + }, + }); + } + } - if (!regTokenUsed && register.checkIp) { - const blacklist = await AbuseIpDbClient.getBlacklist(); - if (blacklist) { - const entry = blacklist.data.find((e) => e.ipAddress === ip); + if (!regTokenUsed && register.checkIp) { + const blacklist = await AbuseIpDbClient.getBlacklist(); + if (blacklist) { + const entry = blacklist.data.find((e) => e.ipAddress === ip); - if (entry && entry.abuseConfidenceScore >= register.blockAbuseIpDbAboveScore) { - console.log(`[Register] ${ip} blocked from registration: AbuseIPDB score ${entry.abuseConfidenceScore} >= ${register.blockAbuseIpDbAboveScore} (BLACKLIST)`); - throw new HTTPError("Your IP is blocked from registration"); - } - } + if (entry && entry.abuseConfidenceScore >= register.blockAbuseIpDbAboveScore) { + console.log(`[Register] ${ip} blocked from registration: AbuseIPDB score ${entry.abuseConfidenceScore} >= ${register.blockAbuseIpDbAboveScore} (BLACKLIST)`); + throw new HTTPError("Your IP is blocked from registration"); + } + } - const checkIp = await AbuseIpDbClient.checkIpAddress(ip); - if (checkIp?.data && checkIp.data.abuseConfidenceScore >= register.blockAbuseIpDbAboveScore) { - console.log(`[Register] ${ip} blocked from registration: AbuseIPDB score ${checkIp.data.abuseConfidenceScore} >= ${register.blockAbuseIpDbAboveScore} (CHECK)`); - throw new HTTPError("Your IP is blocked from registration"); - } + const checkIp = await AbuseIpDbClient.checkIpAddress(ip); + if (checkIp?.data && checkIp.data.abuseConfidenceScore >= register.blockAbuseIpDbAboveScore) { + console.log(`[Register] ${ip} blocked from registration: AbuseIPDB score ${checkIp.data.abuseConfidenceScore} >= ${register.blockAbuseIpDbAboveScore} (CHECK)`); + throw new HTTPError("Your IP is blocked from registration"); + } - const ipData = await IpDataClient.getIpInfo(ip); - if (ipData) { - if (!ipData.threat) { - console.log("Invalid IPData.co response, missing threat field", ipData); - } - const categories = Object.entries(ipData.threat) - .filter(([key, value]) => key.startsWith("is_") && value === true) - .map(([key]) => key.replace("is_", "")); - const blockedCategories = new Set(categories).intersection(new Set(register.blockIpDataCoThreatTypes)); - if (blockedCategories.size > 0) { - console.log(`[Register] ${ip} blocked from registration: IPData.co threat types ${Array.from(blockedCategories).join(", ")}`); - throw new HTTPError("Your IP is blocked from registration"); - } + const ipData = await IpDataClient.getIpInfo(ip); + if (ipData) { + if (!ipData.threat) { + console.log("Invalid IPData.co response, missing threat field", ipData); + } + const categories = Object.entries(ipData.threat) + .filter(([key, value]) => key.startsWith("is_") && value === true) + .map(([key]) => key.replace("is_", "")); + const blockedCategories = new Set(categories).intersection(new Set(register.blockIpDataCoThreatTypes)); + if (blockedCategories.size > 0) { + console.log(`[Register] ${ip} blocked from registration: IPData.co threat types ${Array.from(blockedCategories).join(", ")}`); + throw new HTTPError("Your IP is blocked from registration"); + } - if (register.blockAsnTypes.includes(ipData.asn.type)) { - console.log(`[Register] ${ip} blocked from registration: IPData.co ASN type ${ipData.asn.type} is blocked`); - throw new HTTPError("Your IP is blocked from registration"); - } + if (register.blockAsnTypes.includes(ipData.asn.type)) { + console.log(`[Register] ${ip} blocked from registration: IPData.co ASN type ${ipData.asn.type} is blocked`); + throw new HTTPError("Your IP is blocked from registration"); + } - if (register.blockAsns.includes(ipData.asn.asn)) { - console.log(`[Register] ${ip} blocked from registration: IPData.co ASN ${ipData.asn.name} is blocked`); - throw new HTTPError("Your IP is blocked from registration"); - } + if (register.blockAsns.includes(ipData.asn.asn)) { + console.log(`[Register] ${ip} blocked from registration: IPData.co ASN ${ipData.asn.name} is blocked`); + throw new HTTPError("Your IP is blocked from registration"); + } - if (register.blockProxies && IpDataClient.isProxy(ipData)) { - console.log(`[Register] ${ip} blocked from registration: IPData.co response matched IpDataClient.isProxy() check`); - throw new HTTPError("Your IP is blocked from registration"); - } - } - } + if (register.blockProxies && IpDataClient.isProxy(ipData)) { + console.log(`[Register] ${ip} blocked from registration: IPData.co response matched IpDataClient.isProxy() check`); + throw new HTTPError("Your IP is blocked from registration"); + } + } + } - // TODO: gift_code_sku_id? - // TODO: check password strength + // TODO: gift_code_sku_id? + // TODO: check password strength - const email = body.email; - if (email) { - // replace all dots and chars after +, if its a gmail.com email - if (!email) { - throw FieldErrors({ - email: { - code: "INVALID_EMAIL", - message: req?.t("auth:register.INVALID_EMAIL"), - }, - }); - } + const email = body.email; + if (email) { + // replace all dots and chars after +, if its a gmail.com email + if (!email) { + throw FieldErrors({ + email: { + code: "INVALID_EMAIL", + message: req?.t("auth:register.INVALID_EMAIL"), + }, + }); + } - // check if there is already an account with this email - const exists = await User.findOne({ where: { email: email } }); + // check if there is already an account with this email + const exists = await User.findOne({ where: { email: email } }); - if (exists) { - throw FieldErrors({ - email: { - code: "EMAIL_ALREADY_REGISTERED", - message: req.t("auth:register.EMAIL_ALREADY_REGISTERED"), - }, - }); - } - } else if (register.email.required) { - throw FieldErrors({ - email: { - code: "BASE_TYPE_REQUIRED", - message: req.t("common:field.BASE_TYPE_REQUIRED"), - }, - }); - } + if (exists) { + throw FieldErrors({ + email: { + code: "EMAIL_ALREADY_REGISTERED", + message: req.t("auth:register.EMAIL_ALREADY_REGISTERED"), + }, + }); + } + } else if (register.email.required) { + throw FieldErrors({ + email: { + code: "BASE_TYPE_REQUIRED", + message: req.t("common:field.BASE_TYPE_REQUIRED"), + }, + }); + } - if (register.dateOfBirth.required && !body.date_of_birth) { - throw FieldErrors({ - date_of_birth: { - code: "BASE_TYPE_REQUIRED", - message: req.t("common:field.BASE_TYPE_REQUIRED"), - }, - }); - } else if (register.dateOfBirth.required && register.dateOfBirth.minimum) { - const minimum = new Date(); - minimum.setFullYear(minimum.getFullYear() - register.dateOfBirth.minimum); + if (register.dateOfBirth.required && !body.date_of_birth) { + throw FieldErrors({ + date_of_birth: { + code: "BASE_TYPE_REQUIRED", + message: req.t("common:field.BASE_TYPE_REQUIRED"), + }, + }); + } else if (register.dateOfBirth.required && register.dateOfBirth.minimum) { + const minimum = new Date(); + minimum.setFullYear(minimum.getFullYear() - register.dateOfBirth.minimum); - let parsedDob; - try { - parsedDob = new Date(body.date_of_birth as Date); - if (isNaN(parsedDob.getTime())) { - throw new Error("Invalid date"); - } - } catch (e) { - throw FieldErrors({ - date_of_birth: { - code: "DATE_OF_BIRTH_INVALID", - message: req.t("auth:register.DATE_OF_BIRTH_INVALID"), - }, - }); - } + let parsedDob; + try { + parsedDob = new Date(body.date_of_birth as Date); + if (isNaN(parsedDob.getTime())) { + throw new Error("Invalid date"); + } + } catch (e) { + throw FieldErrors({ + date_of_birth: { + code: "DATE_OF_BIRTH_INVALID", + message: req.t("auth:register.DATE_OF_BIRTH_INVALID"), + }, + }); + } - // higher is younger - if (parsedDob > minimum) { - throw FieldErrors({ - date_of_birth: { - code: "DATE_OF_BIRTH_UNDERAGE", - message: req.t("auth:register.DATE_OF_BIRTH_UNDERAGE", { - years: register.dateOfBirth.minimum, - }), - }, - }); - } - } + // higher is younger + if (parsedDob > minimum) { + throw FieldErrors({ + date_of_birth: { + code: "DATE_OF_BIRTH_UNDERAGE", + message: req.t("auth:register.DATE_OF_BIRTH_UNDERAGE", { + years: register.dateOfBirth.minimum, + }), + }, + }); + } + } - if (body.password) { - const min = register.password.minLength ?? 8; + if (body.password) { + const min = register.password.minLength ?? 8; - if (body.password.length < min) { - throw FieldErrors({ - password: { - code: "PASSWORD_REQUIREMENTS_MIN_LENGTH", - message: req.t("auth:register.PASSWORD_REQUIREMENTS_MIN_LENGTH", { min: min }), - }, - }); - } - // the salt is saved in the password refer to bcrypt docs - body.password = await bcrypt.hash(body.password, 12); - } else if (register.password.required) { - throw FieldErrors({ - password: { - code: "BASE_TYPE_REQUIRED", - message: req.t("common:field.BASE_TYPE_REQUIRED"), - }, - }); - } + if (body.password.length < min) { + throw FieldErrors({ + password: { + code: "PASSWORD_REQUIREMENTS_MIN_LENGTH", + message: req.t("auth:register.PASSWORD_REQUIREMENTS_MIN_LENGTH", { min: min }), + }, + }); + } + // the salt is saved in the password refer to bcrypt docs + body.password = await bcrypt.hash(body.password, 12); + } else if (register.password.required) { + throw FieldErrors({ + password: { + code: "BASE_TYPE_REQUIRED", + message: req.t("common:field.BASE_TYPE_REQUIRED"), + }, + }); + } - if (!regTokenUsed && !body.invite && (register.requireInvite || (register.guestsRequireInvite && !register.email))) { - // require invite to register -> e.g. for organizations to send invites to their employees - throw FieldErrors({ - email: { - code: "INVITE_ONLY", - message: req.t("auth:register.INVITE_ONLY"), - }, - }); - } + if (!regTokenUsed && !body.invite && (register.requireInvite || (register.guestsRequireInvite && !register.email))) { + // require invite to register -> e.g. for organizations to send invites to their employees + throw FieldErrors({ + email: { + code: "INVITE_ONLY", + message: req.t("auth:register.INVITE_ONLY"), + }, + }); + } - if ( - !regTokenUsed && - limits.absoluteRate.register.enabled && - (await User.count({ - where: { - created_at: MoreThan(new Date(Date.now() - limits.absoluteRate.register.window)), - }, - })) >= limits.absoluteRate.register.limit - ) { - console.log(`Global register ratelimit exceeded for ${req.ip}, ${req.body.username}, ${req.body.invite || "No invite given"}`); - throw FieldErrors({ - email: { - code: "TOO_MANY_REGISTRATIONS", - message: req.t("auth:register.TOO_MANY_REGISTRATIONS"), - }, - }); - } + if ( + !regTokenUsed && + limits.absoluteRate.register.enabled && + (await User.count({ + where: { + created_at: MoreThan(new Date(Date.now() - limits.absoluteRate.register.window)), + }, + })) >= limits.absoluteRate.register.limit + ) { + console.log(`Global register ratelimit exceeded for ${req.ip}, ${req.body.username}, ${req.body.invite || "No invite given"}`); + throw FieldErrors({ + email: { + code: "TOO_MANY_REGISTRATIONS", + message: req.t("auth:register.TOO_MANY_REGISTRATIONS"), + }, + }); + } - const { maxUsername } = Config.get().limits.user; - if (body.username.length > maxUsername) { - throw FieldErrors({ - username: { - code: "BASE_TYPE_BAD_LENGTH", - message: `Must be between 2 and ${maxUsername} in length.`, - }, - }); - } + const { maxUsername } = Config.get().limits.user; + if (body.username.length > maxUsername) { + throw FieldErrors({ + username: { + code: "BASE_TYPE_BAD_LENGTH", + message: `Must be between 2 and ${maxUsername} in length.`, + }, + }); + } - const user = await User.register({ ...body, req }); + const user = await User.register({ ...body, req }); - if (body.invite) { - // await to fail if the invite doesn't exist (necessary for requireInvite to work properly) (username only signups are possible) - await Invite.joinGuild(user.id, body.invite); - } + if (body.invite) { + // await to fail if the invite doesn't exist (necessary for requireInvite to work properly) (username only signups are possible) + await Invite.joinGuild(user.id, body.invite); + } - 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/auth/reset.ts b/src/api/routes/auth/reset.ts
index 69f97d28..77e93e84 100644 --- a/src/api/routes/auth/reset.ts +++ b/src/api/routes/auth/reset.ts
@@ -26,54 +26,54 @@ const router = Router({ mergeParams: true }); // TODO: the response interface also returns settings, but this route doesn't actually return that. router.post( - "/", - route({ - requestBody: "PasswordResetSchema", - responses: { - 200: { - body: "TokenOnlyResponse", - }, - 400: { - body: "APIErrorOrCaptchaResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { password, token } = req.body as PasswordResetSchema; + "/", + route({ + requestBody: "PasswordResetSchema", + responses: { + 200: { + body: "TokenOnlyResponse", + }, + 400: { + body: "APIErrorOrCaptchaResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { password, token } = req.body as PasswordResetSchema; - let user; - try { - const userTokenData = await checkToken(token, { - select: ["email"], - fingerprint: req.fingerprint, - ipAddress: req.ip, - }); - user = userTokenData.user; - } catch { - throw FieldErrors({ - password: { - message: req.t("auth:password_reset.INVALID_TOKEN"), - code: "INVALID_TOKEN", - }, - }); - } + let user; + try { + const userTokenData = await checkToken(token, { + select: ["email"], + fingerprint: req.fingerprint, + ipAddress: req.ip, + }); + user = userTokenData.user; + } catch { + throw FieldErrors({ + password: { + message: req.t("auth:password_reset.INVALID_TOKEN"), + code: "INVALID_TOKEN", + }, + }); + } - // the salt is saved in the password refer to bcrypt docs - const hash = await bcrypt.hash(password, 12); + // the salt is saved in the password refer to bcrypt docs + const hash = await bcrypt.hash(password, 12); - const data = { - data: { - hash, - valid_tokens_since: new Date(), - }, - }; - await User.update({ id: user.id }, data); + const data = { + data: { + hash, + valid_tokens_since: new Date(), + }, + }; + await User.update({ id: user.id }, data); - // come on, the user has to have an email to reset their password in the first place - await Email.sendPasswordChanged(user, user.email!); + // come on, the user has to have an email to reset their password in the first place + await Email.sendPasswordChanged(user, user.email!); - res.json({ token: await generateToken(user.id) }); - }, + res.json({ token: await generateToken(user.id) }); + }, ); export default router; diff --git a/src/api/routes/auth/sessions.ts b/src/api/routes/auth/sessions.ts
index 6c721f2e..3a9095f1 100644 --- a/src/api/routes/auth/sessions.ts +++ b/src/api/routes/auth/sessions.ts
@@ -23,54 +23,54 @@ import { SessionsLogoutSchema } from "../../../schemas/api/users/SessionsSchemas import { In } from "typeorm"; const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "GetSessionsResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { extended = false } = req.query; - const sessions = (await Session.find({ where: { user_id: req.user_id, is_admin_session: false } })) as Session[]; + "/", + route({ + responses: { + 200: { + body: "GetSessionsResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { extended = false } = req.query; + const sessions = (await Session.find({ where: { user_id: req.user_id, is_admin_session: false } })) as Session[]; - res.json({ - user_sessions: sessions.map((session) => (extended ? session.getExtendedDeviceInfo() : session.getDiscordDeviceInfo())), - }); - }, + res.json({ + user_sessions: sessions.map((session) => (extended ? session.getExtendedDeviceInfo() : session.getDiscordDeviceInfo())), + }); + }, ); router.post( - "/logout", - route({ - requestBody: "SessionsLogoutSchema", - responses: { - 204: {}, - }, - }), - async (req: Request, res: Response) => { - const body = req.body as SessionsLogoutSchema; + "/logout", + route({ + requestBody: "SessionsLogoutSchema", + responses: { + 204: {}, + }, + }), + async (req: Request, res: Response) => { + const body = req.body as SessionsLogoutSchema; - let sessions: Session[] = []; - if ("session_ids" in body) { - sessions = (await Session.find({ where: { user_id: req.user_id, session_id: In(body.session_ids!) } })) as Session[]; - } + let sessions: Session[] = []; + if ("session_ids" in body) { + sessions = (await Session.find({ where: { user_id: req.user_id, session_id: In(body.session_ids!) } })) as Session[]; + } - if ("session_id_hashes" in body) { - const allSessions = (await Session.find({ where: { user_id: req.user_id } })) as Session[]; - const hashSet = new Set(body.session_id_hashes); - const matchingSessions = allSessions.filter((session) => { - const hash = createHash("sha256").update(session.session_id).digest("hex"); - return hashSet.has(hash); - }); - sessions.push(...matchingSessions); - } + if ("session_id_hashes" in body) { + const allSessions = (await Session.find({ where: { user_id: req.user_id } })) as Session[]; + const hashSet = new Set(body.session_id_hashes); + const matchingSessions = allSessions.filter((session) => { + const hash = createHash("sha256").update(session.session_id).digest("hex"); + return hashSet.has(hash); + }); + sessions.push(...matchingSessions); + } - for (const session of sessions) { - await session.remove(); - } - res.status(204).send(); - }, + for (const session of sessions) { + await session.remove(); + } + res.status(204).send(); + }, ); export default router; diff --git a/src/api/routes/auth/verify/index.ts b/src/api/routes/auth/verify/index.ts
index 43fb23a0..7c8ca187 100644 --- a/src/api/routes/auth/verify/index.ts +++ b/src/api/routes/auth/verify/index.ts
@@ -22,79 +22,79 @@ import { Request, Response, Router } from "express"; const router = Router({ mergeParams: true }); async function getToken(user: User) { - const token = await generateToken(user.id); + const token = await generateToken(user.id); - // Notice this will have a different token structure, than discord - // Discord header is just the user id as string, which is not possible with npm-jsonwebtoken package - // https://user-images.githubusercontent.com/6506416/81051916-dd8c9900-8ec2-11ea-8794-daf12d6f31f0.png + // Notice this will have a different token structure, than discord + // Discord header is just the user id as string, which is not possible with npm-jsonwebtoken package + // https://user-images.githubusercontent.com/6506416/81051916-dd8c9900-8ec2-11ea-8794-daf12d6f31f0.png - return { token }; + return { token }; } // TODO: the response interface also returns settings, but this route doesn't actually return that. router.post( - "/", - route({ - requestBody: "VerifyEmailSchema", - responses: { - 200: { - body: "TokenResponse", - }, - 400: { - body: "APIErrorOrCaptchaResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { captcha_key, token } = req.body; + "/", + route({ + requestBody: "VerifyEmailSchema", + responses: { + 200: { + body: "TokenResponse", + }, + 400: { + body: "APIErrorOrCaptchaResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { captcha_key, token } = req.body; - const config = Config.get(); + const config = Config.get(); - if (config.register.requireCaptcha && config.security.captcha.enabled) { - const { sitekey, service } = config.security.captcha; + if (config.register.requireCaptcha && config.security.captcha.enabled) { + const { sitekey, service } = config.security.captcha; - if (!captcha_key) { - return res.status(400).json({ - captcha_key: ["captcha-required"], - captcha_sitekey: sitekey, - captcha_service: service, - }); - } + if (!captcha_key) { + return res.status(400).json({ + captcha_key: ["captcha-required"], + captcha_sitekey: sitekey, + captcha_service: service, + }); + } - const ip = req.ip; - const verify = await verifyCaptcha(captcha_key, ip); - if (!verify.success) { - return res.status(400).json({ - captcha_key: verify["error-codes"], - captcha_sitekey: sitekey, - captcha_service: service, - }); - } - } + const ip = req.ip; + const verify = await verifyCaptcha(captcha_key, ip); + if (!verify.success) { + return res.status(400).json({ + captcha_key: verify["error-codes"], + captcha_sitekey: sitekey, + captcha_service: service, + }); + } + } - let user; + let user; - try { - const userTokenData = await checkToken(token, { - fingerprint: req.fingerprint, - ipAddress: req.ip, - }); - user = userTokenData.user; - } catch { - throw FieldErrors({ - token: { - message: req.t("auth:password_reset.INVALID_TOKEN"), - code: "INVALID_TOKEN", - }, - }); - } + try { + const userTokenData = await checkToken(token, { + fingerprint: req.fingerprint, + ipAddress: req.ip, + }); + user = userTokenData.user; + } catch { + throw FieldErrors({ + token: { + message: req.t("auth:password_reset.INVALID_TOKEN"), + code: "INVALID_TOKEN", + }, + }); + } - if (user.verified) return res.json(await getToken(user)); + if (user.verified) return res.json(await getToken(user)); - await User.update({ id: user.id }, { verified: true }); + await User.update({ id: user.id }, { verified: true }); - return res.json(await getToken(user)); - }, + return res.json(await getToken(user)); + }, ); export default router; diff --git a/src/api/routes/auth/verify/resend.ts b/src/api/routes/auth/verify/resend.ts
index 4ef1d518..ce6672a7 100644 --- a/src/api/routes/auth/verify/resend.ts +++ b/src/api/routes/auth/verify/resend.ts
@@ -23,43 +23,43 @@ import { HTTPError } from "lambert-server"; const router = Router({ mergeParams: true }); router.post( - "/", - route({ - right: "RESEND_VERIFICATION_EMAIL", - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 500: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const user = await User.findOneOrFail({ - where: { id: req.user_id }, - select: ["username", "email", "verified"], - }); + "/", + route({ + right: "RESEND_VERIFICATION_EMAIL", + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 500: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const user = await User.findOneOrFail({ + where: { id: req.user_id }, + select: ["username", "email", "verified"], + }); - if (!user.email) { - // TODO: whats the proper error response for this? - throw new HTTPError("User does not have an email address", 400); - } + if (!user.email) { + // TODO: whats the proper error response for this? + throw new HTTPError("User does not have an email address", 400); + } - if (user.verified) { - throw new HTTPError("Email is already verified", 400); - } + if (user.verified) { + throw new HTTPError("Email is already verified", 400); + } - await Email.sendVerifyEmail(user, user.email) - .then(() => { - return res.sendStatus(204); - }) - .catch((e) => { - console.error(`Failed to send verification email to ${user.username}#${user.discriminator}: ${e}`); - throw new HTTPError("Failed to send verification email", 500); - }); - }, + await Email.sendVerifyEmail(user, user.email) + .then(() => { + return res.sendStatus(204); + }) + .catch((e) => { + console.error(`Failed to send verification email to ${user.username}#${user.discriminator}: ${e}`); + throw new HTTPError("Failed to send verification email", 500); + }); + }, ); export default router; diff --git a/src/api/routes/auth/verify/view-backup-codes-challenge.ts b/src/api/routes/auth/verify/view-backup-codes-challenge.ts
index 7c15795c..d3d40d3e 100644 --- a/src/api/routes/auth/verify/view-backup-codes-challenge.ts +++ b/src/api/routes/auth/verify/view-backup-codes-challenge.ts
@@ -24,36 +24,36 @@ import { BackupCodesChallengeSchema } from "@spacebar/schemas"; const router = Router({ mergeParams: true }); router.post( - "/", - route({ - requestBody: "BackupCodesChallengeSchema", - responses: { - 200: { body: "BackupCodesChallengeResponse" }, - 400: { body: "APIErrorResponse" }, - }, - }), - async (req: Request, res: Response) => { - const { password } = req.body as BackupCodesChallengeSchema; + "/", + route({ + requestBody: "BackupCodesChallengeSchema", + responses: { + 200: { body: "BackupCodesChallengeResponse" }, + 400: { body: "APIErrorResponse" }, + }, + }), + async (req: Request, res: Response) => { + const { password } = req.body as BackupCodesChallengeSchema; - 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", + }, + }); + } - return res.json({ - nonce: "NoncePlaceholder", - regenerate_nonce: "RegenNoncePlaceholder", - }); - }, + return res.json({ + nonce: "NoncePlaceholder", + regenerate_nonce: "RegenNoncePlaceholder", + }); + }, ); export default router; diff --git a/src/api/routes/beaker.ts b/src/api/routes/beaker.ts
index 62df41e1..4eb2941a 100644 --- a/src/api/routes/beaker.ts +++ b/src/api/routes/beaker.ts
@@ -23,16 +23,16 @@ const router = Router({ mergeParams: true }); // screw off, telemetry requests router.post( - "/", - route({ - responses: { - 204: {}, - }, - }), - (req: Request, res: Response) => { - // TODO: - res.sendStatus(204); - }, + "/", + route({ + responses: { + 204: {}, + }, + }), + (req: Request, res: Response) => { + // TODO: + res.sendStatus(204); + }, ); export default router; diff --git a/src/api/routes/channels/#channel_id/attachments.ts b/src/api/routes/channels/#channel_id/attachments.ts
index 039bfa21..30fd2c63 100644 --- a/src/api/routes/channels/#channel_id/attachments.ts +++ b/src/api/routes/channels/#channel_id/attachments.ts
@@ -25,109 +25,109 @@ import { UploadAttachmentRequestSchema, UploadAttachmentResponseSchema } from "@ const router: Router = Router({ mergeParams: true }); router.post( - "/", - route({ - requestBody: "UploadAttachmentRequestSchema", - responses: { - 200: { - body: "UploadAttachmentResponseSchema", - }, - 404: {}, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const payload = req.body as UploadAttachmentRequestSchema; - const { channel_id } = req.params; + "/", + route({ + requestBody: "UploadAttachmentRequestSchema", + responses: { + 200: { + body: "UploadAttachmentResponseSchema", + }, + 404: {}, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const payload = req.body as UploadAttachmentRequestSchema; + const { channel_id } = req.params; - const user = await User.findOneOrFail({ where: { id: req.user_id } }); - const channel = await Channel.findOneOrFail({ where: { id: channel_id } }); + const user = await User.findOneOrFail({ where: { id: req.user_id } }); + const channel = await Channel.findOneOrFail({ where: { id: channel_id } }); - if (!(await channel.getUserPermissions({ user_id: req.user_id })).has(Permissions.FLAGS.ATTACH_FILES)) { - return res.status(403).json({ - code: 403, - message: "Missing Permissions: ATTACH_FILES", - }); - } + if (!(await channel.getUserPermissions({ user_id: req.user_id })).has(Permissions.FLAGS.ATTACH_FILES)) { + return res.status(403).json({ + code: 403, + message: "Missing Permissions: ATTACH_FILES", + }); + } - const cdnUrl = Config.get().cdn.endpointPublic; - const batchId = `CLOUD_${user.id}_${randomString(128)}`; + const cdnUrl = Config.get().cdn.endpointPublic; + const batchId = `CLOUD_${user.id}_${randomString(128)}`; - // validate IDs - const seenIds: (string | undefined)[] = []; - for (const file of payload.files) { - if (seenIds.includes(file.id)) { - return res.status(400).json({ - code: 400, - message: `Duplicate attachment ID: ${file.id}`, - }); - } - seenIds.push(file.id); - } + // validate IDs + const seenIds: (string | undefined)[] = []; + for (const file of payload.files) { + if (seenIds.includes(file.id)) { + return res.status(400).json({ + code: 400, + message: `Duplicate attachment ID: ${file.id}`, + }); + } + seenIds.push(file.id); + } - const attachments = await Promise.all( - payload.files.map(async (attachment) => { - attachment.filename = attachment.filename.replaceAll(" ", "_").replace(/[^a-zA-Z0-9._]+/g, ""); - const uploadFilename = `${channel_id}/${batchId}/${attachment.id ?? "0"}/${attachment.filename}`; - const newAttachment = CloudAttachment.create({ - user: user, - channel: channel, - uploadFilename: uploadFilename, - userAttachmentId: attachment.id ?? "0", - userFilename: attachment.filename, - userFileSize: attachment.file_size, - userIsClip: attachment.is_clip, - userOriginalContentType: attachment.original_content_type, - }); - await newAttachment.save(); - return newAttachment; - }), - ); + const attachments = await Promise.all( + payload.files.map(async (attachment) => { + attachment.filename = attachment.filename.replaceAll(" ", "_").replace(/[^a-zA-Z0-9._]+/g, ""); + const uploadFilename = `${channel_id}/${batchId}/${attachment.id ?? "0"}/${attachment.filename}`; + const newAttachment = CloudAttachment.create({ + user: user, + channel: channel, + uploadFilename: uploadFilename, + userAttachmentId: attachment.id ?? "0", + userFilename: attachment.filename, + userFileSize: attachment.file_size, + userIsClip: attachment.is_clip, + userOriginalContentType: attachment.original_content_type, + }); + await newAttachment.save(); + return newAttachment; + }), + ); - res.send({ - attachments: attachments.map((a) => { - return { - id: a.userAttachmentId, - upload_filename: a.uploadFilename, - upload_url: `${cdnUrl}/attachments/${a.uploadFilename}`, - original_content_type: a.userOriginalContentType, - }; - }), - } as UploadAttachmentResponseSchema); - }, + res.send({ + attachments: attachments.map((a) => { + return { + id: a.userAttachmentId, + upload_filename: a.uploadFilename, + upload_url: `${cdnUrl}/attachments/${a.uploadFilename}`, + original_content_type: a.userOriginalContentType, + }; + }), + } as UploadAttachmentResponseSchema); + }, ); router.delete("/:cloud_attachment_url", async (req: Request, res: Response) => { - const { channel_id, cloud_attachment_url } = req.params; + const { channel_id, cloud_attachment_url } = req.params; - const user = await User.findOneOrFail({ where: { id: req.user_id } }); - const channel = await Channel.findOneOrFail({ where: { id: channel_id } }); - const att = await CloudAttachment.findOneOrFail({ where: { uploadFilename: decodeURI(cloud_attachment_url) } }); - if (att.userId !== user.id) { - return res.status(403).json({ - code: 403, - message: "You do not own this attachment.", - }); - } + const user = await User.findOneOrFail({ where: { id: req.user_id } }); + const channel = await Channel.findOneOrFail({ where: { id: channel_id } }); + const att = await CloudAttachment.findOneOrFail({ where: { uploadFilename: decodeURI(cloud_attachment_url) } }); + if (att.userId !== user.id) { + return res.status(403).json({ + code: 403, + message: "You do not own this attachment.", + }); + } - if (att.channelId !== channel.id) { - return res.status(400).json({ - code: 400, - message: "Attachment does not belong to this channel.", - }); - } + if (att.channelId !== channel.id) { + return res.status(400).json({ + code: 400, + message: "Attachment does not belong to this channel.", + }); + } - const response = await fetch(`${Config.get().cdn.endpointPrivate}/attachments/${att.uploadFilename}`, { - headers: { - signature: Config.get().security.requestSignature, - }, - method: "DELETE", - }); + const response = await fetch(`${Config.get().cdn.endpointPrivate}/attachments/${att.uploadFilename}`, { + headers: { + signature: Config.get().security.requestSignature, + }, + method: "DELETE", + }); - await att.remove(); - return res.status(response.status).send(response.body); + await att.remove(); + return res.status(response.status).send(response.body); }); export default router; diff --git a/src/api/routes/channels/#channel_id/directory-entries.ts b/src/api/routes/channels/#channel_id/directory-entries.ts
index c07b1f75..da950502 100644 --- a/src/api/routes/channels/#channel_id/directory-entries.ts +++ b/src/api/routes/channels/#channel_id/directory-entries.ts
@@ -22,20 +22,20 @@ import { Request, Response, Router } from "express"; const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "HubDirectoryEntriesResponse", - }, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - res.json([] as HubDirectoryEntriesResponse); - }, + "/", + route({ + responses: { + 200: { + body: "HubDirectoryEntriesResponse", + }, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + res.json([] as HubDirectoryEntriesResponse); + }, ); export default router; diff --git a/src/api/routes/channels/#channel_id/greet.ts b/src/api/routes/channels/#channel_id/greet.ts
index b6ce1ee5..f78f26ab 100644 --- a/src/api/routes/channels/#channel_id/greet.ts +++ b/src/api/routes/channels/#channel_id/greet.ts
@@ -25,83 +25,83 @@ import { GreetRequestSchema, MessageType } from "@spacebar/schemas"; const router: Router = Router({ mergeParams: true }); router.post( - "/", - route({ - requestBody: "GreetRequestSchema", - permission: "MANAGE_CHANNELS", - responses: { - 200: { - body: "Message", - }, - 404: {}, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const payload = req.body as GreetRequestSchema; - const { channel_id } = req.params; + "/", + route({ + requestBody: "GreetRequestSchema", + permission: "MANAGE_CHANNELS", + responses: { + 200: { + body: "Message", + }, + 404: {}, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const payload = req.body as GreetRequestSchema; + const { channel_id } = req.params; - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - }); + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); - const targetMessage = await Message.findOneOrFail({ - where: { - id: payload.message_reference?.message_id, - channel_id: payload.message_reference?.channel_id, - guild_id: payload.message_reference?.guild_id, - }, - }); + const targetMessage = await Message.findOneOrFail({ + where: { + id: payload.message_reference?.message_id, + channel_id: payload.message_reference?.channel_id, + guild_id: payload.message_reference?.guild_id, + }, + }); - if (!channel.isDm() && targetMessage.type != MessageType.GUILD_MEMBER_JOIN) - return res.status(400).json({ - code: 400, // TODO: what's the actual error code? - message: "Cannot send greet message referencing this message.", - }); + if (!channel.isDm() && targetMessage.type != MessageType.GUILD_MEMBER_JOIN) + return res.status(400).json({ + code: 400, // TODO: what's the actual error code? + message: "Cannot send greet message referencing this message.", + }); - if (!(await channel.getUserPermissions({ user_id: req.user_id })).has(Permissions.FLAGS.SEND_MESSAGES)) { - return res.status(403).json({ - code: 403, - message: "Missing Permissions: SEND_MESSAGES", - }); - } + if (!(await channel.getUserPermissions({ user_id: req.user_id })).has(Permissions.FLAGS.SEND_MESSAGES)) { + return res.status(403).json({ + code: 403, + message: "Missing Permissions: SEND_MESSAGES", + }); + } - const specCompliant = true; // incase we want to allow clients to add more than one sticker to pick - if (specCompliant && payload.sticker_ids.length != 1) - return res.status(400).json({ - code: 400, - message: "Must include exactly one sticker.", - }); + const specCompliant = true; // incase we want to allow clients to add more than one sticker to pick + if (specCompliant && payload.sticker_ids.length != 1) + return res.status(400).json({ + code: 400, + message: "Must include exactly one sticker.", + }); - const stickers = await Sticker.find({ where: { id: In(payload.sticker_ids) } }); + const stickers = await Sticker.find({ where: { id: In(payload.sticker_ids) } }); - const randomSticker = stickers[Math.floor(Math.random() * stickers.length)]; + const randomSticker = stickers[Math.floor(Math.random() * stickers.length)]; - const message = Message.create({ - channel_id: channel_id, - author_id: req.user_id, - type: MessageType.REPLY, - message_reference: { ...payload.message_reference, type: 0 }, - referenced_message: targetMessage, - sticker_items: randomSticker ? [{ id: randomSticker.id, name: randomSticker.name, format_type: randomSticker.format_type }] : [], - }); + const message = Message.create({ + channel_id: channel_id, + author_id: req.user_id, + type: MessageType.REPLY, + message_reference: { ...payload.message_reference, type: 0 }, + referenced_message: targetMessage, + sticker_items: randomSticker ? [{ id: randomSticker.id, name: randomSticker.name, format_type: randomSticker.format_type }] : [], + }); - channel.last_message_id = message.id; + channel.last_message_id = message.id; - await Promise.all([ - message.save(), - emitEvent({ - event: "MESSAGE_CREATE", - data: message, - channel_id, - } as MessageCreateEvent), - channel.save(), - ]); + await Promise.all([ + message.save(), + emitEvent({ + event: "MESSAGE_CREATE", + data: message, + channel_id, + } as MessageCreateEvent), + channel.save(), + ]); - res.send(channel); - }, + res.send(channel); + }, ); export default router; diff --git a/src/api/routes/channels/#channel_id/index.ts b/src/api/routes/channels/#channel_id/index.ts
index 97857da6..37909a94 100644 --- a/src/api/routes/channels/#channel_id/index.ts +++ b/src/api/routes/channels/#channel_id/index.ts
@@ -26,132 +26,132 @@ const router: Router = Router({ mergeParams: true }); // TODO: Get channel router.get( - "/", - route({ - permission: "VIEW_CHANNEL", - responses: { - 200: { - body: "Channel", - }, - 404: {}, - }, - }), - async (req: Request, res: Response) => { - const { channel_id } = req.params; + "/", + route({ + permission: "VIEW_CHANNEL", + responses: { + 200: { + body: "Channel", + }, + 404: {}, + }, + }), + async (req: Request, res: Response) => { + const { channel_id } = req.params; - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - }); - if (!channel.guild_id) return res.send(channel); + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); + if (!channel.guild_id) return res.send(channel); - channel.position = await Channel.calculatePosition(channel_id, channel.guild_id, channel.guild); - return res.send(channel); - }, + channel.position = await Channel.calculatePosition(channel_id, channel.guild_id, channel.guild); + return res.send(channel); + }, ); router.delete( - "/", - route({ - permission: "MANAGE_CHANNELS", - responses: { - 200: { - body: "Channel", - }, - 404: {}, - }, - }), - async (req: Request, res: Response) => { - const { channel_id } = req.params; + "/", + route({ + permission: "MANAGE_CHANNELS", + responses: { + 200: { + body: "Channel", + }, + 404: {}, + }, + }), + async (req: Request, res: Response) => { + const { channel_id } = req.params; - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - relations: ["recipients"], - }); + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + relations: ["recipients"], + }); - if (channel.type === ChannelType.DM) { - const recipient = await Recipient.findOneOrFail({ - where: { channel_id: channel_id, user_id: req.user_id }, - }); - recipient.closed = true; - await Promise.all([ - recipient.save(), - emitEvent({ - event: "CHANNEL_DELETE", - data: channel, - user_id: req.user_id, - } as ChannelDeleteEvent), - ]); - } else if (channel.type === ChannelType.GROUP_DM) { - await Channel.removeRecipientFromChannel(channel, req.user_id); - } else { - if (channel.type == ChannelType.GUILD_CATEGORY) { - const channels = await Channel.find({ - where: { parent_id: channel_id }, - }); - for await (const c of channels) { - c.parent_id = null; + if (channel.type === ChannelType.DM) { + const recipient = await Recipient.findOneOrFail({ + where: { channel_id: channel_id, user_id: req.user_id }, + }); + recipient.closed = true; + await Promise.all([ + recipient.save(), + emitEvent({ + event: "CHANNEL_DELETE", + data: channel, + user_id: req.user_id, + } as ChannelDeleteEvent), + ]); + } else if (channel.type === ChannelType.GROUP_DM) { + await Channel.removeRecipientFromChannel(channel, req.user_id); + } else { + if (channel.type == ChannelType.GUILD_CATEGORY) { + const channels = await Channel.find({ + where: { parent_id: channel_id }, + }); + for await (const c of channels) { + c.parent_id = null; - await Promise.all([ - c.save(), - emitEvent({ - event: "CHANNEL_UPDATE", - data: c, - channel_id: c.id, - } as ChannelUpdateEvent), - ]); - } - } + await Promise.all([ + c.save(), + emitEvent({ + event: "CHANNEL_UPDATE", + data: c, + channel_id: c.id, + } as ChannelUpdateEvent), + ]); + } + } - await Promise.all([ - Channel.deleteChannel(channel), - emitEvent({ - event: "CHANNEL_DELETE", - data: channel, - channel_id, - } as ChannelDeleteEvent), - ]); - } + await Promise.all([ + Channel.deleteChannel(channel), + emitEvent({ + event: "CHANNEL_DELETE", + data: channel, + channel_id, + } as ChannelDeleteEvent), + ]); + } - res.send(channel); - }, + res.send(channel); + }, ); router.patch( - "/", - route({ - requestBody: "ChannelModifySchema", - permission: "MANAGE_CHANNELS", - responses: { - 200: { - body: "Channel", - }, - 404: {}, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const payload = req.body as ChannelModifySchema; - const { channel_id } = req.params; - if (payload.icon) payload.icon = await handleFile(`/channel-icons/${channel_id}`, payload.icon); + "/", + route({ + requestBody: "ChannelModifySchema", + permission: "MANAGE_CHANNELS", + responses: { + 200: { + body: "Channel", + }, + 404: {}, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const payload = req.body as ChannelModifySchema; + const { channel_id } = req.params; + if (payload.icon) payload.icon = await handleFile(`/channel-icons/${channel_id}`, payload.icon); - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - }); - channel.assign(payload); + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); + channel.assign(payload); - await Promise.all([ - channel.save(), - emitEvent({ - event: "CHANNEL_UPDATE", - data: channel, - channel_id, - } as ChannelUpdateEvent), - ]); + await Promise.all([ + channel.save(), + emitEvent({ + event: "CHANNEL_UPDATE", + data: channel, + channel_id, + } as ChannelUpdateEvent), + ]); - res.send(channel); - }, + res.send(channel); + }, ); export default router; diff --git a/src/api/routes/channels/#channel_id/invites.ts b/src/api/routes/channels/#channel_id/invites.ts
index 298f42ed..808f0f8c 100644 --- a/src/api/routes/channels/#channel_id/invites.ts +++ b/src/api/routes/channels/#channel_id/invites.ts
@@ -25,96 +25,96 @@ import { InviteCreateSchema, isTextChannel } from "@spacebar/schemas"; const router: Router = Router({ mergeParams: true }); router.post( - "/", - route({ - requestBody: "InviteCreateSchema", - permission: "CREATE_INSTANT_INVITE", - right: "CREATE_INVITES", - responses: { - 201: { - body: "Invite", - }, - 404: {}, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { user_id } = req; - const body = req.body as InviteCreateSchema; - const { channel_id } = req.params; - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - select: ["id", "name", "type", "guild_id"], - }); - isTextChannel(channel.type); + "/", + route({ + requestBody: "InviteCreateSchema", + permission: "CREATE_INSTANT_INVITE", + right: "CREATE_INVITES", + responses: { + 201: { + body: "Invite", + }, + 404: {}, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { user_id } = req; + const body = req.body as InviteCreateSchema; + const { channel_id } = req.params; + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + select: ["id", "name", "type", "guild_id"], + }); + isTextChannel(channel.type); - if (!channel.guild_id) { - throw new HTTPError("This channel doesn't exist", 404); - } - const { guild_id } = channel; + if (!channel.guild_id) { + throw new HTTPError("This channel doesn't exist", 404); + } + const { guild_id } = channel; - const expires_at = body.max_age == 0 || body.max_age == undefined ? undefined : new Date(body.max_age * 1000 + Date.now()); + const expires_at = body.max_age == 0 || body.max_age == undefined ? undefined : new Date(body.max_age * 1000 + Date.now()); - const invite = await Invite.create({ - code: randomString(), - temporary: body.temporary || true, - uses: 0, - max_uses: body.max_uses ? Math.max(0, body.max_uses) : 0, - max_age: body.max_age ? Math.max(0, body.max_age) : 0, - expires_at, - created_at: new Date(), - guild_id, - channel_id: channel_id, - inviter_id: user_id, - flags: body.flags ?? 0, - }).save(); + const invite = await Invite.create({ + code: randomString(), + temporary: body.temporary || true, + uses: 0, + max_uses: body.max_uses ? Math.max(0, body.max_uses) : 0, + max_age: body.max_age ? Math.max(0, body.max_age) : 0, + expires_at, + created_at: new Date(), + guild_id, + channel_id: channel_id, + inviter_id: user_id, + flags: body.flags ?? 0, + }).save(); - const data = invite.toJSON(); - data.inviter = (await User.getPublicUser(req.user_id)).toPublicUser(); - data.guild = await Guild.findOne({ where: { id: guild_id } }); - data.channel = channel; + const data = invite.toJSON(); + data.inviter = (await User.getPublicUser(req.user_id)).toPublicUser(); + data.guild = await Guild.findOne({ where: { id: guild_id } }); + data.channel = channel; - await emitEvent({ - event: "INVITE_CREATE", - data, - guild_id, - } as InviteCreateEvent); + await emitEvent({ + event: "INVITE_CREATE", + data, + guild_id, + } as InviteCreateEvent); - res.status(201).send(data); - }, + res.status(201).send(data); + }, ); router.get( - "/", - route({ - permission: "MANAGE_CHANNELS", - responses: { - 200: { - body: "APIInviteArray", - }, - 404: {}, - }, - }), - async (req: Request, res: Response) => { - const { channel_id } = req.params; - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - }); + "/", + route({ + permission: "MANAGE_CHANNELS", + responses: { + 200: { + body: "APIInviteArray", + }, + 404: {}, + }, + }), + async (req: Request, res: Response) => { + const { channel_id } = req.params; + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); - if (!channel.guild_id) { - throw new HTTPError("This channel doesn't exist", 404); - } - const { guild_id } = channel; + if (!channel.guild_id) { + throw new HTTPError("This channel doesn't exist", 404); + } + const { guild_id } = channel; - const invites = await Invite.find({ - where: { guild_id, channel_id }, - relations: PublicInviteRelation, - }); + const invites = await Invite.find({ + where: { guild_id, channel_id }, + relations: PublicInviteRelation, + }); - res.status(200).send(invites); - }, + res.status(200).send(invites); + }, ); export default router; diff --git a/src/api/routes/channels/#channel_id/messages/#message_id/ack.ts b/src/api/routes/channels/#channel_id/messages/#message_id/ack.ts
index c7c96fea..7b9f0397 100644 --- a/src/api/routes/channels/#channel_id/messages/#message_id/ack.ts +++ b/src/api/routes/channels/#channel_id/messages/#message_id/ack.ts
@@ -27,42 +27,42 @@ const router = Router({ mergeParams: true }); // TODO: advance-only notification cursor router.post( - "/", - route({ - requestBody: "MessageAcknowledgeSchema", - responses: { - 200: {}, - 403: {}, - }, - }), - async (req: Request, res: Response) => { - const { channel_id, message_id } = req.params; + "/", + route({ + requestBody: "MessageAcknowledgeSchema", + responses: { + 200: {}, + 403: {}, + }, + }), + async (req: Request, res: Response) => { + const { channel_id, message_id } = req.params; - const permission = await getPermission(req.user_id, undefined, channel_id); - permission.hasThrow("VIEW_CHANNEL"); + const permission = await getPermission(req.user_id, undefined, channel_id); + permission.hasThrow("VIEW_CHANNEL"); - let read_state = await ReadState.findOne({ - where: { user_id: req.user_id, channel_id }, - }); - if (!read_state) read_state = ReadState.create({ user_id: req.user_id, channel_id }); - read_state.last_message_id = message_id; - //It's a little more complicated but this'll do :P - read_state.mention_count = 0; + let read_state = await ReadState.findOne({ + where: { user_id: req.user_id, channel_id }, + }); + if (!read_state) read_state = ReadState.create({ user_id: req.user_id, channel_id }); + read_state.last_message_id = message_id; + //It's a little more complicated but this'll do :P + read_state.mention_count = 0; - await read_state.save(); + await read_state.save(); - await emitEvent({ - event: "MESSAGE_ACK", - user_id: req.user_id, - data: { - channel_id, - message_id, - version: 3763, - }, - } as MessageAckEvent); + await emitEvent({ + event: "MESSAGE_ACK", + user_id: req.user_id, + data: { + channel_id, + message_id, + version: 3763, + }, + } as MessageAckEvent); - res.json({ token: null }); - }, + res.json({ token: null }); + }, ); export default router; diff --git a/src/api/routes/channels/#channel_id/messages/#message_id/crosspost.ts b/src/api/routes/channels/#channel_id/messages/#message_id/crosspost.ts
index 6d89c504..2aad157d 100644 --- a/src/api/routes/channels/#channel_id/messages/#message_id/crosspost.ts +++ b/src/api/routes/channels/#channel_id/messages/#message_id/crosspost.ts
@@ -22,43 +22,43 @@ import { Request, Response, Router } from "express"; const router = Router({ mergeParams: true }); router.post( - "/", - route({ - permission: "MANAGE_MESSAGES", - responses: { - 200: { - body: "Message", - }, - }, - }), - (req: Request, res: Response) => { - // TODO: - res.json({ - id: "", - type: 0, - content: "", - channel_id: "", - author: { - id: "", - username: "", - avatar: "", - discriminator: "", - public_flags: 64, - }, - attachments: [], - embeds: [], - mentions: [], - mention_roles: [], - pinned: false, - mention_everyone: false, - tts: false, - timestamp: "", - edited_timestamp: null, - flags: 1, - components: [], - poll: {}, - }).status(200); - }, + "/", + route({ + permission: "MANAGE_MESSAGES", + responses: { + 200: { + body: "Message", + }, + }, + }), + (req: Request, res: Response) => { + // TODO: + res.json({ + id: "", + type: 0, + content: "", + channel_id: "", + author: { + id: "", + username: "", + avatar: "", + discriminator: "", + public_flags: 64, + }, + attachments: [], + embeds: [], + mentions: [], + mention_roles: [], + pinned: false, + mention_everyone: false, + tts: false, + timestamp: "", + edited_timestamp: null, + flags: 1, + components: [], + poll: {}, + }).status(200); + }, ); export default router; diff --git a/src/api/routes/channels/#channel_id/messages/#message_id/index.ts b/src/api/routes/channels/#channel_id/messages/#message_id/index.ts
index cdba9b16..64f29c93 100644 --- a/src/api/routes/channels/#channel_id/messages/#message_id/index.ts +++ b/src/api/routes/channels/#channel_id/messages/#message_id/index.ts
@@ -17,19 +17,19 @@ */ import { - Attachment, - Channel, - Message, - MessageCreateEvent, - MessageDeleteEvent, - MessageUpdateEvent, - Snowflake, - SpacebarApiErrors, - emitEvent, - getPermission, - getRights, - uploadFile, - NewUrlUserSignatureData, + Attachment, + Channel, + Message, + MessageCreateEvent, + MessageDeleteEvent, + MessageUpdateEvent, + Snowflake, + SpacebarApiErrors, + emitEvent, + getPermission, + getRights, + uploadFile, + NewUrlUserSignatureData, } from "@spacebar/util"; import { Request, Response, Router } from "express"; import { HTTPError } from "lambert-server"; @@ -41,285 +41,285 @@ const router = Router({ mergeParams: true }); // TODO: message content/embed string length limit const messageUpload = multer({ - limits: { - fileSize: 1024 * 1024 * 100, - fields: 10, - files: 1, - }, - storage: multer.memoryStorage(), + limits: { + fileSize: 1024 * 1024 * 100, + fields: 10, + files: 1, + }, + storage: multer.memoryStorage(), }); // max upload 50 mb router.patch( - "/", - route({ - requestBody: "MessageEditSchema", - permission: "SEND_MESSAGES", - right: "SEND_MESSAGES", - responses: { - 200: { - body: "Message", - }, - 400: { - body: "APIErrorResponse", - }, - 403: {}, - 404: {}, - }, - }), - async (req: Request, res: Response) => { - const { message_id, channel_id } = req.params; - let body = req.body as MessageEditSchema; + "/", + route({ + requestBody: "MessageEditSchema", + permission: "SEND_MESSAGES", + right: "SEND_MESSAGES", + responses: { + 200: { + body: "Message", + }, + 400: { + body: "APIErrorResponse", + }, + 403: {}, + 404: {}, + }, + }), + async (req: Request, res: Response) => { + const { message_id, channel_id } = req.params; + let body = req.body as MessageEditSchema; - const message = await Message.findOneOrFail({ - where: { id: message_id, channel_id }, - relations: ["attachments"], - }); + const message = await Message.findOneOrFail({ + where: { id: message_id, channel_id }, + relations: ["attachments"], + }); - const permissions = await getPermission(req.user_id, undefined, channel_id); + const permissions = await getPermission(req.user_id, undefined, channel_id); - const rights = await getRights(req.user_id); + const rights = await getRights(req.user_id); - if (req.user_id !== message.author_id) { - if (!rights.has("MANAGE_MESSAGES")) { - permissions.hasThrow("MANAGE_MESSAGES"); - body = { flags: body.flags }; - // guild admins can only suppress embeds of other messages, no such restriction imposed to instance-wide admins - } - } else rights.hasThrow("SELF_EDIT_MESSAGES"); + if (req.user_id !== message.author_id) { + if (!rights.has("MANAGE_MESSAGES")) { + permissions.hasThrow("MANAGE_MESSAGES"); + body = { flags: body.flags }; + // guild admins can only suppress embeds of other messages, no such restriction imposed to instance-wide admins + } + } else rights.hasThrow("SELF_EDIT_MESSAGES"); - // no longer necessary, somehow resolved by updating the type of `attachments`...? - // //@ts-expect-error Something is wrong with message_reference here, TS complains since "channel_id" is optional in MessageCreateSchema - const new_message = await handleMessage({ - ...message, - // TODO: should message_reference be overridable? - message_reference: message.message_reference, - ...body, - author_id: message.author_id, - channel_id, - id: message_id, - edited_timestamp: new Date(), - }); + // no longer necessary, somehow resolved by updating the type of `attachments`...? + // //@ts-expect-error Something is wrong with message_reference here, TS complains since "channel_id" is optional in MessageCreateSchema + const new_message = await handleMessage({ + ...message, + // TODO: should message_reference be overridable? + message_reference: message.message_reference, + ...body, + author_id: message.author_id, + channel_id, + id: message_id, + edited_timestamp: new Date(), + }); - await Promise.all([ - new_message.save(), - await emitEvent({ - event: "MESSAGE_UPDATE", - channel_id, - data: { - ...new_message.toJSON(), - nonce: undefined, - member: new_message.member?.toPublicMember(), - }, - } as MessageUpdateEvent), - ]); + await Promise.all([ + new_message.save(), + await emitEvent({ + event: "MESSAGE_UPDATE", + channel_id, + data: { + ...new_message.toJSON(), + nonce: undefined, + member: new_message.member?.toPublicMember(), + }, + } as MessageUpdateEvent), + ]); - postHandleMessage(new_message); + postHandleMessage(new_message); - // TODO: a DTO? - return res.json({ - ...new_message.toJSON(), - id: new_message.id, - type: new_message.type, - channel_id: new_message.channel_id, - member: new_message.member?.toPublicMember(), - author: new_message.author?.toPublicUser(), - attachments: new_message.attachments, - embeds: new_message.embeds, - mentions: new_message.embeds, - mention_roles: new_message.mention_roles, - mention_everyone: new_message.mention_everyone, - pinned: new_message.pinned, - timestamp: new_message.timestamp, - edited_timestamp: new_message.edited_timestamp, + // TODO: a DTO? + return res.json({ + ...new_message.toJSON(), + id: new_message.id, + type: new_message.type, + channel_id: new_message.channel_id, + member: new_message.member?.toPublicMember(), + author: new_message.author?.toPublicUser(), + attachments: new_message.attachments, + embeds: new_message.embeds, + mentions: new_message.embeds, + mention_roles: new_message.mention_roles, + mention_everyone: new_message.mention_everyone, + pinned: new_message.pinned, + timestamp: new_message.timestamp, + edited_timestamp: new_message.edited_timestamp, - // these are not in the Discord.com response - mention_channels: new_message.mention_channels, - }); - }, + // these are not in the Discord.com response + mention_channels: new_message.mention_channels, + }); + }, ); // Backfill message with specific timestamp router.put( - "/", - messageUpload.single("file"), - async (req, res, next) => { - if (req.body.payload_json) { - req.body = JSON.parse(req.body.payload_json); - } + "/", + messageUpload.single("file"), + async (req, res, next) => { + if (req.body.payload_json) { + req.body = JSON.parse(req.body.payload_json); + } - next(); - }, - route({ - requestBody: "MessageCreateSchema", - permission: "SEND_MESSAGES", - right: "SEND_BACKDATED_EVENTS", - responses: { - 200: { - body: "Message", - }, - 400: { - body: "APIErrorResponse", - }, - 403: {}, - 404: {}, - }, - }), - async (req: Request, res: Response) => { - const { channel_id, message_id } = req.params; - const body = req.body as MessageCreateSchema; - const attachments: (MessageCreateAttachment | MessageCreateCloudAttachment)[] = body.attachments ?? []; + next(); + }, + route({ + requestBody: "MessageCreateSchema", + permission: "SEND_MESSAGES", + right: "SEND_BACKDATED_EVENTS", + responses: { + 200: { + body: "Message", + }, + 400: { + body: "APIErrorResponse", + }, + 403: {}, + 404: {}, + }, + }), + async (req: Request, res: Response) => { + const { channel_id, message_id } = req.params; + const body = req.body as MessageCreateSchema; + const attachments: (MessageCreateAttachment | MessageCreateCloudAttachment)[] = body.attachments ?? []; - const rights = await getRights(req.user_id); - rights.hasThrow("SEND_MESSAGES"); + const rights = await getRights(req.user_id); + rights.hasThrow("SEND_MESSAGES"); - // regex to check if message contains anything other than numerals ( also no decimals ) - if (!message_id.match(/^\+?\d+$/)) { - throw new HTTPError("Message IDs must be positive integers", 400); - } + // regex to check if message contains anything other than numerals ( also no decimals ) + if (!message_id.match(/^\+?\d+$/)) { + throw new HTTPError("Message IDs must be positive integers", 400); + } - const snowflake = Snowflake.deconstruct(message_id); - if (Date.now() < snowflake.timestamp) { - // message is in the future - throw SpacebarApiErrors.CANNOT_BACKFILL_TO_THE_FUTURE; - } + const snowflake = Snowflake.deconstruct(message_id); + if (Date.now() < snowflake.timestamp) { + // message is in the future + throw SpacebarApiErrors.CANNOT_BACKFILL_TO_THE_FUTURE; + } - const exists = await Message.findOne({ - where: { id: message_id, channel_id: channel_id }, - }); - if (exists) { - throw SpacebarApiErrors.CANNOT_REPLACE_BY_BACKFILL; - } + const exists = await Message.findOne({ + where: { id: message_id, channel_id: channel_id }, + }); + if (exists) { + throw SpacebarApiErrors.CANNOT_REPLACE_BY_BACKFILL; + } - if (req.file) { - try { - const file = await uploadFile(`/attachments/${req.params.channel_id}`, req.file); - attachments.push(Attachment.create({ ...file, proxy_url: file.url })); - } catch (error) { - return res.status(400).json(error); - } - } - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - relations: ["recipients", "recipients.user"], - }); + if (req.file) { + try { + const file = await uploadFile(`/attachments/${req.params.channel_id}`, req.file); + attachments.push(Attachment.create({ ...file, proxy_url: file.url })); + } catch (error) { + return res.status(400).json(error); + } + } + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + relations: ["recipients", "recipients.user"], + }); - const embeds = body.embeds || []; - if (body.embed) embeds.push(body.embed); - const message = await handleMessage({ - ...body, - type: 0, - pinned: false, - author_id: req.user_id, - id: message_id, - embeds, - channel_id, - attachments, - edited_timestamp: undefined, - timestamp: new Date(snowflake.timestamp), - }); + const embeds = body.embeds || []; + if (body.embed) embeds.push(body.embed); + const message = await handleMessage({ + ...body, + type: 0, + pinned: false, + author_id: req.user_id, + id: message_id, + embeds, + channel_id, + attachments, + edited_timestamp: undefined, + timestamp: new Date(snowflake.timestamp), + }); - //Fix for the client bug - delete message.member; + //Fix for the client bug + delete message.member; - await Promise.all([ - message.save(), - emitEvent({ - event: "MESSAGE_CREATE", - channel_id: channel_id, - data: message, - } as MessageCreateEvent), - channel.save(), - ]); + await Promise.all([ + message.save(), + emitEvent({ + event: "MESSAGE_CREATE", + channel_id: channel_id, + data: message, + } as MessageCreateEvent), + channel.save(), + ]); - // no await as it shouldnt block the message send function and silently catch error - postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e)); + // no await as it shouldnt block the message send function and silently catch error + postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e)); - return res.json( - message.withSignedAttachments( - new NewUrlUserSignatureData({ - ip: req.ip, - userAgent: req.headers["user-agent"] as string, - }), - ), - ); - }, + return res.json( + message.withSignedAttachments( + new NewUrlUserSignatureData({ + ip: req.ip, + userAgent: req.headers["user-agent"] as string, + }), + ), + ); + }, ); router.get( - "/", - route({ - permission: "VIEW_CHANNEL", - responses: { - 200: { - body: "Message", - }, - 400: { - body: "APIErrorResponse", - }, - 403: {}, - 404: {}, - }, - }), - async (req: Request, res: Response) => { - const { message_id, channel_id } = req.params; + "/", + route({ + permission: "VIEW_CHANNEL", + responses: { + 200: { + body: "Message", + }, + 400: { + body: "APIErrorResponse", + }, + 403: {}, + 404: {}, + }, + }), + async (req: Request, res: Response) => { + const { message_id, channel_id } = req.params; - const message = await Message.findOneOrFail({ - where: { id: message_id, channel_id }, - relations: ["attachments"], - }); + const message = await Message.findOneOrFail({ + where: { id: message_id, channel_id }, + relations: ["attachments"], + }); - const permissions = await getPermission(req.user_id, undefined, channel_id); + const permissions = await getPermission(req.user_id, undefined, channel_id); - if (message.author_id !== req.user_id) permissions.hasThrow("READ_MESSAGE_HISTORY"); + if (message.author_id !== req.user_id) permissions.hasThrow("READ_MESSAGE_HISTORY"); - return res.json(message); - }, + return res.json(message); + }, ); router.delete( - "/", - route({ - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 404: {}, - }, - }), - async (req: Request, res: Response) => { - const { message_id, channel_id } = req.params; + "/", + route({ + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 404: {}, + }, + }), + async (req: Request, res: Response) => { + const { message_id, channel_id } = req.params; - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - }); - const message = await Message.findOneOrFail({ - where: { id: message_id }, - }); + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); + const message = await Message.findOneOrFail({ + where: { id: message_id }, + }); - const rights = await getRights(req.user_id); + const rights = await getRights(req.user_id); - if (message.author_id !== req.user_id) { - if (!rights.has("MANAGE_MESSAGES")) { - const permission = await getPermission(req.user_id, channel.guild_id, channel_id); - permission.hasThrow("MANAGE_MESSAGES"); - } - } else rights.hasThrow("SELF_DELETE_MESSAGES"); + if (message.author_id !== req.user_id) { + if (!rights.has("MANAGE_MESSAGES")) { + const permission = await getPermission(req.user_id, channel.guild_id, channel_id); + permission.hasThrow("MANAGE_MESSAGES"); + } + } else rights.hasThrow("SELF_DELETE_MESSAGES"); - await Message.delete({ id: message_id }); + await Message.delete({ id: message_id }); - await emitEvent({ - event: "MESSAGE_DELETE", - channel_id, - data: { - id: message_id, - channel_id, - guild_id: channel.guild_id, - }, - } as MessageDeleteEvent); + await emitEvent({ + event: "MESSAGE_DELETE", + channel_id, + data: { + id: message_id, + channel_id, + guild_id: channel.guild_id, + }, + } as MessageDeleteEvent); - res.sendStatus(204); - }, + res.sendStatus(204); + }, ); export default router; diff --git a/src/api/routes/channels/#channel_id/messages/#message_id/reactions.ts b/src/api/routes/channels/#channel_id/messages/#message_id/reactions.ts
index 63c41cd7..b21dbd34 100644 --- a/src/api/routes/channels/#channel_id/messages/#message_id/reactions.ts +++ b/src/api/routes/channels/#channel_id/messages/#message_id/reactions.ts
@@ -18,18 +18,18 @@ import { route } from "@spacebar/api"; import { - Channel, - emitEvent, - Emoji, - getPermission, - Member, - Message, - MessageReactionAddEvent, - MessageReactionRemoveAllEvent, - MessageReactionRemoveEmojiEvent, - MessageReactionRemoveEvent, - User, - arrayRemove, + Channel, + emitEvent, + Emoji, + getPermission, + Member, + Message, + MessageReactionAddEvent, + MessageReactionRemoveAllEvent, + MessageReactionRemoveEmojiEvent, + MessageReactionRemoveEvent, + User, + arrayRemove, } from "@spacebar/util"; import { Request, Response, Router } from "express"; import { HTTPError } from "lambert-server"; @@ -40,326 +40,326 @@ const router = Router({ mergeParams: true }); // TODO: check if emoji is really an unicode emoji or a properly encoded external emoji function getEmoji(emoji: string): PartialEmoji { - emoji = decodeURIComponent(emoji); - const parts = emoji.includes(":") && emoji.split(":"); - if (parts) - return { - name: parts[0], - id: parts[1], - }; + emoji = decodeURIComponent(emoji); + const parts = emoji.includes(":") && emoji.split(":"); + if (parts) + return { + name: parts[0], + id: parts[1], + }; - return { - id: undefined, - name: emoji, - }; + return { + id: undefined, + name: emoji, + }; } router.delete( - "/", - route({ - permission: "MANAGE_MESSAGES", - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 404: {}, - 403: {}, - }, - }), - async (req: Request, res: Response) => { - const { message_id, channel_id } = req.params; + "/", + route({ + permission: "MANAGE_MESSAGES", + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 404: {}, + 403: {}, + }, + }), + async (req: Request, res: Response) => { + const { message_id, channel_id } = req.params; - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - }); + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); - await Message.update({ id: message_id, channel_id }, { reactions: [] }); + await Message.update({ id: message_id, channel_id }, { reactions: [] }); - await emitEvent({ - event: "MESSAGE_REACTION_REMOVE_ALL", - channel_id, - data: { - channel_id, - message_id, - guild_id: channel.guild_id, - }, - } as MessageReactionRemoveAllEvent); + await emitEvent({ + event: "MESSAGE_REACTION_REMOVE_ALL", + channel_id, + data: { + channel_id, + message_id, + guild_id: channel.guild_id, + }, + } as MessageReactionRemoveAllEvent); - res.sendStatus(204); - }, + res.sendStatus(204); + }, ); router.delete( - "/:emoji", - route({ - permission: "MANAGE_MESSAGES", - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 404: {}, - 403: {}, - }, - }), - async (req: Request, res: Response) => { - const { message_id, channel_id } = req.params; - const emoji = getEmoji(req.params.emoji); + "/:emoji", + route({ + permission: "MANAGE_MESSAGES", + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 404: {}, + 403: {}, + }, + }), + async (req: Request, res: Response) => { + const { message_id, channel_id } = req.params; + const emoji = getEmoji(req.params.emoji); - const message = await Message.findOneOrFail({ - where: { id: message_id, channel_id }, - }); + const message = await Message.findOneOrFail({ + where: { id: message_id, channel_id }, + }); - const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); - if (!already_added) throw new HTTPError("Reaction not found", 404); - arrayRemove(message.reactions, already_added); + const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); + if (!already_added) throw new HTTPError("Reaction not found", 404); + arrayRemove(message.reactions, already_added); - await Promise.all([ - message.save(), - emitEvent({ - event: "MESSAGE_REACTION_REMOVE_EMOJI", - channel_id, - data: { - channel_id, - message_id, - guild_id: message.guild_id, - emoji, - }, - } as MessageReactionRemoveEmojiEvent), - ]); + await Promise.all([ + message.save(), + emitEvent({ + event: "MESSAGE_REACTION_REMOVE_EMOJI", + channel_id, + data: { + channel_id, + message_id, + guild_id: message.guild_id, + emoji, + }, + } as MessageReactionRemoveEmojiEvent), + ]); - res.sendStatus(204); - }, + res.sendStatus(204); + }, ); router.get( - "/:emoji", - route({ - permission: "VIEW_CHANNEL", - responses: { - 200: { - body: "PublicUser", - }, - 400: { - body: "APIErrorResponse", - }, - 404: {}, - 403: {}, - }, - }), - async (req: Request, res: Response) => { - const { message_id, channel_id } = req.params; - const emoji = getEmoji(req.params.emoji); + "/:emoji", + route({ + permission: "VIEW_CHANNEL", + responses: { + 200: { + body: "PublicUser", + }, + 400: { + body: "APIErrorResponse", + }, + 404: {}, + 403: {}, + }, + }), + async (req: Request, res: Response) => { + const { message_id, channel_id } = req.params; + const emoji = getEmoji(req.params.emoji); - const message = await Message.findOneOrFail({ - where: { id: message_id, channel_id }, - }); - const reaction = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); - if (!reaction) throw new HTTPError("Reaction not found", 404); + const message = await Message.findOneOrFail({ + where: { id: message_id, channel_id }, + }); + const reaction = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); + if (!reaction) throw new HTTPError("Reaction not found", 404); - const users = ( - await User.find({ - where: { - id: In(reaction.user_ids), - }, - select: PublicUserProjection, - }) - ).map((user) => user.toPublicUser()); + const users = ( + await User.find({ + where: { + id: In(reaction.user_ids), + }, + select: PublicUserProjection, + }) + ).map((user) => user.toPublicUser()); - res.json(users); - }, + res.json(users); + }, ); router.put( - "/:emoji/:user_id", - route({ - permission: "READ_MESSAGE_HISTORY", - right: "SELF_ADD_REACTIONS", - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 404: {}, - 403: {}, - }, - }), - async (req: Request, res: Response) => { - const { message_id, channel_id, user_id } = req.params; - if (user_id !== "@me") throw new HTTPError("Invalid user"); - const emoji = getEmoji(req.params.emoji); + "/:emoji/:user_id", + route({ + permission: "READ_MESSAGE_HISTORY", + right: "SELF_ADD_REACTIONS", + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 404: {}, + 403: {}, + }, + }), + async (req: Request, res: Response) => { + const { message_id, channel_id, user_id } = req.params; + if (user_id !== "@me") throw new HTTPError("Invalid user"); + const emoji = getEmoji(req.params.emoji); - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - }); - const message = await Message.findOneOrFail({ - where: { id: message_id, channel_id }, - }); - const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); + const message = await Message.findOneOrFail({ + where: { id: message_id, channel_id }, + }); + const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); - if (!already_added) req.permission?.hasThrow("ADD_REACTIONS"); + if (!already_added) req.permission?.hasThrow("ADD_REACTIONS"); - if (emoji.id) { - const external_emoji = await Emoji.findOneOrFail({ - where: { id: emoji.id }, - }); - if (!already_added && channel.guild_id != external_emoji.guild_id) req.permission?.hasThrow("USE_EXTERNAL_EMOJIS"); - emoji.animated = external_emoji.animated; - emoji.name = external_emoji.name; - } + if (emoji.id) { + const external_emoji = await Emoji.findOneOrFail({ + where: { id: emoji.id }, + }); + if (!already_added && channel.guild_id != external_emoji.guild_id) req.permission?.hasThrow("USE_EXTERNAL_EMOJIS"); + emoji.animated = external_emoji.animated; + emoji.name = external_emoji.name; + } - if (already_added) { - if (already_added.user_ids.includes(req.user_id)) return res.sendStatus(204); // Do not throw an error ¯\_(ツ)_/¯ as discord also doesn't throw any error - already_added.count++; - already_added.user_ids.push(req.user_id); - } else - message.reactions.push({ - count: 1, - emoji, - user_ids: [req.user_id], - }); + if (already_added) { + if (already_added.user_ids.includes(req.user_id)) return res.sendStatus(204); // Do not throw an error ¯\_(ツ)_/¯ as discord also doesn't throw any error + already_added.count++; + already_added.user_ids.push(req.user_id); + } else + message.reactions.push({ + count: 1, + emoji, + user_ids: [req.user_id], + }); - await message.save(); + await message.save(); - const member = - channel.guild_id && - ( - await Member.findOneOrFail({ - where: { id: req.user_id }, - select: PublicMemberProjection, - }) - ).toPublicMember(); + const member = + channel.guild_id && + ( + await Member.findOneOrFail({ + where: { id: req.user_id }, + select: PublicMemberProjection, + }) + ).toPublicMember(); - await emitEvent({ - event: "MESSAGE_REACTION_ADD", - channel_id, - data: { - user_id: req.user_id, - channel_id, - message_id, - guild_id: channel.guild_id, - emoji, - member, - }, - } as MessageReactionAddEvent); + await emitEvent({ + event: "MESSAGE_REACTION_ADD", + channel_id, + data: { + user_id: req.user_id, + channel_id, + message_id, + guild_id: channel.guild_id, + emoji, + member, + }, + } as MessageReactionAddEvent); - res.sendStatus(204); - }, + res.sendStatus(204); + }, ); router.delete( - "/:emoji/:user_id", - route({ - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 404: {}, - 403: {}, - }, - }), - async (req: Request, res: Response) => { - let { user_id } = req.params; - const { message_id, channel_id } = req.params; + "/:emoji/:user_id", + route({ + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 404: {}, + 403: {}, + }, + }), + async (req: Request, res: Response) => { + let { user_id } = req.params; + const { message_id, channel_id } = req.params; - const emoji = getEmoji(req.params.emoji); + const emoji = getEmoji(req.params.emoji); - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - }); - const message = await Message.findOneOrFail({ - where: { id: message_id, channel_id }, - }); + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); + const message = await Message.findOneOrFail({ + where: { id: message_id, channel_id }, + }); - if (user_id === "@me") user_id = req.user_id; - else { - const permissions = await getPermission(req.user_id, undefined, channel_id); - permissions.hasThrow("MANAGE_MESSAGES"); - } + if (user_id === "@me") user_id = req.user_id; + else { + const permissions = await getPermission(req.user_id, undefined, channel_id); + permissions.hasThrow("MANAGE_MESSAGES"); + } - const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); - if (!already_added || !already_added.user_ids.includes(user_id)) throw new HTTPError("Reaction not found", 404); + const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); + if (!already_added || !already_added.user_ids.includes(user_id)) throw new HTTPError("Reaction not found", 404); - already_added.count--; + already_added.count--; - if (already_added.count <= 0) arrayRemove(message.reactions, already_added); - else already_added.user_ids.splice(already_added.user_ids.indexOf(user_id), 1); + if (already_added.count <= 0) arrayRemove(message.reactions, already_added); + else already_added.user_ids.splice(already_added.user_ids.indexOf(user_id), 1); - await message.save(); + await message.save(); - await emitEvent({ - event: "MESSAGE_REACTION_REMOVE", - channel_id, - data: { - user_id: req.user_id, - channel_id, - message_id, - guild_id: channel.guild_id, - emoji, - }, - } as MessageReactionRemoveEvent); + await emitEvent({ + event: "MESSAGE_REACTION_REMOVE", + channel_id, + data: { + user_id: req.user_id, + channel_id, + message_id, + guild_id: channel.guild_id, + emoji, + }, + } as MessageReactionRemoveEvent); - res.sendStatus(204); - }, + res.sendStatus(204); + }, ); router.delete( - "/:emoji/:burst/:user_id", - route({ - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 404: {}, - 403: {}, - }, - }), - async (req: Request, res: Response) => { - let { user_id } = req.params; - const { message_id, channel_id } = req.params; + "/:emoji/:burst/:user_id", + route({ + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 404: {}, + 403: {}, + }, + }), + async (req: Request, res: Response) => { + let { user_id } = req.params; + const { message_id, channel_id } = req.params; - const emoji = getEmoji(req.params.emoji); + const emoji = getEmoji(req.params.emoji); - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - }); - const message = await Message.findOneOrFail({ - where: { id: message_id, channel_id }, - }); + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); + const message = await Message.findOneOrFail({ + where: { id: message_id, channel_id }, + }); - if (user_id === "@me") user_id = req.user_id; - else { - const permissions = await getPermission(req.user_id, undefined, channel_id); - permissions.hasThrow("MANAGE_MESSAGES"); - } + if (user_id === "@me") user_id = req.user_id; + else { + const permissions = await getPermission(req.user_id, undefined, channel_id); + permissions.hasThrow("MANAGE_MESSAGES"); + } - const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); - if (!already_added || !already_added.user_ids.includes(user_id)) throw new HTTPError("Reaction not found", 404); + const already_added = message.reactions.find((x) => (x.emoji.id === emoji.id && emoji.id) || x.emoji.name === emoji.name); + if (!already_added || !already_added.user_ids.includes(user_id)) throw new HTTPError("Reaction not found", 404); - already_added.count--; + already_added.count--; - if (already_added.count <= 0) arrayRemove(message.reactions, already_added); - else already_added.user_ids.splice(already_added.user_ids.indexOf(user_id), 1); + if (already_added.count <= 0) arrayRemove(message.reactions, already_added); + else already_added.user_ids.splice(already_added.user_ids.indexOf(user_id), 1); - await message.save(); + await message.save(); - await emitEvent({ - event: "MESSAGE_REACTION_REMOVE", - channel_id, - data: { - user_id: req.user_id, - channel_id, - message_id, - guild_id: channel.guild_id, - emoji, - }, - } as MessageReactionRemoveEvent); + await emitEvent({ + event: "MESSAGE_REACTION_REMOVE", + channel_id, + data: { + user_id: req.user_id, + channel_id, + message_id, + guild_id: channel.guild_id, + emoji, + }, + } as MessageReactionRemoveEvent); - res.sendStatus(204); - }, + res.sendStatus(204); + }, ); export default router; diff --git a/src/api/routes/channels/#channel_id/messages/bulk-delete.ts b/src/api/routes/channels/#channel_id/messages/bulk-delete.ts
index 54801a48..f4645abd 100644 --- a/src/api/routes/channels/#channel_id/messages/bulk-delete.ts +++ b/src/api/routes/channels/#channel_id/messages/bulk-delete.ts
@@ -29,48 +29,48 @@ export default router; // should this request fail, if you provide messages older than 14 days/invalid ids? ANSWER: NO // https://discord.com/developers/docs/resources/channel#bulk-delete-messages router.post( - "/", - route({ - requestBody: "BulkDeleteSchema", - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 403: {}, - 404: {}, - }, - }), - async (req: Request, res: Response) => { - const { channel_id } = req.params; - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - }); - if (!channel.guild_id) throw new HTTPError("Can't bulk delete dm channel messages", 400); + "/", + route({ + requestBody: "BulkDeleteSchema", + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 403: {}, + 404: {}, + }, + }), + async (req: Request, res: Response) => { + const { channel_id } = req.params; + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); + if (!channel.guild_id) throw new HTTPError("Can't bulk delete dm channel messages", 400); - const rights = await getRights(req.user_id); - rights.hasThrow("SELF_DELETE_MESSAGES"); + const rights = await getRights(req.user_id); + rights.hasThrow("SELF_DELETE_MESSAGES"); - const superuser = rights.has("MANAGE_MESSAGES"); - const permission = await getPermission(req.user_id, channel?.guild_id, channel_id); + const superuser = rights.has("MANAGE_MESSAGES"); + const permission = await getPermission(req.user_id, channel?.guild_id, channel_id); - const { maxBulkDelete } = Config.get().limits.message; + const { maxBulkDelete } = Config.get().limits.message; - const { messages } = req.body as { messages: string[] }; - if (messages.length === 0) throw new HTTPError("You must specify messages to bulk delete"); - if (!superuser) { - permission.hasThrow("MANAGE_MESSAGES"); - if (messages.length > maxBulkDelete) throw new HTTPError(`You cannot delete more than ${maxBulkDelete} messages`); - } + const { messages } = req.body as { messages: string[] }; + if (messages.length === 0) throw new HTTPError("You must specify messages to bulk delete"); + if (!superuser) { + permission.hasThrow("MANAGE_MESSAGES"); + if (messages.length > maxBulkDelete) throw new HTTPError(`You cannot delete more than ${maxBulkDelete} messages`); + } - await Message.delete(messages); + await Message.delete(messages); - await emitEvent({ - event: "MESSAGE_DELETE_BULK", - channel_id, - data: { ids: messages, channel_id, guild_id: channel.guild_id }, - } as MessageDeleteBulkEvent); + await emitEvent({ + event: "MESSAGE_DELETE_BULK", + channel_id, + data: { ids: messages, channel_id, guild_id: channel.guild_id }, + } as MessageDeleteBulkEvent); - res.sendStatus(204); - }, + res.sendStatus(204); + }, ); diff --git a/src/api/routes/channels/#channel_id/messages/index.ts b/src/api/routes/channels/#channel_id/messages/index.ts
index 2ce37748..272fa8ac 100644 --- a/src/api/routes/channels/#channel_id/messages/index.ts +++ b/src/api/routes/channels/#channel_id/messages/index.ts
@@ -18,29 +18,29 @@ import { handleMessage, postHandleMessage, route } from "@spacebar/api"; import { - Attachment, - AutomodRule, - AutomodTriggerTypes, - Channel, - Config, - DiscordApiErrors, - DmChannelDTO, - emitEvent, - FieldErrors, - getPermission, - getUrlSignature, - Member, - Message, - MessageCreateEvent, - NewUrlSignatureData, - NewUrlUserSignatureData, - ReadState, - Relationship, - Rights, - Snowflake, - stringGlobToRegexp, - uploadFile, - User, + Attachment, + AutomodRule, + AutomodTriggerTypes, + Channel, + Config, + DiscordApiErrors, + DmChannelDTO, + emitEvent, + FieldErrors, + getPermission, + getUrlSignature, + Member, + Message, + MessageCreateEvent, + NewUrlSignatureData, + NewUrlUserSignatureData, + ReadState, + Relationship, + Rights, + Snowflake, + stringGlobToRegexp, + uploadFile, + User, } from "@spacebar/util"; import { Request, Response, Router } from "express"; import { HTTPError } from "lambert-server"; @@ -48,17 +48,17 @@ import multer from "multer"; import { FindManyOptions, FindOperator, LessThan, MoreThan, MoreThanOrEqual } from "typeorm"; import { URL } from "url"; import { - AcknowledgeDeleteSchema, - AutomodCustomWordsRule, - AutomodRuleActionType, - AutomodRuleEventType, - isTextChannel, - MessageCreateAttachment, - MessageCreateCloudAttachment, - MessageCreateSchema, - Reaction, - ReadStateType, - RelationshipType, + AcknowledgeDeleteSchema, + AutomodCustomWordsRule, + AutomodRuleActionType, + AutomodRuleEventType, + isTextChannel, + MessageCreateAttachment, + MessageCreateCloudAttachment, + MessageCreateSchema, + Reaction, + ReadStateType, + RelationshipType, } from "@spacebar/schemas"; const router: Router = Router({ mergeParams: true }); @@ -66,221 +66,221 @@ const router: Router = Router({ mergeParams: true }); // https://discord.com/developers/docs/resources/channel#create-message // get messages router.get( - "/", - route({ - query: { - around: { - type: "string", - }, - before: { - type: "string", - }, - after: { - type: "string", - }, - limit: { - type: "number", - description: "max number of messages to return (1-100). defaults to 50", - }, - }, - responses: { - 200: { - body: "APIMessageArray", - }, - 400: { - body: "APIErrorResponse", - }, - 403: {}, - 404: {}, - }, - }), - async (req: Request, res: Response) => { - const channel_id = req.params.channel_id; - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - }); - if (!channel) throw new HTTPError("Channel not found", 404); + "/", + route({ + query: { + around: { + type: "string", + }, + before: { + type: "string", + }, + after: { + type: "string", + }, + limit: { + type: "number", + description: "max number of messages to return (1-100). defaults to 50", + }, + }, + responses: { + 200: { + body: "APIMessageArray", + }, + 400: { + body: "APIErrorResponse", + }, + 403: {}, + 404: {}, + }, + }), + async (req: Request, res: Response) => { + const channel_id = req.params.channel_id; + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); + if (!channel) throw new HTTPError("Channel not found", 404); - isTextChannel(channel.type); - const around = req.query.around ? `${req.query.around}` : undefined; - const before = req.query.before ? `${req.query.before}` : undefined; - const after = req.query.after ? `${req.query.after}` : undefined; - const limit = Number(req.query.limit) || 50; - if (limit < 1 || limit > 100) throw new HTTPError("limit must be between 1 and 100", 422); + isTextChannel(channel.type); + const around = req.query.around ? `${req.query.around}` : undefined; + const before = req.query.before ? `${req.query.before}` : undefined; + const after = req.query.after ? `${req.query.after}` : undefined; + const limit = Number(req.query.limit) || 50; + if (limit < 1 || limit > 100) throw new HTTPError("limit must be between 1 and 100", 422); - const permissions = await getPermission(req.user_id, channel.guild_id, channel_id); - permissions.hasThrow("VIEW_CHANNEL"); - if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json([]); + const permissions = await getPermission(req.user_id, channel.guild_id, channel_id); + permissions.hasThrow("VIEW_CHANNEL"); + if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json([]); - const query: FindManyOptions<Message> & { - where: { id?: FindOperator<string> | FindOperator<string>[] }; - } = { - order: { timestamp: "DESC" }, - take: limit, - where: { channel_id }, - 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", - ], - }; + const query: FindManyOptions<Message> & { + where: { id?: FindOperator<string> | FindOperator<string>[] }; + } = { + order: { timestamp: "DESC" }, + take: limit, + where: { channel_id }, + 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", + ], + }; - let messages: Message[]; + let messages: Message[]; - if (around) { - query.take = Math.floor(limit / 2); - if (query.take != 0) { - const [right, left] = await Promise.all([ - Message.find({ - ...query, - where: { channel_id, id: LessThan(around) }, - }), - Message.find({ - ...query, - where: { channel_id, id: MoreThanOrEqual(around) }, - order: { timestamp: "ASC" }, - }), - ]); - left.push(...right); - messages = left.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); - } else { - query.take = 1; - const message = await Message.findOne({ - ...query, - where: { channel_id, id: around }, - }); - messages = message ? [message] : []; - } - } else { - if (after) { - if (BigInt(after) > BigInt(Snowflake.generate())) throw new HTTPError("after parameter must not be greater than current time", 422); + if (around) { + query.take = Math.floor(limit / 2); + if (query.take != 0) { + const [right, left] = await Promise.all([ + Message.find({ + ...query, + where: { channel_id, id: LessThan(around) }, + }), + Message.find({ + ...query, + where: { channel_id, id: MoreThanOrEqual(around) }, + order: { timestamp: "ASC" }, + }), + ]); + left.push(...right); + messages = left.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()); + } else { + query.take = 1; + const message = await Message.findOne({ + ...query, + where: { channel_id, id: around }, + }); + messages = message ? [message] : []; + } + } else { + if (after) { + if (BigInt(after) > BigInt(Snowflake.generate())) throw new HTTPError("after parameter must not be greater than current time", 422); - query.where.id = MoreThan(after); - query.order = { timestamp: "ASC" }; - } else if (before) { - if (BigInt(before) > BigInt(Snowflake.generate())) throw new HTTPError("before parameter must not be greater than current time", 422); + query.where.id = MoreThan(after); + query.order = { timestamp: "ASC" }; + } else if (before) { + if (BigInt(before) > BigInt(Snowflake.generate())) throw new HTTPError("before parameter must not be greater than current time", 422); - query.where.id = LessThan(before); - } + query.where.id = LessThan(before); + } - messages = await Message.find(query); - } + messages = await Message.find(query); + } - const endpoint = Config.get().cdn.endpointPublic; + const endpoint = Config.get().cdn.endpointPublic; - const ret = messages.map((x: Message) => { - x = x.toJSON(); + const ret = messages.map((x: Message) => { + x = x.toJSON(); - (x.reactions || []).forEach((y: Partial<Reaction>) => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - //@ts-ignore - if ((y.user_ids || []).includes(req.user_id)) y.me = true; - delete y.user_ids; - }); - if (!x.author) - x.author = User.create({ - id: "4", - discriminator: "0000", - username: "Spacebar Ghost", - public_flags: 0, - }); - x.attachments?.forEach((y: Attachment) => { - // dynamically set attachment proxy_url in case the endpoint changed - const uri = y.proxy_url.startsWith("http") ? y.proxy_url : `https://example.org${y.proxy_url}`; + (x.reactions || []).forEach((y: Partial<Reaction>) => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + if ((y.user_ids || []).includes(req.user_id)) y.me = true; + delete y.user_ids; + }); + if (!x.author) + x.author = User.create({ + id: "4", + discriminator: "0000", + username: "Spacebar Ghost", + public_flags: 0, + }); + x.attachments?.forEach((y: Attachment) => { + // dynamically set attachment proxy_url in case the endpoint changed + const uri = y.proxy_url.startsWith("http") ? y.proxy_url : `https://example.org${y.proxy_url}`; - const url = new URL(uri); - if (endpoint) { - const newBase = new URL(endpoint); - url.protocol = newBase.protocol; - url.hostname = newBase.hostname; - url.port = newBase.port; - } + const url = new URL(uri); + if (endpoint) { + const newBase = new URL(endpoint); + url.protocol = newBase.protocol; + url.hostname = newBase.hostname; + url.port = newBase.port; + } - y.proxy_url = url.toString(); + y.proxy_url = url.toString(); - y.proxy_url = getUrlSignature( - new NewUrlSignatureData({ - url: y.proxy_url, - userAgent: req.headers["user-agent"], - ip: req.ip, - }), - ) - .applyToUrl(y.proxy_url) - .toString(); + y.proxy_url = getUrlSignature( + new NewUrlSignatureData({ + url: y.proxy_url, + userAgent: req.headers["user-agent"], + ip: req.ip, + }), + ) + .applyToUrl(y.proxy_url) + .toString(); - y.url = getUrlSignature( - new NewUrlSignatureData({ - url: y.url, - userAgent: req.headers["user-agent"], - ip: req.ip, - }), - ) - .applyToUrl(y.url) - .toString(); - }); + y.url = getUrlSignature( + new NewUrlSignatureData({ + url: y.url, + userAgent: req.headers["user-agent"], + ip: req.ip, + }), + ) + .applyToUrl(y.url) + .toString(); + }); - /** + /** Some clients ( discord.js ) only check if a property exists within the response, which causes errors when, say, the `application` property is `null`. **/ - // for (var curr in x) { - // if (x[curr] === null) - // delete x[curr]; - // } + // for (var curr in x) { + // if (x[curr] === null) + // delete x[curr]; + // } - return x; - }); + return x; + }); - await Promise.all( - ret - .filter((x: MessageCreateSchema) => x.interaction_metadata && !x.interaction_metadata.user) - .map(async (x: MessageCreateSchema) => { - x.interaction_metadata!.user = x.interaction!.user = await User.findOneOrFail({ where: { id: (x as Message).interaction_metadata!.user_id } }); - }), - ); + await Promise.all( + ret + .filter((x: MessageCreateSchema) => x.interaction_metadata && !x.interaction_metadata.user) + .map(async (x: MessageCreateSchema) => { + x.interaction_metadata!.user = x.interaction!.user = await User.findOneOrFail({ where: { id: (x as Message).interaction_metadata!.user_id } }); + }), + ); - // polyfill message references for old messages - await Promise.all( - ret - .filter((msg) => msg.message_reference && !msg.referenced_message?.id) - .map(async (msg) => { - const whereOptions: { id: string; guild_id?: string; channel_id?: string } = { - id: msg.message_reference!.message_id, - }; - if (msg.message_reference!.guild_id) whereOptions.guild_id = msg.message_reference!.guild_id; - if (msg.message_reference!.channel_id) whereOptions.channel_id = msg.message_reference!.channel_id; + // polyfill message references for old messages + await Promise.all( + ret + .filter((msg) => msg.message_reference && !msg.referenced_message?.id) + .map(async (msg) => { + const whereOptions: { id: string; guild_id?: string; channel_id?: string } = { + id: msg.message_reference!.message_id, + }; + if (msg.message_reference!.guild_id) whereOptions.guild_id = msg.message_reference!.guild_id; + if (msg.message_reference!.channel_id) whereOptions.channel_id = msg.message_reference!.channel_id; - msg.referenced_message = await Message.findOne({ where: whereOptions, relations: ["author", "mentions", "mention_roles", "mention_channels"] }); - }), - ); + msg.referenced_message = await Message.findOne({ where: whereOptions, relations: ["author", "mentions", "mention_roles", "mention_channels"] }); + }), + ); - return res.json(ret); - }, + return res.json(ret); + }, ); // TODO: config max upload size const messageUpload = multer({ - limits: { - fileSize: Config.get().limits.message.maxAttachmentSize, - fields: 10, - // files: 1 - }, - storage: multer.memoryStorage(), + limits: { + fileSize: Config.get().limits.message.maxAttachmentSize, + fields: 10, + // files: 1 + }, + storage: multer.memoryStorage(), }); // max upload 50 mb /** TODO: dynamically change limit of MessageCreateSchema with config @@ -292,213 +292,213 @@ const messageUpload = multer({ **/ // Send message router.post( - "/", - messageUpload.any(), - (req, res, next) => { - if (req.body.payload_json) { - req.body = JSON.parse(req.body.payload_json); - } + "/", + messageUpload.any(), + (req, res, next) => { + if (req.body.payload_json) { + req.body = JSON.parse(req.body.payload_json); + } - next(); - }, - route({ - requestBody: "MessageCreateSchema", - permission: "SEND_MESSAGES", - right: "SEND_MESSAGES", - responses: { - 200: { - body: "Message", - }, - 400: { - body: "APIErrorResponse", - }, - 403: {}, - 404: {}, - }, - }), - async (req: Request, res: Response) => { - const { channel_id } = req.params; - const body = req.body as MessageCreateSchema; - const attachments: (Attachment | MessageCreateAttachment | MessageCreateCloudAttachment)[] = body.attachments ?? []; + next(); + }, + route({ + requestBody: "MessageCreateSchema", + permission: "SEND_MESSAGES", + right: "SEND_MESSAGES", + responses: { + 200: { + body: "Message", + }, + 400: { + body: "APIErrorResponse", + }, + 403: {}, + 404: {}, + }, + }), + async (req: Request, res: Response) => { + const { channel_id } = req.params; + const body = req.body as MessageCreateSchema; + const attachments: (Attachment | MessageCreateAttachment | MessageCreateCloudAttachment)[] = body.attachments ?? []; - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - relations: ["recipients", "recipients.user"], - }); - if (!channel.isWritable()) { - throw new HTTPError(`Cannot send messages to channel of type ${channel.type}`, 400); - } + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + relations: ["recipients", "recipients.user"], + }); + if (!channel.isWritable()) { + throw new HTTPError(`Cannot send messages to channel of type ${channel.type}`, 400); + } - // handle blocked users in dms - if (channel.recipients?.length == 2) { - const otherUser = channel.recipients.find((r) => r.user_id != req.user_id)?.user; - if (otherUser) { - const relationship = await Relationship.findOne({ - where: [ - { from_id: req.user_id, to_id: otherUser.id }, - { from_id: otherUser.id, to_id: req.user_id }, - ], - }); + // handle blocked users in dms + if (channel.recipients?.length == 2) { + const otherUser = channel.recipients.find((r) => r.user_id != req.user_id)?.user; + if (otherUser) { + const relationship = await Relationship.findOne({ + where: [ + { from_id: req.user_id, to_id: otherUser.id }, + { from_id: otherUser.id, to_id: req.user_id }, + ], + }); - if (relationship?.type === RelationshipType.blocked) { - throw DiscordApiErrors.CANNOT_MESSAGE_USER; - } - } - } + if (relationship?.type === RelationshipType.blocked) { + throw DiscordApiErrors.CANNOT_MESSAGE_USER; + } + } + } - if (body.nonce) { - const existing = await Message.findOne({ - where: { - nonce: body.nonce, - channel_id: channel.id, - author_id: req.user_id, - }, - }); - if (existing) { - return res.json(existing); - } - } + if (body.nonce) { + const existing = await Message.findOne({ + where: { + nonce: body.nonce, + channel_id: channel.id, + author_id: req.user_id, + }, + }); + if (existing) { + return res.json(existing); + } + } - if (!req.rights.has(Rights.FLAGS.BYPASS_RATE_LIMITS)) { - const limits = Config.get().limits; - if (limits.absoluteRate.sendMessage.enabled) { - const count = await Message.count({ - where: { - channel_id, - timestamp: MoreThan(new Date(Date.now() - limits.absoluteRate.sendMessage.window)), - }, - }); + if (!req.rights.has(Rights.FLAGS.BYPASS_RATE_LIMITS)) { + const limits = Config.get().limits; + if (limits.absoluteRate.sendMessage.enabled) { + const count = await Message.count({ + where: { + channel_id, + timestamp: MoreThan(new Date(Date.now() - limits.absoluteRate.sendMessage.window)), + }, + }); - if (count >= limits.absoluteRate.sendMessage.limit) - throw FieldErrors({ - channel_id: { - code: "TOO_MANY_MESSAGES", - message: req.t("common:toomany.MESSAGE"), - }, - }); - } - } + if (count >= limits.absoluteRate.sendMessage.limit) + throw FieldErrors({ + channel_id: { + code: "TOO_MANY_MESSAGES", + message: req.t("common:toomany.MESSAGE"), + }, + }); + } + } - const files = (req.files as Express.Multer.File[]) ?? []; - for (const currFile of files) { - try { - const file = await uploadFile(`/attachments/${channel.id}`, currFile); - attachments.push(Attachment.create({ ...file, proxy_url: file.url })); - } catch (error) { - return res.status(400).json({ message: error?.toString() }); - } - } + const files = (req.files as Express.Multer.File[]) ?? []; + for (const currFile of files) { + try { + const file = await uploadFile(`/attachments/${channel.id}`, currFile); + attachments.push(Attachment.create({ ...file, proxy_url: file.url })); + } catch (error) { + return res.status(400).json({ message: error?.toString() }); + } + } - const embeds = body.embeds || []; - if (body.embed) embeds.push(body.embed); - const message = await handleMessage({ - ...body, - type: 0, - pinned: false, - author_id: req.user_id, - embeds, - channel_id, - attachments, - timestamp: new Date(), - }); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - //@ts-ignore dont care2 - message.edited_timestamp = null; + const embeds = body.embeds || []; + if (body.embed) embeds.push(body.embed); + const message = await handleMessage({ + ...body, + type: 0, + pinned: false, + author_id: req.user_id, + embeds, + channel_id, + attachments, + timestamp: new Date(), + }); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore dont care2 + message.edited_timestamp = null; - channel.last_message_id = message.id; + channel.last_message_id = message.id; - if (channel.isDm()) { - const channel_dto = await DmChannelDTO.from(channel); + if (channel.isDm()) { + const channel_dto = await DmChannelDTO.from(channel); - // Only one recipients should be closed here, since in group DMs the recipient is deleted not closed - await Promise.all( - channel.recipients?.map((recipient) => { - if (recipient.closed) { - recipient.closed = false; - return Promise.all([ - recipient.save(), - emitEvent({ - event: "CHANNEL_CREATE", - data: channel_dto.excludedRecipients([recipient.user_id]), - user_id: recipient.user_id, - }), - ]); - } - }) || [], - ); - } + // Only one recipients should be closed here, since in group DMs the recipient is deleted not closed + await Promise.all( + channel.recipients?.map((recipient) => { + if (recipient.closed) { + recipient.closed = false; + return Promise.all([ + recipient.save(), + emitEvent({ + event: "CHANNEL_CREATE", + data: channel_dto.excludedRecipients([recipient.user_id]), + user_id: recipient.user_id, + }), + ]); + } + }) || [], + ); + } - if (message.guild_id) { - // handleMessage will fetch the Member, but only if they are not guild owner. - // have to fetch ourselves otherwise. - if (!message.member) { - message.member = await Member.findOneOrFail({ - where: { id: req.user_id, guild_id: message.guild_id }, - relations: ["roles"], - }); - } + if (message.guild_id) { + // handleMessage will fetch the Member, but only if they are not guild owner. + // have to fetch ourselves otherwise. + if (!message.member) { + message.member = await Member.findOneOrFail({ + where: { id: req.user_id, guild_id: message.guild_id }, + relations: ["roles"], + }); + } - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - message.member.roles = message.member.roles.filter((x) => x.id != x.guild_id).map((x) => x.id); - } + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + message.member.roles = message.member.roles.filter((x) => x.id != x.guild_id).map((x) => x.id); + } - let read_state = await ReadState.findOne({ - where: { user_id: req.user_id, channel_id }, - }); - if (!read_state) read_state = ReadState.create({ user_id: req.user_id, channel_id }); - read_state.last_message_id = message.id; - //It's a little more complicated than this but this'll do - read_state.mention_count = 0; + let read_state = await ReadState.findOne({ + where: { user_id: req.user_id, channel_id }, + }); + if (!read_state) read_state = ReadState.create({ user_id: req.user_id, channel_id }); + read_state.last_message_id = message.id; + //It's a little more complicated than this but this'll do + read_state.mention_count = 0; - await Promise.all([ - read_state.save(), - message.save(), - emitEvent({ - event: "MESSAGE_CREATE", - channel_id: channel_id, - data: message, - } as MessageCreateEvent), - message.guild_id ? Member.update({ id: req.user_id, guild_id: message.guild_id }, { last_message_id: message.id }) : null, - channel.save(), - ]); + await Promise.all([ + read_state.save(), + message.save(), + emitEvent({ + event: "MESSAGE_CREATE", + channel_id: channel_id, + data: message, + } as MessageCreateEvent), + message.guild_id ? Member.update({ id: req.user_id, guild_id: message.guild_id }, { last_message_id: message.id }) : null, + channel.save(), + ]); - // no await as it shouldnt block the message send function and silently catch error - postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e)); + // no await as it shouldnt block the message send function and silently catch error + postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e)); - return res.json( - message.withSignedAttachments( - new NewUrlUserSignatureData({ - ip: req.ip, - userAgent: req.headers["user-agent"] as string, - }), - ), - ); - }, + return res.json( + message.withSignedAttachments( + new NewUrlUserSignatureData({ + ip: req.ip, + userAgent: req.headers["user-agent"] as string, + }), + ), + ); + }, ); router.delete( - "/ack", - route({ - requestBody: "AcknowledgeDeleteSchema", - responses: { - 204: {}, - }, - }), - async (req: Request, res: Response) => { - const { channel_id } = req.params; // not really a channel id if read_state_type != CHANNEL - const body = req.body as AcknowledgeDeleteSchema; - if (body.version != 2) return res.status(204).send(); - // TODO: handle other read state types - if (body.read_state_type != ReadStateType.CHANNEL) return res.status(204).send(); + "/ack", + route({ + requestBody: "AcknowledgeDeleteSchema", + responses: { + 204: {}, + }, + }), + async (req: Request, res: Response) => { + const { channel_id } = req.params; // not really a channel id if read_state_type != CHANNEL + const body = req.body as AcknowledgeDeleteSchema; + if (body.version != 2) return res.status(204).send(); + // TODO: handle other read state types + if (body.read_state_type != ReadStateType.CHANNEL) return res.status(204).send(); - const readState = await ReadState.findOne({ where: { channel_id, user_id: req.user_id } }); - if (readState) { - await readState.remove(); - } + const readState = await ReadState.findOne({ where: { channel_id, user_id: req.user_id } }); + if (readState) { + await readState.remove(); + } - res.status(204).send(); - }, + res.status(204).send(); + }, ); export default router; diff --git a/src/api/routes/channels/#channel_id/messages/pins/index.ts b/src/api/routes/channels/#channel_id/messages/pins/index.ts
index 1d770bd2..2355c8b9 100644 --- a/src/api/routes/channels/#channel_id/messages/pins/index.ts +++ b/src/api/routes/channels/#channel_id/messages/pins/index.ts
@@ -24,169 +24,169 @@ import { IsNull, Not } from "typeorm"; const router: Router = Router({ mergeParams: true }); router.put( - "/:message_id", - route({ - permission: "VIEW_CHANNEL", - responses: { - 204: {}, - 403: {}, - 404: {}, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { channel_id, message_id } = req.params; + "/:message_id", + route({ + permission: "VIEW_CHANNEL", + responses: { + 204: {}, + 403: {}, + 404: {}, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { channel_id, message_id } = req.params; - const message = await Message.findOneOrFail({ - where: { id: message_id }, - relations: ["author"], - }); + const message = await Message.findOneOrFail({ + where: { id: message_id }, + relations: ["author"], + }); - // * in dm channels anyone can pin messages -> only check for guilds - if (message.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES"); + // * in dm channels anyone can pin messages -> only check for guilds + if (message.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES"); - const pinned_count = await Message.count({ - where: { channel: { id: channel_id }, pinned_at: Not(IsNull()) }, - }); + const pinned_count = await Message.count({ + where: { channel: { id: channel_id }, pinned_at: Not(IsNull()) }, + }); - const { maxPins } = Config.get().limits.channel; - if (pinned_count >= maxPins) throw DiscordApiErrors.MAXIMUM_PINS.withParams(maxPins); + const { maxPins } = Config.get().limits.channel; + if (pinned_count >= maxPins) throw DiscordApiErrors.MAXIMUM_PINS.withParams(maxPins); - message.pinned_at = new Date(); + message.pinned_at = new Date(); - const author = await User.getPublicUser(req.user_id); + const author = await User.getPublicUser(req.user_id); - const systemPinMessage = Message.create({ - timestamp: new Date(), - type: 6, - guild_id: message.guild_id, - channel_id: message.channel_id, - author, - message_reference: { - message_id: message.id, - channel_id: message.channel_id, - guild_id: message.guild_id, - }, - reactions: [], - attachments: [], - embeds: [], - sticker_items: [], - edited_timestamp: undefined, - mentions: [], - mention_channels: [], - mention_roles: [], - mention_everyone: false, - }); + const systemPinMessage = Message.create({ + timestamp: new Date(), + type: 6, + guild_id: message.guild_id, + channel_id: message.channel_id, + author, + message_reference: { + message_id: message.id, + channel_id: message.channel_id, + guild_id: message.guild_id, + }, + reactions: [], + attachments: [], + embeds: [], + sticker_items: [], + edited_timestamp: undefined, + mentions: [], + mention_channels: [], + mention_roles: [], + mention_everyone: false, + }); - await Promise.all([ - message.save(), - emitEvent({ - event: "MESSAGE_UPDATE", - channel_id, - data: message, - } as MessageUpdateEvent), - emitEvent({ - event: "CHANNEL_PINS_UPDATE", - channel_id, - data: { - channel_id, - guild_id: message.guild_id, - last_pin_timestamp: undefined, - }, - } as ChannelPinsUpdateEvent), - systemPinMessage.save(), - emitEvent({ - event: "MESSAGE_CREATE", - channel_id: message.channel_id, - data: systemPinMessage, - } as MessageCreateEvent), - ]); + await Promise.all([ + message.save(), + emitEvent({ + event: "MESSAGE_UPDATE", + channel_id, + data: message, + } as MessageUpdateEvent), + emitEvent({ + event: "CHANNEL_PINS_UPDATE", + channel_id, + data: { + channel_id, + guild_id: message.guild_id, + last_pin_timestamp: undefined, + }, + } as ChannelPinsUpdateEvent), + systemPinMessage.save(), + emitEvent({ + event: "MESSAGE_CREATE", + channel_id: message.channel_id, + data: systemPinMessage, + } as MessageCreateEvent), + ]); - res.sendStatus(204); - }, + res.sendStatus(204); + }, ); router.delete( - "/:message_id", - route({ - permission: "VIEW_CHANNEL", - responses: { - 204: {}, - 403: {}, - 404: {}, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { channel_id, message_id } = req.params; + "/:message_id", + route({ + permission: "VIEW_CHANNEL", + responses: { + 204: {}, + 403: {}, + 404: {}, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { channel_id, message_id } = req.params; - const message = await Message.findOneOrFail({ - where: { id: message_id }, - relations: ["author"], - }); + const message = await Message.findOneOrFail({ + where: { id: message_id }, + relations: ["author"], + }); - if (message.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES"); + if (message.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES"); - message.pinned_at = null; + message.pinned_at = null; - await Promise.all([ - message.save(), - emitEvent({ - event: "MESSAGE_UPDATE", - channel_id, - data: message, - } as MessageUpdateEvent), - emitEvent({ - event: "CHANNEL_PINS_UPDATE", - channel_id, - data: { - channel_id, - guild_id: message.guild_id, - last_pin_timestamp: undefined, - }, - } as ChannelPinsUpdateEvent), - ]); + await Promise.all([ + message.save(), + emitEvent({ + event: "MESSAGE_UPDATE", + channel_id, + data: message, + } as MessageUpdateEvent), + emitEvent({ + event: "CHANNEL_PINS_UPDATE", + channel_id, + data: { + channel_id, + guild_id: message.guild_id, + last_pin_timestamp: undefined, + }, + } as ChannelPinsUpdateEvent), + ]); - res.sendStatus(204); - }, + res.sendStatus(204); + }, ); router.get( - "/", - route({ - permission: ["READ_MESSAGE_HISTORY"], - responses: { - 200: { - body: "APIMessageArray", - }, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { channel_id } = req.params; + "/", + route({ + permission: ["READ_MESSAGE_HISTORY"], + responses: { + 200: { + body: "APIMessageArray", + }, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { channel_id } = req.params; - const pins = await Message.find({ - where: { channel_id: channel_id, pinned_at: Not(IsNull()) }, - relations: ["author"], - order: { pinned_at: "DESC" }, - }); + const pins = await Message.find({ + where: { channel_id: channel_id, pinned_at: Not(IsNull()) }, + relations: ["author"], + order: { pinned_at: "DESC" }, + }); - const items = pins.map((message: Message) => ({ - message, - pinned_at: message.pinned_at, - })); + const items = pins.map((message: Message) => ({ + message, + pinned_at: message.pinned_at, + })); - res.send({ - items, - has_more: false, - }); - }, + res.send({ + items, + has_more: false, + }); + }, ); export default router; diff --git a/src/api/routes/channels/#channel_id/messages/search.ts b/src/api/routes/channels/#channel_id/messages/search.ts
index 7331945d..bec9a44c 100644 --- a/src/api/routes/channels/#channel_id/messages/search.ts +++ b/src/api/routes/channels/#channel_id/messages/search.ts
@@ -27,116 +27,116 @@ import { FindManyOptions, In, Like } from "typeorm"; const router: Router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "GuildMessagesSearchResponse", - }, - 403: { - body: "APIErrorResponse", - }, - 422: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { channel_id } = req.params; - const channel = await Channel.findOneOrFail({ - where: { guild_id: req.params.guild_id }, - select: ["id"], - }); - const { - content, - // include_nsfw, // TODO - offset, - sort_order, - // sort_by, // TODO: Handle 'relevance' - limit, - author_id, - } = req.query; + "/", + route({ + responses: { + 200: { + body: "GuildMessagesSearchResponse", + }, + 403: { + body: "APIErrorResponse", + }, + 422: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { channel_id } = req.params; + const channel = await Channel.findOneOrFail({ + where: { guild_id: req.params.guild_id }, + select: ["id"], + }); + const { + content, + // include_nsfw, // TODO + offset, + sort_order, + // sort_by, // TODO: Handle 'relevance' + limit, + author_id, + } = req.query; - const parsedLimit = Number(limit) || 50; - if (parsedLimit < 1 || parsedLimit > 100) throw new HTTPError("limit must be between 1 and 100", 422); + const parsedLimit = Number(limit) || 50; + if (parsedLimit < 1 || parsedLimit > 100) throw new HTTPError("limit must be between 1 and 100", 422); - if (sort_order) { - if (typeof sort_order != "string" || ["desc", "asc"].indexOf(sort_order) == -1) - throw FieldErrors({ - sort_order: { - message: "Value must be one of ('desc', 'asc').", - code: "BASE_TYPE_CHOICES", - }, - }); // todo this is wrong - } + if (sort_order) { + if (typeof sort_order != "string" || ["desc", "asc"].indexOf(sort_order) == -1) + throw FieldErrors({ + sort_order: { + message: "Value must be one of ('desc', 'asc').", + code: "BASE_TYPE_CHOICES", + }, + }); // todo this is wrong + } - const permissions = await getPermission(req.user_id, channel.guild_id, channel_id as string | undefined); - permissions.hasThrow("VIEW_CHANNEL"); - if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json({ messages: [], total_results: 0 }); + const permissions = await getPermission(req.user_id, channel.guild_id, channel_id as string | undefined); + permissions.hasThrow("VIEW_CHANNEL"); + if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json({ messages: [], total_results: 0 }); - const query: FindManyOptions<Message> = { - order: { - timestamp: sort_order ? (sort_order.toUpperCase() as "ASC" | "DESC") : "DESC", - }, - take: parsedLimit || 0, - where: { - guild: { - id: channel.guild_id, - }, - channel: { - id: channel_id, - }, - }, - relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"], - skip: offset ? Number(offset) : 0, - }; - //@ts-ignore - query.where.channel = { id: channel_id }; + const query: FindManyOptions<Message> = { + order: { + timestamp: sort_order ? (sort_order.toUpperCase() as "ASC" | "DESC") : "DESC", + }, + take: parsedLimit || 0, + where: { + guild: { + id: channel.guild_id, + }, + channel: { + id: channel_id, + }, + }, + relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"], + skip: offset ? Number(offset) : 0, + }; + //@ts-ignore + query.where.channel = { id: channel_id }; - //@ts-ignore - if (author_id) query.where.author = { id: author_id }; - //@ts-ignore - if (content) query.where.content = Like(`%${content}%`); + //@ts-ignore + if (author_id) query.where.author = { id: author_id }; + //@ts-ignore + if (content) query.where.content = Like(`%${content}%`); - const messages: Message[] = await Message.find(query); - delete query.take; - const total_results = await Message.count(query); + const messages: Message[] = await Message.find(query); + delete query.take; + const total_results = await Message.count(query); - const messagesDto = messages.map((x) => [ - { - id: x.id, - type: x.type, - content: x.content, - channel_id: x.channel_id, - author: { - id: x.author?.id, - username: x.author?.username, - avatar: x.author?.avatar, - avatar_decoration: null, - discriminator: x.author?.discriminator, - public_flags: x.author?.public_flags, - }, - attachments: x.attachments, - embeds: x.embeds, - mentions: x.mentions, - mention_roles: x.mention_roles, - pinned: x.pinned, - mention_everyone: x.mention_everyone, - tts: x.tts, - timestamp: x.timestamp, - edited_timestamp: x.edited_timestamp, - flags: x.flags, - components: x.components, - poll: x.poll, - hit: true, - }, - ]); + const messagesDto = messages.map((x) => [ + { + id: x.id, + type: x.type, + content: x.content, + channel_id: x.channel_id, + author: { + id: x.author?.id, + username: x.author?.username, + avatar: x.author?.avatar, + avatar_decoration: null, + discriminator: x.author?.discriminator, + public_flags: x.author?.public_flags, + }, + attachments: x.attachments, + embeds: x.embeds, + mentions: x.mentions, + mention_roles: x.mention_roles, + pinned: x.pinned, + mention_everyone: x.mention_everyone, + tts: x.tts, + timestamp: x.timestamp, + edited_timestamp: x.edited_timestamp, + flags: x.flags, + components: x.components, + poll: x.poll, + hit: true, + }, + ]); - return res.json({ - messages: messagesDto, - total_results, - }); - }, + return res.json({ + messages: messagesDto, + total_results, + }); + }, ); export default router; diff --git a/src/api/routes/channels/#channel_id/permissions.ts b/src/api/routes/channels/#channel_id/permissions.ts
index b45ff144..c03ca03f 100644 --- a/src/api/routes/channels/#channel_id/permissions.ts +++ b/src/api/routes/channels/#channel_id/permissions.ts
@@ -27,80 +27,80 @@ const router: Router = Router({ mergeParams: true }); // TODO: Only permissions your bot has in the guild or channel can be allowed/denied (unless your bot has a MANAGE_ROLES overwrite in the channel) router.put( - "/:overwrite_id", - route({ - requestBody: "ChannelPermissionOverwriteSchema", - permission: "MANAGE_ROLES", - responses: { - 204: {}, - 404: {}, - 501: {}, - 400: { body: "APIErrorResponse" }, - }, - }), - async (req: Request, res: Response) => { - const { channel_id, overwrite_id } = req.params; - const body = req.body as ChannelPermissionOverwriteSchema; + "/:overwrite_id", + route({ + requestBody: "ChannelPermissionOverwriteSchema", + permission: "MANAGE_ROLES", + responses: { + 204: {}, + 404: {}, + 501: {}, + 400: { body: "APIErrorResponse" }, + }, + }), + async (req: Request, res: Response) => { + const { channel_id, overwrite_id } = req.params; + const body = req.body as ChannelPermissionOverwriteSchema; - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - }); - if (!channel.guild_id) throw new HTTPError("Channel not found", 404); - channel.position = await Channel.calculatePosition(channel_id, channel.guild_id, channel.guild); + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); + if (!channel.guild_id) throw new HTTPError("Channel not found", 404); + channel.position = await Channel.calculatePosition(channel_id, channel.guild_id, channel.guild); - if (body.type === ChannelPermissionOverwriteType.role) { - if (!(await Role.count({ where: { id: overwrite_id } }))) throw new HTTPError("role not found", 404); - } else if (body.type === ChannelPermissionOverwriteType.member) { - if (!(await Member.count({ where: { id: overwrite_id } }))) throw new HTTPError("user not found", 404); - } else throw new HTTPError("type not supported", 501); + if (body.type === ChannelPermissionOverwriteType.role) { + if (!(await Role.count({ where: { id: overwrite_id } }))) throw new HTTPError("role not found", 404); + } else if (body.type === ChannelPermissionOverwriteType.member) { + if (!(await Member.count({ where: { id: overwrite_id } }))) throw new HTTPError("user not found", 404); + } else throw new HTTPError("type not supported", 501); - let overwrite: ChannelPermissionOverwrite | undefined = channel.permission_overwrites?.find((x) => x.id === overwrite_id); - if (!overwrite) { - overwrite = { - id: overwrite_id, - type: body.type, - allow: "0", - deny: "0", - }; - channel.permission_overwrites?.push(overwrite); - } - overwrite.allow = String((req.permission?.bitfield || 0n) & (BigInt(body.allow) || BigInt("0"))); - overwrite.deny = String((req.permission?.bitfield || 0n) & (BigInt(body.deny) || BigInt("0"))); + let overwrite: ChannelPermissionOverwrite | undefined = channel.permission_overwrites?.find((x) => x.id === overwrite_id); + if (!overwrite) { + overwrite = { + id: overwrite_id, + type: body.type, + allow: "0", + deny: "0", + }; + channel.permission_overwrites?.push(overwrite); + } + overwrite.allow = String((req.permission?.bitfield || 0n) & (BigInt(body.allow) || BigInt("0"))); + overwrite.deny = String((req.permission?.bitfield || 0n) & (BigInt(body.deny) || BigInt("0"))); - await Promise.all([ - channel.save(), - emitEvent({ - event: "CHANNEL_UPDATE", - channel_id, - data: channel, - } as ChannelUpdateEvent), - ]); + await Promise.all([ + channel.save(), + emitEvent({ + event: "CHANNEL_UPDATE", + channel_id, + data: channel, + } as ChannelUpdateEvent), + ]); - return res.sendStatus(204); - }, + return res.sendStatus(204); + }, ); // TODO: check permission hierarchy router.delete("/:overwrite_id", route({ permission: "MANAGE_ROLES", responses: { 204: {}, 404: {} } }), async (req: Request, res: Response) => { - const { channel_id, overwrite_id } = req.params; + const { channel_id, overwrite_id } = req.params; - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - }); - if (!channel.guild_id) throw new HTTPError("Channel not found", 404); + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); + if (!channel.guild_id) throw new HTTPError("Channel not found", 404); - channel.permission_overwrites = channel.permission_overwrites?.filter((x) => x.id !== overwrite_id); + channel.permission_overwrites = channel.permission_overwrites?.filter((x) => x.id !== overwrite_id); - await Promise.all([ - channel.save(), - emitEvent({ - event: "CHANNEL_UPDATE", - channel_id, - data: channel, - } as ChannelUpdateEvent), - ]); + await Promise.all([ + channel.save(), + emitEvent({ + event: "CHANNEL_UPDATE", + channel_id, + data: channel, + } as ChannelUpdateEvent), + ]); - return res.sendStatus(204); + return res.sendStatus(204); }); export default router; diff --git a/src/api/routes/channels/#channel_id/pins.ts b/src/api/routes/channels/#channel_id/pins.ts
index 4b62115f..8e56a54d 100644 --- a/src/api/routes/channels/#channel_id/pins.ts +++ b/src/api/routes/channels/#channel_id/pins.ts
@@ -25,161 +25,161 @@ const router: Router = Router({ mergeParams: true }); // This is the old endpoint router.put( - "/:message_id", - route({ - permission: "VIEW_CHANNEL", - responses: { - 204: {}, - 403: {}, - 404: {}, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { channel_id, message_id } = req.params; + "/:message_id", + route({ + permission: "VIEW_CHANNEL", + responses: { + 204: {}, + 403: {}, + 404: {}, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { channel_id, message_id } = req.params; - const message = await Message.findOneOrFail({ - where: { id: message_id }, - relations: ["author"], - }); + const message = await Message.findOneOrFail({ + where: { id: message_id }, + relations: ["author"], + }); - // * in dm channels anyone can pin messages -> only check for guilds - if (message.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES"); + // * in dm channels anyone can pin messages -> only check for guilds + if (message.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES"); - const pinned_count = await Message.count({ - where: { channel: { id: channel_id }, pinned_at: Not(IsNull()) }, - }); + const pinned_count = await Message.count({ + where: { channel: { id: channel_id }, pinned_at: Not(IsNull()) }, + }); - const { maxPins } = Config.get().limits.channel; - if (pinned_count >= maxPins) throw DiscordApiErrors.MAXIMUM_PINS.withParams(maxPins); + const { maxPins } = Config.get().limits.channel; + if (pinned_count >= maxPins) throw DiscordApiErrors.MAXIMUM_PINS.withParams(maxPins); - message.pinned_at = new Date(); + message.pinned_at = new Date(); - const author = await User.getPublicUser(req.user_id); + const author = await User.getPublicUser(req.user_id); - const systemPinMessage = Message.create({ - timestamp: new Date(), - type: 6, - guild_id: message.guild_id, - channel_id: message.channel_id, - author, - message_reference: { - message_id: message.id, - channel_id: message.channel_id, - guild_id: message.guild_id, - }, - reactions: [], - attachments: [], - embeds: [], - sticker_items: [], - edited_timestamp: undefined, - mentions: [], - mention_channels: [], - mention_roles: [], - mention_everyone: false, - }); + const systemPinMessage = Message.create({ + timestamp: new Date(), + type: 6, + guild_id: message.guild_id, + channel_id: message.channel_id, + author, + message_reference: { + message_id: message.id, + channel_id: message.channel_id, + guild_id: message.guild_id, + }, + reactions: [], + attachments: [], + embeds: [], + sticker_items: [], + edited_timestamp: undefined, + mentions: [], + mention_channels: [], + mention_roles: [], + mention_everyone: false, + }); - await Promise.all([ - message.save(), - emitEvent({ - event: "MESSAGE_UPDATE", - channel_id, - data: message, - } as MessageUpdateEvent), - emitEvent({ - event: "CHANNEL_PINS_UPDATE", - channel_id, - data: { - channel_id, - guild_id: message.guild_id, - last_pin_timestamp: undefined, - }, - } as ChannelPinsUpdateEvent), - systemPinMessage.save(), - emitEvent({ - event: "MESSAGE_CREATE", - channel_id: message.channel_id, - data: systemPinMessage, - } as MessageCreateEvent), - ]); + await Promise.all([ + message.save(), + emitEvent({ + event: "MESSAGE_UPDATE", + channel_id, + data: message, + } as MessageUpdateEvent), + emitEvent({ + event: "CHANNEL_PINS_UPDATE", + channel_id, + data: { + channel_id, + guild_id: message.guild_id, + last_pin_timestamp: undefined, + }, + } as ChannelPinsUpdateEvent), + systemPinMessage.save(), + emitEvent({ + event: "MESSAGE_CREATE", + channel_id: message.channel_id, + data: systemPinMessage, + } as MessageCreateEvent), + ]); - res.sendStatus(204); - }, + res.sendStatus(204); + }, ); router.delete( - "/:message_id", - route({ - permission: "VIEW_CHANNEL", - responses: { - 204: {}, - 403: {}, - 404: {}, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { channel_id, message_id } = req.params; + "/:message_id", + route({ + permission: "VIEW_CHANNEL", + responses: { + 204: {}, + 403: {}, + 404: {}, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { channel_id, message_id } = req.params; - const message = await Message.findOneOrFail({ - where: { id: message_id }, - relations: ["author"], - }); + const message = await Message.findOneOrFail({ + where: { id: message_id }, + relations: ["author"], + }); - if (message.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES"); + if (message.guild_id) req.permission?.hasThrow("MANAGE_MESSAGES"); - message.pinned_at = null; + message.pinned_at = null; - await Promise.all([ - message.save(), - emitEvent({ - event: "MESSAGE_UPDATE", - channel_id, - data: message, - } as MessageUpdateEvent), - emitEvent({ - event: "CHANNEL_PINS_UPDATE", - channel_id, - data: { - channel_id, - guild_id: message.guild_id, - last_pin_timestamp: undefined, - }, - } as ChannelPinsUpdateEvent), - ]); + await Promise.all([ + message.save(), + emitEvent({ + event: "MESSAGE_UPDATE", + channel_id, + data: message, + } as MessageUpdateEvent), + emitEvent({ + event: "CHANNEL_PINS_UPDATE", + channel_id, + data: { + channel_id, + guild_id: message.guild_id, + last_pin_timestamp: undefined, + }, + } as ChannelPinsUpdateEvent), + ]); - res.sendStatus(204); - }, + res.sendStatus(204); + }, ); router.get( - "/", - route({ - permission: ["READ_MESSAGE_HISTORY"], - responses: { - 200: { - body: "APIMessageArray", - }, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { channel_id } = req.params; + "/", + route({ + permission: ["READ_MESSAGE_HISTORY"], + responses: { + 200: { + body: "APIMessageArray", + }, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { channel_id } = req.params; - const pins = await Message.find({ - where: { channel_id: channel_id, pinned_at: Not(IsNull()) }, - relations: ["author"], - order: { pinned_at: "DESC" }, - }); + const pins = await Message.find({ + where: { channel_id: channel_id, pinned_at: Not(IsNull()) }, + relations: ["author"], + order: { pinned_at: "DESC" }, + }); - res.send(pins); - }, + res.send(pins); + }, ); export default router; diff --git a/src/api/routes/channels/#channel_id/purge.ts b/src/api/routes/channels/#channel_id/purge.ts
index 9686bc20..3a46beb7 100644 --- a/src/api/routes/channels/#channel_id/purge.ts +++ b/src/api/routes/channels/#channel_id/purge.ts
@@ -31,71 +31,71 @@ export default router; TODO: apply the delete bit by bit to prevent client and database stress **/ router.post( - "/", - route({ - /*body: "PurgeSchema",*/ - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 404: {}, - 403: {}, - }, - }), - async (req: Request, res: Response) => { - const { channel_id } = req.params; - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - }); + "/", + route({ + /*body: "PurgeSchema",*/ + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 404: {}, + 403: {}, + }, + }), + async (req: Request, res: Response) => { + const { channel_id } = req.params; + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); - if (!channel.guild_id) throw new HTTPError("Can't purge dm channels", 400); - isTextChannel(channel.type); + if (!channel.guild_id) throw new HTTPError("Can't purge dm channels", 400); + isTextChannel(channel.type); - const rights = await getRights(req.user_id); - if (!rights.has("MANAGE_MESSAGES")) { - const permissions = await getPermission(req.user_id, channel.guild_id, channel_id); - permissions.hasThrow("MANAGE_MESSAGES"); - permissions.hasThrow("MANAGE_CHANNELS"); - } + const rights = await getRights(req.user_id); + if (!rights.has("MANAGE_MESSAGES")) { + const permissions = await getPermission(req.user_id, channel.guild_id, channel_id); + permissions.hasThrow("MANAGE_MESSAGES"); + permissions.hasThrow("MANAGE_CHANNELS"); + } - const { before, after } = req.body as PurgeSchema; + const { before, after } = req.body as PurgeSchema; - // TODO: send the deletion event bite-by-bite to prevent client stress + // TODO: send the deletion event bite-by-bite to prevent client stress - const query: FindManyOptions<Message> & { - where: { id?: FindOperator<string> }; - } = { - order: { id: "ASC" }, - // take: limit, - where: { - channel_id, - id: Between(after, before), // the right way around - author_id: rights.has("SELF_DELETE_MESSAGES") ? undefined : Not(req.user_id), - // if you lack the right of self-deletion, you can't delete your own messages, even in purges - }, - relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"], - }; + const query: FindManyOptions<Message> & { + where: { id?: FindOperator<string> }; + } = { + order: { id: "ASC" }, + // take: limit, + where: { + channel_id, + id: Between(after, before), // the right way around + author_id: rights.has("SELF_DELETE_MESSAGES") ? undefined : Not(req.user_id), + // if you lack the right of self-deletion, you can't delete your own messages, even in purges + }, + relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"], + }; - const messages = await Message.find(query); + const messages = await Message.find(query); - if (messages.length == 0) { - res.sendStatus(304); - return; - } + if (messages.length == 0) { + res.sendStatus(304); + return; + } - await Message.delete(messages.map((x) => x.id)); + await Message.delete(messages.map((x) => x.id)); - await emitEvent({ - event: "MESSAGE_DELETE_BULK", - channel_id, - data: { - ids: messages.map((x) => x.id), - channel_id, - guild_id: channel.guild_id, - }, - } as MessageDeleteBulkEvent); + await emitEvent({ + event: "MESSAGE_DELETE_BULK", + channel_id, + data: { + ids: messages.map((x) => x.id), + channel_id, + guild_id: channel.guild_id, + }, + } as MessageDeleteBulkEvent); - res.sendStatus(204); - }, + res.sendStatus(204); + }, ); diff --git a/src/api/routes/channels/#channel_id/recipients.ts b/src/api/routes/channels/#channel_id/recipients.ts
index 6388cfe2..d0af3b4a 100644 --- a/src/api/routes/channels/#channel_id/recipients.ts +++ b/src/api/routes/channels/#channel_id/recipients.ts
@@ -24,79 +24,79 @@ import { ChannelType, PublicUserProjection } from "@spacebar/schemas"; const router: Router = Router({ mergeParams: true }); router.put( - "/:user_id", - route({ - responses: { - 201: {}, - 404: {}, - }, - }), - async (req: Request, res: Response) => { - const { channel_id, user_id } = req.params; - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - relations: ["recipients"], - }); + "/:user_id", + route({ + responses: { + 201: {}, + 404: {}, + }, + }), + async (req: Request, res: Response) => { + const { channel_id, user_id } = req.params; + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + relations: ["recipients"], + }); - if (channel.type !== ChannelType.GROUP_DM) { - const recipients = [...new Set([...(channel.recipients?.map((r) => r.user_id) || []), user_id])]; + if (channel.type !== ChannelType.GROUP_DM) { + const recipients = [...new Set([...(channel.recipients?.map((r) => r.user_id) || []), user_id])]; - const new_channel = await Channel.createDMChannel(recipients, req.user_id); - return res.status(201).json(new_channel); - } else { - if (channel.recipients?.map((r) => r.user_id).includes(user_id)) { - throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error? - } + const new_channel = await Channel.createDMChannel(recipients, req.user_id); + return res.status(201).json(new_channel); + } else { + if (channel.recipients?.map((r) => r.user_id).includes(user_id)) { + throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error? + } - channel.recipients?.push(Recipient.create({ channel_id: channel_id, user_id: user_id })); - await channel.save(); + channel.recipients?.push(Recipient.create({ channel_id: channel_id, user_id: user_id })); + await channel.save(); - await emitEvent({ - event: "CHANNEL_CREATE", - data: await DmChannelDTO.from(channel, [user_id]), - user_id: user_id, - }); + await emitEvent({ + event: "CHANNEL_CREATE", + data: await DmChannelDTO.from(channel, [user_id]), + user_id: user_id, + }); - await emitEvent({ - event: "CHANNEL_RECIPIENT_ADD", - data: { - channel_id: channel_id, - user: await User.findOneOrFail({ - where: { id: user_id }, - select: PublicUserProjection, - }), - }, - channel_id: channel_id, - } as ChannelRecipientAddEvent); - return res.sendStatus(204); - } - }, + await emitEvent({ + event: "CHANNEL_RECIPIENT_ADD", + data: { + channel_id: channel_id, + user: await User.findOneOrFail({ + where: { id: user_id }, + select: PublicUserProjection, + }), + }, + channel_id: channel_id, + } as ChannelRecipientAddEvent); + return res.sendStatus(204); + } + }, ); router.delete( - "/:user_id", - route({ - responses: { - 204: {}, - 404: {}, - }, - }), - async (req: Request, res: Response) => { - const { channel_id, user_id } = req.params; - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - relations: ["recipients"], - }); - if (!(channel.type === ChannelType.GROUP_DM && (channel.owner_id === req.user_id || user_id === req.user_id))) throw DiscordApiErrors.MISSING_PERMISSIONS; + "/:user_id", + route({ + responses: { + 204: {}, + 404: {}, + }, + }), + async (req: Request, res: Response) => { + const { channel_id, user_id } = req.params; + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + relations: ["recipients"], + }); + if (!(channel.type === ChannelType.GROUP_DM && (channel.owner_id === req.user_id || user_id === req.user_id))) throw DiscordApiErrors.MISSING_PERMISSIONS; - if (!channel.recipients?.map((r) => r.user_id).includes(user_id)) { - throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error? - } + if (!channel.recipients?.map((r) => r.user_id).includes(user_id)) { + throw DiscordApiErrors.INVALID_RECIPIENT; //TODO is this the right error? + } - await Channel.removeRecipientFromChannel(channel, user_id); + await Channel.removeRecipientFromChannel(channel, user_id); - return res.sendStatus(204); - }, + return res.sendStatus(204); + }, ); export default router; diff --git a/src/api/routes/channels/#channel_id/typing.ts b/src/api/routes/channels/#channel_id/typing.ts
index 8c36add1..a41ab7ff 100644 --- a/src/api/routes/channels/#channel_id/typing.ts +++ b/src/api/routes/channels/#channel_id/typing.ts
@@ -23,47 +23,47 @@ import { Request, Response, Router } from "express"; const router: Router = Router({ mergeParams: true }); router.post( - "/", - route({ - permission: "SEND_MESSAGES", - responses: { - 204: {}, - 404: {}, - 403: {}, - }, - }), - async (req: Request, res: Response) => { - const { channel_id } = req.params; - const user_id = req.user_id; - const timestamp = Math.floor(Date.now() / 1000); - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - }); - const member = await Member.findOne({ - where: { id: user_id, guild_id: channel.guild_id }, - relations: ["roles", "user"], - }); - await emitEvent({ - event: "TYPING_START", - channel_id: channel_id, - data: { - ...(member - ? { - member: { - ...member.toPublicMember(), - roles: member?.roles?.map((x) => x.id), - }, - } - : null), - channel_id, - timestamp, - user_id, - guild_id: channel.guild_id, - }, - } as TypingStartEvent); + "/", + route({ + permission: "SEND_MESSAGES", + responses: { + 204: {}, + 404: {}, + 403: {}, + }, + }), + async (req: Request, res: Response) => { + const { channel_id } = req.params; + const user_id = req.user_id; + const timestamp = Math.floor(Date.now() / 1000); + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); + const member = await Member.findOne({ + where: { id: user_id, guild_id: channel.guild_id }, + relations: ["roles", "user"], + }); + await emitEvent({ + event: "TYPING_START", + channel_id: channel_id, + data: { + ...(member + ? { + member: { + ...member.toPublicMember(), + roles: member?.roles?.map((x) => x.id), + }, + } + : null), + channel_id, + timestamp, + user_id, + guild_id: channel.guild_id, + }, + } as TypingStartEvent); - res.sendStatus(204); - }, + res.sendStatus(204); + }, ); export default router; diff --git a/src/api/routes/channels/#channel_id/webhooks.ts b/src/api/routes/channels/#channel_id/webhooks.ts
index 9d226b19..1c3c980a 100644 --- a/src/api/routes/channels/#channel_id/webhooks.ts +++ b/src/api/routes/channels/#channel_id/webhooks.ts
@@ -26,88 +26,88 @@ import { isTextChannel, WebhookCreateSchema, WebhookType } from "@spacebar/schem const router: Router = Router({ mergeParams: true }); router.get( - "/", - route({ - description: "Returns a list of channel webhook objects. Requires the MANAGE_WEBHOOKS permission.", - permission: "MANAGE_WEBHOOKS", - responses: { - 200: { - body: "APIWebhookArray", - }, - }, - }), - async (req: Request, res: Response) => { - const { channel_id } = req.params; - const webhooks = await Webhook.find({ - where: { channel_id }, - relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"], - }); + "/", + route({ + description: "Returns a list of channel webhook objects. Requires the MANAGE_WEBHOOKS permission.", + permission: "MANAGE_WEBHOOKS", + responses: { + 200: { + body: "APIWebhookArray", + }, + }, + }), + async (req: Request, res: Response) => { + const { channel_id } = req.params; + const webhooks = await Webhook.find({ + where: { channel_id }, + relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"], + }); - return res.json( - webhooks.map((webhook) => ({ - ...webhook, - url: Config.get().api.endpointPublic + "/webhooks/" + webhook.id + "/" + webhook.token, - })), - ); - }, + return res.json( + webhooks.map((webhook) => ({ + ...webhook, + url: Config.get().api.endpointPublic + "/webhooks/" + webhook.id + "/" + webhook.token, + })), + ); + }, ); // TODO: use Image Data Type for avatar instead of String router.post( - "/", - route({ - requestBody: "WebhookCreateSchema", - permission: "MANAGE_WEBHOOKS", - responses: { - 200: { - body: "WebhookCreateResponse", - }, - 400: { - body: "APIErrorResponse", - }, - 403: {}, - }, - }), - async (req: Request, res: Response) => { - const channel_id = req.params.channel_id; - const channel = await Channel.findOneOrFail({ - where: { id: channel_id }, - }); + "/", + route({ + requestBody: "WebhookCreateSchema", + permission: "MANAGE_WEBHOOKS", + responses: { + 200: { + body: "WebhookCreateResponse", + }, + 400: { + body: "APIErrorResponse", + }, + 403: {}, + }, + }), + async (req: Request, res: Response) => { + const channel_id = req.params.channel_id; + const channel = await Channel.findOneOrFail({ + where: { id: channel_id }, + }); - isTextChannel(channel.type); - if (!channel.guild_id) throw new HTTPError("Not a guild channel", 400); + isTextChannel(channel.type); + if (!channel.guild_id) throw new HTTPError("Not a guild channel", 400); - const webhook_count = await Webhook.count({ where: { channel_id } }); - const { maxWebhooks } = Config.get().limits.channel; - if (maxWebhooks && webhook_count > maxWebhooks) throw DiscordApiErrors.MAXIMUM_WEBHOOKS.withParams(maxWebhooks); + const webhook_count = await Webhook.count({ where: { channel_id } }); + const { maxWebhooks } = Config.get().limits.channel; + if (maxWebhooks && webhook_count > maxWebhooks) throw DiscordApiErrors.MAXIMUM_WEBHOOKS.withParams(maxWebhooks); - let { avatar, name } = req.body as WebhookCreateSchema; - name = trimSpecial(name); + let { avatar, name } = req.body as WebhookCreateSchema; + name = trimSpecial(name); - // TODO: move this - if (name) { - ValidateName(name); - } + // TODO: move this + if (name) { + ValidateName(name); + } - if (avatar) avatar = await handleFile(`/avatars/${channel_id}`, avatar); + if (avatar) avatar = await handleFile(`/avatars/${channel_id}`, avatar); - const hook = await Webhook.create({ - type: WebhookType.Incoming, - name, - avatar, - guild_id: channel.guild_id, - channel_id: channel.id, - user_id: req.user_id, - token: crypto.randomBytes(24).toString("base64url"), - }).save(); + const hook = await Webhook.create({ + type: WebhookType.Incoming, + name, + avatar, + guild_id: channel.guild_id, + channel_id: channel.id, + user_id: req.user_id, + token: crypto.randomBytes(24).toString("base64url"), + }).save(); - const user = await User.getPublicUser(req.user_id); + const user = await User.getPublicUser(req.user_id); - return res.json({ - ...hook, - user: user, - }); - }, + return res.json({ + ...hook, + user: user, + }); + }, ); export default router; diff --git a/src/api/routes/channels/preload-messages.ts b/src/api/routes/channels/preload-messages.ts
index cdfc58f0..6a190524 100644 --- a/src/api/routes/channels/preload-messages.ts +++ b/src/api/routes/channels/preload-messages.ts
@@ -23,49 +23,49 @@ import { PreloadMessagesRequestSchema, PreloadMessagesResponseSchema } from "@sp const router = Router({ mergeParams: true }); router.post( - "/", - route({ - requestBody: "PreloadMessagesRequestSchema", - responses: { - 200: { - body: "PreloadMessagesResponse", - }, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const body = req.body as PreloadMessagesRequestSchema; - if (body.channels.length > Config.get().limits.message.maxPreloadCount) - return res.status(400).send({ - code: 400, - message: `Cannot preload more than ${Config.get().limits.message.maxPreloadCount} channels at once.`, - }); + "/", + route({ + requestBody: "PreloadMessagesRequestSchema", + responses: { + 200: { + body: "PreloadMessagesResponse", + }, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const body = req.body as PreloadMessagesRequestSchema; + if (body.channels.length > Config.get().limits.message.maxPreloadCount) + return res.status(400).send({ + code: 400, + message: `Cannot preload more than ${Config.get().limits.message.maxPreloadCount} channels at once.`, + }); - const messages = ( - await Promise.all( - body.channels.map( - async (channelId) => - await Message.findOne({ - where: { channel_id: channelId }, - order: { timestamp: "DESC" }, - }), - ), - ) - ).filter((x) => x !== null) as Message[]; + const messages = ( + await Promise.all( + body.channels.map( + async (channelId) => + await Message.findOne({ + where: { channel_id: channelId }, + order: { timestamp: "DESC" }, + }), + ), + ) + ).filter((x) => x !== null) as Message[]; - const filteredMessages = messages.map((message) => { - const x = message.toJSON(); - // https://docs.discord.food/resources/message#preload-messages - reactions are not included in the response - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - x.reactions = undefined; - return x; - }) as PreloadMessagesResponseSchema; + const filteredMessages = messages.map((message) => { + const x = message.toJSON(); + // https://docs.discord.food/resources/message#preload-messages - reactions are not included in the response + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + x.reactions = undefined; + return x; + }) as PreloadMessagesResponseSchema; - return res.status(200).send(filteredMessages); - }, + return res.status(200).send(filteredMessages); + }, ); export default router; diff --git a/src/api/routes/collectibles-categories.ts b/src/api/routes/collectibles-categories.ts
index d2cac396..ffeff099 100644 --- a/src/api/routes/collectibles-categories.ts +++ b/src/api/routes/collectibles-categories.ts
@@ -23,18 +23,18 @@ import { CollectiblesCategoriesResponse } from "@spacebar/schemas"; const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "CollectiblesCategoriesResponse", - }, - 204: {}, - }, - }), - (req: Request, res: Response) => { - res.send([] as CollectiblesCategoriesResponse); - }, + "/", + route({ + responses: { + 200: { + body: "CollectiblesCategoriesResponse", + }, + 204: {}, + }, + }), + (req: Request, res: Response) => { + res.send([] as CollectiblesCategoriesResponse); + }, ); export default router; diff --git a/src/api/routes/collectibles-shop.ts b/src/api/routes/collectibles-shop.ts
index b4c82f57..756c80c5 100644 --- a/src/api/routes/collectibles-shop.ts +++ b/src/api/routes/collectibles-shop.ts
@@ -23,21 +23,21 @@ import { CollectiblesShopResponse } from "@spacebar/schemas"; const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "CollectiblesShopResponse", - }, - 204: {}, - }, - }), - (req: Request, res: Response) => { - res.send({ - shop_blocks: [], - categories: [], - } as CollectiblesShopResponse); - }, + "/", + route({ + responses: { + 200: { + body: "CollectiblesShopResponse", + }, + 204: {}, + }, + }), + (req: Request, res: Response) => { + res.send({ + shop_blocks: [], + categories: [], + } as CollectiblesShopResponse); + }, ); export default router; diff --git a/src/api/routes/connections/#connection_name/#connection_id/refresh.ts b/src/api/routes/connections/#connection_name/#connection_id/refresh.ts
index 46f20aee..4a1a1cb4 100644 --- a/src/api/routes/connections/#connection_name/#connection_id/refresh.ts +++ b/src/api/routes/connections/#connection_name/#connection_id/refresh.ts
@@ -21,9 +21,9 @@ import { Request, Response, Router } from "express"; const router = Router({ mergeParams: true }); router.post("/", route({}), async (req: Request, res: Response) => { - // TODO: - // const { connection_name, connection_id } = req.params; - res.sendStatus(204); + // TODO: + // const { connection_name, connection_id } = req.params; + res.sendStatus(204); }); export default router; diff --git a/src/api/routes/connections/#connection_name/authorize.ts b/src/api/routes/connections/#connection_name/authorize.ts
index b8e50db6..1efb6564 100644 --- a/src/api/routes/connections/#connection_name/authorize.ts +++ b/src/api/routes/connections/#connection_name/authorize.ts
@@ -23,28 +23,28 @@ import { ConnectionStore, FieldErrors } from "../../../../util"; const router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - const { connection_name } = req.params; - const connection = ConnectionStore.connections.get(connection_name); - if (!connection) - throw FieldErrors({ - provider_id: { - code: "BASE_TYPE_CHOICES", - message: req.t("common:field.BASE_TYPE_CHOICES", { - types: Array.from(ConnectionStore.connections.keys()).join(", "), - }), - }, - }); + const { connection_name } = req.params; + const connection = ConnectionStore.connections.get(connection_name); + if (!connection) + throw FieldErrors({ + provider_id: { + code: "BASE_TYPE_CHOICES", + message: req.t("common:field.BASE_TYPE_CHOICES", { + types: Array.from(ConnectionStore.connections.keys()).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.", + }, + }); - res.json({ - url: await connection.getAuthorizationUrl(req.user_id), - }); + res.json({ + url: await connection.getAuthorizationUrl(req.user_id), + }); }); export default router; diff --git a/src/api/routes/connections/#connection_name/callback.ts b/src/api/routes/connections/#connection_name/callback.ts
index 5732a8e6..a83882ca 100644 --- a/src/api/routes/connections/#connection_name/callback.ts +++ b/src/api/routes/connections/#connection_name/callback.ts
@@ -24,38 +24,38 @@ import { ConnectionCallbackSchema } from "@spacebar/schemas"; const router = Router({ mergeParams: true }); router.post("/", route({ requestBody: "ConnectionCallbackSchema" }), async (req: Request, res: Response) => { - const { connection_name } = req.params; - const connection = ConnectionStore.connections.get(connection_name); - if (!connection) - throw FieldErrors({ - provider_id: { - code: "BASE_TYPE_CHOICES", - message: req.t("common:field.BASE_TYPE_CHOICES", { - types: Array.from(ConnectionStore.connections.keys()).join(", "), - }), - }, - }); + const { connection_name } = req.params; + const connection = ConnectionStore.connections.get(connection_name); + if (!connection) + throw FieldErrors({ + provider_id: { + code: "BASE_TYPE_CHOICES", + message: req.t("common:field.BASE_TYPE_CHOICES", { + types: Array.from(ConnectionStore.connections.keys()).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 body = req.body as ConnectionCallbackSchema; - const userId = connection.getUserId(body.state); - const connectedAccnt = await connection.handleCallback(body); + const body = req.body as ConnectionCallbackSchema; + const userId = connection.getUserId(body.state); + const connectedAccnt = await connection.handleCallback(body); - // whether we should emit a connections update event, only used when a connection doesnt already exist - if (connectedAccnt) - emitEvent({ - event: "USER_CONNECTIONS_UPDATE", - data: { ...connectedAccnt, token_data: undefined }, - user_id: userId, - }); + // whether we should emit a connections update event, only used when a connection doesnt already exist + if (connectedAccnt) + emitEvent({ + event: "USER_CONNECTIONS_UPDATE", + data: { ...connectedAccnt, token_data: undefined }, + user_id: userId, + }); - res.sendStatus(204); + res.sendStatus(204); }); export default router; diff --git a/src/api/routes/connections/index.ts b/src/api/routes/connections/index.ts
index b30d4027..017ebbbb 100644 --- a/src/api/routes/connections/index.ts +++ b/src/api/routes/connections/index.ts
@@ -22,24 +22,24 @@ import { Request, Response, Router } from "express"; const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "APIConnectionsConfiguration", - }, - }, - }), - async (req: Request, res: Response) => { - const config = ConnectionConfig.get(); + "/", + route({ + responses: { + 200: { + body: "APIConnectionsConfiguration", + }, + }, + }), + async (req: Request, res: Response) => { + const config = ConnectionConfig.get(); - Object.keys(config).forEach((key) => { - delete config[key].clientId; - delete config[key].clientSecret; - }); + Object.keys(config).forEach((key) => { + delete config[key].clientId; + delete config[key].clientSecret; + }); - res.json(config); - }, + res.json(config); + }, ); export default router; diff --git a/src/api/routes/discoverable-guilds.ts b/src/api/routes/discoverable-guilds.ts
index ce34aa04..dcecec38 100644 --- a/src/api/routes/discoverable-guilds.ts +++ b/src/api/routes/discoverable-guilds.ts
@@ -25,52 +25,52 @@ import { Like } from "typeorm"; const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "DiscoverableGuildsResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { offset, limit, categories } = req.query; - const showAllGuilds = Config.get().guild.discovery.showAllGuilds; - const configLimit = Config.get().guild.discovery.limit; - let guilds; - if (categories == undefined) { - guilds = showAllGuilds - ? await Guild.find({ - take: Math.abs(Number(limit || configLimit)), - }) - : await Guild.find({ - where: { features: Like(`%DISCOVERABLE%`) }, - take: Math.abs(Number(limit || configLimit)), - }); - } else { - guilds = showAllGuilds - ? await Guild.find({ - where: { primary_category_id: categories.toString() }, - take: Math.abs(Number(limit || configLimit)), - }) - : await Guild.find({ - where: { - primary_category_id: categories.toString(), - features: Like("%DISCOVERABLE%"), - }, - take: Math.abs(Number(limit || configLimit)), - }); - } + "/", + route({ + responses: { + 200: { + body: "DiscoverableGuildsResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { offset, limit, categories } = req.query; + const showAllGuilds = Config.get().guild.discovery.showAllGuilds; + const configLimit = Config.get().guild.discovery.limit; + let guilds; + if (categories == undefined) { + guilds = showAllGuilds + ? await Guild.find({ + take: Math.abs(Number(limit || configLimit)), + }) + : await Guild.find({ + where: { features: Like(`%DISCOVERABLE%`) }, + take: Math.abs(Number(limit || configLimit)), + }); + } else { + guilds = showAllGuilds + ? await Guild.find({ + where: { primary_category_id: categories.toString() }, + take: Math.abs(Number(limit || configLimit)), + }) + : await Guild.find({ + where: { + primary_category_id: categories.toString(), + features: Like("%DISCOVERABLE%"), + }, + take: Math.abs(Number(limit || configLimit)), + }); + } - const total = guilds ? guilds.length : undefined; + const total = guilds ? guilds.length : undefined; - res.send({ - total: total, - guilds: guilds, - offset: Number(offset || Config.get().guild.discovery.offset), - limit: Number(limit || configLimit), - }); - }, + res.send({ + total: total, + guilds: guilds, + offset: Number(offset || Config.get().guild.discovery.offset), + limit: Number(limit || configLimit), + }); + }, ); export default router; diff --git a/src/api/routes/discovery.ts b/src/api/routes/discovery.ts
index cb1e3a9e..d865dfe8 100644 --- a/src/api/routes/discovery.ts +++ b/src/api/routes/discovery.ts
@@ -23,25 +23,25 @@ import { Request, Response, Router } from "express"; const router = Router({ mergeParams: true }); router.get( - "/categories", - route({ - responses: { - 200: { - body: "APIDiscoveryCategoryArray", - }, - }, - }), - async (req: Request, res: Response) => { - // TODO: - // Get locale instead + "/categories", + route({ + responses: { + 200: { + body: "APIDiscoveryCategoryArray", + }, + }, + }), + async (req: Request, res: Response) => { + // TODO: + // Get locale instead - // const { locale, primary_only } = req.query; - const { primary_only } = req.query; + // const { locale, primary_only } = req.query; + const { primary_only } = req.query; - const out = primary_only ? await Categories.find({ where: { is_primary: true } }) : await Categories.find(); + const out = primary_only ? await Categories.find({ where: { is_primary: true } }) : await Categories.find(); - res.send(out); - }, + res.send(out); + }, ); export default router; diff --git a/src/api/routes/download.ts b/src/api/routes/download.ts
index 06588326..f54a3bb2 100644 --- a/src/api/routes/download.ts +++ b/src/api/routes/download.ts
@@ -23,36 +23,36 @@ import { Request, Response, Router } from "express"; const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 302: {}, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { platform } = req.query; + "/", + route({ + responses: { + 302: {}, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { platform } = req.query; - if (!platform) - throw FieldErrors({ - platform: { - code: "BASE_TYPE_REQUIRED", - message: req.t("common:field.BASE_TYPE_REQUIRED"), - }, - }); + if (!platform) + throw FieldErrors({ + platform: { + code: "BASE_TYPE_REQUIRED", + message: req.t("common:field.BASE_TYPE_REQUIRED"), + }, + }); - const release = await ClientRelease.findOneOrFail({ - where: { - enabled: true, - platform: platform as string, - }, - order: { pub_date: "DESC" }, - }); + const release = await ClientRelease.findOneOrFail({ + where: { + enabled: true, + platform: platform as string, + }, + order: { pub_date: "DESC" }, + }); - res.redirect(release.url); - }, + res.redirect(release.url); + }, ); export default router; diff --git a/src/api/routes/emojis/#emoji_id/source.ts b/src/api/routes/emojis/#emoji_id/source.ts
index 7a5e1c7a..f5c4a830 100644 --- a/src/api/routes/emojis/#emoji_id/source.ts +++ b/src/api/routes/emojis/#emoji_id/source.ts
@@ -24,63 +24,63 @@ import { APIErrorResponse, EmojiGuild, EmojiSourceResponse } from "@spacebar/sch const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "EmojiSourceResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { emoji_id } = req.params; + "/", + route({ + responses: { + 200: { + body: "EmojiSourceResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { emoji_id } = req.params; - const emoji = await Emoji.findOne({ where: { id: emoji_id } }); - if (!emoji) { - res.status(404).json({ - code: DiscordApiErrors.UNKNOWN_EMOJI.code, - message: `No emoji with ID ${emoji_id} appear to exist. Are you sure you didn't mistype it?`, - errors: {}, - } as APIErrorResponse); - return; - } + const emoji = await Emoji.findOne({ where: { id: emoji_id } }); + if (!emoji) { + res.status(404).json({ + code: DiscordApiErrors.UNKNOWN_EMOJI.code, + message: `No emoji with ID ${emoji_id} appear to exist. Are you sure you didn't mistype it?`, + errors: {}, + } as APIErrorResponse); + return; + } - // TODO: emojis can be owned by applications these days, account for this when we get there? - res.json({ - type: "GUILD", - guild: { - ...(await Guild.findOne({ - where: { - id: emoji.guild_id, - }, - select: { - id: true, - name: true, - icon: true, - description: true, - features: true, - emojis: true, - premium_tier: true, - premium_subscription_count: true, - }, - })), - approximate_member_count: await Member.countBy({ - guild_id: emoji.guild_id, - }), - approximate_presence_count: await Member.countBy({ - guild_id: emoji.guild_id, - user: { - sessions: { - status: "online", - }, - }, - }), - } as EmojiGuild, - } as EmojiSourceResponse); - }, + // TODO: emojis can be owned by applications these days, account for this when we get there? + res.json({ + type: "GUILD", + guild: { + ...(await Guild.findOne({ + where: { + id: emoji.guild_id, + }, + select: { + id: true, + name: true, + icon: true, + description: true, + features: true, + emojis: true, + premium_tier: true, + premium_subscription_count: true, + }, + })), + approximate_member_count: await Member.countBy({ + guild_id: emoji.guild_id, + }), + approximate_presence_count: await Member.countBy({ + guild_id: emoji.guild_id, + user: { + sessions: { + status: "online", + }, + }, + }), + } as EmojiGuild, + } as EmojiSourceResponse); + }, ); export default router; diff --git a/src/api/routes/experiments.ts b/src/api/routes/experiments.ts
index bf120639..9dd54cc0 100644 --- a/src/api/routes/experiments.ts +++ b/src/api/routes/experiments.ts
@@ -22,8 +22,8 @@ import { route } from "@spacebar/api"; const router = Router({ mergeParams: true }); router.get("/", route({}), (req: Request, res: Response) => { - // TODO: - res.send({ fingerprint: "", assignments: [], guild_experiments: [] }); + // TODO: + res.send({ fingerprint: "", assignments: [], guild_experiments: [] }); }); export default router; diff --git a/src/api/routes/games/detectable.ts b/src/api/routes/games/detectable.ts
index 9e10bd58..e1415ea3 100644 --- a/src/api/routes/games/detectable.ts +++ b/src/api/routes/games/detectable.ts
@@ -22,33 +22,33 @@ import { ApplicationDetectableResponse } from "@spacebar/schemas"; const router: Router = Router({ mergeParams: true }); const cache = { - data: {}, - expires: 0, + data: {}, + expires: 0, }; // modern dclients call this, is /applications/detectable deprecated? router.get( - "/", - route({ - responses: { - 200: { - body: "ApplicationDetectableResponse", - }, - }, - }), - async (req: Request, res: Response) => { - // cache for 6 hours - if (Date.now() > cache.expires) { - const response = await fetch("https://discord.com/api/v10/games/detectable"); // because, well, it's unauthenticated anyways - const data = await response.json(); - cache.data = data as ApplicationDetectableResponse; - cache.expires = Date.now() + 6 * 60 * 60 * 1000; - } + "/", + route({ + responses: { + 200: { + body: "ApplicationDetectableResponse", + }, + }, + }), + async (req: Request, res: Response) => { + // cache for 6 hours + if (Date.now() > cache.expires) { + const response = await fetch("https://discord.com/api/v10/games/detectable"); // because, well, it's unauthenticated anyways + const data = await response.json(); + cache.data = data as ApplicationDetectableResponse; + cache.expires = Date.now() + 6 * 60 * 60 * 1000; + } - res.set("Cache-Control", `public, max-age=${Math.floor((cache.expires - Date.now()) / 1000)}, s-maxage=${Math.floor((cache.expires - Date.now()) / 1000)}, immutable`) - .status(200) - .json(cache.data); - }, + res.set("Cache-Control", `public, max-age=${Math.floor((cache.expires - Date.now()) / 1000)}, s-maxage=${Math.floor((cache.expires - Date.now()) / 1000)}, immutable`) + .status(200) + .json(cache.data); + }, ); export default router; diff --git a/src/api/routes/gateway/bot.ts b/src/api/routes/gateway/bot.ts
index 80fa9512..34a648cd 100644 --- a/src/api/routes/gateway/bot.ts +++ b/src/api/routes/gateway/bot.ts
@@ -23,27 +23,27 @@ import { Request, Response, Router } from "express"; const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "GatewayBotResponse", - }, - }, - }), - (req: Request, res: Response) => { - const { endpointPublic } = Config.get().gateway; - res.json({ - url: endpointPublic, - shards: 1, - session_start_limit: { - total: 1000, - remaining: 999, - reset_after: 14400000, - max_concurrency: 1, - }, - }); - }, + "/", + route({ + responses: { + 200: { + body: "GatewayBotResponse", + }, + }, + }), + (req: Request, res: Response) => { + const { endpointPublic } = Config.get().gateway; + res.json({ + url: endpointPublic, + shards: 1, + session_start_limit: { + total: 1000, + remaining: 999, + reset_after: 14400000, + max_concurrency: 1, + }, + }); + }, ); export default router; diff --git a/src/api/routes/gateway/index.ts b/src/api/routes/gateway/index.ts
index 1fafbf5b..b85fcd9f 100644 --- a/src/api/routes/gateway/index.ts +++ b/src/api/routes/gateway/index.ts
@@ -23,20 +23,20 @@ import { Request, Response, Router } from "express"; const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "GatewayResponse", - }, - }, - }), - (req: Request, res: Response) => { - const { endpointPublic } = Config.get().gateway; - res.json({ - url: endpointPublic, - }); - }, + "/", + route({ + responses: { + 200: { + body: "GatewayResponse", + }, + }, + }), + (req: Request, res: Response) => { + const { endpointPublic } = Config.get().gateway; + res.json({ + url: endpointPublic, + }); + }, ); export default router; diff --git a/src/api/routes/gifs/search.ts b/src/api/routes/gifs/search.ts
index e81c2c3a..ca07956e 100644 --- a/src/api/routes/gifs/search.ts +++ b/src/api/routes/gifs/search.ts
@@ -25,45 +25,45 @@ import { TenorGif, TenorMediaTypes } from "@spacebar/schemas"; const router = Router({ mergeParams: true }); router.get( - "/", - route({ - query: { - q: { - type: "string", - required: true, - description: "Search query", - }, - media_format: { - type: "string", - description: "Media format", - values: Object.keys(TenorMediaTypes).filter((key) => isNaN(Number(key))), - }, - locale: { - type: "string", - description: "Locale", - }, - }, - responses: { - 200: { - body: "TenorGifsResponse", - }, - }, - }), - async (req: Request, res: Response) => { - // TODO: Custom providers - const { q, media_format, locale } = req.query; + "/", + route({ + query: { + q: { + type: "string", + required: true, + description: "Search query", + }, + media_format: { + type: "string", + description: "Media format", + values: Object.keys(TenorMediaTypes).filter((key) => isNaN(Number(key))), + }, + locale: { + type: "string", + description: "Locale", + }, + }, + responses: { + 200: { + body: "TenorGifsResponse", + }, + }, + }), + async (req: Request, res: Response) => { + // TODO: Custom providers + const { q, media_format, locale } = req.query; - const apiKey = getGifApiKey(); + const apiKey = getGifApiKey(); - const response = await fetch(`https://g.tenor.com/v1/search?q=${q}&media_format=${media_format}&locale=${locale}&key=${apiKey}`, { - method: "get", - headers: { "Content-Type": "application/json" }, - }); + const response = await fetch(`https://g.tenor.com/v1/search?q=${q}&media_format=${media_format}&locale=${locale}&key=${apiKey}`, { + method: "get", + headers: { "Content-Type": "application/json" }, + }); - const { results } = (await response.json()) as { results: TenorGif[] }; + const { results } = (await response.json()) as { results: TenorGif[] }; - res.json(results.map(parseGifResult)).status(200); - }, + res.json(results.map(parseGifResult)).status(200); + }, ); export default router; diff --git a/src/api/routes/gifs/trending-gifs.ts b/src/api/routes/gifs/trending-gifs.ts
index d5907b4a..99aa528b 100644 --- a/src/api/routes/gifs/trending-gifs.ts +++ b/src/api/routes/gifs/trending-gifs.ts
@@ -24,40 +24,40 @@ import { TenorGif, TenorMediaTypes } from "@spacebar/schemas"; const router = Router({ mergeParams: true }); router.get( - "/", - route({ - query: { - media_format: { - type: "string", - description: "Media format", - values: Object.keys(TenorMediaTypes).filter((key) => isNaN(Number(key))), - }, - locale: { - type: "string", - description: "Locale", - }, - }, - responses: { - 200: { - body: "TenorGifsResponse", - }, - }, - }), - async (req: Request, res: Response) => { - // TODO: Custom providers - const { media_format, locale } = req.query; + "/", + route({ + query: { + media_format: { + type: "string", + description: "Media format", + values: Object.keys(TenorMediaTypes).filter((key) => isNaN(Number(key))), + }, + locale: { + type: "string", + description: "Locale", + }, + }, + responses: { + 200: { + body: "TenorGifsResponse", + }, + }, + }), + async (req: Request, res: Response) => { + // TODO: Custom providers + const { media_format, locale } = req.query; - const apiKey = getGifApiKey(); + const apiKey = getGifApiKey(); - const response = await fetch(`https://g.tenor.com/v1/trending?media_format=${media_format}&locale=${locale}&key=${apiKey}`, { - method: "get", - headers: { "Content-Type": "application/json" }, - }); + const response = await fetch(`https://g.tenor.com/v1/trending?media_format=${media_format}&locale=${locale}&key=${apiKey}`, { + method: "get", + headers: { "Content-Type": "application/json" }, + }); - const { results } = (await response.json()) as { results: TenorGif[] }; + const { results } = (await response.json()) as { results: TenorGif[] }; - res.json(results.map(parseGifResult)).status(200); - }, + res.json(results.map(parseGifResult)).status(200); + }, ); export default router; diff --git a/src/api/routes/gifs/trending.ts b/src/api/routes/gifs/trending.ts
index d86c06b5..4a7a3f3c 100644 --- a/src/api/routes/gifs/trending.ts +++ b/src/api/routes/gifs/trending.ts
@@ -24,50 +24,50 @@ import { TenorCategoriesResults, TenorTrendingResults } from "@spacebar/schemas" const router = Router({ mergeParams: true }); router.get( - "/", - route({ - query: { - locale: { - type: "string", - description: "Locale", - }, - }, - responses: { - 200: { - body: "TenorTrendingResponse", - }, - }, - }), - async (req: Request, res: Response) => { - // TODO: Custom providers - // TODO: return gifs as mp4 - // const { media_format, locale } = req.query; - const { locale } = req.query; + "/", + route({ + query: { + locale: { + type: "string", + description: "Locale", + }, + }, + responses: { + 200: { + body: "TenorTrendingResponse", + }, + }, + }), + async (req: Request, res: Response) => { + // TODO: Custom providers + // TODO: return gifs as mp4 + // const { media_format, locale } = req.query; + const { locale } = req.query; - const apiKey = getGifApiKey(); + const apiKey = getGifApiKey(); - const [responseSource, trendGifSource] = await Promise.all([ - fetch(`https://g.tenor.com/v1/categories?locale=${locale}&key=${apiKey}`, { - method: "get", - headers: { "Content-Type": "application/json" }, - }), - fetch(`https://g.tenor.com/v1/trending?locale=${locale}&key=${apiKey}`, { - method: "get", - headers: { "Content-Type": "application/json" }, - }), - ]); + const [responseSource, trendGifSource] = await Promise.all([ + fetch(`https://g.tenor.com/v1/categories?locale=${locale}&key=${apiKey}`, { + method: "get", + headers: { "Content-Type": "application/json" }, + }), + fetch(`https://g.tenor.com/v1/trending?locale=${locale}&key=${apiKey}`, { + method: "get", + headers: { "Content-Type": "application/json" }, + }), + ]); - const { tags } = (await responseSource.json()) as TenorCategoriesResults; - const { results } = (await trendGifSource.json()) as TenorTrendingResults; + const { tags } = (await responseSource.json()) as TenorCategoriesResults; + const { results } = (await trendGifSource.json()) as TenorTrendingResults; - res.json({ - categories: tags.map((x) => ({ - name: x.searchterm, - src: x.image, - })), - gifs: [parseGifResult(results[0])], - }).status(200); - }, + res.json({ + categories: tags.map((x) => ({ + name: x.searchterm, + src: x.image, + })), + gifs: [parseGifResult(results[0])], + }).status(200); + }, ); export default router; diff --git a/src/api/routes/guild-recommendations.ts b/src/api/routes/guild-recommendations.ts
index fa642ea4..38ba1025 100644 --- a/src/api/routes/guild-recommendations.ts +++ b/src/api/routes/guild-recommendations.ts
@@ -25,32 +25,32 @@ import { Like } from "typeorm"; const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "GuildRecommendationsResponse", - }, - }, - }), - async (req: Request, res: Response) => { - // const { limit, personalization_disabled } = req.query; - const { limit } = req.query; - const showAllGuilds = Config.get().guild.discovery.showAllGuilds; + "/", + route({ + responses: { + 200: { + body: "GuildRecommendationsResponse", + }, + }, + }), + async (req: Request, res: Response) => { + // const { limit, personalization_disabled } = req.query; + const { limit } = req.query; + const showAllGuilds = Config.get().guild.discovery.showAllGuilds; - const genLoadId = (size: number) => [...Array(size)].map(() => Math.floor(Math.random() * 16).toString(16)).join(""); + const genLoadId = (size: number) => [...Array(size)].map(() => Math.floor(Math.random() * 16).toString(16)).join(""); - const guilds = showAllGuilds - ? await Guild.find({ take: Math.abs(Number(limit || 24)) }) - : await Guild.find({ - where: { features: Like("%DISCOVERABLE%") }, - take: Math.abs(Number(limit || 24)), - }); - res.send({ - recommended_guilds: guilds, - load_id: `server_recs/${genLoadId(32)}`, - }).status(200); - }, + const guilds = showAllGuilds + ? await Guild.find({ take: Math.abs(Number(limit || 24)) }) + : await Guild.find({ + where: { features: Like("%DISCOVERABLE%") }, + take: Math.abs(Number(limit || 24)), + }); + res.send({ + recommended_guilds: guilds, + load_id: `server_recs/${genLoadId(32)}`, + }).status(200); + }, ); export default router; diff --git a/src/api/routes/guilds/#guild_id/application-command-index.ts b/src/api/routes/guilds/#guild_id/application-command-index.ts
index bb1d8000..244029d5 100644 --- a/src/api/routes/guilds/#guild_id/application-command-index.ts +++ b/src/api/routes/guilds/#guild_id/application-command-index.ts
@@ -25,66 +25,66 @@ import { ApplicationCommandSchema, ApplicationCommandType } from "@spacebar/sche const router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - const members = await Member.find({ where: { guild_id: req.params.guild_id, user: { bot: true } } }); - const applications: Application[] = []; + const members = await Member.find({ where: { guild_id: req.params.guild_id, user: { bot: true } } }); + const applications: Application[] = []; - for (const member of members) { - const app = await Application.findOne({ where: { id: member.id } }); - if (app) applications.push(app); - } + for (const member of members) { + const app = await Application.findOne({ where: { id: member.id } }); + if (app) applications.push(app); + } - const applicationsSendable = []; + const applicationsSendable = []; - for (const application of applications) { - applicationsSendable.push({ - bot_id: application.bot?.id, - description: application.description, - flags: application.flags, - icon: application.icon, - id: application.id, - name: application.name, - }); - } + for (const application of applications) { + applicationsSendable.push({ + bot_id: application.bot?.id, + description: application.description, + flags: application.flags, + icon: application.icon, + id: application.id, + name: application.name, + }); + } - const applicationCommands: ApplicationCommand[][] = []; + const applicationCommands: ApplicationCommand[][] = []; - for (const application of applications) { - applicationCommands.push(await ApplicationCommand.find({ where: { application_id: application.id, guild_id: IsNull() } })); - applicationCommands.push(await ApplicationCommand.find({ where: { application_id: application.id, guild_id: req.params.guild_id } })); - } + for (const application of applications) { + applicationCommands.push(await ApplicationCommand.find({ where: { application_id: application.id, guild_id: IsNull() } })); + applicationCommands.push(await ApplicationCommand.find({ where: { application_id: application.id, guild_id: req.params.guild_id } })); + } - const applicationCommandsSendable: ApplicationCommandSchema[] = []; + const applicationCommandsSendable: ApplicationCommandSchema[] = []; - for (const command of applicationCommands.flat()) { - applicationCommandsSendable.push({ - id: command.id, - type: command.type, - application_id: command.application_id, - guild_id: command.guild_id, - name: command.name, - name_localizations: command.name_localizations, - // name_localized: // TODO: make this work - description: command.description, - description_localizations: command.description_localizations, - // description_localized: // TODO: make this work - options: command.type === ApplicationCommandType.CHAT_INPUT ? command.options : undefined, - default_member_permissions: command.default_member_permissions, - dm_permission: command.dm_permission, - permissions: command.permissions, - nsfw: command.nsfw, - integration_types: command.integration_types, - global_popularity_rank: command.global_popularity_rank, - contexts: command.contexts, - version: command.version, - handler: command.handler, - }); - } + for (const command of applicationCommands.flat()) { + applicationCommandsSendable.push({ + id: command.id, + type: command.type, + application_id: command.application_id, + guild_id: command.guild_id, + name: command.name, + name_localizations: command.name_localizations, + // name_localized: // TODO: make this work + description: command.description, + description_localizations: command.description_localizations, + // description_localized: // TODO: make this work + options: command.type === ApplicationCommandType.CHAT_INPUT ? command.options : undefined, + default_member_permissions: command.default_member_permissions, + dm_permission: command.dm_permission, + permissions: command.permissions, + nsfw: command.nsfw, + integration_types: command.integration_types, + global_popularity_rank: command.global_popularity_rank, + contexts: command.contexts, + version: command.version, + handler: command.handler, + }); + } - res.send({ - applications: applicationsSendable, - application_commands: applicationCommandsSendable, - version: Snowflake.generate(), - }); + res.send({ + applications: applicationsSendable, + application_commands: applicationCommandsSendable, + version: Snowflake.generate(), + }); }); export default router; diff --git a/src/api/routes/guilds/#guild_id/audit-logs.ts b/src/api/routes/guilds/#guild_id/audit-logs.ts
index 620f8822..4acc8e50 100644 --- a/src/api/routes/guilds/#guild_id/audit-logs.ts +++ b/src/api/routes/guilds/#guild_id/audit-logs.ts
@@ -22,14 +22,14 @@ const router = Router({ mergeParams: true }); //TODO: implement audit logs router.get("/", route({}), async (req: Request, res: Response) => { - res.json({ - audit_log_entries: [], - users: [], - integrations: [], - webhooks: [], - guild_scheduled_events: [], - threads: [], - application_commands: [], - }); + res.json({ + audit_log_entries: [], + users: [], + integrations: [], + webhooks: [], + guild_scheduled_events: [], + threads: [], + application_commands: [], + }); }); export default router; diff --git a/src/api/routes/guilds/#guild_id/auto-moderation/rules.ts b/src/api/routes/guilds/#guild_id/auto-moderation/rules.ts
index e2b37324..8c24651d 100644 --- a/src/api/routes/guilds/#guild_id/auto-moderation/rules.ts +++ b/src/api/routes/guilds/#guild_id/auto-moderation/rules.ts
@@ -25,116 +25,116 @@ import { AutomodRuleSchema } from "@spacebar/schemas"; const router: Router = Router({ mergeParams: true }); router.get( - "/", - route({ - permission: ["MANAGE_GUILD"], - responses: { - 200: { - body: "AutomodRuleSchemaWithId[]", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; - const rules = await AutomodRule.find({ where: { guild_id } }); - return res.json(rules); - }, + "/", + route({ + permission: ["MANAGE_GUILD"], + responses: { + 200: { + body: "AutomodRuleSchemaWithId[]", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + const rules = await AutomodRule.find({ where: { guild_id } }); + return res.json(rules); + }, ); router.post( - "/", - route({ - // requestBody: "AutomodRuleSchema", - permission: ["MANAGE_GUILD"], - responses: { - 200: { - body: "AutomodRuleSchemaWithId", - }, - 400: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; - if (req.user_id !== req.body.creator_id) throw new HTTPError("You can't create a rule for someone else", 403); + "/", + route({ + // requestBody: "AutomodRuleSchema", + permission: ["MANAGE_GUILD"], + responses: { + 200: { + body: "AutomodRuleSchemaWithId", + }, + 400: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + if (req.user_id !== req.body.creator_id) throw new HTTPError("You can't create a rule for someone else", 403); - if (guild_id !== req.body.guild_id) throw new HTTPError("You can't create a rule for another guild", 403); + if (guild_id !== req.body.guild_id) throw new HTTPError("You can't create a rule for another guild", 403); - if (req.body.id) { - throw new HTTPError("You can't specify an ID for a new rule", 400); - } + if (req.body.id) { + throw new HTTPError("You can't specify an ID for a new rule", 400); + } - const data = req.body as AutomodRuleSchema; + const data = req.body as AutomodRuleSchema; - const created = AutomodRule.create({ - creator: await User.findOneOrFail({ - where: { id: data.creator_id }, - }), - ...data, - }); + const created = AutomodRule.create({ + creator: await User.findOneOrFail({ + where: { id: data.creator_id }, + }), + ...data, + }); - const savedRule = await AutomodRule.save(created); - return res.json(savedRule); - }, + const savedRule = await AutomodRule.save(created); + return res.json(savedRule); + }, ); router.patch( - "/:rule_id", - route({ - // requestBody: "AutomodRuleSchema - permission: ["MANAGE_GUILD"], - responses: { - 200: { - body: "AutomodRuleSchemaWithId", - }, - 400: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { rule_id } = req.params; - const rule = await AutomodRule.findOneOrFail({ - where: { id: rule_id }, - }); + "/:rule_id", + route({ + // requestBody: "AutomodRuleSchema + permission: ["MANAGE_GUILD"], + responses: { + 200: { + body: "AutomodRuleSchemaWithId", + }, + 400: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { rule_id } = req.params; + const rule = await AutomodRule.findOneOrFail({ + where: { id: rule_id }, + }); - const data = req.body as AutomodRuleSchema; + const data = req.body as AutomodRuleSchema; - AutomodRule.merge(rule, data); - const savedRule = await AutomodRule.save(rule); - return res.json(savedRule); - }, + AutomodRule.merge(rule, data); + const savedRule = await AutomodRule.save(rule); + return res.json(savedRule); + }, ); router.delete( - "/:rule_id", - route({ - permission: ["MANAGE_GUILD"], - responses: { - 204: {}, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { rule_id } = req.params; - await AutomodRule.delete({ id: rule_id }); - return res.status(204).send(); - }, + "/:rule_id", + route({ + permission: ["MANAGE_GUILD"], + responses: { + 204: {}, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { rule_id } = req.params; + await AutomodRule.delete({ id: rule_id }); + return res.status(204).send(); + }, ); export default router; diff --git a/src/api/routes/guilds/#guild_id/bans.ts b/src/api/routes/guilds/#guild_id/bans.ts
index 03b4edea..cf043b7e 100644 --- a/src/api/routes/guilds/#guild_id/bans.ts +++ b/src/api/routes/guilds/#guild_id/bans.ts
@@ -27,267 +27,267 @@ const router: Router = Router({ mergeParams: true }); /* TODO: Deleting the secrets is just a temporary go-around. Views should be implemented for both safety and better handling. */ router.get( - "/", - route({ - permission: "BAN_MEMBERS", - responses: { - 200: { - body: "APIBansArray", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; + "/", + route({ + permission: "BAN_MEMBERS", + responses: { + 200: { + body: "APIBansArray", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; - let bans = await Ban.find({ where: { guild_id: guild_id } }); - const promisesToAwait: Promise<User>[] = []; - const bansObj: APIBansArray = []; + let bans = await Ban.find({ where: { guild_id: guild_id } }); + const promisesToAwait: Promise<User>[] = []; + const bansObj: APIBansArray = []; - bans = bans.filter((ban) => ban.user_id !== ban.executor_id); // pretend self-bans don't exist to prevent victim chasing + bans = bans.filter((ban) => ban.user_id !== ban.executor_id); // pretend self-bans don't exist to prevent victim chasing - bans.forEach((ban) => { - promisesToAwait.push(User.getPublicUser(ban.user_id)); - }); + bans.forEach((ban) => { + promisesToAwait.push(User.getPublicUser(ban.user_id)); + }); - const bannedUsers = await Promise.all(promisesToAwait); + const bannedUsers = await Promise.all(promisesToAwait); - bans.forEach((ban, index) => { - const user = bannedUsers[index]; - bansObj.push({ - reason: ban.reason ?? null, - user: { - username: user.username, - discriminator: user.discriminator, - id: user.id, - avatar: user.avatar ?? null, - public_flags: user.public_flags, - }, - }); - }); + bans.forEach((ban, index) => { + const user = bannedUsers[index]; + bansObj.push({ + reason: ban.reason ?? null, + user: { + username: user.username, + discriminator: user.discriminator, + id: user.id, + avatar: user.avatar ?? null, + public_flags: user.public_flags, + }, + }); + }); - return res.json(bansObj); - }, + return res.json(bansObj); + }, ); router.get( - "/search", - route({ - permission: "BAN_MEMBERS", - query: { - query: { - type: "string", - description: "Query to match username(s) and display name(s) against (1-32 characters)", - required: true, - }, - limit: { - type: "number", - description: "Max number of members to return (1-10, default 10)", - required: false, - }, - }, - responses: { - 200: { - body: "APIBansArray", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; + "/search", + route({ + permission: "BAN_MEMBERS", + query: { + query: { + type: "string", + description: "Query to match username(s) and display name(s) against (1-32 characters)", + required: true, + }, + limit: { + type: "number", + description: "Max number of members to return (1-10, default 10)", + required: false, + }, + }, + responses: { + 200: { + body: "APIBansArray", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; - const limit = Number(req.query.limit) || 10; - if (limit > 10 || limit < 1) throw new HTTPError("Limit must be between 1 and 10"); + const limit = Number(req.query.limit) || 10; + if (limit > 10 || limit < 1) throw new HTTPError("Limit must be between 1 and 10"); - const query = String(req.query.query); - if (!query || query.trim().length === 0 || query.length > 32) { - throw new HTTPError("The query must be between 1 and 32 characters in length"); - } + const query = String(req.query.query); + if (!query || query.trim().length === 0 || query.length > 32) { + throw new HTTPError("The query must be between 1 and 32 characters in length"); + } - let bans = await Ban.createQueryBuilder("ban") - .leftJoinAndSelect("ban.user", "user") - .where("ban.guild_id = :guildId", { guildId: guild_id }) - .andWhere("user.username LIKE :userName", { - userName: `%${query}%`, - }) - .limit(limit) - .getMany(); + let bans = await Ban.createQueryBuilder("ban") + .leftJoinAndSelect("ban.user", "user") + .where("ban.guild_id = :guildId", { guildId: guild_id }) + .andWhere("user.username LIKE :userName", { + userName: `%${query}%`, + }) + .limit(limit) + .getMany(); - bans = bans.filter((ban) => ban.user_id !== ban.executor_id); // pretend self-bans don't exist to prevent victim chasing + bans = bans.filter((ban) => ban.user_id !== ban.executor_id); // pretend self-bans don't exist to prevent victim chasing - const bansObj: APIBansArray = bans.map((ban) => { - const user = ban.user; - return { - reason: ban.reason ?? null, - user: { - username: user.username, - discriminator: user.discriminator, - id: user.id, - avatar: user.avatar ?? null, - public_flags: user.public_flags, - }, - }; - }); + const bansObj: APIBansArray = bans.map((ban) => { + const user = ban.user; + return { + reason: ban.reason ?? null, + user: { + username: user.username, + discriminator: user.discriminator, + id: user.id, + avatar: user.avatar ?? null, + public_flags: user.public_flags, + }, + }; + }); - return res.json(bansObj); - }, + return res.json(bansObj); + }, ); router.get( - "/:user_id", - route({ - permission: "BAN_MEMBERS", - responses: { - 200: { - body: "GuildBansResponse", - }, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id, user_id } = req.params; + "/:user_id", + route({ + permission: "BAN_MEMBERS", + responses: { + 200: { + body: "GuildBansResponse", + }, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id, user_id } = req.params; - const ban = (await Ban.findOneOrFail({ - where: { guild_id: guild_id, user_id: user_id }, - })) as BanRegistrySchema; + const ban = (await Ban.findOneOrFail({ + where: { guild_id: guild_id, user_id: user_id }, + })) as BanRegistrySchema; - if (ban.user_id === ban.executor_id) throw DiscordApiErrors.UNKNOWN_BAN; - // pretend self-bans don't exist to prevent victim chasing + if (ban.user_id === ban.executor_id) throw DiscordApiErrors.UNKNOWN_BAN; + // pretend self-bans don't exist to prevent victim chasing - const user = await User.getPublicUser(ban.user_id); + const user = await User.getPublicUser(ban.user_id); - const banInfo: GuildBansResponse = { - user: { - username: user.username, - discriminator: user.discriminator, - id: user.id, - avatar: user.avatar ?? null, - public_flags: user.public_flags, - }, - reason: ban.reason ?? null, - }; + const banInfo: GuildBansResponse = { + user: { + username: user.username, + discriminator: user.discriminator, + id: user.id, + avatar: user.avatar ?? null, + public_flags: user.public_flags, + }, + reason: ban.reason ?? null, + }; - return res.json(banInfo); - }, + return res.json(banInfo); + }, ); router.put( - "/:user_id", - route({ - requestBody: "BanCreateSchema", - permission: "BAN_MEMBERS", - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; - const banned_user_id = req.params.user_id; - const opts = req.body as BanCreateSchema; + "/:user_id", + route({ + requestBody: "BanCreateSchema", + permission: "BAN_MEMBERS", + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + const banned_user_id = req.params.user_id; + const opts = req.body as BanCreateSchema; - let deleteMessagesMs = opts.delete_message_days - ? (opts.delete_message_days as number) * 86400000 - : opts.delete_message_seconds - ? (opts.delete_message_seconds as number) * 1000 - : 0; + let deleteMessagesMs = opts.delete_message_days + ? (opts.delete_message_days as number) * 86400000 + : opts.delete_message_seconds + ? (opts.delete_message_seconds as number) * 1000 + : 0; - if (deleteMessagesMs < 0) deleteMessagesMs = 0; + if (deleteMessagesMs < 0) deleteMessagesMs = 0; - if (req.user_id === banned_user_id && banned_user_id === req.permission?.cache.guild?.owner_id) - throw new HTTPError("You are the guild owner, hence can't ban yourself", 403); + if (req.user_id === banned_user_id && banned_user_id === req.permission?.cache.guild?.owner_id) + throw new HTTPError("You are the guild owner, hence can't ban yourself", 403); - if (req.permission?.cache.guild?.owner_id === banned_user_id) throw new HTTPError("You can't ban the owner", 400); + if (req.permission?.cache.guild?.owner_id === banned_user_id) throw new HTTPError("You can't ban the owner", 400); - const existingBan = await Ban.findOne({ - where: { guild_id: guild_id, user_id: banned_user_id }, - }); + const existingBan = await Ban.findOne({ + where: { guild_id: guild_id, user_id: banned_user_id }, + }); - // Bans on already banned users are silently ignored - if (existingBan) return res.status(204).send(); + // Bans on already banned users are silently ignored + if (existingBan) return res.status(204).send(); - const banned_user = await User.getPublicUser(banned_user_id); + const banned_user = await User.getPublicUser(banned_user_id); - const ban = Ban.create({ - user_id: banned_user_id, - guild_id: guild_id, - executor_id: req.user_id, - reason: req.body.reason, // || otherwise empty - }); + const ban = Ban.create({ + user_id: banned_user_id, + guild_id: guild_id, + executor_id: req.user_id, + reason: req.body.reason, // || otherwise empty + }); - await Promise.all([ - Member.removeFromGuild(banned_user_id, guild_id), - ban.save(), - emitEvent({ - event: "GUILD_BAN_ADD", - data: { - guild_id: guild_id, - user: banned_user.toPublicUser(), - delete_message_secs: Math.floor(deleteMessagesMs / 1000), - }, - guild_id: guild_id, - } as GuildBanAddEvent), - ]); + await Promise.all([ + Member.removeFromGuild(banned_user_id, guild_id), + ban.save(), + emitEvent({ + event: "GUILD_BAN_ADD", + data: { + guild_id: guild_id, + user: banned_user.toPublicUser(), + delete_message_secs: Math.floor(deleteMessagesMs / 1000), + }, + guild_id: guild_id, + } as GuildBanAddEvent), + ]); - return res.status(204).send(); - }, + return res.status(204).send(); + }, ); router.delete( - "/:user_id", - route({ - permission: "BAN_MEMBERS", - responses: { - 204: {}, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id, user_id } = req.params; + "/:user_id", + route({ + permission: "BAN_MEMBERS", + responses: { + 204: {}, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id, user_id } = req.params; - await Ban.findOneOrFail({ - where: { guild_id: guild_id, user_id: user_id }, - }); + await Ban.findOneOrFail({ + where: { guild_id: guild_id, user_id: user_id }, + }); - const banned_user = await User.getPublicUser(user_id); + const banned_user = await User.getPublicUser(user_id); - await Promise.all([ - Ban.delete({ - user_id: user_id, - guild_id, - }), + await Promise.all([ + Ban.delete({ + user_id: user_id, + guild_id, + }), - emitEvent({ - event: "GUILD_BAN_REMOVE", - data: { - guild_id, - user: banned_user, - }, - guild_id, - } as GuildBanRemoveEvent), - ]); + emitEvent({ + event: "GUILD_BAN_REMOVE", + data: { + guild_id, + user: banned_user, + }, + guild_id, + } as GuildBanRemoveEvent), + ]); - return res.status(204).send(); - }, + return res.status(204).send(); + }, ); export default router; diff --git a/src/api/routes/guilds/#guild_id/bulk-ban.ts b/src/api/routes/guilds/#guild_id/bulk-ban.ts
index a564e2d4..004c2ab2 100644 --- a/src/api/routes/guilds/#guild_id/bulk-ban.ts +++ b/src/api/routes/guilds/#guild_id/bulk-ban.ts
@@ -25,93 +25,93 @@ import { Config } from "@spacebar/util"; const router: Router = Router({ mergeParams: true }); router.post( - "/", - route({ - requestBody: "BulkBanSchema", - permission: ["BAN_MEMBERS", "MANAGE_GUILD"], - responses: { - 200: { - body: "Ban", - }, - 400: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; + "/", + route({ + requestBody: "BulkBanSchema", + permission: ["BAN_MEMBERS", "MANAGE_GUILD"], + responses: { + 200: { + body: "Ban", + }, + 400: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; - const userIds: Array<string> = req.body.user_ids; - if (!userIds) throw new HTTPError("The user_ids array is missing", 400); + const userIds: Array<string> = req.body.user_ids; + if (!userIds) throw new HTTPError("The user_ids array is missing", 400); - if (userIds.length > Config.get().limits.guild.maxBulkBanUsers) throw new HTTPError("The user_ids array must be between 1 and 200 in length", 400); + if (userIds.length > Config.get().limits.guild.maxBulkBanUsers) throw new HTTPError("The user_ids array must be between 1 and 200 in length", 400); - const banned_users = []; - const failed_users = []; - for await (const banned_user_id of userIds) { - if (req.user_id === banned_user_id && banned_user_id === req.permission?.cache.guild?.owner_id) { - failed_users.push(banned_user_id); - continue; - } + const banned_users = []; + const failed_users = []; + for await (const banned_user_id of userIds) { + if (req.user_id === banned_user_id && banned_user_id === req.permission?.cache.guild?.owner_id) { + failed_users.push(banned_user_id); + continue; + } - if (req.permission?.cache.guild?.owner_id === banned_user_id) { - failed_users.push(banned_user_id); - continue; - } + if (req.permission?.cache.guild?.owner_id === banned_user_id) { + failed_users.push(banned_user_id); + continue; + } - const existingBan = await Ban.findOne({ - where: { guild_id: guild_id, user_id: banned_user_id }, - }); - if (existingBan) { - failed_users.push(banned_user_id); - continue; - } + const existingBan = await Ban.findOne({ + where: { guild_id: guild_id, user_id: banned_user_id }, + }); + if (existingBan) { + failed_users.push(banned_user_id); + continue; + } - let banned_user; - try { - banned_user = await User.getPublicUser(banned_user_id); - } catch { - failed_users.push(banned_user_id); - continue; - } + let banned_user; + try { + banned_user = await User.getPublicUser(banned_user_id); + } catch { + failed_users.push(banned_user_id); + continue; + } - const ban = Ban.create({ - user_id: banned_user_id, - guild_id: guild_id, - ip: req.ip, - executor_id: req.user_id, - reason: req.body.reason, // || otherwise empty - }); + const ban = Ban.create({ + user_id: banned_user_id, + guild_id: guild_id, + ip: req.ip, + executor_id: req.user_id, + reason: req.body.reason, // || otherwise empty + }); - try { - await Promise.all([ - Member.removeFromGuild(banned_user_id, guild_id), - ban.save(), - emitEvent({ - event: "GUILD_BAN_ADD", - data: { - guild_id: guild_id, - user: banned_user.toPublicUser(), - }, - guild_id: guild_id, - } as GuildBanAddEvent), - ]); - banned_users.push(banned_user_id); - } catch { - failed_users.push(banned_user_id); - continue; - } - } + try { + await Promise.all([ + Member.removeFromGuild(banned_user_id, guild_id), + ban.save(), + emitEvent({ + event: "GUILD_BAN_ADD", + data: { + guild_id: guild_id, + user: banned_user.toPublicUser(), + }, + guild_id: guild_id, + } as GuildBanAddEvent), + ]); + banned_users.push(banned_user_id); + } catch { + failed_users.push(banned_user_id); + continue; + } + } - if (banned_users.length === 0 && failed_users.length > 0) throw DiscordApiErrors.BULK_BAN_FAILED; - return res.json({ - banned_users: banned_users, - failed_users: failed_users, - }); - }, + if (banned_users.length === 0 && failed_users.length > 0) throw DiscordApiErrors.BULK_BAN_FAILED; + return res.json({ + banned_users: banned_users, + failed_users: failed_users, + }); + }, ); export default router; diff --git a/src/api/routes/guilds/#guild_id/channels.ts b/src/api/routes/guilds/#guild_id/channels.ts
index ecb25bee..fc374acb 100644 --- a/src/api/routes/guilds/#guild_id/channels.ts +++ b/src/api/routes/guilds/#guild_id/channels.ts
@@ -23,148 +23,148 @@ import { ChannelModifySchema, ChannelReorderSchema } from "@spacebar/schemas"; const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 201: { - body: "APIChannelArray", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; - const channels = await Channel.find({ where: { guild_id } }); + "/", + route({ + responses: { + 201: { + body: "APIChannelArray", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + const channels = await Channel.find({ where: { guild_id } }); - for await (const channel of channels) { - channel.position = await Channel.calculatePosition(channel.id, guild_id, channel.guild); - } - channels.sort((a, b) => a.position - b.position); + for await (const channel of channels) { + channel.position = await Channel.calculatePosition(channel.id, guild_id, channel.guild); + } + channels.sort((a, b) => a.position - b.position); - res.json(channels); - }, + res.json(channels); + }, ); router.post( - "/", - route({ - requestBody: "ChannelModifySchema", - permission: "MANAGE_CHANNELS", - responses: { - 201: { - body: "Channel", - }, - 400: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - // creates a new guild channel https://discord.com/developers/docs/resources/guild#create-guild-channel - const { guild_id } = req.params; - const body = req.body as ChannelModifySchema; + "/", + route({ + requestBody: "ChannelModifySchema", + permission: "MANAGE_CHANNELS", + responses: { + 201: { + body: "Channel", + }, + 400: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + // creates a new guild channel https://discord.com/developers/docs/resources/guild#create-guild-channel + const { guild_id } = req.params; + const body = req.body as ChannelModifySchema; - const channel = await Channel.createChannel({ ...body, guild_id }, req.user_id); - channel.position = await Channel.calculatePosition(channel.id, guild_id, channel.guild); + const channel = await Channel.createChannel({ ...body, guild_id }, req.user_id); + channel.position = await Channel.calculatePosition(channel.id, guild_id, channel.guild); - res.status(201).json(channel); - }, + res.status(201).json(channel); + }, ); router.patch( - "/", - route({ - requestBody: "ChannelReorderSchema", - permission: "MANAGE_CHANNELS", - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - // changes guild channel position - const { guild_id } = req.params; - let body = req.body as ChannelReorderSchema; + "/", + route({ + requestBody: "ChannelReorderSchema", + permission: "MANAGE_CHANNELS", + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + // changes guild channel position + const { guild_id } = req.params; + let body = req.body as ChannelReorderSchema; - const guild = await Guild.findOneOrFail({ - where: { id: guild_id }, - select: { channel_ordering: true }, - }); + const guild = await Guild.findOneOrFail({ + where: { id: guild_id }, + select: { channel_ordering: true }, + }); - body = body.sort((a, b) => { - const apos = a.position || (a.parent_id ? guild.channel_ordering.findIndex((_) => _ === a.parent_id) + 1 : 0); - const bpos = b.position || (b.parent_id ? guild.channel_ordering.findIndex((_) => _ === b.parent_id) + 1 : 0); - return apos - bpos; - }); + body = body.sort((a, b) => { + const apos = a.position || (a.parent_id ? guild.channel_ordering.findIndex((_) => _ === a.parent_id) + 1 : 0); + const bpos = b.position || (b.parent_id ? guild.channel_ordering.findIndex((_) => _ === b.parent_id) + 1 : 0); + return apos - bpos; + }); - // The channels not listed for this query - const notMentioned = guild.channel_ordering.filter((x) => !body.find((c) => c.id == x)); + // The channels not listed for this query + const notMentioned = guild.channel_ordering.filter((x) => !body.find((c) => c.id == x)); - const withParents = body.filter((x) => x.parent_id !== undefined); - const withPositions = body.filter((x) => x.position !== undefined); - // You can't do it with Promise.all or the way this is being done is super incorrect - for await (const opt of withPositions) { - const channel = await Channel.findOneOrFail({ - where: { id: opt.id }, - }); + const withParents = body.filter((x) => x.parent_id !== undefined); + const withPositions = body.filter((x) => x.position !== undefined); + // You can't do it with Promise.all or the way this is being done is super incorrect + for await (const opt of withPositions) { + const channel = await Channel.findOneOrFail({ + where: { id: opt.id }, + }); - notMentioned.splice(opt.position as number, 0, channel.id); - channel.position = notMentioned.findIndex((_) => _ === channel.id); + notMentioned.splice(opt.position as number, 0, channel.id); + channel.position = notMentioned.findIndex((_) => _ === channel.id); - await emitEvent({ - event: "CHANNEL_UPDATE", - data: channel, - channel_id: channel.id, - guild_id, - } as ChannelUpdateEvent); - } - // Due to this also being able to change the order, this needs to be done in order - // have to do the parents after the positions - for await (const opt of withParents) { - const [channel, parent] = await Promise.all([ - Channel.findOneOrFail({ - where: { id: opt.id }, - }), - opt.parent_id - ? Channel.findOneOrFail({ - where: { id: opt.parent_id }, - select: { - permission_overwrites: true, - id: true, - }, - }) - : null, - ]); + await emitEvent({ + event: "CHANNEL_UPDATE", + data: channel, + channel_id: channel.id, + guild_id, + } as ChannelUpdateEvent); + } + // Due to this also being able to change the order, this needs to be done in order + // have to do the parents after the positions + for await (const opt of withParents) { + const [channel, parent] = await Promise.all([ + Channel.findOneOrFail({ + where: { id: opt.id }, + }), + opt.parent_id + ? Channel.findOneOrFail({ + where: { id: opt.parent_id }, + select: { + permission_overwrites: true, + id: true, + }, + }) + : null, + ]); - if (opt.lock_permissions && parent) await Channel.update({ id: channel.id }, { permission_overwrites: parent.permission_overwrites }); - if (parent && opt.position === undefined) { - const parentPos = notMentioned.indexOf(parent.id); - notMentioned.splice(parentPos + 1, 0, channel.id); - channel.position = (parentPos + 1) as number; - } - channel.parent = parent || undefined; - channel.parent_id = parent?.id || null; - await channel.save(); + if (opt.lock_permissions && parent) await Channel.update({ id: channel.id }, { permission_overwrites: parent.permission_overwrites }); + if (parent && opt.position === undefined) { + const parentPos = notMentioned.indexOf(parent.id); + notMentioned.splice(parentPos + 1, 0, channel.id); + channel.position = (parentPos + 1) as number; + } + channel.parent = parent || undefined; + channel.parent_id = parent?.id || null; + await channel.save(); - await emitEvent({ - event: "CHANNEL_UPDATE", - data: channel, - channel_id: channel.id, - guild_id, - } as ChannelUpdateEvent); - } + await emitEvent({ + event: "CHANNEL_UPDATE", + data: channel, + channel_id: channel.id, + guild_id, + } as ChannelUpdateEvent); + } - await Guild.update({ id: guild_id }, { channel_ordering: notMentioned }); + await Guild.update({ id: guild_id }, { channel_ordering: notMentioned }); - return res.sendStatus(204); - }, + return res.sendStatus(204); + }, ); export default router; diff --git a/src/api/routes/guilds/#guild_id/delete.ts b/src/api/routes/guilds/#guild_id/delete.ts
index 509445c4..4edd2c39 100644 --- a/src/api/routes/guilds/#guild_id/delete.ts +++ b/src/api/routes/guilds/#guild_id/delete.ts
@@ -26,40 +26,40 @@ const router = Router({ mergeParams: true }); // discord prefixes this route with /delete instead of using the delete method // docs are wrong https://discord.com/developers/docs/resources/guild#delete-guild router.post( - "/", - route({ - responses: { - 204: {}, - 401: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; + "/", + route({ + responses: { + 204: {}, + 401: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; - const guild = await Guild.findOneOrFail({ - where: { id: guild_id }, - select: ["owner_id"], - }); - if (guild.owner_id !== req.user_id) throw new HTTPError("You are not the owner of this guild", 401); + const guild = await Guild.findOneOrFail({ + where: { id: guild_id }, + select: ["owner_id"], + }); + if (guild.owner_id !== req.user_id) throw new HTTPError("You are not the owner of this guild", 401); - await Promise.all([ - Guild.delete({ id: guild_id }), // this will also delete all guild related data - emitEvent({ - event: "GUILD_DELETE", - data: { - id: guild_id, - }, - guild_id: guild_id, - } as GuildDeleteEvent), - ]); + await Promise.all([ + Guild.delete({ id: guild_id }), // this will also delete all guild related data + emitEvent({ + event: "GUILD_DELETE", + data: { + id: guild_id, + }, + guild_id: guild_id, + } as GuildDeleteEvent), + ]); - return res.sendStatus(204); - }, + return res.sendStatus(204); + }, ); export default router; diff --git a/src/api/routes/guilds/#guild_id/discovery-requirements.ts b/src/api/routes/guilds/#guild_id/discovery-requirements.ts
index 89727335..150c9209 100644 --- a/src/api/routes/guilds/#guild_id/discovery-requirements.ts +++ b/src/api/routes/guilds/#guild_id/discovery-requirements.ts
@@ -22,44 +22,44 @@ import { Request, Response, Router } from "express"; const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "GuildDiscoveryRequirementsResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; - // TODO: - // Load from database - // Admin control, but for now it allows anyone to be discoverable + "/", + route({ + responses: { + 200: { + body: "GuildDiscoveryRequirementsResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + // TODO: + // Load from database + // Admin control, but for now it allows anyone to be discoverable - res.send({ - guild_id: guild_id, - safe_environment: true, - healthy: true, - health_score_pending: false, - size: true, - nsfw_properties: {}, - protected: true, - sufficient: true, - sufficient_without_grace_period: true, - valid_rules_channel: true, - retention_healthy: true, - engagement_healthy: true, - age: true, - minimum_age: 0, - health_score: { - avg_nonnew_participators: 0, - avg_nonnew_communicators: 0, - num_intentful_joiners: 0, - perc_ret_w1_intentful: 0, - }, - minimum_size: 0, - }); - }, + res.send({ + guild_id: guild_id, + safe_environment: true, + healthy: true, + health_score_pending: false, + size: true, + nsfw_properties: {}, + protected: true, + sufficient: true, + sufficient_without_grace_period: true, + valid_rules_channel: true, + retention_healthy: true, + engagement_healthy: true, + age: true, + minimum_age: 0, + health_score: { + avg_nonnew_participators: 0, + avg_nonnew_communicators: 0, + num_intentful_joiners: 0, + perc_ret_w1_intentful: 0, + }, + minimum_size: 0, + }); + }, ); export default router; diff --git a/src/api/routes/guilds/#guild_id/emojis.ts b/src/api/routes/guilds/#guild_id/emojis.ts
index c74f939f..5b07380f 100644 --- a/src/api/routes/guilds/#guild_id/emojis.ts +++ b/src/api/routes/guilds/#guild_id/emojis.ts
@@ -24,186 +24,186 @@ import { EmojiCreateSchema, EmojiModifySchema } from "@spacebar/schemas"; const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "APIEmojiArray", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; + "/", + route({ + responses: { + 200: { + body: "APIEmojiArray", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; - await Member.IsInGuildOrFail(req.user_id, guild_id); + await Member.IsInGuildOrFail(req.user_id, guild_id); - const emojis = await Emoji.find({ - where: { guild_id: guild_id }, - relations: ["user"], - }); + const emojis = await Emoji.find({ + where: { guild_id: guild_id }, + relations: ["user"], + }); - return res.json(emojis); - }, + return res.json(emojis); + }, ); router.get( - "/:emoji_id", - route({ - responses: { - 200: { - body: "Emoji", - }, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id, emoji_id } = req.params; + "/:emoji_id", + route({ + responses: { + 200: { + body: "Emoji", + }, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id, emoji_id } = req.params; - await Member.IsInGuildOrFail(req.user_id, guild_id); + await Member.IsInGuildOrFail(req.user_id, guild_id); - const emoji = await Emoji.findOneOrFail({ - where: { guild_id: guild_id, id: emoji_id }, - relations: ["user"], - }); + const emoji = await Emoji.findOneOrFail({ + where: { guild_id: guild_id, id: emoji_id }, + relations: ["user"], + }); - return res.json(emoji); - }, + return res.json(emoji); + }, ); router.post( - "/", - route({ - requestBody: "EmojiCreateSchema", - permission: "MANAGE_EMOJIS_AND_STICKERS", - responses: { - 201: { - body: "Emoji", - }, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; - const body = req.body as EmojiCreateSchema; + "/", + route({ + requestBody: "EmojiCreateSchema", + permission: "MANAGE_EMOJIS_AND_STICKERS", + responses: { + 201: { + body: "Emoji", + }, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + const body = req.body as EmojiCreateSchema; - const id = Snowflake.generate(); - const emoji_count = await Emoji.count({ - where: { guild_id: guild_id }, - }); - const { maxEmojis } = Config.get().limits.guild; + const id = Snowflake.generate(); + const emoji_count = await Emoji.count({ + where: { guild_id: guild_id }, + }); + const { maxEmojis } = Config.get().limits.guild; - if (emoji_count >= maxEmojis) throw DiscordApiErrors.MAXIMUM_NUMBER_OF_EMOJIS_REACHED.withParams(maxEmojis); - if (body.require_colons == null) body.require_colons = true; + if (emoji_count >= maxEmojis) throw DiscordApiErrors.MAXIMUM_NUMBER_OF_EMOJIS_REACHED.withParams(maxEmojis); + if (body.require_colons == null) body.require_colons = true; - const user = await User.findOneOrFail({ where: { id: req.user_id } }); - await handleFile(`/emojis/${id}`, body.image); + const user = await User.findOneOrFail({ where: { id: req.user_id } }); + await handleFile(`/emojis/${id}`, body.image); - const mimeType = body.image.split(":")[1].split(";")[0]; - const emoji = await Emoji.create({ - id: id, - guild_id: guild_id, - name: body.name, - require_colons: body.require_colons ?? undefined, // schema allows nulls, db does not - user: user, - managed: false, - animated: mimeType == "image/gif" || mimeType == "image/apng" || mimeType == "video/webm", - available: true, - roles: [], - }).save(); + const mimeType = body.image.split(":")[1].split(";")[0]; + const emoji = await Emoji.create({ + id: id, + guild_id: guild_id, + name: body.name, + require_colons: body.require_colons ?? undefined, // schema allows nulls, db does not + user: user, + managed: false, + animated: mimeType == "image/gif" || mimeType == "image/apng" || mimeType == "video/webm", + available: true, + roles: [], + }).save(); - await emitEvent({ - event: "GUILD_EMOJIS_UPDATE", - guild_id: guild_id, - data: { - guild_id: guild_id, - emojis: await Emoji.find({ where: { guild_id: guild_id } }), - }, - } as GuildEmojisUpdateEvent); + await emitEvent({ + event: "GUILD_EMOJIS_UPDATE", + guild_id: guild_id, + data: { + guild_id: guild_id, + emojis: await Emoji.find({ where: { guild_id: guild_id } }), + }, + } as GuildEmojisUpdateEvent); - return res.status(201).json(emoji); - }, + return res.status(201).json(emoji); + }, ); router.patch( - "/:emoji_id", - route({ - requestBody: "EmojiModifySchema", - permission: "MANAGE_EMOJIS_AND_STICKERS", - responses: { - 200: { - body: "Emoji", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { emoji_id, guild_id } = req.params; - const body = req.body as EmojiModifySchema; + "/:emoji_id", + route({ + requestBody: "EmojiModifySchema", + permission: "MANAGE_EMOJIS_AND_STICKERS", + responses: { + 200: { + body: "Emoji", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { emoji_id, guild_id } = req.params; + const body = req.body as EmojiModifySchema; - const emoji = await Emoji.create({ - ...body, - id: emoji_id, - guild_id: guild_id, - }).save(); + const emoji = await Emoji.create({ + ...body, + id: emoji_id, + guild_id: guild_id, + }).save(); - await emitEvent({ - event: "GUILD_EMOJIS_UPDATE", - guild_id: guild_id, - data: { - guild_id: guild_id, - emojis: await Emoji.find({ where: { guild_id: guild_id } }), - }, - } as GuildEmojisUpdateEvent); + await emitEvent({ + event: "GUILD_EMOJIS_UPDATE", + guild_id: guild_id, + data: { + guild_id: guild_id, + emojis: await Emoji.find({ where: { guild_id: guild_id } }), + }, + } as GuildEmojisUpdateEvent); - return res.json(emoji); - }, + return res.json(emoji); + }, ); router.delete( - "/:emoji_id", - route({ - permission: "MANAGE_EMOJIS_AND_STICKERS", - responses: { - 204: {}, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { emoji_id, guild_id } = req.params; + "/:emoji_id", + route({ + permission: "MANAGE_EMOJIS_AND_STICKERS", + responses: { + 204: {}, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { emoji_id, guild_id } = req.params; - await Emoji.delete({ - id: emoji_id, - guild_id: guild_id, - }); + await Emoji.delete({ + id: emoji_id, + guild_id: guild_id, + }); - await emitEvent({ - event: "GUILD_EMOJIS_UPDATE", - guild_id: guild_id, - data: { - guild_id: guild_id, - emojis: await Emoji.find({ where: { guild_id: guild_id } }), - }, - } as GuildEmojisUpdateEvent); + await emitEvent({ + event: "GUILD_EMOJIS_UPDATE", + guild_id: guild_id, + data: { + guild_id: guild_id, + emojis: await Emoji.find({ where: { guild_id: guild_id } }), + }, + } as GuildEmojisUpdateEvent); - res.sendStatus(204); - }, + res.sendStatus(204); + }, ); export default router; diff --git a/src/api/routes/guilds/#guild_id/index.ts b/src/api/routes/guilds/#guild_id/index.ts
index d6593bd8..38cc9139 100644 --- a/src/api/routes/guilds/#guild_id/index.ts +++ b/src/api/routes/guilds/#guild_id/index.ts
@@ -25,188 +25,188 @@ import { GuildUpdateSchema } from "@spacebar/schemas"; const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - "200": { - body: "APIGuildWithJoinedAt", - }, - 401: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; + "/", + route({ + responses: { + "200": { + body: "APIGuildWithJoinedAt", + }, + 401: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; - const [guild, member] = await Promise.all([Guild.findOneOrFail({ where: { id: guild_id } }), Member.findOne({ where: { guild_id: guild_id, id: req.user_id } })]); - if (!member) throw new HTTPError("You are not a member of the guild you are trying to access", 401); + const [guild, member] = await Promise.all([Guild.findOneOrFail({ where: { id: guild_id } }), Member.findOne({ where: { guild_id: guild_id, id: req.user_id } })]); + if (!member) throw new HTTPError("You are not a member of the guild you are trying to access", 401); - return res.send({ - ...guild, - joined_at: member?.joined_at, - }); - }, + return res.send({ + ...guild, + joined_at: member?.joined_at, + }); + }, ); router.patch( - "/", - route({ - requestBody: "GuildUpdateSchema", - permission: "MANAGE_GUILD", - responses: { - 200: { - body: "GuildCreateResponse", - }, - 401: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const body = req.body as GuildUpdateSchema; - const { guild_id } = req.params; + "/", + route({ + requestBody: "GuildUpdateSchema", + permission: "MANAGE_GUILD", + responses: { + 200: { + body: "GuildCreateResponse", + }, + 401: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const body = req.body as GuildUpdateSchema; + const { guild_id } = req.params; - const rights = await getRights(req.user_id); - const permission = await getPermission(req.user_id, guild_id); + const rights = await getRights(req.user_id); + const permission = await getPermission(req.user_id, guild_id); - if (!rights.has("MANAGE_GUILDS") && !permission.has("MANAGE_GUILD")) throw DiscordApiErrors.MISSING_PERMISSIONS.withParams("MANAGE_GUILDS"); + if (!rights.has("MANAGE_GUILDS") && !permission.has("MANAGE_GUILD")) throw DiscordApiErrors.MISSING_PERMISSIONS.withParams("MANAGE_GUILDS"); - const guild = await Guild.findOneOrFail({ - where: { id: guild_id }, - relations: ["emojis", "roles", "stickers"], - }); + const guild = await Guild.findOneOrFail({ + where: { id: guild_id }, + relations: ["emojis", "roles", "stickers"], + }); - // trying to `select` this fails - guild.channel_ordering = ( - await Guild.findOneOrFail({ - where: { id: guild_id }, - select: { channel_ordering: true }, - }) - ).channel_ordering; + // trying to `select` this fails + guild.channel_ordering = ( + await Guild.findOneOrFail({ + where: { id: guild_id }, + select: { channel_ordering: true }, + }) + ).channel_ordering; - // TODO: guild update check image + // TODO: guild update check image - if (body.icon && body.icon != guild.icon) body.icon = await handleFile(`/icons/${guild_id}`, body.icon); + if (body.icon && body.icon != guild.icon) body.icon = await handleFile(`/icons/${guild_id}`, body.icon); - if (body.banner && body.banner !== guild.banner) body.banner = await handleFile(`/banners/${guild_id}`, body.banner); + if (body.banner && body.banner !== guild.banner) body.banner = await handleFile(`/banners/${guild_id}`, body.banner); - if (body.splash && body.splash !== guild.splash) body.splash = await handleFile(`/splashes/${guild_id}`, body.splash); + if (body.splash && body.splash !== guild.splash) body.splash = await handleFile(`/splashes/${guild_id}`, body.splash); - if (body.discovery_splash && body.discovery_splash !== guild.discovery_splash) - body.discovery_splash = await handleFile(`/discovery-splashes/${guild_id}`, body.discovery_splash); + if (body.discovery_splash && body.discovery_splash !== guild.discovery_splash) + body.discovery_splash = await handleFile(`/discovery-splashes/${guild_id}`, body.discovery_splash); - if (body.features) { - const diff = guild.features.filter((x) => !body.features?.includes(x)).concat(body.features.filter((x) => !guild.features.includes(x))); + if (body.features) { + const diff = guild.features.filter((x) => !body.features?.includes(x)).concat(body.features.filter((x) => !guild.features.includes(x))); - // TODO move these - const MUTABLE_FEATURES = ["COMMUNITY", "INVITES_DISABLED", "DISCOVERABLE"]; + // TODO move these + const MUTABLE_FEATURES = ["COMMUNITY", "INVITES_DISABLED", "DISCOVERABLE"]; - for (const feature of diff) { - if (MUTABLE_FEATURES.includes(feature)) continue; + for (const feature of diff) { + if (MUTABLE_FEATURES.includes(feature)) continue; - throw SpacebarApiErrors.FEATURE_IS_IMMUTABLE.withParams(feature); - } + throw SpacebarApiErrors.FEATURE_IS_IMMUTABLE.withParams(feature); + } - // for some reason, they don't update in the assign. - guild.features = body.features; - } + // for some reason, they don't update in the assign. + guild.features = body.features; + } - // TODO: check if body ids are valid - guild.assign(body); + // TODO: check if body ids are valid + guild.assign(body); - if (body.public_updates_channel_id == "1") { - // create an updates channel for them - const channel = await Channel.createChannel( - { - name: "moderator-only", - guild_id: guild.id, - position: 0, - type: 0, - permission_overwrites: [ - // remove SEND_MESSAGES from @everyone - { - id: guild.id, - allow: "0", - deny: Permissions.FLAGS.VIEW_CHANNEL.toString(), - type: 0, - }, - ], - }, - undefined, - { skipPermissionCheck: true }, - ); + if (body.public_updates_channel_id == "1") { + // create an updates channel for them + const channel = await Channel.createChannel( + { + name: "moderator-only", + guild_id: guild.id, + position: 0, + type: 0, + permission_overwrites: [ + // remove SEND_MESSAGES from @everyone + { + id: guild.id, + allow: "0", + deny: Permissions.FLAGS.VIEW_CHANNEL.toString(), + type: 0, + }, + ], + }, + undefined, + { skipPermissionCheck: true }, + ); - await Guild.insertChannelInOrder(guild.id, channel.id, 0, guild); + await Guild.insertChannelInOrder(guild.id, channel.id, 0, guild); - guild.public_updates_channel_id = channel.id; - } else if (body.public_updates_channel_id != undefined) { - // ensure channel exists in this guild - await Channel.findOneOrFail({ - where: { guild_id, id: body.public_updates_channel_id }, - select: { id: true }, - }); - } + guild.public_updates_channel_id = channel.id; + } else if (body.public_updates_channel_id != undefined) { + // ensure channel exists in this guild + await Channel.findOneOrFail({ + where: { guild_id, id: body.public_updates_channel_id }, + select: { id: true }, + }); + } - if (body.rules_channel_id == "1") { - // create a rules for them - const channel = await Channel.createChannel( - { - name: "rules", - guild_id: guild.id, - position: 0, - type: 0, - permission_overwrites: [ - // remove SEND_MESSAGES from @everyone - { - id: guild.id, - allow: "0", - deny: Permissions.FLAGS.SEND_MESSAGES.toString(), - type: 0, - }, - ], - }, - undefined, - { skipPermissionCheck: true }, - ); + if (body.rules_channel_id == "1") { + // create a rules for them + const channel = await Channel.createChannel( + { + name: "rules", + guild_id: guild.id, + position: 0, + type: 0, + permission_overwrites: [ + // remove SEND_MESSAGES from @everyone + { + id: guild.id, + allow: "0", + deny: Permissions.FLAGS.SEND_MESSAGES.toString(), + type: 0, + }, + ], + }, + undefined, + { skipPermissionCheck: true }, + ); - await Guild.insertChannelInOrder(guild.id, channel.id, 0, guild); + await Guild.insertChannelInOrder(guild.id, channel.id, 0, guild); - guild.rules_channel_id = channel.id; - } else if (body.rules_channel_id != undefined) { - // ensure channel exists in this guild - await Channel.findOneOrFail({ - where: { guild_id, id: body.rules_channel_id }, - select: { id: true }, - }); - } + guild.rules_channel_id = channel.id; + } else if (body.rules_channel_id != undefined) { + // ensure channel exists in this guild + await Channel.findOneOrFail({ + where: { guild_id, id: body.rules_channel_id }, + select: { id: true }, + }); + } - const data = guild.toJSON(); - // TODO: guild hashes - // TODO: fix vanity_url_code, template_id - // delete data.vanity_url_code; - delete data.template_id; + const data = guild.toJSON(); + // TODO: guild hashes + // TODO: fix vanity_url_code, template_id + // delete data.vanity_url_code; + delete data.template_id; - await Promise.all([ - guild.save(), - emitEvent({ - event: "GUILD_UPDATE", - data, - guild_id, - } as GuildUpdateEvent), - ]); + await Promise.all([ + guild.save(), + emitEvent({ + event: "GUILD_UPDATE", + data, + guild_id, + } as GuildUpdateEvent), + ]); - return res.json(data); - }, + return res.json(data); + }, ); export default router; diff --git a/src/api/routes/guilds/#guild_id/integrations.ts b/src/api/routes/guilds/#guild_id/integrations.ts
index e396e235..704b5a50 100644 --- a/src/api/routes/guilds/#guild_id/integrations.ts +++ b/src/api/routes/guilds/#guild_id/integrations.ts
@@ -22,6 +22,6 @@ const router = Router({ mergeParams: true }); //TODO: implement integrations list router.get("/", route({}), async (req: Request, res: Response) => { - res.json([]); + res.json([]); }); export default router; diff --git a/src/api/routes/guilds/#guild_id/invites.ts b/src/api/routes/guilds/#guild_id/invites.ts
index 9372651a..32d86a4a 100644 --- a/src/api/routes/guilds/#guild_id/invites.ts +++ b/src/api/routes/guilds/#guild_id/invites.ts
@@ -23,33 +23,33 @@ import { Request, Response, Router } from "express"; const router = Router({ mergeParams: true }); router.get( - "/", - route({ - permission: "MANAGE_GUILD", - responses: { - 200: { - body: "APIInviteArray", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; + "/", + route({ + permission: "MANAGE_GUILD", + responses: { + 200: { + body: "APIInviteArray", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; - const invites = await Invite.find({ - where: { guild_id }, - relations: PublicInviteRelation, - }); + const invites = await Invite.find({ + where: { guild_id }, + relations: PublicInviteRelation, + }); - await Promise.all( - invites - .filter((i) => i.isExpired()) - .map(async (i) => { - await Invite.delete({ code: i.code }); - }), - ); + await Promise.all( + invites + .filter((i) => i.isExpired()) + .map(async (i) => { + await Invite.delete({ code: i.code }); + }), + ); - return res.json(invites.filter((i) => !i.isExpired())); - }, + return res.json(invites.filter((i) => !i.isExpired())); + }, ); export default router; diff --git a/src/api/routes/guilds/#guild_id/member-verification.ts b/src/api/routes/guilds/#guild_id/member-verification.ts
index 20225e2b..11b36a3c 100644 --- a/src/api/routes/guilds/#guild_id/member-verification.ts +++ b/src/api/routes/guilds/#guild_id/member-verification.ts
@@ -21,22 +21,22 @@ import { Request, Response, Router } from "express"; const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - // TODO: member verification + "/", + route({ + responses: { + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + // TODO: member verification - res.status(404).json({ - message: "Unknown Guild Member Verification Form", - code: 10068, - }); - }, + res.status(404).json({ + message: "Unknown Guild Member Verification Form", + code: 10068, + }); + }, ); export default router; diff --git a/src/api/routes/guilds/#guild_id/members/#member_id/index.ts b/src/api/routes/guilds/#guild_id/members/#member_id/index.ts
index 9696cb6b..81cda182 100644 --- a/src/api/routes/guilds/#guild_id/members/#member_id/index.ts +++ b/src/api/routes/guilds/#guild_id/members/#member_id/index.ts
@@ -24,212 +24,212 @@ import { MemberChangeSchema, PublicMemberProjection, PublicUserProjection } from const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "APIPublicMember", - }, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id, member_id } = req.params; - await Member.IsInGuildOrFail(req.user_id, guild_id); + "/", + route({ + responses: { + 200: { + body: "APIPublicMember", + }, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id, member_id } = req.params; + await Member.IsInGuildOrFail(req.user_id, guild_id); - const member = await Member.findOneOrFail({ - where: { id: member_id, guild_id }, - relations: ["roles", "user"], - select: { - index: true, - // only grab public member props - ...Object.fromEntries(PublicMemberProjection.map((x) => [x, true])), - // and public user props - user: Object.fromEntries(PublicUserProjection.map((x) => [x, true])), - roles: { - id: true, - }, - }, - }); + const member = await Member.findOneOrFail({ + where: { id: member_id, guild_id }, + relations: ["roles", "user"], + select: { + index: true, + // only grab public member props + ...Object.fromEntries(PublicMemberProjection.map((x) => [x, true])), + // and public user props + user: Object.fromEntries(PublicUserProjection.map((x) => [x, true])), + roles: { + id: true, + }, + }, + }); - return res.json({ - ...member.toPublicMember(), - user: member.user.toPublicUser(), - roles: member.roles.map((x) => x.id), - }); - }, + return res.json({ + ...member.toPublicMember(), + user: member.user.toPublicUser(), + roles: member.roles.map((x) => x.id), + }); + }, ); router.patch( - "/", - route({ - requestBody: "MemberChangeSchema", - responses: { - 200: { - body: "Member", - }, - 400: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; - const member_id = req.params.member_id === "@me" ? req.user_id : req.params.member_id; - const body = req.body as MemberChangeSchema; + "/", + route({ + requestBody: "MemberChangeSchema", + responses: { + 200: { + body: "Member", + }, + 400: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + const member_id = req.params.member_id === "@me" ? req.user_id : req.params.member_id; + const body = req.body as MemberChangeSchema; - const member = await Member.findOneOrFail({ - where: { id: member_id, guild_id }, - relations: ["roles", "user"], - }); - const permission = await getPermission(req.user_id, guild_id); + const member = await Member.findOneOrFail({ + where: { id: member_id, guild_id }, + relations: ["roles", "user"], + }); + const permission = await getPermission(req.user_id, guild_id); - if ("nick" in body) { - permission.hasThrow("MANAGE_NICKNAMES"); + if ("nick" in body) { + permission.hasThrow("MANAGE_NICKNAMES"); - if (!body.nick) { - delete body.nick; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - //@ts-ignore shut up - member.nick = null; // remove the nickname - } - } + if (!body.nick) { + delete body.nick; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore shut up + member.nick = null; // remove the nickname + } + } - if (("bio" in body || "avatar" in body) && req.params.member_id != "@me") { - const rights = await getRights(req.user_id); - rights.hasThrow("MANAGE_USERS"); - } + if (("bio" in body || "avatar" in body) && req.params.member_id != "@me") { + const rights = await getRights(req.user_id); + rights.hasThrow("MANAGE_USERS"); + } - if (body.avatar) body.avatar = await handleFile(`/guilds/${guild_id}/users/${member_id}/avatars`, body.avatar as string); + if (body.avatar) body.avatar = await handleFile(`/guilds/${guild_id}/users/${member_id}/avatars`, body.avatar as string); - member.assign(body); + member.assign(body); - // must do this after the assign because the body roles array - // is string[] not Role[] - if ("roles" in body) { - permission.hasThrow("MANAGE_ROLES"); + // must do this after the assign because the body roles array + // is string[] not Role[] + if ("roles" in body) { + permission.hasThrow("MANAGE_ROLES"); - body.roles = body.roles || []; - body.roles.filter((x) => !!x); + body.roles = body.roles || []; + body.roles.filter((x) => !!x); - if (body.roles.indexOf(guild_id) === -1) body.roles.push(guild_id); - // foreign key constraint will fail if role doesn't exist - member.roles = body.roles.map((x) => Role.create({ id: x })); - } + if (body.roles.indexOf(guild_id) === -1) body.roles.push(guild_id); + // foreign key constraint will fail if role doesn't exist + member.roles = body.roles.map((x) => Role.create({ id: x })); + } - if ("communication_disabled_until" in body) { - permission.hasThrow("MODERATE_MEMBERS"); - member.communication_disabled_until = body.communication_disabled_until == null ? null : new Date(body.communication_disabled_until); - } + if ("communication_disabled_until" in body) { + permission.hasThrow("MODERATE_MEMBERS"); + member.communication_disabled_until = body.communication_disabled_until == null ? null : new Date(body.communication_disabled_until); + } - await member.save(); + await member.save(); - member.roles = member.roles.filter((x) => x.id !== guild_id); + member.roles = member.roles.filter((x) => x.id !== guild_id); - // do not use promise.all as we have to first write to db before emitting the event to catch errors - await emitEvent({ - event: "GUILD_MEMBER_UPDATE", - guild_id, - data: { ...member, roles: member.roles.map((x) => x.id) }, - } as GuildMemberUpdateEvent); + // do not use promise.all as we have to first write to db before emitting the event to catch errors + await emitEvent({ + event: "GUILD_MEMBER_UPDATE", + guild_id, + data: { ...member, roles: member.roles.map((x) => x.id) }, + } as GuildMemberUpdateEvent); - res.json(member); - }, + res.json(member); + }, ); router.put( - "/", - route({ - responses: { - 200: { - body: "MemberJoinGuildResponse", - }, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - // TODO: Lurker mode + "/", + route({ + responses: { + 200: { + body: "MemberJoinGuildResponse", + }, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + // TODO: Lurker mode - const rights = await getRights(req.user_id); + const rights = await getRights(req.user_id); - const { guild_id } = req.params; - let { member_id } = req.params; - if (member_id === "@me") { - member_id = req.user_id; - rights.hasThrow("JOIN_GUILDS"); - if (req.user_bot) throw DiscordApiErrors.BOT_PROHIBITED_ENDPOINT; - } else { - // TODO: check oauth2 scope + const { guild_id } = req.params; + let { member_id } = req.params; + if (member_id === "@me") { + member_id = req.user_id; + rights.hasThrow("JOIN_GUILDS"); + if (req.user_bot) throw DiscordApiErrors.BOT_PROHIBITED_ENDPOINT; + } else { + // TODO: check oauth2 scope - throw DiscordApiErrors.MISSING_REQUIRED_OAUTH2_SCOPE; - } + throw DiscordApiErrors.MISSING_REQUIRED_OAUTH2_SCOPE; + } - const guild = await Guild.findOneOrFail({ - where: { id: guild_id }, - }); + const guild = await Guild.findOneOrFail({ + where: { id: guild_id }, + }); - if (!guild.features.includes("DISCOVERABLE")) { - throw DiscordApiErrors.UNKNOWN_GUILD; - } + if (!guild.features.includes("DISCOVERABLE")) { + throw DiscordApiErrors.UNKNOWN_GUILD; + } - const emoji = await Emoji.find({ - where: { guild_id: guild_id }, - }); + const emoji = await Emoji.find({ + where: { guild_id: guild_id }, + }); - const roles = await Role.find({ - where: { guild_id: guild_id }, - }); + const roles = await Role.find({ + where: { guild_id: guild_id }, + }); - const stickers = await Sticker.find({ - where: { guild_id: guild_id }, - }); + const stickers = await Sticker.find({ + where: { guild_id: guild_id }, + }); - await Member.addToGuild(member_id, guild_id); - res.send({ ...guild, emojis: emoji, roles: roles, stickers: stickers }); - }, + await Member.addToGuild(member_id, guild_id); + res.send({ ...guild, emojis: emoji, roles: roles, stickers: stickers }); + }, ); router.delete( - "/", - route({ - responses: { - 204: {}, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id, member_id } = req.params; - const permission = await getPermission(req.user_id, guild_id); - const rights = await getRights(req.user_id); - if (member_id === "@me" || member_id === req.user_id) { - // TODO: unless force-joined - rights.hasThrow("SELF_LEAVE_GROUPS"); - } else { - rights.hasThrow("KICK_BAN_MEMBERS"); - permission.hasThrow("KICK_MEMBERS"); - } + "/", + route({ + responses: { + 204: {}, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id, member_id } = req.params; + const permission = await getPermission(req.user_id, guild_id); + const rights = await getRights(req.user_id); + if (member_id === "@me" || member_id === req.user_id) { + // TODO: unless force-joined + rights.hasThrow("SELF_LEAVE_GROUPS"); + } else { + rights.hasThrow("KICK_BAN_MEMBERS"); + permission.hasThrow("KICK_MEMBERS"); + } - await Member.removeFromGuild(member_id, guild_id); - res.sendStatus(204); - }, + await Member.removeFromGuild(member_id, guild_id); + res.sendStatus(204); + }, ); export default router; diff --git a/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts b/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts
index 2a707771..33f89152 100644 --- a/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts +++ b/src/api/routes/guilds/#guild_id/members/#member_id/nick.ts
@@ -23,38 +23,38 @@ import { Request, Response, Router } from "express"; const router = Router({ mergeParams: true }); router.patch( - "/", - route({ - requestBody: "MemberNickChangeSchema", - responses: { - 200: { - body: "APIPublicMember", - }, - 400: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; - let permissionString: PermissionResolvable = "MANAGE_NICKNAMES"; - const member_id = req.params.member_id === "@me" ? ((permissionString = "CHANGE_NICKNAME"), req.user_id) : req.params.member_id; + "/", + route({ + requestBody: "MemberNickChangeSchema", + responses: { + 200: { + body: "APIPublicMember", + }, + 400: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + let permissionString: PermissionResolvable = "MANAGE_NICKNAMES"; + const member_id = req.params.member_id === "@me" ? ((permissionString = "CHANGE_NICKNAME"), req.user_id) : req.params.member_id; - const perms = await getPermission(req.user_id, guild_id); - perms.hasThrow(permissionString); + const perms = await getPermission(req.user_id, guild_id); + perms.hasThrow(permissionString); - await Member.changeNickname(member_id, guild_id, req.body.nick); + await Member.changeNickname(member_id, guild_id, req.body.nick); - const member = await Member.findOne({ - where: { id: member_id, guild_id }, - relations: ["roles"], - }); + const member = await Member.findOne({ + where: { id: member_id, guild_id }, + relations: ["roles"], + }); - res.send(member?.toPublicMember()); - }, + res.send(member?.toPublicMember()); + }, ); export default router; diff --git a/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts b/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts
index 5a5bfdfd..d482c06e 100644 --- a/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts +++ b/src/api/routes/guilds/#guild_id/members/#member_id/roles/#role_id/index.ts
@@ -23,39 +23,39 @@ import { Request, Response, Router } from "express"; const router = Router({ mergeParams: true }); router.delete( - "/", - route({ - permission: "MANAGE_ROLES", - responses: { - 204: {}, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id, role_id, member_id } = req.params; + "/", + route({ + permission: "MANAGE_ROLES", + responses: { + 204: {}, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id, role_id, member_id } = req.params; - await Member.removeRole(member_id, guild_id, role_id); - res.sendStatus(204); - }, + await Member.removeRole(member_id, guild_id, role_id); + res.sendStatus(204); + }, ); router.put( - "/", - route({ - permission: "MANAGE_ROLES", - responses: { - 204: {}, - 403: {}, - }, - }), - async (req: Request, res: Response) => { - const { guild_id, role_id, member_id } = req.params; + "/", + route({ + permission: "MANAGE_ROLES", + responses: { + 204: {}, + 403: {}, + }, + }), + async (req: Request, res: Response) => { + const { guild_id, role_id, member_id } = req.params; - await Member.addRole(member_id, guild_id, role_id); - res.sendStatus(204); - }, + await Member.addRole(member_id, guild_id, role_id); + res.sendStatus(204); + }, ); export default router; diff --git a/src/api/routes/guilds/#guild_id/members/index.ts b/src/api/routes/guilds/#guild_id/members/index.ts
index a047472c..08b77076 100644 --- a/src/api/routes/guilds/#guild_id/members/index.ts +++ b/src/api/routes/guilds/#guild_id/members/index.ts
@@ -29,44 +29,44 @@ const router = Router({ mergeParams: true }); // TODO: check for GUILD_MEMBERS intent router.get( - "/", - route({ - query: { - limit: { - type: "number", - description: "max number of members to return (1-1000). default 1", - }, - after: { - type: "string", - }, - }, - responses: { - 200: { - body: "APIMemberArray", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; - const limit = Number(req.query.limit) || 1; - if (limit > 1000 || limit < 1) throw new HTTPError("Limit must be between 1 and 1000"); - const after = `${req.query.after}`; - const query = after ? { id: MoreThan(after) } : {}; + "/", + route({ + query: { + limit: { + type: "number", + description: "max number of members to return (1-1000). default 1", + }, + after: { + type: "string", + }, + }, + responses: { + 200: { + body: "APIMemberArray", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + const limit = Number(req.query.limit) || 1; + if (limit > 1000 || limit < 1) throw new HTTPError("Limit must be between 1 and 1000"); + const after = `${req.query.after}`; + const query = after ? { id: MoreThan(after) } : {}; - await Member.IsInGuildOrFail(req.user_id, guild_id); + await Member.IsInGuildOrFail(req.user_id, guild_id); - const members = await Member.find({ - where: { guild_id, ...query }, - select: PublicMemberProjection, - take: limit, - order: { id: "ASC" }, - }); + const members = await Member.find({ + where: { guild_id, ...query }, + select: PublicMemberProjection, + take: limit, + order: { id: "ASC" }, + }); - return res.json(members); - }, + return res.json(members); + }, ); export default router; diff --git a/src/api/routes/guilds/#guild_id/messages/search.ts b/src/api/routes/guilds/#guild_id/messages/search.ts
index 6532b53b..be00d5de 100644 --- a/src/api/routes/guilds/#guild_id/messages/search.ts +++ b/src/api/routes/guilds/#guild_id/messages/search.ts
@@ -27,125 +27,125 @@ import { FindManyOptions, In, Like } from "typeorm"; const router: Router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "GuildMessagesSearchResponse", - }, - 403: { - body: "APIErrorResponse", - }, - 422: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { - channel_id, - content, - // include_nsfw, // TODO - offset, - sort_order, - // sort_by, // TODO: Handle 'relevance' - limit, - author_id, - } = req.query; + "/", + route({ + responses: { + 200: { + body: "GuildMessagesSearchResponse", + }, + 403: { + body: "APIErrorResponse", + }, + 422: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { + channel_id, + content, + // include_nsfw, // TODO + offset, + sort_order, + // sort_by, // TODO: Handle 'relevance' + limit, + author_id, + } = req.query; - const parsedLimit = Number(limit) || 50; - if (parsedLimit < 1 || parsedLimit > 100) throw new HTTPError("limit must be between 1 and 100", 422); + const parsedLimit = Number(limit) || 50; + if (parsedLimit < 1 || parsedLimit > 100) throw new HTTPError("limit must be between 1 and 100", 422); - if (sort_order) { - if (typeof sort_order != "string" || ["desc", "asc"].indexOf(sort_order) == -1) - throw FieldErrors({ - sort_order: { - message: "Value must be one of ('desc', 'asc').", - code: "BASE_TYPE_CHOICES", - }, - }); // todo this is wrong - } + if (sort_order) { + if (typeof sort_order != "string" || ["desc", "asc"].indexOf(sort_order) == -1) + throw FieldErrors({ + sort_order: { + message: "Value must be one of ('desc', 'asc').", + code: "BASE_TYPE_CHOICES", + }, + }); // todo this is wrong + } - const permissions = await getPermission(req.user_id, req.params.guild_id, channel_id as string | undefined); - permissions.hasThrow("VIEW_CHANNEL"); - if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json({ messages: [], total_results: 0 }); + const permissions = await getPermission(req.user_id, req.params.guild_id, channel_id as string | undefined); + permissions.hasThrow("VIEW_CHANNEL"); + if (!permissions.has("READ_MESSAGE_HISTORY")) return res.json({ messages: [], total_results: 0 }); - const query: FindManyOptions<Message> = { - order: { - timestamp: sort_order ? (sort_order.toUpperCase() as "ASC" | "DESC") : "DESC", - }, - take: parsedLimit || 0, - where: { - guild: { - id: req.params.guild_id, - }, - }, - relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"], - skip: offset ? Number(offset) : 0, - }; - //@ts-ignore - if (channel_id) query.where.channel = { id: channel_id }; - else { - // get all channel IDs that this user can access - const channels = await Channel.find({ - where: { guild_id: req.params.guild_id }, - select: ["id"], - }); - const ids = []; + const query: FindManyOptions<Message> = { + order: { + timestamp: sort_order ? (sort_order.toUpperCase() as "ASC" | "DESC") : "DESC", + }, + take: parsedLimit || 0, + where: { + guild: { + id: req.params.guild_id, + }, + }, + relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"], + skip: offset ? Number(offset) : 0, + }; + //@ts-ignore + if (channel_id) query.where.channel = { id: channel_id }; + else { + // get all channel IDs that this user can access + const channels = await Channel.find({ + where: { guild_id: req.params.guild_id }, + select: ["id"], + }); + const ids = []; - for (const channel of channels) { - const perm = await getPermission(req.user_id, req.params.guild_id, channel.id); - if (!perm.has("VIEW_CHANNEL") || !perm.has("READ_MESSAGE_HISTORY")) continue; - ids.push(channel.id); - } + for (const channel of channels) { + const perm = await getPermission(req.user_id, req.params.guild_id, channel.id); + if (!perm.has("VIEW_CHANNEL") || !perm.has("READ_MESSAGE_HISTORY")) continue; + ids.push(channel.id); + } - //@ts-ignore - query.where.channel = { id: In(ids) }; - } - //@ts-ignore - if (author_id) query.where.author = { id: author_id }; - //@ts-ignore - if (content) query.where.content = Like(`%${content}%`); + //@ts-ignore + query.where.channel = { id: In(ids) }; + } + //@ts-ignore + if (author_id) query.where.author = { id: author_id }; + //@ts-ignore + if (content) query.where.content = Like(`%${content}%`); - const messages: Message[] = await Message.find(query); - delete query.take; - const total_results = await Message.count(query); + const messages: Message[] = await Message.find(query); + delete query.take; + const total_results = await Message.count(query); - const messagesDto = messages.map((x) => [ - { - id: x.id, - type: x.type, - content: x.content, - channel_id: x.channel_id, - author: { - id: x.author?.id, - username: x.author?.username, - avatar: x.author?.avatar, - avatar_decoration: null, - discriminator: x.author?.discriminator, - public_flags: x.author?.public_flags, - }, - attachments: x.attachments, - embeds: x.embeds, - mentions: x.mentions, - mention_roles: x.mention_roles, - pinned: x.pinned, - mention_everyone: x.mention_everyone, - tts: x.tts, - timestamp: x.timestamp, - edited_timestamp: x.edited_timestamp, - flags: x.flags, - components: x.components, - poll: x.poll, - hit: true, - }, - ]); + const messagesDto = messages.map((x) => [ + { + id: x.id, + type: x.type, + content: x.content, + channel_id: x.channel_id, + author: { + id: x.author?.id, + username: x.author?.username, + avatar: x.author?.avatar, + avatar_decoration: null, + discriminator: x.author?.discriminator, + public_flags: x.author?.public_flags, + }, + attachments: x.attachments, + embeds: x.embeds, + mentions: x.mentions, + mention_roles: x.mention_roles, + pinned: x.pinned, + mention_everyone: x.mention_everyone, + tts: x.tts, + timestamp: x.timestamp, + edited_timestamp: x.edited_timestamp, + flags: x.flags, + components: x.components, + poll: x.poll, + hit: true, + }, + ]); - return res.json({ - messages: messagesDto, - total_results, - }); - }, + return res.json({ + messages: messagesDto, + total_results, + }); + }, ); export default router; diff --git a/src/api/routes/guilds/#guild_id/premium.ts b/src/api/routes/guilds/#guild_id/premium.ts
index b72ded26..fc0d34bf 100644 --- a/src/api/routes/guilds/#guild_id/premium.ts +++ b/src/api/routes/guilds/#guild_id/premium.ts
@@ -21,8 +21,8 @@ import { route } from "@spacebar/api"; const router = Router({ mergeParams: true }); router.get("/subscriptions", route({}), async (req: Request, res: Response) => { - // TODO: - res.json([]); + // TODO: + res.json([]); }); export default router; diff --git a/src/api/routes/guilds/#guild_id/profile.ts b/src/api/routes/guilds/#guild_id/profile.ts
index 4b263fb0..176c612c 100644 --- a/src/api/routes/guilds/#guild_id/profile.ts +++ b/src/api/routes/guilds/#guild_id/profile.ts
@@ -24,43 +24,43 @@ import { GuildProfileResponse, GuildVisibilityLevel } from "@spacebar/schemas"; const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - "200": { - body: "GuildProfileResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; - const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); - const profileResponse: GuildProfileResponse = { - id: guild_id, - name: guild.name, - icon_hash: guild.icon ?? null, - member_count: guild.member_count!, - online_count: guild.member_count!, - description: guild.description ?? "A Spacebar guild", - brand_color_primary: "#FF00FF", - banner_hash: null, - game_application_ids: [], // We don't track this - game_activity: {}, // We don't track this - tag: guild.name.substring(0, 4).toUpperCase(), // TODO: allow custom tags - badge: 0, - badge_color_primary: "#FF00FF", - badge_color_secondary: "#00FFFF", - badge_hash: "", - traits: [], - features: guild.features ?? [], - visibility: GuildVisibilityLevel.PUBLIC, - custom_banner_hash: guild.banner ?? null, - premium_subscription_count: guild.premium_subscription_count ?? 0, - premium_tier: guild.premium_tier ?? 0, - }; + "/", + route({ + responses: { + "200": { + body: "GuildProfileResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); + const profileResponse: GuildProfileResponse = { + id: guild_id, + name: guild.name, + icon_hash: guild.icon ?? null, + member_count: guild.member_count!, + online_count: guild.member_count!, + description: guild.description ?? "A Spacebar guild", + brand_color_primary: "#FF00FF", + banner_hash: null, + game_application_ids: [], // We don't track this + game_activity: {}, // We don't track this + tag: guild.name.substring(0, 4).toUpperCase(), // TODO: allow custom tags + badge: 0, + badge_color_primary: "#FF00FF", + badge_color_secondary: "#00FFFF", + badge_hash: "", + traits: [], + features: guild.features ?? [], + visibility: GuildVisibilityLevel.PUBLIC, + custom_banner_hash: guild.banner ?? null, + premium_subscription_count: guild.premium_subscription_count ?? 0, + premium_tier: guild.premium_tier ?? 0, + }; - res.send(profileResponse); - }, + res.send(profileResponse); + }, ); export default router; diff --git a/src/api/routes/guilds/#guild_id/profile/index.ts b/src/api/routes/guilds/#guild_id/profile/index.ts
index 8f0e3857..7a944b1f 100644 --- a/src/api/routes/guilds/#guild_id/profile/index.ts +++ b/src/api/routes/guilds/#guild_id/profile/index.ts
@@ -24,47 +24,47 @@ import { MemberChangeProfileSchema } from "@spacebar/schemas"; const router = Router({ mergeParams: true }); router.patch( - "/:member_id", - route({ - requestBody: "MemberChangeProfileSchema", - responses: { - 200: { - body: "Member", - }, - 400: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; - // const member_id = - // req.params.member_id === "@me" ? req.user_id : req.params.member_id; - const body = req.body as MemberChangeProfileSchema; + "/:member_id", + route({ + requestBody: "MemberChangeProfileSchema", + responses: { + 200: { + body: "Member", + }, + 400: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + // const member_id = + // req.params.member_id === "@me" ? req.user_id : req.params.member_id; + const body = req.body as MemberChangeProfileSchema; - let member = await Member.findOneOrFail({ - where: { id: req.user_id, guild_id }, - relations: ["roles", "user"], - }); + let member = await Member.findOneOrFail({ + where: { id: req.user_id, guild_id }, + relations: ["roles", "user"], + }); - if (body.banner) body.banner = await handleFile(`/guilds/${guild_id}/users/${req.user_id}/avatars`, body.banner as string); + if (body.banner) body.banner = await handleFile(`/guilds/${guild_id}/users/${req.user_id}/avatars`, body.banner as string); - member = await OrmUtils.mergeDeep(member, body); + member = await OrmUtils.mergeDeep(member, body); - await member.save(); + await member.save(); - // do not use promise.all as we have to first write to db before emitting the event to catch errors - await emitEvent({ - event: "GUILD_MEMBER_UPDATE", - guild_id, - data: { ...member, roles: member.roles.map((x) => x.id) }, - } as GuildMemberUpdateEvent); + // do not use promise.all as we have to first write to db before emitting the event to catch errors + await emitEvent({ + event: "GUILD_MEMBER_UPDATE", + guild_id, + data: { ...member, roles: member.roles.map((x) => x.id) }, + } as GuildMemberUpdateEvent); - res.json(member); - }, + res.json(member); + }, ); export default router; diff --git a/src/api/routes/guilds/#guild_id/prune.ts b/src/api/routes/guilds/#guild_id/prune.ts
index 47984e02..c8a42b84 100644 --- a/src/api/routes/guilds/#guild_id/prune.ts +++ b/src/api/routes/guilds/#guild_id/prune.ts
@@ -24,102 +24,102 @@ const router = Router({ mergeParams: true }); //Returns all inactive members, respecting role hierarchy const inactiveMembers = async (guild_id: string, user_id: string, days: number, roles: string[] = []) => { - const date = new Date(); - date.setDate(date.getDate() - days); - //Snowflake should have `generateFromTime` method? Or similar? - const minId = BigInt(date.valueOf() - Snowflake.EPOCH) << BigInt(22); + const date = new Date(); + date.setDate(date.getDate() - days); + //Snowflake should have `generateFromTime` method? Or similar? + const minId = BigInt(date.valueOf() - Snowflake.EPOCH) << BigInt(22); - /** + /** idea: ability to customise the cutoff variable possible candidates: public read receipt, last presence, last VC leave **/ - let members = await Member.find({ - where: [ - { - guild_id, - last_message_id: LessThan(minId.toString()), - }, - { - guild_id, - last_message_id: IsNull(), - }, - ], - relations: ["roles"], - }); - if (!members.length) return []; + let members = await Member.find({ + where: [ + { + guild_id, + last_message_id: LessThan(minId.toString()), + }, + { + guild_id, + last_message_id: IsNull(), + }, + ], + relations: ["roles"], + }); + if (!members.length) return []; - //I'm sure I can do this in the above db query ( and it would probably be better to do so ), but oh well. - if (roles.length && members.length) members = members.filter((user) => user.roles?.some((role) => roles.includes(role.id))); + //I'm sure I can do this in the above db query ( and it would probably be better to do so ), but oh well. + if (roles.length && members.length) members = members.filter((user) => user.roles?.some((role) => roles.includes(role.id))); - const me = await Member.findOneOrFail({ - where: { id: user_id, guild_id }, - relations: ["roles"], - }); - const myHighestRole = Math.max(...(me.roles?.map((x) => x.position) || [])); + const me = await Member.findOneOrFail({ + where: { id: user_id, guild_id }, + relations: ["roles"], + }); + const myHighestRole = Math.max(...(me.roles?.map((x) => x.position) || [])); - const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); + const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); - members = members.filter( - (member) => - member.id !== guild.owner_id && //can't kick owner - member.roles?.some( - (role) => - role.position < myHighestRole || //roles higher than me can't be kicked - me.id === guild.owner_id, //owner can kick anyone - ), - ); + members = members.filter( + (member) => + member.id !== guild.owner_id && //can't kick owner + member.roles?.some( + (role) => + role.position < myHighestRole || //roles higher than me can't be kicked + me.id === guild.owner_id, //owner can kick anyone + ), + ); - return members; + return members; }; router.get( - "/", - route({ - responses: { - "200": { - body: "GuildPruneResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const days = parseInt(req.query.days as string); + "/", + route({ + responses: { + "200": { + body: "GuildPruneResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const days = parseInt(req.query.days as string); - let roles = req.query.include_roles; - if (typeof roles === "string") roles = [roles]; //express will return array otherwise + let roles = req.query.include_roles; + if (typeof roles === "string") roles = [roles]; //express will return array otherwise - const members = await inactiveMembers(req.params.guild_id, req.user_id, days, roles as string[]); + const members = await inactiveMembers(req.params.guild_id, req.user_id, days, roles as string[]); - res.send({ pruned: members.length }); - }, + res.send({ pruned: members.length }); + }, ); router.post( - "/", - route({ - permission: "KICK_MEMBERS", - right: "KICK_BAN_MEMBERS", - responses: { - 200: { - body: "GuildPurgeResponse", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const days = parseInt(req.body.days); + "/", + route({ + permission: "KICK_MEMBERS", + right: "KICK_BAN_MEMBERS", + responses: { + 200: { + body: "GuildPurgeResponse", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const days = parseInt(req.body.days); - let roles = req.query.include_roles; - if (typeof roles === "string") roles = [roles]; + let roles = req.query.include_roles; + if (typeof roles === "string") roles = [roles]; - const { guild_id } = req.params; - const members = await inactiveMembers(guild_id, req.user_id, days, roles as string[]); + const { guild_id } = req.params; + const members = await inactiveMembers(guild_id, req.user_id, days, roles as string[]); - await Promise.all(members.map((x) => Member.removeFromGuild(x.id, guild_id))); + await Promise.all(members.map((x) => Member.removeFromGuild(x.id, guild_id))); - res.send({ purged: members.length }); - }, + res.send({ purged: members.length }); + }, ); export default router; diff --git a/src/api/routes/guilds/#guild_id/regions.ts b/src/api/routes/guilds/#guild_id/regions.ts
index a3c5a81b..b2a311d7 100644 --- a/src/api/routes/guilds/#guild_id/regions.ts +++ b/src/api/routes/guilds/#guild_id/regions.ts
@@ -23,23 +23,23 @@ import { Request, Response, Router } from "express"; const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "APIGuildVoiceRegion", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; - const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); - //TODO we should use an enum for guild's features and not hardcoded strings - return res.json(await getVoiceRegions(req.ip!, guild.features.includes("VIP_REGIONS"))); - }, + "/", + route({ + responses: { + 200: { + body: "APIGuildVoiceRegion", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); + //TODO we should use an enum for guild's features and not hardcoded strings + return res.json(await getVoiceRegions(req.ip!, guild.features.includes("VIP_REGIONS"))); + }, ); export default router; diff --git a/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts b/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts
index b8349eb6..1811284a 100644 --- a/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts +++ b/src/api/routes/guilds/#guild_id/roles/#role_id/index.ts
@@ -25,121 +25,121 @@ import { RoleModifySchema } from "@spacebar/schemas"; const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "Role", - }, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id, role_id } = req.params; - await Member.IsInGuildOrFail(req.user_id, guild_id); - const role = await Role.findOneOrFail({ - where: { guild_id, id: role_id }, - }); - return res.json(role); - }, + "/", + route({ + responses: { + 200: { + body: "Role", + }, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id, role_id } = req.params; + await Member.IsInGuildOrFail(req.user_id, guild_id); + const role = await Role.findOneOrFail({ + where: { guild_id, id: role_id }, + }); + return res.json(role); + }, ); router.delete( - "/", - route({ - permission: "MANAGE_ROLES", - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id, role_id } = req.params; - if (role_id === guild_id) throw new HTTPError("You can't delete the @everyone role"); + "/", + route({ + permission: "MANAGE_ROLES", + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id, role_id } = req.params; + if (role_id === guild_id) throw new HTTPError("You can't delete the @everyone role"); - await Promise.all([ - Role.delete({ - id: role_id, - guild_id: guild_id, - }), - emitEvent({ - event: "GUILD_ROLE_DELETE", - guild_id, - data: { - guild_id, - role_id, - }, - } as GuildRoleDeleteEvent), - ]); + await Promise.all([ + Role.delete({ + id: role_id, + guild_id: guild_id, + }), + emitEvent({ + event: "GUILD_ROLE_DELETE", + guild_id, + data: { + guild_id, + role_id, + }, + } as GuildRoleDeleteEvent), + ]); - res.sendStatus(204); - }, + res.sendStatus(204); + }, ); // TODO: check role hierarchy router.patch( - "/", - route({ - requestBody: "RoleModifySchema", - permission: "MANAGE_ROLES", - responses: { - 200: { - body: "Role", - }, - 400: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { role_id, guild_id } = req.params; - const body = req.body as RoleModifySchema; + "/", + route({ + requestBody: "RoleModifySchema", + permission: "MANAGE_ROLES", + responses: { + 200: { + body: "Role", + }, + 400: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { role_id, guild_id } = req.params; + const body = req.body as RoleModifySchema; - if (body.icon && body.icon.length) body.icon = await handleFile(`/role-icons/${role_id}`, body.icon as string); - else body.icon = undefined; + if (body.icon && body.icon.length) body.icon = await handleFile(`/role-icons/${role_id}`, body.icon as string); + else body.icon = undefined; - const role = await Role.findOneOrFail({ - where: { id: role_id, guild: { id: guild_id } }, - }); - role.assign({ - ...body, - permissions: String((req.permission?.bitfield || 0n) & BigInt(body.permissions || "0")), - }); + const role = await Role.findOneOrFail({ + where: { id: role_id, guild: { id: guild_id } }, + }); + role.assign({ + ...body, + permissions: String((req.permission?.bitfield || 0n) & BigInt(body.permissions || "0")), + }); - await Promise.all([ - role.save(), - emitEvent({ - event: "GUILD_ROLE_UPDATE", - guild_id, - data: { - guild_id, - role, - }, - } as GuildRoleUpdateEvent), - ]); + await Promise.all([ + role.save(), + emitEvent({ + event: "GUILD_ROLE_UPDATE", + guild_id, + data: { + guild_id, + role, + }, + } as GuildRoleUpdateEvent), + ]); - res.json(role); - }, + res.json(role); + }, ); export default router; diff --git a/src/api/routes/guilds/#guild_id/roles/#role_id/member-ids.ts b/src/api/routes/guilds/#guild_id/roles/#role_id/member-ids.ts
index 6558da58..ab61325e 100644 --- a/src/api/routes/guilds/#guild_id/roles/#role_id/member-ids.ts +++ b/src/api/routes/guilds/#guild_id/roles/#role_id/member-ids.ts
@@ -23,20 +23,20 @@ import { route } from "@spacebar/api"; const router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - const { guild_id, role_id } = req.params; + const { guild_id, role_id } = req.params; - // TODO: Is this route really not paginated? - const members = await Member.find({ - select: ["id"], - where: { - roles: { - id: role_id, - }, - guild_id, - }, - }); + // TODO: Is this route really not paginated? + const members = await Member.find({ + select: ["id"], + where: { + roles: { + id: role_id, + }, + guild_id, + }, + }); - return res.json(members.map((x) => x.id)); + return res.json(members.map((x) => x.id)); }); export default router; diff --git a/src/api/routes/guilds/#guild_id/roles/#role_id/members.ts b/src/api/routes/guilds/#guild_id/roles/#role_id/members.ts
index d22d1434..f89d5935 100644 --- a/src/api/routes/guilds/#guild_id/roles/#role_id/members.ts +++ b/src/api/routes/guilds/#guild_id/roles/#role_id/members.ts
@@ -23,24 +23,24 @@ import { route } from "@spacebar/api"; const router = Router({ mergeParams: true }); router.patch("/", route({ permission: "MANAGE_ROLES" }), async (req: Request, res: Response) => { - // Payload is JSON containing a list of member_ids, the new list of members to have the role - const { guild_id, role_id } = req.params; - const { member_ids } = req.body; + // Payload is JSON containing a list of member_ids, the new list of members to have the role + const { guild_id, role_id } = req.params; + const { member_ids } = req.body; - // don't mess with @everyone - if (role_id == guild_id) throw DiscordApiErrors.INVALID_ROLE; + // don't mess with @everyone + if (role_id == guild_id) throw DiscordApiErrors.INVALID_ROLE; - const members = await Member.find({ - where: { guild_id }, - relations: ["roles"], - }); + const members = await Member.find({ + where: { guild_id }, + relations: ["roles"], + }); - const [add, remove] = arrayPartition(members, (member) => member_ids.includes(member.id) && !member.roles.map((role) => role.id).includes(role_id)); + const [add, remove] = arrayPartition(members, (member) => member_ids.includes(member.id) && !member.roles.map((role) => role.id).includes(role_id)); - // TODO (erkin): have a bulk add/remove function that adds the roles in a single txn - await Promise.all([...add.map((member) => Member.addRole(member.id, guild_id, role_id)), ...remove.map((member) => Member.removeRole(member.id, guild_id, role_id))]); + // TODO (erkin): have a bulk add/remove function that adds the roles in a single txn + await Promise.all([...add.map((member) => Member.addRole(member.id, guild_id, role_id)), ...remove.map((member) => Member.removeRole(member.id, guild_id, role_id))]); - res.sendStatus(204); + res.sendStatus(204); }); export default router; diff --git a/src/api/routes/guilds/#guild_id/roles/index.ts b/src/api/routes/guilds/#guild_id/roles/index.ts
index 26584e90..8caea56f 100644 --- a/src/api/routes/guilds/#guild_id/roles/index.ts +++ b/src/api/routes/guilds/#guild_id/roles/index.ts
@@ -25,129 +25,129 @@ import { RoleModifySchema, RolePositionUpdateSchema } from "@spacebar/schemas"; const router: Router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - const guild_id = req.params.guild_id; + const guild_id = req.params.guild_id; - await Member.IsInGuildOrFail(req.user_id, guild_id); + await Member.IsInGuildOrFail(req.user_id, guild_id); - const roles = await Role.find({ where: { guild_id: guild_id } }); + const roles = await Role.find({ where: { guild_id: guild_id } }); - return res.json(roles); + return res.json(roles); }); router.post( - "/", - route({ - requestBody: "RoleModifySchema", - permission: "MANAGE_ROLES", - responses: { - 200: { - body: "Role", - }, - 400: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const guild_id = req.params.guild_id; - const body = req.body as RoleModifySchema; + "/", + route({ + requestBody: "RoleModifySchema", + permission: "MANAGE_ROLES", + responses: { + 200: { + body: "Role", + }, + 400: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const guild_id = req.params.guild_id; + const body = req.body as RoleModifySchema; - const role_count = await Role.count({ where: { guild_id } }); - const { maxRoles } = Config.get().limits.guild; + const role_count = await Role.count({ where: { guild_id } }); + const { maxRoles } = Config.get().limits.guild; - if (role_count > maxRoles) throw DiscordApiErrors.MAXIMUM_ROLES.withParams(maxRoles); + if (role_count > maxRoles) throw DiscordApiErrors.MAXIMUM_ROLES.withParams(maxRoles); - const role = Role.create({ - // values before ...body are default and can be overridden - position: 1, - hoist: false, - color: 0, - mentionable: false, - ...body, - guild_id: guild_id, - managed: false, - permissions: String((req.permission?.bitfield || 0n) & BigInt(body.permissions || "0")), - tags: undefined, - icon: undefined, - unicode_emoji: undefined, - id: Snowflake.generate(), - colors: { - primary_color: body.colors?.primary_color || body.color || 0, - secondary_color: body.colors?.secondary_color || undefined, // gradient - tertiary_color: body.colors?.tertiary_color || undefined, // "holographic" - }, - }); + const role = Role.create({ + // values before ...body are default and can be overridden + position: 1, + hoist: false, + color: 0, + mentionable: false, + ...body, + guild_id: guild_id, + managed: false, + permissions: String((req.permission?.bitfield || 0n) & BigInt(body.permissions || "0")), + tags: undefined, + icon: undefined, + unicode_emoji: undefined, + id: Snowflake.generate(), + colors: { + primary_color: body.colors?.primary_color || body.color || 0, + secondary_color: body.colors?.secondary_color || undefined, // gradient + tertiary_color: body.colors?.tertiary_color || undefined, // "holographic" + }, + }); - await Promise.all([ - role.save(), - // Move all existing roles up one position, to accommodate the new role - Role.createQueryBuilder("roles") - .where({ - guild: { id: guild_id }, - name: Not("@everyone"), - id: Not(role.id), - }) - .update({ position: () => "position + 1" }) - .execute(), - emitEvent({ - event: "GUILD_ROLE_CREATE", - guild_id, - data: { - guild_id, - role: role, - }, - } as GuildRoleCreateEvent), - ]); + await Promise.all([ + role.save(), + // Move all existing roles up one position, to accommodate the new role + Role.createQueryBuilder("roles") + .where({ + guild: { id: guild_id }, + name: Not("@everyone"), + id: Not(role.id), + }) + .update({ position: () => "position + 1" }) + .execute(), + emitEvent({ + event: "GUILD_ROLE_CREATE", + guild_id, + data: { + guild_id, + role: role, + }, + } as GuildRoleCreateEvent), + ]); - res.json(role); - }, + res.json(role); + }, ); router.patch( - "/", - route({ - requestBody: "RolePositionUpdateSchema", - permission: "MANAGE_ROLES", - responses: { - 200: { - body: "APIRoleArray", - }, - 400: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; - const body = req.body as RolePositionUpdateSchema; + "/", + route({ + requestBody: "RolePositionUpdateSchema", + permission: "MANAGE_ROLES", + responses: { + 200: { + body: "APIRoleArray", + }, + 400: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + const body = req.body as RolePositionUpdateSchema; - await Promise.all(body.map(async (x) => Role.update({ guild_id, id: x.id }, { position: x.position }))); + await Promise.all(body.map(async (x) => Role.update({ guild_id, id: x.id }, { position: x.position }))); - const roles = await Role.find({ - where: body.map((x) => ({ id: x.id, guild_id })), - }); + const roles = await Role.find({ + where: body.map((x) => ({ id: x.id, guild_id })), + }); - await Promise.all( - roles.map((x) => - emitEvent({ - event: "GUILD_ROLE_UPDATE", - guild_id, - data: { - guild_id, - role: x, - }, - } as GuildRoleUpdateEvent), - ), - ); + await Promise.all( + roles.map((x) => + emitEvent({ + event: "GUILD_ROLE_UPDATE", + guild_id, + data: { + guild_id, + role: x, + }, + } as GuildRoleUpdateEvent), + ), + ); - res.json(roles); - }, + res.json(roles); + }, ); export default router; diff --git a/src/api/routes/guilds/#guild_id/roles/member-counts.ts b/src/api/routes/guilds/#guild_id/roles/member-counts.ts
index 5574e59e..d77149d5 100644 --- a/src/api/routes/guilds/#guild_id/roles/member-counts.ts +++ b/src/api/routes/guilds/#guild_id/roles/member-counts.ts
@@ -23,16 +23,16 @@ import { route } from "@spacebar/api"; const router: Router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - const { guild_id } = req.params; - await Member.IsInGuildOrFail(req.user_id, guild_id); + const { guild_id } = req.params; + await Member.IsInGuildOrFail(req.user_id, guild_id); - const role_ids = await Role.find({ where: { guild_id }, select: ["id"] }); - const counts: { [id: string]: number } = {}; - for (const { id } of role_ids) { - counts[id] = await Member.count({ where: { roles: { id }, guild_id } }); - } + const role_ids = await Role.find({ where: { guild_id }, select: ["id"] }); + const counts: { [id: string]: number } = {}; + for (const { id } of role_ids) { + counts[id] = await Member.count({ where: { roles: { id }, guild_id } }); + } - return res.json(counts); + return res.json(counts); }); export default router; diff --git a/src/api/routes/guilds/#guild_id/stickers.ts b/src/api/routes/guilds/#guild_id/stickers.ts
index 231c3b55..f3a9f527 100644 --- a/src/api/routes/guilds/#guild_id/stickers.ts +++ b/src/api/routes/guilds/#guild_id/stickers.ts
@@ -25,185 +25,185 @@ import { ModifyGuildStickerSchema, StickerFormatType, StickerType } from "@space const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "APIStickerArray", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; - await Member.IsInGuildOrFail(req.user_id, guild_id); + "/", + route({ + responses: { + 200: { + body: "APIStickerArray", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + await Member.IsInGuildOrFail(req.user_id, guild_id); - res.json(await Sticker.find({ where: { guild_id } })); - }, + res.json(await Sticker.find({ where: { guild_id } })); + }, ); const bodyParser = multer({ - limits: { - fileSize: 1024 * 1024 * 100, - fields: 10, - files: 1, - }, - storage: multer.memoryStorage(), + limits: { + fileSize: 1024 * 1024 * 100, + fields: 10, + files: 1, + }, + storage: multer.memoryStorage(), }).single("file"); router.post( - "/", - bodyParser, - route({ - permission: "MANAGE_EMOJIS_AND_STICKERS", - requestBody: "ModifyGuildStickerSchema", - responses: { - 200: { - body: "Sticker", - }, - 400: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - if (!req.file) throw new HTTPError("missing file"); + "/", + bodyParser, + route({ + permission: "MANAGE_EMOJIS_AND_STICKERS", + requestBody: "ModifyGuildStickerSchema", + responses: { + 200: { + body: "Sticker", + }, + 400: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + if (!req.file) throw new HTTPError("missing file"); - const { guild_id } = req.params; - const body = req.body as ModifyGuildStickerSchema; - const id = Snowflake.generate(); + const { guild_id } = req.params; + const body = req.body as ModifyGuildStickerSchema; + const id = Snowflake.generate(); - const sticker_count = await Sticker.count({ - where: { guild_id: guild_id }, - }); - const { maxStickers } = Config.get().limits.guild; + const sticker_count = await Sticker.count({ + where: { guild_id: guild_id }, + }); + const { maxStickers } = Config.get().limits.guild; - if (sticker_count >= maxStickers) throw DiscordApiErrors.MAXIMUM_STICKERS.withParams(maxStickers); + if (sticker_count >= maxStickers) throw DiscordApiErrors.MAXIMUM_STICKERS.withParams(maxStickers); - const [sticker] = await Promise.all([ - Sticker.create({ - ...body, - guild_id, - id, - type: StickerType.GUILD, - format_type: getStickerFormat(req.file.mimetype), - available: true, - }).save(), - uploadFile(`/stickers/${id}`, req.file), - ]); + const [sticker] = await Promise.all([ + Sticker.create({ + ...body, + guild_id, + id, + type: StickerType.GUILD, + format_type: getStickerFormat(req.file.mimetype), + available: true, + }).save(), + uploadFile(`/stickers/${id}`, req.file), + ]); - await sendStickerUpdateEvent(guild_id); + await sendStickerUpdateEvent(guild_id); - res.json(sticker); - }, + res.json(sticker); + }, ); function getStickerFormat(mime_type: string) { - switch (mime_type) { - case "image/apng": - return StickerFormatType.APNG; - case "application/json": - return StickerFormatType.LOTTIE; - case "image/png": - return StickerFormatType.PNG; - case "image/gif": - return StickerFormatType.GIF; - default: - throw new HTTPError("invalid sticker format: must be png, apng or lottie"); - } + switch (mime_type) { + case "image/apng": + return StickerFormatType.APNG; + case "application/json": + return StickerFormatType.LOTTIE; + case "image/png": + return StickerFormatType.PNG; + case "image/gif": + return StickerFormatType.GIF; + default: + throw new HTTPError("invalid sticker format: must be png, apng or lottie"); + } } router.get( - "/:sticker_id", - route({ - responses: { - 200: { - body: "Sticker", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id, sticker_id } = req.params; - await Member.IsInGuildOrFail(req.user_id, guild_id); + "/:sticker_id", + route({ + responses: { + 200: { + body: "Sticker", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id, sticker_id } = req.params; + await Member.IsInGuildOrFail(req.user_id, guild_id); - res.json( - await Sticker.findOneOrFail({ - where: { guild_id, id: sticker_id }, - }), - ); - }, + res.json( + await Sticker.findOneOrFail({ + where: { guild_id, id: sticker_id }, + }), + ); + }, ); router.patch( - "/:sticker_id", - route({ - requestBody: "ModifyGuildStickerSchema", - permission: "MANAGE_EMOJIS_AND_STICKERS", - responses: { - 200: { - body: "Sticker", - }, - 400: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id, sticker_id } = req.params; - const body = req.body as ModifyGuildStickerSchema; + "/:sticker_id", + route({ + requestBody: "ModifyGuildStickerSchema", + permission: "MANAGE_EMOJIS_AND_STICKERS", + responses: { + 200: { + body: "Sticker", + }, + 400: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id, sticker_id } = req.params; + const body = req.body as ModifyGuildStickerSchema; - const sticker = await Sticker.create({ - ...body, - guild_id, - id: sticker_id, - }).save(); - await sendStickerUpdateEvent(guild_id); + const sticker = await Sticker.create({ + ...body, + guild_id, + id: sticker_id, + }).save(); + await sendStickerUpdateEvent(guild_id); - return res.json(sticker); - }, + return res.json(sticker); + }, ); async function sendStickerUpdateEvent(guild_id: string) { - return emitEvent({ - event: "GUILD_STICKERS_UPDATE", - guild_id: guild_id, - data: { - guild_id: guild_id, - stickers: await Sticker.find({ where: { guild_id: guild_id } }), - }, - } as GuildStickersUpdateEvent); + return emitEvent({ + event: "GUILD_STICKERS_UPDATE", + guild_id: guild_id, + data: { + guild_id: guild_id, + stickers: await Sticker.find({ where: { guild_id: guild_id } }), + }, + } as GuildStickersUpdateEvent); } router.delete( - "/:sticker_id", - route({ - permission: "MANAGE_EMOJIS_AND_STICKERS", - responses: { - 204: {}, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id, sticker_id } = req.params; + "/:sticker_id", + route({ + permission: "MANAGE_EMOJIS_AND_STICKERS", + responses: { + 204: {}, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id, sticker_id } = req.params; - await Sticker.delete({ guild_id, id: sticker_id }); - await sendStickerUpdateEvent(guild_id); + await Sticker.delete({ guild_id, id: sticker_id }); + await sendStickerUpdateEvent(guild_id); - return res.sendStatus(204); - }, + return res.sendStatus(204); + }, ); export default router; diff --git a/src/api/routes/guilds/#guild_id/templates.ts b/src/api/routes/guilds/#guild_id/templates.ts
index ef9d12c6..2cc9604c 100644 --- a/src/api/routes/guilds/#guild_id/templates.ts +++ b/src/api/routes/guilds/#guild_id/templates.ts
@@ -24,160 +24,160 @@ import { HTTPError } from "lambert-server"; const router: Router = Router({ mergeParams: true }); const TemplateGuildProjection: (keyof Guild)[] = [ - "id", - "name", - "description", - "region", - "verification_level", - "default_message_notifications", - "explicit_content_filter", - "preferred_locale", - "afk_timeout", - // "roles", - // "channels", - "afk_channel_id", - "system_channel_id", - "system_channel_flags", - "icon", + "id", + "name", + "description", + "region", + "verification_level", + "default_message_notifications", + "explicit_content_filter", + "preferred_locale", + "afk_timeout", + // "roles", + // "channels", + "afk_channel_id", + "system_channel_id", + "system_channel_flags", + "icon", ]; router.get( - "/", - route({ - responses: { - 200: { - body: "APITemplateArray", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; + "/", + route({ + responses: { + 200: { + body: "APITemplateArray", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; - const templates = await Template.find({ - where: { source_guild_id: guild_id }, - }); + const templates = await Template.find({ + where: { source_guild_id: guild_id }, + }); - return res.json(templates); - }, + return res.json(templates); + }, ); router.post( - "/", - route({ - requestBody: "TemplateCreateSchema", - permission: "MANAGE_GUILD", - responses: { - 200: { - body: "Template", - }, - 400: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; - const guild = await Guild.findOneOrFail({ - where: { id: guild_id }, - select: TemplateGuildProjection, - relations: ["roles", "channels"], - }); - const exists = await Template.findOne({ - where: { id: guild_id }, - }); - if (exists) throw new HTTPError("Template already exists", 400); + "/", + route({ + requestBody: "TemplateCreateSchema", + permission: "MANAGE_GUILD", + responses: { + 200: { + body: "Template", + }, + 400: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + const guild = await Guild.findOneOrFail({ + where: { id: guild_id }, + select: TemplateGuildProjection, + relations: ["roles", "channels"], + }); + const exists = await Template.findOne({ + where: { id: guild_id }, + }); + if (exists) throw new HTTPError("Template already exists", 400); - const template = await Template.create({ - ...req.body, - code: generateCode(), - creator_id: req.user_id, - created_at: new Date(), - updated_at: new Date(), - source_guild_id: guild_id, - serialized_source_guild: guild, - }).save(); + const template = await Template.create({ + ...req.body, + code: generateCode(), + creator_id: req.user_id, + created_at: new Date(), + updated_at: new Date(), + source_guild_id: guild_id, + serialized_source_guild: guild, + }).save(); - res.json(template); - }, + res.json(template); + }, ); router.delete( - "/:code", - route({ - permission: "MANAGE_GUILD", - responses: { - 200: { body: "Template" }, - 403: { body: "APIErrorResponse" }, - }, - }), - async (req: Request, res: Response) => { - const { code, guild_id } = req.params; + "/:code", + route({ + permission: "MANAGE_GUILD", + responses: { + 200: { body: "Template" }, + 403: { body: "APIErrorResponse" }, + }, + }), + async (req: Request, res: Response) => { + const { code, guild_id } = req.params; - const template = await Template.delete({ - code, - source_guild_id: guild_id, - }); + const template = await Template.delete({ + code, + source_guild_id: guild_id, + }); - res.json(template); - }, + res.json(template); + }, ); router.put( - "/:code", - route({ - permission: "MANAGE_GUILD", - responses: { - 200: { body: "Template" }, - 403: { body: "APIErrorResponse" }, - }, - }), - async (req: Request, res: Response) => { - const { code, guild_id } = req.params; - const guild = await Guild.findOneOrFail({ - where: { id: guild_id }, - select: TemplateGuildProjection, - }); + "/:code", + route({ + permission: "MANAGE_GUILD", + responses: { + 200: { body: "Template" }, + 403: { body: "APIErrorResponse" }, + }, + }), + async (req: Request, res: Response) => { + const { code, guild_id } = req.params; + const guild = await Guild.findOneOrFail({ + where: { id: guild_id }, + select: TemplateGuildProjection, + }); - const template = await Template.create({ - code, - serialized_source_guild: guild, - }).save(); + const template = await Template.create({ + code, + serialized_source_guild: guild, + }).save(); - res.json(template); - }, + res.json(template); + }, ); router.patch( - "/:code", - route({ - requestBody: "TemplateModifySchema", - permission: "MANAGE_GUILD", - responses: { - 200: { body: "Template" }, - 403: { body: "APIErrorResponse" }, - }, - }), - async (req: Request, res: Response) => { - const { code, guild_id } = req.params; - const { name, description } = req.body; + "/:code", + route({ + requestBody: "TemplateModifySchema", + permission: "MANAGE_GUILD", + responses: { + 200: { body: "Template" }, + 403: { body: "APIErrorResponse" }, + }, + }), + async (req: Request, res: Response) => { + const { code, guild_id } = req.params; + const { name, description } = req.body; - const template = await Template.findOneOrFail({ - where: { code, source_guild_id: guild_id }, - }); + const template = await Template.findOneOrFail({ + where: { code, source_guild_id: guild_id }, + }); - template.name = name; - template.description = description; + template.name = name; + template.description = description; - await template.save(); + await template.save(); - res.json(template); - }, + res.json(template); + }, ); export default router; diff --git a/src/api/routes/guilds/#guild_id/vanity-url.ts b/src/api/routes/guilds/#guild_id/vanity-url.ts
index 77d72ed0..e622c255 100644 --- a/src/api/routes/guilds/#guild_id/vanity-url.ts +++ b/src/api/routes/guilds/#guild_id/vanity-url.ts
@@ -27,96 +27,96 @@ const router = Router({ mergeParams: true }); const InviteRegex = /\W/g; router.get( - "/", - route({ - permission: "MANAGE_GUILD", - responses: { - 200: { - body: "GuildVanityUrlResponse", - }, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; - const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); + "/", + route({ + permission: "MANAGE_GUILD", + responses: { + 200: { + body: "GuildVanityUrlResponse", + }, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); - if (!guild.features.includes("ALIASABLE_NAMES")) { - const invite = await Invite.findOne({ - where: { guild_id: guild_id, vanity_url: true }, - }); - if (!invite) return res.json({ code: null }); + if (!guild.features.includes("ALIASABLE_NAMES")) { + const invite = await Invite.findOne({ + where: { guild_id: guild_id, vanity_url: true }, + }); + if (!invite) return res.json({ code: null }); - return res.json({ code: invite.code, uses: invite.uses }); - } else { - const invite = await Invite.find({ - where: { guild_id: guild_id, vanity_url: true }, - }); - if (!invite || invite.length == 0) return res.json({ code: null }); + return res.json({ code: invite.code, uses: invite.uses }); + } else { + const invite = await Invite.find({ + where: { guild_id: guild_id, vanity_url: true }, + }); + if (!invite || invite.length == 0) return res.json({ code: null }); - return res.json(invite.map((x) => ({ code: x.code, uses: x.uses }))); - } - }, + return res.json(invite.map((x) => ({ code: x.code, uses: x.uses }))); + } + }, ); router.patch( - "/", - route({ - requestBody: "VanityUrlSchema", - permission: "MANAGE_GUILD", - responses: { - 200: { - body: "GuildVanityUrlCreateResponse", - }, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; - const body = req.body as VanityUrlSchema; - const code = body.code?.replace(InviteRegex, ""); + "/", + route({ + requestBody: "VanityUrlSchema", + permission: "MANAGE_GUILD", + responses: { + 200: { + body: "GuildVanityUrlCreateResponse", + }, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + const body = req.body as VanityUrlSchema; + const code = body.code?.replace(InviteRegex, ""); - const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); - if (!guild.features.includes("VANITY_URL")) throw new HTTPError("Your guild doesn't support vanity urls"); + const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); + if (!guild.features.includes("VANITY_URL")) throw new HTTPError("Your guild doesn't support vanity urls"); - if (!code || code.length === 0) throw new HTTPError("Code cannot be null or empty"); + if (!code || code.length === 0) throw new HTTPError("Code cannot be null or empty"); - const invite = await Invite.findOne({ where: { code } }); - if (invite) throw new HTTPError("Invite already exists"); + const invite = await Invite.findOne({ where: { code } }); + if (invite) throw new HTTPError("Invite already exists"); - const { id } = await Channel.findOneOrFail({ - where: { guild_id, type: ChannelType.GUILD_TEXT }, - }); + const { id } = await Channel.findOneOrFail({ + where: { guild_id, type: ChannelType.GUILD_TEXT }, + }); - if (!guild.features.includes("ALIASABLE_NAMES")) { - await Invite.delete({ guild_id, vanity_url: true }); - } + if (!guild.features.includes("ALIASABLE_NAMES")) { + await Invite.delete({ guild_id, vanity_url: true }); + } - await Invite.create({ - vanity_url: true, - code, - temporary: false, - uses: 0, - max_uses: 0, - max_age: 0, - created_at: new Date(), - guild_id: guild_id, - channel_id: id, - flags: 0, - }).save(); + await Invite.create({ + vanity_url: true, + code, + temporary: false, + uses: 0, + max_uses: 0, + max_age: 0, + created_at: new Date(), + guild_id: guild_id, + channel_id: id, + flags: 0, + }).save(); - return res.json({ code }); - }, + return res.json({ code }); + }, ); export default router; diff --git a/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts b/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts
index 38be139c..5283ae01 100644 --- a/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts +++ b/src/api/routes/guilds/#guild_id/voice-states/#user_id/index.ts
@@ -25,67 +25,67 @@ const router = Router({ mergeParams: true }); //TODO need more testing when community guild and voice stage channel are working router.patch( - "/", - route({ - requestBody: "VoiceStateUpdateSchema", - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const body = req.body as VoiceStateUpdateSchema; - const { guild_id } = req.params; - const user_id = req.params.user_id === "@me" ? req.user_id : req.params.user_id; + "/", + route({ + requestBody: "VoiceStateUpdateSchema", + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const body = req.body as VoiceStateUpdateSchema; + const { guild_id } = req.params; + const user_id = req.params.user_id === "@me" ? req.user_id : req.params.user_id; - const perms = await getPermission(req.user_id, guild_id, body.channel_id); + const perms = await getPermission(req.user_id, guild_id, body.channel_id); - /* + /* From https://discord.com/developers/docs/resources/guild#modify-current-user-voice-state You must have the MUTE_MEMBERS permission to unsuppress others. You can always suppress yourself. You must have the REQUEST_TO_SPEAK permission to request to speak. You can always clear your own request to speak. */ - if (body.suppress && user_id !== req.user_id) { - perms.hasThrow("MUTE_MEMBERS"); - } - if (!body.suppress) body.request_to_speak_timestamp = new Date(); - if (body.request_to_speak_timestamp) perms.hasThrow("REQUEST_TO_SPEAK"); + if (body.suppress && user_id !== req.user_id) { + perms.hasThrow("MUTE_MEMBERS"); + } + if (!body.suppress) body.request_to_speak_timestamp = new Date(); + if (body.request_to_speak_timestamp) perms.hasThrow("REQUEST_TO_SPEAK"); - const voice_state = await VoiceState.findOne({ - where: { - guild_id, - channel_id: body.channel_id, - user_id, - }, - }); - if (!voice_state) throw DiscordApiErrors.UNKNOWN_VOICE_STATE; + const voice_state = await VoiceState.findOne({ + where: { + guild_id, + channel_id: body.channel_id, + user_id, + }, + }); + if (!voice_state) throw DiscordApiErrors.UNKNOWN_VOICE_STATE; - voice_state.assign(body); - const channel = await Channel.findOneOrFail({ - where: { guild_id, id: body.channel_id }, - }); - if (channel.type !== ChannelType.GUILD_STAGE_VOICE) { - throw DiscordApiErrors.CANNOT_EXECUTE_ON_THIS_CHANNEL_TYPE; - } + voice_state.assign(body); + const channel = await Channel.findOneOrFail({ + where: { guild_id, id: body.channel_id }, + }); + if (channel.type !== ChannelType.GUILD_STAGE_VOICE) { + throw DiscordApiErrors.CANNOT_EXECUTE_ON_THIS_CHANNEL_TYPE; + } - await Promise.all([ - voice_state.save(), - emitEvent({ - event: "VOICE_STATE_UPDATE", - data: voice_state.toPublicVoiceState(), - guild_id, - } as VoiceStateUpdateEvent), - ]); - return res.sendStatus(204); - }, + await Promise.all([ + voice_state.save(), + emitEvent({ + event: "VOICE_STATE_UPDATE", + data: voice_state.toPublicVoiceState(), + guild_id, + } as VoiceStateUpdateEvent), + ]); + return res.sendStatus(204); + }, ); export default router; diff --git a/src/api/routes/guilds/#guild_id/webhooks.ts b/src/api/routes/guilds/#guild_id/webhooks.ts
index 49f34b6b..81837d34 100644 --- a/src/api/routes/guilds/#guild_id/webhooks.ts +++ b/src/api/routes/guilds/#guild_id/webhooks.ts
@@ -22,31 +22,31 @@ import { Request, Response, Router } from "express"; const router = Router({ mergeParams: true }); router.get( - "/", - route({ - description: "Returns a list of guild webhook objects. Requires the MANAGE_WEBHOOKS permission.", - permission: "MANAGE_WEBHOOKS", - responses: { - 200: { - body: "APIWebhookArray", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; - const webhooks = await Webhook.find({ - where: { guild_id }, - relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"], - }); + "/", + route({ + description: "Returns a list of guild webhook objects. Requires the MANAGE_WEBHOOKS permission.", + permission: "MANAGE_WEBHOOKS", + responses: { + 200: { + body: "APIWebhookArray", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; + const webhooks = await Webhook.find({ + where: { guild_id }, + relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"], + }); - const instanceUrl = Config.get().api.endpointPublic; - return res.json( - webhooks.map((webhook) => ({ - ...webhook, - url: instanceUrl + "/webhooks/" + webhook.id + "/" + webhook.token, - })), - ); - }, + const instanceUrl = Config.get().api.endpointPublic; + return res.json( + webhooks.map((webhook) => ({ + ...webhook, + url: instanceUrl + "/webhooks/" + webhook.id + "/" + webhook.token, + })), + ); + }, ); export default router; diff --git a/src/api/routes/guilds/#guild_id/welcome-screen.ts b/src/api/routes/guilds/#guild_id/welcome-screen.ts
index a8956eea..474d5977 100644 --- a/src/api/routes/guilds/#guild_id/welcome-screen.ts +++ b/src/api/routes/guilds/#guild_id/welcome-screen.ts
@@ -24,69 +24,69 @@ import { GuildUpdateWelcomeScreenSchema } from "@spacebar/schemas"; const router: Router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "GuildWelcomeScreen", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const guild_id = req.params.guild_id; + "/", + route({ + responses: { + 200: { + body: "GuildWelcomeScreen", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const guild_id = req.params.guild_id; - const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); - await Member.IsInGuildOrFail(req.user_id, guild_id); + const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); + await Member.IsInGuildOrFail(req.user_id, guild_id); - res.json(guild.welcome_screen); - }, + res.json(guild.welcome_screen); + }, ); router.patch( - "/", - route({ - requestBody: "GuildUpdateWelcomeScreenSchema", - permission: "MANAGE_GUILD", - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const guild_id = req.params.guild_id; - const body = req.body as GuildUpdateWelcomeScreenSchema; + "/", + route({ + requestBody: "GuildUpdateWelcomeScreenSchema", + permission: "MANAGE_GUILD", + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const guild_id = req.params.guild_id; + const body = req.body as GuildUpdateWelcomeScreenSchema; - const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); + const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); - if (body.enabled != undefined) guild.welcome_screen.enabled = body.enabled; + if (body.enabled != undefined) guild.welcome_screen.enabled = body.enabled; - if (body.description != undefined) guild.welcome_screen.description = body.description; + if (body.description != undefined) guild.welcome_screen.description = body.description; - if (body.welcome_channels != undefined) { - // Ensure channels exist within the guild - await Promise.all( - body.welcome_channels?.map(({ channel_id }) => - Channel.findOneOrFail({ - where: { id: channel_id, guild_id }, - select: { id: true }, - }), - ) || [], - ); - guild.welcome_screen.welcome_channels = body.welcome_channels; - } + if (body.welcome_channels != undefined) { + // Ensure channels exist within the guild + await Promise.all( + body.welcome_channels?.map(({ channel_id }) => + Channel.findOneOrFail({ + where: { id: channel_id, guild_id }, + select: { id: true }, + }), + ) || [], + ); + guild.welcome_screen.welcome_channels = body.welcome_channels; + } - await guild.save(); + await guild.save(); - res.status(200).json(guild.welcome_screen); - }, + res.status(200).json(guild.welcome_screen); + }, ); export default router; diff --git a/src/api/routes/guilds/#guild_id/widget.json.ts b/src/api/routes/guilds/#guild_id/widget.json.ts
index d6f958f7..8f41417a 100644 --- a/src/api/routes/guilds/#guild_id/widget.json.ts +++ b/src/api/routes/guilds/#guild_id/widget.json.ts
@@ -32,88 +32,88 @@ const router: Router = Router({ mergeParams: true }); // https://discord.com/developers/docs/resources/guild#get-guild-widget // TODO: Cache the response for a guild for 5 minutes regardless of response router.get( - "/", - route({ - responses: { - 200: { - body: "GuildWidgetJsonResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; + "/", + route({ + responses: { + 200: { + body: "GuildWidgetJsonResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; - const guild = await Guild.findOneOrFail({ - where: { id: guild_id }, - select: { - channel_ordering: true, - widget_channel_id: true, - widget_enabled: true, - presence_count: true, - name: true, - }, - }); - if (!guild.widget_enabled) throw DiscordApiErrors.EMBED_DISABLED; + const guild = await Guild.findOneOrFail({ + where: { id: guild_id }, + select: { + channel_ordering: true, + widget_channel_id: true, + widget_enabled: true, + presence_count: true, + name: true, + }, + }); + if (!guild.widget_enabled) throw DiscordApiErrors.EMBED_DISABLED; - // Fetch existing widget invite for widget channel - let invite = await Invite.findOne({ - where: { channel_id: guild.widget_channel_id }, - }); + // Fetch existing widget invite for widget channel + let invite = await Invite.findOne({ + where: { channel_id: guild.widget_channel_id }, + }); - if (guild.widget_channel_id && !invite) { - // Create invite for channel if none exists - // TODO: Refactor invite create code to a shared function - const max_age = 86400; // 24 hours - const expires_at = new Date(max_age * 1000 + Date.now()); + if (guild.widget_channel_id && !invite) { + // Create invite for channel if none exists + // TODO: Refactor invite create code to a shared function + const max_age = 86400; // 24 hours + const expires_at = new Date(max_age * 1000 + Date.now()); - invite = await Invite.create({ - code: randomString(), - temporary: false, - uses: 0, - max_uses: 0, - max_age: max_age, - expires_at, - created_at: new Date(), - guild_id, - channel_id: guild.widget_channel_id, - flags: 0, - }).save(); - } + invite = await Invite.create({ + code: randomString(), + temporary: false, + uses: 0, + max_uses: 0, + max_age: max_age, + expires_at, + created_at: new Date(), + guild_id, + channel_id: guild.widget_channel_id, + flags: 0, + }).save(); + } - // Fetch voice channels, and the @everyone permissions object - const channels: { id: string; name: string; position: number }[] = []; + // Fetch voice channels, and the @everyone permissions object + const channels: { id: string; name: string; position: number }[] = []; - (await Channel.getOrderedChannels(guild.id, guild)).filter((doc) => { - // Only return channels where @everyone has the CONNECT permission - if (doc.permission_overwrites === undefined || Permissions.channelPermission(doc.permission_overwrites, Permissions.FLAGS.CONNECT) === Permissions.FLAGS.CONNECT) { - channels.push({ - id: doc.id, - name: doc.name ?? "Unknown channel", - position: doc.position ?? 0, - }); - } - }); + (await Channel.getOrderedChannels(guild.id, guild)).filter((doc) => { + // Only return channels where @everyone has the CONNECT permission + if (doc.permission_overwrites === undefined || Permissions.channelPermission(doc.permission_overwrites, Permissions.FLAGS.CONNECT) === Permissions.FLAGS.CONNECT) { + channels.push({ + id: doc.id, + name: doc.name ?? "Unknown channel", + position: doc.position ?? 0, + }); + } + }); - // Fetch members - // TODO: Understand how Discord's max 100 random member sample works, and apply to here (see top of this file) - const members = await Member.find({ where: { guild_id: guild_id } }); + // Fetch members + // TODO: Understand how Discord's max 100 random member sample works, and apply to here (see top of this file) + const members = await Member.find({ where: { guild_id: guild_id } }); - // Construct object to respond with - const data = { - id: guild_id, - name: guild.name, - instant_invite: invite?.code, - channels: channels, - members: members, - presence_count: guild.presence_count, - }; + // Construct object to respond with + const data = { + id: guild_id, + name: guild.name, + instant_invite: invite?.code, + channels: channels, + members: members, + presence_count: guild.presence_count, + }; - res.set("Cache-Control", "public, max-age=300"); - return res.json(data); - }, + res.set("Cache-Control", "public, max-age=300"); + return res.json(data); + }, ); export default router; diff --git a/src/api/routes/guilds/#guild_id/widget.png.ts b/src/api/routes/guilds/#guild_id/widget.png.ts
index a7a9b60b..b0abc95b 100644 --- a/src/api/routes/guilds/#guild_id/widget.png.ts +++ b/src/api/routes/guilds/#guild_id/widget.png.ts
@@ -33,112 +33,112 @@ const router: Router = Router({ mergeParams: true }); // https://discord.com/developers/docs/resources/guild#get-guild-widget-image // TODO: Cache the response router.get( - "/", - route({ - responses: { - 200: {}, - 400: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; + "/", + route({ + responses: { + 200: {}, + 400: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; - const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); - if (!guild.widget_enabled) throw DiscordApiErrors.EMBED_DISABLED; + const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); + if (!guild.widget_enabled) throw DiscordApiErrors.EMBED_DISABLED; - // Fetch guild information - const icon = "avatars/" + guild_id + "/" + guild.icon; - const name = guild.name; - const presence = guild.presence_count + " ONLINE"; + // Fetch guild information + const icon = "avatars/" + guild_id + "/" + guild.icon; + const name = guild.name; + const presence = guild.presence_count + " ONLINE"; - // Fetch parameter - const style = req.query.style?.toString() || "shield"; - if (!["shield", "banner1", "banner2", "banner3", "banner4"].includes(style)) { - throw new HTTPError("Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').", 400); - } + // Fetch parameter + const style = req.query.style?.toString() || "shield"; + if (!["shield", "banner1", "banner2", "banner3", "banner4"].includes(style)) { + throw new HTTPError("Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').", 400); + } - // Setup canvas - const { createCanvas, loadImage } = require("canvas"); - const sizeOf = require("image-size"); + // Setup canvas + const { createCanvas, loadImage } = require("canvas"); + const sizeOf = require("image-size"); - // TODO: Widget style templates need Spacebar branding - const source = path.join(__dirname, "..", "..", "..", "..", "..", "assets", "widget", `${style}.png`); - if (!fs.existsSync(source)) { - throw new HTTPError("Widget template does not exist.", 400); - } + // TODO: Widget style templates need Spacebar branding + const source = path.join(__dirname, "..", "..", "..", "..", "..", "assets", "widget", `${style}.png`); + if (!fs.existsSync(source)) { + throw new HTTPError("Widget template does not exist.", 400); + } - // Create base template image for parameter - const { width, height } = await sizeOf(source); - const canvas = createCanvas(width, height); - const ctx = canvas.getContext("2d"); - const template = await loadImage(source); - ctx.drawImage(template, 0, 0); + // Create base template image for parameter + const { width, height } = await sizeOf(source); + const canvas = createCanvas(width, height); + const ctx = canvas.getContext("2d"); + const template = await loadImage(source); + ctx.drawImage(template, 0, 0); - // Add the guild specific information to the template asset image - switch (style) { - case "shield": - ctx.textAlign = "center"; - await drawText(ctx, 73, 13, "#FFFFFF", "thin 10px Verdana", presence); - break; - case "banner1": - if (icon) await drawIcon(ctx, 20, 27, 50, icon); - await drawText(ctx, 83, 51, "#FFFFFF", "12px Verdana", name, 22); - await drawText(ctx, 83, 66, "#C9D2F0FF", "thin 11px Verdana", presence); - break; - case "banner2": - if (icon) await drawIcon(ctx, 13, 19, 36, icon); - await drawText(ctx, 62, 34, "#FFFFFF", "12px Verdana", name, 15); - await drawText(ctx, 62, 49, "#C9D2F0FF", "thin 11px Verdana", presence); - break; - case "banner3": - if (icon) await drawIcon(ctx, 20, 20, 50, icon); - await drawText(ctx, 83, 44, "#FFFFFF", "12px Verdana", name, 27); - await drawText(ctx, 83, 58, "#C9D2F0FF", "thin 11px Verdana", presence); - break; - case "banner4": - if (icon) await drawIcon(ctx, 21, 136, 50, icon); - await drawText(ctx, 84, 156, "#FFFFFF", "13px Verdana", name, 27); - await drawText(ctx, 84, 171, "#C9D2F0FF", "thin 12px Verdana", presence); - break; - default: - throw new HTTPError("Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').", 400); - } + // Add the guild specific information to the template asset image + switch (style) { + case "shield": + ctx.textAlign = "center"; + await drawText(ctx, 73, 13, "#FFFFFF", "thin 10px Verdana", presence); + break; + case "banner1": + if (icon) await drawIcon(ctx, 20, 27, 50, icon); + await drawText(ctx, 83, 51, "#FFFFFF", "12px Verdana", name, 22); + await drawText(ctx, 83, 66, "#C9D2F0FF", "thin 11px Verdana", presence); + break; + case "banner2": + if (icon) await drawIcon(ctx, 13, 19, 36, icon); + await drawText(ctx, 62, 34, "#FFFFFF", "12px Verdana", name, 15); + await drawText(ctx, 62, 49, "#C9D2F0FF", "thin 11px Verdana", presence); + break; + case "banner3": + if (icon) await drawIcon(ctx, 20, 20, 50, icon); + await drawText(ctx, 83, 44, "#FFFFFF", "12px Verdana", name, 27); + await drawText(ctx, 83, 58, "#C9D2F0FF", "thin 11px Verdana", presence); + break; + case "banner4": + if (icon) await drawIcon(ctx, 21, 136, 50, icon); + await drawText(ctx, 84, 156, "#FFFFFF", "13px Verdana", name, 27); + await drawText(ctx, 84, 171, "#C9D2F0FF", "thin 12px Verdana", presence); + break; + default: + throw new HTTPError("Value must be one of ('shield', 'banner1', 'banner2', 'banner3', 'banner4').", 400); + } - // Return final image - const buffer = canvas.toBuffer("image/png"); - res.set("Content-Type", "image/png"); - res.set("Cache-Control", "public, max-age=3600"); - return res.send(buffer); - }, + // Return final image + const buffer = canvas.toBuffer("image/png"); + res.set("Content-Type", "image/png"); + res.set("Cache-Control", "public, max-age=3600"); + return res.send(buffer); + }, ); async function drawIcon(canvas: any, x: number, y: number, scale: number, icon: string) { - const { loadImage } = require("canvas"); - const img = await loadImage(await storage.get(icon)); + const { loadImage } = require("canvas"); + const img = await loadImage(await storage.get(icon)); - // Do some canvas clipping magic! - canvas.save(); - canvas.beginPath(); + // Do some canvas clipping magic! + canvas.save(); + canvas.beginPath(); - const r = scale / 2; // use scale to determine radius - canvas.arc(x + r, y + r, r, 0, 2 * Math.PI, false); // start circle at x, and y coords + radius to find center + const r = scale / 2; // use scale to determine radius + canvas.arc(x + r, y + r, r, 0, 2 * Math.PI, false); // start circle at x, and y coords + radius to find center - canvas.clip(); - canvas.drawImage(img, x, y, scale, scale); + canvas.clip(); + canvas.drawImage(img, x, y, scale, scale); - canvas.restore(); + canvas.restore(); } async function drawText(canvas: any, x: number, y: number, color: string, font: string, text: string, maxcharacters?: number) { - canvas.fillStyle = color; - canvas.font = font; - if (text.length > (maxcharacters || 0) && maxcharacters) text = text.slice(0, maxcharacters) + "..."; - canvas.fillText(text, x, y); + canvas.fillStyle = color; + canvas.font = font; + if (text.length > (maxcharacters || 0) && maxcharacters) text = text.slice(0, maxcharacters) + "..."; + canvas.fillText(text, x, y); } export default router; diff --git a/src/api/routes/guilds/#guild_id/widget.ts b/src/api/routes/guilds/#guild_id/widget.ts
index cad26f3d..5a2fdb96 100644 --- a/src/api/routes/guilds/#guild_id/widget.ts +++ b/src/api/routes/guilds/#guild_id/widget.ts
@@ -25,62 +25,62 @@ const router: Router = Router({ mergeParams: true }); // https://discord.com/developers/docs/resources/guild#get-guild-widget-settings router.get( - "/", - route({ - responses: { - 200: { - body: "GuildWidgetSettingsResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { guild_id } = req.params; + "/", + route({ + responses: { + 200: { + body: "GuildWidgetSettingsResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { guild_id } = req.params; - const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); + const guild = await Guild.findOneOrFail({ where: { id: guild_id } }); - return res.json({ - enabled: guild.widget_enabled || false, - channel_id: guild.widget_channel_id || null, - }); - }, + return res.json({ + enabled: guild.widget_enabled || false, + channel_id: guild.widget_channel_id || null, + }); + }, ); // https://discord.com/developers/docs/resources/guild#modify-guild-widget router.patch( - "/", - route({ - requestBody: "WidgetModifySchema", - permission: "MANAGE_GUILD", - responses: { - 200: { - body: "WidgetModifySchema", - }, - 400: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const body = req.body as WidgetModifySchema; - const { guild_id } = req.params; + "/", + route({ + requestBody: "WidgetModifySchema", + permission: "MANAGE_GUILD", + responses: { + 200: { + body: "WidgetModifySchema", + }, + 400: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const body = req.body as WidgetModifySchema; + const { guild_id } = req.params; - await Guild.update( - { id: guild_id }, - { - widget_enabled: body.enabled, - widget_channel_id: body.channel_id, - }, - ); - // Widget invite for the widget_channel_id gets created as part of the /guilds/{guild.id}/widget.json request + await Guild.update( + { id: guild_id }, + { + widget_enabled: body.enabled, + widget_channel_id: body.channel_id, + }, + ); + // Widget invite for the widget_channel_id gets created as part of the /guilds/{guild.id}/widget.json request - return res.json(body); - }, + return res.json(body); + }, ); export default router; diff --git a/src/api/routes/guilds/automations/email-domain-lookup.ts b/src/api/routes/guilds/automations/email-domain-lookup.ts
index 93ae231b..605026ba 100644 --- a/src/api/routes/guilds/automations/email-domain-lookup.ts +++ b/src/api/routes/guilds/automations/email-domain-lookup.ts
@@ -26,74 +26,74 @@ import { EmailDomainLookupResponse, EmailDomainLookupSchema, EmailDomainLookupVe const router = Router({ mergeParams: true }); router.post( - "/", - route({ - requestBody: "EmailDomainLookupSchema", - responses: { - 200: { - body: "EmailDomainLookupResponse", - }, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { email } = req.body as EmailDomainLookupSchema; + "/", + route({ + requestBody: "EmailDomainLookupSchema", + responses: { + 200: { + body: "EmailDomainLookupResponse", + }, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { email } = req.body as EmailDomainLookupSchema; - const [_, tld] = email.split("@"); + const [_, tld] = email.split("@"); - if (emailProviders.includes(tld.toLowerCase())) { - throw FieldErrors({ - name: { - message: "That looks like a personal email address. Please use your official student email.", - code: "EMAIL_IS_UNOFFICIAL", - }, - }); - } + if (emailProviders.includes(tld.toLowerCase())) { + throw FieldErrors({ + name: { + message: "That looks like a personal email address. Please use your official student email.", + code: "EMAIL_IS_UNOFFICIAL", + }, + }); + } - return res.json({ - guilds_info: [], - has_matching_guild: false, - } as EmailDomainLookupResponse); - }, + return res.json({ + guilds_info: [], + has_matching_guild: false, + } as EmailDomainLookupResponse); + }, ); router.post( - "/verify-code", - route({ - requestBody: "EmailDomainLookupVerifyCodeSchema", - responses: { - // 200: { - // body: "EmailDomainLookupVerifyCodeResponse", - // }, - 400: { - body: "APIErrorResponse", - }, - 501: {}, - }, - }), - async (req: Request, res: Response) => { - const { email } = req.body as EmailDomainLookupVerifyCodeSchema; + "/verify-code", + route({ + requestBody: "EmailDomainLookupVerifyCodeSchema", + responses: { + // 200: { + // body: "EmailDomainLookupVerifyCodeResponse", + // }, + 400: { + body: "APIErrorResponse", + }, + 501: {}, + }, + }), + async (req: Request, res: Response) => { + const { email } = req.body as EmailDomainLookupVerifyCodeSchema; - const [_, tld] = email.split("@"); + const [_, tld] = email.split("@"); - if (emailProviders.includes(tld.toLowerCase())) { - throw FieldErrors({ - name: { - message: "That looks like a personal email address. Please use your official student email.", - code: "EMAIL_IS_UNOFFICIAL", - }, - }); - } + if (emailProviders.includes(tld.toLowerCase())) { + throw FieldErrors({ + name: { + message: "That looks like a personal email address. Please use your official student email.", + code: "EMAIL_IS_UNOFFICIAL", + }, + }); + } - throw new HTTPError("Not implemented", 501); + throw new HTTPError("Not implemented", 501); - // return res.json({ - // guild: null, - // joined: false, - // } as EmailDomainLookupVerifyCodeResponse); - }, + // return res.json({ + // guild: null, + // joined: false, + // } as EmailDomainLookupVerifyCodeResponse); + }, ); export default router; diff --git a/src/api/routes/guilds/index.ts b/src/api/routes/guilds/index.ts
index 6f60fa67..78ba93a2 100644 --- a/src/api/routes/guilds/index.ts +++ b/src/api/routes/guilds/index.ts
@@ -26,49 +26,49 @@ const router: Router = Router({ mergeParams: true }); //TODO: create default channel router.post( - "/", - route({ - requestBody: "GuildCreateSchema", - right: "CREATE_GUILDS", - responses: { - 201: { - body: "GuildCreateResponse", - }, - 400: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const body = req.body as GuildCreateSchema; + "/", + route({ + requestBody: "GuildCreateSchema", + right: "CREATE_GUILDS", + responses: { + 201: { + body: "GuildCreateResponse", + }, + 400: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const body = req.body as GuildCreateSchema; - const { maxGuilds } = Config.get().limits.user; - const guild_count = await Member.count({ where: { id: req.user_id } }); - const rights = await getRights(req.user_id); - if (guild_count >= maxGuilds && !rights.has("MANAGE_GUILDS")) { - throw DiscordApiErrors.MAXIMUM_GUILDS.withParams(maxGuilds); - } + const { maxGuilds } = Config.get().limits.user; + const guild_count = await Member.count({ where: { id: req.user_id } }); + const rights = await getRights(req.user_id); + if (guild_count >= maxGuilds && !rights.has("MANAGE_GUILDS")) { + throw DiscordApiErrors.MAXIMUM_GUILDS.withParams(maxGuilds); + } - const guild = await Guild.createGuild({ - ...body, - owner_id: req.user_id, - template_guild_id: null, - }); + const guild = await Guild.createGuild({ + ...body, + owner_id: req.user_id, + template_guild_id: null, + }); - const { autoJoin } = Config.get().guild; - if (autoJoin.enabled && !autoJoin.guilds?.length) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - await Config.set({ guild: { autoJoin: { guilds: [guild.id] } } }); - } + const { autoJoin } = Config.get().guild; + if (autoJoin.enabled && !autoJoin.guilds?.length) { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + await Config.set({ guild: { autoJoin: { guilds: [guild.id] } } }); + } - await Member.addToGuild(req.user_id, guild.id); + await Member.addToGuild(req.user_id, guild.id); - res.status(201).json(guild); - }, + res.status(201).json(guild); + }, ); export default router; diff --git a/src/api/routes/guilds/templates/index.ts b/src/api/routes/guilds/templates/index.ts
index 50ba017d..e5dfd684 100644 --- a/src/api/routes/guilds/templates/index.ts +++ b/src/api/routes/guilds/templates/index.ts
@@ -25,80 +25,80 @@ import { GuildTemplateCreateSchema } from "@spacebar/schemas"; const router: Router = Router({ mergeParams: true }); router.get( - "/:template_code", - route({ - responses: { - 200: { - body: "Template", - }, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { template_code } = req.params; + "/:template_code", + route({ + responses: { + 200: { + body: "Template", + }, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { template_code } = req.params; - const template = await getTemplate(template_code); + const template = await getTemplate(template_code); - res.json(template); - }, + res.json(template); + }, ); router.post("/:template_code", route({ requestBody: "GuildTemplateCreateSchema" }), async (req: Request, res: Response) => { - const { template_code } = req.params; - const body = req.body as GuildTemplateCreateSchema; + const { template_code } = req.params; + const body = req.body as GuildTemplateCreateSchema; - const { maxGuilds } = Config.get().limits.user; + const { maxGuilds } = Config.get().limits.user; - const guild_count = await Member.count({ where: { id: req.user_id } }); - if (guild_count >= maxGuilds) throw DiscordApiErrors.MAXIMUM_GUILDS.withParams(maxGuilds); + const guild_count = await Member.count({ where: { id: req.user_id } }); + if (guild_count >= maxGuilds) throw DiscordApiErrors.MAXIMUM_GUILDS.withParams(maxGuilds); - const template = (await getTemplate(template_code)) as Template; + const template = (await getTemplate(template_code)) as Template; - const guild = await Guild.createGuild({ - ...template.serialized_source_guild, - // body comes after the template - ...body, - owner_id: req.user_id, - template_guild_id: template.source_guild_id, - }); + const guild = await Guild.createGuild({ + ...template.serialized_source_guild, + // body comes after the template + ...body, + owner_id: req.user_id, + template_guild_id: template.source_guild_id, + }); - await Member.addToGuild(req.user_id, guild.id); + await Member.addToGuild(req.user_id, guild.id); - res.status(201).json({ id: guild.id }); + res.status(201).json({ id: guild.id }); }); async function getTemplate(code: string) { - const { allowDiscordTemplates, allowRaws, enabled } = Config.get().templates; + const { allowDiscordTemplates, allowRaws, enabled } = Config.get().templates; - if (!enabled) throw new HTTPError("Template creation & usage is disabled on this instance.", 403); + if (!enabled) throw new HTTPError("Template creation & usage is disabled on this instance.", 403); - if (code.startsWith("discord:")) { - if (!allowDiscordTemplates) throw new HTTPError("Discord templates cannot be used on this instance.", 403); + if (code.startsWith("discord:")) { + if (!allowDiscordTemplates) throw new HTTPError("Discord templates cannot be used on this instance.", 403); - const discordTemplateID = code.split("discord:", 2)[1]; + const discordTemplateID = code.split("discord:", 2)[1]; - const discordTemplateData = await fetch(`https://discord.com/api/v9/guilds/templates/${discordTemplateID}`, { - method: "get", - headers: { "Content-Type": "application/json" }, - }); + const discordTemplateData = await fetch(`https://discord.com/api/v9/guilds/templates/${discordTemplateID}`, { + method: "get", + headers: { "Content-Type": "application/json" }, + }); - return await discordTemplateData.json(); - } + return await discordTemplateData.json(); + } - if (code.startsWith("external:")) { - if (!allowRaws) throw new HTTPError("Importing raws is disabled on this instance.", 403); + if (code.startsWith("external:")) { + if (!allowRaws) throw new HTTPError("Importing raws is disabled on this instance.", 403); - return code.split("external:", 2)[1]; - } + return code.split("external:", 2)[1]; + } - return await Template.findOneOrFail({ - where: { code: code }, - }); + return await Template.findOneOrFail({ + where: { code: code }, + }); } export default router; diff --git a/src/api/routes/hub-waitlist.ts b/src/api/routes/hub-waitlist.ts
index bdf09311..43c2662b 100644 --- a/src/api/routes/hub-waitlist.ts +++ b/src/api/routes/hub-waitlist.ts
@@ -22,28 +22,28 @@ import { Request, Response, Router } from "express"; const router = Router({ mergeParams: true }); router.post( - "/signup", - route({ - requestBody: "HubWaitlistSignupSchema", - responses: { - 200: { - body: "HubWaitlistSignupResponse", - }, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { email, school } = req.body as HubWaitlistSignupSchema; + "/signup", + route({ + requestBody: "HubWaitlistSignupSchema", + responses: { + 200: { + body: "HubWaitlistSignupResponse", + }, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { email, school } = req.body as HubWaitlistSignupSchema; - res.json({ - email, - email_domain: email.split("@")[1], - school, - user_id: req.user_id, - } as HubWaitlistSignupResponse); - }, + res.json({ + email, + email_domain: email.split("@")[1], + school, + user_id: req.user_id, + } as HubWaitlistSignupResponse); + }, ); export default router; diff --git a/src/api/routes/interactions/#interaction_id/#interaction_token/callback.ts b/src/api/routes/interactions/#interaction_id/#interaction_token/callback.ts
index 442ce0ef..e94d455a 100644 --- a/src/api/routes/interactions/#interaction_id/#interaction_token/callback.ts +++ b/src/api/routes/interactions/#interaction_id/#interaction_token/callback.ts
@@ -26,79 +26,79 @@ import { sendMessage } from "../../../../util/handlers/Message"; const router = Router({ mergeParams: true }); router.post("/", route({}), async (req: Request, res: Response) => { - const body = req.body as InteractionCallbackSchema; + const body = req.body as InteractionCallbackSchema; - const errors: Record<string, { code?: string; message: string }> = {}; - const knownComponentIds: string[] = []; + const errors: Record<string, { code?: string; message: string }> = {}; + const knownComponentIds: string[] = []; - for (const row of body.data.components || []) { - if (!row.components) { - continue; - } + for (const row of body.data.components || []) { + if (!row.components) { + continue; + } - if (row.components.length < 1 || row.components.length > 5) { - errors[`data.components[${body.data.components!.indexOf(row)}].components`] = { - code: "BASE_TYPE_BAD_LENGTH", - message: `Must be between 1 and 5 in length.`, - }; - } + if (row.components.length < 1 || row.components.length > 5) { + errors[`data.components[${body.data.components!.indexOf(row)}].components`] = { + code: "BASE_TYPE_BAD_LENGTH", + message: `Must be between 1 and 5 in length.`, + }; + } - for (const component of row.components) { - if (component.type == MessageComponentType.Button && component.style != ButtonStyle.Link) { - if (component.custom_id?.trim() === "") { - errors[`data.components[${body.data.components!.indexOf(row)}].components[${row.components.indexOf(component)}].custom_id`] = { - code: "BUTTON_COMPONENT_CUSTOM_ID_REQUIRED", - message: "A custom id required", - }; - } + for (const component of row.components) { + if (component.type == MessageComponentType.Button && component.style != ButtonStyle.Link) { + if (component.custom_id?.trim() === "") { + errors[`data.components[${body.data.components!.indexOf(row)}].components[${row.components.indexOf(component)}].custom_id`] = { + code: "BUTTON_COMPONENT_CUSTOM_ID_REQUIRED", + message: "A custom id required", + }; + } - if (knownComponentIds.includes(component.custom_id!)) { - errors[`data.components[${body.data.components!.indexOf(row)}].components[${row.components.indexOf(component)}].custom_id`] = { - code: "COMPONENT_CUSTOM_ID_DUPLICATED", - message: "Component custom id cannot be duplicated", - }; - } else { - knownComponentIds.push(component.custom_id!); - } - } - } - } + if (knownComponentIds.includes(component.custom_id!)) { + errors[`data.components[${body.data.components!.indexOf(row)}].components[${row.components.indexOf(component)}].custom_id`] = { + code: "COMPONENT_CUSTOM_ID_DUPLICATED", + message: "Component custom id cannot be duplicated", + }; + } else { + knownComponentIds.push(component.custom_id!); + } + } + } + } - if (Object.keys(errors).length > 0) { - throw FieldErrors(errors); - } + if (Object.keys(errors).length > 0) { + throw FieldErrors(errors); + } - const interactionId = req.params.interaction_id; - const interaction = pendingInteractions.get(req.params.interaction_id); + const interactionId = req.params.interaction_id; + const interaction = pendingInteractions.get(req.params.interaction_id); - if (!interaction) { - return; - } + if (!interaction) { + return; + } - clearTimeout(interaction.timeout); + clearTimeout(interaction.timeout); - emitEvent({ - event: "INTERACTION_SUCCESS", - user_id: interaction?.userId, - data: { - id: interactionId, - nonce: interaction?.nonce, - }, - } as InteractionSuccessEvent); + emitEvent({ + event: "INTERACTION_SUCCESS", + user_id: interaction?.userId, + data: { + id: interactionId, + nonce: interaction?.nonce, + }, + } as InteractionSuccessEvent); - switch (body.type) { - case InteractionCallbackType.PONG: - // TODO - break; - case InteractionCallbackType.ACKNOWLEDGE: - // Deprected - break; - case InteractionCallbackType.CHANNEL_MESSAGE: - // TODO - break; - case InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE: { - const user = await User.findOneOrFail({ where: { id: interaction.userId } }); - /* + switch (body.type) { + case InteractionCallbackType.PONG: + // TODO + break; + case InteractionCallbackType.ACKNOWLEDGE: + // Deprected + break; + case InteractionCallbackType.CHANNEL_MESSAGE: + // TODO + break; + case InteractionCallbackType.CHANNEL_MESSAGE_WITH_SOURCE: { + const user = await User.findOneOrFail({ where: { id: interaction.userId } }); + /* const files = (req.files as Express.Multer.File[]) ?? []; //I don't think traditional attachments are allowed anyways const attachments: (Attachment | MessageCreateAttachment | MessageCreateCloudAttachment)[] = []; @@ -111,71 +111,71 @@ router.post("/", route({}), async (req: Request, res: Response) => { } } */ - await sendMessage({ - type: MessageType.APPLICATION_COMMAND, - timestamp: new Date(), - application_id: interaction.applicationId, - channel_id: interaction.channelId, - author_id: interaction.applicationId, - nonce: interaction.nonce, - content: body.data.content, - components: body.data.components || [], - tts: body.data.tts, - embeds: body.data.embeds || [], - attachments: body.data.attachments, - poll: body.data.poll, - flags: body.data.flags, - reactions: [], - // webhook_id: interaction.applicationId, // This one requires a webhook to be created first - interaction: { - id: interactionId, - name: interaction.commandName, - type: 2, - user, - }, - interaction_metadata: { - id: interactionId, - type: 2, - user_id: interaction.userId, - user, - authorizing_integration_owners: { - "1": interaction.userId, - }, - name: interaction.commandName, - command_type: interaction.commandType, - }, - }); + await sendMessage({ + type: MessageType.APPLICATION_COMMAND, + timestamp: new Date(), + application_id: interaction.applicationId, + channel_id: interaction.channelId, + author_id: interaction.applicationId, + nonce: interaction.nonce, + content: body.data.content, + components: body.data.components || [], + tts: body.data.tts, + embeds: body.data.embeds || [], + attachments: body.data.attachments, + poll: body.data.poll, + flags: body.data.flags, + reactions: [], + // webhook_id: interaction.applicationId, // This one requires a webhook to be created first + interaction: { + id: interactionId, + name: interaction.commandName, + type: 2, + user, + }, + interaction_metadata: { + id: interactionId, + type: 2, + user_id: interaction.userId, + user, + authorizing_integration_owners: { + "1": interaction.userId, + }, + name: interaction.commandName, + command_type: interaction.commandType, + }, + }); - break; - } - case InteractionCallbackType.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE: - // TODO - break; - case InteractionCallbackType.DEFERRED_UPDATE_MESSAGE: - // TODO - break; - case InteractionCallbackType.UPDATE_MESSAGE: - // TODO - break; - case InteractionCallbackType.APPLICATION_COMMAND_AUTOCOMPLETE_RESULT: - // TODO - break; - case InteractionCallbackType.MODAL: - // TODO - break; - case InteractionCallbackType.PREMIUM_REQUIRED: - // Deprecated - break; - case InteractionCallbackType.IFRAME_MODAL: - // TODO - break; - case InteractionCallbackType.LAUNCH_ACTIVITY: - // TODO - break; - } + break; + } + case InteractionCallbackType.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE: + // TODO + break; + case InteractionCallbackType.DEFERRED_UPDATE_MESSAGE: + // TODO + break; + case InteractionCallbackType.UPDATE_MESSAGE: + // TODO + break; + case InteractionCallbackType.APPLICATION_COMMAND_AUTOCOMPLETE_RESULT: + // TODO + break; + case InteractionCallbackType.MODAL: + // TODO + break; + case InteractionCallbackType.PREMIUM_REQUIRED: + // Deprecated + break; + case InteractionCallbackType.IFRAME_MODAL: + // TODO + break; + case InteractionCallbackType.LAUNCH_ACTIVITY: + // TODO + break; + } - pendingInteractions.delete(interactionId); - res.sendStatus(204); + pendingInteractions.delete(interactionId); + res.sendStatus(204); }); export default router; diff --git a/src/api/routes/interactions/index.ts b/src/api/routes/interactions/index.ts
index 9f154395..eb516ced 100644 --- a/src/api/routes/interactions/index.ts +++ b/src/api/routes/interactions/index.ts
@@ -27,104 +27,104 @@ import { InteractionCreateSchema } from "@spacebar/schemas/api/bots/InteractionC const router = Router({ mergeParams: true }); router.post("/", route({}), async (req: Request, res: Response) => { - const body = req.body as InteractionSchema; + const body = req.body as InteractionSchema; - const interactionId = Snowflake.generate(); - const interactionToken = randomBytes(24).toString("base64url"); + const interactionId = Snowflake.generate(); + const interactionToken = randomBytes(24).toString("base64url"); - emitEvent({ - event: "INTERACTION_CREATE", - user_id: req.user_id, - data: { - id: interactionId, - nonce: body.nonce, - }, - } as InteractionCreateEvent); + emitEvent({ + event: "INTERACTION_CREATE", + user_id: req.user_id, + data: { + id: interactionId, + nonce: body.nonce, + }, + } as InteractionCreateEvent); - const user = await User.findOneOrFail({ where: { id: req.user_id } }); + const user = await User.findOneOrFail({ where: { id: req.user_id } }); - const interactionData: Partial<InteractionCreateSchema> = { - id: interactionId, - application_id: body.application_id, - channel_id: body.channel_id, - type: body.type, - token: interactionToken, - version: 1, - entitlements: [], - authorizing_integration_owners: { "0": req.user_id }, - attachment_size_limit: Config.get().cdn.maxAttachmentSize, - }; + const interactionData: Partial<InteractionCreateSchema> = { + id: interactionId, + application_id: body.application_id, + channel_id: body.channel_id, + type: body.type, + token: interactionToken, + version: 1, + entitlements: [], + authorizing_integration_owners: { "0": req.user_id }, + attachment_size_limit: Config.get().cdn.maxAttachmentSize, + }; - if (body.type === InteractionType.ApplicationCommand || body.type === InteractionType.MessageComponent || body.type === InteractionType.ModalSubmit) { - interactionData.data = body.data; - } + if (body.type === InteractionType.ApplicationCommand || body.type === InteractionType.MessageComponent || body.type === InteractionType.ModalSubmit) { + interactionData.data = body.data; + } - if (body.type != InteractionType.Ping) { - interactionData.locale = user?.settings?.locale; - } + if (body.type != InteractionType.Ping) { + interactionData.locale = user?.settings?.locale; + } - if (body.guild_id) { - interactionData.context = 0; - interactionData.guild_id = body.guild_id; - interactionData.app_permissions = (await getPermission(body.application_id, body.guild_id, body.channel_id)).bitfield.toString(); + if (body.guild_id) { + interactionData.context = 0; + interactionData.guild_id = body.guild_id; + interactionData.app_permissions = (await getPermission(body.application_id, body.guild_id, body.channel_id)).bitfield.toString(); - const guild = await Guild.findOneOrFail({ where: { id: body.guild_id } }); - const member = await Member.findOneOrFail({ where: { guild_id: body.guild_id, id: req.user_id }, relations: ["user"] }); + const guild = await Guild.findOneOrFail({ where: { id: body.guild_id } }); + const member = await Member.findOneOrFail({ where: { guild_id: body.guild_id, id: req.user_id }, relations: ["user"] }); - interactionData.guild = { - id: guild.id, - features: guild.features, - locale: guild.preferred_locale!, - }; + interactionData.guild = { + id: guild.id, + features: guild.features, + locale: guild.preferred_locale!, + }; - interactionData.guild_locale = guild.preferred_locale; - interactionData.member = member.toPublicMember(); - } else { - interactionData.user = user.toPublicUser(); - interactionData.app_permissions = (await getPermission(body.application_id, "", body.channel_id)).bitfield.toString(); + interactionData.guild_locale = guild.preferred_locale; + interactionData.member = member.toPublicMember(); + } else { + interactionData.user = user.toPublicUser(); + interactionData.app_permissions = (await getPermission(body.application_id, "", body.channel_id)).bitfield.toString(); - if (body.channel_id === body.application_id) { - interactionData.context = 1; - } else { - interactionData.context = 2; - } - } + if (body.channel_id === body.application_id) { + interactionData.context = 1; + } else { + interactionData.context = 2; + } + } - if (body.type === InteractionType.MessageComponent || body.data.type === InteractionType.ModalSubmit) { - interactionData.message = await Message.findOneOrFail({ where: { id: body.message_id, flags: undefined }, relations: ["author"] }); - } + if (body.type === InteractionType.MessageComponent || body.data.type === InteractionType.ModalSubmit) { + interactionData.message = await Message.findOneOrFail({ where: { id: body.message_id, flags: undefined }, relations: ["author"] }); + } - emitEvent({ - event: "INTERACTION_CREATE", - user_id: body.application_id, - data: interactionData, - } as InteractionCreateEvent); + emitEvent({ + event: "INTERACTION_CREATE", + user_id: body.application_id, + data: interactionData, + } as InteractionCreateEvent); - const interactionTimeout = setTimeout(() => { - emitEvent({ - event: "INTERACTION_FAILURE", - user_id: req.user_id, - data: { - id: interactionId, - nonce: body.nonce, - reason_code: 2, // when types are done: InteractionFailureReason.TIMEOUT, - }, - } as InteractionFailureEvent); - }, 3000); + const interactionTimeout = setTimeout(() => { + emitEvent({ + event: "INTERACTION_FAILURE", + user_id: req.user_id, + data: { + id: interactionId, + nonce: body.nonce, + reason_code: 2, // when types are done: InteractionFailureReason.TIMEOUT, + }, + } as InteractionFailureEvent); + }, 3000); - pendingInteractions.set(interactionId, { - timeout: interactionTimeout, - nonce: body.nonce, - applicationId: body.application_id, - userId: req.user_id, - guildId: body.guild_id, - channelId: body.channel_id, - type: body.type, - commandType: body.data.type, - commandName: body.data.name, - }); + pendingInteractions.set(interactionId, { + timeout: interactionTimeout, + nonce: body.nonce, + applicationId: body.application_id, + userId: req.user_id, + guildId: body.guild_id, + channelId: body.channel_id, + type: body.type, + commandType: body.data.type, + commandName: body.data.name, + }); - res.sendStatus(204); + res.sendStatus(204); }); export default router; diff --git a/src/api/routes/invites/index.ts b/src/api/routes/invites/index.ts
index 87715f35..26b0ad90 100644 --- a/src/api/routes/invites/index.ts +++ b/src/api/routes/invites/index.ts
@@ -25,134 +25,134 @@ import { UserFlags } from "@spacebar/schemas"; const router: Router = Router({ mergeParams: true }); router.get( - "/:invite_code", - route({ - responses: { - "200": { - body: "Invite", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { invite_code } = req.params; + "/:invite_code", + route({ + responses: { + "200": { + body: "Invite", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { invite_code } = req.params; - const invite = await Invite.findOneOrFail({ - where: { code: invite_code }, - relations: PublicInviteRelation, - }); + const invite = await Invite.findOneOrFail({ + where: { code: invite_code }, + relations: PublicInviteRelation, + }); - res.status(200).send(invite); - }, + res.status(200).send(invite); + }, ); router.post( - "/:invite_code", - route({ - right: "USE_MASS_INVITES", - responses: { - "200": { - body: "Invite", - }, - 401: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - if (req.user_bot) throw DiscordApiErrors.BOT_PROHIBITED_ENDPOINT; + "/:invite_code", + route({ + right: "USE_MASS_INVITES", + responses: { + "200": { + body: "Invite", + }, + 401: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + if (req.user_bot) throw DiscordApiErrors.BOT_PROHIBITED_ENDPOINT; - const { invite_code } = req.params; - const { guild_id } = await Invite.findOneOrFail({ - where: { code: invite_code }, - }); - const { features } = await Guild.findOneOrFail({ - where: { id: guild_id }, - }); - const { public_flags } = await User.findOneOrFail({ - where: { id: req.user_id }, - }); - const ban = await Ban.findOne({ - where: [ - { guild_id: guild_id, user_id: req.user_id }, - { guild_id: guild_id, ip: req.ip }, - ], - }); + const { invite_code } = req.params; + const { guild_id } = await Invite.findOneOrFail({ + where: { code: invite_code }, + }); + const { features } = await Guild.findOneOrFail({ + where: { id: guild_id }, + }); + const { public_flags } = await User.findOneOrFail({ + where: { id: req.user_id }, + }); + const ban = await Ban.findOne({ + where: [ + { guild_id: guild_id, user_id: req.user_id }, + { guild_id: guild_id, ip: req.ip }, + ], + }); - if (ban) { - console.log(`[Invite] User ${req.user_id} tried to join guild ${guild_id} but is banned by ${ban.user_id === req.user_id ? "User ID" : "IP address"}.`); - throw DiscordApiErrors.USER_BANNED; - } + if (ban) { + console.log(`[Invite] User ${req.user_id} tried to join guild ${guild_id} but is banned by ${ban.user_id === req.user_id ? "User ID" : "IP address"}.`); + throw DiscordApiErrors.USER_BANNED; + } - if ((BigInt(public_flags) & UserFlags.FLAGS.QUARANTINED) === UserFlags.FLAGS.QUARANTINED) { - console.log(`[Invite] User ${req.user_id} tried to join guild ${guild_id} but is quarantined.`); - throw DiscordApiErrors.UNKNOWN_INVITE; - } + if ((BigInt(public_flags) & UserFlags.FLAGS.QUARANTINED) === UserFlags.FLAGS.QUARANTINED) { + console.log(`[Invite] User ${req.user_id} tried to join guild ${guild_id} but is quarantined.`); + throw DiscordApiErrors.UNKNOWN_INVITE; + } - if (features.includes("INTERNAL_EMPLOYEE_ONLY") && (public_flags & 1) !== 1) { - console.log(`[Invite] User ${req.user_id} tried to join guild ${guild_id} but is not staff.`); - throw new HTTPError("Only intended for the staff of this instance.", 401); - } + if (features.includes("INTERNAL_EMPLOYEE_ONLY") && (public_flags & 1) !== 1) { + console.log(`[Invite] User ${req.user_id} tried to join guild ${guild_id} but is not staff.`); + throw new HTTPError("Only intended for the staff of this instance.", 401); + } - if (features.includes("INVITES_DISABLED")) { - console.log(`[Invite] User ${req.user_id} tried to join guild ${guild_id} but joins are closed.`); - throw new HTTPError("Sorry, this guild has joins closed.", 403); - } + if (features.includes("INVITES_DISABLED")) { + console.log(`[Invite] User ${req.user_id} tried to join guild ${guild_id} but joins are closed.`); + throw new HTTPError("Sorry, this guild has joins closed.", 403); + } - const invite = await Invite.joinGuild(req.user_id, invite_code); + const invite = await Invite.joinGuild(req.user_id, invite_code); - res.json(invite); - }, + res.json(invite); + }, ); // * cant use permission of route() function because path doesn't have guild_id/channel_id router.delete( - "/:invite_code", - route({ - responses: { - "200": { - body: "Invite", - }, - 401: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { invite_code } = req.params; - const invite = await Invite.findOneOrFail({ where: { code: invite_code } }); - const { guild_id, channel_id } = invite; + "/:invite_code", + route({ + responses: { + "200": { + body: "Invite", + }, + 401: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { invite_code } = req.params; + const invite = await Invite.findOneOrFail({ where: { code: invite_code } }); + const { guild_id, channel_id } = invite; - const permission = await getPermission(req.user_id, guild_id, channel_id); + const permission = await getPermission(req.user_id, guild_id, channel_id); - if (!permission.has("MANAGE_GUILD") && !permission.has("MANAGE_CHANNELS")) throw new HTTPError("You missing the MANAGE_GUILD or MANAGE_CHANNELS permission", 401); + if (!permission.has("MANAGE_GUILD") && !permission.has("MANAGE_CHANNELS")) throw new HTTPError("You missing the MANAGE_GUILD or MANAGE_CHANNELS permission", 401); - await Promise.all([ - Invite.delete({ code: invite_code }), - emitEvent({ - event: "INVITE_DELETE", - guild_id: guild_id, - data: { - channel_id: channel_id, - guild_id: guild_id, - code: invite_code, - }, - } as InviteDeleteEvent), - ]); + await Promise.all([ + Invite.delete({ code: invite_code }), + emitEvent({ + event: "INVITE_DELETE", + guild_id: guild_id, + data: { + channel_id: channel_id, + guild_id: guild_id, + code: invite_code, + }, + } as InviteDeleteEvent), + ]); - res.json({ invite: invite }); - }, + res.json({ invite: invite }); + }, ); export default router; diff --git a/src/api/routes/oauth2/applications/@me.ts b/src/api/routes/oauth2/applications/@me.ts
index 6daad797..9cdd47c0 100644 --- a/src/api/routes/oauth2/applications/@me.ts +++ b/src/api/routes/oauth2/applications/@me.ts
@@ -24,30 +24,30 @@ import { PublicUserProjection } from "@spacebar/schemas"; const router: Router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "Application", - }, - }, - }), - async (req: Request, res: Response) => { - const app = await Application.findOneOrFail({ - where: { id: req.params.id }, // ...huh? there's no ID in the path... - relations: ["bot", "owner"], - select: { - owner: Object.fromEntries(PublicUserProjection.map((x) => [x, true])), - }, - }); + "/", + route({ + responses: { + 200: { + body: "Application", + }, + }, + }), + async (req: Request, res: Response) => { + const app = await Application.findOneOrFail({ + where: { id: req.params.id }, // ...huh? there's no ID in the path... + relations: ["bot", "owner"], + select: { + owner: Object.fromEntries(PublicUserProjection.map((x) => [x, true])), + }, + }); - if (!app.bot) throw DiscordApiErrors.BOT_ONLY_ENDPOINT; + if (!app.bot) throw DiscordApiErrors.BOT_ONLY_ENDPOINT; - res.json({ - ...app, - owner: app.owner.toPublicUser(), - install_params: app.install_params !== null ? app.install_params : undefined, - }); - }, + res.json({ + ...app, + owner: app.owner.toPublicUser(), + install_params: app.install_params !== null ? app.install_params : undefined, + }); + }, ); export default router; diff --git a/src/api/routes/oauth2/authorize.ts b/src/api/routes/oauth2/authorize.ts
index 4084a059..4dea75c6 100644 --- a/src/api/routes/oauth2/authorize.ts +++ b/src/api/routes/oauth2/authorize.ts
@@ -25,198 +25,198 @@ const router = Router({ mergeParams: true }); // TODO: scopes, other oauth types router.get( - "/", - route({ - query: { - client_id: { - type: "string", - }, - }, - responses: { - // TODO: I really didn't feel like typing all of it out - 200: {}, - 400: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - // const { client_id, scope, response_type, redirect_url } = req.query; - const { client_id } = req.query; - if (!client_id) { - throw FieldErrors({ - client_id: { - code: "BASE_TYPE_REQUIRED", - message: req.t("common:field.BASE_TYPE_REQUIRED"), - }, - }); - } + "/", + route({ + query: { + client_id: { + type: "string", + }, + }, + responses: { + // TODO: I really didn't feel like typing all of it out + 200: {}, + 400: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + // const { client_id, scope, response_type, redirect_url } = req.query; + const { client_id } = req.query; + if (!client_id) { + throw FieldErrors({ + client_id: { + code: "BASE_TYPE_REQUIRED", + message: req.t("common:field.BASE_TYPE_REQUIRED"), + }, + }); + } - const app = await Application.findOne({ - where: { - id: client_id as string, - }, - relations: ["bot"], - }); + const app = await Application.findOne({ + where: { + id: client_id as string, + }, + relations: ["bot"], + }); - // TODO: use DiscordApiErrors - // findOneOrFail throws code 404 - if (!app) throw DiscordApiErrors.UNKNOWN_APPLICATION; - if (!app.bot) throw DiscordApiErrors.OAUTH2_APPLICATION_BOT_ABSENT; + // TODO: use DiscordApiErrors + // findOneOrFail throws code 404 + if (!app) throw DiscordApiErrors.UNKNOWN_APPLICATION; + if (!app.bot) throw DiscordApiErrors.OAUTH2_APPLICATION_BOT_ABSENT; - const bot = app.bot; - delete app.bot; + const bot = app.bot; + delete app.bot; - const user = await User.findOneOrFail({ - where: { - id: req.user_id, - bot: false, - }, - select: ["id", "username", "avatar", "discriminator", "public_flags"], - }); + const user = await User.findOneOrFail({ + where: { + id: req.user_id, + bot: false, + }, + select: ["id", "username", "avatar", "discriminator", "public_flags"], + }); - const guilds = await Member.find({ - where: { - id: req.user_id, - }, - relations: ["guild", "roles", "user"], - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - //@ts-ignore - // prettier-ignore - select: ["guild.id", "guild.name", "guild.icon", "guild.mfa_level", "guild.owner_id", "roles.id", "user.flags"], - }); + const guilds = await Member.find({ + where: { + id: req.user_id, + }, + relations: ["guild", "roles", "user"], + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + // prettier-ignore + select: ["guild.id", "guild.name", "guild.icon", "guild.mfa_level", "guild.owner_id", "roles.id", "user.flags"], + }); - const guildsWithPermissions = guilds.map((x) => { - const perms = Permissions.finalPermission({ - user: { - id: user.id, - roles: x.roles?.map((x) => x.id) || [], - communication_disabled_until: x.communication_disabled_until, - flags: x.user.flags, - }, - guild: { - roles: x?.roles || [], - id: x.guild.id, - owner_id: x.guild.owner_id!, // ownerless guilds...? - }, - }); + const guildsWithPermissions = guilds.map((x) => { + const perms = Permissions.finalPermission({ + user: { + id: user.id, + roles: x.roles?.map((x) => x.id) || [], + communication_disabled_until: x.communication_disabled_until, + flags: x.user.flags, + }, + guild: { + roles: x?.roles || [], + id: x.guild.id, + owner_id: x.guild.owner_id!, // ownerless guilds...? + }, + }); - return { - id: x.guild.id, - name: x.guild.name, - icon: x.guild.icon, - mfa_level: x.guild.mfa_level, - permissions: perms.bitfield.toString(), - }; - }); + return { + id: x.guild.id, + name: x.guild.name, + icon: x.guild.icon, + mfa_level: x.guild.mfa_level, + permissions: perms.bitfield.toString(), + }; + }); - return res.json({ - guilds: guildsWithPermissions, - user: { - id: user.id, - username: user.username, - avatar: user.avatar, - avatar_decoration: null, // TODO - discriminator: user.discriminator, - public_flags: user.public_flags, - }, - application: { - id: app.id, - name: app.name, - icon: app.icon, - description: app.description, - summary: app.summary, - type: app.type, - hook: app.hook, - guild_id: null, // TODO support guilds - bot_public: app.bot_public, - bot_require_code_grant: app.bot_require_code_grant, - verify_key: app.verify_key, - flags: app.flags, - }, - bot: { - id: bot.id, - username: bot.username, - avatar: bot.avatar, - avatar_decoration: null, // TODO - discriminator: bot.discriminator, - public_flags: bot.public_flags, - bot: true, - approximated_guild_count: 0, // TODO - }, - authorized: false, - }); - }, + return res.json({ + guilds: guildsWithPermissions, + user: { + id: user.id, + username: user.username, + avatar: user.avatar, + avatar_decoration: null, // TODO + discriminator: user.discriminator, + public_flags: user.public_flags, + }, + application: { + id: app.id, + name: app.name, + icon: app.icon, + description: app.description, + summary: app.summary, + type: app.type, + hook: app.hook, + guild_id: null, // TODO support guilds + bot_public: app.bot_public, + bot_require_code_grant: app.bot_require_code_grant, + verify_key: app.verify_key, + flags: app.flags, + }, + bot: { + id: bot.id, + username: bot.username, + avatar: bot.avatar, + avatar_decoration: null, // TODO + discriminator: bot.discriminator, + public_flags: bot.public_flags, + bot: true, + approximated_guild_count: 0, // TODO + }, + authorized: false, + }); + }, ); router.post( - "/", - route({ - requestBody: "ApplicationAuthorizeSchema", - query: { - client_id: { - type: "string", - }, - }, - responses: { - 200: { - body: "OAuthAuthorizeResponse", - }, - 400: { - body: "APIErrorResponse", - }, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const body = req.body as ApplicationAuthorizeSchema; - // const { client_id, scope, response_type, redirect_url } = req.query; - const { client_id } = req.query; + "/", + route({ + requestBody: "ApplicationAuthorizeSchema", + query: { + client_id: { + type: "string", + }, + }, + responses: { + 200: { + body: "OAuthAuthorizeResponse", + }, + 400: { + body: "APIErrorResponse", + }, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const body = req.body as ApplicationAuthorizeSchema; + // const { client_id, scope, response_type, redirect_url } = req.query; + const { client_id } = req.query; - if (!client_id) { - throw FieldErrors({ - client_id: { - code: "BASE_TYPE_REQUIRED", - message: req.t("common:field.BASE_TYPE_REQUIRED"), - }, - }); - } + if (!client_id) { + throw FieldErrors({ + client_id: { + code: "BASE_TYPE_REQUIRED", + message: req.t("common:field.BASE_TYPE_REQUIRED"), + }, + }); + } - // TODO: ensure guild_id is not an empty string - // TODO: captcha verification - // TODO: MFA verification + // TODO: ensure guild_id is not an empty string + // TODO: captcha verification + // TODO: MFA verification - const perms = await getPermission(req.user_id, body.guild_id, undefined, { member_relations: ["user"] }); - // getPermission cache won't exist if we're owner - if (Object.keys(perms.cache || {}).length > 0 && perms.cache.member?.user.bot) throw DiscordApiErrors.UNAUTHORIZED; - perms.hasThrow("MANAGE_GUILD"); + const perms = await getPermission(req.user_id, body.guild_id, undefined, { member_relations: ["user"] }); + // getPermission cache won't exist if we're owner + if (Object.keys(perms.cache || {}).length > 0 && perms.cache.member?.user.bot) throw DiscordApiErrors.UNAUTHORIZED; + perms.hasThrow("MANAGE_GUILD"); - const app = await Application.findOne({ - where: { - id: client_id as string, - }, - relations: ["bot"], - }); + const app = await Application.findOne({ + where: { + id: client_id as string, + }, + relations: ["bot"], + }); - // TODO: use DiscordApiErrors - // findOneOrFail throws code 404 - if (!app) throw new ApiError("Unknown Application", 10002, 404); - if (!app.bot) throw new ApiError("OAuth2 application does not have a bot", 50010, 400); + // TODO: use DiscordApiErrors + // findOneOrFail throws code 404 + if (!app) throw new ApiError("Unknown Application", 10002, 404); + if (!app.bot) throw new ApiError("OAuth2 application does not have a bot", 50010, 400); - await Member.addToGuild(app.id, body.guild_id); + await Member.addToGuild(app.id, body.guild_id); - return res.json({ - location: "/oauth2/authorized", // redirect URL - }); - }, + return res.json({ + location: "/oauth2/authorized", // redirect URL + }); + }, ); export default router; diff --git a/src/api/routes/oauth2/tokens.ts b/src/api/routes/oauth2/tokens.ts
index 6b85bab5..4af10611 100644 --- a/src/api/routes/oauth2/tokens.ts +++ b/src/api/routes/oauth2/tokens.ts
@@ -21,8 +21,8 @@ import { route } from "@spacebar/api"; const router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - //TODO - res.json([]); + //TODO + res.json([]); }); export default router; diff --git a/src/api/routes/outbound-promotions.ts b/src/api/routes/outbound-promotions.ts
index f1f9a292..e8e6f657 100644 --- a/src/api/routes/outbound-promotions.ts +++ b/src/api/routes/outbound-promotions.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/partners/#guild_id/requirements.ts b/src/api/routes/partners/#guild_id/requirements.ts
index 30ec8b3f..9a08bc6b 100644 --- a/src/api/routes/partners/#guild_id/requirements.ts +++ b/src/api/routes/partners/#guild_id/requirements.ts
@@ -22,34 +22,34 @@ import { route } from "@spacebar/api"; const router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - const { guild_id } = req.params; - // TODO: - // Load from database - // Admin control, but for now it allows anyone to be discoverable + const { guild_id } = req.params; + // TODO: + // Load from database + // Admin control, but for now it allows anyone to be discoverable - res.send({ - guild_id: guild_id, - safe_environment: true, - healthy: true, - health_score_pending: false, - size: true, - nsfw_properties: {}, - protected: true, - sufficient: true, - sufficient_without_grace_period: true, - valid_rules_channel: true, - retention_healthy: true, - engagement_healthy: true, - age: true, - minimum_age: 0, - health_score: { - avg_nonnew_participators: 0, - avg_nonnew_communicators: 0, - num_intentful_joiners: 0, - perc_ret_w1_intentful: 0, - }, - minimum_size: 0, - }); + res.send({ + guild_id: guild_id, + safe_environment: true, + healthy: true, + health_score_pending: false, + size: true, + nsfw_properties: {}, + protected: true, + sufficient: true, + sufficient_without_grace_period: true, + valid_rules_channel: true, + retention_healthy: true, + engagement_healthy: true, + age: true, + minimum_age: 0, + health_score: { + avg_nonnew_participators: 0, + avg_nonnew_communicators: 0, + num_intentful_joiners: 0, + perc_ret_w1_intentful: 0, + }, + minimum_size: 0, + }); }); export default router; diff --git a/src/api/routes/ping.ts b/src/api/routes/ping.ts
index 55e4b7d5..be737f84 100644 --- a/src/api/routes/ping.ts +++ b/src/api/routes/ping.ts
@@ -23,32 +23,32 @@ import { Request, Response, Router } from "express"; const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "InstancePingResponse", - }, - }, - }), - (req: Request, res: Response) => { - const { general } = Config.get(); - res.send({ - ping: "pong!", - instance: { - id: general.instanceId, - name: general.instanceName, - description: general.instanceDescription, - image: general.image, + "/", + route({ + responses: { + 200: { + body: "InstancePingResponse", + }, + }, + }), + (req: Request, res: Response) => { + const { general } = Config.get(); + res.send({ + ping: "pong!", + instance: { + id: general.instanceId, + name: general.instanceName, + description: general.instanceDescription, + image: general.image, - correspondenceEmail: general.correspondenceEmail, - correspondenceUserID: general.correspondenceUserID, + correspondenceEmail: general.correspondenceEmail, + correspondenceUserID: general.correspondenceUserID, - frontPage: general.frontPage, - tosPage: general.tosPage, - }, - }); - }, + frontPage: general.frontPage, + tosPage: general.tosPage, + }, + }); + }, ); export default router; diff --git a/src/api/routes/policies/instance/config.ts b/src/api/routes/policies/instance/config.ts
index 97fb5093..946ac8aa 100755 --- a/src/api/routes/policies/instance/config.ts +++ b/src/api/routes/policies/instance/config.ts
@@ -23,44 +23,44 @@ import { Request, Response, Router } from "express"; const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "Object", - }, - }, - }), - async (req: Request, res: Response) => { - const general = Config.get(); - let outputtedConfig; - if (req.user_id) { - const rights = await getRights(req.user_id); - if (rights.has("OPERATOR")) outputtedConfig = general; - } else { - outputtedConfig = { - limits_user_maxGuilds: general.limits.user.maxGuilds, - limits_user_maxBio: general.limits.user.maxBio, - limits_guild_maxEmojis: general.limits.guild.maxEmojis, - limits_guild_maxRoles: general.limits.guild.maxRoles, - limits_message_maxCharacters: general.limits.message.maxCharacters, - limits_message_maxAttachmentSize: general.limits.message.maxAttachmentSize, - limits_message_maxEmbedDownloadSize: general.limits.message.maxEmbedDownloadSize, - limits_channel_maxWebhooks: general.limits.channel.maxWebhooks, - register_dateOfBirth_requiredc: general.register.dateOfBirth.required, - register_password_required: general.register.password.required, - register_disabled: general.register.disabled, - register_requireInvite: general.register.requireInvite, - register_allowNewRegistration: general.register.allowNewRegistration, - register_allowMultipleAccounts: general.register.allowMultipleAccounts, - guild_autoJoin_canLeave: general.guild.autoJoin.canLeave, - guild_autoJoin_guilds_x: general.guild.autoJoin.guilds, - register_email_required: general.register.email.required, - can_recover_account: general.email.provider != null && general.general.frontPage != null, - }; - } - res.send(outputtedConfig); - }, + "/", + route({ + responses: { + 200: { + body: "Object", + }, + }, + }), + async (req: Request, res: Response) => { + const general = Config.get(); + let outputtedConfig; + if (req.user_id) { + const rights = await getRights(req.user_id); + if (rights.has("OPERATOR")) outputtedConfig = general; + } else { + outputtedConfig = { + limits_user_maxGuilds: general.limits.user.maxGuilds, + limits_user_maxBio: general.limits.user.maxBio, + limits_guild_maxEmojis: general.limits.guild.maxEmojis, + limits_guild_maxRoles: general.limits.guild.maxRoles, + limits_message_maxCharacters: general.limits.message.maxCharacters, + limits_message_maxAttachmentSize: general.limits.message.maxAttachmentSize, + limits_message_maxEmbedDownloadSize: general.limits.message.maxEmbedDownloadSize, + limits_channel_maxWebhooks: general.limits.channel.maxWebhooks, + register_dateOfBirth_requiredc: general.register.dateOfBirth.required, + register_password_required: general.register.password.required, + register_disabled: general.register.disabled, + register_requireInvite: general.register.requireInvite, + register_allowNewRegistration: general.register.allowNewRegistration, + register_allowMultipleAccounts: general.register.allowMultipleAccounts, + guild_autoJoin_canLeave: general.guild.autoJoin.canLeave, + guild_autoJoin_guilds_x: general.guild.autoJoin.guilds, + register_email_required: general.register.email.required, + can_recover_account: general.email.provider != null && general.general.frontPage != null, + }; + } + res.send(outputtedConfig); + }, ); export default router; diff --git a/src/api/routes/policies/instance/domains.ts b/src/api/routes/policies/instance/domains.ts
index 967ac2f4..7aaaeb59 100644 --- a/src/api/routes/policies/instance/domains.ts +++ b/src/api/routes/policies/instance/domains.ts
@@ -22,26 +22,26 @@ import { Request, Response, Router } from "express"; const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "InstanceDomainsResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const { cdn, gateway, api } = Config.get(); + "/", + route({ + responses: { + 200: { + body: "InstanceDomainsResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const { cdn, gateway, api } = Config.get(); - res.json({ - admin: Config.get().admin.endpointPublic, - api: Config.get().api.endpointPublic?.split("/api")[0] || "", // Transitional, see /.well-known/spacebar/client - apiEndpoint: api.endpointPublic, - cdn: cdn.endpointPublic, - defaultApiVersion: api.defaultVersion, - gateway: gateway.endpointPublic, - }); - }, + res.json({ + admin: Config.get().admin.endpointPublic, + api: Config.get().api.endpointPublic?.split("/api")[0] || "", // Transitional, see /.well-known/spacebar/client + apiEndpoint: api.endpointPublic, + cdn: cdn.endpointPublic, + defaultApiVersion: api.defaultVersion, + gateway: gateway.endpointPublic, + }); + }, ); export default router; diff --git a/src/api/routes/policies/instance/index.ts b/src/api/routes/policies/instance/index.ts
index 8fc214f8..a54570f7 100644 --- a/src/api/routes/policies/instance/index.ts +++ b/src/api/routes/policies/instance/index.ts
@@ -22,18 +22,18 @@ import { Request, Response, Router } from "express"; const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "APIGeneralConfiguration", - }, - }, - }), - async (req: Request, res: Response) => { - const { general } = Config.get(); - res.json(general); - }, + "/", + route({ + responses: { + 200: { + body: "APIGeneralConfiguration", + }, + }, + }), + async (req: Request, res: Response) => { + const { general } = Config.get(); + res.json(general); + }, ); export default router; diff --git a/src/api/routes/policies/instance/limits.ts b/src/api/routes/policies/instance/limits.ts
index 0d0ed326..7ade1519 100644 --- a/src/api/routes/policies/instance/limits.ts +++ b/src/api/routes/policies/instance/limits.ts
@@ -22,18 +22,18 @@ import { Request, Response, Router } from "express"; const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "APILimitsConfiguration", - }, - }, - }), - async (req: Request, res: Response) => { - const { limits } = Config.get(); - res.json(limits); - }, + "/", + route({ + responses: { + 200: { + body: "APILimitsConfiguration", + }, + }, + }), + async (req: Request, res: Response) => { + const { limits } = Config.get(); + res.json(limits); + }, ); export default router; diff --git a/src/api/routes/policies/stats.ts b/src/api/routes/policies/stats.ts
index 0828c37c..0edcfd68 100644 --- a/src/api/routes/policies/stats.ts +++ b/src/api/routes/policies/stats.ts
@@ -22,32 +22,32 @@ import { Request, Response, Router } from "express"; const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "InstanceStatsResponse", - }, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - if (!Config.get().security.statsWorldReadable) { - const rights = await getRights(req.user_id); - rights.hasThrow("VIEW_SERVER_STATS"); - } + "/", + route({ + responses: { + 200: { + body: "InstanceStatsResponse", + }, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + if (!Config.get().security.statsWorldReadable) { + const rights = await getRights(req.user_id); + rights.hasThrow("VIEW_SERVER_STATS"); + } - res.json({ - counts: { - user: await User.count(), - guild: await Guild.count(), - message: await Message.count(), - members: await Member.count(), - }, - }); - }, + res.json({ + counts: { + user: await User.count(), + guild: await Guild.count(), + message: await Message.count(), + members: await Member.count(), + }, + }); + }, ); export default router; diff --git a/src/api/routes/read-states/ack-bulk.ts b/src/api/routes/read-states/ack-bulk.ts
index a597e4bc..d0212c0b 100644 --- a/src/api/routes/read-states/ack-bulk.ts +++ b/src/api/routes/read-states/ack-bulk.ts
@@ -23,48 +23,48 @@ import { AckBulkSchema } from "@spacebar/schemas"; const router = Router({ mergeParams: true }); router.post( - "/", - route({ - requestBody: "AckBulkSchema", - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const body = req.body as AckBulkSchema; + "/", + route({ + requestBody: "AckBulkSchema", + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const body = req.body as AckBulkSchema; - // TODO: what is read_state_type ? + // TODO: what is read_state_type ? - await Promise.all([ - // for every new state - ...body.read_states.map(async (x) => { - // find an existing one - const ret = - (await ReadState.findOne({ - where: { - user_id: req.user_id, - channel_id: x.channel_id, - }, - })) ?? - // if it doesn't exist, create it (not a promise) - ReadState.create({ - user_id: req.user_id, - channel_id: x.channel_id, - }); + await Promise.all([ + // for every new state + ...body.read_states.map(async (x) => { + // find an existing one + const ret = + (await ReadState.findOne({ + where: { + user_id: req.user_id, + channel_id: x.channel_id, + }, + })) ?? + // if it doesn't exist, create it (not a promise) + ReadState.create({ + user_id: req.user_id, + channel_id: x.channel_id, + }); - ret.last_message_id = x.message_id; - //It's a little more complicated than this but this'll do - ret.mention_count = 0; + ret.last_message_id = x.message_id; + //It's a little more complicated than this but this'll do + ret.mention_count = 0; - return ret.save(); - }), - ]); + return ret.save(); + }), + ]); - return res.sendStatus(204); - }, + return res.sendStatus(204); + }, ); export default router; diff --git a/src/api/routes/reporting/menu/message.ts b/src/api/routes/reporting/menu/message.ts
index c72bc840..de57c794 100644 --- a/src/api/routes/reporting/menu/message.ts +++ b/src/api/routes/reporting/menu/message.ts
@@ -22,19 +22,19 @@ import { Request, Response, Router } from "express"; const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "ReportingMenuResponse", - }, - 204: {}, - }, - }), - (req: Request, res: Response) => { - // TODO: implement - //res.send([] as ReportingMenuResponseSchema); - }, + "/", + route({ + responses: { + 200: { + body: "ReportingMenuResponse", + }, + 204: {}, + }, + }), + (req: Request, res: Response) => { + // TODO: implement + //res.send([] as ReportingMenuResponseSchema); + }, ); export default router; diff --git a/src/api/routes/safety-hub/@me/index.ts b/src/api/routes/safety-hub/@me/index.ts
index 06a1e285..7d0e7ff9 100644 --- a/src/api/routes/safety-hub/@me/index.ts +++ b/src/api/routes/safety-hub/@me/index.ts
@@ -24,36 +24,36 @@ import { AccountStandingResponse, AccountStandingState, AppealEligibility } from const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "AccountStandingResponse", - }, - 401: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const user = await User.findOneOrFail({ - where: { id: req.user_id }, - select: ["data"], - }); + "/", + route({ + responses: { + 200: { + body: "AccountStandingResponse", + }, + 401: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const user = await User.findOneOrFail({ + where: { id: req.user_id }, + select: ["data"], + }); - res.send({ - classifications: [], - guild_classifications: [], - account_standing: { - state: AccountStandingState.ALL_GOOD, - }, - is_dsa_eligible: true, - username: user.username, - discriminator: user.discriminator, - is_appeal_eligible: true, - appeal_eligibility: [AppealEligibility.DSA_ELIGIBLE, AppealEligibility.IN_APP_ELIGIBLE, AppealEligibility.AGE_VERIFY_ELIGIBLE], - } as AccountStandingResponse); - }, + res.send({ + classifications: [], + guild_classifications: [], + account_standing: { + state: AccountStandingState.ALL_GOOD, + }, + is_dsa_eligible: true, + username: user.username, + discriminator: user.discriminator, + is_appeal_eligible: true, + appeal_eligibility: [AppealEligibility.DSA_ELIGIBLE, AppealEligibility.IN_APP_ELIGIBLE, AppealEligibility.AGE_VERIFY_ELIGIBLE], + } as AccountStandingResponse); + }, ); export default router; diff --git a/src/api/routes/safety-hub/suspended/@me.ts b/src/api/routes/safety-hub/suspended/@me.ts
index 06a1e285..7d0e7ff9 100644 --- a/src/api/routes/safety-hub/suspended/@me.ts +++ b/src/api/routes/safety-hub/suspended/@me.ts
@@ -24,36 +24,36 @@ import { AccountStandingResponse, AccountStandingState, AppealEligibility } from const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "AccountStandingResponse", - }, - 401: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const user = await User.findOneOrFail({ - where: { id: req.user_id }, - select: ["data"], - }); + "/", + route({ + responses: { + 200: { + body: "AccountStandingResponse", + }, + 401: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const user = await User.findOneOrFail({ + where: { id: req.user_id }, + select: ["data"], + }); - res.send({ - classifications: [], - guild_classifications: [], - account_standing: { - state: AccountStandingState.ALL_GOOD, - }, - is_dsa_eligible: true, - username: user.username, - discriminator: user.discriminator, - is_appeal_eligible: true, - appeal_eligibility: [AppealEligibility.DSA_ELIGIBLE, AppealEligibility.IN_APP_ELIGIBLE, AppealEligibility.AGE_VERIFY_ELIGIBLE], - } as AccountStandingResponse); - }, + res.send({ + classifications: [], + guild_classifications: [], + account_standing: { + state: AccountStandingState.ALL_GOOD, + }, + is_dsa_eligible: true, + username: user.username, + discriminator: user.discriminator, + is_appeal_eligible: true, + appeal_eligibility: [AppealEligibility.DSA_ELIGIBLE, AppealEligibility.IN_APP_ELIGIBLE, AppealEligibility.AGE_VERIFY_ELIGIBLE], + } as AccountStandingResponse); + }, ); export default router; diff --git a/src/api/routes/scheduled-maintenances/upcoming.json.ts b/src/api/routes/scheduled-maintenances/upcoming.json.ts
index de4dcbf1..5958d582 100644 --- a/src/api/routes/scheduled-maintenances/upcoming.json.ts +++ b/src/api/routes/scheduled-maintenances/upcoming.json.ts
@@ -21,10 +21,10 @@ import { route } from "@spacebar/api"; const router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - res.json({ - page: {}, - scheduled_maintenances: {}, - }); + res.json({ + page: {}, + scheduled_maintenances: {}, + }); }); export default router; diff --git a/src/api/routes/science.ts b/src/api/routes/science.ts
index 2d2d5195..4a6ddeab 100644 --- a/src/api/routes/science.ts +++ b/src/api/routes/science.ts
@@ -22,16 +22,16 @@ import { Request, Response, Router } from "express"; const router = Router({ mergeParams: true }); router.post( - "/", - route({ - responses: { - 204: {}, - }, - }), - (req: Request, res: Response) => { - // TODO: - res.sendStatus(204); - }, + "/", + route({ + responses: { + 204: {}, + }, + }), + (req: Request, res: Response) => { + // TODO: + res.sendStatus(204); + }, ); export default router; diff --git a/src/api/routes/stage-instances.ts b/src/api/routes/stage-instances.ts
index f1f9a292..e8e6f657 100644 --- a/src/api/routes/stage-instances.ts +++ b/src/api/routes/stage-instances.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/sticker-packs/index.ts b/src/api/routes/sticker-packs/index.ts
index 7d2fe178..749ce026 100644 --- a/src/api/routes/sticker-packs/index.ts +++ b/src/api/routes/sticker-packs/index.ts
@@ -23,21 +23,21 @@ import { Request, Response, Router } from "express"; const router: Router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "APIStickerPackArray", - }, - }, - }), - async (req: Request, res: Response) => { - const sticker_packs = await StickerPack.find({ - relations: ["stickers"], - }); + "/", + route({ + responses: { + 200: { + body: "APIStickerPackArray", + }, + }, + }), + async (req: Request, res: Response) => { + const sticker_packs = await StickerPack.find({ + relations: ["stickers"], + }); - res.json({ sticker_packs }); - }, + res.json({ sticker_packs }); + }, ); export default router; diff --git a/src/api/routes/stickers/#sticker_id/index.ts b/src/api/routes/stickers/#sticker_id/index.ts
index ddbc3978..0f850efd 100644 --- a/src/api/routes/stickers/#sticker_id/index.ts +++ b/src/api/routes/stickers/#sticker_id/index.ts
@@ -22,19 +22,19 @@ import { Request, Response, Router } from "express"; const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "Sticker", - }, - }, - }), - async (req: Request, res: Response) => { - const { sticker_id } = req.params; + "/", + route({ + responses: { + 200: { + body: "Sticker", + }, + }, + }), + async (req: Request, res: Response) => { + const { sticker_id } = req.params; - res.json(await Sticker.find({ where: { id: sticker_id } })); - }, + res.json(await Sticker.find({ where: { id: sticker_id } })); + }, ); export default router; diff --git a/src/api/routes/stop.ts b/src/api/routes/stop.ts
index bf4af562..be29e0ad 100644 --- a/src/api/routes/stop.ts +++ b/src/api/routes/stop.ts
@@ -22,21 +22,21 @@ import { Request, Response, Router } from "express"; const router: Router = Router({ mergeParams: true }); router.post( - "/", - route({ - right: "OPERATOR", - responses: { - 200: {}, - 403: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - console.log(`/stop was called by ${req.user_id} at ${new Date()}`); - res.sendStatus(200); - process.kill(process.pid, "SIGTERM"); - }, + "/", + route({ + right: "OPERATOR", + responses: { + 200: {}, + 403: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + console.log(`/stop was called by ${req.user_id} at ${new Date()}`); + res.sendStatus(200); + process.kill(process.pid, "SIGTERM"); + }, ); export default router; diff --git a/src/api/routes/store/published-listings/applications/#application_id/index.ts b/src/api/routes/store/published-listings/applications/#application_id/index.ts
index eb2df64e..39fa9db2 100644 --- a/src/api/routes/store/published-listings/applications/#application_id/index.ts +++ b/src/api/routes/store/published-listings/applications/#application_id/index.ts
@@ -22,76 +22,76 @@ import { route } from "@spacebar/api"; const router: Router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - //TODO - // const id = req.params.id; - res.json({ - id: "", - summary: "", - sku: { - id: "", - type: 1, - dependent_sku_id: null, - application_id: "", - manifest_labels: [], - access_type: 2, - name: "", - features: [], - release_date: "", - premium: false, - slug: "", - flags: 4, - genres: [], - legal_notice: "", - application: { - id: "", - name: "", - icon: "", - description: "", - summary: "", - cover_image: "", - primary_sku_id: "", - hook: true, - slug: "", - guild_id: "", - bot_public: "", - bot_require_code_grant: false, - verify_key: "", - publishers: [ - { - id: "", - name: "", - }, - ], - developers: [ - { - id: "", - name: "", - }, - ], - system_requirements: {}, - show_age_gate: false, - price: { - amount: 0, - currency: "EUR", - }, - locales: [], - }, - tagline: "", - description: "", - carousel_items: [ - { - asset_id: "", - }, - ], - header_logo_dark_theme: {}, //{id: "", size: 4665, mime_type: "image/gif", width 160, height: 160} - header_logo_light_theme: {}, - box_art: {}, - thumbnail: {}, - header_background: {}, - hero_background: {}, - assets: [], - }, - }).status(200); + //TODO + // const id = req.params.id; + res.json({ + id: "", + summary: "", + sku: { + id: "", + type: 1, + dependent_sku_id: null, + application_id: "", + manifest_labels: [], + access_type: 2, + name: "", + features: [], + release_date: "", + premium: false, + slug: "", + flags: 4, + genres: [], + legal_notice: "", + application: { + id: "", + name: "", + icon: "", + description: "", + summary: "", + cover_image: "", + primary_sku_id: "", + hook: true, + slug: "", + guild_id: "", + bot_public: "", + bot_require_code_grant: false, + verify_key: "", + publishers: [ + { + id: "", + name: "", + }, + ], + developers: [ + { + id: "", + name: "", + }, + ], + system_requirements: {}, + show_age_gate: false, + price: { + amount: 0, + currency: "EUR", + }, + locales: [], + }, + tagline: "", + description: "", + carousel_items: [ + { + asset_id: "", + }, + ], + header_logo_dark_theme: {}, //{id: "", size: 4665, mime_type: "image/gif", width 160, height: 160} + header_logo_light_theme: {}, + box_art: {}, + thumbnail: {}, + header_background: {}, + hero_background: {}, + assets: [], + }, + }).status(200); }); export default router; diff --git a/src/api/routes/store/published-listings/applications/#application_id/subscription-plans.ts b/src/api/routes/store/published-listings/applications/#application_id/subscription-plans.ts
index b8f5963f..953e3919 100644 --- a/src/api/routes/store/published-listings/applications/#application_id/subscription-plans.ts +++ b/src/api/routes/store/published-listings/applications/#application_id/subscription-plans.ts
@@ -22,22 +22,22 @@ import { route } from "@spacebar/api"; const router: Router = Router({ mergeParams: true }); router.get("/", route({}), async (req: Request, res: Response) => { - //TODO - res.json([ - { - id: "", - name: "", - interval: 1, - interval_count: 1, - tax_inclusive: true, - sku_id: "", - fallback_price: 499, - fallback_currency: "eur", - currency: "eur", - price: 4199, - price_tier: null, - }, - ]).status(200); + //TODO + res.json([ + { + id: "", + name: "", + interval: 1, + interval_count: 1, + tax_inclusive: true, + sku_id: "", + fallback_price: 499, + fallback_currency: "eur", + currency: "eur", + price: 4199, + price_tier: null, + }, + ]).status(200); }); export default router; diff --git a/src/api/routes/store/published-listings/skus.ts b/src/api/routes/store/published-listings/skus.ts
index a463b4e4..03cbab46 100644 --- a/src/api/routes/store/published-listings/skus.ts +++ b/src/api/routes/store/published-listings/skus.ts
@@ -22,76 +22,76 @@ import { route } from "@spacebar/api"; const router: Router = Router({ mergeParams: true }); router.get("/:sku_id", route({}), async (req: Request, res: Response) => { - //TODO - // const id = req.params.id; - res.json({ - id: "", - summary: "", - sku: { - id: "", - type: 1, - dependent_sku_id: null, - application_id: "", - manifets_labels: [], - access_type: 2, - name: "", - features: [], - release_date: "", - premium: false, - slug: "", - flags: 4, - genres: [], - legal_notice: "", - application: { - id: "", - name: "", - icon: "", - description: "", - summary: "", - cover_image: "", - primary_sku_id: "", - hook: true, - slug: "", - guild_id: "", - bot_public: "", - bot_require_code_grant: false, - verify_key: "", - publishers: [ - { - id: "", - name: "", - }, - ], - developers: [ - { - id: "", - name: "", - }, - ], - system_requirements: {}, - show_age_gate: false, - price: { - amount: 0, - currency: "EUR", - }, - locales: [], - }, - tagline: "", - description: "", - carousel_items: [ - { - asset_id: "", - }, - ], - header_logo_dark_theme: {}, //{id: "", size: 4665, mime_type: "image/gif", width 160, height: 160} - header_logo_light_theme: {}, - box_art: {}, - thumbnail: {}, - header_background: {}, - hero_background: {}, - assets: [], - }, - }).status(200); + //TODO + // const id = req.params.id; + res.json({ + id: "", + summary: "", + sku: { + id: "", + type: 1, + dependent_sku_id: null, + application_id: "", + manifets_labels: [], + access_type: 2, + name: "", + features: [], + release_date: "", + premium: false, + slug: "", + flags: 4, + genres: [], + legal_notice: "", + application: { + id: "", + name: "", + icon: "", + description: "", + summary: "", + cover_image: "", + primary_sku_id: "", + hook: true, + slug: "", + guild_id: "", + bot_public: "", + bot_require_code_grant: false, + verify_key: "", + publishers: [ + { + id: "", + name: "", + }, + ], + developers: [ + { + id: "", + name: "", + }, + ], + system_requirements: {}, + show_age_gate: false, + price: { + amount: 0, + currency: "EUR", + }, + locales: [], + }, + tagline: "", + description: "", + carousel_items: [ + { + asset_id: "", + }, + ], + header_logo_dark_theme: {}, //{id: "", size: 4665, mime_type: "image/gif", width 160, height: 160} + header_logo_light_theme: {}, + box_art: {}, + thumbnail: {}, + header_background: {}, + hero_background: {}, + assets: [], + }, + }).status(200); }); export default router; diff --git a/src/api/routes/store/published-listings/skus/#sku_id/subscription-plans.ts b/src/api/routes/store/published-listings/skus/#sku_id/subscription-plans.ts
index f2ea23cc..0aae1495 100644 --- a/src/api/routes/store/published-listings/skus/#sku_id/subscription-plans.ts +++ b/src/api/routes/store/published-listings/skus/#sku_id/subscription-plans.ts
@@ -22,310 +22,310 @@ import { route } from "@spacebar/api"; const router: Router = Router({ mergeParams: true }); const skus = new Map([ - [ - "521842865731534868", - [ - { - id: "511651856145973248", - name: "Individual Premium Tier 3 Monthly (Legacy)", - interval: 1, - interval_count: 1, - tax_inclusive: true, - sku_id: "521842865731534868", - currency: "eur", - price: 0, - price_tier: null, - }, - { - id: "511651860671627264", - name: "Individiual Premium Tier 3 Yearly (Legacy)", - interval: 2, - interval_count: 1, - tax_inclusive: true, - sku_id: "521842865731534868", - currency: "eur", - price: 0, - price_tier: null, - }, - ], - ], - [ - "521846918637420545", - [ - { - id: "511651871736201216", - name: "Individual Premium Tier 2 Monthly", - interval: 1, - interval_count: 1, - tax_inclusive: true, - sku_id: "521846918637420545", - currency: "eur", - price: 0, - price_tier: null, - }, - { - id: "511651876987469824", - name: "Individual Premum Tier 2 Yearly", - interval: 2, - interval_count: 1, - tax_inclusive: true, - sku_id: "521846918637420545", - currency: "eur", - price: 0, - price_tier: null, - }, - { - id: "978380684370378761", - name: "Individual Premum Tier 1", - interval: 2, - interval_count: 1, - tax_inclusive: true, - sku_id: "521846918637420545", - currency: "eur", - price: 0, - price_tier: null, - }, - ], - ], - [ - "521847234246082599", - [ - { - id: "642251038925127690", - name: "Individual Premium Tier 3 Quarterly", - interval: 1, - interval_count: 3, - tax_inclusive: true, - sku_id: "521847234246082599", - currency: "eur", - price: 0, - price_tier: null, - }, - { - id: "511651880837840896", - name: "Individual Premium Tier 3 Monthly", - interval: 1, - interval_count: 1, - tax_inclusive: true, - sku_id: "521847234246082599", - currency: "eur", - price: 0, - price_tier: null, - }, - { - id: "511651885459963904", - name: "Individual Premium Tier 3 Yearly", - interval: 2, - interval_count: 1, - tax_inclusive: true, - sku_id: "521847234246082599", - currency: "eur", - price: 0, - price_tier: null, - }, - ], - ], - [ - "590663762298667008", - [ - { - id: "590665532894740483", - name: "Crowd Premium Monthly", - interval: 1, - interval_count: 1, - tax_inclusive: true, - sku_id: "590663762298667008", - discount_price: 0, - currency: "eur", - price: 0, - price_tier: null, - }, - { - id: "590665538238152709", - name: "Crowd Premium Yearly", - interval: 2, - interval_count: 1, - tax_inclusive: true, - sku_id: "590663762298667008", - discount_price: 0, - currency: "eur", - price: 0, - price_tier: null, - }, - ], - ], - [ - "978380684370378762", - [ - [ - { - id: "978380692553465866", - name: "Premium Tier 0 Monthly", - interval: 1, - interval_count: 1, - tax_inclusive: true, - sku_id: "978380684370378762", - currency: "usd", - price: 299, - price_tier: null, - prices: { - "0": { - country_prices: { - country_code: "US", - prices: [ - { - currency: "usd", - amount: 0, - exponent: 2, - }, - ], - }, - payment_source_prices: { - "775487223059316758": [ - { - currency: "usd", - amount: 0, - exponent: 2, - }, - ], - "736345864146255982": [ - { - currency: "usd", - amount: 0, - exponent: 2, - }, - ], - "683074999590060249": [ - { - currency: "usd", - amount: 0, - exponent: 2, - }, - ], - }, - }, - "3": { - country_prices: { - country_code: "US", - prices: [ - { - currency: "usd", - amount: 0, - exponent: 2, - }, - ], - }, - payment_source_prices: { - "775487223059316758": [ - { - currency: "usd", - amount: 0, - exponent: 2, - }, - ], - "736345864146255982": [ - { - currency: "usd", - amount: 0, - exponent: 2, - }, - ], - "683074999590060249": [ - { - currency: "usd", - amount: 0, - exponent: 2, - }, - ], - }, - }, - "4": { - country_prices: { - country_code: "US", - prices: [ - { - currency: "usd", - amount: 0, - exponent: 2, - }, - ], - }, - payment_source_prices: { - "775487223059316758": [ - { - currency: "usd", - amount: 0, - exponent: 2, - }, - ], - "736345864146255982": [ - { - currency: "usd", - amount: 0, - exponent: 2, - }, - ], - "683074999590060249": [ - { - currency: "usd", - amount: 0, - exponent: 2, - }, - ], - }, - }, - "1": { - country_prices: { - country_code: "US", - prices: [ - { - currency: "usd", - amount: 0, - exponent: 2, - }, - ], - }, - payment_source_prices: { - "775487223059316758": [ - { - currency: "usd", - amount: 0, - exponent: 2, - }, - ], - "736345864146255982": [ - { - currency: "usd", - amount: 0, - exponent: 2, - }, - ], - "683074999590060249": [ - { - currency: "usd", - amount: 0, - exponent: 2, - }, - ], - }, - }, - }, - }, - ], - ], - ], + [ + "521842865731534868", + [ + { + id: "511651856145973248", + name: "Individual Premium Tier 3 Monthly (Legacy)", + interval: 1, + interval_count: 1, + tax_inclusive: true, + sku_id: "521842865731534868", + currency: "eur", + price: 0, + price_tier: null, + }, + { + id: "511651860671627264", + name: "Individiual Premium Tier 3 Yearly (Legacy)", + interval: 2, + interval_count: 1, + tax_inclusive: true, + sku_id: "521842865731534868", + currency: "eur", + price: 0, + price_tier: null, + }, + ], + ], + [ + "521846918637420545", + [ + { + id: "511651871736201216", + name: "Individual Premium Tier 2 Monthly", + interval: 1, + interval_count: 1, + tax_inclusive: true, + sku_id: "521846918637420545", + currency: "eur", + price: 0, + price_tier: null, + }, + { + id: "511651876987469824", + name: "Individual Premum Tier 2 Yearly", + interval: 2, + interval_count: 1, + tax_inclusive: true, + sku_id: "521846918637420545", + currency: "eur", + price: 0, + price_tier: null, + }, + { + id: "978380684370378761", + name: "Individual Premum Tier 1", + interval: 2, + interval_count: 1, + tax_inclusive: true, + sku_id: "521846918637420545", + currency: "eur", + price: 0, + price_tier: null, + }, + ], + ], + [ + "521847234246082599", + [ + { + id: "642251038925127690", + name: "Individual Premium Tier 3 Quarterly", + interval: 1, + interval_count: 3, + tax_inclusive: true, + sku_id: "521847234246082599", + currency: "eur", + price: 0, + price_tier: null, + }, + { + id: "511651880837840896", + name: "Individual Premium Tier 3 Monthly", + interval: 1, + interval_count: 1, + tax_inclusive: true, + sku_id: "521847234246082599", + currency: "eur", + price: 0, + price_tier: null, + }, + { + id: "511651885459963904", + name: "Individual Premium Tier 3 Yearly", + interval: 2, + interval_count: 1, + tax_inclusive: true, + sku_id: "521847234246082599", + currency: "eur", + price: 0, + price_tier: null, + }, + ], + ], + [ + "590663762298667008", + [ + { + id: "590665532894740483", + name: "Crowd Premium Monthly", + interval: 1, + interval_count: 1, + tax_inclusive: true, + sku_id: "590663762298667008", + discount_price: 0, + currency: "eur", + price: 0, + price_tier: null, + }, + { + id: "590665538238152709", + name: "Crowd Premium Yearly", + interval: 2, + interval_count: 1, + tax_inclusive: true, + sku_id: "590663762298667008", + discount_price: 0, + currency: "eur", + price: 0, + price_tier: null, + }, + ], + ], + [ + "978380684370378762", + [ + [ + { + id: "978380692553465866", + name: "Premium Tier 0 Monthly", + interval: 1, + interval_count: 1, + tax_inclusive: true, + sku_id: "978380684370378762", + currency: "usd", + price: 299, + price_tier: null, + prices: { + "0": { + country_prices: { + country_code: "US", + prices: [ + { + currency: "usd", + amount: 0, + exponent: 2, + }, + ], + }, + payment_source_prices: { + "775487223059316758": [ + { + currency: "usd", + amount: 0, + exponent: 2, + }, + ], + "736345864146255982": [ + { + currency: "usd", + amount: 0, + exponent: 2, + }, + ], + "683074999590060249": [ + { + currency: "usd", + amount: 0, + exponent: 2, + }, + ], + }, + }, + "3": { + country_prices: { + country_code: "US", + prices: [ + { + currency: "usd", + amount: 0, + exponent: 2, + }, + ], + }, + payment_source_prices: { + "775487223059316758": [ + { + currency: "usd", + amount: 0, + exponent: 2, + }, + ], + "736345864146255982": [ + { + currency: "usd", + amount: 0, + exponent: 2, + }, + ], + "683074999590060249": [ + { + currency: "usd", + amount: 0, + exponent: 2, + }, + ], + }, + }, + "4": { + country_prices: { + country_code: "US", + prices: [ + { + currency: "usd", + amount: 0, + exponent: 2, + }, + ], + }, + payment_source_prices: { + "775487223059316758": [ + { + currency: "usd", + amount: 0, + exponent: 2, + }, + ], + "736345864146255982": [ + { + currency: "usd", + amount: 0, + exponent: 2, + }, + ], + "683074999590060249": [ + { + currency: "usd", + amount: 0, + exponent: 2, + }, + ], + }, + }, + "1": { + country_prices: { + country_code: "US", + prices: [ + { + currency: "usd", + amount: 0, + exponent: 2, + }, + ], + }, + payment_source_prices: { + "775487223059316758": [ + { + currency: "usd", + amount: 0, + exponent: 2, + }, + ], + "736345864146255982": [ + { + currency: "usd", + amount: 0, + exponent: 2, + }, + ], + "683074999590060249": [ + { + currency: "usd", + amount: 0, + exponent: 2, + }, + ], + }, + }, + }, + }, + ], + ], + ], ]); router.get("/", route({}), async (req: Request, res: Response) => { - // TODO: add the ability to add custom - const { sku_id } = req.params; + // TODO: add the ability to add custom + const { sku_id } = req.params; - if (!skus.has(sku_id)) { - console.log(`Request for invalid SKU ${sku_id}! Please report this!`); - res.sendStatus(404); - } else { - res.json(skus.get(sku_id)).status(200); - } + if (!skus.has(sku_id)) { + console.log(`Request for invalid SKU ${sku_id}! Please report this!`); + res.sendStatus(404); + } else { + res.json(skus.get(sku_id)).status(200); + } }); export default router; diff --git a/src/api/routes/teams.ts b/src/api/routes/teams.ts
index 21ec3a5d..27ccabf2 100644 --- a/src/api/routes/teams.ts +++ b/src/api/routes/teams.ts
@@ -25,67 +25,67 @@ import { TeamCreateSchema, TeamMemberRole, TeamMemberState } from "@spacebar/sch const router: Router = Router({ mergeParams: true }); router.get( - "/", - route({ - query: { - include_payout_account_status: { - type: "boolean", - description: "Whether to include team payout account status in the response (default false)", - }, - }, - responses: { - 200: { - body: "TeamListResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const teams = await Team.find({ - where: { - owner_user_id: req.user_id, - }, - relations: ["members"], - }); + "/", + route({ + query: { + include_payout_account_status: { + type: "boolean", + description: "Whether to include team payout account status in the response (default false)", + }, + }, + responses: { + 200: { + body: "TeamListResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const teams = await Team.find({ + where: { + owner_user_id: req.user_id, + }, + relations: ["members"], + }); - res.send(teams); - }, + res.send(teams); + }, ); router.post( - "/", - route({ - requestBody: "TeamCreateSchema", - responses: { - 200: { - body: "Team", - }, - }, - }), - async (req: Request, res: Response) => { - const user = await User.findOneOrFail({ - where: [{ id: req.user_id }], - select: ["mfa_enabled"], - }); - if (!user.mfa_enabled) throw new HTTPError("You must enable MFA to create a team"); + "/", + route({ + requestBody: "TeamCreateSchema", + responses: { + 200: { + body: "Team", + }, + }, + }), + async (req: Request, res: Response) => { + const user = await User.findOneOrFail({ + where: [{ id: req.user_id }], + select: ["mfa_enabled"], + }); + if (!user.mfa_enabled) throw new HTTPError("You must enable MFA to create a team"); - const body = req.body as TeamCreateSchema; + const body = req.body as TeamCreateSchema; - const team = Team.create({ - name: body.name, - owner_user_id: req.user_id, - }); - await team.save(); + const team = Team.create({ + name: body.name, + owner_user_id: req.user_id, + }); + await team.save(); - await TeamMember.create({ - user_id: req.user_id, - team_id: team.id, - membership_state: TeamMemberState.ACCEPTED, - permissions: ["*"], - role: TeamMemberRole.ADMIN, - }).save(); + await TeamMember.create({ + user_id: req.user_id, + team_id: team.id, + membership_state: TeamMemberState.ACCEPTED, + permissions: ["*"], + role: TeamMemberRole.ADMIN, + }).save(); - res.json(team); - }, + res.json(team); + }, ); export default router; diff --git a/src/api/routes/track.ts b/src/api/routes/track.ts
index 644302cd..d3206138 100644 --- a/src/api/routes/track.ts +++ b/src/api/routes/track.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/updates.ts b/src/api/routes/updates.ts
index 5262dc2d..7228a761 100644 --- a/src/api/routes/updates.ts +++ b/src/api/routes/updates.ts
@@ -23,46 +23,46 @@ import { Request, Response, Router } from "express"; const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "UpdatesResponse", - }, - 400: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const platform = req.query.platform; + "/", + route({ + responses: { + 200: { + body: "UpdatesResponse", + }, + 400: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const platform = req.query.platform; - if (!platform) - throw FieldErrors({ - platform: { - code: "BASE_TYPE_REQUIRED", - message: req.t("common:field.BASE_TYPE_REQUIRED"), - }, - }); + if (!platform) + throw FieldErrors({ + platform: { + code: "BASE_TYPE_REQUIRED", + message: req.t("common:field.BASE_TYPE_REQUIRED"), + }, + }); - const release = await ClientRelease.findOneOrFail({ - where: { - enabled: true, - platform: platform as string, - }, - order: { pub_date: "DESC" }, - }); + const release = await ClientRelease.findOneOrFail({ + where: { + enabled: true, + platform: platform as string, + }, + order: { pub_date: "DESC" }, + }); - res.json({ - name: release.name, - pub_date: release.pub_date, - url: release.url, - notes: release.notes, - }); - }, + res.json({ + name: release.name, + pub_date: release.pub_date, + url: release.url, + notes: release.notes, + }); + }, ); export default router; diff --git a/src/api/routes/users/#user_id/delete.ts b/src/api/routes/users/#user_id/delete.ts
index be361f18..c2286cea 100644 --- a/src/api/routes/users/#user_id/delete.ts +++ b/src/api/routes/users/#user_id/delete.ts
@@ -18,20 +18,20 @@ import { route } from "@spacebar/api"; import { - Channel, - ChannelDeleteEvent, - ChannelRecipientRemoveEvent, - emitEvent, - Emoji, - Guild, - InstanceBan, - Member, - Recipient, - Sticker, - Stopwatch, - User, - UserDeleteEvent, - UserSettingsProtos, + Channel, + ChannelDeleteEvent, + ChannelRecipientRemoveEvent, + emitEvent, + Emoji, + Guild, + InstanceBan, + Member, + Recipient, + Sticker, + Stopwatch, + User, + UserDeleteEvent, + UserSettingsProtos, } from "@spacebar/util"; import { Request, Response, Router } from "express"; import { ChannelType, InstanceUserDeleteSchema, PrivateUserProjection } from "@spacebar/schemas"; @@ -40,158 +40,158 @@ import { Not } from "typeorm"; const router = Router({ mergeParams: true }); router.post( - "/", - route({ - right: "MANAGE_USERS", - requestBody: "InstanceUserDeleteSchema", - responses: { - 204: {}, - 403: { - body: "APIErrorResponse", - }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const sw = Stopwatch.startNew(); - const body = req.body as InstanceUserDeleteSchema | undefined; - const user = await User.findOneOrFail({ - where: { id: req.params.user_id }, - select: [...PrivateUserProjection, "data"], - }); + "/", + route({ + right: "MANAGE_USERS", + requestBody: "InstanceUserDeleteSchema", + responses: { + 204: {}, + 403: { + body: "APIErrorResponse", + }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const sw = Stopwatch.startNew(); + const body = req.body as InstanceUserDeleteSchema | undefined; + const user = await User.findOneOrFail({ + where: { id: req.params.user_id }, + select: [...PrivateUserProjection, "data"], + }); - if ((body?.persistInstanceBan ?? true) && !(await InstanceBan.findOne({ where: { user_id: user.id } }))) - await InstanceBan.create({ user_id: user.id, reason: body?.reason ?? "<legacy instance ban API - no reason specified>" }).save(); + if ((body?.persistInstanceBan ?? true) && !(await InstanceBan.findOne({ where: { user_id: user.id } }))) + await InstanceBan.create({ user_id: user.id, reason: body?.reason ?? "<legacy instance ban API - no reason specified>" }).save(); - // prevent bugginess with clients - delete all DMs, only having half of the conversation is quite useless anyhow - const dmChannels = await user.getDmChannels(); - for (const channel of dmChannels) { - console.log(`[Instance ban] Deleting DM channel ${channel.id} for user ${user.id}`); - await emitEvent({ - event: "CHANNEL_DELETE", - data: channel.toJSON(), - channel_id: channel.id, - } as ChannelDeleteEvent); - await Recipient.delete({ channel_id: channel.id }); - await Channel.deleteChannel(channel); - } + // prevent bugginess with clients - delete all DMs, only having half of the conversation is quite useless anyhow + const dmChannels = await user.getDmChannels(); + for (const channel of dmChannels) { + console.log(`[Instance ban] Deleting DM channel ${channel.id} for user ${user.id}`); + await emitEvent({ + event: "CHANNEL_DELETE", + data: channel.toJSON(), + channel_id: channel.id, + } as ChannelDeleteEvent); + await Recipient.delete({ channel_id: channel.id }); + await Channel.deleteChannel(channel); + } - //leave all group channels - const groupChannels = await Channel.find({ - where: { type: ChannelType.GROUP_DM }, - relations: ["recipients"], - select: { - id: true, - owner_id: true, - recipients: { - id: true, - user_id: true, - }, - }, - }); + //leave all group channels + const groupChannels = await Channel.find({ + where: { type: ChannelType.GROUP_DM }, + relations: ["recipients"], + select: { + id: true, + owner_id: true, + recipients: { + id: true, + user_id: true, + }, + }, + }); - await Promise.all( - groupChannels.map(async (channel) => { - const recipient = channel.recipients!.find((r) => r.user_id === user.id); - if (recipient) { - await Recipient.delete({ id: recipient.id }); - await emitEvent({ - event: "CHANNEL_RECIPIENT_REMOVE", - data: { - user: user.toPublicUser(), - channel_id: channel.id, - }, - channel_id: channel.id, - } as ChannelRecipientRemoveEvent); - console.log(`[Instance ban] Removed user ${user.id} from group channel ${channel.id}`); - } + await Promise.all( + groupChannels.map(async (channel) => { + const recipient = channel.recipients!.find((r) => r.user_id === user.id); + if (recipient) { + await Recipient.delete({ id: recipient.id }); + await emitEvent({ + event: "CHANNEL_RECIPIENT_REMOVE", + data: { + user: user.toPublicUser(), + channel_id: channel.id, + }, + channel_id: channel.id, + } as ChannelRecipientRemoveEvent); + console.log(`[Instance ban] Removed user ${user.id} from group channel ${channel.id}`); + } - // if no recipients remain, delete the channel - const remainingRecipients = await Recipient.find({ where: { channel_id: channel.id } }); - if (remainingRecipients.length === 0) { - await emitEvent({ - event: "CHANNEL_DELETE", - data: channel.toJSON(), - channel_id: channel.id, - } as ChannelDeleteEvent); - await Channel.deleteChannel(channel); - console.log(`[Instance ban] Deleted empty group channel ${channel.id}`); - } else { - // otherwise, if the banned user was the owner, reassign ownership - if (channel.owner_id === user.id) { - channel.owner_id = remainingRecipients[0].user_id; - await channel.save(); - console.log(`[Instance ban] Reassigned ownership of group channel ${channel.id} to user ${channel.owner_id}`); - } - } - }), - ); + // if no recipients remain, delete the channel + const remainingRecipients = await Recipient.find({ where: { channel_id: channel.id } }); + if (remainingRecipients.length === 0) { + await emitEvent({ + event: "CHANNEL_DELETE", + data: channel.toJSON(), + channel_id: channel.id, + } as ChannelDeleteEvent); + await Channel.deleteChannel(channel); + console.log(`[Instance ban] Deleted empty group channel ${channel.id}`); + } else { + // otherwise, if the banned user was the owner, reassign ownership + if (channel.owner_id === user.id) { + channel.owner_id = remainingRecipients[0].user_id; + await channel.save(); + console.log(`[Instance ban] Reassigned ownership of group channel ${channel.id} to user ${channel.owner_id}`); + } + } + }), + ); - // change ownership on guilds - const guilds = await Guild.find({ where: { owner_id: req.params.user_id } }); - await Promise.all( - guilds.map(async (guild) => { - const members = await Member.find({ - where: { guild_id: guild.id, id: Not(req.params.user_id) }, - relations: { roles: true }, - select: { id: true, roles: { id: true, position: true } }, - }); - const sortedMembers = members - .filter((m) => m.id !== req.params.user_id) - .sort((a, b) => { - const aHighestRole = a.roles.reduce((prev, curr) => (curr.position > prev.position ? curr : prev), { position: -1 } as { position: number }); - const bHighestRole = b.roles.reduce((prev, curr) => (curr.position > prev.position ? curr : prev), { position: -1 } as { position: number }); - return bHighestRole.position - aHighestRole.position; - }); - if (sortedMembers.length === 0) { - // no members left, delete guild - await guild.remove(); - console.log(`[Instance ban] Deleted guild ${guild.id} as user ${user.id} was the last member`); - } else { - // assign new owner - guild.owner_id = sortedMembers[0].id; - await guild.save(); - console.log(`[Instance ban] Transferred ownership of guild ${guild.id} to user ${guild.owner_id}`); + // change ownership on guilds + const guilds = await Guild.find({ where: { owner_id: req.params.user_id } }); + await Promise.all( + guilds.map(async (guild) => { + const members = await Member.find({ + where: { guild_id: guild.id, id: Not(req.params.user_id) }, + relations: { roles: true }, + select: { id: true, roles: { id: true, position: true } }, + }); + const sortedMembers = members + .filter((m) => m.id !== req.params.user_id) + .sort((a, b) => { + const aHighestRole = a.roles.reduce((prev, curr) => (curr.position > prev.position ? curr : prev), { position: -1 } as { position: number }); + const bHighestRole = b.roles.reduce((prev, curr) => (curr.position > prev.position ? curr : prev), { position: -1 } as { position: number }); + return bHighestRole.position - aHighestRole.position; + }); + if (sortedMembers.length === 0) { + // no members left, delete guild + await guild.remove(); + console.log(`[Instance ban] Deleted guild ${guild.id} as user ${user.id} was the last member`); + } else { + // assign new owner + guild.owner_id = sortedMembers[0].id; + await guild.save(); + console.log(`[Instance ban] Transferred ownership of guild ${guild.id} to user ${guild.owner_id}`); - // safety - reassign emojis/stickers owned by the old owner - const stickers = await Sticker.find({ where: { guild_id: guild.id, user_id: req.params.user_id } }); - await Promise.all( - stickers.map(async (sticker) => { - sticker.user_id = guild.owner_id; - await sticker.save(); - console.log(`[Instance ban] Reassigned sticker ${sticker.id} ownership to user ${guild.owner_id}`); - }), - ); + // safety - reassign emojis/stickers owned by the old owner + const stickers = await Sticker.find({ where: { guild_id: guild.id, user_id: req.params.user_id } }); + await Promise.all( + stickers.map(async (sticker) => { + sticker.user_id = guild.owner_id; + await sticker.save(); + console.log(`[Instance ban] Reassigned sticker ${sticker.id} ownership to user ${guild.owner_id}`); + }), + ); - const emojis = await Emoji.find({ where: { guild_id: guild.id, user_id: req.params.user_id } }); - await Promise.all( - emojis.map(async (emoji) => { - emoji.user_id = guild.owner_id!; - await emoji.save(); - console.log(`[Instance ban] Reassigned emoji ${emoji.id} ownership to user ${guild.owner_id}`); - }), - ); - } - }), - ); + const emojis = await Emoji.find({ where: { guild_id: guild.id, user_id: req.params.user_id } }); + await Promise.all( + emojis.map(async (emoji) => { + emoji.user_id = guild.owner_id!; + await emoji.save(); + console.log(`[Instance ban] Reassigned emoji ${emoji.id} ownership to user ${guild.owner_id}`); + }), + ); + } + }), + ); - const members = await Member.find({ where: { id: req.params.user_id } }); - await Promise.all([...members.map((member) => Member.removeFromGuild(member.id, member.guild_id))]); - await UserSettingsProtos.delete({ user_id: req.params.user_id }); - await User.delete({ id: req.params.user_id }); + const members = await Member.find({ where: { id: req.params.user_id } }); + await Promise.all([...members.map((member) => Member.removeFromGuild(member.id, member.guild_id))]); + await UserSettingsProtos.delete({ user_id: req.params.user_id }); + await User.delete({ id: req.params.user_id }); - // TODO: respect intents as USER_DELETE has potential to cause privacy issues - await emitEvent({ - event: "USER_DELETE", - user_id: req.user_id, - data: { user_id: req.params.user_id }, - } as UserDeleteEvent); + // TODO: respect intents as USER_DELETE has potential to cause privacy issues + await emitEvent({ + event: "USER_DELETE", + user_id: req.user_id, + data: { user_id: req.params.user_id }, + } as UserDeleteEvent); - console.log(`[Instance ban] Deleted user ${user.id} from instance in ${sw.elapsed().toString()}`); - res.sendStatus(204); - }, + console.log(`[Instance ban] Deleted user ${user.id} from instance in ${sw.elapsed().toString()}`); + res.sendStatus(204); + }, ); export default router; diff --git a/src/api/routes/users/#user_id/index.ts b/src/api/routes/users/#user_id/index.ts
index 417f961d..7fca1e0e 100644 --- a/src/api/routes/users/#user_id/index.ts +++ b/src/api/routes/users/#user_id/index.ts
@@ -23,19 +23,19 @@ import { Request, Response, Router } from "express"; const router: Router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "APIPublicUser", - }, - }, - }), - async (req: Request, res: Response) => { - const { user_id } = req.params; + "/", + route({ + responses: { + 200: { + body: "APIPublicUser", + }, + }, + }), + async (req: Request, res: Response) => { + const { user_id } = req.params; - res.json(await User.getPublicUser(user_id)); - }, + res.json(await User.getPublicUser(user_id)); + }, ); export default router; diff --git a/src/api/routes/users/#user_id/messages.ts b/src/api/routes/users/#user_id/messages.ts
index 717bca96..95c7cfc4 100644 --- a/src/api/routes/users/#user_id/messages.ts +++ b/src/api/routes/users/#user_id/messages.ts
@@ -23,33 +23,33 @@ import { DmMessagesResponseSchema } from "@spacebar/schemas"; const router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "DmMessagesResponseSchema", - }, - 400: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const user = await User.findOneOrFail({ where: { id: req.params.user_id } }); - const channel = await user.getDmChannelWith(req.user_id); + "/", + route({ + responses: { + 200: { + body: "DmMessagesResponseSchema", + }, + 400: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const user = await User.findOneOrFail({ where: { id: req.params.user_id } }); + const channel = await user.getDmChannelWith(req.user_id); - const messages = ( - await Message.find({ - where: { channel_id: channel?.id }, - order: { timestamp: "DESC" }, - take: Math.min(Math.max(req.query.limit ? Number(req.query.limit) : 50, 1), Config.get().limits.message.maxPreloadCount), - }) - ).filter((x) => x !== null) as Message[]; + const messages = ( + await Message.find({ + where: { channel_id: channel?.id }, + order: { timestamp: "DESC" }, + take: Math.min(Math.max(req.query.limit ? Number(req.query.limit) : 50, 1), Config.get().limits.message.maxPreloadCount), + }) + ).filter((x) => x !== null) as Message[]; - const filteredMessages = messages.map((message) => message.toPartialMessage()) as DmMessagesResponseSchema; + const filteredMessages = messages.map((message) => message.toPartialMessage()) as DmMessagesResponseSchema; - return res.status(200).send(filteredMessages); - }, + return res.status(200).send(filteredMessages); + }, ); // TODO: POST to send a message to the user diff --git a/src/api/routes/users/#user_id/profile.ts b/src/api/routes/users/#user_id/profile.ts
index a5ea3e9c..bcd59cd1 100644 --- a/src/api/routes/users/#user_id/profile.ts +++ b/src/api/routes/users/#user_id/profile.ts
@@ -25,146 +25,146 @@ import { PrivateUserProjection, PublicUser, PublicUserProjection, RelationshipTy const router: Router = Router({ mergeParams: true }); router.get("/", route({ responses: { 200: { body: "UserProfileResponse" } } }), async (req: Request, res: Response) => { - if (req.params.user_id === "@me") req.params.user_id = req.user_id; + if (req.params.user_id === "@me") req.params.user_id = req.user_id; - const { guild_id, with_mutual_guilds, with_mutual_friends, with_mutual_friends_count } = req.query; + const { guild_id, with_mutual_guilds, with_mutual_friends, with_mutual_friends_count } = req.query; - const user = await User.getPublicUser(req.params.user_id, { - relations: ["connected_accounts"], - }); + const user = await User.getPublicUser(req.params.user_id, { + relations: ["connected_accounts"], + }); - const mutual_guilds: object[] = []; - let premium_guild_since; + const mutual_guilds: object[] = []; + let premium_guild_since; - if (with_mutual_guilds == "true") { - const requested_member = await Member.find({ - where: { id: req.params.user_id }, - }); - const self_member = await Member.find({ - where: { id: req.user_id }, - }); + if (with_mutual_guilds == "true") { + const requested_member = await Member.find({ + where: { id: req.params.user_id }, + }); + const self_member = await Member.find({ + where: { id: req.user_id }, + }); - for (const rmem of requested_member) { - if (rmem.premium_since) { - if (premium_guild_since) { - if (premium_guild_since > rmem.premium_since) { - premium_guild_since = rmem.premium_since; - } - } else { - premium_guild_since = rmem.premium_since; - } - } - for (const smem of self_member) { - if (smem.guild_id === rmem.guild_id) { - mutual_guilds.push({ - id: rmem.guild_id, - nick: rmem.nick, - }); - } - } - } - } + for (const rmem of requested_member) { + if (rmem.premium_since) { + if (premium_guild_since) { + if (premium_guild_since > rmem.premium_since) { + premium_guild_since = rmem.premium_since; + } + } else { + premium_guild_since = rmem.premium_since; + } + } + for (const smem of self_member) { + if (smem.guild_id === rmem.guild_id) { + mutual_guilds.push({ + id: rmem.guild_id, + nick: rmem.nick, + }); + } + } + } + } - const guild_member = - guild_id && typeof guild_id == "string" - ? await Member.findOneOrFail({ - where: { id: req.params.user_id, guild_id: guild_id }, - relations: ["roles"], - }) - : undefined; + const guild_member = + guild_id && typeof guild_id == "string" + ? await Member.findOneOrFail({ + where: { id: req.params.user_id, guild_id: guild_id }, + relations: ["roles"], + }) + : undefined; - // TODO: make proper DTO's in util? + // TODO: make proper DTO's in util? - const userProfile = { - bio: req.user_bot ? null : user.bio, - accent_color: user.accent_color, - banner: user.banner, - pronouns: user.pronouns, - theme_colors: user.theme_colors?.map((t) => Number(t)), // these are strings for some reason, they should be numbers - }; + const userProfile = { + bio: req.user_bot ? null : user.bio, + accent_color: user.accent_color, + banner: user.banner, + pronouns: user.pronouns, + theme_colors: user.theme_colors?.map((t) => Number(t)), // these are strings for some reason, they should be numbers + }; - const guildMemberProfile = { - accent_color: null, - banner: guild_member?.banner || null, - bio: guild_member?.bio || "", - guild_id, - }; + const guildMemberProfile = { + accent_color: null, + banner: guild_member?.banner || null, + bio: guild_member?.bio || "", + guild_id, + }; - const badges = await Badge.find(); + const badges = await Badge.find(); - let mutual_friends: PublicUser[] = []; - let mutual_friends_count = 0; + let mutual_friends: PublicUser[] = []; + let mutual_friends_count = 0; - if (with_mutual_friends == "true" || with_mutual_friends_count == "true") { - const relationshipsSelf = await Relationship.find({ where: { from_id: req.user_id, type: RelationshipType.friends } }); - const relationshipsUser = await Relationship.find({ where: { from_id: req.params.user_id, type: RelationshipType.friends } }); - const relationshipsIntersection = relationshipsSelf.filter((r1) => relationshipsUser.some((r2) => r2.to_id === r1.to_id)); - if (with_mutual_friends_count) mutual_friends_count = relationshipsIntersection.length; - if (with_mutual_friends) { - const users = await User.find({ where: { id: In(relationshipsIntersection.map((r) => r.to_id)) }, select: PublicUserProjection }); - mutual_friends = users.map((u) => u.toPublicUser()); - } - } + if (with_mutual_friends == "true" || with_mutual_friends_count == "true") { + const relationshipsSelf = await Relationship.find({ where: { from_id: req.user_id, type: RelationshipType.friends } }); + const relationshipsUser = await Relationship.find({ where: { from_id: req.params.user_id, type: RelationshipType.friends } }); + const relationshipsIntersection = relationshipsSelf.filter((r1) => relationshipsUser.some((r2) => r2.to_id === r1.to_id)); + if (with_mutual_friends_count) mutual_friends_count = relationshipsIntersection.length; + if (with_mutual_friends) { + const users = await User.find({ where: { id: In(relationshipsIntersection.map((r) => r.to_id)) }, select: PublicUserProjection }); + mutual_friends = users.map((u) => u.toPublicUser()); + } + } - res.json({ - connected_accounts: user.connected_accounts.filter((x) => x.visibility != 0), - premium_guild_since: premium_guild_since, // TODO - premium_since: user.premium_since, // TODO - mutual_guilds: with_mutual_guilds ? mutual_guilds : undefined, // TODO {id: "", nick: null} when ?with_mutual_guilds=true - mutual_friends: with_mutual_friends ? mutual_friends : undefined, - mutual_friends_count: with_mutual_friends_count ? mutual_friends_count : undefined, - user: user.toPublicUser(), - premium_type: user.premium_type, - profile_themes_experiment_bucket: 4, // TODO: This doesn't make it available, for some reason? - user_profile: userProfile, - guild_member: { ...guild_member?.toPublicMember(), user: user.toPublicUser() }, - guild_member_profile: guild_id && guildMemberProfile, - badges: badges.filter((x) => user.badge_ids?.includes(x.id)), - }); + res.json({ + connected_accounts: user.connected_accounts.filter((x) => x.visibility != 0), + premium_guild_since: premium_guild_since, // TODO + premium_since: user.premium_since, // TODO + mutual_guilds: with_mutual_guilds ? mutual_guilds : undefined, // TODO {id: "", nick: null} when ?with_mutual_guilds=true + mutual_friends: with_mutual_friends ? mutual_friends : undefined, + mutual_friends_count: with_mutual_friends_count ? mutual_friends_count : undefined, + user: user.toPublicUser(), + premium_type: user.premium_type, + profile_themes_experiment_bucket: 4, // TODO: This doesn't make it available, for some reason? + user_profile: userProfile, + guild_member: { ...guild_member?.toPublicMember(), user: user.toPublicUser() }, + guild_member_profile: guild_id && guildMemberProfile, + badges: badges.filter((x) => user.badge_ids?.includes(x.id)), + }); }); router.patch("/", route({ requestBody: "UserProfileModifySchema" }), async (req: Request, res: Response) => { - const body = req.body as UserProfileModifySchema; + const body = req.body as UserProfileModifySchema; - if (body.banner) body.banner = await handleFile(`/banners/${req.user_id}`, body.banner as string); - const user = await User.findOneOrFail({ - where: { id: req.user_id }, - select: [...PrivateUserProjection, "data"], - }); + if (body.banner) body.banner = await handleFile(`/banners/${req.user_id}`, body.banner as string); + const user = await User.findOneOrFail({ + where: { id: req.user_id }, + select: [...PrivateUserProjection, "data"], + }); - 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); - await user.save(); + user.assign(body); + 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({ - accent_color: user.accent_color, - bio: user.bio, - banner: user.banner, - theme_colors: user.theme_colors, - pronouns: user.pronouns, - }); + res.json({ + accent_color: user.accent_color, + bio: user.bio, + banner: user.banner, + theme_colors: user.theme_colors, + pronouns: user.pronouns, + }); }); export default router; diff --git a/src/api/routes/users/#user_id/relationships.ts b/src/api/routes/users/#user_id/relationships.ts
index 7e33291e..3272ef7b 100644 --- a/src/api/routes/users/#user_id/relationships.ts +++ b/src/api/routes/users/#user_id/relationships.ts
@@ -24,44 +24,44 @@ import { UserRelationsResponse } from "@spacebar/schemas"; const router: Router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { body: "UserRelationsResponse" }, - 404: { - body: "APIErrorResponse", - }, - }, - }), - async (req: Request, res: Response) => { - const mutual_relations: UserRelationsResponse = []; + "/", + route({ + responses: { + 200: { body: "UserRelationsResponse" }, + 404: { + body: "APIErrorResponse", + }, + }, + }), + async (req: Request, res: Response) => { + const mutual_relations: UserRelationsResponse = []; - const requested_relations = await User.findOneOrFail({ - where: { id: req.params.user_id }, - relations: ["relationships"], - }); - const self_relations = await User.findOneOrFail({ - where: { id: req.user_id }, - relations: ["relationships"], - }); + const requested_relations = await User.findOneOrFail({ + where: { id: req.params.user_id }, + relations: ["relationships"], + }); + const self_relations = await User.findOneOrFail({ + where: { id: req.user_id }, + relations: ["relationships"], + }); - for (const rmem of requested_relations.relationships) { - for (const smem of self_relations.relationships) - if (rmem.to_id === smem.to_id && rmem.type === 1 && rmem.to_id !== req.user_id) { - const relation_user = await User.getPublicUser(rmem.to_id); + for (const rmem of requested_relations.relationships) { + for (const smem of self_relations.relationships) + if (rmem.to_id === smem.to_id && rmem.type === 1 && rmem.to_id !== req.user_id) { + const relation_user = await User.getPublicUser(rmem.to_id); - mutual_relations.push({ - id: relation_user.id, - username: relation_user.username, - avatar: relation_user.avatar, - discriminator: relation_user.discriminator, - public_flags: relation_user.public_flags, - }); - } - } + mutual_relations.push({ + id: relation_user.id, + username: relation_user.username, + avatar: relation_user.avatar, + discriminator: relation_user.discriminator, + public_flags: relation_user.public_flags, + }); + } + } - res.json(mutual_relations); - }, + res.json(mutual_relations); + }, ); export default router; 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; diff --git a/src/api/routes/voice/regions.ts b/src/api/routes/voice/regions.ts
index ba261231..a6759b55 100644 --- a/src/api/routes/voice/regions.ts +++ b/src/api/routes/voice/regions.ts
@@ -22,17 +22,17 @@ import { Request, Response, Router } from "express"; const router: Router = Router({ mergeParams: true }); router.get( - "/", - route({ - responses: { - 200: { - body: "APIGuildVoiceRegion", - }, - }, - }), - async (req: Request, res: Response) => { - res.json(await getVoiceRegions(req.ip!, true)); //vip true? - }, + "/", + route({ + responses: { + 200: { + body: "APIGuildVoiceRegion", + }, + }, + }), + async (req: Request, res: Response) => { + res.json(await getVoiceRegions(req.ip!, true)); //vip true? + }, ); export default router; diff --git a/src/api/routes/webhooks/#webhook_id/#token/github.ts b/src/api/routes/webhooks/#webhook_id/#token/github.ts
index 78d6bbb8..81aa40a9 100644 --- a/src/api/routes/webhooks/#webhook_id/#token/github.ts +++ b/src/api/routes/webhooks/#webhook_id/#token/github.ts
@@ -7,402 +7,402 @@ import { WebhookExecuteSchema } from "@spacebar/schemas"; const router = Router({ mergeParams: true }); const parseGitHubWebhook = (req: Request, res: Response, next: NextFunction) => { - const eventType = req.headers["x-github-event"] as string; - if (!eventType) { - throw new HTTPError("Missing X-GitHub-Event header", 400); - } + const eventType = req.headers["x-github-event"] as string; + if (!eventType) { + throw new HTTPError("Missing X-GitHub-Event header", 400); + } - const discordPayload: WebhookExecuteSchema = { - username: "GitHub", - avatar_url: "https://github.githubassets.com/assets/GitHub-Mark-ea2971cee799.png", - }; + const discordPayload: WebhookExecuteSchema = { + username: "GitHub", + avatar_url: "https://github.githubassets.com/assets/GitHub-Mark-ea2971cee799.png", + }; - switch (eventType) { - case "commit_comment": - if (req.body.action !== "created") { - return; - } + switch (eventType) { + case "commit_comment": + if (req.body.action !== "created") { + return; + } - discordPayload.embeds = [ - { - author: { - name: req.body.sender.login, - icon_url: req.body.sender.avatar_url, - proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), - url: req.body.sender.html_url, - }, - title: `[${req.body.repository.full_name}] New comment on commit \`${req.body.comment.commit_id.slice(0, 7)}\``, - description: req.body.comment.body.length > 500 ? `${req.body.comment.body.slice(0, 497)}...` : req.body.comment.body, - url: req.body.comment.html_url, - }, - ]; - break; - case "create": - discordPayload.embeds = [ - { - author: { - name: req.body.sender.login, - icon_url: req.body.sender.avatar_url, - proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), - url: req.body.sender.html_url, - }, - title: `[${req.body.repository.full_name}] New ${req.body.ref_type} created: ${req.body.ref}`, - }, - ]; - break; - case "delete": - discordPayload.embeds = [ - { - author: { - name: req.body.sender.login, - icon_url: req.body.sender.avatar_url, - proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), - url: req.body.sender.html_url, - }, - title: `[${req.body.repository.full_name}] ${req.body.ref_type} deleted: ${req.body.ref}`, - }, - ]; - break; - case "fork": - discordPayload.embeds = [ - { - author: { - name: req.body.sender.login, - icon_url: req.body.sender.avatar_url, - proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), - url: req.body.sender.html_url, - }, - title: `[${req.body.repository.full_name}] Fork created: ${req.body.forkee.full_name}`, - url: req.body.forkee.html_url, - }, - ]; - break; - case "issue_comment": - if (req.body.action !== "created") { - return; - } + discordPayload.embeds = [ + { + author: { + name: req.body.sender.login, + icon_url: req.body.sender.avatar_url, + proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), + url: req.body.sender.html_url, + }, + title: `[${req.body.repository.full_name}] New comment on commit \`${req.body.comment.commit_id.slice(0, 7)}\``, + description: req.body.comment.body.length > 500 ? `${req.body.comment.body.slice(0, 497)}...` : req.body.comment.body, + url: req.body.comment.html_url, + }, + ]; + break; + case "create": + discordPayload.embeds = [ + { + author: { + name: req.body.sender.login, + icon_url: req.body.sender.avatar_url, + proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), + url: req.body.sender.html_url, + }, + title: `[${req.body.repository.full_name}] New ${req.body.ref_type} created: ${req.body.ref}`, + }, + ]; + break; + case "delete": + discordPayload.embeds = [ + { + author: { + name: req.body.sender.login, + icon_url: req.body.sender.avatar_url, + proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), + url: req.body.sender.html_url, + }, + title: `[${req.body.repository.full_name}] ${req.body.ref_type} deleted: ${req.body.ref}`, + }, + ]; + break; + case "fork": + discordPayload.embeds = [ + { + author: { + name: req.body.sender.login, + icon_url: req.body.sender.avatar_url, + proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), + url: req.body.sender.html_url, + }, + title: `[${req.body.repository.full_name}] Fork created: ${req.body.forkee.full_name}`, + url: req.body.forkee.html_url, + }, + ]; + break; + case "issue_comment": + if (req.body.action !== "created") { + return; + } - discordPayload.embeds = [ - { - author: { - name: req.body.sender.login, - icon_url: req.body.sender.avatar_url, - proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), - url: req.body.sender.html_url, - }, - color: "pull_request" in req.body.issue ? 12576191 : 15109472, - title: `[${req.body.repository.full_name}] New comment on ${"pull_request" in req.body.issue ? "pull request" : "issue"} #${req.body.issue.number}: ${ - req.body.issue.title.length > 150 ? `${req.body.issue.title.slice(0, 147)}...` : req.body.issue.title - }`, - url: req.body.comment.html_url, - description: req.body.comment.body.length > 150 ? `${req.body.comment.body.slice(0, 497)}...` : req.body.comment.body, - }, - ]; - break; - case "issues": - if (!["opened", "closed"].includes(req.body.action)) { - return; - } + discordPayload.embeds = [ + { + author: { + name: req.body.sender.login, + icon_url: req.body.sender.avatar_url, + proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), + url: req.body.sender.html_url, + }, + color: "pull_request" in req.body.issue ? 12576191 : 15109472, + title: `[${req.body.repository.full_name}] New comment on ${"pull_request" in req.body.issue ? "pull request" : "issue"} #${req.body.issue.number}: ${ + req.body.issue.title.length > 150 ? `${req.body.issue.title.slice(0, 147)}...` : req.body.issue.title + }`, + url: req.body.comment.html_url, + description: req.body.comment.body.length > 150 ? `${req.body.comment.body.slice(0, 497)}...` : req.body.comment.body, + }, + ]; + break; + case "issues": + if (!["opened", "closed"].includes(req.body.action)) { + return; + } - discordPayload.embeds = [ - { - author: { - name: req.body.sender.login, - icon_url: req.body.sender.avatar_url, - proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), - url: req.body.sender.html_url, - }, - title: `[${req.body.repository.full_name}] Issue ${req.body.action} #${req.body.issue.number}: ${req.body.issue.title}`, - url: req.body.issue.html_url, - }, - ]; + discordPayload.embeds = [ + { + author: { + name: req.body.sender.login, + icon_url: req.body.sender.avatar_url, + proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), + url: req.body.sender.html_url, + }, + title: `[${req.body.repository.full_name}] Issue ${req.body.action} #${req.body.issue.number}: ${req.body.issue.title}`, + url: req.body.issue.html_url, + }, + ]; - if (req.body.action === "opened") { - discordPayload.embeds[0].color = 15426592; - discordPayload.embeds[0].description = req.body.issue.body.length > 150 ? `${req.body.issue.body.slice(0, 147)}...` : req.body.issue.body; - } - break; - case "member": - if (req.body.action !== "added") { - return; - } + if (req.body.action === "opened") { + discordPayload.embeds[0].color = 15426592; + discordPayload.embeds[0].description = req.body.issue.body.length > 150 ? `${req.body.issue.body.slice(0, 147)}...` : req.body.issue.body; + } + break; + case "member": + if (req.body.action !== "added") { + return; + } - discordPayload.embeds = [ - { - author: { - name: req.body.sender.login, - icon_url: req.body.sender.avatar_url, - proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), - url: req.body.sender.html_url, - }, - title: `[${req.body.repository.full_name}] New collaborator added: ${req.body.member.login}`, - url: req.body.member.html_url, - }, - ]; - break; - case "public": - discordPayload.embeds = [ - { - author: { - name: req.body.sender.login, - icon_url: req.body.sender.avatar_url, - proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), - url: req.body.sender.html_url, - }, - title: `[${req.body.repository.full_name}] Now open sourced!`, - }, - ]; - break; - case "pull_request": // funfact: for some reason, if a PR's title is over 216 chars in length you won't see any actions taken on the PR on discord - if (!["opened", "closed"].includes(req.body.action)) { - return; - } + discordPayload.embeds = [ + { + author: { + name: req.body.sender.login, + icon_url: req.body.sender.avatar_url, + proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), + url: req.body.sender.html_url, + }, + title: `[${req.body.repository.full_name}] New collaborator added: ${req.body.member.login}`, + url: req.body.member.html_url, + }, + ]; + break; + case "public": + discordPayload.embeds = [ + { + author: { + name: req.body.sender.login, + icon_url: req.body.sender.avatar_url, + proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), + url: req.body.sender.html_url, + }, + title: `[${req.body.repository.full_name}] Now open sourced!`, + }, + ]; + break; + case "pull_request": // funfact: for some reason, if a PR's title is over 216 chars in length you won't see any actions taken on the PR on discord + if (!["opened", "closed"].includes(req.body.action)) { + return; + } - discordPayload.embeds = [ - { - author: { - name: req.body.sender.login, - icon_url: req.body.sender.avatar_url, - proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), - url: req.body.sender.html_url, - }, - title: `[${req.body.repository.full_name}] Pull request ${req.body.action}: #${req.body.number} ${req.body.pull_request.title.length > 216 ? `${req.body.pull_request.title.slice(0, 213)}...` : req.body.pull_request.title}`, - url: req.body.pull_request.html_url, - }, - ]; + discordPayload.embeds = [ + { + author: { + name: req.body.sender.login, + icon_url: req.body.sender.avatar_url, + proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), + url: req.body.sender.html_url, + }, + title: `[${req.body.repository.full_name}] Pull request ${req.body.action}: #${req.body.number} ${req.body.pull_request.title.length > 216 ? `${req.body.pull_request.title.slice(0, 213)}...` : req.body.pull_request.title}`, + url: req.body.pull_request.html_url, + }, + ]; - if (req.body.action === "opened") { - if (req.body.pull_request.body != null) { - discordPayload.embeds[0].description = req.body.pull_request.body.length > 500 ? `${req.body.pull_request.body.slice(0, 497)}...` : req.body.pull_request.body; - } - discordPayload.embeds[0].color = 38912; - } - break; - case "pull_request_review": // funfact: for some reason, if a PR's title is over 216 chars in length you won't see any actions taken on the PR on discord - if (req.body.action !== "submitted") { - return; - } + if (req.body.action === "opened") { + if (req.body.pull_request.body != null) { + discordPayload.embeds[0].description = req.body.pull_request.body.length > 500 ? `${req.body.pull_request.body.slice(0, 497)}...` : req.body.pull_request.body; + } + discordPayload.embeds[0].color = 38912; + } + break; + case "pull_request_review": // funfact: for some reason, if a PR's title is over 216 chars in length you won't see any actions taken on the PR on discord + if (req.body.action !== "submitted") { + return; + } - discordPayload.embeds = [ - { - author: { - name: req.body.sender.login, - icon_url: req.body.sender.avatar_url, - proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), - url: req.body.sender.html_url, - }, - title: `[${req.body.repository.full_name}] Pull request review submitted: #${req.body.pull_request.number} ${req.body.pull_request.title.length > 216 ? `${req.body.pull_request.title.slice(0, 213)}...` : req.body.pull_request.title}`, - description: req.body.review.body.length > 500 ? `${req.body.review.body.slice(0, 497)}...` : req.body.review.body, - url: req.body.review.html_url, - }, - ]; - break; - case "pull_request_review_comment": // funfact: for some reason, if a PR's title is over 216 chars in length you won't see any actions taken on the PR on discord - if (req.body.action !== "created") { - return; - } + discordPayload.embeds = [ + { + author: { + name: req.body.sender.login, + icon_url: req.body.sender.avatar_url, + proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), + url: req.body.sender.html_url, + }, + title: `[${req.body.repository.full_name}] Pull request review submitted: #${req.body.pull_request.number} ${req.body.pull_request.title.length > 216 ? `${req.body.pull_request.title.slice(0, 213)}...` : req.body.pull_request.title}`, + description: req.body.review.body.length > 500 ? `${req.body.review.body.slice(0, 497)}...` : req.body.review.body, + url: req.body.review.html_url, + }, + ]; + break; + case "pull_request_review_comment": // funfact: for some reason, if a PR's title is over 216 chars in length you won't see any actions taken on the PR on discord + if (req.body.action !== "created") { + return; + } - discordPayload.embeds = [ - { - author: { - name: req.body.sender.login, - icon_url: req.body.sender.avatar_url, - proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), - url: req.body.sender.html_url, - }, - color: 12576191, - title: `[${req.body.repository.full_name}] New review comment on pull request: #${req.body.pull_request.number} ${req.body.pull_request.title.length > 216 ? `${req.body.pull_request.title.slice(0, 213)}...` : req.body.pull_request.title}`, - description: req.body.comment.body.length > 500 ? `${req.body.comment.body.slice(0, 497)}...` : req.body.comment.body, - url: req.body.comment.html_url, - }, - ]; - break; - case "push": - if (!req.body.ref.startsWith("refs/heads/")) { - return; - } + discordPayload.embeds = [ + { + author: { + name: req.body.sender.login, + icon_url: req.body.sender.avatar_url, + proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), + url: req.body.sender.html_url, + }, + color: 12576191, + title: `[${req.body.repository.full_name}] New review comment on pull request: #${req.body.pull_request.number} ${req.body.pull_request.title.length > 216 ? `${req.body.pull_request.title.slice(0, 213)}...` : req.body.pull_request.title}`, + description: req.body.comment.body.length > 500 ? `${req.body.comment.body.slice(0, 497)}...` : req.body.comment.body, + url: req.body.comment.html_url, + }, + ]; + break; + case "push": + if (!req.body.ref.startsWith("refs/heads/")) { + return; + } - if (req.body.forced) { - discordPayload.embeds = [ - { - color: 16525609, - author: { - name: req.body.sender.login, - icon_url: req.body.sender.avatar_url, - proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), - url: req.body.sender.html_url, - }, - title: `[${req.body.repository.name}] Branch ${req.body.ref.slice(11)} was force-pushed to \`${req.body.head_commit.id.slice(0, 7)}\``, - description: `[Compare changes](${req.body.compare})`, - }, - ]; - } else { - discordPayload.embeds = [ - { - color: 7506394, - author: { - name: req.body.sender.login, - icon_url: req.body.sender.avatar_url, - proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), - url: req.body.sender.html_url, - }, - title: `[${req.body.repository.name}:${req.body.ref.slice(11)}] ${req.body.commits.length} new commit${req.body.commits.length > 1 ? "s" : ""}`, - url: req.body.commits.length > 1 ? req.body.compare : req.body.head_commit.url, - description: req.body.commits - .slice(0, 5) // Discord only shows 5 first commits - .map( - (c: { id: string; url: string; message: string; author: { username: string } }) => - `[\`${c.id.slice(0, 7)}\`](${c.url}) ${c.message.split("\n")[0].length > 46 ? `${c.message.slice(0, 47)}...` : c.message.split("\n")[0]} - ${c.author.username}`, - ) - .join("\n"), - }, - ]; - } - break; - case "release": - if (req.body.action !== "created") { - return; - } + if (req.body.forced) { + discordPayload.embeds = [ + { + color: 16525609, + author: { + name: req.body.sender.login, + icon_url: req.body.sender.avatar_url, + proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), + url: req.body.sender.html_url, + }, + title: `[${req.body.repository.name}] Branch ${req.body.ref.slice(11)} was force-pushed to \`${req.body.head_commit.id.slice(0, 7)}\``, + description: `[Compare changes](${req.body.compare})`, + }, + ]; + } else { + discordPayload.embeds = [ + { + color: 7506394, + author: { + name: req.body.sender.login, + icon_url: req.body.sender.avatar_url, + proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), + url: req.body.sender.html_url, + }, + title: `[${req.body.repository.name}:${req.body.ref.slice(11)}] ${req.body.commits.length} new commit${req.body.commits.length > 1 ? "s" : ""}`, + url: req.body.commits.length > 1 ? req.body.compare : req.body.head_commit.url, + description: req.body.commits + .slice(0, 5) // Discord only shows 5 first commits + .map( + (c: { id: string; url: string; message: string; author: { username: string } }) => + `[\`${c.id.slice(0, 7)}\`](${c.url}) ${c.message.split("\n")[0].length > 46 ? `${c.message.slice(0, 47)}...` : c.message.split("\n")[0]} - ${c.author.username}`, + ) + .join("\n"), + }, + ]; + } + break; + case "release": + if (req.body.action !== "created") { + return; + } - discordPayload.embeds = [ - { - author: { - name: req.body.sender.login, - icon_url: req.body.sender.avatar_url, - proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), - url: req.body.sender.html_url, - }, - title: `[${req.body.repository.full_name}] New release published: ${req.body.release.tag_name}`, - url: req.body.release.html_url, - }, - ]; - break; - case "watch": - if (req.body.action !== "started") { - return; - } + discordPayload.embeds = [ + { + author: { + name: req.body.sender.login, + icon_url: req.body.sender.avatar_url, + proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), + url: req.body.sender.html_url, + }, + title: `[${req.body.repository.full_name}] New release published: ${req.body.release.tag_name}`, + url: req.body.release.html_url, + }, + ]; + break; + case "watch": + if (req.body.action !== "started") { + return; + } - discordPayload.embeds = [ - { - author: { - name: req.body.sender.login, - icon_url: req.body.sender.avatar_url, - proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), - url: req.body.sender.html_url, - }, - title: `[${req.body.repository.full_name}] New star added`, - url: req.body.repository.html_url, - }, - ]; - break; - case "check_run": - if (req.body.action !== "completed") { - return; - } + discordPayload.embeds = [ + { + author: { + name: req.body.sender.login, + icon_url: req.body.sender.avatar_url, + proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), + url: req.body.sender.html_url, + }, + title: `[${req.body.repository.full_name}] New star added`, + url: req.body.repository.html_url, + }, + ]; + break; + case "check_run": + if (req.body.action !== "completed") { + return; + } - discordPayload.embeds = [ - { - color: req.body.check_run.conclusion == "success" ? 38912 : 16525609, - title: `[${req.body.repository.name}] ${req.body.check_run.name} ${req.body.check_run.conclusion} on ${req.body.check_run.check_suite.head_branch}`, - url: req.body.check_run.html_url, - }, - ]; - break; - case "check_suite": - if (req.body.action !== "completed") { - return; - } + discordPayload.embeds = [ + { + color: req.body.check_run.conclusion == "success" ? 38912 : 16525609, + title: `[${req.body.repository.name}] ${req.body.check_run.name} ${req.body.check_run.conclusion} on ${req.body.check_run.check_suite.head_branch}`, + url: req.body.check_run.html_url, + }, + ]; + break; + case "check_suite": + if (req.body.action !== "completed") { + return; + } - discordPayload.embeds = [ - { - color: req.body.check_suite.conclusion == "success" ? 38912 : 16525609, - title: `[${req.body.repository.name}] GitHub Actions checks ${req.body.check_suite.conclusion} on ${req.body.check_suite.head_branch}`, - url: `https://github.com/${req.body.repository.full_name}/commit/${req.body.check_suite.head_commit.id}`, - }, - ]; - break; - case "discussion": - if (req.body.action !== "created") { - return; - } + discordPayload.embeds = [ + { + color: req.body.check_suite.conclusion == "success" ? 38912 : 16525609, + title: `[${req.body.repository.name}] GitHub Actions checks ${req.body.check_suite.conclusion} on ${req.body.check_suite.head_branch}`, + url: `https://github.com/${req.body.repository.full_name}/commit/${req.body.check_suite.head_commit.id}`, + }, + ]; + break; + case "discussion": + if (req.body.action !== "created") { + return; + } - discordPayload.embeds = [ - { - author: { - name: req.body.sender.login, - icon_url: req.body.sender.avatar_url, - proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), - url: req.body.sender.html_url, - }, - color: 15109472, - title: `[${req.body.repository.name}] New discussion #${req.body.discussion.number}: ${req.body.discussion.title.length > 150 ? `${req.body.discussion.title.slice(0, 151)}...` : req.body.discussion.title}`, - url: req.body.discussion.html_url, - description: req.body.discussion.body.length > 500 ? `${req.body.discussion.body.slice(0, 497)}...` : req.body.discussion.body, - }, - ]; - break; - case "discussion_comment": - if (req.body.action !== "created") { - return; - } + discordPayload.embeds = [ + { + author: { + name: req.body.sender.login, + icon_url: req.body.sender.avatar_url, + proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), + url: req.body.sender.html_url, + }, + color: 15109472, + title: `[${req.body.repository.name}] New discussion #${req.body.discussion.number}: ${req.body.discussion.title.length > 150 ? `${req.body.discussion.title.slice(0, 151)}...` : req.body.discussion.title}`, + url: req.body.discussion.html_url, + description: req.body.discussion.body.length > 500 ? `${req.body.discussion.body.slice(0, 497)}...` : req.body.discussion.body, + }, + ]; + break; + case "discussion_comment": + if (req.body.action !== "created") { + return; + } - discordPayload.embeds = [ - { - author: { - name: req.body.sender.login, - icon_url: req.body.sender.avatar_url, - proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), - url: req.body.sender.html_url, - }, - color: 15109472, - title: `[${req.body.comment.repository_url}] New comment on discussion #${req.body.discussion.number}: ${req.body.discussion.title.length > 150 ? `${req.body.discussion.title.slice(0, 151)}...` : req.body.discussion.title}`, - url: req.body.comment.html_url, - description: req.body.comment.body.length > 500 ? `${req.body.comment.body.slice(0, 497)}...` : req.body.comment.body, - }, - ]; - break; - default: - return res.status(204).end(); // Yes, discord sends 204 even on invalid event - } + discordPayload.embeds = [ + { + author: { + name: req.body.sender.login, + icon_url: req.body.sender.avatar_url, + proxy_icon_url: getProxyUrl(new URL(req.body.sender.avatar_url), 80, 80), + url: req.body.sender.html_url, + }, + color: 15109472, + title: `[${req.body.comment.repository_url}] New comment on discussion #${req.body.discussion.number}: ${req.body.discussion.title.length > 150 ? `${req.body.discussion.title.slice(0, 151)}...` : req.body.discussion.title}`, + url: req.body.comment.html_url, + description: req.body.comment.body.length > 500 ? `${req.body.comment.body.slice(0, 497)}...` : req.body.comment.body, + }, + ]; + break; + default: + return res.status(204).end(); // Yes, discord sends 204 even on invalid event + } - req.body = discordPayload; - req.query.wait ||= "true"; + req.body = discordPayload; + req.query.wait ||= "true"; - next(); + next(); }; router.post( - "/", - parseGitHubWebhook, - (req, _res, next) => { - if (req.body.payload_json) { - req.body = JSON.parse(req.body.payload_json); - } - next(); - }, - route({ - requestBody: "WebhookExecuteSchema", - query: { - wait: { - type: "boolean", - required: false, - description: "waits for server confirmation of message send before response, and returns the created message body", - }, - thread_id: { - type: "string", - required: false, - description: "Send a message to the specified thread within a webhook's channel.", - }, - }, - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 404: {}, - }, - }), - executeWebhook, + "/", + parseGitHubWebhook, + (req, _res, next) => { + if (req.body.payload_json) { + req.body = JSON.parse(req.body.payload_json); + } + next(); + }, + route({ + requestBody: "WebhookExecuteSchema", + query: { + wait: { + type: "boolean", + required: false, + description: "waits for server confirmation of message send before response, and returns the created message body", + }, + thread_id: { + type: "string", + required: false, + description: "Send a message to the specified thread within a webhook's channel.", + }, + }, + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 404: {}, + }, + }), + executeWebhook, ); export default router; diff --git a/src/api/routes/webhooks/#webhook_id/#token/index.ts b/src/api/routes/webhooks/#webhook_id/#token/index.ts
index 1b589a7b..eecbfcae 100644 --- a/src/api/routes/webhooks/#webhook_id/#token/index.ts +++ b/src/api/routes/webhooks/#webhook_id/#token/index.ts
@@ -8,179 +8,179 @@ import { WebhookUpdateSchema } from "@spacebar/schemas"; const router = Router({ mergeParams: true }); router.get( - "/", - route({ - description: "Returns a webhook object for the given id and token.", - responses: { - 200: { - body: "APIWebhook", - }, - 404: {}, - }, - }), - async (req: Request, res: Response) => { - const { webhook_id, token } = req.params; - const webhook = await Webhook.findOne({ - where: { - id: webhook_id, - }, - relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"], - }); + "/", + route({ + description: "Returns a webhook object for the given id and token.", + responses: { + 200: { + body: "APIWebhook", + }, + 404: {}, + }, + }), + async (req: Request, res: Response) => { + const { webhook_id, token } = req.params; + const webhook = await Webhook.findOne({ + where: { + id: webhook_id, + }, + relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"], + }); - if (!webhook) { - throw DiscordApiErrors.UNKNOWN_WEBHOOK; - } + if (!webhook) { + throw DiscordApiErrors.UNKNOWN_WEBHOOK; + } - if (webhook.token !== token) { - throw DiscordApiErrors.INVALID_WEBHOOK_TOKEN_PROVIDED; - } + if (webhook.token !== token) { + throw DiscordApiErrors.INVALID_WEBHOOK_TOKEN_PROVIDED; + } - return res.json({ - ...webhook, - url: Config.get().api.endpointPublic + "/webhooks/" + webhook.id + "/" + webhook.token, - }); - }, + return res.json({ + ...webhook, + url: Config.get().api.endpointPublic + "/webhooks/" + webhook.id + "/" + webhook.token, + }); + }, ); // TODO: config max upload size const messageUpload = multer({ - limits: { - fileSize: Config.get().limits.message.maxAttachmentSize, - fields: 10, - // files: 1 - }, - storage: multer.memoryStorage(), + limits: { + fileSize: Config.get().limits.message.maxAttachmentSize, + fields: 10, + // files: 1 + }, + storage: multer.memoryStorage(), }); // max upload 50 mb // https://discord.com/developers/docs/resources/webhook#execute-webhook // TODO: Slack compatible hooks router.post( - "/", - messageUpload.any(), - (req, _res, next) => { - if (req.body.payload_json) { - req.body = JSON.parse(req.body.payload_json); - } + "/", + messageUpload.any(), + (req, _res, next) => { + if (req.body.payload_json) { + req.body = JSON.parse(req.body.payload_json); + } - next(); - }, - route({ - requestBody: "WebhookExecuteSchema", - query: { - wait: { - type: "boolean", - required: false, - description: "waits for server confirmation of message send before response, and returns the created message body", - }, - thread_id: { - type: "string", - required: false, - description: "Send a message to the specified thread within a webhook's channel.", - }, - }, - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 404: {}, - }, - }), - executeWebhook, + next(); + }, + route({ + requestBody: "WebhookExecuteSchema", + query: { + wait: { + type: "boolean", + required: false, + description: "waits for server confirmation of message send before response, and returns the created message body", + }, + thread_id: { + type: "string", + required: false, + description: "Send a message to the specified thread within a webhook's channel.", + }, + }, + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 404: {}, + }, + }), + executeWebhook, ); router.delete( - "/", - route({ - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 404: {}, - }, - }), - async (req: Request, res: Response) => { - const { webhook_id, token } = req.params; + "/", + route({ + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 404: {}, + }, + }), + async (req: Request, res: Response) => { + const { webhook_id, token } = req.params; - const webhook = await Webhook.findOne({ - where: { - id: webhook_id, - }, - relations: ["channel", "guild", "application"], - }); + const webhook = await Webhook.findOne({ + where: { + id: webhook_id, + }, + relations: ["channel", "guild", "application"], + }); - if (!webhook) { - throw DiscordApiErrors.UNKNOWN_WEBHOOK; - } + if (!webhook) { + throw DiscordApiErrors.UNKNOWN_WEBHOOK; + } - if (webhook.token !== token) { - throw DiscordApiErrors.INVALID_WEBHOOK_TOKEN_PROVIDED; - } - const channel_id = webhook.channel_id; - await Webhook.delete({ id: webhook_id }); + if (webhook.token !== token) { + throw DiscordApiErrors.INVALID_WEBHOOK_TOKEN_PROVIDED; + } + const channel_id = webhook.channel_id; + await Webhook.delete({ id: webhook_id }); - await emitEvent({ - event: "WEBHOOKS_UPDATE", - channel_id, - data: { - channel_id, - guild_id: webhook.guild_id, - }, - } as WebhooksUpdateEvent); + await emitEvent({ + event: "WEBHOOKS_UPDATE", + channel_id, + data: { + channel_id, + guild_id: webhook.guild_id, + }, + } as WebhooksUpdateEvent); - res.sendStatus(204); - }, + res.sendStatus(204); + }, ); router.patch( - "/", - route({ - requestBody: "WebhookUpdateSchema", - responses: { - 200: { - body: "Message", - }, - 400: { - body: "APIErrorResponse", - }, - 403: {}, - 404: {}, - }, - }), - async (req: Request, res: Response) => { - const { webhook_id, token } = req.params; - const body = req.body as WebhookUpdateSchema; + "/", + route({ + requestBody: "WebhookUpdateSchema", + responses: { + 200: { + body: "Message", + }, + 400: { + body: "APIErrorResponse", + }, + 403: {}, + 404: {}, + }, + }), + async (req: Request, res: Response) => { + const { webhook_id, token } = req.params; + const body = req.body as WebhookUpdateSchema; - const webhook = await Webhook.findOneOrFail({ - where: { id: webhook_id }, - relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"], - }); - const channel_id = webhook.channel_id; - if (!body.name && !body.avatar) { - throw new HTTPError("Empty webhook updates are not allowed", 50006); - } - if (body.avatar) body.avatar = await handleFile(`/avatars/${webhook_id}`, body.avatar as string); + const webhook = await Webhook.findOneOrFail({ + where: { id: webhook_id }, + relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"], + }); + const channel_id = webhook.channel_id; + if (!body.name && !body.avatar) { + throw new HTTPError("Empty webhook updates are not allowed", 50006); + } + if (body.avatar) body.avatar = await handleFile(`/avatars/${webhook_id}`, body.avatar as string); - if (body.name) { - ValidateName(body.name); - } + if (body.name) { + ValidateName(body.name); + } - webhook.assign(body); + webhook.assign(body); - await Promise.all([ - webhook.save(), - emitEvent({ - event: "WEBHOOKS_UPDATE", - channel_id, - data: { - channel_id, - guild_id: webhook.guild_id, - }, - } as WebhooksUpdateEvent), - ]); - res.status(204); - }, + await Promise.all([ + webhook.save(), + emitEvent({ + event: "WEBHOOKS_UPDATE", + channel_id, + data: { + channel_id, + guild_id: webhook.guild_id, + }, + } as WebhooksUpdateEvent), + ]); + res.status(204); + }, ); export default router; diff --git a/src/api/routes/webhooks/#webhook_id/index.ts b/src/api/routes/webhooks/#webhook_id/index.ts
index 9c0a6418..8844b8ef 100644 --- a/src/api/routes/webhooks/#webhook_id/index.ts +++ b/src/api/routes/webhooks/#webhook_id/index.ts
@@ -6,141 +6,141 @@ import { WebhookUpdateSchema } from "@spacebar/schemas"; const router = Router({ mergeParams: true }); router.get( - "/", - route({ - description: "Returns a webhook object for the given id. Requires the MANAGE_WEBHOOKS permission or to be the owner of the webhook.", - responses: { - 200: { - body: "APIWebhook", - }, - 404: {}, - }, - }), - async (req: Request, res: Response) => { - const { webhook_id } = req.params; - const webhook = await Webhook.findOneOrFail({ - where: { id: webhook_id }, - relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"], - }); + "/", + route({ + description: "Returns a webhook object for the given id. Requires the MANAGE_WEBHOOKS permission or to be the owner of the webhook.", + responses: { + 200: { + body: "APIWebhook", + }, + 404: {}, + }, + }), + async (req: Request, res: Response) => { + const { webhook_id } = req.params; + const webhook = await Webhook.findOneOrFail({ + where: { id: webhook_id }, + relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"], + }); - if (webhook.guild_id) { - const permission = await getPermission(req.user_id, webhook.guild_id); + if (webhook.guild_id) { + const permission = await getPermission(req.user_id, webhook.guild_id); - if (!permission.has("MANAGE_WEBHOOKS")) throw DiscordApiErrors.UNKNOWN_WEBHOOK; - } else if (webhook.user_id != req.user_id) throw DiscordApiErrors.UNKNOWN_WEBHOOK; + if (!permission.has("MANAGE_WEBHOOKS")) throw DiscordApiErrors.UNKNOWN_WEBHOOK; + } else if (webhook.user_id != req.user_id) throw DiscordApiErrors.UNKNOWN_WEBHOOK; - return res.json({ - ...webhook, - url: Config.get().api.endpointPublic + "/webhooks/" + webhook.id + "/" + webhook.token, - }); - }, + return res.json({ + ...webhook, + url: Config.get().api.endpointPublic + "/webhooks/" + webhook.id + "/" + webhook.token, + }); + }, ); router.delete( - "/", - route({ - responses: { - 204: {}, - 400: { - body: "APIErrorResponse", - }, - 404: {}, - }, - }), - async (req: Request, res: Response) => { - const { webhook_id } = req.params; + "/", + route({ + responses: { + 204: {}, + 400: { + body: "APIErrorResponse", + }, + 404: {}, + }, + }), + async (req: Request, res: Response) => { + const { webhook_id } = req.params; - const webhook = await Webhook.findOneOrFail({ - where: { id: webhook_id }, - relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"], - }); + const webhook = await Webhook.findOneOrFail({ + where: { id: webhook_id }, + relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"], + }); - if (webhook.guild_id) { - const permission = await getPermission(req.user_id, webhook.guild_id); + if (webhook.guild_id) { + const permission = await getPermission(req.user_id, webhook.guild_id); - if (!permission.has("MANAGE_WEBHOOKS")) throw DiscordApiErrors.UNKNOWN_WEBHOOK; - } else if (webhook.user_id != req.user_id) throw DiscordApiErrors.UNKNOWN_WEBHOOK; + if (!permission.has("MANAGE_WEBHOOKS")) throw DiscordApiErrors.UNKNOWN_WEBHOOK; + } else if (webhook.user_id != req.user_id) throw DiscordApiErrors.UNKNOWN_WEBHOOK; - const channel_id = webhook.channel_id; - await Webhook.delete({ id: webhook_id }); + const channel_id = webhook.channel_id; + await Webhook.delete({ id: webhook_id }); - await emitEvent({ - event: "WEBHOOKS_UPDATE", - channel_id, - data: { - channel_id, - guild_id: webhook.guild_id, - }, - } as WebhooksUpdateEvent); + await emitEvent({ + event: "WEBHOOKS_UPDATE", + channel_id, + data: { + channel_id, + guild_id: webhook.guild_id, + }, + } as WebhooksUpdateEvent); - res.sendStatus(204); - }, + res.sendStatus(204); + }, ); router.patch( - "/", - route({ - requestBody: "WebhookUpdateSchema", - responses: { - 200: { - body: "WebhookCreateResponse", - }, - 400: { - body: "APIErrorResponse", - }, - 403: {}, - 404: {}, - }, - }), - async (req: Request, res: Response) => { - const { webhook_id } = req.params; - const body = req.body as WebhookUpdateSchema; + "/", + route({ + requestBody: "WebhookUpdateSchema", + responses: { + 200: { + body: "WebhookCreateResponse", + }, + 400: { + body: "APIErrorResponse", + }, + 403: {}, + 404: {}, + }, + }), + async (req: Request, res: Response) => { + const { webhook_id } = req.params; + const body = req.body as WebhookUpdateSchema; - const webhook = await Webhook.findOneOrFail({ - where: { id: webhook_id }, - relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"], - }); + const webhook = await Webhook.findOneOrFail({ + where: { id: webhook_id }, + relations: ["user", "channel", "source_channel", "guild", "source_guild", "application"], + }); - if (webhook.guild_id) { - const permission = await getPermission(req.user_id, webhook.guild_id); + if (webhook.guild_id) { + const permission = await getPermission(req.user_id, webhook.guild_id); - if (!permission.has("MANAGE_WEBHOOKS")) throw DiscordApiErrors.UNKNOWN_WEBHOOK; - } else if (webhook.user_id != req.user_id) throw DiscordApiErrors.UNKNOWN_WEBHOOK; + if (!permission.has("MANAGE_WEBHOOKS")) throw DiscordApiErrors.UNKNOWN_WEBHOOK; + } else if (webhook.user_id != req.user_id) throw DiscordApiErrors.UNKNOWN_WEBHOOK; - if (!body.name && !body.avatar && !body.channel_id) { - throw new HTTPError("Empty webhook updates are not allowed", 50006); - } + if (!body.name && !body.avatar && !body.channel_id) { + throw new HTTPError("Empty webhook updates are not allowed", 50006); + } - if (body.avatar) body.avatar = await handleFile(`/avatars/${webhook_id}`, body.avatar as string); + if (body.avatar) body.avatar = await handleFile(`/avatars/${webhook_id}`, body.avatar as string); - if (body.name) { - ValidateName(body.name); - } + if (body.name) { + ValidateName(body.name); + } - const channel_id = body.channel_id || webhook.channel_id; - webhook.assign(body); + const channel_id = body.channel_id || webhook.channel_id; + webhook.assign(body); - if (body.channel_id) - webhook.assign({ - channel: await Channel.findOneOrFail({ - where: { id: channel_id }, - }), - }); + if (body.channel_id) + webhook.assign({ + channel: await Channel.findOneOrFail({ + where: { id: channel_id }, + }), + }); - await Promise.all([ - webhook.save(), - emitEvent({ - event: "WEBHOOKS_UPDATE", - channel_id, - data: { - channel_id, - guild_id: webhook.guild_id, - }, - } as WebhooksUpdateEvent), - ]); + await Promise.all([ + webhook.save(), + emitEvent({ + event: "WEBHOOKS_UPDATE", + channel_id, + data: { + channel_id, + guild_id: webhook.guild_id, + }, + } as WebhooksUpdateEvent), + ]); - res.json(webhook); - }, + res.json(webhook); + }, ); export default router; diff --git a/src/api/start.ts b/src/api/start.ts
index cd3373be..96c0c348 100644 --- a/src/api/start.ts +++ b/src/api/start.ts
@@ -28,30 +28,30 @@ import cluster from "cluster"; import os from "os"; let cores = 1; try { - cores = Number(process.env.THREADS) || os.cpus().length; + cores = Number(process.env.THREADS) || os.cpus().length; } catch { - console.log("[API] Failed to get thread count! Using 1..."); + console.log("[API] Failed to get thread count! Using 1..."); } if (cluster.isPrimary && process.env.NODE_ENV == "production") { - console.log(`Primary PID: ${process.pid}`); + console.log(`Primary PID: ${process.pid}`); - // Fork workers. - for (let i = 0; i < cores; i++) { - cluster.fork(); - } + // Fork workers. + for (let i = 0; i < cores; i++) { + cluster.fork(); + } - cluster.on("exit", (worker) => { - console.log(`Worker ${worker.process.pid} died, restarting worker`); - cluster.fork(); - }); + cluster.on("exit", (worker) => { + console.log(`Worker ${worker.process.pid} died, restarting worker`); + cluster.fork(); + }); } else { - const port = Number(process.env.PORT) || 3001; + const port = Number(process.env.PORT) || 3001; - const server = new SpacebarServer({ port }); - server.start().catch(console.error); + const server = new SpacebarServer({ port }); + server.start().catch(console.error); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - global.server = server; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + global.server = server; } diff --git a/src/api/util/handlers/Instance.ts b/src/api/util/handlers/Instance.ts
index 86dda74b..e71c3eda 100644 --- a/src/api/util/handlers/Instance.ts +++ b/src/api/util/handlers/Instance.ts
@@ -21,34 +21,34 @@ import { Like } from "typeorm"; import { setInterval } from "timers"; export async function initInstance() { - // TODO: clean up database and delete tombstone data - // TODO: set first user as instance administrator/or generate one if none exists and output it in the terminal + // TODO: clean up database and delete tombstone data + // TODO: set first user as instance administrator/or generate one if none exists and output it in the terminal - // create default guild and add it to auto join - // TODO: check if any current user is not part of autoJoinGuilds - // const { autoJoin } = Config.get().guild; + // create default guild and add it to auto join + // TODO: check if any current user is not part of autoJoinGuilds + // const { autoJoin } = Config.get().guild; - // if (autoJoin.enabled && !autoJoin.guilds?.length) { - // const guild = await Guild.findOne({ where: {}, select: ["id"] }); - // if (guild) { - // await Config.set({ guild: { autoJoin: { guilds: [guild.id] } } }); - // } - // } + // if (autoJoin.enabled && !autoJoin.guilds?.length) { + // const guild = await Guild.findOne({ where: {}, select: ["id"] }); + // if (guild) { + // await Config.set({ guild: { autoJoin: { guilds: [guild.id] } } }); + // } + // } - // TODO: do no clear sessions for instance cluster - // await Session.clear(); // This is now used as part of authentication... - // ... but we can still expire temporary sessions for legacy tokens - setInterval( - async () => { - for await (const session of await Session.createQueryBuilder("session").where("last_seen = '1970/01/01'").select().stream()) { - // session object has all fields prefixed with `session_`... thanks typeorm - if (TimeSpan.fromDates((session.session_created_at as Date).getTime(), new Date().getTime()).totalHours > 1) { - console.log(`[API/Instance.ts] Deleting unused session ${session.session_session_id} created at ${session.session_created_at}`); - await Session.delete({ session_id: session.session_session_id }); - } - } - }, - 1000 * 60 * 5, - ); - // await Session.delete({ session_id: Like("TEMP_%") }); + // TODO: do no clear sessions for instance cluster + // await Session.clear(); // This is now used as part of authentication... + // ... but we can still expire temporary sessions for legacy tokens + setInterval( + async () => { + for await (const session of await Session.createQueryBuilder("session").where("last_seen = '1970/01/01'").select().stream()) { + // session object has all fields prefixed with `session_`... thanks typeorm + if (TimeSpan.fromDates((session.session_created_at as Date).getTime(), new Date().getTime()).totalHours > 1) { + console.log(`[API/Instance.ts] Deleting unused session ${session.session_session_id} created at ${session.session_created_at}`); + await Session.delete({ session_id: session.session_session_id }); + } + } + }, + 1000 * 60 * 5, + ); + // await Session.delete({ session_id: Like("TEMP_%") }); } diff --git a/src/api/util/handlers/Message.ts b/src/api/util/handlers/Message.ts
index 06a3e081..65583f53 100644 --- a/src/api/util/handlers/Message.ts +++ b/src/api/util/handlers/Message.ts
@@ -18,36 +18,36 @@ import { EmbedHandlers } from "@spacebar/api"; import { - Application, - Attachment, - Channel, - Config, - EmbedCache, - emitEvent, - EVERYONE_MENTION, - getPermission, - getRights, - Guild, - HERE_MENTION, - Message, - MessageCreateEvent, - MessageUpdateEvent, - Role, - ROLE_MENTION, - Sticker, - User, - //CHANNEL_MENTION, - USER_MENTION, - Webhook, - handleFile, - Permissions, - normalizeUrl, - DiscordApiErrors, - CloudAttachment, - ReadState, - Member, - Session, - MessageFlags, + Application, + Attachment, + Channel, + Config, + EmbedCache, + emitEvent, + EVERYONE_MENTION, + getPermission, + getRights, + Guild, + HERE_MENTION, + Message, + MessageCreateEvent, + MessageUpdateEvent, + Role, + ROLE_MENTION, + Sticker, + User, + //CHANNEL_MENTION, + USER_MENTION, + Webhook, + handleFile, + Permissions, + normalizeUrl, + DiscordApiErrors, + CloudAttachment, + ReadState, + Member, + Session, + MessageFlags, } from "@spacebar/util"; import { HTTPError } from "lambert-server"; import { In, Or, Equal, IsNull } from "typeorm"; @@ -59,515 +59,515 @@ const allow_empty = false; const LINK_REGEX = /<?https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)>?/g; export async function handleMessage(opts: MessageOptions): Promise<Message> { - const channel = await Channel.findOneOrFail({ - where: { id: opts.channel_id }, - relations: ["recipients"], - }); - if (!channel || !opts.channel_id) throw new HTTPError("Channel not found", 404); + const channel = await Channel.findOneOrFail({ + where: { id: opts.channel_id }, + relations: ["recipients"], + }); + if (!channel || !opts.channel_id) throw new HTTPError("Channel not found", 404); - let permission: undefined | Permissions; - const limit = channel.rate_limit_per_user; + let permission: undefined | Permissions; + const limit = channel.rate_limit_per_user; - if (limit) { - const lastMsgTime = (await Message.findOne({ where: { channel_id: channel.id, author_id: opts.author_id }, select: { timestamp: true }, order: { timestamp: "DESC" } })) - ?.timestamp; - if (lastMsgTime && Date.now() - limit * 1000 < +lastMsgTime) { - permission ||= await getPermission(opts.author_id, channel.guild_id, channel); - //FIXME MANAGE_MESSAGES and MANAGE_CHANNELS will need to be removed once they're gone as checks - if (!permission.has("MANAGE_MESSAGES") && !permission.has("MANAGE_CHANNELS") && !permission.has("BYPASS_SLOWMODE")) { - throw DiscordApiErrors.SLOWMODE_RATE_LIMIT; - } - } - } + if (limit) { + const lastMsgTime = (await Message.findOne({ where: { channel_id: channel.id, author_id: opts.author_id }, select: { timestamp: true }, order: { timestamp: "DESC" } })) + ?.timestamp; + if (lastMsgTime && Date.now() - limit * 1000 < +lastMsgTime) { + permission ||= await getPermission(opts.author_id, channel.guild_id, channel); + //FIXME MANAGE_MESSAGES and MANAGE_CHANNELS will need to be removed once they're gone as checks + if (!permission.has("MANAGE_MESSAGES") && !permission.has("MANAGE_CHANNELS") && !permission.has("BYPASS_SLOWMODE")) { + throw DiscordApiErrors.SLOWMODE_RATE_LIMIT; + } + } + } - const stickers = opts.sticker_ids ? await Sticker.find({ where: { id: In(opts.sticker_ids) } }) : undefined; - // cloud attachments with indexes - const cloudAttachments = opts.attachments?.reduce( - (acc, att, index) => { - if ("uploaded_filename" in att) { - acc.push({ attachment: att, index }); - } - return acc; - }, - [] as { attachment: MessageCreateCloudAttachment; index: number }[], - ); + const stickers = opts.sticker_ids ? await Sticker.find({ where: { id: In(opts.sticker_ids) } }) : undefined; + // cloud attachments with indexes + const cloudAttachments = opts.attachments?.reduce( + (acc, att, index) => { + if ("uploaded_filename" in att) { + acc.push({ attachment: att, index }); + } + return acc; + }, + [] as { attachment: MessageCreateCloudAttachment; index: number }[], + ); - const message = Message.create({ - ...opts, - poll: opts.poll, - sticker_items: stickers, - guild_id: channel.guild_id, - channel_id: opts.channel_id, - attachments: opts.attachments || [], - embeds: opts.embeds || [], - reactions: opts.reactions || [], - type: opts.type ?? 0, - mentions: [], - components: opts.components ?? undefined, // Fix Discord-Go? - }); - const ephermal = (message.flags & (1 << 6)) !== 0; + const message = Message.create({ + ...opts, + poll: opts.poll, + sticker_items: stickers, + guild_id: channel.guild_id, + channel_id: opts.channel_id, + attachments: opts.attachments || [], + embeds: opts.embeds || [], + reactions: opts.reactions || [], + type: opts.type ?? 0, + mentions: [], + components: opts.components ?? undefined, // Fix Discord-Go? + }); + const ephermal = (message.flags & (1 << 6)) !== 0; - if (cloudAttachments && cloudAttachments.length > 0) { - console.log("[Message] Processing attachments for message", message.id, ":", message.attachments); - const uploadedAttachments = await Promise.all( - cloudAttachments.map(async (att) => { - const cAtt = att.attachment; - const attEnt = await CloudAttachment.findOneOrFail({ - where: { - uploadFilename: cAtt.uploaded_filename, - }, - }); + if (cloudAttachments && cloudAttachments.length > 0) { + console.log("[Message] Processing attachments for message", message.id, ":", message.attachments); + const uploadedAttachments = await Promise.all( + cloudAttachments.map(async (att) => { + const cAtt = att.attachment; + const attEnt = await CloudAttachment.findOneOrFail({ + where: { + uploadFilename: cAtt.uploaded_filename, + }, + }); - const cloneResponse = await fetch(`${Config.get().cdn.endpointPrivate}/attachments/${attEnt.uploadFilename}/clone_to_message/${message.id}`, { - method: "POST", - headers: { - signature: Config.get().security.requestSignature || "", - }, - }); + const cloneResponse = await fetch(`${Config.get().cdn.endpointPrivate}/attachments/${attEnt.uploadFilename}/clone_to_message/${message.id}`, { + method: "POST", + headers: { + signature: Config.get().security.requestSignature || "", + }, + }); - if (!cloneResponse.ok) { - console.error(`[Message] Failed to clone attachment ${attEnt.userFilename} to message ${message.id}`); - throw new HTTPError("Failed to process attachment: " + (await cloneResponse.text()), 500); - } + if (!cloneResponse.ok) { + console.error(`[Message] Failed to clone attachment ${attEnt.userFilename} to message ${message.id}`); + throw new HTTPError("Failed to process attachment: " + (await cloneResponse.text()), 500); + } - const cloneRespBody = (await cloneResponse.json()) as { success: boolean; new_path: string }; + const cloneRespBody = (await cloneResponse.json()) as { success: boolean; new_path: string }; - const realAtt = Attachment.create({ - filename: attEnt.userFilename, - url: `${Config.get().cdn.endpointPublic}/${cloneRespBody.new_path}`, - proxy_url: `${Config.get().cdn.endpointPublic}/${cloneRespBody.new_path}`, - size: attEnt.size, - height: attEnt.height, - width: attEnt.width, - content_type: attEnt.contentType || attEnt.userOriginalContentType, - }); - await realAtt.save(); - return { attachment: realAtt, index: att.index }; - }), - ); - console.log("[Message] Processed attachments for message", message.id, ":", message.attachments); + const realAtt = Attachment.create({ + filename: attEnt.userFilename, + url: `${Config.get().cdn.endpointPublic}/${cloneRespBody.new_path}`, + proxy_url: `${Config.get().cdn.endpointPublic}/${cloneRespBody.new_path}`, + size: attEnt.size, + height: attEnt.height, + width: attEnt.width, + content_type: attEnt.contentType || attEnt.userOriginalContentType, + }); + await realAtt.save(); + return { attachment: realAtt, index: att.index }; + }), + ); + console.log("[Message] Processed attachments for message", message.id, ":", message.attachments); - for (const att of uploadedAttachments) { - message.attachments![att.index] = att.attachment; - } - } - // else console.log("[Message] No cloud attachments to process for message", message.id, ":", message.attachments); + for (const att of uploadedAttachments) { + message.attachments![att.index] = att.attachment; + } + } + // else console.log("[Message] No cloud attachments to process for message", message.id, ":", message.attachments); - if (message.content && message.content.length > Config.get().limits.message.maxCharacters) { - throw new HTTPError("Content length over max character limit"); - } + if (message.content && message.content.length > Config.get().limits.message.maxCharacters) { + throw new HTTPError("Content length over max character limit"); + } - if (opts.author_id) { - message.author = await User.getPublicUser(opts.author_id); - const rights = await getRights(opts.author_id); - rights.hasThrow("SEND_MESSAGES"); - } - if (opts.application_id) { - message.application = await Application.findOneOrFail({ - where: { id: opts.application_id }, - }); - } + if (opts.author_id) { + message.author = await User.getPublicUser(opts.author_id); + const rights = await getRights(opts.author_id); + rights.hasThrow("SEND_MESSAGES"); + } + if (opts.application_id) { + message.application = await Application.findOneOrFail({ + where: { id: opts.application_id }, + }); + } - if (opts.webhook_id) { - message.webhook = await Webhook.findOneOrFail({ - where: { id: opts.webhook_id }, - }); + if (opts.webhook_id) { + message.webhook = await Webhook.findOneOrFail({ + where: { id: opts.webhook_id }, + }); - message.author = - (await User.findOne({ - where: { id: opts.webhook_id }, - })) || undefined; + message.author = + (await User.findOne({ + where: { id: opts.webhook_id }, + })) || undefined; - if (!message.author) { - message.author = User.create({ - id: opts.webhook_id, - username: message.webhook.name, - discriminator: "0000", - avatar: message.webhook.avatar, - public_flags: 0, - premium: false, - premium_type: 0, - bot: true, - created_at: new Date(), - verified: true, - rights: "0", - data: { - valid_tokens_since: new Date(), - }, - }); + if (!message.author) { + message.author = User.create({ + id: opts.webhook_id, + username: message.webhook.name, + discriminator: "0000", + avatar: message.webhook.avatar, + public_flags: 0, + premium: false, + premium_type: 0, + bot: true, + created_at: new Date(), + verified: true, + rights: "0", + data: { + valid_tokens_since: new Date(), + }, + }); - await message.author.save(); - } + await message.author.save(); + } - if (opts.username) { - message.username = opts.username; - message.author.username = message.username; - } - if (opts.avatar_url) { - const avatarData = await fetch(opts.avatar_url); - const base64 = await avatarData.arrayBuffer().then((x) => Buffer.from(x).toString("base64")); + if (opts.username) { + message.username = opts.username; + message.author.username = message.username; + } + if (opts.avatar_url) { + const avatarData = await fetch(opts.avatar_url); + const base64 = await avatarData.arrayBuffer().then((x) => Buffer.from(x).toString("base64")); - const dataUri = "data:" + avatarData.headers.get("content-type") + ";base64," + base64; + const dataUri = "data:" + avatarData.headers.get("content-type") + ";base64," + base64; - message.avatar = await handleFile(`/avatars/${opts.webhook_id}`, dataUri as string); - message.author.avatar = message.avatar; - } - } else { - permission ||= await getPermission(opts.author_id, channel.guild_id, channel); - permission.hasThrow("SEND_MESSAGES"); - if (permission.cache.member) { - message.member = permission.cache.member; - } + message.avatar = await handleFile(`/avatars/${opts.webhook_id}`, dataUri as string); + message.author.avatar = message.avatar; + } + } else { + permission ||= await getPermission(opts.author_id, channel.guild_id, channel); + permission.hasThrow("SEND_MESSAGES"); + if (permission.cache.member) { + message.member = permission.cache.member; + } - if (opts.tts) permission.hasThrow("SEND_TTS_MESSAGES"); - if (opts.message_reference) { - permission.hasThrow("READ_MESSAGE_HISTORY"); - // code below has to be redone when we add custom message routing - if (message.guild_id !== null) { - const guild = await Guild.findOneOrFail({ - where: { id: channel.guild_id }, - }); - if (!opts.message_reference.guild_id) opts.message_reference.guild_id = channel.guild_id; - if (!opts.message_reference.channel_id) opts.message_reference.channel_id = opts.channel_id; + if (opts.tts) permission.hasThrow("SEND_TTS_MESSAGES"); + if (opts.message_reference) { + permission.hasThrow("READ_MESSAGE_HISTORY"); + // code below has to be redone when we add custom message routing + if (message.guild_id !== null) { + const guild = await Guild.findOneOrFail({ + where: { id: channel.guild_id }, + }); + if (!opts.message_reference.guild_id) opts.message_reference.guild_id = channel.guild_id; + if (!opts.message_reference.channel_id) opts.message_reference.channel_id = opts.channel_id; - if (!guild.features.includes("CROSS_CHANNEL_REPLIES")) { - if (opts.message_reference.guild_id !== channel.guild_id) throw new HTTPError("You can only reference messages from this guild"); - if (opts.message_reference.channel_id !== opts.channel_id) throw new HTTPError("You can only reference messages from this channel"); - } + if (!guild.features.includes("CROSS_CHANNEL_REPLIES")) { + if (opts.message_reference.guild_id !== channel.guild_id) throw new HTTPError("You can only reference messages from this guild"); + if (opts.message_reference.channel_id !== opts.channel_id) throw new HTTPError("You can only reference messages from this channel"); + } - message.message_reference = opts.message_reference; - message.referenced_message = await Message.findOneOrFail({ - where: { - id: opts.message_reference.message_id, - }, - relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"], - }); + message.message_reference = opts.message_reference; + message.referenced_message = await Message.findOneOrFail({ + where: { + id: opts.message_reference.message_id, + }, + relations: ["author", "webhook", "application", "mentions", "mention_roles", "mention_channels", "sticker_items", "attachments"], + }); - if (message.referenced_message.channel_id && message.referenced_message.channel_id !== opts.message_reference.channel_id) - throw new HTTPError("Referenced message not found in the specified channel", 404); - if (message.referenced_message.guild_id && message.referenced_message.guild_id !== opts.message_reference.guild_id) - throw new HTTPError("Referenced message not found in the specified channel", 404); - } - /** Q: should be checked if the referenced message exists? ANSWER: NO + if (message.referenced_message.channel_id && message.referenced_message.channel_id !== opts.message_reference.channel_id) + throw new HTTPError("Referenced message not found in the specified channel", 404); + if (message.referenced_message.guild_id && message.referenced_message.guild_id !== opts.message_reference.guild_id) + throw new HTTPError("Referenced message not found in the specified channel", 404); + } + /** Q: should be checked if the referenced message exists? ANSWER: NO otherwise backfilling won't work **/ - message.type = MessageType.REPLY; - } - } + message.type = MessageType.REPLY; + } + } - // TODO: stickers/activity - if (!allow_empty && !opts.content && !opts.embeds?.length && !opts.attachments?.length && !opts.sticker_ids?.length && !opts.poll && !opts.components?.length) { - console.log("[Message] Rejecting empty message:", opts, message); - throw new HTTPError("Empty messages are not allowed", 50006); - } + // TODO: stickers/activity + if (!allow_empty && !opts.content && !opts.embeds?.length && !opts.attachments?.length && !opts.sticker_ids?.length && !opts.poll && !opts.components?.length) { + console.log("[Message] Rejecting empty message:", opts, message); + throw new HTTPError("Empty messages are not allowed", 50006); + } - let content = opts.content; + let content = opts.content; - // root@Rory - 20/02/2023 - This breaks channel mentions in test client. We're not sure this was used in older clients. - //const mention_channel_ids = [] as string[]; - const mention_role_ids = [] as string[]; - const mention_user_ids = [] as string[]; - let mention_everyone = false; + // root@Rory - 20/02/2023 - This breaks channel mentions in test client. We're not sure this was used in older clients. + //const mention_channel_ids = [] as string[]; + const mention_role_ids = [] as string[]; + const mention_user_ids = [] as string[]; + let mention_everyone = false; - if (content) { - // TODO: explicit-only mentions - message.content = content.trim(); - content = content.replace(/ *`[^)]*` */g, ""); // remove codeblocks - // root@Rory - 20/02/2023 - This breaks channel mentions in test client. We're not sure this was used in older clients. - /*for (const [, mention] of content.matchAll(CHANNEL_MENTION)) { + if (content) { + // TODO: explicit-only mentions + message.content = content.trim(); + content = content.replace(/ *`[^)]*` */g, ""); // remove codeblocks + // root@Rory - 20/02/2023 - This breaks channel mentions in test client. We're not sure this was used in older clients. + /*for (const [, mention] of content.matchAll(CHANNEL_MENTION)) { if (!mention_channel_ids.includes(mention)) mention_channel_ids.push(mention); }*/ - for (const [, mention] of content.matchAll(USER_MENTION)) { - if (!mention_user_ids.includes(mention)) mention_user_ids.push(mention); - } + for (const [, mention] of content.matchAll(USER_MENTION)) { + if (!mention_user_ids.includes(mention)) mention_user_ids.push(mention); + } - await Promise.all( - Array.from(content.matchAll(ROLE_MENTION)).map(async ([, mention]) => { - const role = await Role.findOneOrFail({ - where: { id: mention, guild_id: channel.guild_id }, - }); - if (role.mentionable || opts.webhook_id || permission?.has("MANAGE_ROLES")) { - mention_role_ids.push(mention); - } - }), - ); + await Promise.all( + Array.from(content.matchAll(ROLE_MENTION)).map(async ([, mention]) => { + const role = await Role.findOneOrFail({ + where: { id: mention, guild_id: channel.guild_id }, + }); + if (role.mentionable || opts.webhook_id || permission?.has("MANAGE_ROLES")) { + mention_role_ids.push(mention); + } + }), + ); - if (opts.webhook_id || permission?.has("MENTION_EVERYONE")) { - mention_everyone = !!content.match(EVERYONE_MENTION) || !!content.match(HERE_MENTION); - } - } + if (opts.webhook_id || permission?.has("MENTION_EVERYONE")) { + mention_everyone = !!content.match(EVERYONE_MENTION) || !!content.match(HERE_MENTION); + } + } - if (message.message_reference?.message_id) { - const referencedMessage = await Message.findOne({ - where: { - id: message.message_reference.message_id, - channel_id: message.channel_id, - }, - }); - if (referencedMessage && referencedMessage.author_id !== message.author_id) { - message.mentions.push( - User.create({ - id: referencedMessage.author_id, - }), - ); - } - } + if (message.message_reference?.message_id) { + const referencedMessage = await Message.findOne({ + where: { + id: message.message_reference.message_id, + channel_id: message.channel_id, + }, + }); + if (referencedMessage && referencedMessage.author_id !== message.author_id) { + message.mentions.push( + User.create({ + id: referencedMessage.author_id, + }), + ); + } + } - // root@Rory - 20/02/2023 - This breaks channel mentions in test client. We're not sure this was used in older clients. - /*message.mention_channels = mention_channel_ids.map((x) => + // root@Rory - 20/02/2023 - This breaks channel mentions in test client. We're not sure this was used in older clients. + /*message.mention_channels = mention_channel_ids.map((x) => Channel.create({ id: x }), );*/ - message.mention_roles = ( - await Promise.all( - mention_role_ids.map((x) => { - return Role.findOne({ where: { id: x } }); - }), - ) - ).filter((role) => role !== null); + message.mention_roles = ( + await Promise.all( + mention_role_ids.map((x) => { + return Role.findOne({ where: { id: x } }); + }), + ) + ).filter((role) => role !== null); - message.mentions = [ - ...message.mentions, - ...( - await Promise.all( - mention_user_ids.map((x) => { - return User.findOne({ where: { id: x } }); - }), - ) - ).filter((user) => user !== null), - ]; + message.mentions = [ + ...message.mentions, + ...( + await Promise.all( + mention_user_ids.map((x) => { + return User.findOne({ where: { id: x } }); + }), + ) + ).filter((user) => user !== null), + ]; - message.mention_everyone = mention_everyone; - async function fillInMissingIDs(ids: string[]) { - const states = await ReadState.findBy({ - user_id: Or(...ids.map((id) => Equal(id))), - channel_id: channel.id, - }); - const users = new Set(ids); - states.forEach((state) => users.delete(state.user_id)); - if (!users.size) { - return; - } - return Promise.all( - [...users].map((user_id) => { - return ReadState.create({ user_id, channel_id: channel.id }).save(); - }), - ); - } - if (ephermal) { - const id = message.interaction_metadata?.user_id; - if (id) { - let pinged = mention_everyone || channel.type === ChannelType.DM || channel.type === ChannelType.GROUP_DM; - if (!pinged) pinged = !!message.mentions.find((user) => user.id === id); - if (!pinged) pinged = !!(await Member.find({ where: { id, roles: Or(...message.mention_roles.map(({ id }) => Equal(id))) } })); - if (pinged) { - //stuff - } - } - } else if ((!!message.content?.match(EVERYONE_MENTION) && permission?.has("MENTION_EVERYONE")) || channel.type === ChannelType.DM || channel.type === ChannelType.GROUP_DM) { - if (channel.type === ChannelType.DM || channel.type === ChannelType.GROUP_DM) { - if (channel.recipients) { - await fillInMissingIDs(channel.recipients.map(({ user_id }) => user_id)); - } - } else { - console.log(channel.guild_id); - await fillInMissingIDs((await Member.find({ where: { guild_id: channel.guild_id } })).map(({ id }) => id)); - } - const repository = ReadState.getRepository(); - const condition = { channel_id: channel.id }; - await repository.update({ ...condition, mention_count: IsNull() }, { mention_count: 0 }); - await repository.increment(condition, "mention_count", 1); - } else { - const users = new Set<string>([ - ...(message.mention_roles.length - ? await Member.find({ - where: [ - ...message.mention_roles.map((role) => { - return { roles: { id: role.id } }; - }), - ], - }) - : [] - ).map((member) => member.id), - ...message.mentions.map((user) => user.id), - ]); - if (!!message.content?.match(HERE_MENTION) && permission?.has("MENTION_EVERYONE")) { - const ids = (await Member.find({ where: { guild_id: channel.guild_id } })).map(({ id }) => id); - (await Session.find({ where: { user_id: Or(...ids.map((id) => Equal(id))) } })).forEach(({ user_id }) => users.add(user_id)); - } - if (users.size) { - const repository = ReadState.getRepository(); - const condition = { user_id: Or(...[...users].map((id) => Equal(id))), channel_id: channel.id }; + message.mention_everyone = mention_everyone; + async function fillInMissingIDs(ids: string[]) { + const states = await ReadState.findBy({ + user_id: Or(...ids.map((id) => Equal(id))), + channel_id: channel.id, + }); + const users = new Set(ids); + states.forEach((state) => users.delete(state.user_id)); + if (!users.size) { + return; + } + return Promise.all( + [...users].map((user_id) => { + return ReadState.create({ user_id, channel_id: channel.id }).save(); + }), + ); + } + if (ephermal) { + const id = message.interaction_metadata?.user_id; + if (id) { + let pinged = mention_everyone || channel.type === ChannelType.DM || channel.type === ChannelType.GROUP_DM; + if (!pinged) pinged = !!message.mentions.find((user) => user.id === id); + if (!pinged) pinged = !!(await Member.find({ where: { id, roles: Or(...message.mention_roles.map(({ id }) => Equal(id))) } })); + if (pinged) { + //stuff + } + } + } else if ((!!message.content?.match(EVERYONE_MENTION) && permission?.has("MENTION_EVERYONE")) || channel.type === ChannelType.DM || channel.type === ChannelType.GROUP_DM) { + if (channel.type === ChannelType.DM || channel.type === ChannelType.GROUP_DM) { + if (channel.recipients) { + await fillInMissingIDs(channel.recipients.map(({ user_id }) => user_id)); + } + } else { + console.log(channel.guild_id); + await fillInMissingIDs((await Member.find({ where: { guild_id: channel.guild_id } })).map(({ id }) => id)); + } + const repository = ReadState.getRepository(); + const condition = { channel_id: channel.id }; + await repository.update({ ...condition, mention_count: IsNull() }, { mention_count: 0 }); + await repository.increment(condition, "mention_count", 1); + } else { + const users = new Set<string>([ + ...(message.mention_roles.length + ? await Member.find({ + where: [ + ...message.mention_roles.map((role) => { + return { roles: { id: role.id } }; + }), + ], + }) + : [] + ).map((member) => member.id), + ...message.mentions.map((user) => user.id), + ]); + if (!!message.content?.match(HERE_MENTION) && permission?.has("MENTION_EVERYONE")) { + const ids = (await Member.find({ where: { guild_id: channel.guild_id } })).map(({ id }) => id); + (await Session.find({ where: { user_id: Or(...ids.map((id) => Equal(id))) } })).forEach(({ user_id }) => users.add(user_id)); + } + if (users.size) { + const repository = ReadState.getRepository(); + const condition = { user_id: Or(...[...users].map((id) => Equal(id))), channel_id: channel.id }; - await fillInMissingIDs([...users]); + await fillInMissingIDs([...users]); - await repository.update({ ...condition, mention_count: IsNull() }, { mention_count: 0 }); - await repository.increment(condition, "mention_count", 1); - } - } + await repository.update({ ...condition, mention_count: IsNull() }, { mention_count: 0 }); + await repository.increment(condition, "mention_count", 1); + } + } - // TODO: check and put it all in the body + // TODO: check and put it all in the body - return message; + return message; } // TODO: cache link result in db export async function postHandleMessage(message: Message) { - const content = message.content?.replace(/ *`[^)]*` */g, ""); // remove markdown + const content = message.content?.replace(/ *`[^)]*` */g, ""); // remove markdown - const linkMatches = content?.match(LINK_REGEX) || []; + const linkMatches = content?.match(LINK_REGEX) || []; - const data = { ...message }; + const data = { ...message }; - const currentNormalizedUrls = new Set<string>(); - for (const link of linkMatches) { - // Don't process links in <> - if (link.startsWith("<") && link.endsWith(">")) { - continue; - } - try { - const normalized = normalizeUrl(link); - currentNormalizedUrls.add(normalized); - } catch (e) { - continue; - } - } + const currentNormalizedUrls = new Set<string>(); + for (const link of linkMatches) { + // Don't process links in <> + if (link.startsWith("<") && link.endsWith(">")) { + continue; + } + try { + const normalized = normalizeUrl(link); + currentNormalizedUrls.add(normalized); + } catch (e) { + continue; + } + } - data.embeds.forEach((embed) => { - if (!embed.type) { - embed.type = EmbedType.rich; - } - }); - // Filter out embeds that could be links, start from scratch - data.embeds = data.embeds.filter((embed) => embed.type === "rich"); + data.embeds.forEach((embed) => { + if (!embed.type) { + embed.type = EmbedType.rich; + } + }); + // Filter out embeds that could be links, start from scratch + data.embeds = data.embeds.filter((embed) => embed.type === "rich"); - const seenNormalizedUrls = new Set<string>(); - const uniqueLinks: string[] = []; + const seenNormalizedUrls = new Set<string>(); + const uniqueLinks: string[] = []; - for (const link of linkMatches.slice(0, 20)) { - // embed max 20 links - TODO: make this configurable with instance policies - // Don't embed links in <> - if (link.startsWith("<") && link.endsWith(">")) continue; + for (const link of linkMatches.slice(0, 20)) { + // embed max 20 links - TODO: make this configurable with instance policies + // Don't embed links in <> + if (link.startsWith("<") && link.endsWith(">")) continue; - try { - const normalized = normalizeUrl(link); + try { + const normalized = normalizeUrl(link); - if (!seenNormalizedUrls.has(normalized)) { - seenNormalizedUrls.add(normalized); - uniqueLinks.push(link); - } - } catch (e) { - // Invalid URL, skip - continue; - } - } + if (!seenNormalizedUrls.has(normalized)) { + seenNormalizedUrls.add(normalized); + uniqueLinks.push(link); + } + } catch (e) { + // Invalid URL, skip + continue; + } + } - if (uniqueLinks.length === 0) { - // No valid unique links found, update message to remove old embeds - data.embeds = data.embeds.filter((embed) => embed.type === "rich"); - const author = data.author?.toPublicUser(); - const event = { - event: "MESSAGE_UPDATE", - channel_id: message.channel_id, - data: { - ...data, - author, - }, - } as MessageUpdateEvent; - await Promise.all([emitEvent(event), Message.update({ id: message.id, channel_id: message.channel_id }, { embeds: data.embeds })]); - return; - } + if (uniqueLinks.length === 0) { + // No valid unique links found, update message to remove old embeds + data.embeds = data.embeds.filter((embed) => embed.type === "rich"); + const author = data.author?.toPublicUser(); + const event = { + event: "MESSAGE_UPDATE", + channel_id: message.channel_id, + data: { + ...data, + author, + }, + } as MessageUpdateEvent; + await Promise.all([emitEvent(event), Message.update({ id: message.id, channel_id: message.channel_id }, { embeds: data.embeds })]); + return; + } - const cachePromises = []; + const cachePromises = []; - for (const link of uniqueLinks) { - let url: URL; - try { - url = new URL(link); - } catch (e) { - // Skip invalid URLs - continue; - } + for (const link of uniqueLinks) { + let url: URL; + try { + url = new URL(link); + } catch (e) { + // Skip invalid URLs + continue; + } - const normalizedUrl = normalizeUrl(link); + const normalizedUrl = normalizeUrl(link); - // Check cache using normalized URL - const cached = await EmbedCache.findOne({ - where: { url: normalizedUrl }, - }); + // Check cache using normalized URL + const cached = await EmbedCache.findOne({ + where: { url: normalizedUrl }, + }); - if (cached) { - data.embeds.push(cached.embed); - continue; - } + if (cached) { + data.embeds.push(cached.embed); + continue; + } - // bit gross, but whatever! - const endpointPublic = Config.get().cdn.endpointPublic; // lol - const handler = url.hostname === new URL(endpointPublic!).hostname ? EmbedHandlers["self"] : EmbedHandlers[url.hostname] || EmbedHandlers["default"]; + // bit gross, but whatever! + const endpointPublic = Config.get().cdn.endpointPublic; // lol + const handler = url.hostname === new URL(endpointPublic!).hostname ? EmbedHandlers["self"] : EmbedHandlers[url.hostname] || EmbedHandlers["default"]; - try { - let res = await handler(url); - if (!res) continue; - // tried to use shorthand but types didn't like me L - if (!Array.isArray(res)) res = [res]; + try { + let res = await handler(url); + if (!res) continue; + // tried to use shorthand but types didn't like me L + if (!Array.isArray(res)) res = [res]; - for (const embed of res) { - // Cache with normalized URL - const cache = EmbedCache.create({ - url: normalizedUrl, - embed: embed, - }); - cachePromises.push(cache.save()); - data.embeds.push(embed); - } - } catch (e) { - console.error(`[Embeds] Error while generating embed for ${link}`, e); - } - } + for (const embed of res) { + // Cache with normalized URL + const cache = EmbedCache.create({ + url: normalizedUrl, + embed: embed, + }); + cachePromises.push(cache.save()); + data.embeds.push(embed); + } + } catch (e) { + console.error(`[Embeds] Error while generating embed for ${link}`, e); + } + } - await Promise.all([ - emitEvent({ - event: "MESSAGE_UPDATE", - channel_id: message.channel_id, - data, - } as MessageUpdateEvent), - Message.update({ id: message.id, channel_id: message.channel_id }, { embeds: data.embeds }), - ...cachePromises, - ]); + await Promise.all([ + emitEvent({ + event: "MESSAGE_UPDATE", + channel_id: message.channel_id, + data, + } as MessageUpdateEvent), + Message.update({ id: message.id, channel_id: message.channel_id }, { embeds: data.embeds }), + ...cachePromises, + ]); } export async function sendMessage(opts: MessageOptions) { - const message = await handleMessage({ ...opts, timestamp: new Date() }); + const message = await handleMessage({ ...opts, timestamp: new Date() }); - const ephemeral = (message.flags & Number(MessageFlags.FLAGS.EPHEMERAL)) !== 0; - await Promise.all([ - Message.insert(message), - emitEvent({ - event: "MESSAGE_CREATE", - ...(ephemeral ? { user_id: message.interaction_metadata?.user_id } : { channel_id: message.channel_id }), - data: message.toJSON(), - } as MessageCreateEvent), - ]); + const ephemeral = (message.flags & Number(MessageFlags.FLAGS.EPHEMERAL)) !== 0; + await Promise.all([ + Message.insert(message), + emitEvent({ + event: "MESSAGE_CREATE", + ...(ephemeral ? { user_id: message.interaction_metadata?.user_id } : { channel_id: message.channel_id }), + data: message.toJSON(), + } as MessageCreateEvent), + ]); - // no await as it should catch error non-blockingly - postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e)); + // no await as it should catch error non-blockingly + postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e)); - return message; + return message; } interface MessageOptions extends MessageCreateSchema { - id?: string; - type?: MessageType; - pinned?: boolean; - author_id?: string; - webhook_id?: string; - application_id?: string; - embeds?: Embed[]; - reactions?: Reaction[]; - channel_id?: string; - attachments?: (MessageCreateAttachment | MessageCreateCloudAttachment | Attachment)[]; // why are we masking this? - edited_timestamp?: Date; - timestamp?: Date; - username?: string; - avatar_url?: string; + id?: string; + type?: MessageType; + pinned?: boolean; + author_id?: string; + webhook_id?: string; + application_id?: string; + embeds?: Embed[]; + reactions?: Reaction[]; + channel_id?: string; + attachments?: (MessageCreateAttachment | MessageCreateCloudAttachment | Attachment)[]; // why are we masking this? + edited_timestamp?: Date; + timestamp?: Date; + username?: string; + avatar_url?: string; } diff --git a/src/api/util/handlers/Voice.ts b/src/api/util/handlers/Voice.ts
index e34b6794..409338f7 100644 --- a/src/api/util/handlers/Voice.ts +++ b/src/api/util/handlers/Voice.ts
@@ -20,31 +20,31 @@ import { Config, IpDataClient } from "@spacebar/util"; import { distanceBetweenLocations } from "../utility/ipAddress"; export async function getVoiceRegions(ipAddress: string, vip: boolean) { - const regions = Config.get().regions; - const availableRegions = regions.available.filter((ar) => (vip ? true : !ar.vip)); - let optimalId = regions.default; + const regions = Config.get().regions; + const availableRegions = regions.available.filter((ar) => (vip ? true : !ar.vip)); + let optimalId = regions.default; - if (!regions.useDefaultAsOptimal) { - const clientIpAnalysis = await IpDataClient.getIpInfo(ipAddress); + if (!regions.useDefaultAsOptimal) { + const clientIpAnalysis = await IpDataClient.getIpInfo(ipAddress); - let min = Number.POSITIVE_INFINITY; + let min = Number.POSITIVE_INFINITY; - for (const ar of availableRegions) { - //TODO the endpoint location should be saved in the database if not already present to prevent IPAnalysis call - const dist = distanceBetweenLocations(clientIpAnalysis!, ar.location || (await IpDataClient.getIpInfo(ar.endpoint))!); + for (const ar of availableRegions) { + //TODO the endpoint location should be saved in the database if not already present to prevent IPAnalysis call + const dist = distanceBetweenLocations(clientIpAnalysis!, ar.location || (await IpDataClient.getIpInfo(ar.endpoint))!); - if (dist < min) { - min = dist; - optimalId = ar.id; - } - } - } + if (dist < min) { + min = dist; + optimalId = ar.id; + } + } + } - return availableRegions.map((ar) => ({ - id: ar.id, - name: ar.name, - custom: ar.custom, - deprecated: ar.deprecated, - optimal: ar.id === optimalId, - })); + return availableRegions.map((ar) => ({ + id: ar.id, + name: ar.name, + custom: ar.custom, + deprecated: ar.deprecated, + optimal: ar.id === optimalId, + })); } diff --git a/src/api/util/handlers/Webhook.ts b/src/api/util/handlers/Webhook.ts
index 54ef26ec..cd0f2f6e 100644 --- a/src/api/util/handlers/Webhook.ts +++ b/src/api/util/handlers/Webhook.ts
@@ -6,118 +6,118 @@ import { MoreThan } from "typeorm"; import { WebhookExecuteSchema } from "@spacebar/schemas"; export const executeWebhook = async (req: Request, res: Response) => { - const body = req.body as WebhookExecuteSchema; + const body = req.body as WebhookExecuteSchema; - const { webhook_id, token } = req.params; + const { webhook_id, token } = req.params; - const webhook = await Webhook.findOne({ - where: { - id: webhook_id, - }, - relations: ["channel", "guild", "application"], - }); + const webhook = await Webhook.findOne({ + where: { + id: webhook_id, + }, + relations: ["channel", "guild", "application"], + }); - if (!webhook) { - throw DiscordApiErrors.UNKNOWN_WEBHOOK; - } + if (!webhook) { + throw DiscordApiErrors.UNKNOWN_WEBHOOK; + } - if (webhook.token !== token) { - throw DiscordApiErrors.INVALID_WEBHOOK_TOKEN_PROVIDED; - } + if (webhook.token !== token) { + throw DiscordApiErrors.INVALID_WEBHOOK_TOKEN_PROVIDED; + } - if (body.username) { - ValidateName(body.username); - } + if (body.username) { + ValidateName(body.username); + } - // ensure one of content, embeds, components, or file is present - if (!body.content && !body.embeds && !body.components && !body.file && !body.attachments) { - throw DiscordApiErrors.CANNOT_SEND_EMPTY_MESSAGE; - } + // ensure one of content, embeds, components, or file is present + if (!body.content && !body.embeds && !body.components && !body.file && !body.attachments) { + throw DiscordApiErrors.CANNOT_SEND_EMPTY_MESSAGE; + } - const wait = req.query.wait === "true"; + const wait = req.query.wait === "true"; - if (!wait) { - res.status(204).send(); - } + if (!wait) { + res.status(204).send(); + } - const attachments: Attachment[] = []; + const attachments: Attachment[] = []; - if (!webhook.channel.isWritable()) { - if (wait) { - throw new HTTPError(`Cannot send messages to channel of type ${webhook.channel.type}`, 400); - } else { - return; - } - } + if (!webhook.channel.isWritable()) { + if (wait) { + throw new HTTPError(`Cannot send messages to channel of type ${webhook.channel.type}`, 400); + } else { + return; + } + } - // TODO: creating messages by users checks if the user can bypass rate limits, we cant do that on webhooks, but maybe we could check the application if there is one? - const limits = Config.get().limits; - if (limits.absoluteRate.register.enabled) { - const count = await Message.count({ - where: { - channel_id: webhook.channel_id, - timestamp: MoreThan(new Date(Date.now() - limits.absoluteRate.sendMessage.window)), - }, - }); + // TODO: creating messages by users checks if the user can bypass rate limits, we cant do that on webhooks, but maybe we could check the application if there is one? + const limits = Config.get().limits; + if (limits.absoluteRate.register.enabled) { + const count = await Message.count({ + where: { + channel_id: webhook.channel_id, + timestamp: MoreThan(new Date(Date.now() - limits.absoluteRate.sendMessage.window)), + }, + }); - if (count >= limits.absoluteRate.sendMessage.limit) - if (wait) { - throw FieldErrors({ - channel_id: { - code: "TOO_MANY_MESSAGES", - message: req.t("common:toomany.MESSAGE"), - }, - }); - } else { - return; - } - } + if (count >= limits.absoluteRate.sendMessage.limit) + if (wait) { + throw FieldErrors({ + channel_id: { + code: "TOO_MANY_MESSAGES", + message: req.t("common:toomany.MESSAGE"), + }, + }); + } else { + return; + } + } - const files = (req.files as Express.Multer.File[]) ?? []; - for (const currFile of files) { - try { - const file = await uploadFile(`/attachments/${webhook.channel.id}`, currFile); - attachments.push(Attachment.create({ ...file, proxy_url: file.url })); - } catch (error) { - if (wait) res.status(400).json({ message: error?.toString() }); - return; - } - } + const files = (req.files as Express.Multer.File[]) ?? []; + for (const currFile of files) { + try { + const file = await uploadFile(`/attachments/${webhook.channel.id}`, currFile); + attachments.push(Attachment.create({ ...file, proxy_url: file.url })); + } catch (error) { + if (wait) res.status(400).json({ message: error?.toString() }); + return; + } + } - const embeds = body.embeds || []; - const message = await handleMessage({ - ...body, - username: body.username || webhook.name, - avatar_url: body.avatar_url || webhook.avatar, - type: 0, - pinned: false, - webhook_id: webhook.id, - application_id: webhook.application?.id, - embeds, - // TODO: Support thread_id/thread_name once threads are implemented - channel_id: webhook.channel_id, - attachments, - timestamp: new Date(), - }); + const embeds = body.embeds || []; + const message = await handleMessage({ + ...body, + username: body.username || webhook.name, + avatar_url: body.avatar_url || webhook.avatar, + type: 0, + pinned: false, + webhook_id: webhook.id, + application_id: webhook.application?.id, + embeds, + // TODO: Support thread_id/thread_name once threads are implemented + channel_id: webhook.channel_id, + attachments, + timestamp: new Date(), + }); - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - //@ts-ignore dont care2 - message.edited_timestamp = null; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore dont care2 + message.edited_timestamp = null; - webhook.channel.last_message_id = message.id; + webhook.channel.last_message_id = message.id; - await Promise.all([ - message.save(), - webhook.channel.save(), - emitEvent({ - event: "MESSAGE_CREATE", - channel_id: webhook.channel_id, - data: message, - } as MessageCreateEvent), - ]); + await Promise.all([ + message.save(), + webhook.channel.save(), + emitEvent({ + event: "MESSAGE_CREATE", + channel_id: webhook.channel_id, + data: message, + } as MessageCreateEvent), + ]); - // no await as it shouldnt block the message send function and silently catch error - postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e)); - if (wait) res.json(message); - return; + // no await as it shouldnt block the message send function and silently catch error + postHandleMessage(message).catch((e) => console.error("[Message] post-message handler failed", e)); + if (wait) res.json(message); + return; }; diff --git a/src/api/util/handlers/route.ts b/src/api/util/handlers/route.ts
index bb38c530..7ae00a55 100644 --- a/src/api/util/handlers/route.ts +++ b/src/api/util/handlers/route.ts
@@ -22,107 +22,107 @@ import { NextFunction, Request, Response } from "express"; import { ajv } from "@spacebar/schemas"; const ignoredRequestSchemas = [ - // skip validation for settings proto JSON updates - TODO: figure out if this even possible to fix? - "SettingsProtoUpdateJsonSchema", + // skip validation for settings proto JSON updates - TODO: figure out if this even possible to fix? + "SettingsProtoUpdateJsonSchema", ]; declare global { - // TODO: fix this - // eslint-disable-next-line @typescript-eslint/no-namespace - namespace Express { - interface Request { - permission?: Permissions; - } - } + // TODO: fix this + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace Express { + interface Request { + permission?: Permissions; + } + } } export type RouteResponse = { - status?: number; - body?: `${string}Response`; - headers?: Record<string, string>; + status?: number; + body?: `${string}Response`; + headers?: Record<string, string>; }; export interface RouteOptions { - permission?: PermissionResolvable; - right?: RightResolvable; - requestBody?: `${string}Schema`; // typescript interface name - responses?: { - [status: number]: { - // body?: `${string}Response`; - body?: string; - }; - }; - event?: EVENT | EVENT[]; - summary?: string; - description?: string; - query?: { - [key: string]: { - type: string; - required?: boolean; - description?: string; - values?: string[]; - }; - }; - deprecated?: boolean; - // test?: { - // response?: RouteResponse; - // body?: unknown; - // path?: string; - // event?: EVENT | EVENT[]; - // headers?: Record<string, string>; - // }; + permission?: PermissionResolvable; + right?: RightResolvable; + requestBody?: `${string}Schema`; // typescript interface name + responses?: { + [status: number]: { + // body?: `${string}Response`; + body?: string; + }; + }; + event?: EVENT | EVENT[]; + summary?: string; + description?: string; + query?: { + [key: string]: { + type: string; + required?: boolean; + description?: string; + values?: string[]; + }; + }; + deprecated?: boolean; + // test?: { + // response?: RouteResponse; + // body?: unknown; + // path?: string; + // event?: EVENT | EVENT[]; + // headers?: Record<string, string>; + // }; } export function route(opts: RouteOptions) { - let validate: AnyValidateFunction | undefined; - if (opts.requestBody) { - try { - validate = ajv.getSchema(opts.requestBody); - } catch (e) { - console.error("AJV getSchema failed!"); - throw e; - } + let validate: AnyValidateFunction | undefined; + if (opts.requestBody) { + try { + validate = ajv.getSchema(opts.requestBody); + } catch (e) { + console.error("AJV getSchema failed!"); + throw e; + } - if (!validate) throw new Error(`Body schema ${opts.requestBody} not found`); - } + if (!validate) throw new Error(`Body schema ${opts.requestBody} not found`); + } - return async (req: Request, res: Response, next: NextFunction) => { - if (opts.permission) { - req.permission = await getPermission(req.user_id, req.params.guild_id, req.params.channel_id); + return async (req: Request, res: Response, next: NextFunction) => { + if (opts.permission) { + req.permission = await getPermission(req.user_id, req.params.guild_id, req.params.channel_id); - const requiredPerms = Array.isArray(opts.permission) ? opts.permission : [opts.permission]; - requiredPerms.forEach((perm) => { - // bitfield comparison: check if user lacks certain permission - if (!req.permission!.has(new Permissions(perm))) { - throw DiscordApiErrors.MISSING_PERMISSIONS.withParams(perm as string); - } - }); - } + const requiredPerms = Array.isArray(opts.permission) ? opts.permission : [opts.permission]; + requiredPerms.forEach((perm) => { + // bitfield comparison: check if user lacks certain permission + if (!req.permission!.has(new Permissions(perm))) { + throw DiscordApiErrors.MISSING_PERMISSIONS.withParams(perm as string); + } + }); + } - if (opts.right) { - const required = new Rights(opts.right); - req.rights = await getRights(req.user_id); + if (opts.right) { + const required = new Rights(opts.right); + req.rights = await getRights(req.user_id); - if (!req.rights || !req.rights.has(required)) { - throw SpacebarApiErrors.MISSING_RIGHTS.withParams(opts.right as string); - } - } + if (!req.rights || !req.rights.has(required)) { + throw SpacebarApiErrors.MISSING_RIGHTS.withParams(opts.right as string); + } + } - if (validate && !ignoredRequestSchemas.includes(opts.requestBody!)) { - const valid = validate(req.body); - if (!valid) { - const fields: Record<string, { code?: string; message: string }> = {}; - validate.errors?.forEach( - (x) => - (fields[x.instancePath.slice(1)] = { - code: x.keyword, - message: x.message || "", - }), - ); - if (process.env.LOG_VALIDATION_ERRORS) console.log(`[VALIDATION ERROR] ${req.method} ${req.originalUrl} - SCHEMA='${opts.requestBody}' -`, validate?.errors); - throw FieldErrors(fields, validate.errors!); - } - } - next(); - }; + if (validate && !ignoredRequestSchemas.includes(opts.requestBody!)) { + const valid = validate(req.body); + if (!valid) { + const fields: Record<string, { code?: string; message: string }> = {}; + validate.errors?.forEach( + (x) => + (fields[x.instancePath.slice(1)] = { + code: x.keyword, + message: x.message || "", + }), + ); + if (process.env.LOG_VALIDATION_ERRORS) console.log(`[VALIDATION ERROR] ${req.method} ${req.originalUrl} - SCHEMA='${opts.requestBody}' -`, validate?.errors); + throw FieldErrors(fields, validate.errors!); + } + } + next(); + }; } diff --git a/src/api/util/utility/Base64.ts b/src/api/util/utility/Base64.ts
index 7426d22b..78a56f63 100644 --- a/src/api/util/utility/Base64.ts +++ b/src/api/util/utility/Base64.ts
@@ -25,41 +25,41 @@ const b2s = alphabet.split(""); // 123 == 'z'.charCodeAt(0) + 1 const s2b = new Array(123); for (let i = 0; i < alphabet.length; i++) { - s2b[alphabet.charCodeAt(i)] = i; + s2b[alphabet.charCodeAt(i)] = i; } // number to base64 export const ntob = (n: number): string => { - if (n < 0) return `-${ntob(-n)}`; + if (n < 0) return `-${ntob(-n)}`; - let lo = n >>> 0; - let hi = (n / 4294967296) >>> 0; + let lo = n >>> 0; + let hi = (n / 4294967296) >>> 0; - let right = ""; - while (hi > 0) { - right = b2s[0x3f & lo] + right; - lo >>>= 6; - lo |= (0x3f & hi) << 26; - hi >>>= 6; - } + let right = ""; + while (hi > 0) { + right = b2s[0x3f & lo] + right; + lo >>>= 6; + lo |= (0x3f & hi) << 26; + hi >>>= 6; + } - let left = ""; - do { - left = b2s[0x3f & lo] + left; - lo >>>= 6; - } while (lo > 0); + let left = ""; + do { + left = b2s[0x3f & lo] + left; + lo >>>= 6; + } while (lo > 0); - return left + right; + return left + right; }; // base64 to number export const bton = (base64: string) => { - let number = 0; - const sign = base64.charAt(0) === "-" ? 1 : 0; + let number = 0; + const sign = base64.charAt(0) === "-" ? 1 : 0; - for (let i = sign; i < base64.length; i++) { - number = number * 64 + s2b[base64.charCodeAt(i)]; - } + for (let i = sign; i < base64.length; i++) { + number = number * 64 + s2b[base64.charCodeAt(i)]; + } - return sign ? -number : number; + return sign ? -number : number; }; diff --git a/src/api/util/utility/EmbedHandlers.ts b/src/api/util/utility/EmbedHandlers.ts
index 8bbd6283..2e50c536 100644 --- a/src/api/util/utility/EmbedHandlers.ts +++ b/src/api/util/utility/EmbedHandlers.ts
@@ -24,494 +24,494 @@ import { yellow } from "picocolors"; import probe from "probe-image-size"; export const DEFAULT_FETCH_OPTIONS: RequestInit = { - redirect: "follow", - headers: { - "user-agent": "Mozilla/5.0 (compatible; Spacebar/1.0; +https://github.com/spacebarchat/server)", - }, - // size: 1024 * 1024 * 5, // grabbed from config later - method: "GET", + redirect: "follow", + headers: { + "user-agent": "Mozilla/5.0 (compatible; Spacebar/1.0; +https://github.com/spacebarchat/server)", + }, + // size: 1024 * 1024 * 5, // grabbed from config later + method: "GET", }; const makeEmbedImage = (url: string | undefined, width: number | undefined, height: number | undefined): Required<EmbedImage> | undefined => { - if (!url || !width || !height) return undefined; - return { - url, - width, - height, - proxy_url: getProxyUrl(new URL(url), width, height), - }; + if (!url || !width || !height) return undefined; + return { + url, + width, + height, + proxy_url: getProxyUrl(new URL(url), width, height), + }; }; let hasWarnedAboutImagor = false; export const getProxyUrl = (url: URL, width: number, height: number): string => { - const { resizeWidthMax, resizeHeightMax, imagorServerUrl } = Config.get().cdn; - const secret = Config.get().security.requestSignature; - width = Math.min(width || 500, resizeWidthMax || width); - height = Math.min(height || 500, resizeHeightMax || width); + const { resizeWidthMax, resizeHeightMax, imagorServerUrl } = Config.get().cdn; + const secret = Config.get().security.requestSignature; + width = Math.min(width || 500, resizeWidthMax || width); + height = Math.min(height || 500, resizeHeightMax || width); - // Imagor - if (imagorServerUrl) { - const path = `${width}x${height}/${url.host}${url.pathname}`; + // Imagor + if (imagorServerUrl) { + const path = `${width}x${height}/${url.host}${url.pathname}`; - const hash = crypto.createHmac("sha1", secret).update(path).digest("base64").replace(/\+/g, "-").replace(/\//g, "_"); + const hash = crypto.createHmac("sha1", secret).update(path).digest("base64").replace(/\+/g, "-").replace(/\//g, "_"); - return `${imagorServerUrl}/${hash}/${path}`; - } + return `${imagorServerUrl}/${hash}/${path}`; + } - if (!hasWarnedAboutImagor) { - hasWarnedAboutImagor = true; - console.log("[Embeds]", yellow("Imagor has not been set up correctly. https://docs.spacebar.chat/setup/server/configuration/imagor/")); - } + if (!hasWarnedAboutImagor) { + hasWarnedAboutImagor = true; + console.log("[Embeds]", yellow("Imagor has not been set up correctly. https://docs.spacebar.chat/setup/server/configuration/imagor/")); + } - return url.toString(); + return url.toString(); }; const getMeta = ($: cheerio.CheerioAPI, name: string): string | undefined => { - let elem = $(`meta[property="${name}"]`); - if (!elem.length) elem = $(`meta[name="${name}"]`); - const ret = elem.attr("content") || elem.text(); - return ret.trim().length == 0 ? undefined : ret; + let elem = $(`meta[property="${name}"]`); + if (!elem.length) elem = $(`meta[name="${name}"]`); + const ret = elem.attr("content") || elem.text(); + return ret.trim().length == 0 ? undefined : ret; }; const tryParseInt = (str: string | undefined) => { - if (!str) return undefined; - try { - return parseInt(str); - } catch (e) { - return undefined; - } + if (!str) return undefined; + try { + return parseInt(str); + } catch (e) { + return undefined; + } }; export const getMetaDescriptions = (text: string) => { - const $ = cheerio.load(text); + const $ = cheerio.load(text); - return { - type: getMeta($, "og:type"), - title: getMeta($, "og:title") || $("title").first().text(), - provider_name: getMeta($, "og:site_name"), - author: getMeta($, "article:author"), - description: getMeta($, "og:description") || getMeta($, "description"), - image: getMeta($, "og:image") || getMeta($, "twitter:image"), - image_fallback: $(`image`).attr("src"), - video_fallback: $(`video`).attr("src"), - width: tryParseInt(getMeta($, "og:image:width")), - height: tryParseInt(getMeta($, "og:image:height")), - url: getMeta($, "og:url"), - youtube_embed: getMeta($, "og:video:secure_url"), - site_name: getMeta($, "og:site_name"), + return { + type: getMeta($, "og:type"), + title: getMeta($, "og:title") || $("title").first().text(), + provider_name: getMeta($, "og:site_name"), + author: getMeta($, "article:author"), + description: getMeta($, "og:description") || getMeta($, "description"), + image: getMeta($, "og:image") || getMeta($, "twitter:image"), + image_fallback: $(`image`).attr("src"), + video_fallback: $(`video`).attr("src"), + width: tryParseInt(getMeta($, "og:image:width")), + height: tryParseInt(getMeta($, "og:image:height")), + url: getMeta($, "og:url"), + youtube_embed: getMeta($, "og:video:secure_url"), + site_name: getMeta($, "og:site_name"), - $, - }; + $, + }; }; const doFetch = async (url: URL) => { - try { - const res = await fetch(url, { - ...DEFAULT_FETCH_OPTIONS, - }); - if (res.headers.get("content-length")) { - const contentLength = parseInt(res.headers.get("content-length")!); - if (Config.get().limits.message.maxEmbedDownloadSize && contentLength > Config.get().limits.message.maxEmbedDownloadSize) { - return null; - } - } - return res; - } catch (e) { - return null; - } + try { + const res = await fetch(url, { + ...DEFAULT_FETCH_OPTIONS, + }); + if (res.headers.get("content-length")) { + const contentLength = parseInt(res.headers.get("content-length")!); + if (Config.get().limits.message.maxEmbedDownloadSize && contentLength > Config.get().limits.message.maxEmbedDownloadSize) { + return null; + } + } + return res; + } catch (e) { + return null; + } }; const genericImageHandler = async (url: URL): Promise<Embed | null> => { - const type = await fetch(url, { - ...DEFAULT_FETCH_OPTIONS, - method: "HEAD", - }); + const type = await fetch(url, { + ...DEFAULT_FETCH_OPTIONS, + method: "HEAD", + }); - let image; + let image; - if (type.headers.get("content-type")?.indexOf("image") !== -1) { - const result = await probe(url.href); - image = makeEmbedImage(url.href, result.width, result.height); - } else if (type.headers.get("content-type")?.indexOf("video") !== -1) { - // TODO - return null; - } else { - // have to download the page, unfortunately - const response = await doFetch(url); - if (!response) return null; - const metas = getMetaDescriptions(await response.text()); - image = makeEmbedImage(metas.image || metas.image_fallback, metas.width, metas.height); - } + if (type.headers.get("content-type")?.indexOf("image") !== -1) { + const result = await probe(url.href); + image = makeEmbedImage(url.href, result.width, result.height); + } else if (type.headers.get("content-type")?.indexOf("video") !== -1) { + // TODO + return null; + } else { + // have to download the page, unfortunately + const response = await doFetch(url); + if (!response) return null; + const metas = getMetaDescriptions(await response.text()); + image = makeEmbedImage(metas.image || metas.image_fallback, metas.width, metas.height); + } - if (!image) return null; + if (!image) return null; - return { - url: url.href, - type: EmbedType.image, - thumbnail: image, - }; + return { + url: url.href, + type: EmbedType.image, + thumbnail: image, + }; }; export const EmbedHandlers: { - [key: string]: (url: URL) => Promise<Embed | Embed[] | null>; + [key: string]: (url: URL) => Promise<Embed | Embed[] | null>; } = { - // the url does not have a special handler - default: async (url: URL) => { - const type = await fetch(url, { - ...DEFAULT_FETCH_OPTIONS, - method: "HEAD", - }); - if (type.headers.get("content-type")?.indexOf("image") !== -1) return await genericImageHandler(url); + // the url does not have a special handler + default: async (url: URL) => { + const type = await fetch(url, { + ...DEFAULT_FETCH_OPTIONS, + method: "HEAD", + }); + if (type.headers.get("content-type")?.indexOf("image") !== -1) return await genericImageHandler(url); - const response = await doFetch(url); - if (!response) return null; + const response = await doFetch(url); + if (!response) return null; - const text = await response.text(); - const metas = getMetaDescriptions(text); + const text = await response.text(); + const metas = getMetaDescriptions(text); - // TODO: handle video + // TODO: handle video - if (!metas.image) metas.image = metas.image_fallback; + if (!metas.image) metas.image = metas.image_fallback; - if (metas.image && (!metas.width || !metas.height)) { - metas.image = new URL(metas.image, url).toString(); - const result = await probe(metas.image); - metas.width = result.width; - metas.height = result.height; - } + if (metas.image && (!metas.width || !metas.height)) { + metas.image = new URL(metas.image, url).toString(); + const result = await probe(metas.image); + metas.width = result.width; + metas.height = result.height; + } - if (!metas.image && (!metas.title || !metas.description)) { - // we don't have any content to display - return null; - } + if (!metas.image && (!metas.title || !metas.description)) { + // we don't have any content to display + return null; + } - let embedType = EmbedType.link; - if (metas.type == "article") embedType = EmbedType.article; - if (metas.type == "object") embedType = EmbedType.article; // github - if (metas.type == "rich") embedType = EmbedType.rich; + let embedType = EmbedType.link; + if (metas.type == "article") embedType = EmbedType.article; + if (metas.type == "object") embedType = EmbedType.article; // github + if (metas.type == "rich") embedType = EmbedType.rich; - return { - url: url.href, - type: embedType, - title: metas.title, - thumbnail: makeEmbedImage(metas.image, metas.width, metas.height), - description: metas.description, - provider: metas.site_name - ? { - name: metas.site_name, - url: url.origin, - } - : undefined, - }; - }, + return { + url: url.href, + type: embedType, + title: metas.title, + thumbnail: makeEmbedImage(metas.image, metas.width, metas.height), + description: metas.description, + provider: metas.site_name + ? { + name: metas.site_name, + url: url.origin, + } + : undefined, + }; + }, - "giphy.com": genericImageHandler, - "media4.giphy.com": genericImageHandler, - "tenor.com": genericImageHandler, - "c.tenor.com": genericImageHandler, - "media.tenor.com": genericImageHandler, + "giphy.com": genericImageHandler, + "media4.giphy.com": genericImageHandler, + "tenor.com": genericImageHandler, + "c.tenor.com": genericImageHandler, + "media.tenor.com": genericImageHandler, - "facebook.com": (url) => EmbedHandlers["www.facebook.com"](url), - "www.facebook.com": async (url: URL) => { - const response = await doFetch(url); - if (!response) return null; - const metas = getMetaDescriptions(await response.text()); + "facebook.com": (url) => EmbedHandlers["www.facebook.com"](url), + "www.facebook.com": async (url: URL) => { + const response = await doFetch(url); + if (!response) return null; + const metas = getMetaDescriptions(await response.text()); - return { - url: url.href, - type: EmbedType.link, - title: metas.title, - description: metas.description, - thumbnail: makeEmbedImage(metas.image, 640, 640), - color: 16777215, - }; - }, + return { + url: url.href, + type: EmbedType.link, + title: metas.title, + description: metas.description, + thumbnail: makeEmbedImage(metas.image, 640, 640), + color: 16777215, + }; + }, - "twitter.com": (url) => EmbedHandlers["www.twitter.com"](url), - "www.twitter.com": async (url: URL) => { - const token = Config.get().external.twitter; - if (!token) return null; + "twitter.com": (url) => EmbedHandlers["www.twitter.com"](url), + "www.twitter.com": async (url: URL) => { + const token = Config.get().external.twitter; + if (!token) return null; - if (!url.href.includes("/status/")) return null; // TODO; - const id = url.pathname.split("/")[3]; // super bad lol - if (!parseInt(id)) return null; - const endpointUrl = - `https://api.twitter.com/2/tweets/${id}` + - `?expansions=author_id,attachments.media_keys` + - `&media.fields=url,width,height` + - `&tweet.fields=created_at,public_metrics` + - `&user.fields=profile_image_url`; + if (!url.href.includes("/status/")) return null; // TODO; + const id = url.pathname.split("/")[3]; // super bad lol + if (!parseInt(id)) return null; + const endpointUrl = + `https://api.twitter.com/2/tweets/${id}` + + `?expansions=author_id,attachments.media_keys` + + `&media.fields=url,width,height` + + `&tweet.fields=created_at,public_metrics` + + `&user.fields=profile_image_url`; - const response = await fetch(endpointUrl, { - ...DEFAULT_FETCH_OPTIONS, - headers: { - authorization: `Bearer ${token}`, - }, - }); - const json = (await response.json()) as { - errors?: never[]; - includes: { - users: { - profile_image_url: string; - username: string; - name: string; - }[]; - media: { - type: string; - width: number; - height: number; - url: string; - }[]; - }; - data: { - text: string; - created_at: string; - public_metrics: { like_count: number; retweet_count: number }; - }; - }; - if (json.errors) return null; - const author = json.includes.users[0]; - const text = json.data.text; - const created_at = new Date(json.data.created_at); - const metrics = json.data.public_metrics; - const media = json.includes.media?.filter((x: { type: string }) => x.type == "photo"); + const response = await fetch(endpointUrl, { + ...DEFAULT_FETCH_OPTIONS, + headers: { + authorization: `Bearer ${token}`, + }, + }); + const json = (await response.json()) as { + errors?: never[]; + includes: { + users: { + profile_image_url: string; + username: string; + name: string; + }[]; + media: { + type: string; + width: number; + height: number; + url: string; + }[]; + }; + data: { + text: string; + created_at: string; + public_metrics: { like_count: number; retweet_count: number }; + }; + }; + if (json.errors) return null; + const author = json.includes.users[0]; + const text = json.data.text; + const created_at = new Date(json.data.created_at); + const metrics = json.data.public_metrics; + const media = json.includes.media?.filter((x: { type: string }) => x.type == "photo"); - const embed: Embed = { - type: EmbedType.rich, - url: `${url.origin}${url.pathname}`, - description: text, - author: { - url: `https://twitter.com/${author.username}`, - name: `${author.name} (@${author.username})`, - proxy_icon_url: getProxyUrl(new URL(author.profile_image_url), 400, 400), - icon_url: author.profile_image_url, - }, - timestamp: created_at, - fields: [ - { - inline: true, - name: "Likes", - value: metrics.like_count.toString(), - }, - { - inline: true, - name: "Retweet", - value: metrics.retweet_count.toString(), - }, - ], - color: 1942002, - footer: { - text: "Twitter", - proxy_icon_url: getProxyUrl(new URL("https://abs.twimg.com/icons/apple-touch-icon-192x192.png"), 192, 192), - icon_url: "https://abs.twimg.com/icons/apple-touch-icon-192x192.png", - }, - // Discord doesn't send this? - // provider: { - // name: "Twitter", - // url: "https://twitter.com" - // }, - }; + const embed: Embed = { + type: EmbedType.rich, + url: `${url.origin}${url.pathname}`, + description: text, + author: { + url: `https://twitter.com/${author.username}`, + name: `${author.name} (@${author.username})`, + proxy_icon_url: getProxyUrl(new URL(author.profile_image_url), 400, 400), + icon_url: author.profile_image_url, + }, + timestamp: created_at, + fields: [ + { + inline: true, + name: "Likes", + value: metrics.like_count.toString(), + }, + { + inline: true, + name: "Retweet", + value: metrics.retweet_count.toString(), + }, + ], + color: 1942002, + footer: { + text: "Twitter", + proxy_icon_url: getProxyUrl(new URL("https://abs.twimg.com/icons/apple-touch-icon-192x192.png"), 192, 192), + icon_url: "https://abs.twimg.com/icons/apple-touch-icon-192x192.png", + }, + // Discord doesn't send this? + // provider: { + // name: "Twitter", + // url: "https://twitter.com" + // }, + }; - if (media && media.length > 0) { - embed.image = { - width: media[0].width, - height: media[0].height, - url: media[0].url, - proxy_url: getProxyUrl(new URL(media[0].url), media[0].width, media[0].height), - }; - media.shift(); - } + if (media && media.length > 0) { + embed.image = { + width: media[0].width, + height: media[0].height, + url: media[0].url, + proxy_url: getProxyUrl(new URL(media[0].url), media[0].width, media[0].height), + }; + media.shift(); + } - return embed; + return embed; - // TODO: Client won't merge these into a single embed, for some reason. - // return [embed, ...media.map((x: any) => ({ - // // generate new embeds for each additional attachment - // type: EmbedType.rich, - // url: url.href, - // image: { - // width: x.width, - // height: x.height, - // url: x.url, - // proxy_url: getProxyUrl(new URL(x.url), x.width, x.height) - // } - // }))]; - }, + // TODO: Client won't merge these into a single embed, for some reason. + // return [embed, ...media.map((x: any) => ({ + // // generate new embeds for each additional attachment + // type: EmbedType.rich, + // url: url.href, + // image: { + // width: x.width, + // height: x.height, + // url: x.url, + // proxy_url: getProxyUrl(new URL(x.url), x.width, x.height) + // } + // }))]; + }, - "open.spotify.com": async (url: URL) => { - const response = await doFetch(url); - if (!response) return null; - const metas = getMetaDescriptions(await response.text()); + "open.spotify.com": async (url: URL) => { + const response = await doFetch(url); + if (!response) return null; + const metas = getMetaDescriptions(await response.text()); - return { - url: url.href, - type: EmbedType.link, - title: metas.title, - description: metas.description, - thumbnail: makeEmbedImage(metas.image, 640, 640), - provider: { - url: "https://spotify.com", - name: "Spotify", - }, - }; - }, + return { + url: url.href, + type: EmbedType.link, + title: metas.title, + description: metas.description, + thumbnail: makeEmbedImage(metas.image, 640, 640), + provider: { + url: "https://spotify.com", + name: "Spotify", + }, + }; + }, - // TODO: docs: Pixiv won't work without Imagor - "pixiv.net": (url) => EmbedHandlers["www.pixiv.net"](url), - "www.pixiv.net": async (url: URL) => { - const response = await doFetch(url); - if (!response) return null; - const metas = getMetaDescriptions(await response.text()); + // TODO: docs: Pixiv won't work without Imagor + "pixiv.net": (url) => EmbedHandlers["www.pixiv.net"](url), + "www.pixiv.net": async (url: URL) => { + const response = await doFetch(url); + if (!response) return null; + const metas = getMetaDescriptions(await response.text()); - if (!metas.image) return null; + if (!metas.image) return null; - return { - url: url.href, - type: EmbedType.image, - title: metas.title, - description: metas.description, - image: makeEmbedImage(metas.image || metas.image_fallback, metas.width, metas.height), - provider: { - url: "https://pixiv.net", - name: "Pixiv", - }, - }; - }, + return { + url: url.href, + type: EmbedType.image, + title: metas.title, + description: metas.description, + image: makeEmbedImage(metas.image || metas.image_fallback, metas.width, metas.height), + provider: { + url: "https://pixiv.net", + name: "Pixiv", + }, + }; + }, - "store.steampowered.com": async (url: URL) => { - const response = await doFetch(url); - if (!response) return null; - const metas = getMetaDescriptions(await response.text()); - const numReviews = metas.$("#review_summary_num_reviews").val() as string | undefined; - const price = metas.$(".game_purchase_price.price").data("price-final") as number | undefined; - const releaseDate = metas.$(".release_date").find("div.date").text().trim(); - const isReleased = new Date(releaseDate) < new Date(); + "store.steampowered.com": async (url: URL) => { + const response = await doFetch(url); + if (!response) return null; + const metas = getMetaDescriptions(await response.text()); + const numReviews = metas.$("#review_summary_num_reviews").val() as string | undefined; + const price = metas.$(".game_purchase_price.price").data("price-final") as number | undefined; + const releaseDate = metas.$(".release_date").find("div.date").text().trim(); + const isReleased = new Date(releaseDate) < new Date(); - const fields: Embed["fields"] = []; + const fields: Embed["fields"] = []; - if (numReviews) - fields.push({ - name: "Reviews", - value: numReviews, - inline: true, - }); + if (numReviews) + fields.push({ + name: "Reviews", + value: numReviews, + inline: true, + }); - if (price) - fields.push({ - name: "Price", - value: `$${price / 100}`, - inline: true, - }); + if (price) + fields.push({ + name: "Price", + value: `$${price / 100}`, + inline: true, + }); - // if the release date is in the past, it's already out - if (releaseDate && !isReleased) - fields.push({ - name: "Release Date", - value: releaseDate, - inline: true, - }); + // if the release date is in the past, it's already out + if (releaseDate && !isReleased) + fields.push({ + name: "Release Date", + value: releaseDate, + inline: true, + }); - return { - url: url.href, - type: EmbedType.rich, - title: metas.title, - description: metas.description, - image: { - // TODO: meant to be thumbnail. - // isn't this standard across all of steam? - width: 460, - height: 215, - url: metas.image, - proxy_url: metas.image ? getProxyUrl(new URL(metas.image), 460, 215) : undefined, - }, - provider: { - url: "https://store.steampowered.com", - name: "Steam", - }, - fields, - // TODO: Video - }; - }, + return { + url: url.href, + type: EmbedType.rich, + title: metas.title, + description: metas.description, + image: { + // TODO: meant to be thumbnail. + // isn't this standard across all of steam? + width: 460, + height: 215, + url: metas.image, + proxy_url: metas.image ? getProxyUrl(new URL(metas.image), 460, 215) : undefined, + }, + provider: { + url: "https://store.steampowered.com", + name: "Steam", + }, + fields, + // TODO: Video + }; + }, - "reddit.com": (url) => EmbedHandlers["www.reddit.com"](url), - "www.reddit.com": async (url: URL) => { - const res = await EmbedHandlers["default"](url); - return { - ...res, - color: 16777215, - provider: { - name: "reddit", - }, - }; - }, + "reddit.com": (url) => EmbedHandlers["www.reddit.com"](url), + "www.reddit.com": async (url: URL) => { + const res = await EmbedHandlers["default"](url); + return { + ...res, + color: 16777215, + provider: { + name: "reddit", + }, + }; + }, - "youtu.be": (url) => EmbedHandlers["www.youtube.com"](url), - "youtube.com": (url) => EmbedHandlers["www.youtube.com"](url), - "www.youtube.com": async (url: URL): Promise<Embed | null> => { - const response = await doFetch(url); - if (!response) return null; - const metas = getMetaDescriptions(await response.text()); + "youtu.be": (url) => EmbedHandlers["www.youtube.com"](url), + "youtube.com": (url) => EmbedHandlers["www.youtube.com"](url), + "www.youtube.com": async (url: URL): Promise<Embed | null> => { + const response = await doFetch(url); + if (!response) return null; + const metas = getMetaDescriptions(await response.text()); - return { - video: makeEmbedImage(metas.youtube_embed, metas.width, metas.height), - url: url.href, - type: metas.youtube_embed ? EmbedType.video : EmbedType.link, - title: metas.title, - thumbnail: makeEmbedImage(metas.image || metas.image_fallback, metas.width, metas.height), - provider: { - url: "https://www.youtube.com", - name: "YouTube", - }, - description: metas.description, - color: 16711680, - author: metas.author - ? { - name: metas.author, - // TODO: author channel url - } - : undefined, - }; - }, + return { + video: makeEmbedImage(metas.youtube_embed, metas.width, metas.height), + url: url.href, + type: metas.youtube_embed ? EmbedType.video : EmbedType.link, + title: metas.title, + thumbnail: makeEmbedImage(metas.image || metas.image_fallback, metas.width, metas.height), + provider: { + url: "https://www.youtube.com", + name: "YouTube", + }, + description: metas.description, + color: 16711680, + author: metas.author + ? { + name: metas.author, + // TODO: author channel url + } + : undefined, + }; + }, - "www.xkcd.com": (url) => EmbedHandlers["xkcd.com"](url), - "xkcd.com": async (url) => { - const response = await doFetch(url); - if (!response) return null; + "www.xkcd.com": (url) => EmbedHandlers["xkcd.com"](url), + "xkcd.com": async (url) => { + const response = await doFetch(url); + if (!response) return null; - const metas = getMetaDescriptions(await response.text()); - const hoverText = metas.$("#comic img").attr("title"); + const metas = getMetaDescriptions(await response.text()); + const hoverText = metas.$("#comic img").attr("title"); - if (!metas.image) return null; + if (!metas.image) return null; - const { width, height } = await probe(metas.image); + const { width, height } = await probe(metas.image); - return { - url: url.href, - type: EmbedType.rich, - title: `xkcd: ${metas.title}`, - image: makeEmbedImage(metas.image, width, height), - footer: hoverText - ? { - text: hoverText, - } - : undefined, - }; - }, + return { + url: url.href, + type: EmbedType.rich, + title: `xkcd: ${metas.title}`, + image: makeEmbedImage(metas.image, width, height), + footer: hoverText + ? { + text: hoverText, + } + : undefined, + }; + }, - // the url is an image from this instance - self: async (url: URL): Promise<Embed | null> => { - const result = await probe(url.href); + // the url is an image from this instance + self: async (url: URL): Promise<Embed | null> => { + const result = await probe(url.href); - return { - url: url.href, - type: EmbedType.image, - thumbnail: { - width: result.width, - height: result.height, - url: url.href, - proxy_url: url.href, - }, - }; - }, + return { + url: url.href, + type: EmbedType.image, + thumbnail: { + width: result.width, + height: result.height, + url: url.href, + proxy_url: url.href, + }, + }; + }, }; diff --git a/src/api/util/utility/RandomInviteID.ts b/src/api/util/utility/RandomInviteID.ts
index 3850df54..0718a736 100644 --- a/src/api/util/utility/RandomInviteID.ts +++ b/src/api/util/utility/RandomInviteID.ts
@@ -23,42 +23,42 @@ import crypto from "crypto"; // And why is this even here? Just use cryto.randomBytes? export function randomString(length = 6) { - // Declare all characters - const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + // Declare all characters + const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - // Pick characers randomly - let str = ""; - for (let i = 0; i < length; i++) { - str += chars.charAt(Math.floor(crypto.randomInt(chars.length))); - } + // Pick characers randomly + let str = ""; + for (let i = 0; i < length; i++) { + str += chars.charAt(Math.floor(crypto.randomInt(chars.length))); + } - return str; + return str; } export function snowflakeBasedInvite() { - // Declare all characters - const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; - const base = BigInt(chars.length); - let snowflake = Snowflake.generateWorkerProcess(); + // Declare all characters + const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + const base = BigInt(chars.length); + let snowflake = Snowflake.generateWorkerProcess(); - // snowflakes hold ~10.75 characters worth of entropy; - // safe to generate a 8-char invite out of them - const str = ""; - for (let i = 0; i < 10; i++) { - str.concat(chars.charAt(Number(snowflake % base))); - snowflake = snowflake / base; - } + // snowflakes hold ~10.75 characters worth of entropy; + // safe to generate a 8-char invite out of them + const str = ""; + for (let i = 0; i < 10; i++) { + str.concat(chars.charAt(Number(snowflake % base))); + snowflake = snowflake / base; + } - return str.substr(3, 8).split("").reverse().join(""); + return str.substr(3, 8).split("").reverse().join(""); } export function randomUpperString(length: number = 10) { - const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - let result = ""; - for (let i = 0; i < length; i++) { - result += chars.charAt(Math.floor(Math.random() * chars.length)); - } + let result = ""; + for (let i = 0; i < length; i++) { + result += chars.charAt(Math.floor(Math.random() * chars.length)); + } - return result; + return result; } diff --git a/src/api/util/utility/String.ts b/src/api/util/utility/String.ts
index e5c1279a..e33613a3 100644 --- a/src/api/util/utility/String.ts +++ b/src/api/util/utility/String.ts
@@ -21,18 +21,18 @@ import { ntob } from "./Base64"; import { FieldErrors, Random } from "@spacebar/util"; export function checkLength(str: string, min: number, max: number, key: string, req: Request) { - if (str.length < min || str.length > max) { - throw FieldErrors({ - [key]: { - code: "BASE_TYPE_BAD_LENGTH", - message: req.t("common:field.BASE_TYPE_BAD_LENGTH", { - length: `${min} - ${max}`, - }), - }, - }); - } + if (str.length < min || str.length > max) { + throw FieldErrors({ + [key]: { + code: "BASE_TYPE_BAD_LENGTH", + message: req.t("common:field.BASE_TYPE_BAD_LENGTH", { + length: `${min} - ${max}`, + }), + }, + }); + } } export function generateCode() { - return ntob(Date.now() + Random.nextInt(0, 10000)); + return ntob(Date.now() + Random.nextInt(0, 10000)); } diff --git a/src/api/util/utility/captcha.ts b/src/api/util/utility/captcha.ts
index f6d4d0e1..c4d8c9a3 100644 --- a/src/api/util/utility/captcha.ts +++ b/src/api/util/utility/captcha.ts
@@ -19,46 +19,46 @@ import { Config } from "@spacebar/util"; export interface hcaptchaResponse { - success: boolean; - challenge_ts: string; - hostname: string; - credit: boolean; - "error-codes": string[]; - score: number; // enterprise only - score_reason: string[]; // enterprise only + success: boolean; + challenge_ts: string; + hostname: string; + credit: boolean; + "error-codes": string[]; + score: number; // enterprise only + score_reason: string[]; // enterprise only } export interface recaptchaResponse { - success: boolean; - score: number; // between 0 - 1 - action: string; - challenge_ts: string; - hostname: string; - "error-codes"?: string[]; + success: boolean; + score: number; // between 0 - 1 + action: string; + challenge_ts: string; + hostname: string; + "error-codes"?: string[]; } const verifyEndpoints = { - hcaptcha: "https://hcaptcha.com/siteverify", - recaptcha: "https://www.google.com/recaptcha/api/siteverify", + hcaptcha: "https://hcaptcha.com/siteverify", + recaptcha: "https://www.google.com/recaptcha/api/siteverify", }; export async function verifyCaptcha(response: string, ip?: string) { - const { security } = Config.get(); - const { service, secret, sitekey } = security.captcha; + const { security } = Config.get(); + const { service, secret, sitekey } = security.captcha; - if (!service || !secret || !sitekey) throw new Error("CAPTCHA is not configured correctly. https://docs.spacebar.chat/setup/server/security/captcha/"); + if (!service || !secret || !sitekey) throw new Error("CAPTCHA is not configured correctly. https://docs.spacebar.chat/setup/server/security/captcha/"); - const res = await fetch(verifyEndpoints[service], { - method: "POST", - headers: { - "Content-Type": "application/x-www-form-urlencoded", - }, - body: - `response=${encodeURIComponent(response)}` + - `&secret=${encodeURIComponent(secret)}` + - `&sitekey=${encodeURIComponent(sitekey)}` + - (ip ? `&remoteip=${encodeURIComponent(ip)}` : ""), - }); + const res = await fetch(verifyEndpoints[service], { + method: "POST", + headers: { + "Content-Type": "application/x-www-form-urlencoded", + }, + body: + `response=${encodeURIComponent(response)}` + + `&secret=${encodeURIComponent(secret)}` + + `&sitekey=${encodeURIComponent(sitekey)}` + + (ip ? `&remoteip=${encodeURIComponent(ip)}` : ""), + }); - return (await res.json()) as hcaptchaResponse | recaptchaResponse; + return (await res.json()) as hcaptchaResponse | recaptchaResponse; } diff --git a/src/api/util/utility/ipAddress.ts b/src/api/util/utility/ipAddress.ts
index 19408253..399edb67 100644 --- a/src/api/util/utility/ipAddress.ts +++ b/src/api/util/utility/ipAddress.ts
@@ -18,14 +18,14 @@ type Location = { latitude: number; longitude: number }; export function distanceBetweenLocations(loc1: Location, loc2: Location): number { - return distanceBetweenCoords(loc1.latitude, loc1.longitude, loc2.latitude, loc2.longitude); + return distanceBetweenCoords(loc1.latitude, loc1.longitude, loc2.latitude, loc2.longitude); } //Haversine function function distanceBetweenCoords(lat1: number, lon1: number, lat2: number, lon2: number) { - const p = 0.017453292519943295; // Math.PI / 180 - const c = Math.cos; - const a = 0.5 - c((lat2 - lat1) * p) / 2 + (c(lat1 * p) * c(lat2 * p) * (1 - c((lon2 - lon1) * p))) / 2; + const p = 0.017453292519943295; // Math.PI / 180 + const c = Math.cos; + const a = 0.5 - c((lat2 - lat1) * p) / 2 + (c(lat1 * p) * c(lat2 * p) * (1 - c((lon2 - lon1) * p))) / 2; - return 12742 * Math.asin(Math.sqrt(a)); // 2 * R; R = 6371 km + return 12742 * Math.asin(Math.sqrt(a)); // 2 * R; R = 6371 km } diff --git a/src/api/util/utility/passwordStrength.ts b/src/api/util/utility/passwordStrength.ts
index beb277b0..7a9d4c5f 100644 --- a/src/api/util/utility/passwordStrength.ts +++ b/src/api/util/utility/passwordStrength.ts
@@ -35,43 +35,43 @@ const reSYMBOLS = /[A-Za-z0-9]/g; * Returns: 0 > pw > 1 */ export function checkPassword(password: string): number { - const { minLength, minNumbers, minUpperCase, minSymbols } = Config.get().register.password; - let strength = 0; + const { minLength, minNumbers, minUpperCase, minSymbols } = Config.get().register.password; + let strength = 0; - // checks for total password len - if (password.length >= minLength - 1) { - strength += 0.05; - } + // checks for total password len + if (password.length >= minLength - 1) { + strength += 0.05; + } - // checks for amount of Numbers - if (password.match(reNUMBER)?.length ?? 0 >= minNumbers - 1) { - strength += 0.05; - } + // checks for amount of Numbers + if (password.match(reNUMBER)?.length ?? 0 >= minNumbers - 1) { + strength += 0.05; + } - // checks for amount of Uppercase Letters - if (password.match(reUPPERCASELETTER)?.length ?? 0 >= minUpperCase - 1) { - strength += 0.05; - } + // checks for amount of Uppercase Letters + if (password.match(reUPPERCASELETTER)?.length ?? 0 >= minUpperCase - 1) { + strength += 0.05; + } - // checks for amount of symbols - if (password.replace(reSYMBOLS, "").length >= minSymbols - 1) { - strength += 0.05; - } + // checks for amount of symbols + if (password.replace(reSYMBOLS, "").length >= minSymbols - 1) { + strength += 0.05; + } - // checks if password only consists of numbers or only consists of chars - if (password.length == password.match(reNUMBER)?.length || password.length === password.match(reUPPERCASELETTER)?.length) { - strength = 0; - } + // checks if password only consists of numbers or only consists of chars + if (password.length == password.match(reNUMBER)?.length || password.length === password.match(reUPPERCASELETTER)?.length) { + strength = 0; + } - const entropyMap: { [key: string]: number } = {}; - for (let i = 0; i < password.length; i++) { - if (entropyMap[password[i]]) entropyMap[password[i]]++; - else entropyMap[password[i]] = 1; - } + const entropyMap: { [key: string]: number } = {}; + for (let i = 0; i < password.length; i++) { + if (entropyMap[password[i]]) entropyMap[password[i]]++; + else entropyMap[password[i]] = 1; + } - const entropies = Object.values(entropyMap); + const entropies = Object.values(entropyMap); - entropies.map((x) => x / entropyMap.length); - strength += entropies.reduceRight((a: number, x: number) => a - x * Math.log2(x)) / Math.log2(password.length); - return strength; + entropies.map((x) => x / entropyMap.length); + strength += entropies.reduceRight((a: number, x: number) => a - x * Math.log2(x)) / Math.log2(password.length); + return strength; }