From e98bd1f647f85a1b055646b3e662b9207f40681f Mon Sep 17 00:00:00 2001 From: HumanoidSandvichDispenser Date: Tue, 13 May 2025 23:13:44 -0700 Subject: [PATCH] Add Become Player admin feature --- availabili.tf/package-lock.json | 112 +++++++++--------- availabili.tf/src/assets/main.css | 66 +++++++++++ availabili.tf/src/client/index.ts | 2 + .../src/client/models/GetUserResponse.ts | 12 ++ .../src/client/models/PlayerSchema.ts | 1 + .../src/client/models/PlayerSchemaList.ts | 6 + .../client/models/ViewTeamMembersResponse.ts | 1 + .../src/client/services/DefaultService.ts | 76 ++++++++---- availabili.tf/src/components/AdminPanel.vue | 0 .../src/components/ProfileDropdown.vue | 11 ++ availabili.tf/src/router/index.ts | 20 ++++ availabili.tf/src/stores/auth.ts | 44 ++++++- availabili.tf/src/views/Admin/DoasView.vue | 64 ++++++++++ availabili.tf/src/views/Admin/GeneralView.vue | 9 ++ availabili.tf/src/views/AdminView.vue | 22 ++++ availabili.tf/src/views/TeamSettingsView.vue | 67 +---------- backend-flask/login.py | 23 +++- backend-flask/middleware.py | 26 ++++ backend-flask/user.py | 54 ++++++++- 19 files changed, 464 insertions(+), 152 deletions(-) create mode 100644 availabili.tf/src/client/models/GetUserResponse.ts create mode 100644 availabili.tf/src/client/models/PlayerSchemaList.ts create mode 100644 availabili.tf/src/components/AdminPanel.vue create mode 100644 availabili.tf/src/views/Admin/DoasView.vue create mode 100644 availabili.tf/src/views/Admin/GeneralView.vue create mode 100644 availabili.tf/src/views/AdminView.vue diff --git a/availabili.tf/package-lock.json b/availabili.tf/package-lock.json index 62f854d..1ba51b5 100644 --- a/availabili.tf/package-lock.json +++ b/availabili.tf/package-lock.json @@ -1400,12 +1400,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/web-bluetooth": { - "version": "0.0.20", - "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", - "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", - "license": "MIT" - }, "node_modules/@types/yauzl": { "version": "2.10.3", "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", @@ -2035,56 +2029,6 @@ } } }, - "node_modules/@vueuse/core": { - "version": "10.11.1", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.11.1.tgz", - "integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==", - "license": "MIT", - "dependencies": { - "@types/web-bluetooth": "^0.0.20", - "@vueuse/metadata": "10.11.1", - "@vueuse/shared": "10.11.1", - "vue-demi": ">=0.14.8" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/core/node_modules/vue-demi": { - "version": "0.14.10", - "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", - "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", - "hasInstallScript": true, - "license": "MIT", - "bin": { - "vue-demi-fix": "bin/vue-demi-fix.js", - "vue-demi-switch": "bin/vue-demi-switch.js" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "@vue/composition-api": "^1.0.0-rc.1", - "vue": "^3.0.0-0 || ^2.6.0" - }, - "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - } - } - }, - "node_modules/@vueuse/metadata": { - "version": "10.11.1", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.1.tgz", - "integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, "node_modules/@vueuse/shared": { "version": "10.11.1", "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.11.1.tgz", @@ -6399,6 +6343,62 @@ "vue": ">= 3.2.0" } }, + "node_modules/radix-vue/node_modules/@types/web-bluetooth": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", + "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", + "license": "MIT" + }, + "node_modules/radix-vue/node_modules/@vueuse/core": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.11.1.tgz", + "integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.20", + "@vueuse/metadata": "10.11.1", + "@vueuse/shared": "10.11.1", + "vue-demi": ">=0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/radix-vue/node_modules/@vueuse/core/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/radix-vue/node_modules/@vueuse/metadata": { + "version": "10.11.1", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.1.tgz", + "integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/radix-vue/node_modules/nanoid": { "version": "5.0.9", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.9.tgz", diff --git a/availabili.tf/src/assets/main.css b/availabili.tf/src/assets/main.css index fcb52aa..9c34cd5 100644 --- a/availabili.tf/src/assets/main.css +++ b/availabili.tf/src/assets/main.css @@ -421,3 +421,69 @@ tr:last-child > td:first-child { tr:last-child > td:last-child { border-bottom-right-radius: 4px; } + +.sidebar-container { + display: flex; + gap: 16px; + justify-content: center; +} + +.sidebar-container nav.sidebar { + display: flex; + justify-content: end; +} + +.sidebar-container .view { + width: 60%; +} + + +nav.sidebar h3 { + text-transform: uppercase; + color: var(--overlay-0); + padding: 0 8px; + font-size: 8pt; +} + +nav.sidebar > .categories { + display: flex; + flex-direction: column; + width: 192px; + gap: 4px; +} + +nav.sidebar a.tab { + font-size: 11pt; + color: var(--overlay-0); + padding: 6px 10px; + font-weight: 500; + border-radius: 4px; +} + +nav.sidebar a.tab:hover { + background-color: var(--crust); + color: var(--text); +} + +nav.sidebar a.tab.router-link-exact-active { + background-color: var(--crust); + color: var(--text); +} + +nav.sidebar button { + font-size: 11pt; + font-weight: 500; + padding: 6px 10px; + background-color: transparent; + color: var(--overlay-0); +} + +nav.sidebar button:hover { + background-color: var(--crust); + color: var(--text); +} + +nav.sidebar button.destructive-on-hover:hover { + background-color: var(--destructive); + color: var(--base); +} diff --git a/availabili.tf/src/client/index.ts b/availabili.tf/src/client/index.ts index fece2fc..d305b61 100644 --- a/availabili.tf/src/client/index.ts +++ b/availabili.tf/src/client/index.ts @@ -22,10 +22,12 @@ export type { EventWithPlayerSchema } from './models/EventWithPlayerSchema'; export type { EventWithPlayerSchemaList } from './models/EventWithPlayerSchemaList'; export type { GetEventPlayersResponse } from './models/GetEventPlayersResponse'; export type { GetMatchQuery } from './models/GetMatchQuery'; +export type { GetUserResponse } from './models/GetUserResponse'; export type { MatchSchema } from './models/MatchSchema'; export type { PlayerEventRolesSchema } from './models/PlayerEventRolesSchema'; export type { PlayerRoleSchema } from './models/PlayerRoleSchema'; export type { PlayerSchema } from './models/PlayerSchema'; +export type { PlayerSchemaList } from './models/PlayerSchemaList'; export type { PlayerTeamAvailabilityRoleSchema } from './models/PlayerTeamAvailabilityRoleSchema'; export type { PutScheduleForm } from './models/PutScheduleForm'; export type { RoleSchema } from './models/RoleSchema'; diff --git a/availabili.tf/src/client/models/GetUserResponse.ts b/availabili.tf/src/client/models/GetUserResponse.ts new file mode 100644 index 0000000..3d6f38c --- /dev/null +++ b/availabili.tf/src/client/models/GetUserResponse.ts @@ -0,0 +1,12 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { PlayerSchema } from './PlayerSchema'; +export type GetUserResponse = { + isAdmin?: boolean; + realUser: (PlayerSchema | null); + steamId: string; + username: string; +}; + diff --git a/availabili.tf/src/client/models/PlayerSchema.ts b/availabili.tf/src/client/models/PlayerSchema.ts index 7be47da..0e86040 100644 --- a/availabili.tf/src/client/models/PlayerSchema.ts +++ b/availabili.tf/src/client/models/PlayerSchema.ts @@ -3,6 +3,7 @@ /* tslint:disable */ /* eslint-disable */ export type PlayerSchema = { + isAdmin?: boolean; steamId: string; username: string; }; diff --git a/availabili.tf/src/client/models/PlayerSchemaList.ts b/availabili.tf/src/client/models/PlayerSchemaList.ts new file mode 100644 index 0000000..383acdf --- /dev/null +++ b/availabili.tf/src/client/models/PlayerSchemaList.ts @@ -0,0 +1,6 @@ +/* generated using openapi-typescript-codegen -- do not edit */ +/* istanbul ignore file */ +/* tslint:disable */ +/* eslint-disable */ +import type { PlayerSchema } from './PlayerSchema'; +export type PlayerSchemaList = Array; diff --git a/availabili.tf/src/client/models/ViewTeamMembersResponse.ts b/availabili.tf/src/client/models/ViewTeamMembersResponse.ts index c5f977f..7ec2da3 100644 --- a/availabili.tf/src/client/models/ViewTeamMembersResponse.ts +++ b/availabili.tf/src/client/models/ViewTeamMembersResponse.ts @@ -6,6 +6,7 @@ import type { RoleSchema } from './RoleSchema'; export type ViewTeamMembersResponse = { availability: Array; createdAt: string; + isAdmin?: boolean; isTeamLeader?: boolean; playtime: number; roles: Array; diff --git a/availabili.tf/src/client/services/DefaultService.ts b/availabili.tf/src/client/services/DefaultService.ts index c290348..9d4e32e 100644 --- a/availabili.tf/src/client/services/DefaultService.ts +++ b/availabili.tf/src/client/services/DefaultService.ts @@ -12,8 +12,10 @@ import type { EventSchema } from '../models/EventSchema'; import type { EventWithPlayerSchema } from '../models/EventWithPlayerSchema'; import type { EventWithPlayerSchemaList } from '../models/EventWithPlayerSchemaList'; import type { GetEventPlayersResponse } from '../models/GetEventPlayersResponse'; +import type { GetUserResponse } from '../models/GetUserResponse'; import type { MatchSchema } from '../models/MatchSchema'; import type { PlayerSchema } from '../models/PlayerSchema'; +import type { PlayerSchemaList } from '../models/PlayerSchemaList'; import type { PutScheduleForm } from '../models/PutScheduleForm'; import type { SetUsernameJson } from '../models/SetUsernameJson'; import type { SubmitMatchJson } from '../models/SubmitMatchJson'; @@ -279,10 +281,10 @@ export class DefaultService { } /** * get_user - * @returns PlayerSchema OK + * @returns GetUserResponse OK * @throws ApiError */ - public getUser(): CancelablePromise { + public getUser(): CancelablePromise { return this.httpRequest.request({ method: 'GET', url: '/api/login/get-user', @@ -518,28 +520,6 @@ export class DefaultService { }, }); } - /** - * delete_team - * @param teamId - * @returns any OK - * @throws ApiError - */ - public deleteTeam( - teamId: number, - ): CancelablePromise { - return this.httpRequest.request({ - method: 'DELETE', - url: '/api/team/id/{team_id}/', - path: { - 'team_id': teamId, - }, - errors: { - 403: `Forbidden`, - 404: `Not Found`, - 422: `Unprocessable Content`, - }, - }); - } /** * view_team * @param teamId @@ -801,6 +781,54 @@ export class DefaultService { }, }); } + /** + * get_all_users + * @returns PlayerSchemaList OK + * @throws ApiError + */ + public getAllUsers(): CancelablePromise { + return this.httpRequest.request({ + method: 'GET', + url: '/api/user/all', + errors: { + 422: `Unprocessable Content`, + }, + }); + } + /** + * unset_doas + * @returns void + * @throws ApiError + */ + public unsetDoas(): CancelablePromise { + return this.httpRequest.request({ + method: 'DELETE', + url: '/api/user/doas', + errors: { + 422: `Unprocessable Content`, + }, + }); + } + /** + * set_doas + * @param steamId + * @returns PlayerSchema OK + * @throws ApiError + */ + public setDoas( + steamId: string, + ): CancelablePromise { + return this.httpRequest.request({ + method: 'PUT', + url: '/api/user/doas/{steam_id}', + path: { + 'steam_id': steamId, + }, + errors: { + 422: `Unprocessable Content`, + }, + }); + } /** * set_username * @param requestBody diff --git a/availabili.tf/src/components/AdminPanel.vue b/availabili.tf/src/components/AdminPanel.vue new file mode 100644 index 0000000..e69de29 diff --git a/availabili.tf/src/components/ProfileDropdown.vue b/availabili.tf/src/components/ProfileDropdown.vue index 8cc0b49..3b83fd5 100644 --- a/availabili.tf/src/components/ProfileDropdown.vue +++ b/availabili.tf/src/components/ProfileDropdown.vue @@ -19,6 +19,9 @@ function logout() {