HACKHACK: Migrate to typeorm v1.0.0, still work to do in actually updating all the queries properly

This commit is contained in:
Rory&
2026-06-13 01:10:59 +02:00
parent 8da172d2ad
commit 92f25c3a22
27 changed files with 108 additions and 55 deletions
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -118,7 +118,7 @@
"prom-client": "^15.1.3",
"reflect-metadata": "^0.2.2",
"tslib": "^2.8.1",
"typeorm": "^0.3.30",
"typeorm": "^1.0.0",
"why-is-node-running": "^3.2.2",
"wretch": "^3.0.8",
"ws": "^8.21.0"
+2 -2
View File
@@ -151,7 +151,7 @@ router.patch(
const { channel_id } = req.params as { [key: string]: string };
const channel = await Channel.findOneOrFail({
where: { id: channel_id },
relations: ["available_tags"],
relations: { available_tags: true },
});
if (channel.isThread()) {
@@ -182,7 +182,7 @@ router.patch(
where: {
id: channel.parent_id as string,
},
relations: ["available_tags"],
relations: { available_tags: true },
});
if (!parent.available_tags) throw new Error("shoot, internetal error");
const realTags = new Map(parent.available_tags.map((tag) => [tag.id, tag]));
@@ -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);
@@ -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());
@@ -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 },
@@ -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(),
},
+3 -3
View File
@@ -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");
@@ -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);
+1 -1
View File
@@ -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(
@@ -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" },
});
@@ -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<string>();
ms.forEach((memb) => {
memb.roles.forEach(({ id }) => rSet.add(id));
+2 -2
View File
@@ -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({
+1 -1
View File
@@ -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());
+1 -1
View File
@@ -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 } })))
+5 -2
View File
@@ -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) {
+2 -2
View File
@@ -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
+7 -7
View File
@@ -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;
+1 -1
View File
@@ -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,
+1 -1
View File
@@ -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({
+1 -1
View File
@@ -235,7 +235,7 @@ export class User extends BaseClass {
static async getPublicUser(user_id: string): Promise<PublicUser> {
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();
}
+3 -2
View File
@@ -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", ...(<string[]>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(() =>
+1 -1
View File
@@ -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
}),
) || [],
)
+27
View File
@@ -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);
});
});
+22
View File
@@ -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<any> = {};
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;
}
}
+8 -7
View File
@@ -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", ...(<string[]>opts.channel_select || [])]), // TODO: cleanup
} as FindOneOptions<Channel>;
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", ...(<string[]>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",
+12 -13
View File
@@ -16,18 +16,17 @@
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
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<User>;
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,
]);