Added routing by permissions #130

This commit is contained in:
Sven Heidemann 2023-02-16 21:24:29 +01:00
parent e2ef4f3bde
commit f847841582
17 changed files with 260 additions and 26 deletions

View File

@ -1,7 +1,10 @@
from datetime import datetime from datetime import datetime
from typing import Optional from typing import Optional
from cpl_query.extension import List
from bot_api.abc.dto_abc import DtoABC 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 from bot_data.model.auth_role_enum import AuthRoleEnum
@ -15,6 +18,7 @@ class AuthUserDTO(DtoABC):
password: str = None, password: str = None,
confirmation_id: Optional[str] = None, confirmation_id: Optional[str] = None,
auth_role: AuthRoleEnum = None, auth_role: AuthRoleEnum = None,
users: List[UserDTO] = None,
created_at: datetime = None, created_at: datetime = None,
modified_at: datetime = None, modified_at: datetime = None,
): ):
@ -30,6 +34,11 @@ class AuthUserDTO(DtoABC):
self._created_at = created_at self._created_at = created_at
self._modified_at = modified_at self._modified_at = modified_at
if users is None:
self._users = List(UserDTO)
else:
self._users = users
@property @property
def id(self) -> int: def id(self) -> int:
return self._id return self._id
@ -82,6 +91,10 @@ class AuthUserDTO(DtoABC):
def auth_role(self, value: AuthRoleEnum): def auth_role(self, value: AuthRoleEnum):
self._auth_role = value self._auth_role = value
@property
def users(self) -> List[UserDTO]:
return self._users
@property @property
def created_at(self) -> datetime: def created_at(self) -> datetime:
return self._created_at return self._created_at
@ -98,6 +111,12 @@ class AuthUserDTO(DtoABC):
self._password = values["password"] self._password = values["password"]
self._is_confirmed = values["isConfirmed"] self._is_confirmed = values["isConfirmed"]
self._auth_role = AuthRoleEnum(values["authRole"]) 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._created_at = values["createdAt"]
self._modified_at = values["modifiedAt"] self._modified_at = values["modifiedAt"]
@ -111,6 +130,7 @@ class AuthUserDTO(DtoABC):
"password": self._password, "password": self._password,
"isConfirmed": self._is_confirmed, "isConfirmed": self._is_confirmed,
"authRole": self._auth_role.value, "authRole": self._auth_role.value,
"users": self._users.select(lambda u: u.to_dict()).to_list(),
"createdAt": self._created_at, "createdAt": self._created_at,
"modifiedAt": self._modified_at, "modifiedAt": self._modified_at,
} }

View File

@ -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,
}

View File

@ -1,9 +1,16 @@
from datetime import datetime 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.abc.transformer_abc import TransformerABC
from bot_api.model.auth_user_dto import AuthUserDTO 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_role_enum import AuthRoleEnum
from bot_data.model.auth_user import AuthUser 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): class AuthUserTransformer(TransformerABC):
@ -25,7 +32,28 @@ class AuthUserTransformer(TransformerABC):
) )
@staticmethod @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( return AuthUserDTO(
db.id, db.id,
db.first_name, db.first_name,
@ -34,6 +62,21 @@ class AuthUserTransformer(TransformerABC):
"" if password is None else password, "" if password is None else password,
db.confirmation_id, db.confirmation_id,
db.auth_role, 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.created_at,
db.modified_at, db.modified_at,
) )

View File

@ -42,6 +42,8 @@ class AuthUser(TableABC):
if users is None: if users is None:
self._users = List(User) self._users = List(User)
else:
self._users = users
self._auth_role_id = auth_role self._auth_role_id = auth_role

View File

@ -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_server_filter import UserJoinedServerFilter
from bot_graphql.filter.user_joined_voice_channel_filter import UserJoinedVoiceChannelFilter from bot_graphql.filter.user_joined_voice_channel_filter import UserJoinedVoiceChannelFilter
from modules.level.service.level_service import LevelService from modules.level.service.level_service import LevelService
from modules.permission.abc.permission_service_abc import PermissionServiceABC
class UserQuery(DataQueryABC): class UserQuery(DataQueryABC):
@ -21,6 +22,7 @@ class UserQuery(DataQueryABC):
ujs: UserJoinedServerRepositoryABC, ujs: UserJoinedServerRepositoryABC,
ujvs: UserJoinedVoiceChannelRepositoryABC, ujvs: UserJoinedVoiceChannelRepositoryABC,
user_joined_game_server: UserJoinedGameServerRepositoryABC, user_joined_game_server: UserJoinedGameServerRepositoryABC,
permissions: PermissionServiceABC,
): ):
DataQueryABC.__init__(self, "User") DataQueryABC.__init__(self, "User")
@ -30,6 +32,7 @@ class UserQuery(DataQueryABC):
self._user_joined_game_server = user_joined_game_server self._user_joined_game_server = user_joined_game_server
self._ujs = ujs self._ujs = ujs
self._ujvs = ujvs self._ujvs = ujvs
self._permissions = permissions
self.set_field("id", self.resolve_id) self.set_field("id", self.resolve_id)
self.set_field("discordId", self.resolve_discord_id) self.set_field("discordId", self.resolve_discord_id)

View File

@ -1,13 +1,28 @@
import { AuthRoles } from "./auth-roles.enum"; import {AuthRoles} from "./auth-roles.enum";
export interface AuthUserDTO { export interface AuthUserDTO {
id?: number; id?: number;
firstName: string | null; firstName: string | null;
lastName: string | null; lastName: string | null;
email: string | null; email: string | null;
password: string | null; password: string | null;
isConfirmed?: boolean isConfirmed?: boolean;
authRole?: AuthRoles; authRole?: AuthRoles;
createdAt?: string; users?: UserDTO[];
modifiedAt?: string; 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;
} }

View File

@ -122,7 +122,7 @@ export class DashboardComponent implements OnInit {
} }
selectServer(server: Server) { selectServer(server: Server) {
this.sidebar.serverName$.next(server.name ?? ""); this.sidebar.server$.next(server);
this.router.navigate(["/server", server.id]); this.router.navigate(["/server", server.id]);
} }

View File

@ -0,0 +1 @@
<p>profile works!</p>

View File

@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ProfileComponent } from './profile.component';
describe('ProfileComponent', () => {
let component: ProfileComponent;
let fixture: ComponentFixture<ProfileComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ ProfileComponent ]
})
.compileComponents();
fixture = TestBed.createComponent(ProfileComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,10 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-profile',
templateUrl: './profile.component.html',
styleUrls: ['./profile.component.scss']
})
export class ProfileComponent {
}

View File

@ -42,7 +42,7 @@ export class ServerDashboardComponent implements OnInit {
} }
).subscribe(server => { ).subscribe(server => {
this.server = server; this.server = server;
this.sidebar.serverName$.next(server.name ?? ""); this.sidebar.server$.next(server);
this.spinner.hideSpinner(); this.spinner.hideSpinner();
}); });
} }

View File

@ -1,9 +1,11 @@
import { NgModule } from "@angular/core"; import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router"; import { RouterModule, Routes } from "@angular/router";
import { ServerDashboardComponent } from "./server-dashboard/server-dashboard.component"; import { ServerDashboardComponent } from "./server-dashboard/server-dashboard.component";
import { ProfileComponent } from "./profile/profile.component";
const routes: Routes = [ const routes: Routes = [
{ path: '', component: ServerDashboardComponent }, { path: '', component: ServerDashboardComponent },
{ path: 'profile', component: ProfileComponent },
]; ];
@NgModule({ @NgModule({

View File

@ -3,12 +3,14 @@ import { CommonModule } from '@angular/common';
import { ServerDashboardComponent } from './server-dashboard/server-dashboard.component'; import { ServerDashboardComponent } from './server-dashboard/server-dashboard.component';
import { ServerRoutingModule } from './server-routing.module'; import { ServerRoutingModule } from './server-routing.module';
import { SharedModule } from '../../shared/shared.module'; import { SharedModule } from '../../shared/shared.module';
import { ProfileComponent } from './profile/profile.component';
@NgModule({ @NgModule({
declarations: [ declarations: [
ServerDashboardComponent ServerDashboardComponent,
ProfileComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,

View File

@ -241,6 +241,20 @@ export class AuthService {
return null return null
} }
async getLoggedInUser(): Promise<AuthUserDTO | null> {
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<boolean> { async isUserLoggedInAsync(): Promise<boolean> {
const token = this.getToken(); const token = this.getToken();

View File

@ -4,8 +4,10 @@ import { BehaviorSubject } from "rxjs";
import { AuthRoles } from "../../models/auth/auth-roles.enum"; import { AuthRoles } from "../../models/auth/auth-roles.enum";
import { AuthService } from "../auth/auth.service"; import { AuthService } from "../auth/auth.service";
import { TranslateService } from "@ngx-translate/core"; 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 { ThemeService } from "../theme/theme.service";
import { Server } from "../../models/data/server.model";
import { UserDTO } from "../../models/auth/auth-user.dto";
@Injectable({ @Injectable({
providedIn: "root" providedIn: "root"
@ -13,14 +15,14 @@ import { ThemeService } from "../theme/theme.service";
export class SidebarService { export class SidebarService {
isSidebarOpen: boolean = true; isSidebarOpen: boolean = true;
menuItems$: BehaviorSubject<MenuItem[]> = new BehaviorSubject(new Array<MenuItem>()); menuItems$ = new BehaviorSubject<MenuItem[]>(new Array<MenuItem>());
serverName$: BehaviorSubject<string> = new BehaviorSubject(""); server$ = new BehaviorSubject<Server | null>(null);
constructor( constructor(
private themeService: ThemeService, private themeService: ThemeService,
private authService: AuthService, private authService: AuthService,
private translateService: TranslateService, private translateService: TranslateService,
private router: Router, private router: Router
) { ) {
this.themeService.isSidebarOpen$.subscribe(value => { this.themeService.isSidebarOpen$.subscribe(value => {
this.isSidebarOpen = value; this.isSidebarOpen = value;
@ -28,7 +30,7 @@ export class SidebarService {
}); });
this.serverName$.subscribe(value => { this.server$.subscribe(value => {
this.setMenu(); this.setMenu();
}); });
@ -40,18 +42,27 @@ export class SidebarService {
} }
setMenu() { setMenu() {
this.authService.hasUserPermission(AuthRoles.Admin).then(hasPermission => { this.authService.hasUserPermission(AuthRoles.Admin).then(async hasPermission => {
let menuItems: MenuItem[] = [ let menuItems: MenuItem[] = [
{ label: this.isSidebarOpen ? this.translateService.instant("sidebar.dashboard") : "", icon: "pi pi-th-large", routerLink: "dashboard" } { label: this.isSidebarOpen ? this.translateService.instant("sidebar.dashboard") : "", icon: "pi pi-th-large", routerLink: "dashboard" }
]; ];
const serverMenu = { const serverMenu = {
label: this.isSidebarOpen ? this.serverName$.value : "", icon: "pi pi-server", items: [ label: this.isSidebarOpen ? this.server$.value?.name : "", 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.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" } // { 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); menuItems.push(serverMenu);
} else if (menuItems.find(x => x.icon == "pi pi-server")) { } else if (menuItems.find(x => x.icon == "pi pi-server")) {
menuItems.splice(menuItems.indexOf(serverMenu), 1); menuItems.splice(menuItems.indexOf(serverMenu), 1);

View File

@ -9,8 +9,8 @@
"dashboard": "Dashboard", "dashboard": "Dashboard",
"server": "Server", "server": "Server",
"server_empty": "Kein Server ausgewählt", "server_empty": "Kein Server ausgewählt",
"settings": "Einstellungen",
"members": "Mitglieder", "members": "Mitglieder",
"settings": "Einstellungen",
"administration": "Administration", "administration": "Administration",
"config": "Konfiguration", "config": "Konfiguration",
"auth_user_list": "Benutzer" "auth_user_list": "Benutzer"
@ -146,7 +146,7 @@
"servers": "Server", "servers": "Server",
"server": { "server": {
"header": "Server", "header": "Server",
"member_count": "Mitglid(er)" "member_count": "Mitglied(er)"
}, },
"filter": { "filter": {
"name": "Name" "name": "Name"