diff --git a/kdb-bot/src/bot_graphql/graphql/server.gql b/kdb-bot/src/bot_graphql/graphql/server.gql index 6b29f0f9..2dda4cef 100644 --- a/kdb-bot/src/bot_graphql/graphql/server.gql +++ b/kdb-bot/src/bot_graphql/graphql/server.gql @@ -35,7 +35,7 @@ type Server implements TableWithHistoryQuery { shortRoleNames(filter: ShortRoleNameFilter, page: Page, sort: Sort): [ShortRoleName] config: ServerConfig - hasFeatureFlag(flag: String): Boolean + hasFeatureFlag(flag: String): FeatureFlag createdAt: String modifiedAt: String diff --git a/kdb-bot/src/bot_graphql/queries/server_query.py b/kdb-bot/src/bot_graphql/queries/server_query.py index 3690c37f..6ba77149 100644 --- a/kdb-bot/src/bot_graphql/queries/server_query.py +++ b/kdb-bot/src/bot_graphql/queries/server_query.py @@ -103,4 +103,7 @@ class ServerQuery(DataQueryWithHistoryABC): settings: ServerConfig = self._config.get_configuration(f"ServerConfig_{server.discord_id}") if "flag" not in kwargs: return False - return FeatureFlagsSettings.get_flag_from_dict(settings.feature_flags, FeatureFlagsEnum(kwargs["flag"])) + return { + "key": kwargs["flag"], + "value": FeatureFlagsSettings.get_flag_from_dict(settings.feature_flags, FeatureFlagsEnum(kwargs["flag"])), + } diff --git a/kdb-web/src/app/models/data/server.model.ts b/kdb-web/src/app/models/data/server.model.ts index fb2d2864..483a6e46 100644 --- a/kdb-web/src/app/models/data/server.model.ts +++ b/kdb-web/src/app/models/data/server.model.ts @@ -4,6 +4,7 @@ import {Level} from "./level.model"; import {Client} from "./client.model"; import { AutoRole } from "./auto_role.model"; import { ServerConfig } from "../config/server-config.model"; +import { FeatureFlag } from "../config/feature-flags.model"; export interface GameServer { id?: number; @@ -24,7 +25,8 @@ export interface Server extends Data { userCount?: number; users?: User[]; config?: ServerConfig; - hasFeatureFlag?: boolean; + hasFeatureFlag?: FeatureFlag; + activeFeatureFlags?: FeatureFlag[]; } export interface ServerFilter { diff --git a/kdb-web/src/app/models/graphql/queries.model.ts b/kdb-web/src/app/models/graphql/queries.model.ts index 1f069b6b..51d51e2e 100644 --- a/kdb-web/src/app/models/graphql/queries.model.ts +++ b/kdb-web/src/app/models/graphql/queries.model.ts @@ -85,7 +85,10 @@ export class Queries { static hasServerFeatureFlag = ` query HasServerFeatureFlag($filter: ServerFilter, $flag: String) { servers(filter: $filter) { - hasFeatureFlag(flag: $flag) + hasFeatureFlag(flag: $flag) { + key + value + } } } `; diff --git a/kdb-web/src/app/models/graphql/query.model.ts b/kdb-web/src/app/models/graphql/query.model.ts index 332a63f9..7fc95684 100644 --- a/kdb-web/src/app/models/graphql/query.model.ts +++ b/kdb-web/src/app/models/graphql/query.model.ts @@ -7,6 +7,7 @@ import { Achievement, AchievementAttribute } from "../data/achievement.model"; import { TechnicianConfig } from "../config/technician-config.model"; import { ServerConfig } from "../config/server-config.model"; import { ShortRoleName } from "../data/short_role_name.model"; +import { FeatureFlag } from "../config/feature-flags.model"; export interface Query { serverCount: number; @@ -66,7 +67,7 @@ export interface PossibleFeatureFlagsQuery { } export interface HasServerFeatureFlagQuery { - hasFeatureFlag: boolean; + hasFeatureFlag: FeatureFlag; } export interface ShortRoleNameListQuery { diff --git a/kdb-web/src/app/services/server.service.ts b/kdb-web/src/app/services/server.service.ts index b01a608a..594ec34c 100644 --- a/kdb-web/src/app/services/server.service.ts +++ b/kdb-web/src/app/services/server.service.ts @@ -9,7 +9,7 @@ import { NavigationEnd, Router } from "@angular/router"; export class ServerService { - server$ = new BehaviorSubject(null); + server$ = new BehaviorSubject(undefined); constructor( private router: Router @@ -19,16 +19,17 @@ export class ServerService { return; } if (!event.url.startsWith("/server/") && this.server$.value) { - this.setServer(null); + this.setServer(undefined); } }); } - setServer(server: Server | null) { + setServer(server: Server | undefined) { if (!server) { - this.server$.next(server); + this.server$.next(undefined); return; } + if (server.id != this.server$.value?.id) { this.server$.next(server); } diff --git a/kdb-web/src/app/services/sidebar/sidebar.service.ts b/kdb-web/src/app/services/sidebar/sidebar.service.ts index a2e05f05..4b4ace21 100644 --- a/kdb-web/src/app/services/sidebar/sidebar.service.ts +++ b/kdb-web/src/app/services/sidebar/sidebar.service.ts @@ -1,6 +1,6 @@ import { Injectable } from "@angular/core"; import { MenuItem } from "primeng/api"; -import { BehaviorSubject } from "rxjs"; +import { BehaviorSubject, forkJoin, Observable } from "rxjs"; import { AuthRoles } from "../../models/auth/auth-roles.enum"; import { AuthService } from "../auth/auth.service"; import { TranslateService } from "@ngx-translate/core"; @@ -9,170 +9,222 @@ import { ThemeService } from "../theme/theme.service"; import { Server } from "../../models/data/server.model"; import { UserDTO } from "../../models/auth/auth-user.dto"; import { ServerService } from "../server.service"; +import { HasServerFeatureFlagQuery, PossibleFeatureFlagsQuery, Query } from "../../models/graphql/query.model"; +import { Queries } from "../../models/graphql/queries.model"; +import { DataService } from "../data/data.service"; +import { FeatureFlag } from "../../models/config/feature-flags.model"; @Injectable({ - providedIn: "root" + providedIn: "root" }) export class SidebarService { - isSidebarOpen: boolean = true; - menuItems$ = new BehaviorSubject(new Array()); - server!: Server | null; + isSidebarOpen: boolean = true; + menuItems$ = new BehaviorSubject(new Array()); + server!: Server | undefined; - dashboard: MenuItem = {}; - serverDashboard: MenuItem = {}; - serverProfile: MenuItem = {}; - serverMembers: MenuItem = {}; - serverAutoRoles: MenuItem = {}; - serverLevels: MenuItem = {}; - serverAchievements: MenuItem = {}; - serverShortRoleNames: MenuItem = {}; - serverConfig: MenuItem = {}; - serverMenu: MenuItem = {}; - adminConfig: MenuItem = {}; - adminUsers: MenuItem = {}; - adminMenu: MenuItem = {}; + dashboard: MenuItem = {}; + serverDashboard: MenuItem = {}; + serverProfile: MenuItem = {}; + serverMembers: MenuItem = {}; + serverAutoRoles: MenuItem = {}; + serverLevels: MenuItem = {}; + serverAchievements: MenuItem = {}; + serverShortRoleNames: MenuItem = {}; + serverConfig: MenuItem = {}; + serverMenu: MenuItem = {}; + adminConfig: MenuItem = {}; + adminUsers: MenuItem = {}; + adminMenu: MenuItem = {}; - constructor( - private themeService: ThemeService, - private authService: AuthService, - private translateService: TranslateService, - private router: Router, - private serverService: ServerService - ) { - this.themeService.isSidebarOpen$.subscribe(value => { - this.isSidebarOpen = value; - this.setMenu(true); - }); + featureFlags: FeatureFlag[] = []; - this.translateService.onLangChange.subscribe(_ => { - this.setMenu(true); - }); + constructor( + private themeService: ThemeService, + private authService: AuthService, + private translateService: TranslateService, + private router: Router, + private serverService: ServerService, + private data: DataService + ) { + this.themeService.isSidebarOpen$.subscribe(value => { + this.isSidebarOpen = value; + this.setMenu(true); + }); - this.serverService.server$.subscribe(server => { - this.server = server; - if (server) { - this.setMenu(true); - } else { - this.setMenu(false); - } - }); - } + this.translateService.onLangChange.subscribe(_ => { + this.setMenu(true); + }); - async buildMenu(user: UserDTO | null, hasPermission: boolean, isTechnician: boolean = false) { - this.dashboard = { - label: this.isSidebarOpen ? this.translateService.instant("sidebar.dashboard") : "", - icon: "pi pi-th-large", - routerLink: "dashboard" - }; - this.serverDashboard = { - label: this.isSidebarOpen ? this.translateService.instant("sidebar.server.dashboard") : "", - icon: "pi pi-th-large", - routerLink: `server/${this.server?.id}` - }; - this.serverProfile = { - label: this.isSidebarOpen ? this.translateService.instant("sidebar.server.profile") : "", - icon: "pi pi-id-card", - routerLink: `server/${this.server?.id}/members/${user?.id}` - }; - this.serverMembers = { - label: this.isSidebarOpen ? this.translateService.instant("sidebar.server.members") : "", - icon: "pi pi-users", - visible: true, - routerLink: `server/${this.server?.id}/members` - }; + this.serverService.server$.subscribe(server => { + this.server = server; + if (server) { + this.setMenu(true); + } else { + this.setMenu(false); + } + }); + } - this.serverAutoRoles = { - label: this.isSidebarOpen ? this.translateService.instant("sidebar.server.auto_roles") : "", - icon: "pi pi-sitemap", - visible: true, - routerLink: `server/${this.server?.id}/auto-roles` - }; + async buildMenu(user: UserDTO | null, hasPermission: boolean, isTechnician: boolean = false) { + this.dashboard = { + label: this.isSidebarOpen ? this.translateService.instant("sidebar.dashboard") : "", + icon: "pi pi-th-large", + routerLink: "dashboard" + }; + this.serverDashboard = { + label: this.isSidebarOpen ? this.translateService.instant("sidebar.server.dashboard") : "", + icon: "pi pi-th-large", + routerLink: `server/${this.server?.id}` + }; + this.serverProfile = { + label: this.isSidebarOpen ? this.translateService.instant("sidebar.server.profile") : "", + icon: "pi pi-id-card", + routerLink: `server/${this.server?.id}/members/${user?.id}` + }; + this.serverMembers = { + label: this.isSidebarOpen ? this.translateService.instant("sidebar.server.members") : "", + icon: "pi pi-users", + visible: true, + routerLink: `server/${this.server?.id}/members` + }; - this.serverLevels = { - label: this.isSidebarOpen ? this.translateService.instant("sidebar.server.levels") : "", - icon: "pi pi-book", - visible: true, - routerLink: `server/${this.server?.id}/levels` - }; + this.serverAutoRoles = { + label: this.isSidebarOpen ? this.translateService.instant("sidebar.server.auto_roles") : "", + icon: "pi pi-sitemap", + visible: true, + routerLink: `server/${this.server?.id}/auto-roles` + }; - this.serverAchievements = { - label: this.isSidebarOpen ? this.translateService.instant("sidebar.server.achievements") : "", - icon: "pi pi-angle-double-up", - visible: true, - routerLink: `server/${this.server?.id}/achievements` - }; + this.serverLevels = { + label: this.isSidebarOpen ? this.translateService.instant("sidebar.server.levels") : "", + icon: "pi pi-book", + visible: true, + routerLink: `server/${this.server?.id}/levels` + }; - this.serverShortRoleNames = { - label: this.isSidebarOpen ? this.translateService.instant("sidebar.server.short_role_names") : "", - icon: "pi pi-list", - visible: true, - routerLink: `server/${this.server?.id}/short-role-names` - }; + this.serverAchievements = { + label: this.isSidebarOpen ? this.translateService.instant("sidebar.server.achievements") : "", + icon: "pi pi-angle-double-up", + visible: true, + routerLink: `server/${this.server?.id}/achievements` + }; - this.serverConfig = { - label: this.isSidebarOpen ? this.translateService.instant("sidebar.server.configuration") : "", - icon: "pi pi-cog", - visible: true, - routerLink: `server/${this.server?.id}/config` - }; + this.serverShortRoleNames = { + label: this.isSidebarOpen ? this.translateService.instant("sidebar.server.short_role_names") : "", + icon: "pi pi-list", + visible: true, + routerLink: `server/${this.server?.id}/short-role-names` + }; - this.serverMenu = { - label: this.isSidebarOpen ? this.server?.name : "", - icon: "pi pi-server", - visible: false, - expanded: true, - items: [this.serverDashboard, this.serverProfile, this.serverMembers, this.serverAutoRoles, this.serverLevels, this.serverAchievements, this.serverShortRoleNames, this.serverConfig] - }; - this.adminConfig = { - label: this.isSidebarOpen ? this.translateService.instant("sidebar.config") : "", - visible: hasPermission || isTechnician, - icon: "pi pi-cog", - routerLink: "/admin/settings" - }; - this.adminUsers = { - label: this.isSidebarOpen ? this.translateService.instant("sidebar.auth_user_list") : "", - visible: hasPermission, - icon: "pi pi-user-edit", - routerLink: "/admin/users" - }; - this.adminMenu = { - label: this.isSidebarOpen ? this.translateService.instant("sidebar.administration") : "", - icon: "pi pi-cog", - visible: hasPermission || isTechnician, - expanded: true, - items: [this.adminConfig, this.adminUsers] - }; - } + this.serverConfig = { + label: this.isSidebarOpen ? this.translateService.instant("sidebar.server.configuration") : "", + icon: "pi pi-cog", + visible: true, + routerLink: `server/${this.server?.id}/config` + }; - setMenu(build: boolean = false) { - this.authService.hasUserPermission(AuthRoles.Admin).then(async hasPermission => { - let authUser = await this.authService.getLoggedInUser(); - let user: UserDTO | null = authUser?.users?.find(u => u.server == this.server?.id) ?? null; - let isTechnician = authUser?.users?.map(u => u.isTechnician).filter(u => u) ?? []; + this.serverMenu = { + label: this.isSidebarOpen ? this.server?.name : "", + icon: "pi pi-server", + visible: false, + expanded: true, + items: [this.serverDashboard, this.serverProfile, this.serverMembers, this.serverAutoRoles, this.serverLevels, this.serverAchievements, this.serverShortRoleNames, this.serverConfig] + }; + this.adminConfig = { + label: this.isSidebarOpen ? this.translateService.instant("sidebar.config") : "", + visible: hasPermission || isTechnician, + icon: "pi pi-cog", + routerLink: "/admin/settings" + }; + this.adminUsers = { + label: this.isSidebarOpen ? this.translateService.instant("sidebar.auth_user_list") : "", + visible: hasPermission, + icon: "pi pi-user-edit", + routerLink: "/admin/users" + }; + this.adminMenu = { + label: this.isSidebarOpen ? this.translateService.instant("sidebar.administration") : "", + icon: "pi pi-cog", + visible: hasPermission || isTechnician, + expanded: true, + items: [this.adminConfig, this.adminUsers] + }; + } - if (build || this.menuItems$.value.length == 0) { - await this.buildMenu(user, hasPermission, isTechnician.length > 0); - } + setMenu(build: boolean = false) { + const server = this.server; - if (this.server) { - this.serverMenu.visible = true; - this.serverMembers.visible = !!user?.isModerator; - this.serverAutoRoles.visible = !!user?.isModerator; - this.serverLevels.visible = !!user?.isModerator; - this.serverAchievements.visible = !!user?.isModerator; - this.serverShortRoleNames.visible = !!user?.isAdmin; - this.serverConfig.visible = !!user?.isAdmin || isTechnician.length > 0; - } else { - this.serverMenu.visible = false; - } + if (server) { + this.featureFlags = []; + this.data.query("{possibleFeatureFlags}" + ).subscribe(data => { + let observables: Observable[] = []; + data.possibleFeatureFlags.forEach(flag => { + observables.push( + this.data.query(Queries.hasServerFeatureFlag, { + filter: { id: server.id }, + flag: flag + }, + function(data: Query) { + return data.servers[0]; + } + ) + ); + }); - let menuItems: MenuItem[] = [ - this.dashboard, - this.serverMenu, - this.adminMenu - ]; - this.menuItems$.next(menuItems); - }); - } + forkJoin(observables).subscribe(data => { + data.forEach(flag => { + if (!flag.hasFeatureFlag.value) { + return; + } + this.featureFlags.push(flag.hasFeatureFlag); + }); + this._setMenu(build); + }); + }); + } else { + this._setMenu(build); + } + } + + private _setMenu(build: boolean = false) { + this.authService.hasUserPermission(AuthRoles.Admin).then(async hasPermission => { + let authUser = await this.authService.getLoggedInUser(); + let user: UserDTO | null = authUser?.users?.find(u => u.server == this.server?.id) ?? null; + let isTechnician = authUser?.users?.map(u => u.isTechnician).filter(u => u) ?? []; + + if (build || this.menuItems$.value.length == 0) { + await this.buildMenu(user, hasPermission, isTechnician.length > 0); + } + + if (this.server) { + this.serverMenu.visible = true; + this.serverMembers.visible = !!user?.isModerator; + this.serverAutoRoles.visible = this.hasFeature("AutoRoleModule") ? !!user?.isModerator : false; + this.serverLevels.visible = this.hasFeature("LevelModule") ? !!user?.isModerator : false; + this.serverAchievements.visible = this.hasFeature("AchievementsModule") ? !!user?.isModerator : false; + this.serverShortRoleNames.visible = this.hasFeature("ShortRoleNameModule") ? !!user?.isAdmin : false; + + this.serverConfig.visible = !!user?.isAdmin || isTechnician.length > 0; + } else { + this.serverMenu.visible = false; + } + + let menuItems: MenuItem[] = [ + this.dashboard, + this.serverMenu, + this.adminMenu + ]; + this.menuItems$.next(menuItems); + }); + } + + private hasFeature(key: string): boolean { + const flag = this.featureFlags.filter(flag => flag.key == key); + if (flag.length == 0) { + return false; + } + return flag[0].value; + } }