From 9a415989b1830c14748cf1ea9dc9f63dccd588fb Mon Sep 17 00:00:00 2001 From: Puyodead1 Date: Fri, 19 May 2023 15:46:52 -0400 Subject: [PATCH] add legacy_username user field --- src/api/routes/users/#id/profile.ts | 158 +++++++++++++++++++++++ src/api/routes/users/#user_id/profile.ts | 1 + src/util/entities/User.ts | 6 +- 3 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 src/api/routes/users/#id/profile.ts diff --git a/src/api/routes/users/#id/profile.ts b/src/api/routes/users/#id/profile.ts new file mode 100644 index 000000000..eecec0f35 --- /dev/null +++ b/src/api/routes/users/#id/profile.ts @@ -0,0 +1,158 @@ +/* + Spacebar: A FOSS re-implementation and extension of the Discord.com backend. + Copyright (C) 2023 Spacebar and Spacebar Contributors + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . +*/ + +import { route } from "@spacebar/api"; +import { + Member, + PrivateUserProjection, + User, + UserProfileModifySchema, + UserUpdateEvent, + emitEvent, + handleFile, +} from "@spacebar/util"; +import { Request, Response, Router } from "express"; + +const router: Router = Router(); + +router.get( + "/", + route({ responses: { 200: { body: "UserProfileResponse" } } }), + async (req: Request, res: Response) => { + if (req.params.id === "@me") req.params.id = req.user_id; + + const { guild_id, with_mutual_guilds } = req.query; + + const user = await User.getPublicUser(req.params.id, { + relations: ["connected_accounts"], + }); + + const mutual_guilds: object[] = []; + let premium_guild_since; + + if (with_mutual_guilds == "true") { + const requested_member = await Member.find({ + where: { id: req.params.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, + }); + } + } + } + } + + const guild_member = + guild_id && typeof guild_id == "string" + ? await Member.findOneOrFail({ + where: { id: req.params.id, guild_id: guild_id }, + relations: ["roles"], + }) + : undefined; + + // 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, + }; + + const guildMemberProfile = { + accent_color: null, + banner: guild_member?.banner || null, + bio: guild_member?.bio || "", + guild_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: mutual_guilds, // TODO {id: "", nick: null} when ?with_mutual_guilds=true + 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(), + guild_member_profile: guild_id && guildMemberProfile, + }); + }, +); + +router.patch( + "/", + route({ requestBody: "UserProfileModifySchema" }), + async (req: Request, res: Response) => { + 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"], + }); + + user.assign(body); + await user.save(); + + // 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); + + 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/profile.ts b/src/api/routes/users/#user_id/profile.ts index ec7528a18..1f2b9b40f 100644 --- a/src/api/routes/users/#user_id/profile.ts +++ b/src/api/routes/users/#user_id/profile.ts @@ -124,6 +124,7 @@ router.get("/", route({ responses: { 200: { body: "UserProfileResponse" } } }), guild_member: { ...guild_member?.toPublicMember(), user: user.toPublicUser() }, guild_member_profile: guild_id && guildMemberProfile, badges: badges.filter((x) => user.badge_ids?.includes(x.id)), + legacy_username: user.legacy_username, // part of the uniqueUsernames feature, only used for migrated accounts }); }); diff --git a/src/util/entities/User.ts b/src/util/entities/User.ts index e03054afb..e955bd004 100644 --- a/src/util/entities/User.ts +++ b/src/util/entities/User.ts @@ -32,6 +32,7 @@ import { ChannelType, PrivateUserProjection, PublicUser, PublicUserProjection, U export enum PublicUserEnum { username, global_name, + legacy_username, discriminator, id, public_flags, @@ -55,7 +56,10 @@ export class User extends BaseClass { username: string; // username max length 32, min 2 (should be configurable) @Column({ nullable: true }) - global_name?: string; // puyo: uniqueUsernames + global_name?: string; // puyo: part of the uniqueUsernames feature, this is the users "nickname" + + @Column({ nullable: true }) + legacy_username?: string; // puyo: part of the uniqueUsernames feature, shows the users old username (only used for migrated accounts) @Column() discriminator: string; // opaque string: 4 digits on discord.com, 0 for uniqueUsernames