From f847841582e35e2a95c018850b4f3963d76fa8eb Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Thu, 16 Feb 2023 21:24:29 +0100 Subject: [PATCH] Added routing by permissions #130 --- kdb-bot/src/bot_api/model/auth_user_dto.py | 20 +++++ kdb-bot/src/bot_api/model/user_dto.py | 88 +++++++++++++++++++ .../transformer/auth_user_transformer.py | 45 +++++++++- kdb-bot/src/bot_data/model/auth_user.py | 2 + kdb-bot/src/bot_graphql/queries/user_query.py | 3 + kdb-web/src/app/models/auth/auth-user.dto.ts | 35 +++++--- .../dashboard/dashboard.component.ts | 2 +- .../server/profile/profile.component.html | 1 + .../server/profile/profile.component.scss | 0 .../server/profile/profile.component.spec.ts | 23 +++++ .../view/server/profile/profile.component.ts | 10 +++ .../server-dashboard.component.ts | 2 +- .../view/server/server-routing.module.ts | 2 + .../app/modules/view/server/server.module.ts | 4 +- kdb-web/src/app/services/auth/auth.service.ts | 14 +++ .../app/services/sidebar/sidebar.service.ts | 31 ++++--- kdb-web/src/assets/i18n/de.json | 4 +- 17 files changed, 260 insertions(+), 26 deletions(-) create mode 100644 kdb-bot/src/bot_api/model/user_dto.py create mode 100644 kdb-web/src/app/modules/view/server/profile/profile.component.html create mode 100644 kdb-web/src/app/modules/view/server/profile/profile.component.scss create mode 100644 kdb-web/src/app/modules/view/server/profile/profile.component.spec.ts create mode 100644 kdb-web/src/app/modules/view/server/profile/profile.component.ts diff --git a/kdb-bot/src/bot_api/model/auth_user_dto.py b/kdb-bot/src/bot_api/model/auth_user_dto.py index 6d80410b..0572fad8 100644 --- a/kdb-bot/src/bot_api/model/auth_user_dto.py +++ b/kdb-bot/src/bot_api/model/auth_user_dto.py @@ -1,7 +1,10 @@ from datetime import datetime from typing import Optional +from cpl_query.extension import List + from bot_api.abc.dto_abc import DtoABC +from bot_api.model.user_dto import UserDTO from bot_data.model.auth_role_enum import AuthRoleEnum @@ -15,6 +18,7 @@ class AuthUserDTO(DtoABC): password: str = None, confirmation_id: Optional[str] = None, auth_role: AuthRoleEnum = None, + users: List[UserDTO] = None, created_at: datetime = None, modified_at: datetime = None, ): @@ -30,6 +34,11 @@ class AuthUserDTO(DtoABC): self._created_at = created_at self._modified_at = modified_at + if users is None: + self._users = List(UserDTO) + else: + self._users = users + @property def id(self) -> int: return self._id @@ -82,6 +91,10 @@ class AuthUserDTO(DtoABC): def auth_role(self, value: AuthRoleEnum): self._auth_role = value + @property + def users(self) -> List[UserDTO]: + return self._users + @property def created_at(self) -> datetime: return self._created_at @@ -98,6 +111,12 @@ class AuthUserDTO(DtoABC): self._password = values["password"] self._is_confirmed = values["isConfirmed"] self._auth_role = AuthRoleEnum(values["authRole"]) + if "users" in values: + self._users = List(UserDTO) + for u in values["users"]: + user = UserDTO() + user.from_dict(u) + self._users.add(user) self._created_at = values["createdAt"] self._modified_at = values["modifiedAt"] @@ -111,6 +130,7 @@ class AuthUserDTO(DtoABC): "password": self._password, "isConfirmed": self._is_confirmed, "authRole": self._auth_role.value, + "users": self._users.select(lambda u: u.to_dict()).to_list(), "createdAt": self._created_at, "modifiedAt": self._modified_at, } diff --git a/kdb-bot/src/bot_api/model/user_dto.py b/kdb-bot/src/bot_api/model/user_dto.py new file mode 100644 index 00000000..14b9e043 --- /dev/null +++ b/kdb-bot/src/bot_api/model/user_dto.py @@ -0,0 +1,88 @@ +from typing import Optional + +from bot_api.abc.dto_abc import DtoABC +from bot_data.model.server import Server + + +class UserDTO(DtoABC): + def __init__( + self, + id: int = None, + dc_id: int = None, + xp: int = None, + minecraft_id: Optional[str] = None, + server: Optional[Server] = None, + is_technician: Optional[bool] = None, + is_admin: Optional[bool] = None, + is_moderator: Optional[bool] = None, + ): + DtoABC.__init__(self) + + self._user_id = id + self._discord_id = dc_id + self._xp = xp + self._minecraft_id = minecraft_id + self._server = server + + self._is_technician = is_technician + self._is_admin = is_admin + self._is_moderator = is_moderator + + @property + def user_id(self) -> int: + return self._user_id + + @property + def discord_id(self) -> int: + return self._discord_id + + @property + def xp(self) -> int: + return self._xp + + @xp.setter + def xp(self, value: int): + self._xp = value + + @property + def minecraft_id(self) -> Optional[str]: + return self._minecraft_id + + @minecraft_id.setter + def minecraft_id(self, value: str): + self._minecraft_id = value + + @property + def server(self) -> Optional[Server]: + return self._server + + @property + def is_technician(self) -> bool: + return self._is_technician if self._is_technician is not None else False + + @property + def is_admin(self) -> bool: + return self._is_admin if self._is_admin is not None else False + + @property + def is_moderator(self) -> bool: + return self._is_moderator if self._is_moderator is not None else False + + def from_dict(self, values: dict): + self._user_id = values["id"] + self._discord_id = values["dcId"] + self._xp = values["xp"] + self._minecraft_id = values["minecraftId"] + self._server = values["server"] + + def to_dict(self) -> dict: + return { + "id": self._user_id, + "dcId": self._discord_id, + "xp": self._xp, + "minecraftId": self._minecraft_id, + "server": self._server.server_id, + "isTechnician": self.is_technician, + "isAdmin": self.is_admin, + "isModerator": self.is_moderator, + } diff --git a/kdb-bot/src/bot_api/transformer/auth_user_transformer.py b/kdb-bot/src/bot_api/transformer/auth_user_transformer.py index 0e47ae35..63ba2189 100644 --- a/kdb-bot/src/bot_api/transformer/auth_user_transformer.py +++ b/kdb-bot/src/bot_api/transformer/auth_user_transformer.py @@ -1,9 +1,16 @@ from datetime import datetime +from cpl_core.dependency_injection import ServiceProviderABC +from cpl_discord.service import DiscordBotServiceABC +from cpl_query.extension import List + from bot_api.abc.transformer_abc import TransformerABC from bot_api.model.auth_user_dto import AuthUserDTO +from bot_api.model.user_dto import UserDTO from bot_data.model.auth_role_enum import AuthRoleEnum from bot_data.model.auth_user import AuthUser +from bot_data.model.user import User +from modules.permission.abc.permission_service_abc import PermissionServiceABC class AuthUserTransformer(TransformerABC): @@ -25,7 +32,28 @@ class AuthUserTransformer(TransformerABC): ) @staticmethod - def to_dto(db: AuthUser, password: str = None) -> AuthUserDTO: + @ServiceProviderABC.inject + def _is_technician(user: User, bot: DiscordBotServiceABC, permissions: PermissionServiceABC): + guild = bot.get_guild(user.server.discord_server_id) + member = guild.get_member(user.discord_id) + return permissions.is_member_technician(member) + + @staticmethod + @ServiceProviderABC.inject + def _is_admin(user: User, bot: DiscordBotServiceABC, permissions: PermissionServiceABC): + guild = bot.get_guild(user.server.discord_server_id) + member = guild.get_member(user.discord_id) + return permissions.is_member_technician(member) + + @staticmethod + @ServiceProviderABC.inject + def _is_moderator(user: User, bot: DiscordBotServiceABC, permissions: PermissionServiceABC): + guild = bot.get_guild(user.server.discord_server_id) + member = guild.get_member(user.discord_id) + return permissions.is_member_technician(member) + + @classmethod + def to_dto(cls, db: AuthUser, password: str = None) -> AuthUserDTO: return AuthUserDTO( db.id, db.first_name, @@ -34,6 +62,21 @@ class AuthUserTransformer(TransformerABC): "" if password is None else password, db.confirmation_id, db.auth_role, + List( + UserDTO, + db.users.select( + lambda u: UserDTO( + u.user_id, + u.discord_id, + u.xp, + u.minecraft_id, + u.server, + cls._is_technician(u), + cls._is_admin(u), + cls._is_moderator(u), + ) + ), + ), db.created_at, db.modified_at, ) diff --git a/kdb-bot/src/bot_data/model/auth_user.py b/kdb-bot/src/bot_data/model/auth_user.py index f3a46707..84ba4aac 100644 --- a/kdb-bot/src/bot_data/model/auth_user.py +++ b/kdb-bot/src/bot_data/model/auth_user.py @@ -42,6 +42,8 @@ class AuthUser(TableABC): if users is None: self._users = List(User) + else: + self._users = users self._auth_role_id = auth_role diff --git a/kdb-bot/src/bot_graphql/queries/user_query.py b/kdb-bot/src/bot_graphql/queries/user_query.py index ddcda7b9..0c527515 100644 --- a/kdb-bot/src/bot_graphql/queries/user_query.py +++ b/kdb-bot/src/bot_graphql/queries/user_query.py @@ -10,6 +10,7 @@ from bot_graphql.filter.user_joined_game_server_filter import UserJoinedGameServ from bot_graphql.filter.user_joined_server_filter import UserJoinedServerFilter from bot_graphql.filter.user_joined_voice_channel_filter import UserJoinedVoiceChannelFilter from modules.level.service.level_service import LevelService +from modules.permission.abc.permission_service_abc import PermissionServiceABC class UserQuery(DataQueryABC): @@ -21,6 +22,7 @@ class UserQuery(DataQueryABC): ujs: UserJoinedServerRepositoryABC, ujvs: UserJoinedVoiceChannelRepositoryABC, user_joined_game_server: UserJoinedGameServerRepositoryABC, + permissions: PermissionServiceABC, ): DataQueryABC.__init__(self, "User") @@ -30,6 +32,7 @@ class UserQuery(DataQueryABC): self._user_joined_game_server = user_joined_game_server self._ujs = ujs self._ujvs = ujvs + self._permissions = permissions self.set_field("id", self.resolve_id) self.set_field("discordId", self.resolve_discord_id) diff --git a/kdb-web/src/app/models/auth/auth-user.dto.ts b/kdb-web/src/app/models/auth/auth-user.dto.ts index 5f38cbeb..4d7b7f6a 100644 --- a/kdb-web/src/app/models/auth/auth-user.dto.ts +++ b/kdb-web/src/app/models/auth/auth-user.dto.ts @@ -1,13 +1,28 @@ -import { AuthRoles } from "./auth-roles.enum"; +import {AuthRoles} from "./auth-roles.enum"; export interface AuthUserDTO { - id?: number; - firstName: string | null; - lastName: string | null; - email: string | null; - password: string | null; - isConfirmed?: boolean - authRole?: AuthRoles; - createdAt?: string; - modifiedAt?: string; + id?: number; + firstName: string | null; + lastName: string | null; + email: string | null; + password: string | null; + isConfirmed?: boolean; + authRole?: AuthRoles; + users?: UserDTO[]; + createdAt?: string; + modifiedAt?: string; +} + + +export interface UserDTO { + id: number; + discordId: number; + xp: number; + minecraftId: number | null; + server: number; + createdAt: string; + modifiedAt: string; + isTechnician: boolean; + isAdmin: boolean; + IsModerator: boolean; } diff --git a/kdb-web/src/app/modules/view/dashboard/components/dashboard/dashboard.component.ts b/kdb-web/src/app/modules/view/dashboard/components/dashboard/dashboard.component.ts index 685eaf9c..39f4f372 100644 --- a/kdb-web/src/app/modules/view/dashboard/components/dashboard/dashboard.component.ts +++ b/kdb-web/src/app/modules/view/dashboard/components/dashboard/dashboard.component.ts @@ -122,7 +122,7 @@ export class DashboardComponent implements OnInit { } selectServer(server: Server) { - this.sidebar.serverName$.next(server.name ?? ""); + this.sidebar.server$.next(server); this.router.navigate(["/server", server.id]); } diff --git a/kdb-web/src/app/modules/view/server/profile/profile.component.html b/kdb-web/src/app/modules/view/server/profile/profile.component.html new file mode 100644 index 00000000..9df0576d --- /dev/null +++ b/kdb-web/src/app/modules/view/server/profile/profile.component.html @@ -0,0 +1 @@ +

profile works!

diff --git a/kdb-web/src/app/modules/view/server/profile/profile.component.scss b/kdb-web/src/app/modules/view/server/profile/profile.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/kdb-web/src/app/modules/view/server/profile/profile.component.spec.ts b/kdb-web/src/app/modules/view/server/profile/profile.component.spec.ts new file mode 100644 index 00000000..246039d7 --- /dev/null +++ b/kdb-web/src/app/modules/view/server/profile/profile.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ProfileComponent } from './profile.component'; + +describe('ProfileComponent', () => { + let component: ProfileComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ ProfileComponent ] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ProfileComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/kdb-web/src/app/modules/view/server/profile/profile.component.ts b/kdb-web/src/app/modules/view/server/profile/profile.component.ts new file mode 100644 index 00000000..16fa22f7 --- /dev/null +++ b/kdb-web/src/app/modules/view/server/profile/profile.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'app-profile', + templateUrl: './profile.component.html', + styleUrls: ['./profile.component.scss'] +}) +export class ProfileComponent { + +} diff --git a/kdb-web/src/app/modules/view/server/server-dashboard/server-dashboard.component.ts b/kdb-web/src/app/modules/view/server/server-dashboard/server-dashboard.component.ts index 28f87e1a..fd923e09 100644 --- a/kdb-web/src/app/modules/view/server/server-dashboard/server-dashboard.component.ts +++ b/kdb-web/src/app/modules/view/server/server-dashboard/server-dashboard.component.ts @@ -42,7 +42,7 @@ export class ServerDashboardComponent implements OnInit { } ).subscribe(server => { this.server = server; - this.sidebar.serverName$.next(server.name ?? ""); + this.sidebar.server$.next(server); this.spinner.hideSpinner(); }); } diff --git a/kdb-web/src/app/modules/view/server/server-routing.module.ts b/kdb-web/src/app/modules/view/server/server-routing.module.ts index d7e96c59..4d89b0f9 100644 --- a/kdb-web/src/app/modules/view/server/server-routing.module.ts +++ b/kdb-web/src/app/modules/view/server/server-routing.module.ts @@ -1,9 +1,11 @@ import { NgModule } from "@angular/core"; import { RouterModule, Routes } from "@angular/router"; import { ServerDashboardComponent } from "./server-dashboard/server-dashboard.component"; +import { ProfileComponent } from "./profile/profile.component"; const routes: Routes = [ { path: '', component: ServerDashboardComponent }, + { path: 'profile', component: ProfileComponent }, ]; @NgModule({ diff --git a/kdb-web/src/app/modules/view/server/server.module.ts b/kdb-web/src/app/modules/view/server/server.module.ts index 824e2412..03adf64a 100644 --- a/kdb-web/src/app/modules/view/server/server.module.ts +++ b/kdb-web/src/app/modules/view/server/server.module.ts @@ -3,12 +3,14 @@ import { CommonModule } from '@angular/common'; import { ServerDashboardComponent } from './server-dashboard/server-dashboard.component'; import { ServerRoutingModule } from './server-routing.module'; import { SharedModule } from '../../shared/shared.module'; +import { ProfileComponent } from './profile/profile.component'; @NgModule({ declarations: [ - ServerDashboardComponent + ServerDashboardComponent, + ProfileComponent ], imports: [ CommonModule, diff --git a/kdb-web/src/app/services/auth/auth.service.ts b/kdb-web/src/app/services/auth/auth.service.ts index 3a0c86cf..73686887 100644 --- a/kdb-web/src/app/services/auth/auth.service.ts +++ b/kdb-web/src/app/services/auth/auth.service.ts @@ -241,6 +241,20 @@ export class AuthService { return null } + async getLoggedInUser(): Promise { + if (!await this.isUserLoggedInAsync()) { + return null; + } + const token = this.getDecodedToken(); + if (!token) return null; + + try { + return await firstValueFrom(this.findUserByEMail(token["email"])); + } catch (error: unknown) { + return null; + } + } + async isUserLoggedInAsync(): Promise { const token = this.getToken(); diff --git a/kdb-web/src/app/services/sidebar/sidebar.service.ts b/kdb-web/src/app/services/sidebar/sidebar.service.ts index ef3f6a95..a8177851 100644 --- a/kdb-web/src/app/services/sidebar/sidebar.service.ts +++ b/kdb-web/src/app/services/sidebar/sidebar.service.ts @@ -4,8 +4,10 @@ import { BehaviorSubject } from "rxjs"; import { AuthRoles } from "../../models/auth/auth-roles.enum"; import { AuthService } from "../auth/auth.service"; import { TranslateService } from "@ngx-translate/core"; -import { ActivatedRoute, NavigationEnd, Router } from "@angular/router"; +import { NavigationEnd, Router } from "@angular/router"; import { ThemeService } from "../theme/theme.service"; +import { Server } from "../../models/data/server.model"; +import { UserDTO } from "../../models/auth/auth-user.dto"; @Injectable({ providedIn: "root" @@ -13,14 +15,14 @@ import { ThemeService } from "../theme/theme.service"; export class SidebarService { isSidebarOpen: boolean = true; - menuItems$: BehaviorSubject = new BehaviorSubject(new Array()); - serverName$: BehaviorSubject = new BehaviorSubject(""); + menuItems$ = new BehaviorSubject(new Array()); + server$ = new BehaviorSubject(null); constructor( private themeService: ThemeService, private authService: AuthService, private translateService: TranslateService, - private router: Router, + private router: Router ) { this.themeService.isSidebarOpen$.subscribe(value => { this.isSidebarOpen = value; @@ -28,7 +30,7 @@ export class SidebarService { }); - this.serverName$.subscribe(value => { + this.server$.subscribe(value => { this.setMenu(); }); @@ -40,18 +42,27 @@ export class SidebarService { } setMenu() { - this.authService.hasUserPermission(AuthRoles.Admin).then(hasPermission => { + this.authService.hasUserPermission(AuthRoles.Admin).then(async hasPermission => { let menuItems: MenuItem[] = [ { label: this.isSidebarOpen ? this.translateService.instant("sidebar.dashboard") : "", icon: "pi pi-th-large", routerLink: "dashboard" } ]; const serverMenu = { - label: this.isSidebarOpen ? this.serverName$.value : "", icon: "pi pi-server", items: [ - { label: this.isSidebarOpen ? this.translateService.instant("sidebar.settings") : "", icon: "pi pi-cog", routerLink: "server/settings" }, - { label: this.isSidebarOpen ? this.translateService.instant("sidebar.members") : "", icon: "pi pi-users", routerLink: "server/members" } + label: this.isSidebarOpen ? this.server$.value?.name : "", icon: "pi pi-server", items: [ + { label: this.isSidebarOpen ? this.translateService.instant("sidebar.profile") : "", icon: "pi pi-user", routerLink: `server/${this.server$.value?.id}/profile` }, + // { label: this.isSidebarOpen ? this.translateService.instant("sidebar.members") : "", icon: "pi pi-users", routerLink: "server/members" } ] }; - if (this.serverName$.value != "") { + if (this.server$.value) { + let authUser = await this.authService.getLoggedInUser(); + let user: UserDTO | null = authUser?.users?.find(u => u.server == this.server$.value?.id) ?? null; + + if (user?.isAdmin) { + serverMenu.items.push( + { label: this.isSidebarOpen ? this.translateService.instant("sidebar.members") : "", icon: "pi pi-users", routerLink: `server/${this.server$.value?.id}/members` } + ); + } + menuItems.push(serverMenu); } else if (menuItems.find(x => x.icon == "pi pi-server")) { menuItems.splice(menuItems.indexOf(serverMenu), 1); diff --git a/kdb-web/src/assets/i18n/de.json b/kdb-web/src/assets/i18n/de.json index 9317f607..4e4e06b9 100644 --- a/kdb-web/src/assets/i18n/de.json +++ b/kdb-web/src/assets/i18n/de.json @@ -9,8 +9,8 @@ "dashboard": "Dashboard", "server": "Server", "server_empty": "Kein Server ausgewählt", - "settings": "Einstellungen", "members": "Mitglieder", + "settings": "Einstellungen", "administration": "Administration", "config": "Konfiguration", "auth_user_list": "Benutzer" @@ -146,7 +146,7 @@ "servers": "Server", "server": { "header": "Server", - "member_count": "Mitglid(er)" + "member_count": "Mitglied(er)" }, "filter": { "name": "Name"