From 61bdc8a52a2da782de8e1e5e5db752efd04768ec Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Tue, 10 Oct 2023 15:50:38 +0200 Subject: [PATCH] Improved user warnings in WI #402 --- .../user_warnings_repository_service.py | 2 +- kdb-bot/src/bot_graphql/graphql/user.gql | 1 + .../src/bot_graphql/graphql/userWarning.gql | 7 ++ .../mutations/server_config_mutation.py | 3 +- .../bot_graphql/mutations/user_mutation.py | 26 +++++++ .../src/modules/base/command/user_group.py | 4 +- .../base/service/user_warnings_service.py | 41 +++++++--- .../src/app/models/graphql/mutations.model.ts | 54 ++++++++++++- .../src/app/models/graphql/queries.model.ts | 4 + .../src/app/models/graphql/result.model.ts | 9 +++ .../server/profile/profile.component.html | 11 ++- .../view/server/profile/profile.component.ts | 76 +++++++++++++++++-- 12 files changed, 215 insertions(+), 23 deletions(-) diff --git a/kdb-bot/src/bot_data/service/user_warnings_repository_service.py b/kdb-bot/src/bot_data/service/user_warnings_repository_service.py index ff19d61f..34cded9b 100644 --- a/kdb-bot/src/bot_data/service/user_warnings_repository_service.py +++ b/kdb-bot/src/bot_data/service/user_warnings_repository_service.py @@ -32,7 +32,7 @@ class UserWarningsRepositoryService(UserWarningsRepositoryABC): def _from_result(self, sql_result: tuple) -> UserWarnings: user = self._users.get_user_by_id(self._get_value_from_result(sql_result[2])) author = None - author_id = self._get_value_from_result(sql_result[2]) + author_id = self._get_value_from_result(sql_result[3]) if author_id is not None: author = self._users.get_user_by_id(author_id) diff --git a/kdb-bot/src/bot_graphql/graphql/user.gql b/kdb-bot/src/bot_graphql/graphql/user.gql index 84800a8b..f89e8483 100644 --- a/kdb-bot/src/bot_graphql/graphql/user.gql +++ b/kdb-bot/src/bot_graphql/graphql/user.gql @@ -63,4 +63,5 @@ input UserInput { id: ID xp: Int levelId: ID + userWarnings: [UserWarningInput] } \ No newline at end of file diff --git a/kdb-bot/src/bot_graphql/graphql/userWarning.gql b/kdb-bot/src/bot_graphql/graphql/userWarning.gql index 22454da3..d36a9eeb 100644 --- a/kdb-bot/src/bot_graphql/graphql/userWarning.gql +++ b/kdb-bot/src/bot_graphql/graphql/userWarning.gql @@ -24,4 +24,11 @@ type UserWarningHistory implements HistoryTableQuery { input UserWarningFilter { id: ID user: UserFilter +} + +input UserWarningInput { + id: ID + user: ID + description: String + author: ID } \ No newline at end of file diff --git a/kdb-bot/src/bot_graphql/mutations/server_config_mutation.py b/kdb-bot/src/bot_graphql/mutations/server_config_mutation.py index 044ec3be..024ab3c0 100644 --- a/kdb-bot/src/bot_graphql/mutations/server_config_mutation.py +++ b/kdb-bot/src/bot_graphql/mutations/server_config_mutation.py @@ -139,6 +139,7 @@ class ServerConfigMutation(QueryABC): self._update_team_role_ids(server_config) self._db.save_changes() + self._bot.loop.create_task(self._config_service.reload_server_config(server_config.server)) return server_config def _update_afk_channel_ids(self, new_config: ServerConfig): @@ -178,5 +179,3 @@ class ServerConfigMutation(QueryABC): continue self._server_configs.add_server_team_role_id_config(role_id) - - self._bot.loop.create_task(self._config_service.reload_server_config(new_config.server)) diff --git a/kdb-bot/src/bot_graphql/mutations/user_mutation.py b/kdb-bot/src/bot_graphql/mutations/user_mutation.py index 9cd71438..6c6c0bc6 100644 --- a/kdb-bot/src/bot_graphql/mutations/user_mutation.py +++ b/kdb-bot/src/bot_graphql/mutations/user_mutation.py @@ -4,8 +4,11 @@ from cpl_discord.service import DiscordBotServiceABC from bot_data.abc.level_repository_abc import LevelRepositoryABC from bot_data.abc.server_repository_abc import ServerRepositoryABC from bot_data.abc.user_repository_abc import UserRepositoryABC +from bot_data.abc.user_warnings_repository_abc import UserWarningsRepositoryABC +from bot_data.model.user import User from bot_data.model.user_role_enum import UserRoleEnum from bot_graphql.abc.query_abc import QueryABC +from modules.base.service.user_warnings_service import UserWarningsService from modules.level.service.level_service import LevelService from modules.permission.service.permission_service import PermissionService @@ -20,6 +23,8 @@ class UserMutation(QueryABC): permissions: PermissionService, levels: LevelRepositoryABC, level_service: LevelService, + user_warnings: UserWarningsRepositoryABC, + user_warning_service: UserWarningsService, ): QueryABC.__init__(self, "UserMutation") @@ -30,6 +35,8 @@ class UserMutation(QueryABC): self._permissions = permissions self._levels = levels self._level_service = level_service + self._user_warnings = user_warnings + self._user_warning_service = user_warning_service self.set_field("updateUser", self.resolve_update_user) @@ -45,9 +52,28 @@ class UserMutation(QueryABC): user.xp = new_xp if new_xp is not None else input["xp"] if "xp" in input else user.xp + if "userWarnings" in input: + self._update_user_warning(user, input["userWarnings"]) + self._users.update_user(user) self._db.save_changes() self._bot.loop.create_task(self._level_service.set_level(user)) user = self._users.get_user_by_id(input["id"]) return user + + def _update_user_warning(self, user: User, new_warnings: dict): + old_warnings = self._user_warnings.get_user_warnings_by_user_id(user.id) + for warning in old_warnings: + if warning.id in [int(x["id"]) if "id" in x else None for x in new_warnings]: + continue + + self._user_warning_service.remove_warnings(warning.id) + + for warning in new_warnings: + if "id" in warning and int(warning["id"]) in old_warnings.select(lambda x: x.id): + continue + + member = self._bot.get_guild(user.server.discord_id).get_member(user.discord_id) + author = self._users.get_user_by_id(int(warning["author"])) + self._user_warning_service.add_warnings(member, warning["description"], author.discord_id) diff --git a/kdb-bot/src/modules/base/command/user_group.py b/kdb-bot/src/modules/base/command/user_group.py index 37570a70..2c6f563f 100644 --- a/kdb-bot/src/modules/base/command/user_group.py +++ b/kdb-bot/src/modules/base/command/user_group.py @@ -400,7 +400,7 @@ class UserGroup(DiscordCommandABC): async def add(self, ctx: Context, member: discord.Member, description: str): self._logger.debug(__name__, f"Received command user warning add {ctx}:{member},{description}") try: - await self._user_warnings_service.add_warnings(member, description, ctx.author.id) + await self._user_warnings_service.add_warnings_async(member, description, ctx.author.id) await self._message_service.send_ctx_msg(ctx, self._t.transform("modules.base.warnings.add.success")) except Exception as e: self._logger.error(__name__, f"Adding user warning failed", e) @@ -414,7 +414,7 @@ class UserGroup(DiscordCommandABC): async def remove(self, ctx: Context, warning_id: int): self._logger.debug(__name__, f"Received command user warning remove {ctx}:{warning_id}") try: - await self._user_warnings_service.remove_warnings(warning_id) + await self._user_warnings_service.remove_warnings_async(warning_id) await self._message_service.send_ctx_msg(ctx, self._t.transform("modules.base.warnings.remove.success")) except Exception as e: self._logger.error(__name__, f"Removing user warning failed", e) diff --git a/kdb-bot/src/modules/base/service/user_warnings_service.py b/kdb-bot/src/modules/base/service/user_warnings_service.py index f3b0ab3f..d69659e6 100644 --- a/kdb-bot/src/modules/base/service/user_warnings_service.py +++ b/kdb-bot/src/modules/base/service/user_warnings_service.py @@ -108,7 +108,15 @@ class UserWarningsService: await self.notify_team(member, self._t.transform("modules.base.warnings.kick").format(member.mention)) await member.kick() - async def add_warnings(self, member: discord.Member, description: str, author_id: int = None): + async def _notify_after_add(self, member: discord.Member, warning: UserWarnings): + server = self._servers.get_server_by_discord_id(member.guild.id) + user = self._users.get_user_by_discord_id_and_server_id(member.id, server.id) + + await self.notify_user(member, self._t.transform("modules.base.warnings.warned").format(warning.description)) + await self.notify_team(member, warning.description) + await self.check_for_warnings(member, user) + + def _add_warnings(self, member: discord.Member, description: str, author_id: int = None): server = self._servers.get_server_by_discord_id(member.guild.id) user = self._users.get_user_by_discord_id_and_server_id(member.id, server.id) @@ -119,17 +127,32 @@ class UserWarningsService: warning = UserWarnings(description, user, author) self._warnings.add_user_warnings(warning) self._db.save_changes() - await self.notify_user(member, self._t.transform("modules.base.warnings.warned").format(warning.description)) - await self.notify_team(member, warning.description) - await self.check_for_warnings(member, user) + return warning - async def remove_warnings(self, id: int): + def add_warnings(self, member: discord.Member, description: str, author_id: int = None): + warning = self._add_warnings(member, description, author_id) + self._bot.loop.create_task(self._notify_after_add(member, warning)) + + async def add_warnings_async(self, member: discord.Member, description: str, author_id: int = None): + warning = self._add_warnings(member, description, author_id) + await self._notify_after_add(member, warning) + + async def _notify_after_remove(self, warning: UserWarnings): + guild = self._bot.get_guild(warning.user.server.discord_id) + member = guild.get_member(warning.user.discord_id) + await self.notify_user(member, self._t.transform("modules.base.warnings.removed").format(warning.description)) + await self.notify_team(member, warning.description, removed=True) + + def _remove_warnings(self, id: int): warning = self._warnings.get_user_warnings_by_id(id) self._warnings.delete_user_warnings(warning) self._db.save_changes() + return warning - guild = self._bot.get_guild(warning.user.server.discord_id) - member = guild.get_member(warning.user.discord_id) + def remove_warnings(self, id: int): + warning = self._remove_warnings(id) + self._bot.loop.create_task(self._notify_after_remove(warning)) - await self.notify_user(member, self._t.transform("modules.base.warnings.removed").format(warning.description)) - await self.notify_team(member, warning.description, removed=True) + async def remove_warnings_async(self, id: int): + warning = self._remove_warnings(id) + await self._notify_after_remove(warning) diff --git a/kdb-web/src/app/models/graphql/mutations.model.ts b/kdb-web/src/app/models/graphql/mutations.model.ts index ece931d6..8a877cea 100644 --- a/kdb-web/src/app/models/graphql/mutations.model.ts +++ b/kdb-web/src/app/models/graphql/mutations.model.ts @@ -1,8 +1,8 @@ export class Mutations { static updateUser = ` - mutation updateUser($id: ID, $xp: Int, $levelId: ID) { + mutation updateUser($id: ID, $xp: Int, $levelId: ID, $userWarnings: [UserWarningInput]) { user { - updateUser(input: { id: $id, xp: $xp, levelId: $levelId }) { + updateUser(input: { id: $id, xp: $xp, levelId: $levelId, userWarnings: $userWarnings }) { id name xp @@ -10,6 +10,10 @@ export class Mutations { id name } + userWarnings { + id + description + } } } } @@ -314,4 +318,50 @@ export class Mutations { } } `; + + + + static createUserWarning = ` + mutation createUserWarning($name: String, $description: String, $attribute: String, $operator: String, $value: String, $serverId: ID) { + userWarning { + createUserWarning(input: { name: $name, description: $description, attribute: $attribute, operator: $operator, value: $value, serverId: $serverId}) { + id + name + description + attribute + operator + value + server { + id + } + } + } + } + `; + + static updateUserWarning = ` + mutation updateUserWarning($id: ID, $name: String, $description: String, $attribute: String, $operator: String, $value: String) { + userWarning { + updateUserWarning(input: { id: $id, name: $name, description: $description, attribute: $attribute, operator: $operator, value: $value}) { + id + name + description + attribute + operator + value + } + } + } + `; + + static deleteUserWarning = ` + mutation deleteUserWarning($id: ID) { + userWarning { + deleteUserWarning(id: $id) { + id + name + } + } + } + `; } diff --git a/kdb-web/src/app/models/graphql/queries.model.ts b/kdb-web/src/app/models/graphql/queries.model.ts index 4f87d53b..2e9d362b 100644 --- a/kdb-web/src/app/models/graphql/queries.model.ts +++ b/kdb-web/src/app/models/graphql/queries.model.ts @@ -360,6 +360,10 @@ export class Queries { userWarningCount userWarnings { id + user { + id + name + } description author { id diff --git a/kdb-web/src/app/models/graphql/result.model.ts b/kdb-web/src/app/models/graphql/result.model.ts index cff8fcf2..411d6f1d 100644 --- a/kdb-web/src/app/models/graphql/result.model.ts +++ b/kdb-web/src/app/models/graphql/result.model.ts @@ -6,6 +6,7 @@ import { Achievement } 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 { UserWarning } from "../data/user_warning.model"; export interface GraphQLResult { data: { @@ -77,3 +78,11 @@ export interface ShortRoleNameMutationResult { deleteShortRoleName?: ShortRoleName }; } + +export interface UserWarningMutationResult { + userWarning: { + createUserWarning?: UserWarning + updateUserWarning?: UserWarning + deleteUserWarning?: UserWarning + }; +} 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 index a101087f..46ddaf7c 100644 --- a/kdb-web/src/app/modules/view/server/profile/profile.component.html +++ b/kdb-web/src/app/modules/view/server/profile/profile.component.html @@ -135,10 +135,10 @@ - {{value.author.name}} + {{value.author?.name}} - {{value.author.name}} + {{value.author?.name}} @@ -262,5 +262,12 @@ + +
+ +
+ +
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 index 60650137..c06e8387 100644 --- a/kdb-web/src/app/modules/view/server/profile/profile.component.ts +++ b/kdb-web/src/app/modules/view/server/profile/profile.component.ts @@ -10,9 +10,13 @@ import { AuthService } from "src/app/services/auth/auth.service"; import { ToastService } from "src/app/services/toast/toast.service"; import { TranslateService } from "@ngx-translate/core"; import { Server } from "../../../../models/data/server.model"; -import { forkJoin, Subject } from "rxjs"; -import { takeUntil } from "rxjs/operators"; +import { forkJoin, Subject, throwError } from "rxjs"; +import { catchError, takeUntil } from "rxjs/operators"; import { Table } from "primeng/table"; +import { UserWarning } from "../../../../models/data/user_warning.model"; +import { LevelMutationResult, UpdateUserMutationResult, UserWarningMutationResult } from "../../../../models/graphql/result.model"; +import { Mutations } from "../../../../models/graphql/mutations.model"; +import { ConfirmationDialogService } from "../../../../services/confirmation-dialog/confirmation-dialog.service"; @Component({ selector: "app-profile", @@ -23,6 +27,10 @@ export class ProfileComponent implements OnInit, OnDestroy { user: User = { createdAt: "", modifiedAt: "" }; private server: Server = {}; + private author?: UserDTO; + private clonedUserWarnings: UserWarning[] = []; + public isEditingNewUserWarning: boolean = false; + public isEditing: boolean = false; private unsubscriber = new Subject(); @@ -33,11 +41,16 @@ export class ProfileComponent implements OnInit, OnDestroy { private data: DataService, private auth: AuthService, private toast: ToastService, - private translate: TranslateService + private translate: TranslateService, + private toastService: ToastService ) { } public ngOnInit(): void { + this.loadProfile(); + } + + private loadProfile() { this.route.params.pipe(takeUntil(this.unsubscriber)).subscribe(params => { this.data.getServerFromRoute(this.route).then(async (server) => { if (!params["memberId"] || params["memberId"] == "undefined") { @@ -55,6 +68,7 @@ export class ProfileComponent implements OnInit, OnDestroy { await this.router.navigate(["/server", server.id]); return; } + this.author = user; this.data.query(Queries.userProfile, { serverId: this.server.id, @@ -85,6 +99,32 @@ export class ProfileComponent implements OnInit, OnDestroy { }); } + public updateUser() { + this.spinner.showSpinner(); + this.spinner.showSpinner(); + this.data.mutation(Mutations.updateUser, { + id: this.user.id, + xp: this.user.xp, + levelId: this.user.level?.id, + userWarnings: this.user.userWarnings?.map(userWarning => { + return { + id: userWarning.id, + user: userWarning.user?.id ?? this.user.id, + description: userWarning.description, + author: userWarning.author?.id ?? this.author?.id + } + }) + } + ).pipe(catchError(err => { + this.spinner.hideSpinner(); + return throwError(err); + })).subscribe(_ => { + this.spinner.hideSpinner(); + this.toastService.success(this.translate.instant("view.server.members.message.user_changed"), this.translate.instant("view.server.members.message.user_changed_d", { name: this.user.name })); + this.loadProfile(); + }); + } + public ngOnDestroy(): void { this.unsubscriber.next(); this.unsubscriber.complete(); @@ -141,16 +181,42 @@ export class ProfileComponent implements OnInit, OnDestroy { } addNewUserWarning(table: Table) { + const newWarning: UserWarning = { + description: "", + user: this.user + }; + + this.user.userWarnings = [newWarning, ...this.user.userWarnings ?? []]; + + table.initRowEdit(newWarning); + + const index = this.user.userWarnings.findIndex(l => l.id == newWarning.id); + this.onRowEditInit(table, newWarning, index); + + this.isEditingNewUserWarning = true; + } + + public onRowEditInit(table: Table, user: User, index: number): void { + this.clonedUserWarnings[index] = { ...user }; } deleteUserWarning(index: number) { + this.user.userWarnings?.splice(index, 1); } editSaveUserWarning(value: any, index: number) { + this.isEditingNewUserWarning = false; + if (!value.value || !this.user.userWarnings || this.user.userWarnings[index] == this.clonedUserWarnings[index]) { + return; + } + + delete this.clonedUserWarnings[index]; } editCancelUserWarning(index: number) { + if (this.user.userWarnings) { + this.user.userWarnings[index] = this.clonedUserWarnings[index]; + } + delete this.clonedUserWarnings[index]; } - - protected readonly visualViewport = visualViewport; }