diff --git a/assets/openapi.json b/assets/openapi.json index 14d13fa4..5a973072 100644 Binary files a/assets/openapi.json and b/assets/openapi.json differ diff --git a/assets/schemas.json b/assets/schemas.json index b9112572..08ba372c 100644 Binary files a/assets/schemas.json and b/assets/schemas.json differ diff --git a/src/api/routes/teams.ts b/src/api/routes/teams.ts index 26570165..1328f661 100644 --- a/src/api/routes/teams.ts +++ b/src/api/routes/teams.ts @@ -1,29 +1,99 @@ /* 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 { Request, Response, Router } from "express"; import { route } from "@spacebar/api"; +import { + Team, + TeamCreateSchema, + TeamMember, + TeamMemberRole, + TeamMemberState, + User, +} from "@spacebar/util"; +import { HTTPError } from "lambert-server"; const router: Router = Router(); -router.get("/", route({}), async (req: Request, res: Response) => { - //TODO - res.send([]); -}); +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"], + }); + + 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"); + + const body = req.body as TeamCreateSchema; + + 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(); + + res.json(team); + }, +); export default router; diff --git a/src/util/entities/TeamMember.ts b/src/util/entities/TeamMember.ts index 2cd04710..e4c18b89 100644 --- a/src/util/entities/TeamMember.ts +++ b/src/util/entities/TeamMember.ts @@ -25,6 +25,11 @@ export enum TeamMemberState { INVITED = 1, ACCEPTED = 2, } +export enum TeamMemberRole { + ADMIN = "admin", + DEVELOPER = "developer", + READ_ONLY = "read_only", +} @Entity({ name: "team_members", @@ -37,6 +42,9 @@ export class TeamMember extends BaseClass { @Column({ type: "simple-array" }) permissions: string[]; + @Column() + role: TeamMemberRole; + @Column({ nullable: true }) @RelationId((member: TeamMember) => member.team) team_id: string; diff --git a/src/util/migration/mariadb/1724477620293-teamMemberRole.ts b/src/util/migration/mariadb/1724477620293-teamMemberRole.ts new file mode 100644 index 00000000..ff4be7bf --- /dev/null +++ b/src/util/migration/mariadb/1724477620293-teamMemberRole.ts @@ -0,0 +1,17 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class TeamMemberRole1724477620293 implements MigrationInterface { + name = "TeamMemberRole1724477620293"; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + "ALTER TABLE `team_members` ADD COLUMN `role` VARCHAR(255) NOT NULL AFTER `permissions`", + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + "ALTER TABLE `team_members` DROP COLUMN `role`", + ); + } +} diff --git a/src/util/migration/mysql/1724477620293-teamMemberRole.ts b/src/util/migration/mysql/1724477620293-teamMemberRole.ts new file mode 100644 index 00000000..ff4be7bf --- /dev/null +++ b/src/util/migration/mysql/1724477620293-teamMemberRole.ts @@ -0,0 +1,17 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class TeamMemberRole1724477620293 implements MigrationInterface { + name = "TeamMemberRole1724477620293"; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + "ALTER TABLE `team_members` ADD COLUMN `role` VARCHAR(255) NOT NULL AFTER `permissions`", + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + "ALTER TABLE `team_members` DROP COLUMN `role`", + ); + } +} diff --git a/src/util/migration/postgres/1724477620293-teamMemberRole.ts b/src/util/migration/postgres/1724477620293-teamMemberRole.ts new file mode 100644 index 00000000..d3276d91 --- /dev/null +++ b/src/util/migration/postgres/1724477620293-teamMemberRole.ts @@ -0,0 +1,15 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class TeamMemberRole1724477620293 implements MigrationInterface { + name = "TeamMemberRole1724477620293"; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + "ALTER TABLE team_members ADD COLUMN role text NOT NULL", + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query("ALTER TABLE team_members DROP COLUMN role"); + } +} diff --git a/src/util/schemas/ApplicationCreateSchema.ts b/src/util/schemas/ApplicationCreateSchema.ts index 17bbc94c..80956ec4 100644 --- a/src/util/schemas/ApplicationCreateSchema.ts +++ b/src/util/schemas/ApplicationCreateSchema.ts @@ -1,22 +1,22 @@ /* 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 . */ export interface ApplicationCreateSchema { name: string; - team_id?: string | number; + team_id?: string; } diff --git a/src/util/schemas/TeamCreateSchema.ts b/src/util/schemas/TeamCreateSchema.ts new file mode 100644 index 00000000..5903a536 --- /dev/null +++ b/src/util/schemas/TeamCreateSchema.ts @@ -0,0 +1,21 @@ +/* + 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 . +*/ + +export interface TeamCreateSchema { + name: string; +} diff --git a/src/util/schemas/index.ts b/src/util/schemas/index.ts index 62199dfb..7d8abaa0 100644 --- a/src/util/schemas/index.ts +++ b/src/util/schemas/index.ts @@ -62,6 +62,7 @@ export * from "./RequestGuildMembersSchema"; export * from "./RoleModifySchema"; export * from "./RolePositionUpdateSchema"; export * from "./SelectProtocolSchema"; +export * from "./TeamCreateSchema"; export * from "./TemplateCreateSchema"; export * from "./TemplateModifySchema"; export * from "./TotpDisableSchema"; diff --git a/src/util/schemas/responses/TeamListResponse.ts b/src/util/schemas/responses/TeamListResponse.ts new file mode 100644 index 00000000..375f0ae6 --- /dev/null +++ b/src/util/schemas/responses/TeamListResponse.ts @@ -0,0 +1,21 @@ +/* + 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 { Team } from "@spacebar/util"; + +export interface TeamListResponse extends Array {} diff --git a/src/util/schemas/responses/index.ts b/src/util/schemas/responses/index.ts index accb26f5..070a2e55 100644 --- a/src/util/schemas/responses/index.ts +++ b/src/util/schemas/responses/index.ts @@ -1,17 +1,17 @@ /* 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 . */ @@ -40,6 +40,7 @@ export * from "./InstanceStatsResponse"; export * from "./LocationMetadataResponse"; export * from "./MemberJoinGuildResponse"; export * from "./OAuthAuthorizeResponse"; +export * from "./TeamListResponse"; export * from "./Tenor"; export * from "./TokenResponse"; export * from "./TypedResponses";