Compare commits
13 Commits
36d2605655
...
266dacb301
Author | SHA1 | Date | |
---|---|---|---|
266dacb301 | |||
280cd9827d | |||
36998470e8 | |||
53c6bf4208 | |||
4e80e3ccb7 | |||
ba173a6743 | |||
84ff2a8a9b | |||
13bc38fea8 | |||
dfdf0555d7 | |||
39299eb11b | |||
dfb6751bf9 | |||
4a75635a55 | |||
f6b2739394 |
@ -11,7 +11,7 @@ jobs:
|
|||||||
container: git.sh-edraft.de/sh-edraft.de/act-runner:latest
|
container: git.sh-edraft.de/sh-edraft.de/act-runner:latest
|
||||||
steps:
|
steps:
|
||||||
- name: Shutdown stack
|
- name: Shutdown stack
|
||||||
run: docker stack rm sdb_test
|
run: docker stack rm sdb_staging
|
||||||
|
|
||||||
build-bot:
|
build-bot:
|
||||||
needs: pre-build
|
needs: pre-build
|
||||||
@ -124,6 +124,6 @@ jobs:
|
|||||||
portainer-username: "gitea_job"
|
portainer-username: "gitea_job"
|
||||||
portainer-password: "${{ secrets.docker_job }}"
|
portainer-password: "${{ secrets.docker_job }}"
|
||||||
portainer-endpoint: 2
|
portainer-endpoint: 2
|
||||||
name: sdb_test
|
name: sdb_staging
|
||||||
file: bot/docker/docker-compose.staging.yml
|
file: bot/docker/docker-compose.staging.yml
|
||||||
variables: '{}'
|
variables: '{}'
|
||||||
|
@ -33,7 +33,8 @@
|
|||||||
"cryptography==41.0.4",
|
"cryptography==41.0.4",
|
||||||
"discord==2.3.2",
|
"discord==2.3.2",
|
||||||
"bs4==0.0.1",
|
"bs4==0.0.1",
|
||||||
"lxml==4.9.3"
|
"lxml==4.9.3",
|
||||||
|
"python-valve==0.2.1"
|
||||||
],
|
],
|
||||||
"DevDependencies": [
|
"DevDependencies": [
|
||||||
"cpl-cli==2023.4.0.post3",
|
"cpl-cli==2023.4.0.post3",
|
||||||
|
@ -168,6 +168,13 @@ class User(TableABC):
|
|||||||
game_idents_repo: UserGameIdentRepositoryABC = services.get_service(UserGameIdentRepositoryABC)
|
game_idents_repo: UserGameIdentRepositoryABC = services.get_service(UserGameIdentRepositoryABC)
|
||||||
return game_idents_repo.get_user_game_idents_by_user_id(self.id)
|
return game_idents_repo.get_user_game_idents_by_user_id(self.id)
|
||||||
|
|
||||||
|
@property
|
||||||
|
@ServiceProviderABC.inject
|
||||||
|
def profile_picture_url(self, bot: DiscordBotServiceABC) -> str:
|
||||||
|
guild = bot.get_guild(self.server.discord_id)
|
||||||
|
user = guild.get_member(self._discord_id)
|
||||||
|
return None if user is None else user.avatar.url
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_select_all_string() -> str:
|
def get_select_all_string() -> str:
|
||||||
return str(
|
return str(
|
||||||
|
@ -11,6 +11,8 @@ type User implements TableWithHistoryQuery {
|
|||||||
level: Level
|
level: Level
|
||||||
activityScore: Int
|
activityScore: Int
|
||||||
|
|
||||||
|
profilePictureURL: String
|
||||||
|
|
||||||
joinedServers(filter: UserJoinedServerFilter, page: Page, sort: Sort): [UserJoinedServer]
|
joinedServers(filter: UserJoinedServerFilter, page: Page, sort: Sort): [UserJoinedServer]
|
||||||
joinedServerCount: Int
|
joinedServerCount: Int
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ from flask import request
|
|||||||
from bot_api.configuration.authentication_settings import AuthenticationSettings
|
from bot_api.configuration.authentication_settings import AuthenticationSettings
|
||||||
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||||
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
|
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
|
||||||
|
from bot_core.service.permission_service import PermissionService
|
||||||
from bot_data.abc.api_key_repository_abc import ApiKeyRepositoryABC
|
from bot_data.abc.api_key_repository_abc import ApiKeyRepositoryABC
|
||||||
from bot_data.abc.game_server_repository_abc import GameServerRepositoryABC
|
from bot_data.abc.game_server_repository_abc import GameServerRepositoryABC
|
||||||
from bot_data.abc.server_repository_abc import ServerRepositoryABC
|
from bot_data.abc.server_repository_abc import ServerRepositoryABC
|
||||||
@ -24,7 +25,6 @@ from bot_data.model.server_config import ServerConfig
|
|||||||
from bot_data.model.user_joined_game_server import UserJoinedGameServer
|
from bot_data.model.user_joined_game_server import UserJoinedGameServer
|
||||||
from bot_data.model.user_role_enum import UserRoleEnum
|
from bot_data.model.user_role_enum import UserRoleEnum
|
||||||
from bot_graphql.abc.query_abc import QueryABC
|
from bot_graphql.abc.query_abc import QueryABC
|
||||||
from bot_core.service.permission_service import PermissionService
|
|
||||||
|
|
||||||
|
|
||||||
class UserJoinedGameServerMutation(QueryABC):
|
class UserJoinedGameServerMutation(QueryABC):
|
||||||
@ -79,7 +79,10 @@ class UserJoinedGameServerMutation(QueryABC):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def resolve_user_joined(self, *_, input: dict):
|
def resolve_user_joined(self, *_, input: dict):
|
||||||
game_ident = self._user_game_idents.get_user_game_ident_by_ident(input["ident"])
|
game_ident = self._user_game_idents.find_user_game_ident_by_ident(input["ident"])
|
||||||
|
if game_ident is None:
|
||||||
|
return
|
||||||
|
|
||||||
user = game_ident.user
|
user = game_ident.user
|
||||||
|
|
||||||
settings: ServerConfig = self._config.get_configuration(f"ServerConfig_{user.server.discord_id}")
|
settings: ServerConfig = self._config.get_configuration(f"ServerConfig_{user.server.discord_id}")
|
||||||
|
@ -4,6 +4,7 @@ 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_api.route.route import Route
|
||||||
|
from bot_core.service.permission_service import PermissionService
|
||||||
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
|
||||||
@ -13,7 +14,6 @@ from bot_data.model.user_role_enum import UserRoleEnum
|
|||||||
from bot_graphql.abc.query_abc import QueryABC
|
from bot_graphql.abc.query_abc import QueryABC
|
||||||
from modules.base.service.user_warnings_service import UserWarningsService
|
from modules.base.service.user_warnings_service import UserWarningsService
|
||||||
from modules.level.service.level_service import LevelService
|
from modules.level.service.level_service import LevelService
|
||||||
from bot_core.service.permission_service import PermissionService
|
|
||||||
|
|
||||||
|
|
||||||
class UserMutation(QueryABC):
|
class UserMutation(QueryABC):
|
||||||
@ -50,7 +50,7 @@ class UserMutation(QueryABC):
|
|||||||
member = self._bot.get_guild(user.server.discord_id).get_member(
|
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
|
auth_user.users.where(lambda x: x.server.id == user.server.id).single().discord_id
|
||||||
)
|
)
|
||||||
if member.id != user.discord_id:
|
if self._permissions.is_member_moderator(member) or 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
|
||||||
|
@ -88,3 +88,4 @@ class UserQuery(DataQueryWithHistoryABC):
|
|||||||
|
|
||||||
self.set_field("server", lambda user, *_: user.server)
|
self.set_field("server", lambda user, *_: user.server)
|
||||||
self.set_field("leftServer", lambda user, *_: user.left_server)
|
self.set_field("leftServer", lambda user, *_: user.left_server)
|
||||||
|
self.set_field("profilePictureURL", lambda user, *_: user.profile_picture_url)
|
||||||
|
@ -62,8 +62,8 @@ class BaseModule(ModuleABC):
|
|||||||
services.add_transient(BaseReactionHandler)
|
services.add_transient(BaseReactionHandler)
|
||||||
services.add_singleton(EventService)
|
services.add_singleton(EventService)
|
||||||
services.add_transient(UserWarningsService)
|
services.add_transient(UserWarningsService)
|
||||||
services.add_singleton(TaskABC, BirthdayWatcher)
|
services.add_transient(TaskABC, BirthdayWatcher)
|
||||||
services.add_singleton(TaskABC, ScheduledEventsWatcher)
|
services.add_transient(TaskABC, ScheduledEventsWatcher)
|
||||||
|
|
||||||
# forms
|
# forms
|
||||||
services.add_transient(BugReportForm)
|
services.add_transient(BugReportForm)
|
||||||
|
@ -9,6 +9,7 @@ from cpl_translation import TranslatePipe
|
|||||||
from discord import app_commands
|
from discord import app_commands
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
from discord.ext.commands import Context
|
from discord.ext.commands import Context
|
||||||
|
from valve.steam.id import SteamID
|
||||||
|
|
||||||
from bot_core.abc.client_utils_abc import ClientUtilsABC
|
from bot_core.abc.client_utils_abc import ClientUtilsABC
|
||||||
from bot_core.abc.message_service_abc import MessageServiceABC
|
from bot_core.abc.message_service_abc import MessageServiceABC
|
||||||
@ -50,6 +51,17 @@ class RegisterGroup(DiscordCommandABC):
|
|||||||
|
|
||||||
self._logger.trace(__name__, f"Loaded command service: {type(self).__name__}")
|
self._logger.trace(__name__, f"Loaded command service: {type(self).__name__}")
|
||||||
|
|
||||||
|
async def _game_server_autocomplete(
|
||||||
|
self, interaction: discord.Interaction, current: str
|
||||||
|
) -> TList[app_commands.Choice[str]]:
|
||||||
|
server = self._servers.get_server_by_discord_id(interaction.guild.id)
|
||||||
|
game_servers = self._game_server.get_game_servers_by_server_id(server.id)
|
||||||
|
|
||||||
|
return [
|
||||||
|
app_commands.Choice(name=gs.name, value=gs.id)
|
||||||
|
for gs in self._client_utils.get_auto_complete_list(game_servers, current, lambda x: x.name)
|
||||||
|
]
|
||||||
|
|
||||||
@commands.hybrid_group()
|
@commands.hybrid_group()
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def register(self, ctx: Context):
|
async def register(self, ctx: Context):
|
||||||
@ -101,10 +113,44 @@ class RegisterGroup(DiscordCommandABC):
|
|||||||
async def game_server_autocomplete(
|
async def game_server_autocomplete(
|
||||||
self, interaction: discord.Interaction, current: str
|
self, interaction: discord.Interaction, current: str
|
||||||
) -> TList[app_commands.Choice[str]]:
|
) -> TList[app_commands.Choice[str]]:
|
||||||
server = self._servers.get_server_by_discord_id(interaction.guild.id)
|
return await self._game_server_autocomplete(interaction, current)
|
||||||
game_servers = self._game_server.get_game_servers_by_server_id(server.id)
|
|
||||||
|
|
||||||
return [
|
@register.command()
|
||||||
app_commands.Choice(name=gs.name, value=gs.id)
|
@commands.guild_only()
|
||||||
for gs in self._client_utils.get_auto_complete_list(game_servers, current, lambda x: x.name)
|
@CommandChecks.check_is_ready()
|
||||||
]
|
@CommandChecks.check_is_member_moderator()
|
||||||
|
async def gmod(self, ctx: Context, member: discord.Member, game_server: int, steam_url: str):
|
||||||
|
self._logger.debug(__name__, f"Received command register gmod {ctx}")
|
||||||
|
|
||||||
|
steam_id = None
|
||||||
|
try:
|
||||||
|
self._logger.debug(__name__, f"Try to get steam id for {id}")
|
||||||
|
steam_id = SteamID.from_community_url(steam_url)
|
||||||
|
except Exception as e:
|
||||||
|
self._logger.error(__name__, f"Get steam id for {steam_id} failed", e)
|
||||||
|
await self._message_service.send_interaction_msg(
|
||||||
|
ctx.interaction, self._t.transform("modules.base.register.not_found")
|
||||||
|
)
|
||||||
|
|
||||||
|
if steam_id is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
server = self._servers.get_server_by_discord_id(ctx.guild.id)
|
||||||
|
user = self._users.get_user_by_discord_id_and_server_id(member.id, server.id)
|
||||||
|
gs = self._game_server.get_game_server_by_id(game_server)
|
||||||
|
|
||||||
|
game_ident = UserGameIdent(user, gs, steam_id)
|
||||||
|
self._user_game_ident.add_user_game_ident(game_ident)
|
||||||
|
self._db.save_changes()
|
||||||
|
|
||||||
|
await self._message_service.send_interaction_msg(
|
||||||
|
ctx.interaction, self._t.transform("modules.base.register.success")
|
||||||
|
)
|
||||||
|
|
||||||
|
self._logger.trace(__name__, f"Finished register gmod command")
|
||||||
|
|
||||||
|
@gmod.autocomplete("game_server")
|
||||||
|
async def game_server_autocomplete(
|
||||||
|
self, interaction: discord.Interaction, current: str
|
||||||
|
) -> TList[app_commands.Choice[str]]:
|
||||||
|
return await self._game_server_autocomplete(interaction, current)
|
||||||
|
@ -1,22 +1,34 @@
|
|||||||
|
from cpl_core.configuration import ConfigurationABC
|
||||||
from cpl_discord.command import DiscordCommandABC
|
from cpl_discord.command import DiscordCommandABC
|
||||||
|
from cpl_translation import TranslatePipe
|
||||||
from discord.ext import commands
|
from discord.ext import commands
|
||||||
from discord.ext.commands import Context
|
from discord.ext.commands import Context
|
||||||
|
|
||||||
|
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||||
|
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
|
||||||
from bot_core.helper.command_checks import CommandChecks
|
from bot_core.helper.command_checks import CommandChecks
|
||||||
from bot_core.logging.command_logger import CommandLogger
|
from bot_core.logging.command_logger import CommandLogger
|
||||||
|
from bot_core.service.message_service import MessageService
|
||||||
|
from bot_data.model.server_config import ServerConfig
|
||||||
from modules.base.service.event_service import EventService
|
from modules.base.service.event_service import EventService
|
||||||
|
|
||||||
|
|
||||||
class ScheduledEventsCommand(DiscordCommandABC):
|
class ScheduledEventsCommand(DiscordCommandABC):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
config: ConfigurationABC,
|
||||||
logger: CommandLogger,
|
logger: CommandLogger,
|
||||||
events: EventService,
|
events: EventService,
|
||||||
|
message_service: MessageService,
|
||||||
|
t: TranslatePipe,
|
||||||
):
|
):
|
||||||
DiscordCommandABC.__init__(self)
|
DiscordCommandABC.__init__(self)
|
||||||
|
|
||||||
|
self._config = config
|
||||||
self._logger = logger
|
self._logger = logger
|
||||||
self._events = events
|
self._events = events
|
||||||
|
self._message_service = message_service
|
||||||
|
self._t = t
|
||||||
|
|
||||||
self._logger.trace(__name__, f"Loaded command service: {type(self).__name__}")
|
self._logger.trace(__name__, f"Loaded command service: {type(self).__name__}")
|
||||||
|
|
||||||
@ -30,6 +42,12 @@ class ScheduledEventsCommand(DiscordCommandABC):
|
|||||||
@CommandChecks.check_is_ready()
|
@CommandChecks.check_is_ready()
|
||||||
@CommandChecks.check_is_member_moderator()
|
@CommandChecks.check_is_member_moderator()
|
||||||
async def reload(self, ctx: Context):
|
async def reload(self, ctx: Context):
|
||||||
|
settings: ServerConfig = self._config.get_configuration(f"ServerConfig_{ctx.guild.id}")
|
||||||
|
if not FeatureFlagsSettings.get_flag_from_dict(settings.feature_flags, FeatureFlagsEnum.scheduled_events):
|
||||||
|
self._logger.debug(__name__, f"Skipping scheduled event check for {ctx.guild.id}. Feature is disabled.")
|
||||||
|
await self._message_service.send_ctx_msg(ctx, self._t.transform("common.feature_not_activated"))
|
||||||
|
return
|
||||||
|
|
||||||
self._logger.debug(__name__, "Running scheduled-events reload")
|
self._logger.debug(__name__, "Running scheduled-events reload")
|
||||||
try:
|
try:
|
||||||
await self._events.check_and_create_scheduled_events(ctx.guild)
|
await self._events.check_and_create_scheduled_events(ctx.guild)
|
||||||
|
@ -9,7 +9,7 @@ from cpl_core.database.context import DatabaseContextABC
|
|||||||
from cpl_core.logging import LoggerABC
|
from cpl_core.logging import LoggerABC
|
||||||
from cpl_discord.container import Guild
|
from cpl_discord.container import Guild
|
||||||
from cpl_query.extension import List
|
from cpl_query.extension import List
|
||||||
from discord import PrivacyLevel
|
from discord import PrivacyLevel, EventStatus
|
||||||
from discord.scheduled_event import ScheduledEvent as DiscordEvent
|
from discord.scheduled_event import ScheduledEvent as DiscordEvent
|
||||||
|
|
||||||
from bot_data.abc.scheduled_event_repository_abc import ScheduledEventRepositoryABC
|
from bot_data.abc.scheduled_event_repository_abc import ScheduledEventRepositoryABC
|
||||||
@ -106,6 +106,7 @@ class EventService:
|
|||||||
lambda x: x.name == scheduled_event.name
|
lambda x: x.name == scheduled_event.name
|
||||||
and x.description == scheduled_event.description
|
and x.description == scheduled_event.description
|
||||||
and x.entity_type == scheduled_event.entity_type
|
and x.entity_type == scheduled_event.entity_type
|
||||||
|
and x.status == EventStatus.scheduled
|
||||||
)
|
)
|
||||||
if from_guild.count() != 0:
|
if from_guild.count() != 0:
|
||||||
self._logger.debug(__name__, f"Event {scheduled_event.name} already exists on discord server")
|
self._logger.debug(__name__, f"Event {scheduled_event.name} already exists on discord server")
|
||||||
|
@ -37,7 +37,7 @@ class BirthdayWatcher(TaskABC):
|
|||||||
if not self._is_maintenance():
|
if not self._is_maintenance():
|
||||||
self.watch.start()
|
self.watch.start()
|
||||||
|
|
||||||
@tasks.loop(time=datetime.time(hour=8, minute=0))
|
@tasks.loop(time=datetime.time(hour=8, minute=0), reconnect=True)
|
||||||
async def watch(self):
|
async def watch(self):
|
||||||
self._logger.info(__name__, "Watching birthdays")
|
self._logger.info(__name__, "Watching birthdays")
|
||||||
try:
|
try:
|
||||||
|
@ -5,9 +5,12 @@ from cpl_translation import TranslatePipe
|
|||||||
from discord.ext import tasks
|
from discord.ext import tasks
|
||||||
|
|
||||||
from bot_core.abc.task_abc import TaskABC
|
from bot_core.abc.task_abc import TaskABC
|
||||||
|
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
|
||||||
|
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
|
||||||
from bot_core.logging.task_logger import TaskLogger
|
from bot_core.logging.task_logger import TaskLogger
|
||||||
from bot_core.service.message_service import MessageService
|
from bot_core.service.message_service import MessageService
|
||||||
from bot_data.abc.server_repository_abc import ServerRepositoryABC
|
from bot_data.abc.server_repository_abc import ServerRepositoryABC
|
||||||
|
from bot_data.model.server_config import ServerConfig
|
||||||
from modules.base.service.event_service import EventService
|
from modules.base.service.event_service import EventService
|
||||||
|
|
||||||
|
|
||||||
@ -37,11 +40,18 @@ class ScheduledEventsWatcher(TaskABC):
|
|||||||
if not self._is_maintenance():
|
if not self._is_maintenance():
|
||||||
self.watch.start()
|
self.watch.start()
|
||||||
|
|
||||||
@tasks.loop(hours=12)
|
@tasks.loop(hours=3, reconnect=True)
|
||||||
async def watch(self):
|
async def watch(self):
|
||||||
self._logger.info(__name__, "Watching scheduled events")
|
self._logger.info(__name__, "Watching scheduled events")
|
||||||
try:
|
try:
|
||||||
for guild in self._bot.guilds:
|
for guild in self._bot.guilds:
|
||||||
|
settings: ServerConfig = self._config.get_configuration(f"ServerConfig_{guild.id}")
|
||||||
|
if not FeatureFlagsSettings.get_flag_from_dict(
|
||||||
|
settings.feature_flags, FeatureFlagsEnum.scheduled_events
|
||||||
|
):
|
||||||
|
self._logger.debug(__name__, f"Skipping scheduled event check for {guild.id}. Feature is disabled.")
|
||||||
|
continue
|
||||||
|
|
||||||
await self._events.check_and_create_scheduled_events(guild)
|
await self._events.check_and_create_scheduled_events(guild)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self._logger.error(__name__, f"Watching scheduled events failed", e)
|
self._logger.error(__name__, f"Watching scheduled events failed", e)
|
||||||
|
@ -17,6 +17,6 @@ class SteamSpecialOffersModule(ModuleABC):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC):
|
def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC):
|
||||||
services.add_singleton(TaskABC, SteamOfferWatcher)
|
services.add_transient(TaskABC, SteamOfferWatcher)
|
||||||
# commands
|
# commands
|
||||||
# events
|
# events
|
||||||
|
@ -187,7 +187,7 @@ class SteamOfferWatcher(TaskABC):
|
|||||||
self._logger.trace(__name__, "Finished watching")
|
self._logger.trace(__name__, "Finished watching")
|
||||||
return offers_for_notifications
|
return offers_for_notifications
|
||||||
|
|
||||||
@tasks.loop(hours=4)
|
@tasks.loop(hours=4, reconnect=True)
|
||||||
async def watch(self):
|
async def watch(self):
|
||||||
self._logger.info(__name__, "Watching steam special offers")
|
self._logger.info(__name__, "Watching steam special offers")
|
||||||
try:
|
try:
|
||||||
|
@ -36,6 +36,8 @@ export interface User extends DataWithHistory {
|
|||||||
|
|
||||||
userWarningCount?: number;
|
userWarningCount?: number;
|
||||||
userWarnings?: UserWarning[];
|
userWarnings?: UserWarning[];
|
||||||
|
|
||||||
|
profilePictureURL?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserFilter {
|
export interface UserFilter {
|
||||||
|
@ -406,6 +406,7 @@ export class Queries {
|
|||||||
joinedOn
|
joinedOn
|
||||||
leavedOn
|
leavedOn
|
||||||
}
|
}
|
||||||
|
profilePictureURL
|
||||||
|
|
||||||
createdAt
|
createdAt
|
||||||
modifiedAt
|
modifiedAt
|
||||||
|
@ -40,6 +40,7 @@ import { TabViewModule } from "primeng/tabview";
|
|||||||
import { RadioButtonModule } from "primeng/radiobutton";
|
import { RadioButtonModule } from "primeng/radiobutton";
|
||||||
import { InputTextareaModule } from "primeng/inputtextarea";
|
import { InputTextareaModule } from "primeng/inputtextarea";
|
||||||
import { InputMaskModule } from "primeng/inputmask";
|
import { InputMaskModule } from "primeng/inputmask";
|
||||||
|
import { KnobModule } from "primeng/knob";
|
||||||
|
|
||||||
|
|
||||||
const PrimeNGModules = [
|
const PrimeNGModules = [
|
||||||
@ -73,7 +74,8 @@ const PrimeNGModules = [
|
|||||||
TabViewModule,
|
TabViewModule,
|
||||||
RadioButtonModule,
|
RadioButtonModule,
|
||||||
InputTextareaModule,
|
InputTextareaModule,
|
||||||
InputMaskModule
|
InputMaskModule,
|
||||||
|
KnobModule
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@ -2,119 +2,95 @@
|
|||||||
{{'view.server.profile.header' | translate}}
|
{{'view.server.profile.header' | translate}}
|
||||||
</h1>
|
</h1>
|
||||||
<div class="content-wrapper">
|
<div class="content-wrapper">
|
||||||
<div class="content-header">
|
|
||||||
<h2>
|
|
||||||
{{user.name}}
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<div class="content-row">
|
<div class="content-row">
|
||||||
<div class="content-column">
|
<div class="content-column">
|
||||||
<div class="content-data-name">{{'common.id' | translate}}:</div>
|
<div class="logo">
|
||||||
<div class="content-data-value">{{user.id}}</div>
|
<img *ngIf="user ? user.profilePictureURL : ''" [src]="user ? user.profilePictureURL : ''">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="content-column">
|
<div class="content">
|
||||||
<div class="content-data-name">{{'common.discord_id' | translate}}:</div>
|
<div class="content-column">
|
||||||
<div class="content-data-value">{{user.discordId}}</div>
|
<h1>{{user.name}}</h1>
|
||||||
|
</div>
|
||||||
|
<div class="content-column">
|
||||||
|
<div class="content-data-name">{{'common.id' | translate}}:</div>
|
||||||
|
<div class="content-data-value">{{user.id}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="content-column">
|
||||||
|
<div class="content-data-name">{{'common.discord_id' | translate}}:</div>
|
||||||
|
<div class="content-data-value">{{user.discordId}}</div>
|
||||||
|
</div>
|
||||||
|
<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 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 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 class="content-divider"></div>
|
||||||
|
<div class="content-column">
|
||||||
|
<div class="content-data-name">{{'view.server.profile.left_server' | translate}}:</div>
|
||||||
|
<div class="content-data-value">{{user.leftServer | bool}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="content-column">
|
||||||
|
<div class="content-data-name">{{'common.created_at' | translate}}:</div>
|
||||||
|
<div class="content-data-value">{{user.createdAt | date:'dd.MM.yyyy HH:mm:ss'}}</div>
|
||||||
|
</div>
|
||||||
|
<div class="content-column">
|
||||||
|
<div class="content-data-name">{{'common.modified_at' | translate}}:</div>
|
||||||
|
<div class="content-data-value">{{user.modifiedAt | date:'dd.MM.yyyy HH:mm:ss'}}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="content">
|
||||||
|
<div class="content-column">
|
||||||
|
<div class="content-data-name" style="flex-direction: column; justify-content: start;">
|
||||||
|
<p-knob valueTemplate="{value} {{'view.server.profile.xp' | translate}}" [(ngModel)]="user.xp" [size]="150"
|
||||||
|
[strokeWidth]="10"
|
||||||
|
[max]="maxXp" valueColor="SlateGray" rangeColor="MediumTurquoise"
|
||||||
|
[readonly]="isModerator && !isEditing"></p-knob>
|
||||||
|
</div>
|
||||||
|
<div class="content-data-value" style="flex-direction: column; justify-content: start;">
|
||||||
|
<p-knob valueTemplate="Lvl {value}" [(ngModel)]="activeLevelIndex"
|
||||||
|
[size]="150"
|
||||||
|
[strokeWidth]="10"
|
||||||
|
[min]="1"
|
||||||
|
[max]="levels ? levels.length : 0" valueColor="SlateGray" rangeColor="MediumTurquoise"
|
||||||
|
[readonly]="isModerator && !isEditing"></p-knob>
|
||||||
|
|
||||||
<div class="content-row">
|
<div *ngIf="user.level">
|
||||||
<div class="content-column">
|
{{user.level.name}}
|
||||||
<div class="content-data-name">{{'view.server.profile.name' | translate}}:</div>
|
</div>
|
||||||
<div class="content-data-value">{{user.name}}</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="content-divider"></div>
|
||||||
|
<div class="content-column">
|
||||||
<div class="content-row">
|
<div class="content-data-name">{{'common.ontime' | translate}}:</div>
|
||||||
<div class="content-column">
|
<div class="content-data-value">{{user.ontime}}</div>
|
||||||
<div class="content-data-name">{{'view.server.profile.xp' | translate}}:</div>
|
</div>
|
||||||
<div *ngIf="!isEditing" class="content-data-value">{{user.xp}}</div>
|
<div class="content-column">
|
||||||
<div *ngIf="isModerator && isEditing" class="content-data-value"><input class="table-edit-input" pInputText
|
<div class="content-data-name">{{'common.game_ontime' | translate}}:</div>
|
||||||
min="0" type="number"
|
<div class="content-data-value">{{user.gameOntime}}</div>
|
||||||
[(ngModel)]="user.xp"></div>
|
</div>
|
||||||
</div>
|
<div class="content-column">
|
||||||
</div>
|
<div class="content-data-name">{{'common.activity' | translate}}:</div>
|
||||||
|
<div class="content-data-value">{{user.activityScore}}</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>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="content-row">
|
|
||||||
<div class="content-column">
|
|
||||||
<div class="content-data-name">{{'common.ontime' | translate}}:</div>
|
|
||||||
<div class="content-data-value">{{user.ontime}}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="content-row">
|
|
||||||
<div class="content-column">
|
|
||||||
<div class="content-data-name">{{'common.game_ontime' | translate}}:</div>
|
|
||||||
<div class="content-data-value">{{user.gameOntime}}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="content-row">
|
|
||||||
<div class="content-column">
|
|
||||||
<div class="content-data-name">{{'common.activity' | translate}}:</div>
|
|
||||||
<div class="content-data-value">{{user.activityScore}}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="content-row">
|
|
||||||
<div class="content-column">
|
|
||||||
<div class="content-data-name">{{'view.server.profile.level' | translate}}:</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 class="content-row">
|
|
||||||
<div class="content-column">
|
|
||||||
<div class="content-data-name">{{'view.server.profile.left_server' | translate}}:</div>
|
|
||||||
<div class="content-data-value">{{user.leftServer | bool}}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="content-row">
|
|
||||||
<div class="content-column">
|
|
||||||
<div class="content-data-name">{{'common.created_at' | translate}}:</div>
|
|
||||||
<div class="content-data-value">{{user.createdAt | date:'dd.MM.yyyy HH:mm:ss'}}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="content-row">
|
|
||||||
<div class="content-column">
|
|
||||||
<div class="content-data-name">{{'common.modified_at' | translate}}:</div>
|
|
||||||
<div class="content-data-value">{{user.modifiedAt | date:'dd.MM.yyyy HH:mm:ss'}}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
<div>
|
<div>
|
||||||
<div class="content-divider"></div>
|
<div class="content-divider"></div>
|
||||||
<p-table #dt [value]="(user.userWarnings ?? [])" [responsive]="true" responsiveLayout="stack"
|
<p-table #dt [value]="(user.userWarnings ?? [])" [responsive]="true" responsiveLayout="stack"
|
||||||
@ -199,7 +175,8 @@
|
|||||||
<button *ngIf="!editing" pButton type="button" class="btn danger-icon-btn" icon="pi pi-trash"
|
<button *ngIf="!editing" pButton type="button" class="btn danger-icon-btn" icon="pi pi-trash"
|
||||||
(click)="deleteUserWarning(ri)"></button>
|
(click)="deleteUserWarning(ri)"></button>
|
||||||
|
|
||||||
<button *ngIf="editing" pButton type="button" pSaveEditableRow class="btn icon-btn" icon="pi pi-check"
|
<button *ngIf="editing" pButton type="button" pSaveEditableRow class="btn icon-btn"
|
||||||
|
icon="pi pi-check"
|
||||||
(click)="editSaveUserWarning(value, ri)"></button>
|
(click)="editSaveUserWarning(value, ri)"></button>
|
||||||
<button *ngIf="editing" pButton type="button" pCancelEditableRow class="btn danger-icon-btn"
|
<button *ngIf="editing" pButton type="button" pCancelEditableRow class="btn danger-icon-btn"
|
||||||
icon="pi pi-times"
|
icon="pi pi-times"
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
.logo {
|
||||||
|
img {
|
||||||
|
width: 12.5vw;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
@ -15,10 +15,10 @@ import { catchError, takeUntil } from "rxjs/operators";
|
|||||||
import { Table } from "primeng/table";
|
import { Table } from "primeng/table";
|
||||||
import { UpdateUserMutationResult } from "../../../../models/graphql/result.model";
|
import { UpdateUserMutationResult } from "../../../../models/graphql/result.model";
|
||||||
import { Mutations } from "../../../../models/graphql/mutations.model";
|
import { Mutations } from "../../../../models/graphql/mutations.model";
|
||||||
import { MenuItem } from "primeng/api";
|
|
||||||
import { UserWarning } from "../../../../models/data/user_warning.model";
|
import { UserWarning } from "../../../../models/data/user_warning.model";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { SidebarService } from "../../../../services/sidebar/sidebar.service";
|
import { SidebarService } from "../../../../services/sidebar/sidebar.service";
|
||||||
|
import { Level } from "../../../../models/data/level.model";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-profile",
|
selector: "app-profile",
|
||||||
@ -28,7 +28,7 @@ import { SidebarService } from "../../../../services/sidebar/sidebar.service";
|
|||||||
export class ProfileComponent implements OnInit, OnDestroy {
|
export class ProfileComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
user: User = { createdAt: "", modifiedAt: "" };
|
user: User = { createdAt: "", modifiedAt: "" };
|
||||||
levels!: MenuItem[];
|
levels!: { label: string, value: Level }[];
|
||||||
private server: Server = {};
|
private server: Server = {};
|
||||||
private author?: UserDTO;
|
private author?: UserDTO;
|
||||||
private clonedUserWarnings: UserWarning[] = [];
|
private clonedUserWarnings: UserWarning[] = [];
|
||||||
@ -36,6 +36,21 @@ export class ProfileComponent implements OnInit, OnDestroy {
|
|||||||
public isEditing: boolean = false;
|
public isEditing: boolean = false;
|
||||||
public isModerator: boolean = false;
|
public isModerator: boolean = false;
|
||||||
public hasTechnicianAccess: boolean = false;
|
public hasTechnicianAccess: boolean = false;
|
||||||
|
public maxXp: number = 0;
|
||||||
|
private _activeLevelIndex: number = 0;
|
||||||
|
|
||||||
|
public get activeLevelIndex(): number {
|
||||||
|
return this._activeLevelIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set activeLevelIndex(value: number) {
|
||||||
|
if (value < 1) {
|
||||||
|
value = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._activeLevelIndex = value;
|
||||||
|
this.user.level = this.levels.map(x => x.value)[value - 1];
|
||||||
|
}
|
||||||
|
|
||||||
private unsubscriber = new Subject<void>();
|
private unsubscriber = new Subject<void>();
|
||||||
|
|
||||||
@ -48,7 +63,7 @@ export class ProfileComponent implements OnInit, OnDestroy {
|
|||||||
private toast: ToastService,
|
private toast: ToastService,
|
||||||
private translate: TranslateService,
|
private translate: TranslateService,
|
||||||
private toastService: ToastService,
|
private toastService: ToastService,
|
||||||
private sidebarService: SidebarService,
|
private sidebarService: SidebarService
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,7 +89,7 @@ export class ProfileComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
).subscribe(data => {
|
).subscribe(data => {
|
||||||
this.levels = data.levels.map(level => {
|
this.levels = data.levels.map(level => {
|
||||||
return { label: level.name, value: level };
|
return { label: level.name ?? "", value: level };
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -100,6 +115,11 @@ export class ProfileComponent implements OnInit, OnDestroy {
|
|||||||
this.router.navigate([`/server/${server.id}`]);
|
this.router.navigate([`/server/${server.id}`]);
|
||||||
}
|
}
|
||||||
this.user = users.users[0];
|
this.user = users.users[0];
|
||||||
|
const minXps = this.levels.reverse().map(x => x.value.minXp ?? 0);
|
||||||
|
this.maxXp = minXps.filter(x => this.user.xp && x > this.user.xp)[0] ?? this.user.xp;
|
||||||
|
if (this.user.level) {
|
||||||
|
this.activeLevelIndex = this.levels.map(l => l.value.id).indexOf(this.user.level.id) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
this.data.query<UserWarningQuery>(Queries.userProfileWarnings, {
|
this.data.query<UserWarningQuery>(Queries.userProfileWarnings, {
|
||||||
serverId: this.server.id,
|
serverId: this.server.id,
|
||||||
@ -128,7 +148,7 @@ export class ProfileComponent implements OnInit, OnDestroy {
|
|||||||
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"),
|
birthday: moment(this.user.birthday, "DD.MM.YYYY").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 {
|
||||||
|
@ -541,6 +541,7 @@
|
|||||||
},
|
},
|
||||||
"left_server": "Hat Server verlassen",
|
"left_server": "Hat Server verlassen",
|
||||||
"level": "Level",
|
"level": "Level",
|
||||||
|
"lvl": "Lvl",
|
||||||
"message_count": "Anzahl Nachrichten",
|
"message_count": "Anzahl Nachrichten",
|
||||||
"minecraft_id": "Minecraft Id",
|
"minecraft_id": "Minecraft Id",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
|
@ -541,6 +541,7 @@
|
|||||||
},
|
},
|
||||||
"left_server": "Leaved server",
|
"left_server": "Leaved server",
|
||||||
"level": "Level",
|
"level": "Level",
|
||||||
|
"lvl": "Lvl",
|
||||||
"message_count": "Message count",
|
"message_count": "Message count",
|
||||||
"minecraft_id": "Minecraft Id",
|
"minecraft_id": "Minecraft Id",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
|
@ -177,40 +177,40 @@ header {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
margin: 5px 0;
|
margin: 5px 0;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
.content-column {
|
.content-column {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-data-name {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
font-size: 18px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-data-value {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
font-size: 18px;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
input {
|
||||||
|
width: 100% !important;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.content-data-name {
|
.content-data-value-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-data-value {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
font-size: 18px;
|
|
||||||
flex: 1;
|
|
||||||
|
|
||||||
input {
|
|
||||||
width: 100% !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-data-value-row {
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-divider {
|
.content-divider {
|
||||||
|
@ -814,4 +814,22 @@
|
|||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p-knob {
|
||||||
|
.p-knob {
|
||||||
|
svg {
|
||||||
|
.p-knob-value {
|
||||||
|
stroke: $primaryHeaderColor !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-knob-range {
|
||||||
|
stroke: $secondaryBackgroundColor !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-knob-text {
|
||||||
|
font-size: 1rem !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user