diff --git a/src/api/routes/users/#id/profile.ts b/src/api/routes/users/#id/profile.ts index eecec0f3..db0922d6 100644 --- a/src/api/routes/users/#id/profile.ts +++ b/src/api/routes/users/#id/profile.ts @@ -18,6 +18,7 @@ import { route } from "@spacebar/api"; import { + Badge, Member, PrivateUserProjection, User, @@ -98,6 +99,9 @@ router.get( bio: guild_member?.bio || "", guild_id, }; + + const badges = await Badge.find(); + res.json({ connected_accounts: user.connected_accounts.filter( (x) => x.visibility != 0, @@ -111,6 +115,7 @@ router.get( user_profile: userProfile, guild_member: guild_member?.toPublicMember(), guild_member_profile: guild_id && guildMemberProfile, + badges: badges.filter((x) => user.badge_ids?.includes(x.id)), }); }, ); diff --git a/src/util/dtos/UserDTO.ts b/src/util/dtos/UserDTO.ts index a24c8d96..17e4435f 100644 --- a/src/util/dtos/UserDTO.ts +++ b/src/util/dtos/UserDTO.ts @@ -24,6 +24,7 @@ export class MinimalPublicUserDTO { id: string; public_flags: number; username: string; + badge_ids?: string[] | null; constructor(user: User) { this.avatar = user.avatar; @@ -31,5 +32,6 @@ export class MinimalPublicUserDTO { this.id = user.id; this.public_flags = user.public_flags; this.username = user.username; + this.badge_ids = user.badge_ids; } } diff --git a/src/util/entities/Badge.ts b/src/util/entities/Badge.ts new file mode 100644 index 00000000..9535e207 --- /dev/null +++ b/src/util/entities/Badge.ts @@ -0,0 +1,35 @@ +/* + 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 { Column, Entity } from "typeorm"; +import { BaseClassWithoutId } from "./BaseClass"; + +@Entity("badges") +export class Badge extends BaseClassWithoutId { + @Column({ primary: true }) + id: string; + + @Column() + description: string; + + @Column() + icon: string; + + @Column({ nullable: true }) + link?: string; +} diff --git a/src/util/entities/User.ts b/src/util/entities/User.ts index c6582b00..c929039e 100644 --- a/src/util/entities/User.ts +++ b/src/util/entities/User.ts @@ -49,6 +49,7 @@ export enum PublicUserEnum { premium_type, theme_colors, pronouns, + badge_ids, } export type PublicUserKeys = keyof typeof PublicUserEnum; @@ -231,6 +232,9 @@ export class User extends BaseClass { @OneToMany(() => SecurityKey, (key: SecurityKey) => key.user) security_keys: SecurityKey[]; + @Column({ type: "simple-array", nullable: true }) + badge_ids?: string[]; + // TODO: I don't like this method? validate() { if (this.discriminator) { diff --git a/src/util/entities/index.ts b/src/util/entities/index.ts index aa943dca..b2356aa7 100644 --- a/src/util/entities/index.ts +++ b/src/util/entities/index.ts @@ -20,6 +20,7 @@ export * from "./Application"; export * from "./Attachment"; export * from "./AuditLog"; export * from "./BackupCodes"; +export * from "./Badge"; export * from "./Ban"; export * from "./BaseClass"; export * from "./Categories"; diff --git a/src/util/migration/mariadb/1720628601997-badges.ts b/src/util/migration/mariadb/1720628601997-badges.ts new file mode 100644 index 00000000..af298e42 --- /dev/null +++ b/src/util/migration/mariadb/1720628601997-badges.ts @@ -0,0 +1,21 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class Badges1720628601997 implements MigrationInterface { + name = "Badges1720628601997"; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE \`badges\` (\`id\` varchar(255) NOT NULL, \`description\` varchar(255) NOT NULL, \`icon\` varchar(255) NOT NULL, \`link\` varchar(255) NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`, + ); + await queryRunner.query( + `ALTER TABLE \`users\` ADD \`badge_ids\` text NULL`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE \`users\` DROP COLUMN \`badge_ids\``, + ); + await queryRunner.query(`DROP TABLE \`badges\``); + } +} diff --git a/src/util/migration/mysql/1720628601997-badges.ts b/src/util/migration/mysql/1720628601997-badges.ts new file mode 100644 index 00000000..af298e42 --- /dev/null +++ b/src/util/migration/mysql/1720628601997-badges.ts @@ -0,0 +1,21 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class Badges1720628601997 implements MigrationInterface { + name = "Badges1720628601997"; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE \`badges\` (\`id\` varchar(255) NOT NULL, \`description\` varchar(255) NOT NULL, \`icon\` varchar(255) NOT NULL, \`link\` varchar(255) NULL, PRIMARY KEY (\`id\`)) ENGINE=InnoDB`, + ); + await queryRunner.query( + `ALTER TABLE \`users\` ADD \`badge_ids\` text NULL`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE \`users\` DROP COLUMN \`badge_ids\``, + ); + await queryRunner.query(`DROP TABLE \`badges\``); + } +} diff --git a/src/util/migration/postgres/1720628601997-badges.ts b/src/util/migration/postgres/1720628601997-badges.ts new file mode 100644 index 00000000..f7b9958b --- /dev/null +++ b/src/util/migration/postgres/1720628601997-badges.ts @@ -0,0 +1,16 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class Badges1720628601997 implements MigrationInterface { + name = "Badges1720628601997"; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "badges" ("id" character varying NOT NULL, "description" character varying NOT NULL, "icon" character varying NOT NULL, "link" character varying, CONSTRAINT "PK_8a651318b8de577e8e217676466" PRIMARY KEY ("id"))`, + ); + await queryRunner.query(`ALTER TABLE "users" ADD "badge_ids" text`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "users" DROP COLUMN "badge_ids"`); + } +} diff --git a/src/util/schemas/responses/UserProfileResponse.ts b/src/util/schemas/responses/UserProfileResponse.ts index 26e7e3bc..7b63542e 100644 --- a/src/util/schemas/responses/UserProfileResponse.ts +++ b/src/util/schemas/responses/UserProfileResponse.ts @@ -17,6 +17,7 @@ */ import { + Badge, Member, PublicConnectedAccount, PublicMember, @@ -52,4 +53,5 @@ export interface UserProfileResponse { user_profile: UserProfile; guild_member?: PublicMember; guild_member_profile?: PublicMemberProfile; + badges: Badge[]; }