Added birthday to wi #401

This commit is contained in:
Sven Heidemann 2023-10-10 18:50:20 +02:00
parent 61bdc8a52a
commit 7aff767a4b
27 changed files with 449 additions and 148 deletions

View File

@ -9,11 +9,13 @@ from bot_data.migration.api_key_migration import ApiKeyMigration
from bot_data.migration.api_migration import ApiMigration from bot_data.migration.api_migration import ApiMigration
from bot_data.migration.auto_role_fix1_migration import AutoRoleFix1Migration from bot_data.migration.auto_role_fix1_migration import AutoRoleFix1Migration
from bot_data.migration.auto_role_migration import AutoRoleMigration from bot_data.migration.auto_role_migration import AutoRoleMigration
from bot_data.migration.birthday_migration import BirthdayMigration
from bot_data.migration.config_feature_flags_migration import ConfigFeatureFlagsMigration from bot_data.migration.config_feature_flags_migration import ConfigFeatureFlagsMigration
from bot_data.migration.config_migration import ConfigMigration from bot_data.migration.config_migration import ConfigMigration
from bot_data.migration.db_history_migration import DBHistoryMigration from bot_data.migration.db_history_migration import DBHistoryMigration
from bot_data.migration.default_role_migration import DefaultRoleMigration from bot_data.migration.default_role_migration import DefaultRoleMigration
from bot_data.migration.fix_updates_migration import FixUpdatesMigration from bot_data.migration.fix_updates_migration import FixUpdatesMigration
from bot_data.migration.fix_user_history_migration import FixUserHistoryMigration
from bot_data.migration.initial_migration import InitialMigration from bot_data.migration.initial_migration import InitialMigration
from bot_data.migration.level_migration import LevelMigration from bot_data.migration.level_migration import LevelMigration
from bot_data.migration.remove_stats_migration import RemoveStatsMigration from bot_data.migration.remove_stats_migration import RemoveStatsMigration
@ -56,3 +58,5 @@ class StartupMigrationExtension(StartupExtensionABC):
services.add_transient(MigrationABC, ShortRoleNameMigration) # 28.09.2023 #378 - 1.1.7 services.add_transient(MigrationABC, ShortRoleNameMigration) # 28.09.2023 #378 - 1.1.7
services.add_transient(MigrationABC, FixUpdatesMigration) # 28.09.2023 #378 - 1.1.7 services.add_transient(MigrationABC, FixUpdatesMigration) # 28.09.2023 #378 - 1.1.7
services.add_transient(MigrationABC, ShortRoleNameOnlyHighestMigration) # 02.10.2023 #391 - 1.1.9 services.add_transient(MigrationABC, ShortRoleNameOnlyHighestMigration) # 02.10.2023 #391 - 1.1.9
services.add_transient(MigrationABC, FixUserHistoryMigration) # 10.10.2023 #401 - 1.2.0
services.add_transient(MigrationABC, BirthdayMigration) # 10.10.2023 #401 - 1.2.0

View File

@ -0,0 +1,84 @@
from bot_core.logging.database_logger import DatabaseLogger
from bot_data.abc.migration_abc import MigrationABC
from bot_data.db_context import DBContext
class BirthdayMigration(MigrationABC):
name = "1.2.0_BirthdayMigration"
def __init__(self, logger: DatabaseLogger, db: DBContext):
MigrationABC.__init__(self)
self._logger = logger
self._db = db
self._cursor = db.cursor
def upgrade(self):
self._logger.debug(__name__, "Running upgrade")
self._cursor.execute(
str(
f"""
ALTER TABLE Users
ADD Birthday DATE NULL AFTER MessageCount;
"""
)
)
self._cursor.execute(
str(
f"""
ALTER TABLE UsersHistory
ADD Birthday DATE NULL AFTER MessageCount;
"""
)
)
self._exec(__file__, "users.sql")
self._cursor.execute(
str(
f"""
ALTER TABLE CFG_Server
ADD XpForBirthday BIGINT(20) NOT NULL DEFAULT 0 AFTER XpPerAchievement;
"""
)
)
self._cursor.execute(
str(
f"""
ALTER TABLE CFG_ServerHistory
ADD XpForBirthday BIGINT(20) NOT NULL DEFAULT 0 AFTER XpPerAchievement;
"""
)
)
self._exec(__file__, "config/server.sql")
def downgrade(self):
self._cursor.execute(
str(
f"""
ALTER TABLE Users DROP COLUMN Birthday;
"""
)
)
self._cursor.execute(
str(
f"""
ALTER TABLE UsersHistory DROP COLUMN Birthday;
"""
)
)
self._cursor.execute(
str(
f"""
ALTER TABLE CFG_Server DROP COLUMN XpForBirthday;
"""
)
)
self._cursor.execute(
str(
f"""
ALTER TABLE CFG_ServerHistory DROP COLUMN XpForBirthday;
"""
)
)

View File

@ -9,6 +9,9 @@ CREATE TABLE IF NOT EXISTS `UsersHistory`
`Id` BIGINT(20) NOT NULL, `Id` BIGINT(20) NOT NULL,
`DiscordId` BIGINT(20) NOT NULL, `DiscordId` BIGINT(20) NOT NULL,
`XP` BIGINT(20) NOT NULL DEFAULT 0, `XP` BIGINT(20) NOT NULL DEFAULT 0,
`ReactionCount` BIGINT(20) NOT NULL DEFAULT 0,
`MessageCount` BIGINT(20) NOT NULL DEFAULT 0,
`Birthday` DATE NULL,
`ServerId` BIGINT(20) DEFAULT NULL, `ServerId` BIGINT(20) DEFAULT NULL,
`Deleted` BOOL DEFAULT FALSE, `Deleted` BOOL DEFAULT FALSE,
`DateFrom` DATETIME(6) NOT NULL, `DateFrom` DATETIME(6) NOT NULL,
@ -22,12 +25,10 @@ CREATE TRIGGER `TR_UsersUpdate`
ON `Users` ON `Users`
FOR EACH ROW FOR EACH ROW
BEGIN BEGIN
INSERT INTO `UsersHistory` ( INSERT INTO `UsersHistory` (`Id`, `DiscordId`, `XP`, `ReactionCount`, `MessageCount`, `Birthday`, `ServerId`,
`Id`, `DiscordId`, `XP`, `ServerId`, `DateFrom`, `DateTo` `DateFrom`, `DateTo`)
) VALUES (OLD.UserId, OLD.DiscordId, OLD.XP, OLD.ReactionCount, OLD.MessageCount, OLD.Birthday, OLD.ServerId,
VALUES ( OLD.LastModifiedAt, CURRENT_TIMESTAMP(6));
OLD.UserId, OLD.DiscordId, OLD.XP, OLD.ServerId, OLD.LastModifiedAt, CURRENT_TIMESTAMP(6)
);
END; END;
DROP TRIGGER IF EXISTS `TR_UsersDelete`; DROP TRIGGER IF EXISTS `TR_UsersDelete`;
@ -37,10 +38,8 @@ CREATE TRIGGER `TR_UsersDelete`
ON `Users` ON `Users`
FOR EACH ROW FOR EACH ROW
BEGIN BEGIN
INSERT INTO `UsersHistory` ( INSERT INTO `UsersHistory` (`Id`, `DiscordId`, `XP`, `ReactionCount`, `MessageCount`, `Birthday`, `ServerId`,
`Id`, `DiscordId`, `XP`, `ServerId`, `Deleted`, `DateFrom`, `DateTo` `Deleted`, `DateFrom`, `DateTo`)
) VALUES (OLD.UserId, OLD.DiscordId, OLD.XP, OLD.ReactionCount, OLD.MessageCount, OLD.Birthday, OLD.ServerId, TRUE,
VALUES ( OLD.LastModifiedAt, CURRENT_TIMESTAMP(6));
OLD.UserId, OLD.DiscordId, OLD.XP, OLD.ServerId, TRUE, OLD.LastModifiedAt, CURRENT_TIMESTAMP(6)
);
END; END;

View File

@ -0,0 +1,45 @@
from bot_core.logging.database_logger import DatabaseLogger
from bot_data.abc.migration_abc import MigrationABC
from bot_data.db_context import DBContext
class FixUserHistoryMigration(MigrationABC):
name = "1.2.0_FixUserHistoryMigration"
def __init__(self, logger: DatabaseLogger, db: DBContext):
MigrationABC.__init__(self)
self._logger = logger
self._db = db
self._cursor = db.cursor
def upgrade(self):
self._logger.debug(__name__, "Running upgrade")
# fix 1.1.0_AchievementsMigration
self._cursor.execute(
str(
f"""ALTER TABLE UsersHistory ADD COLUMN IF NOT EXISTS ReactionCount BIGINT NOT NULL DEFAULT 0 AFTER XP;"""
)
)
self._cursor.execute(
str(
f"""ALTER TABLE UsersHistory ADD COLUMN IF NOT EXISTS MessageCount BIGINT NOT NULL DEFAULT 0 AFTER ReactionCount;"""
)
)
self._exec(__file__, "users.sql")
def downgrade(self):
self._cursor.execute(
str(
f"""
ALTER TABLE UsersHistory DROP COLUMN MessageCount;
"""
)
)
self._cursor.execute(
str(
f"""
ALTER TABLE UsersHistory DROP COLUMN ReactionCount;
"""
)
)

View File

@ -24,6 +24,7 @@ class ServerConfig(TableABC, ConfigurationModelABC):
xp_per_ontime_hour: int, xp_per_ontime_hour: int,
xp_per_event_participation: int, xp_per_event_participation: int,
xp_per_achievement: int, xp_per_achievement: int,
xp_for_birthday: int,
afk_command_channel_id: int, afk_command_channel_id: int,
help_voice_channel_id: int, help_voice_channel_id: int,
team_channel_id: int, team_channel_id: int,
@ -48,6 +49,7 @@ class ServerConfig(TableABC, ConfigurationModelABC):
self._xp_per_ontime_hour = xp_per_ontime_hour self._xp_per_ontime_hour = xp_per_ontime_hour
self._xp_per_event_participation = xp_per_event_participation self._xp_per_event_participation = xp_per_event_participation
self._xp_per_achievement = xp_per_achievement self._xp_per_achievement = xp_per_achievement
self._xp_for_birthday = xp_for_birthday
self._afk_command_channel_id = afk_command_channel_id self._afk_command_channel_id = afk_command_channel_id
self._help_voice_channel_id = help_voice_channel_id self._help_voice_channel_id = help_voice_channel_id
self._team_channel_id = team_channel_id self._team_channel_id = team_channel_id
@ -76,6 +78,7 @@ class ServerConfig(TableABC, ConfigurationModelABC):
10, 10,
10, 10,
10, 10,
10,
guild.system_channel.id, guild.system_channel.id,
guild.system_channel.id, guild.system_channel.id,
guild.system_channel.id, guild.system_channel.id,
@ -164,6 +167,14 @@ class ServerConfig(TableABC, ConfigurationModelABC):
def xp_per_achievement(self, value: int): def xp_per_achievement(self, value: int):
self._xp_per_achievement = value self._xp_per_achievement = value
@property
def xp_for_birthday(self) -> int:
return self._xp_for_birthday
@xp_for_birthday.setter
def xp_for_birthday(self, value: int):
self._xp_for_birthday = value
@property @property
def afk_command_channel_id(self) -> int: def afk_command_channel_id(self) -> int:
return self._afk_command_channel_id return self._afk_command_channel_id
@ -280,6 +291,7 @@ class ServerConfig(TableABC, ConfigurationModelABC):
`XpPerOntimeHour`, `XpPerOntimeHour`,
`XpPerEventParticipation`, `XpPerEventParticipation`,
`XpPerAchievement`, `XpPerAchievement`,
`XpForBirthday`,
`AFKCommandChannelId`, `AFKCommandChannelId`,
`HelpVoiceChannelId`, `HelpVoiceChannelId`,
`TeamChannelId`, `TeamChannelId`,
@ -298,6 +310,7 @@ class ServerConfig(TableABC, ConfigurationModelABC):
{self._xp_per_ontime_hour}, {self._xp_per_ontime_hour},
{self._xp_per_event_participation}, {self._xp_per_event_participation},
{self._xp_per_achievement}, {self._xp_per_achievement},
'{self._xp_for_birthday}',
{self._afk_command_channel_id}, {self._afk_command_channel_id},
{self._help_voice_channel_id}, {self._help_voice_channel_id},
{self._team_channel_id}, {self._team_channel_id},
@ -324,6 +337,7 @@ class ServerConfig(TableABC, ConfigurationModelABC):
`XpPerOntimeHour` = {self._xp_per_ontime_hour}, `XpPerOntimeHour` = {self._xp_per_ontime_hour},
`XpPerEventParticipation` = {self._xp_per_event_participation}, `XpPerEventParticipation` = {self._xp_per_event_participation},
`XpPerAchievement` = {self._xp_per_achievement}, `XpPerAchievement` = {self._xp_per_achievement},
`XpForBirthday` = {self._xp_for_birthday},
`AFKCommandChannelId` = {self._afk_command_channel_id}, `AFKCommandChannelId` = {self._afk_command_channel_id},
`HelpVoiceChannelId` = {self._help_voice_channel_id}, `HelpVoiceChannelId` = {self._help_voice_channel_id},
`TeamChannelId` = {self._team_channel_id}, `TeamChannelId` = {self._team_channel_id},

View File

@ -1,4 +1,4 @@
from datetime import datetime from datetime import datetime, date
from typing import Optional from typing import Optional
from cpl_core.database import TableABC from cpl_core.database import TableABC
@ -17,6 +17,7 @@ class User(TableABC):
xp: int, xp: int,
reaction_count: int, reaction_count: int,
message_count: int, message_count: int,
birthday: Optional[date],
server: Optional[Server], server: Optional[Server],
created_at: datetime = None, created_at: datetime = None,
modified_at: datetime = None, modified_at: datetime = None,
@ -27,6 +28,7 @@ class User(TableABC):
self._xp = xp self._xp = xp
self._reaction_count = reaction_count self._reaction_count = reaction_count
self._message_count = message_count self._message_count = message_count
self._birthday = birthday
self._server = server self._server = server
TableABC.__init__(self) TableABC.__init__(self)
@ -79,6 +81,14 @@ class User(TableABC):
def reaction_count(self, value: int): def reaction_count(self, value: int):
self._reaction_count = value self._reaction_count = value
@property
def birthday(self) -> Optional[datetime]:
return self._birthday
@birthday.setter
def birthday(self, value: Optional[datetime]):
self._birthday = value
@property @property
@ServiceProviderABC.inject @ServiceProviderABC.inject
def ontime(self, services: ServiceProviderABC) -> float: def ontime(self, services: ServiceProviderABC) -> float:
@ -171,12 +181,13 @@ class User(TableABC):
return str( return str(
f""" f"""
INSERT INTO `Users` ( INSERT INTO `Users` (
`DiscordId`, `XP`, `MessageCount`, `ReactionCount`, `ServerId` `DiscordId`, `XP`, `MessageCount`, `ReactionCount`, `Birthday`, `ServerId`
) VALUES ( ) VALUES (
{self._discord_id}, {self._discord_id},
{self._xp}, {self._xp},
{self._message_count}, {self._message_count},
{self._reaction_count}, {self._reaction_count},
'{self._birthday}',
{self._server.id} {self._server.id}
); );
""" """
@ -189,7 +200,8 @@ class User(TableABC):
UPDATE `Users` UPDATE `Users`
SET `XP` = {self._xp}, SET `XP` = {self._xp},
`MessageCount` = {self._message_count}, `MessageCount` = {self._message_count},
`ReactionCount` = {self._reaction_count} `ReactionCount` = {self._reaction_count},
`Birthday` = '{self._birthday}'
WHERE `UserId` = {self._user_id}; WHERE `UserId` = {self._user_id};
""" """
) )

View File

@ -66,12 +66,13 @@ class ServerConfigRepositoryService(ServerConfigRepositoryABC):
result[13], result[13],
result[14], result[14],
result[15], result[15],
json.loads(result[16]), result[16],
self._servers.get_server_by_id(result[17]), json.loads(result[17]),
self._get_afk_channel_ids(result[17]), self._servers.get_server_by_id(result[18]),
self._get_team_role_ids(result[17]), self._get_afk_channel_ids(result[18]),
result[18], self._get_team_role_ids(result[18]),
result[19], result[19],
result[20],
id=result[0], id=result[0],
) )

View File

@ -29,9 +29,10 @@ class UserRepositoryService(UserRepositoryABC):
result[2], result[2],
result[3], result[3],
result[4], result[4],
self._servers.get_server_by_id(result[5]), result[5].strftime("%d.%m.%Y") if result[5] is not None else None,
result[6], self._servers.get_server_by_id(result[6]),
result[7], result[7],
result[8],
id=result[0], id=result[0],
) )

View File

@ -9,6 +9,7 @@ type ServerConfig implements TableWithHistoryQuery {
xpPerOntimeHour: Int xpPerOntimeHour: Int
xpPerEventParticipation: Int xpPerEventParticipation: Int
xpPerAchievement: Int xpPerAchievement: Int
xpForBirthday: Int
afkCommandChannelId: String afkCommandChannelId: String
helpVoiceChannelId: String helpVoiceChannelId: String
teamChannelId: String teamChannelId: String
@ -41,6 +42,7 @@ type ServerConfigHistory implements HistoryTableQuery {
xpPerOntimeHour: Int xpPerOntimeHour: Int
xpPerEventParticipation: Int xpPerEventParticipation: Int
xpPerAchievement: Int xpPerAchievement: Int
xpForBirthday: Int
afkCommandChannelId: String afkCommandChannelId: String
helpVoiceChannelId: String helpVoiceChannelId: String
teamChannelId: String teamChannelId: String
@ -91,6 +93,7 @@ input ServerConfigInput {
xpPerOntimeHour: Int xpPerOntimeHour: Int
xpPerEventParticipation: Int xpPerEventParticipation: Int
xpPerAchievement: Int xpPerAchievement: Int
xpForBirthday: Int
afkCommandChannelId: String afkCommandChannelId: String
helpVoiceChannelId: String helpVoiceChannelId: String
teamChannelId: String teamChannelId: String

View File

@ -5,6 +5,7 @@ type User implements TableWithHistoryQuery {
xp: Int xp: Int
messageCount: Int messageCount: Int
reactionCount: Int reactionCount: Int
birthday: String
ontime: Float ontime: Float
level: Level level: Level
@ -62,6 +63,7 @@ type UserMutation {
input UserInput { input UserInput {
id: ID id: ID
xp: Int xp: Int
birthday: String
levelId: ID levelId: ID
userWarnings: [UserWarningInput] userWarnings: [UserWarningInput]
} }

View File

@ -1,6 +1,9 @@
from datetime import datetime
from cpl_core.database.context import DatabaseContextABC from cpl_core.database.context import DatabaseContextABC
from cpl_discord.service import DiscordBotServiceABC from cpl_discord.service import DiscordBotServiceABC
from bot_api.route.route import Route
from bot_data.abc.level_repository_abc import LevelRepositoryABC from bot_data.abc.level_repository_abc import LevelRepositoryABC
from bot_data.abc.server_repository_abc import ServerRepositoryABC from bot_data.abc.server_repository_abc import ServerRepositoryABC
from bot_data.abc.user_repository_abc import UserRepositoryABC from bot_data.abc.user_repository_abc import UserRepositoryABC
@ -42,6 +45,12 @@ class UserMutation(QueryABC):
def resolve_update_user(self, *_, input: dict): def resolve_update_user(self, *_, input: dict):
user = self._users.get_user_by_id(input["id"]) user = self._users.get_user_by_id(input["id"])
auth_user = Route.get_user()
member = self._bot.get_guild(user.server.discord_id).get_member(
auth_user.users.where(lambda x: x.server.id == user.server.id).single().discord_id
)
if member.id != user.discord_id:
self._can_user_mutate_data(user.server, UserRoleEnum.moderator) self._can_user_mutate_data(user.server, UserRoleEnum.moderator)
new_xp = None new_xp = None
@ -50,11 +59,13 @@ class UserMutation(QueryABC):
if user.level.id != level.id: if user.level.id != level.id:
new_xp = level.min_xp new_xp = level.min_xp
user.xp = new_xp if new_xp is not None else input["xp"] if "xp" in input else user.xp
if "userWarnings" in input: if "userWarnings" in input:
self._update_user_warning(user, input["userWarnings"]) self._update_user_warning(user, input["userWarnings"])
user.xp = new_xp if new_xp is not None else input["xp"] if "xp" in input else user.xp
user.birthday = datetime.strptime(input["birthday"], "%d.%m.%Y") if "birthday" in input else user.birthday
self._users.update_user(user) self._users.update_user(user)
self._db.save_changes() self._db.save_changes()
self._bot.loop.create_task(self._level_service.set_level(user)) self._bot.loop.create_task(self._level_service.set_level(user))

View File

@ -20,6 +20,7 @@ class ServerConfigQuery(DataQueryWithHistoryABC):
self.set_field("xpPerOntimeHour", lambda config, *_: config.xp_per_ontime_hour) self.set_field("xpPerOntimeHour", lambda config, *_: config.xp_per_ontime_hour)
self.set_field("xpPerEventParticipation", lambda config, *_: config.xp_per_event_participation) self.set_field("xpPerEventParticipation", lambda config, *_: config.xp_per_event_participation)
self.set_field("xpPerAchievement", lambda config, *_: config.xp_per_achievement) self.set_field("xpPerAchievement", lambda config, *_: config.xp_per_achievement)
self.set_field("xpForBirthday", lambda config, *_: config.xp_for_birthday)
self.set_field("afkCommandChannelId", lambda config, *_: config.afk_command_channel_id) self.set_field("afkCommandChannelId", lambda config, *_: config.afk_command_channel_id)
self.set_field("helpVoiceChannelId", lambda config, *_: config.help_voice_channel_id) self.set_field("helpVoiceChannelId", lambda config, *_: config.help_voice_channel_id)
self.set_field("teamChannelId", lambda config, *_: config.team_channel_id) self.set_field("teamChannelId", lambda config, *_: config.team_channel_id)

View File

@ -50,6 +50,7 @@ class UserQuery(DataQueryWithHistoryABC):
self.set_field("xp", self.resolve_xp) self.set_field("xp", self.resolve_xp)
self.set_field("messageCount", lambda x, *_: x.message_count) self.set_field("messageCount", lambda x, *_: x.message_count)
self.set_field("reactionCount", lambda x, *_: x.reaction_count) self.set_field("reactionCount", lambda x, *_: x.reaction_count)
self.set_field("birthday", lambda x, *_: x.birthday)
self.set_field("ontime", self.resolve_ontime) self.set_field("ontime", self.resolve_ontime)
self.set_field("level", self.resolve_level) self.set_field("level", self.resolve_level)
self.add_collection( self.add_collection(

View File

@ -1,12 +1,12 @@
{ {
"name": "kdb-web", "name": "kdb-web",
"version": "1.0.dev127_config_in_wi", "version": "1.2.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "kdb-web", "name": "kdb-web",
"version": "1.0.dev127_config_in_wi", "version": "1.2.0",
"dependencies": { "dependencies": {
"@angular/animations": "^15.1.4", "@angular/animations": "^15.1.4",
"@angular/common": "^15.1.4", "@angular/common": "^15.1.4",
@ -21,7 +21,7 @@
"@ngx-translate/core": "^14.0.0", "@ngx-translate/core": "^14.0.0",
"@ngx-translate/http-loader": "^7.0.0", "@ngx-translate/http-loader": "^7.0.0",
"@types/socket.io-client": "^3.0.0", "@types/socket.io-client": "^3.0.0",
"primeflex": "^3.3.1", "moment": "^2.29.4",
"primeicons": "^6.0.1", "primeicons": "^6.0.1",
"primeng": "^15.2.0", "primeng": "^15.2.0",
"rxjs": "~7.5.0", "rxjs": "~7.5.0",
@ -8157,6 +8157,14 @@
"mkdirp": "bin/cmd.js" "mkdirp": "bin/cmd.js"
} }
}, },
"node_modules/moment": {
"version": "2.29.4",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
"engines": {
"node": "*"
}
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@ -9303,11 +9311,6 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/primeflex": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/primeflex/-/primeflex-3.3.1.tgz",
"integrity": "sha512-zaOq3YvcOYytbAmKv3zYc+0VNS9Wg5d37dfxZnveKBFPr7vEIwfV5ydrpiouTft8MVW6qNjfkaQphHSnvgQbpQ=="
},
"node_modules/primeicons": { "node_modules/primeicons": {
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/primeicons/-/primeicons-6.0.1.tgz", "resolved": "https://registry.npmjs.org/primeicons/-/primeicons-6.0.1.tgz",

View File

@ -1,6 +1,6 @@
{ {
"name": "kdb-web", "name": "kdb-web",
"version": "1.1.dev402", "version": "1.2.0",
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
"update-version": "ts-node update-version.ts", "update-version": "ts-node update-version.ts",
@ -30,6 +30,7 @@
"@ngx-translate/core": "^14.0.0", "@ngx-translate/core": "^14.0.0",
"@ngx-translate/http-loader": "^7.0.0", "@ngx-translate/http-loader": "^7.0.0",
"@types/socket.io-client": "^3.0.0", "@types/socket.io-client": "^3.0.0",
"moment": "^2.29.4",
"primeicons": "^6.0.1", "primeicons": "^6.0.1",
"primeng": "^15.2.0", "primeng": "^15.2.0",
"rxjs": "~7.5.0", "rxjs": "~7.5.0",

View File

@ -1,23 +1,22 @@
import { HttpClient, HttpClientModule } from '@angular/common/http'; import { HttpClient, HttpClientModule } from "@angular/common/http";
import { APP_INITIALIZER, ErrorHandler, NgModule } from '@angular/core'; import { APP_INITIALIZER, ErrorHandler, NgModule } from "@angular/core";
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from "@angular/platform-browser";
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { JwtModule } from '@auth0/angular-jwt'; import { JwtModule } from "@auth0/angular-jwt";
import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateLoader, TranslateModule } from "@ngx-translate/core";
import { TranslateHttpLoader } from '@ngx-translate/http-loader'; import { TranslateHttpLoader } from "@ngx-translate/http-loader";
import { ConfirmationService, MessageService } from 'primeng/api'; import { ConfirmationService, MessageService } from "primeng/api";
import { DialogService } from 'primeng/dynamicdialog'; import { DialogService } from "primeng/dynamicdialog";
import { AppRoutingModule } from './app-routing.module'; import { AppRoutingModule } from "./app-routing.module";
import { AppComponent } from './app.component'; import { AppComponent } from "./app.component";
import { NotFoundComponent } from './components/error/not-found/not-found.component'; import { NotFoundComponent } from "./components/error/not-found/not-found.component";
import { FooterComponent } from './components/footer/footer.component'; import { FooterComponent } from "./components/footer/footer.component";
import { HeaderComponent } from './components/header/header.component'; import { HeaderComponent } from "./components/header/header.component";
import { SidebarComponent } from './components/sidebar/sidebar.component'; import { SidebarComponent } from "./components/sidebar/sidebar.component";
import { SpinnerComponent } from './components/spinner/spinner.component'; import { SpinnerComponent } from "./components/spinner/spinner.component";
import { SharedModule } from './modules/shared/shared.module'; import { SharedModule } from "./modules/shared/shared.module";
import { ErrorHandlerService } from './services/error-handler/error-handler.service'; import { ErrorHandlerService } from "./services/error-handler/error-handler.service";
import { SettingsService } from './services/settings/settings.service'; import { SettingsService } from "./services/settings/settings.service";
@NgModule({ @NgModule({
@ -63,7 +62,7 @@ import { SettingsService } from './services/settings/settings.service';
}, },
MessageService, MessageService,
ConfirmationService, ConfirmationService,
DialogService DialogService,
], ],
bootstrap: [AppComponent] bootstrap: [AppComponent]
}) })

View File

@ -12,8 +12,9 @@ export interface User extends DataWithHistory {
discordId?: number; discordId?: number;
name?: string; name?: string;
xp?: number; xp?: number;
message_count?: number; messageCount?: number;
reaction_count?: number; reactionCount?: number;
birthday?: string;
ontime?: number; ontime?: number;
level?: Level; level?: Level;
server?: Server; server?: Server;

View File

@ -1,11 +1,14 @@
export class Mutations { export class Mutations {
static updateUser = ` static updateUser = `
mutation updateUser($id: ID, $xp: Int, $levelId: ID, $userWarnings: [UserWarningInput]) { mutation updateUser($id: ID, $xp: Int $birthday: String, $levelId: ID, $userWarnings: [UserWarningInput]) {
user { user {
updateUser(input: { id: $id, xp: $xp, levelId: $levelId, userWarnings: $userWarnings }) { updateUser(input: { id: $id, xp: $xp, birthday: $birthday, levelId: $levelId, userWarnings: $userWarnings }) {
id id
name name
xp xp
messageCount
reactionCount
birthday
level { level {
id id
name name

View File

@ -285,6 +285,9 @@ export class Queries {
discordId discordId
name name
xp xp
messageCount
reactionCount
birthday
ontime ontime
level { level {
id id

View File

@ -32,8 +32,37 @@ import { HideableHeaderComponent } from './components/hideable-header/hideable-h
import { MultiSelectColumnsComponent } from './base/multi-select-columns/multi-select-columns.component'; import { MultiSelectColumnsComponent } from './base/multi-select-columns/multi-select-columns.component';
import { FeatureFlagListComponent } from './components/feature-flag-list/feature-flag-list.component'; import { FeatureFlagListComponent } from './components/feature-flag-list/feature-flag-list.component';
import { InputSwitchModule } from "primeng/inputswitch"; import { InputSwitchModule } from "primeng/inputswitch";
import { CalendarModule } from "primeng/calendar";
const PrimeNGModules = [
ButtonModule,
PasswordModule,
MenuModule,
DialogModule,
ProgressSpinnerModule,
HttpClientModule,
FormsModule,
ReactiveFormsModule,
ToastModule,
ConfirmDialogModule,
TableModule,
InputTextModule,
CheckboxModule,
DropdownModule,
TranslateModule,
DynamicDialogModule,
PanelMenuModule,
PanelModule,
InputNumberModule,
ImageModule,
SidebarModule,
DataViewModule,
MultiSelectModule,
InputSwitchModule,
CalendarModule,
]
@NgModule({ @NgModule({
declarations: [ declarations: [
AuthRolePipe, AuthRolePipe,
@ -48,66 +77,20 @@ import { InputSwitchModule } from "primeng/inputswitch";
], ],
imports: [ imports: [
CommonModule, CommonModule,
ButtonModule, ...PrimeNGModules
PasswordModule,
MenuModule,
DialogModule,
ProgressSpinnerModule,
HttpClientModule,
FormsModule,
ReactiveFormsModule,
ToastModule,
ConfirmDialogModule,
TableModule,
InputTextModule,
CheckboxModule,
DropdownModule,
TranslateModule,
DynamicDialogModule,
PanelMenuModule,
PanelModule,
InputNumberModule,
ImageModule,
SidebarModule,
DataViewModule,
MultiSelectModule,
InputSwitchModule,
], ],
exports: [ exports: [
ButtonModule, ...PrimeNGModules,
PasswordModule,
MenuModule,
DialogModule,
ProgressSpinnerModule,
HttpClientModule,
FormsModule,
ReactiveFormsModule,
ToastModule,
ConfirmDialogModule,
TableModule,
InputTextModule,
CheckboxModule,
DropdownModule,
TranslateModule,
DynamicDialogModule,
PanelMenuModule,
PanelModule,
AuthRolePipe, AuthRolePipe,
IpAddressPipe, IpAddressPipe,
BoolPipe, BoolPipe,
InputNumberModule,
ImageModule,
SidebarModule,
HistoryBtnComponent, HistoryBtnComponent,
DataViewModule,
DataViewLayoutOptions, DataViewLayoutOptions,
ConfigListComponent, ConfigListComponent,
MultiSelectModule,
HideableColumnComponent, HideableColumnComponent,
HideableHeaderComponent, HideableHeaderComponent,
MultiSelectColumnsComponent, MultiSelectColumnsComponent,
FeatureFlagListComponent, FeatureFlagListComponent,
InputSwitchModule,
] ]
}) })
export class SharedModule { export class SharedModule {

View File

@ -204,7 +204,7 @@
<span class="p-column-title">{{'common.level' | translate}}:</span> <span class="p-column-title">{{'common.level' | translate}}:</span>
<p-cellEditor> <p-cellEditor>
<ng-template pTemplate="input"> <ng-template pTemplate="input">
<p-dropdown [options]="levels" [(ngModel)]="member.level" placeholder="{{'common.level' | translate}}"></p-dropdown> <p-dropdown [options]="levels" [(ngModel)]="member.level" dataKey="id" placeholder="{{'common.level' | translate}}"></p-dropdown>
</ng-template> </ng-template>
<ng-template pTemplate="output"> <ng-template pTemplate="output">
{{member.level.name}} {{member.level.name}}

View File

@ -30,7 +30,32 @@
<div class="content-row"> <div class="content-row">
<div class="content-column"> <div class="content-column">
<div class="content-data-name">{{'view.server.profile.xp' | translate}}:</div> <div class="content-data-name">{{'view.server.profile.xp' | translate}}:</div>
<div class="content-data-value">{{user.xp}}</div> <div *ngIf="!isEditing" class="content-data-value">{{user.xp}}</div>
<div *ngIf="isModerator && isEditing" class="content-data-value"><input class="table-edit-input" pInputText min="0" type="number" [(ngModel)]="user.xp"></div>
</div>
</div>
<div class="content-row">
<div class="content-column">
<div class="content-data-name">{{'view.server.profile.message_count' | translate}}:</div>
<div class="content-data-value">{{user.messageCount}}</div>
</div>
</div>
<div class="content-row">
<div class="content-column">
<div class="content-data-name">{{'view.server.profile.reaction_count' | translate}}:</div>
<div class="content-data-value">{{user.reactionCount}}</div>
</div>
</div>
<div class="content-row">
<div class="content-column">
<div class="content-data-name">{{'view.server.profile.birthday' | translate}}:</div>
<div *ngIf="!isEditing" class="content-data-value">{{user.birthday}}</div>
<div *ngIf="isEditing" class="content-data-value">
<p-calendar [(ngModel)]="user.birthday" dateFormat="dd.mm.yy" [showIcon]="true"></p-calendar>
</div>
</div> </div>
</div> </div>
@ -41,17 +66,14 @@
</div> </div>
</div> </div>
<!-- <div class="content-row">-->
<!-- <div class="content-column">-->
<!-- <div class="content-data-name">{{'view.server.profile.minecraft_id' | translate}}:</div>-->
<!-- <div class="content-data-value">{{user.minecraftId}}</div>-->
<!-- </div>-->
<!-- </div>-->
<div class="content-row"> <div class="content-row">
<div class="content-column"> <div class="content-column">
<div class="content-data-name">{{'view.server.profile.level' | translate}}:</div> <div class="content-data-name">{{'view.server.profile.level' | translate}}:</div>
<div class="content-data-value">{{user.level?.name}}</div> <div *ngIf="!isEditing" class="content-data-value">{{user.level?.name}}</div>
<div *ngIf="isModerator && isEditing" class="content-data-value">
<p-dropdown [options]="levels" [(ngModel)]="user.level" dataKey="id" placeholder="{{'common.level' | translate}}">
</p-dropdown>
</div>
</div> </div>
</div> </div>
@ -266,8 +288,12 @@
<div class="content-divider"></div> <div class="content-divider"></div>
<div class="content-row"> <div class="content-row">
<div style="width: 100%; display: flex; gap: 10px; justify-content: end;">
<button pButton icon="pi pi-edit" label="{{'common.edit' | translate}}" class="btn login-form-submit-btn"
(click)="toogleEditUser()"></button>
<button pButton icon="pi pi-save" label="{{'common.save' | translate}}" class="btn login-form-submit-btn" <button pButton icon="pi pi-save" label="{{'common.save' | translate}}" class="btn login-form-submit-btn"
(click)="updateUser()"></button> (click)="updateUser()" [disabled]="!isEditing"></button>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,7 +1,7 @@
import { Component, OnDestroy, OnInit } from "@angular/core"; import { Component, OnDestroy, OnInit } from "@angular/core";
import { ActivatedRoute, Router } from "@angular/router"; import { ActivatedRoute, Router } from "@angular/router";
import { Queries } from "../../../../models/graphql/queries.model"; import { Queries } from "../../../../models/graphql/queries.model";
import { UserListQuery, UserWarningQuery } from "../../../../models/graphql/query.model"; import { LevelListQuery, Query, UserListQuery, UserWarningQuery } from "../../../../models/graphql/query.model";
import { SpinnerService } from "../../../../services/spinner/spinner.service"; import { SpinnerService } from "../../../../services/spinner/spinner.service";
import { DataService } from "../../../../services/data/data.service"; import { DataService } from "../../../../services/data/data.service";
import { User } from "../../../../models/data/user.model"; import { User } from "../../../../models/data/user.model";
@ -13,10 +13,11 @@ import { Server } from "../../../../models/data/server.model";
import { forkJoin, Subject, throwError } from "rxjs"; import { forkJoin, Subject, throwError } from "rxjs";
import { catchError, takeUntil } from "rxjs/operators"; import { catchError, takeUntil } from "rxjs/operators";
import { Table } from "primeng/table"; import { Table } from "primeng/table";
import { UserWarning } from "../../../../models/data/user_warning.model"; import { UpdateUserMutationResult } from "../../../../models/graphql/result.model";
import { LevelMutationResult, UpdateUserMutationResult, UserWarningMutationResult } from "../../../../models/graphql/result.model";
import { Mutations } from "../../../../models/graphql/mutations.model"; import { Mutations } from "../../../../models/graphql/mutations.model";
import { ConfirmationDialogService } from "../../../../services/confirmation-dialog/confirmation-dialog.service"; import { MenuItem } from "primeng/api";
import { UserWarning } from "../../../../models/data/user_warning.model";
import moment from "moment";
@Component({ @Component({
selector: "app-profile", selector: "app-profile",
@ -26,11 +27,13 @@ import { ConfirmationDialogService } from "../../../../services/confirmation-dia
export class ProfileComponent implements OnInit, OnDestroy { export class ProfileComponent implements OnInit, OnDestroy {
user: User = { createdAt: "", modifiedAt: "" }; user: User = { createdAt: "", modifiedAt: "" };
levels!: MenuItem[];
private server: Server = {}; private server: Server = {};
private author?: UserDTO; private author?: UserDTO;
private clonedUserWarnings: UserWarning[] = []; private clonedUserWarnings: UserWarning[] = [];
public isEditingNewUserWarning: boolean = false; public isEditingNewUserWarning: boolean = false;
public isEditing: boolean = false; public isEditing: boolean = false;
public isModerator: boolean = false;
private unsubscriber = new Subject<void>(); private unsubscriber = new Subject<void>();
@ -47,6 +50,7 @@ export class ProfileComponent implements OnInit, OnDestroy {
} }
public ngOnInit(): void { public ngOnInit(): void {
this.isEditing = false;
this.loadProfile(); this.loadProfile();
} }
@ -59,6 +63,18 @@ export class ProfileComponent implements OnInit, OnDestroy {
} }
this.server = server; this.server = server;
this.data.query<LevelListQuery>(Queries.levelQuery, {
serverId: server.id
},
(data: Query) => {
return data.servers[0];
}
).subscribe(data => {
this.levels = data.levels.map(level => {
return { label: level.name, value: level };
});
});
let authUser = await this.auth.getLoggedInUser(); let authUser = await this.auth.getLoggedInUser();
this.spinner.showSpinner(); this.spinner.showSpinner();
let user: UserDTO | null = authUser?.users?.find(u => u.server == server.id) ?? null; let user: UserDTO | null = authUser?.users?.find(u => u.server == server.id) ?? null;
@ -69,6 +85,7 @@ export class ProfileComponent implements OnInit, OnDestroy {
return; return;
} }
this.author = user; this.author = user;
this.isModerator = user?.isModerator;
this.data.query<UserListQuery>(Queries.userProfile, { this.data.query<UserListQuery>(Queries.userProfile, {
serverId: this.server.id, serverId: this.server.id,
@ -90,7 +107,6 @@ export class ProfileComponent implements OnInit, OnDestroy {
).subscribe(result => { ).subscribe(result => {
this.user.userWarningCount = result.userWarningCount; this.user.userWarningCount = result.userWarningCount;
this.user.userWarnings = result.userWarnings; this.user.userWarnings = result.userWarnings;
console.log(result);
this.spinner.hideSpinner(); this.spinner.hideSpinner();
}); });
@ -99,12 +115,16 @@ export class ProfileComponent implements OnInit, OnDestroy {
}); });
} }
public toogleEditUser() {
this.isEditing = !this.isEditing;
}
public updateUser() { public updateUser() {
this.spinner.showSpinner();
this.spinner.showSpinner(); this.spinner.showSpinner();
this.data.mutation<UpdateUserMutationResult>(Mutations.updateUser, { this.data.mutation<UpdateUserMutationResult>(Mutations.updateUser, {
id: this.user.id, id: this.user.id,
xp: this.user.xp, xp: this.user.xp,
birthday: moment(this.user.birthday).format("DD.MM.YYYY"),
levelId: this.user.level?.id, levelId: this.user.level?.id,
userWarnings: this.user.userWarnings?.map(userWarning => { userWarnings: this.user.userWarnings?.map(userWarning => {
return { return {
@ -112,15 +132,17 @@ export class ProfileComponent implements OnInit, OnDestroy {
user: userWarning.user?.id ?? this.user.id, user: userWarning.user?.id ?? this.user.id,
description: userWarning.description, description: userWarning.description,
author: userWarning.author?.id ?? this.author?.id author: userWarning.author?.id ?? this.author?.id
} };
}) })
} }
).pipe(catchError(err => { ).pipe(catchError(err => {
this.spinner.hideSpinner(); this.spinner.hideSpinner();
this.isEditing = false;
return throwError(err); return throwError(err);
})).subscribe(_ => { })).subscribe(_ => {
this.spinner.hideSpinner(); 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.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.isEditing = false;
this.loadProfile(); this.loadProfile();
}); });
} }
@ -180,7 +202,7 @@ export class ProfileComponent implements OnInit, OnDestroy {
this.user.joinedVoiceChannels = []; this.user.joinedVoiceChannels = [];
} }
addNewUserWarning(table: Table) { public addNewUserWarning(table: Table) {
const newWarning: UserWarning = { const newWarning: UserWarning = {
description: "", description: "",
user: this.user user: this.user
@ -200,11 +222,11 @@ export class ProfileComponent implements OnInit, OnDestroy {
this.clonedUserWarnings[index] = { ...user }; this.clonedUserWarnings[index] = { ...user };
} }
deleteUserWarning(index: number) { public deleteUserWarning(index: number) {
this.user.userWarnings?.splice(index, 1); this.user.userWarnings?.splice(index, 1);
} }
editSaveUserWarning(value: any, index: number) { public editSaveUserWarning(value: any, index: number) {
this.isEditingNewUserWarning = false; this.isEditingNewUserWarning = false;
if (!value.value || !this.user.userWarnings || this.user.userWarnings[index] == this.clonedUserWarnings[index]) { if (!value.value || !this.user.userWarnings || this.user.userWarnings[index] == this.clonedUserWarnings[index]) {
return; return;
@ -213,7 +235,7 @@ export class ProfileComponent implements OnInit, OnDestroy {
delete this.clonedUserWarnings[index]; delete this.clonedUserWarnings[index];
} }
editCancelUserWarning(index: number) { public editCancelUserWarning(index: number) {
if (this.user.userWarnings) { if (this.user.userWarnings) {
this.user.userWarnings[index] = this.clonedUserWarnings[index]; this.user.userWarnings[index] = this.clonedUserWarnings[index];
} }

View File

@ -122,6 +122,7 @@
} }
}, },
"common": { "common": {
"edit": "Bearbeiten",
"user_warnings": "Verwarnungen", "user_warnings": "Verwarnungen",
"author": "Autor", "author": "Autor",
"404": "404 - Der Eintrag konnte nicht gefunden werden", "404": "404 - Der Eintrag konnte nicht gefunden werden",
@ -484,6 +485,9 @@
} }
}, },
"profile": { "profile": {
"message_count": "Anzahl Nachrichten",
"reaction_count": "Anzahl Reaktionen",
"birthday": "Geburtstag",
"achievements": { "achievements": {
"header": "Errungeschaften", "header": "Errungeschaften",
"time": "Erreicht am" "time": "Erreicht am"

View File

@ -1,7 +1,7 @@
{ {
"WebVersion": { "WebVersion": {
"Major": "1", "Major": "1",
"Minor": "1", "Minor": "2",
"Micro": "dev402" "Micro": "0"
} }
} }

View File

@ -94,8 +94,13 @@ p-table {
} }
} }
.p-dropdown { .content-row {
p-dropdown,
.p-dropdown,
p-calendar,
.p-calendar, {
width: 100% !important; width: 100% !important;
}
} }
.pi-sort-alt:before { .pi-sort-alt:before {

View File

@ -683,4 +683,77 @@
p-inputNumber { p-inputNumber {
background-color: $primaryBackgroundColor !important; background-color: $primaryBackgroundColor !important;
} }
p-calendar > span > button {
background-color: $primaryHeaderColor !important;
border: 1px solid $primaryHeaderColor !important;
&:focus {
box-shadow: none !important;
}
}
.p-calendar {
.p-datepicker:not(.p-datepicker-inline) {
background-color: $secondaryBackgroundColor !important;
}
.p-datepicker {
.p-datepicker-header {
color: $primaryHeaderColor !important;
background-color: $primaryBackgroundColor !important;
.p-datepicker-title .p-datepicker-year,
.p-datepicker-title .p-datepicker-month,
.p-datepicker .p-datepicker-header .p-datepicker-title .p-datepicker-month {
color: $primaryTextColor !important;
&:hover {
color: $primaryHeaderColor !important;
}
&:focus {
box-shadow: none !important;
}
}
}
}
table td > span {
color: $primaryTextColor !important;
&:hover {
color: $primaryHeaderColor !important;
background-color: $primaryBackgroundColor !important;
}
&:focus {
box-shadow: none !important;
}
}
table td.p-datepicker-today > span {
color: $primaryHeaderColor !important;
background-color: $primaryBackgroundColor !important;
}
table td > span.p-highlight {
color: $primaryHeaderColor !important;
background-color: $primaryBackgroundColor !important;
}
.p-yearpicker .p-yearpicker-year,
.p-monthpicker .p-monthpicker-month:not(.p-disabled):not(.p-highlight) {
color: $primaryTextColor !important;
background-color: $secondaryBackgroundColor !important;
&:hover {
color: $primaryHeaderColor !important;
}
&:focus {
box-shadow: none !important;
}
}
}
} }