From 92f25c3a22249c65205dd61dfc8399989a010a0e Mon Sep 17 00:00:00 2001 From: Rory& Date: Sat, 13 Jun 2026 01:10:59 +0200 Subject: [PATCH] HACKHACK: Migrate to typeorm v1.0.0, still work to do in actually updating all the queries properly --- package-lock.json | Bin 329055 -> 297031 bytes package.json | 2 +- src/api/routes/channels/#channel_id/index.ts | 4 +-- .../routes/channels/#channel_id/invites.ts | 2 +- .../messages/#message_id/reactions.ts | 2 +- .../messages/#message_id/threads.ts | 2 +- .../routes/channels/#channel_id/recipients.ts | 2 +- src/api/routes/channels/#channel_id/tags.ts | 6 ++-- .../routes/channels/#channel_id/threads.ts | 2 +- src/api/routes/guilds/#guild_id/invites.ts | 2 +- .../routes/guilds/#guild_id/members/index.ts | 2 +- .../guilds/#guild_id/messages/search.ts | 2 +- src/api/routes/guilds/#guild_id/templates.ts | 4 +-- src/api/routes/invites/index.ts | 2 +- src/api/routes/users/#user_id/delete.ts | 2 +- src/api/routes/users/#user_id/profile.ts | 7 +++-- src/api/routes/users/@me/index.ts | 4 +-- src/api/routes/users/@me/relationships.ts | 14 ++++----- src/database/entities/Channel.ts | 2 +- src/database/entities/Member.ts | 2 +- src/database/entities/User.ts | 2 +- src/gateway/opcodes/Identify.ts | 5 ++-- src/util/dtos/DmChannelDTO.ts | 2 +- src/util/imports/OrmUtils.test.ts | 27 ++++++++++++++++++ src/util/imports/OrmUtils.ts | 22 ++++++++++++++ src/util/util/Permissions.ts | 15 +++++----- src/util/util/Token.ts | 25 ++++++++-------- 27 files changed, 108 insertions(+), 55 deletions(-) create mode 100644 src/util/imports/OrmUtils.test.ts diff --git a/package-lock.json b/package-lock.json index b65d85318756e07f2315fa168b04a1b23a5bcc9c..cdd18899bb311a7be833ea6b38939ea5b64ba1aa 100644 GIT binary patch delta 982 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 [tag.id, tag])); diff --git a/src/api/routes/channels/#channel_id/invites.ts b/src/api/routes/channels/#channel_id/invites.ts index 8afc7dc06..3df4a5a85 100644 --- a/src/api/routes/channels/#channel_id/invites.ts +++ b/src/api/routes/channels/#channel_id/invites.ts @@ -111,7 +111,7 @@ router.get( const invites = await Invite.find({ where: { guild_id, channel_id }, - relations: PublicInviteRelation, + relations: Object.fromEntries(PublicInviteRelation.map((i) => [i, true])), //TODO: cleanup }); res.status(200).send(invites); 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 c045bb54a..709b7c73a 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 @@ -161,7 +161,7 @@ router.get( where: { id: In(reaction.user_ids), }, - select: PublicUserProjection, + select: Object.fromEntries(PublicUserProjection.map((i) => [i, true])), //TODO: cleanup take: limit, }) ).map((user) => user.toPublicUser()); diff --git a/src/api/routes/channels/#channel_id/messages/#message_id/threads.ts b/src/api/routes/channels/#channel_id/messages/#message_id/threads.ts index bb48b4fbe..bf37dfe18 100644 --- a/src/api/routes/channels/#channel_id/messages/#message_id/threads.ts +++ b/src/api/routes/channels/#channel_id/messages/#message_id/threads.ts @@ -44,7 +44,7 @@ router.post( const body = req.body as MessageThreadCreationSchema; const message = await Message.findOneOrFail({ where: { id: message_id, channel_id }, - relations: ["guild"], + relations: { guild: true }, }); const channel = await Channel.findOneOrFail({ where: { id: channel_id }, diff --git a/src/api/routes/channels/#channel_id/recipients.ts b/src/api/routes/channels/#channel_id/recipients.ts index c72b3aa45..7e7c5d348 100644 --- a/src/api/routes/channels/#channel_id/recipients.ts +++ b/src/api/routes/channels/#channel_id/recipients.ts @@ -65,7 +65,7 @@ router.put( user: ( await User.findOneOrFail({ where: { id: user_id }, - select: PublicUserProjection, + select: Object.fromEntries(PublicUserProjection.map((i) => [i, true])), //TODO: cleanup }) ).toPublicUser(), }, diff --git a/src/api/routes/channels/#channel_id/tags.ts b/src/api/routes/channels/#channel_id/tags.ts index ffdb428d6..73ca8a9d1 100644 --- a/src/api/routes/channels/#channel_id/tags.ts +++ b/src/api/routes/channels/#channel_id/tags.ts @@ -43,7 +43,7 @@ router.post( const channel = await Channel.findOneOrFail({ where: { id: channel_id }, - relations: ["available_tags"], + relations: { available_tags: true }, }); if (!channel.isForum()) throw new Error("is not thread only channel"); @@ -88,7 +88,7 @@ router.put( const channel = await Channel.findOneOrFail({ where: { id: channel_id }, - relations: ["available_tags"], + relations: { available_tags: true }, }); if (!channel.isForum()) throw new Error("is not thread only channel"); @@ -127,7 +127,7 @@ router.delete( const channel = await Channel.findOneOrFail({ where: { id: channel_id }, - relations: ["available_tags"], + relations: { available_tags: true }, }); if (!channel.isForum()) throw new Error("is not thread only channel"); diff --git a/src/api/routes/channels/#channel_id/threads.ts b/src/api/routes/channels/#channel_id/threads.ts index 9485ab752..b9850635b 100644 --- a/src/api/routes/channels/#channel_id/threads.ts +++ b/src/api/routes/channels/#channel_id/threads.ts @@ -56,7 +56,7 @@ router.post( const channel = await Channel.findOneOrFail({ where: { id: channel_id }, - relations: ["available_tags"], + relations: { available_tags: true }, }); if (!body.applied_tags?.length) { const required = channel.flags & Number(ChannelFlags.FLAGS.REQUIRE_TAG); diff --git a/src/api/routes/guilds/#guild_id/invites.ts b/src/api/routes/guilds/#guild_id/invites.ts index 7fb330deb..aadd040a8 100644 --- a/src/api/routes/guilds/#guild_id/invites.ts +++ b/src/api/routes/guilds/#guild_id/invites.ts @@ -37,7 +37,7 @@ router.get( const invites = await Invite.find({ where: { guild_id }, - relations: PublicInviteRelation, + relations: Object.fromEntries(PublicInviteRelation.map((i) => [i, true])), // TODO cleanup }); await Promise.all( diff --git a/src/api/routes/guilds/#guild_id/members/index.ts b/src/api/routes/guilds/#guild_id/members/index.ts index 2586d84d5..942da645d 100644 --- a/src/api/routes/guilds/#guild_id/members/index.ts +++ b/src/api/routes/guilds/#guild_id/members/index.ts @@ -60,7 +60,7 @@ router.get( const members = await Member.find({ where: { guild_id, ...query }, - select: PublicMemberProjection, + select: Object.fromEntries(PublicMemberProjection.map((i) => [i, true])), // TODO: cleanup take: limit, order: { id: "ASC" }, }); diff --git a/src/api/routes/guilds/#guild_id/messages/search.ts b/src/api/routes/guilds/#guild_id/messages/search.ts index 6d9bbe5e8..c3c4b3405 100644 --- a/src/api/routes/guilds/#guild_id/messages/search.ts +++ b/src/api/routes/guilds/#guild_id/messages/search.ts @@ -128,7 +128,7 @@ router.get( mentions = mentions instanceof Array ? mentions : mentions ? [mentions] : []; let roleids = [] as string[]; if (mentions) { - const ms = await Member.find({ where: { id: In(mentions), guild_id: req.params.guild_id as string }, relations: ["roles"] }); + const ms = await Member.find({ where: { id: In(mentions), guild_id: req.params.guild_id as string }, relations: { roles: true } }); const rSet = new Set(); ms.forEach((memb) => { memb.roles.forEach(({ id }) => rSet.add(id)); diff --git a/src/api/routes/guilds/#guild_id/templates.ts b/src/api/routes/guilds/#guild_id/templates.ts index f2782bb85..b6d513735 100644 --- a/src/api/routes/guilds/#guild_id/templates.ts +++ b/src/api/routes/guilds/#guild_id/templates.ts @@ -86,7 +86,7 @@ router.post( const { guild_id } = req.params as { [key: string]: string }; const guild = await Guild.findOneOrFail({ where: { id: guild_id }, - select: TemplateGuildProjection, + select: Object.fromEntries(TemplateGuildProjection.map((i) => [i, true])), //TODO: cleanup relations: { roles: true, channels: true }, }); const exists = await Template.findOne({ @@ -142,7 +142,7 @@ router.put( const { code, guild_id } = req.params as { [key: string]: string }; const guild = await Guild.findOneOrFail({ where: { id: guild_id }, - select: TemplateGuildProjection, + select: Object.fromEntries(TemplateGuildProjection.map((i) => [i, true])), //TODO: cleanup }); const template = await Template.create({ diff --git a/src/api/routes/invites/index.ts b/src/api/routes/invites/index.ts index a1ba3131a..323511ceb 100644 --- a/src/api/routes/invites/index.ts +++ b/src/api/routes/invites/index.ts @@ -42,7 +42,7 @@ router.get( const invite = await Invite.findOneOrFail({ where: { code: invite_code }, - relations: PublicInviteRelation, + relations: Object.fromEntries(PublicInviteRelation.map((i) => [i, true])), //TODO: clean up }); res.status(200).send(invite.toPublicJSON()); diff --git a/src/api/routes/users/#user_id/delete.ts b/src/api/routes/users/#user_id/delete.ts index 9b12c7bbb..cf3fb2541 100644 --- a/src/api/routes/users/#user_id/delete.ts +++ b/src/api/routes/users/#user_id/delete.ts @@ -46,7 +46,7 @@ router.post( const body = req.body as InstanceUserDeleteSchema | undefined; const user = await User.findOneOrFail({ where: { id: req.params.user_id as string }, - select: [...PrivateUserProjection, "data"], + select: Object.fromEntries([...PrivateUserProjection, "data"].map((i) => [i, true])), // TODO: clean up }); if ((body?.persistInstanceBan ?? true) && !(await InstanceBan.findOne({ where: { user_id: user.id } }))) diff --git a/src/api/routes/users/#user_id/profile.ts b/src/api/routes/users/#user_id/profile.ts index 84082afa3..bbe8faed7 100644 --- a/src/api/routes/users/#user_id/profile.ts +++ b/src/api/routes/users/#user_id/profile.ts @@ -120,7 +120,10 @@ router.get("/", route({ responses: { 200: { body: "UserProfileResponse" } } }), 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 }); + const users = await User.find({ + where: { id: In(relationshipsIntersection.map((r) => r.to_id)) }, + select: Object.fromEntries(PublicUserProjection.map((i) => [i, true])), + }); //TODO: clean up mutual_friends = users.map((u) => u.toPublicUser()); } } @@ -169,7 +172,7 @@ router.patch("/", route({ requestBody: "UserProfileModifySchema" }), async (req: 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"], + select: Object.fromEntries([...PrivateUserProjection, "data"].map((i) => [i, true])), //TODO: cleanup }); if (body.bio) { diff --git a/src/api/routes/users/@me/index.ts b/src/api/routes/users/@me/index.ts index 1fda0afb6..ecb0b35cb 100644 --- a/src/api/routes/users/@me/index.ts +++ b/src/api/routes/users/@me/index.ts @@ -37,7 +37,7 @@ router.get( async (req: Request, res: Response) => { res.json( await User.findOne({ - select: PrivateUserProjection, + select: Object.fromEntries(PrivateUserProjection.map((i) => [i, true])), //TODO: cleanup where: { id: req.user_id }, }), ); @@ -65,7 +65,7 @@ router.patch( const user = await User.findOneOrFail({ where: { id: req.user_id }, - select: [...PrivateUserProjection, "data"], + select: Object.fromEntries([...PrivateUserProjection, "data"].map((i) => [i, true])), //TODO: cleanup }); // Populated on password change diff --git a/src/api/routes/users/@me/relationships.ts b/src/api/routes/users/@me/relationships.ts index d4d1eb1e8..6a6db5e4e 100644 --- a/src/api/routes/users/@me/relationships.ts +++ b/src/api/routes/users/@me/relationships.ts @@ -72,7 +72,7 @@ router.put( await User.findOneOrFail({ where: { id: req.params.user_id as string }, relations: { relationships: { to: true } }, - select: userProjection, + select: Object.fromEntries(userProjection.map((i) => [i, true])), // TODO: cleanup }), req.body.type ?? RelationshipType.friends, ), @@ -136,7 +136,7 @@ router.post( res, await User.findOneOrFail({ relations: { relationships: { to: true } }, - select: userProjection, + select: Object.fromEntries(userProjection.map((i) => [i, true])), // TODO: cleanup 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, @@ -165,12 +165,12 @@ router.delete( const user = await User.findOneOrFail({ where: { id: req.user_id }, - select: userProjection, + select: Object.fromEntries(userProjection.map((i) => [i, true])), // TODO: cleanup relations: { relationships: true }, }); const friend = await User.findOneOrFail({ where: { id: user_id }, - select: userProjection, + select: Object.fromEntries(userProjection.map((i) => [i, true])), // TODO: cleanup relations: { relationships: true }, }); @@ -215,8 +215,6 @@ router.delete( }, ); -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"); @@ -224,7 +222,7 @@ async function updateRelationship(req: Request, res: Response, friend: User, typ const user = await User.findOneOrFail({ where: { id: req.user_id }, relations: { relationships: { to: true } }, - select: userProjection, + select: Object.fromEntries(userProjection.map((i) => [i, true])), //TODO: cleanup }); let relationship = user.relationships.find((x) => x.to_id === id); @@ -316,3 +314,5 @@ async function updateRelationship(req: Request, res: Response, friend: User, typ return res.sendStatus(204); } + +export default router; diff --git a/src/database/entities/Channel.ts b/src/database/entities/Channel.ts index 6771fc407..1543d20b0 100644 --- a/src/database/entities/Channel.ts +++ b/src/database/entities/Channel.ts @@ -546,7 +546,7 @@ export class Channel extends BaseClass { channel_id: channel.id, user: await User.findOneOrFail({ where: { id: user_id }, - select: PublicUserProjection, + select: Object.fromEntries(PublicUserProjection.map((i) => [i, true])), //TODO: cleanup }), }, channel_id: channel.id, diff --git a/src/database/entities/Member.ts b/src/database/entities/Member.ts index b5b0b3982..e8e186602 100644 --- a/src/database/entities/Member.ts +++ b/src/database/entities/Member.ts @@ -335,7 +335,7 @@ export class Member extends BaseClassWithoutId { where: { id: guild_id, }, - relations: PublicGuildRelations, + relations: Object.fromEntries(PublicGuildRelations.map((i) => [i, true])), //TODO: clean up relationLoadStrategy: "query", }); const channelPositionsGuild = await Guild.findOneOrFail({ diff --git a/src/database/entities/User.ts b/src/database/entities/User.ts index 5c2c4fe12..434f16303 100644 --- a/src/database/entities/User.ts +++ b/src/database/entities/User.ts @@ -235,7 +235,7 @@ export class User extends BaseClass { static async getPublicUser(user_id: string): Promise { const user = await User.findOneOrFail({ where: { id: user_id }, - select: PublicUserProjection, + select: Object.fromEntries(PublicUserProjection.map((i) => [i, true])), // TODO: clean up }); return user.toPublicUser(); } diff --git a/src/gateway/opcodes/Identify.ts b/src/gateway/opcodes/Identify.ts index 5da55b046..e1ce6e8c3 100644 --- a/src/gateway/opcodes/Identify.ts +++ b/src/gateway/opcodes/Identify.ts @@ -49,6 +49,7 @@ import { GuildOrUnavailable, Intents, OPCodes, + OrmUtils, PresenceUpdateEvent, ReadyEventData, ReadyGuildDTO, @@ -260,7 +261,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { where: { id: this.user_id }, select: { // We only want some member props - ...Object.fromEntries(["index", ...MemberPrivateProjection].map((x) => [x, true])), + ...OrmUtils.keysToObject(["index", ...(MemberPrivateProjection)]), settings: true, // guild settings roles: { id: true }, // the full role is fetched from the `guild` relation guild: { id: true }, @@ -353,7 +354,7 @@ export async function onIdentify(this: WebSocket, data: Payload) { type: Not(In([ChannelType.GUILD_PUBLIC_THREAD, ChannelType.GUILD_PRIVATE_THREAD, ChannelType.GUILD_NEWS_THREAD])), }, order: { guild_id: "ASC" }, - relations: ["available_tags"], + relations: { available_tags: true }, }), ), timePromise(() => diff --git a/src/util/dtos/DmChannelDTO.ts b/src/util/dtos/DmChannelDTO.ts index 9e6f05dad..ee541295a 100644 --- a/src/util/dtos/DmChannelDTO.ts +++ b/src/util/dtos/DmChannelDTO.ts @@ -46,7 +46,7 @@ export class DmChannelDTO { .map((r) => User.findOneOrFail({ where: { id: r.user_id }, - select: PublicUserProjection, + select: Object.fromEntries(PublicUserProjection.map((i) => [i, true])), // TODO: clean up }), ) || [], ) diff --git a/src/util/imports/OrmUtils.test.ts b/src/util/imports/OrmUtils.test.ts new file mode 100644 index 000000000..bc0ab055d --- /dev/null +++ b/src/util/imports/OrmUtils.test.ts @@ -0,0 +1,27 @@ +import moduleAlias from "module-alias"; +moduleAlias(__dirname + "../../../../package.json"); +import { config } from "dotenv"; +config({ quiet: true }); + +import { describe, test } from "node:test"; +import assert from "node:assert/strict"; +import { OrmUtils } from "./OrmUtils"; + +describe("OrmUtils", () => { + test("should be able convert string keys to boolean object", () => { + const keys = ["member", "member.user", "member.guild.meow", "member.guild", "guild.name"]; + const expected = { + member: { + user: true, + guild: { + meow: true, + }, + }, + guild: { + name: true, + }, + }; + const result = OrmUtils.keysToObject(keys); + assert.deepStrictEqual(result, expected); + }); +}); diff --git a/src/util/imports/OrmUtils.ts b/src/util/imports/OrmUtils.ts index 5a3c99e83..18dde56ea 100644 --- a/src/util/imports/OrmUtils.ts +++ b/src/util/imports/OrmUtils.ts @@ -2,6 +2,9 @@ // Copyright (c) 2015-2022 TypeORM. http://typeorm.github.io /* eslint-disable @typescript-eslint/no-explicit-any */ // @fc-license-skip +import { arrayGroupBy } from "@spacebar/extensions"; +import { FindOptionsSelect, FindOptionsWhere } from "typeorm"; + export class OrmUtils { // Checks if it's an object made by Object.create(null), {} or new Object() private static isPlainObject(item: unknown) { @@ -96,4 +99,23 @@ export class OrmUtils { return target; } + + // Copyright Spacebar & contributors 2026 (AGPLv3) + static keysToObject(keys: string[]) { + const target: FindOptionsSelect = {}; + for (const k of keys.filter((x) => !x.includes("."))) { + target[k] = true; + } + + const keyGroups = arrayGroupBy( + keys.filter((x) => x.includes(".")), + (k) => k.split(".", 2)[0], + ); + + for (const [k, v] of keyGroups) { + target[k] = this.keysToObject(v.map((vv) => vv.replace(k + ".", ""))); + } + + return target; + } } diff --git a/src/util/util/Permissions.ts b/src/util/util/Permissions.ts index 6585f6927..adb1ad5e5 100644 --- a/src/util/util/Permissions.ts +++ b/src/util/util/Permissions.ts @@ -7,6 +7,7 @@ import { BitField, BitFieldResolvable, BitFlag } from "./BitField"; import { HTTPError } from "lambert-server/HTTPError"; import { ChannelPermissionOverwrite, ChannelPermissionOverwriteType, ChannelType, UserFlags } from "@spacebar/schemas"; import { FindOneOptions } from "typeorm"; +import { OrmUtils } from "@spacebar/util"; export type PermissionResolvable = bigint | number | Permissions | PermissionResolvable[] | PermissionString; @@ -259,8 +260,8 @@ export async function getPermission( select: { id: true, flags: true }, }); const query = { - relations: ["recipients", "thread_members", "thread_members.member", ...(opts.channel_relations || [])], - select: ["type", "parent_id", "id", "recipients", "permission_overwrites", "owner_id", "guild_id", ...(opts.channel_select || [])], + relations: OrmUtils.keysToObject(["recipients", "thread_members", "thread_members.member", ...(opts.channel_relations || [])]), // TODO: cleanup + select: OrmUtils.keysToObject(["type", "parent_id", "id", "recipients", "permission_overwrites", "owner_id", "guild_id", ...(opts.channel_select || [])]), // TODO: cleanup } as FindOneOptions; if (typeof channel_id === "string") { channel = await Channel.findOneOrFail({ where: { id: channel_id }, ...query }); @@ -287,17 +288,17 @@ export async function getPermission( if (typeof guild_id === "string") { guild = await Guild.findOneOrFail({ where: { id: guild_id }, - select: ["id", "owner_id", ...(opts.guild_select || [])], - relations: opts.guild_relations, + select: !opts.guild_select ? { id: true, owner_id: true } : OrmUtils.keysToObject(["id", "owner_id", ...(opts.guild_select || [])]), // TODO: clean up + relations: !opts.guild_relations ? undefined : OrmUtils.keysToObject(opts.guild_relations), // TODO: clean up }); } else { guild = guild_id; } - if (guild.owner_id === user_id) return new Permissions(Permissions.FLAGS.ADMINISTRATOR); + if (guild!.owner_id === user_id) return new Permissions(Permissions.FLAGS.ADMINISTRATOR); member = await Member.findOneOrFail({ - where: { guild_id: guild.id, id: user_id }, - relations: ["roles", ...(opts.member_relations || [])], + where: { guild_id: guild!.id, id: user_id }, + relations: OrmUtils.keysToObject(["roles", ...(opts.member_relations || [])]), // TODO: clean up // select: [ // "id", // TODO: Bug in typeorm? adding these selects breaks the query. // "roles", diff --git a/src/util/util/Token.ts b/src/util/util/Token.ts index e8cd9ecb2..4eaa4bc9d 100644 --- a/src/util/util/Token.ts +++ b/src/util/util/Token.ts @@ -16,18 +16,17 @@ along with this program. If not, see . */ -import jwt from "jsonwebtoken"; -import { Config } from "./Config"; -import { InstanceBan, Session, User } from "../../database/entities"; import crypto from "node:crypto"; -import fs from "node:fs/promises"; import { existsSync } from "node:fs"; -// TODO: dont use deprecated APIs lol -import { FindOptionsRelationByString, FindOptionsSelectByString } from "typeorm"; -import { randomUpperString } from "@spacebar/api"; -import { TimeSpan } from "../../extensions/Timespan"; -import { HTTPError } from "lambert-server/HTTPError"; import path from "node:path"; +import fs from "node:fs/promises"; +import jwt from "jsonwebtoken"; +import { HTTPError } from "lambert-server/HTTPError"; +import { InstanceBan, Session, User } from "@spacebar/database"; +import { randomUpperString } from "@spacebar/api"; +import { TimeSpan } from "@spacebar/extensions"; +import { Config } from "./Config"; +import { OrmUtils } from "@spacebar/util"; /// Change history: /// 1 - Initial version with HS256 @@ -62,8 +61,8 @@ function rejectAndLog(rejectFunction: (reason?: unknown) => void, httpCode: numb export const checkToken = ( token: string, opts?: { - select?: FindOptionsSelectByString; - relations?: FindOptionsRelationByString; + select?: string[]; // TODO: clean up + relations?: string[]; // TODO: clean up ipAddress?: string; fingerprint?: string; }, @@ -85,8 +84,8 @@ export const checkToken = ( let [user, session] = await Promise.all([ User.findOne({ where: { id: decoded.id }, - select: [...(opts?.select || []), "id", "bot", "disabled", "deleted", "rights", "data"], - relations: opts?.relations, + select: OrmUtils.keysToObject([...(opts?.select || []), "id", "bot", "disabled", "deleted", "rights", "data"]), // TODO: clean up + relations: !opts?.relations ? undefined : OrmUtils.keysToObject(opts.relations), // TODO: clean up }), decoded.did ? Session.findOne({ where: { session_id: decoded.did, user_id: decoded.id } }) : undefined, ]);