38 Commits

Author SHA1 Message Date
9bad75e7c2 Temporarily removed server stats
All checks were successful
Deploy prod on push / on-push-deploy_sh-edraft (push) Successful in 5m7s
Deploy staging on push / on-push-deploy_sh-edraft (push) Successful in 3m16s
Deploy dev on push / on-push-deploy_sh-edraft (push) Successful in 3m11s
2023-11-07 00:28:22 +01:00
7358b67072 Fixed server loading in web
All checks were successful
Deploy prod on push / on-push-deploy_sh-edraft (push) Successful in 5m6s
2023-11-07 00:06:17 +01:00
1b0ba01258 Fixed gql types
All checks were successful
Deploy dev on push / on-push-deploy_sh-edraft (push) Successful in 3m52s
Deploy staging on push / on-push-deploy_sh-edraft (push) Successful in 3m3s
Deploy prod on push / on-push-deploy_sh-edraft (push) Successful in 3m3s
2023-11-06 23:29:11 +01:00
6fc5ee3ed3 Merge pull request 'Fixed DeleteUserJoinedServer' (#431) from staging into master
All checks were successful
Deploy prod on push / on-push-deploy_sh-edraft (push) Successful in 3m18s
Reviewed-on: #431
2023-11-06 21:43:36 +01:00
099707446d Merge branch 'master' into staging
Some checks reported warnings
Deploy staging on push / on-push-deploy_sh-edraft (push) Has been cancelled
2023-11-06 21:43:25 +01:00
d6b7fd73df Fixed DeleteUserJoinedServer
All checks were successful
Deploy staging on push / on-push-deploy_sh-edraft (push) Successful in 4m2s
2023-11-06 21:42:52 +01:00
6bfb0ddbf9 Merge pull request 'Fixed remove user process' (#429) from staging into master
All checks were successful
Deploy prod on push / on-push-deploy_sh-edraft (push) Successful in 3m10s
Reviewed-on: #429
Reviewed-by: edraft-dev <dev.sven.heidemann@sh-edraft.de>
2023-11-06 21:34:29 +01:00
e1b76fa628 Possibly fixed rate limit in actions
All checks were successful
Deploy staging on push / on-push-deploy_sh-edraft (push) Successful in 3m0s
2023-11-06 21:26:25 +01:00
e6f98def6a Possibly fixed rate limit in actions 2023-11-06 21:26:25 +01:00
590479eee2 Set migration only on prod 2023-11-06 21:26:25 +01:00
d25305be4a Merge pull request 'Fixed migration scripts' (#427) from staging into master
All checks were successful
Deploy prod on push / on-push-deploy_sh-edraft (push) Successful in 3m24s
Reviewed-on: #427
Reviewed-by: edraft-dev <dev.sven.heidemann@sh-edraft.de>
2023-11-06 20:26:57 +01:00
2889a97f17 Merge branch 'master' into staging
All checks were successful
Deploy staging on push / on-push-deploy_sh-edraft (push) Successful in 4m21s
2023-11-06 20:25:59 +01:00
b540821a32 Fixed migration scripts
Some checks reported warnings
Deploy staging on push / on-push-deploy_sh-edraft (push) Has been cancelled
2023-11-06 20:25:10 +01:00
5b43b72838 Merge pull request 'staging into master' (#426) from staging into master
All checks were successful
Deploy prod on push / on-push-deploy_sh-edraft (push) Successful in 3m30s
Reviewed-on: #426
Reviewed-by: edraft-dev <dev.sven.heidemann@sh-edraft.de>
2023-11-06 20:06:33 +01:00
46cd33d194 Updated gitea actions
All checks were successful
Deploy staging on push / on-push-deploy_sh-edraft (push) Successful in 4m11s
2023-11-06 20:05:10 +01:00
1287829bdf Set migration only on prod
Some checks failed
Deploy staging on push / on-push-deploy_sh-edraft (push) Failing after 5s
2023-11-06 19:41:52 +01:00
c91c8b2bcd Updated config
Some checks failed
Deploy staging on push / on-push-deploy_sh-edraft (push) Failing after 5s
2023-11-06 19:25:40 +01:00
a5b4c4cd66 Updated config
All checks were successful
Deploy staging on push / on-push-deploy_sh-edraft (push) Successful in 4m27s
2023-11-06 19:22:40 +01:00
1688347367 Merge pull request 'dev into staging' (#425) from dev into staging
All checks were successful
Deploy staging on push / on-push-deploy_sh-edraft (push) Successful in 3m54s
Reviewed-on: #425
2023-11-06 19:06:09 +01:00
6e79811bc9 Added maintenance option to technician settings #424
All checks were successful
Deploy dev on push / on-push-deploy_sh-edraft (push) Successful in 4m20s
2023-11-06 18:56:04 +01:00
e1258151de Added maintenance to db and gql #424 2023-11-06 18:02:09 +01:00
a7c833b9db Added maintenance mode #423
All checks were successful
Deploy dev on push / on-push-deploy_sh-edraft (push) Successful in 4m9s
2023-11-05 22:22:39 +01:00
2f161d2f43 Fixed technicianFullDataAccess
All checks were successful
Deploy dev on push / on-push-deploy_sh-edraft (push) Successful in 3m59s
2023-11-05 17:50:37 +01:00
0a5f23f1af Merge pull request 'dev' (#422) from dev into staging
All checks were successful
Deploy staging on push / on-push-deploy_sh-edraft (push) Successful in 3m22s
Reviewed-on: #422
2023-11-05 17:07:56 +01:00
23a3dbbc5e Merge branch 'staging' into dev
All checks were successful
Deploy dev on push / on-push-deploy_sh-edraft (push) Successful in 3m42s
2023-11-05 17:07:48 +01:00
8975665407 Merge pull request '#420' (#421) from #420 into dev
All checks were successful
Deploy dev on push / on-push-deploy_sh-edraft (push) Successful in 4m23s
Reviewed-on: #421
2023-11-05 15:51:47 +01:00
90de90684c Added statistics #420 2023-11-05 15:51:25 +01:00
d1ecfe9603 Added activity score calculation #420 2023-11-05 15:34:17 +01:00
f7f25e9428 Added base for server statistics #420 2023-11-05 14:01:24 +01:00
6ae7ab5c5a Set higher timer for steam offer watcher
All checks were successful
Deploy dev on push / on-push-deploy_sh-edraft (push) Successful in 4m20s
2023-11-05 11:48:45 +01:00
315c90cacc Fixed dropdown width
All checks were successful
Deploy dev on push / on-push-deploy_sh-edraft (push) Successful in 3m57s
2023-11-05 00:38:44 +01:00
2caa910613 Merge pull request 'dev into staging' (#415) from dev into staging
All checks were successful
Deploy staging on push / on-push-deploy_sh-edraft (push) Successful in 4m29s
Reviewed-on: #415
2023-11-05 00:29:44 +01:00
8e55ebc4b7 Added active member count #413
All checks were successful
Deploy dev on push / on-push-deploy_sh-edraft (push) Successful in 6m38s
2023-11-05 00:18:01 +01:00
665e524aa9 Fixed menu design
All checks were successful
Deploy dev on push / on-push-deploy_sh-edraft (push) Successful in 3m13s
2023-11-04 23:56:36 +01:00
ae021ac7ea Added custom level header to make multi env compatible
All checks were successful
Deploy dev on push / on-push-deploy_sh-edraft (push) Successful in 4m0s
2023-11-04 23:43:54 +01:00
dcbb481b10 Added setting for max steam offer count 2023-11-04 23:37:07 +01:00
e6165caed9 Only view first 150 elements of steam offers
All checks were successful
Deploy dev on push / on-push-deploy_sh-edraft (push) Successful in 3m27s
2023-11-04 18:01:40 +01:00
58fdc9f48f Fixed staging icon
All checks were successful
Deploy staging on push / on-push-deploy_sh-edraft (push) Successful in 4m5s
2023-11-04 17:54:39 +01:00
96 changed files with 919 additions and 106 deletions

View File

@@ -8,14 +8,8 @@ on:
jobs: jobs:
on-push-deploy_sh-edraft: on-push-deploy_sh-edraft:
runs-on: [ dobby.sh-edraft.de, ubuntu-latest ] runs-on: [ dobby.sh-edraft.de, ubuntu-latest ]
container: catthehacker/ubuntu:act-latest container: sh-edraft.de/act-runner:latest
steps: steps:
- name: Setup Python 3.10
uses: actions/setup-python@v3
with:
python-version: "3.10.12"
- run: python -v
- name: Setup docker - name: Setup docker
uses: https://github.com/papodaca/install-docker-action@main uses: https://github.com/papodaca/install-docker-action@main
- run: docker -v - run: docker -v
@@ -29,7 +23,7 @@ jobs:
- name: Prepare bot build - name: Prepare bot build
run: | run: |
cd bot cd bot
pip install --extra-index-url https://pip.sh-edraft.de cpl-cli python3.10 -m pip install --extra-index-url https://pip.sh-edraft.de cpl-cli
cpl i cpl i
- name: Setup node - name: Setup node

View File

@@ -8,14 +8,8 @@ on:
jobs: jobs:
on-push-deploy_sh-edraft: on-push-deploy_sh-edraft:
runs-on: [ dobby.sh-edraft.de, ubuntu-latest ] runs-on: [ dobby.sh-edraft.de, ubuntu-latest ]
container: catthehacker/ubuntu:act-latest container: sh-edraft.de/act-runner:latest
steps: steps:
- name: Setup Python 3.10
uses: actions/setup-python@v3
with:
python-version: "3.10.12"
- run: python -v
- name: Setup docker - name: Setup docker
uses: https://github.com/papodaca/install-docker-action@main uses: https://github.com/papodaca/install-docker-action@main
- run: docker -v - run: docker -v
@@ -29,7 +23,7 @@ jobs:
- name: Prepare bot build - name: Prepare bot build
run: | run: |
cd bot cd bot
pip install --extra-index-url https://pip.sh-edraft.de cpl-cli python3.10 -m pip install --extra-index-url https://pip.sh-edraft.de cpl-cli
cpl i cpl i
- name: Setup node - name: Setup node

View File

@@ -8,14 +8,8 @@ on:
jobs: jobs:
on-push-deploy_sh-edraft: on-push-deploy_sh-edraft:
runs-on: [ dobby.sh-edraft.de, ubuntu-latest ] runs-on: [ dobby.sh-edraft.de, ubuntu-latest ]
container: catthehacker/ubuntu:act-latest container: sh-edraft.de/act-runner:latest
steps: steps:
- name: Setup Python 3.10
uses: actions/setup-python@v3
with:
python-version: "3.10.12"
- run: python -v
- name: Setup docker - name: Setup docker
uses: https://github.com/papodaca/install-docker-action@main uses: https://github.com/papodaca/install-docker-action@main
- run: docker -v - run: docker -v
@@ -29,7 +23,7 @@ jobs:
- name: Prepare bot build - name: Prepare bot build
run: | run: |
cd bot cd bot
pip install --extra-index-url https://pip.sh-edraft.de cpl-cli python3.10 -m pip install --extra-index-url https://pip.sh-edraft.de cpl-cli
cpl i cpl i
- name: Setup node - name: Setup node

View File

@@ -11,6 +11,7 @@ from bot_api.api_thread import ApiThread
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_enum import FeatureFlagsEnum
from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings from bot_core.configuration.feature_flags_settings import FeatureFlagsSettings
from bot_core.environment_variables import MAINTENANCE
from bot_core.service.data_integrity_service import DataIntegrityService from bot_core.service.data_integrity_service import DataIntegrityService
@@ -58,8 +59,9 @@ class Application(DiscordBotApplicationABC):
return return
self._logger.info(__name__, f"Try to start {DiscordBotService.__name__}") self._logger.info(__name__, f"Try to start {DiscordBotService.__name__}")
for task in self._tasks: if not self._config.get_configuration(MAINTENANCE):
await self._bot.add_cog(task) for task in self._tasks:
await self._bot.add_cog(task)
await self._bot.start_async() await self._bot.start_async()
await self._bot.stop_async() await self._bot.stop_async()

View File

@@ -9,7 +9,7 @@ from bot.application import Application
from bot.extension.init_bot_extension import InitBotExtension from bot.extension.init_bot_extension import InitBotExtension
from bot.startup import Startup from bot.startup import Startup
from bot.startup_discord_extension import StartupDiscordExtension from bot.startup_discord_extension import StartupDiscordExtension
from bot.startup_migration_extension import StartupMigrationExtension from bot_data.startup_migration_extension import StartupMigrationExtension
from bot.startup_module_extension import StartupModuleExtension from bot.startup_module_extension import StartupModuleExtension
from bot.startup_settings_extension import StartupSettingsExtension from bot.startup_settings_extension import StartupSettingsExtension
from bot_api.app_api_extension import AppApiExtension from bot_api.app_api_extension import AppApiExtension

View File

@@ -8,6 +8,7 @@ from cpl_core.dependency_injection import ServiceCollectionABC
from cpl_core.environment import ApplicationEnvironmentABC from cpl_core.environment import ApplicationEnvironmentABC
from bot_core.configuration.bot_logging_settings import BotLoggingSettings from bot_core.configuration.bot_logging_settings import BotLoggingSettings
from bot_core.environment_variables import MAINTENANCE, MIGRATION_ONLY
class StartupSettingsExtension(StartupExtensionABC): class StartupSettingsExtension(StartupExtensionABC):
@@ -19,6 +20,12 @@ class StartupSettingsExtension(StartupExtensionABC):
environment.set_working_directory(os.path.dirname(os.path.realpath(__file__))) environment.set_working_directory(os.path.dirname(os.path.realpath(__file__)))
configuration.add_environment_variables("SDB_") configuration.add_environment_variables("SDB_")
configuration.add_environment_variables("DISCORD_") configuration.add_environment_variables("DISCORD_")
configuration.add_configuration(
MAINTENANCE, configuration.get_configuration(MAINTENANCE) in [True, "true", "True"]
)
configuration.add_configuration(
MIGRATION_ONLY, configuration.get_configuration(MIGRATION_ONLY) in [True, "true", "True"]
)
configuration.add_json_file(f"config/appsettings.json", optional=False) configuration.add_json_file(f"config/appsettings.json", optional=False)
configuration.add_json_file(f"config/appsettings.{environment.environment_name}.json", optional=True) configuration.add_json_file(f"config/appsettings.{environment.environment_name}.json", optional=True)

View File

@@ -90,7 +90,8 @@
"booting": "Ich fahre gerade hoch...", "booting": "Ich fahre gerade hoch...",
"restart": "Muss neue Kekse holen...", "restart": "Muss neue Kekse holen...",
"running": "Ich esse Kekse :D", "running": "Ich esse Kekse :D",
"shutdown": "Ich werde bestimmt wieder kommen..." "shutdown": "Ich werde bestimmt wieder kommen...",
"maintenance": "In Wartung!"
} }
}, },
"modules": { "modules": {

View File

@@ -75,3 +75,7 @@ class ClientUtilsABC(ABC):
@abstractmethod @abstractmethod
async def check_default_role(self, member: Union[discord.User, discord.Member]): async def check_default_role(self, member: Union[discord.User, discord.Member]):
pass pass
@abstractmethod
async def set_maintenance_mode(self, state: bool):
pass

View File

@@ -6,6 +6,7 @@ from cpl_core.dependency_injection import ServiceProviderABC
from cpl_discord.service import DiscordBotServiceABC from cpl_discord.service import DiscordBotServiceABC
from discord.ext import commands from discord.ext import commands
from bot_core.environment_variables import MAINTENANCE
from bot_core.logging.task_logger import TaskLogger from bot_core.logging.task_logger import TaskLogger
@@ -14,14 +15,18 @@ class TaskABC(commands.Cog):
def __init__(self): def __init__(self):
commands.Cog.__init__(self) commands.Cog.__init__(self)
@ServiceProviderABC.inject
def _is_maintenance(self, config: ConfigurationABC) -> bool:
return config.get_configuration(MAINTENANCE) is True
@ServiceProviderABC.inject @ServiceProviderABC.inject
async def _wait_until_ready(self, config: ConfigurationABC, logger: TaskLogger, bot: DiscordBotServiceABC): async def _wait_until_ready(self, config: ConfigurationABC, logger: TaskLogger, bot: DiscordBotServiceABC):
logger.debug(__name__, f"Waiting before {type(self).__name__}") logger.debug(__name__, f"Waiting before {type(self).__name__}")
await bot.wait_until_ready() await bot.wait_until_ready()
async def wait(): async def wait():
is_ready = config.get_configuration("IS_READY") is_ready = config.get_configuration("IS_READY") is True
if is_ready != "true": if not is_ready:
await asyncio.sleep(1) await asyncio.sleep(1)
await wait() await wait()

View File

@@ -1,16 +1,19 @@
import asyncio import asyncio
from cpl_core.configuration import ConfigurationABC
from cpl_core.logging import LoggerABC from cpl_core.logging import LoggerABC
from cpl_discord.events import OnReadyABC from cpl_discord.events import OnReadyABC
from cpl_discord.service import DiscordBotServiceABC from cpl_discord.service import DiscordBotServiceABC
from cpl_translation import TranslatePipe from cpl_translation import TranslatePipe
from bot_core.abc.client_utils_abc import ClientUtilsABC from bot_core.abc.client_utils_abc import ClientUtilsABC
from bot_core.environment_variables import MAINTENANCE
class CoreExtensionOnReadyEvent(OnReadyABC): class CoreExtensionOnReadyEvent(OnReadyABC):
def __init__( def __init__(
self, self,
config: ConfigurationABC,
logger: LoggerABC, logger: LoggerABC,
bot: DiscordBotServiceABC, bot: DiscordBotServiceABC,
client_utils: ClientUtilsABC, client_utils: ClientUtilsABC,
@@ -18,6 +21,7 @@ class CoreExtensionOnReadyEvent(OnReadyABC):
): ):
OnReadyABC.__init__(self) OnReadyABC.__init__(self)
self._config = config
self._logger = logger self._logger = logger
self._bot = bot self._bot = bot
self._client_utils = client_utils self._client_utils = client_utils
@@ -27,5 +31,5 @@ class CoreExtensionOnReadyEvent(OnReadyABC):
async def on_ready(self): async def on_ready(self):
self._logger.debug(__name__, f"Module {type(self)} started") self._logger.debug(__name__, f"Module {type(self)} started")
await self._client_utils.presence_game("common.presence.running") await self._client_utils.set_maintenance_mode(self._config.get_configuration(MAINTENANCE))
self._logger.trace(__name__, f"Module {type(self)} stopped") self._logger.trace(__name__, f"Module {type(self)} stopped")

View File

@@ -0,0 +1,2 @@
MIGRATION_ONLY = "MIGRATION_ONLY"
MAINTENANCE = "MAINTENANCE"

View File

@@ -1,3 +1,4 @@
import inspect
from typing import Optional from typing import Optional
from discord.ext import commands from discord.ext import commands
@@ -17,11 +18,18 @@ class EventChecks:
cls._client_utils = client_utils cls._client_utils = client_utils
@classmethod @classmethod
def check_is_ready(cls): def check_is_ready(cls, func):
async def check_if_bot_is_ready() -> bool: async def check_if_bot_is_ready(*args, **kwargs):
result = await cls._client_utils.check_if_bot_is_ready_yet() result = await cls._client_utils.check_if_bot_is_ready_yet()
if not result: if not result:
raise CheckError(f"Bot is not ready")
return result
return commands.check(check_if_bot_is_ready) def empty(*args, **kwargs):
return
return empty
return await func(*args, **kwargs)
check_if_bot_is_ready.__name__ = func.__name__
sig = inspect.signature(func)
check_if_bot_is_ready.__signature__ = sig.replace(parameters=tuple(sig.parameters.values())[1:])
return check_if_bot_is_ready

View File

@@ -16,6 +16,7 @@ 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
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.environment_variables import MAINTENANCE
from bot_data.abc.client_repository_abc import ClientRepositoryABC from bot_data.abc.client_repository_abc import ClientRepositoryABC
from bot_data.abc.server_repository_abc import ServerRepositoryABC from bot_data.abc.server_repository_abc import ServerRepositoryABC
from bot_data.abc.user_joined_voice_channel_repository_abc import ( from bot_data.abc.user_joined_voice_channel_repository_abc import (
@@ -87,7 +88,14 @@ class ClientUtilsService(ClientUtilsABC):
return client return client
async def check_if_bot_is_ready_yet(self) -> bool: async def check_if_bot_is_ready_yet(self) -> bool:
if self._config.get_configuration("IS_READY") == "true": if self._config.get_configuration(MAINTENANCE):
self._logger.warn(
__name__,
f"Bot is in maintenance mode",
)
return False
if self._config.get_configuration("IS_READY") is True:
return True return True
self._logger.debug( self._logger.debug(
@@ -239,3 +247,10 @@ class ClientUtilsService(ClientUtilsABC):
except Exception as e: except Exception as e:
self._logger.error(__name__, f"Cannot check for default role for member {member.id}", e) self._logger.error(__name__, f"Cannot check for default role for member {member.id}", e)
async def set_maintenance_mode(self, state: bool):
self._config.add_configuration(MAINTENANCE, state)
if state:
await self.presence_game("common.presence.maintenance")
else:
await self.presence_game("common.presence.running")

View File

@@ -398,6 +398,9 @@ class DataIntegrityService:
if user is None: if user is None:
continue continue
for join in self._user_joins.get_user_joined_servers_by_user_id(user.id):
self._user_joins.delete_user_joined_server(join)
self._user_joins_vc.delete_user_joined_voice_channel_by_user_id(user.id) self._user_joins_vc.delete_user_joined_voice_channel_by_user_id(user.id)
self._users.delete_user(user) self._users.delete_user(user)
self._db_context.save_changes() self._db_context.save_changes()

View File

@@ -5,6 +5,8 @@ CREATE TABLE IF NOT EXISTS `CFG_TechnicianHistory`
`WaitForRestart` BIGINT NOT NULL DEFAULT 8, `WaitForRestart` BIGINT NOT NULL DEFAULT 8,
`WaitForShutdown` BIGINT NOT NULL DEFAULT 8, `WaitForShutdown` BIGINT NOT NULL DEFAULT 8,
`CacheMaxMessages` BIGINT NOT NULL DEFAULT 1000000, `CacheMaxMessages` BIGINT NOT NULL DEFAULT 1000000,
`MaxSteamOfferCount` BIGINT NOT NULL DEFAULT 250,
`Maintenance` BOOLEAN DEFAULT FALSE,
`FeatureFlags` JSON NULL DEFAULT ('{}'), `FeatureFlags` JSON NULL DEFAULT ('{}'),
`Deleted` BOOL DEFAULT FALSE, `Deleted` BOOL DEFAULT FALSE,
`DateFrom` DATETIME(6) NOT NULL, `DateFrom` DATETIME(6) NOT NULL,
@@ -23,6 +25,8 @@ BEGIN
`WaitForRestart`, `WaitForRestart`,
`WaitForShutdown`, `WaitForShutdown`,
`CacheMaxMessages`, `CacheMaxMessages`,
`MaxSteamOfferCount`,
`Maintenance`,
`FeatureFlags`, `FeatureFlags`,
`DateFrom`, `DateFrom`,
`DateTo`) `DateTo`)
@@ -31,6 +35,8 @@ BEGIN
OLD.WaitForRestart, OLD.WaitForRestart,
OLD.WaitForShutdown, OLD.WaitForShutdown,
OLD.CacheMaxMessages, OLD.CacheMaxMessages,
OLD.MaxSteamOfferCount,
OLD.Maintenance,
OLD.FeatureFlags, OLD.FeatureFlags,
OLD.LastModifiedAt, OLD.LastModifiedAt,
CURRENT_TIMESTAMP(6)); CURRENT_TIMESTAMP(6));
@@ -48,6 +54,8 @@ BEGIN
`WaitForRestart`, `WaitForRestart`,
`WaitForShutdown`, `WaitForShutdown`,
`CacheMaxMessages`, `CacheMaxMessages`,
`MaxSteamOfferCount`,
`Maintenance`,
`FeatureFlags`, `FeatureFlags`,
`Deleted`, `Deleted`,
`DateFrom`, `DateFrom`,
@@ -57,6 +65,8 @@ BEGIN
OLD.WaitForRestart, OLD.WaitForRestart,
OLD.WaitForShutdown, OLD.WaitForShutdown,
OLD.CacheMaxMessages, OLD.CacheMaxMessages,
OLD.MaxSteamOfferCount,
OLD.Maintenance,
OLD.FeatureFlags, OLD.FeatureFlags,
TRUE, TRUE,
OLD.LastModifiedAt, OLD.LastModifiedAt,

View File

@@ -17,14 +17,10 @@ class FixUserHistoryMigration(MigrationABC):
# fix 1.1.0_AchievementsMigration # fix 1.1.0_AchievementsMigration
self._cursor.execute( self._cursor.execute(
str( str(f"""ALTER TABLE UsersHistory ADD COLUMN ReactionCount BIGINT NOT NULL DEFAULT 0 AFTER XP;""")
f"""ALTER TABLE UsersHistory ADD COLUMN IF NOT EXISTS ReactionCount BIGINT NOT NULL DEFAULT 0 AFTER XP;"""
)
) )
self._cursor.execute( self._cursor.execute(
str( str(f"""ALTER TABLE UsersHistory ADD COLUMN MessageCount BIGINT NOT NULL DEFAULT 0 AFTER ReactionCount;""")
f"""ALTER TABLE UsersHistory ADD COLUMN IF NOT EXISTS MessageCount BIGINT NOT NULL DEFAULT 0 AFTER ReactionCount;"""
)
) )
self._exec(__file__, "users.sql") self._exec(__file__, "users.sql")

View File

@@ -0,0 +1,51 @@
from bot_core.logging.database_logger import DatabaseLogger
from bot_data.abc.migration_abc import MigrationABC
from bot_data.db_context import DBContext
class MaintenanceModeMigration(MigrationABC):
name = "1.2.0_MaintenanceModeMigration"
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 CFG_Technician
ADD Maintenance BOOLEAN DEFAULT FALSE AFTER MaxSteamOfferCount;
"""
)
)
self._cursor.execute(
str(
f"""
ALTER TABLE CFG_TechnicianHistory
ADD Maintenance BOOLEAN DEFAULT FALSE AFTER MaxSteamOfferCount;
"""
)
)
self._exec(__file__, "config/technician.sql")
def downgrade(self):
self._cursor.execute(
str(
f"""
ALTER TABLE CFG_Technician DROP COLUMN Maintenance;
"""
)
)
self._cursor.execute(
str(
f"""
ALTER TABLE CFG_TechnicianHistory DROP COLUMN Maintenance;
"""
)
)

View File

@@ -0,0 +1,51 @@
from bot_core.logging.database_logger import DatabaseLogger
from bot_data.abc.migration_abc import MigrationABC
from bot_data.db_context import DBContext
class MaxSteamOfferCountMigration(MigrationABC):
name = "1.2.0_MaxSteamOfferCountMigration"
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 CFG_Technician
ADD MaxSteamOfferCount BIGINT NOT NULL DEFAULT 250 AFTER CacheMaxMessages;
"""
)
)
self._cursor.execute(
str(
f"""
ALTER TABLE CFG_TechnicianHistory
ADD MaxSteamOfferCount BIGINT NOT NULL DEFAULT 250 AFTER CacheMaxMessages;
"""
)
)
self._exec(__file__, "config/technician.sql")
def downgrade(self):
self._cursor.execute(
str(
f"""
ALTER TABLE CFG_Technician DROP COLUMN MaxSteamOfferCount;
"""
)
)
self._cursor.execute(
str(
f"""
ALTER TABLE CFG_TechnicianHistory DROP COLUMN MaxSteamOfferCount;
"""
)
)

View File

@@ -36,7 +36,7 @@ class SteamSpecialOfferMigration(MigrationABC):
str( str(
f""" f"""
ALTER TABLE CFG_Server ALTER TABLE CFG_Server
ADD COLUMN IF NOT EXISTS GameOfferNotificationChatId BIGINT NULL AFTER ShortRoleNameSetOnlyHighest; ADD COLUMN GameOfferNotificationChatId BIGINT NULL AFTER ShortRoleNameSetOnlyHighest;
""" """
) )
) )
@@ -45,7 +45,7 @@ class SteamSpecialOfferMigration(MigrationABC):
str( str(
f""" f"""
ALTER TABLE CFG_ServerHistory ALTER TABLE CFG_ServerHistory
ADD COLUMN IF NOT EXISTS GameOfferNotificationChatId BIGINT NULL AFTER ShortRoleNameSetOnlyHighest; ADD COLUMN GameOfferNotificationChatId BIGINT NULL AFTER ShortRoleNameSetOnlyHighest;
""" """
) )
) )

View File

@@ -15,6 +15,8 @@ class TechnicianConfig(TableABC, ConfigurationModelABC):
wait_for_restart: int, wait_for_restart: int,
wait_for_shutdown: int, wait_for_shutdown: int,
cache_max_messages: int, cache_max_messages: int,
max_steam_offer_count: int,
maintenance: bool,
feature_flags: dict[FeatureFlagsEnum], feature_flags: dict[FeatureFlagsEnum],
technician_ids: List[int], technician_ids: List[int],
ping_urls: List[str], ping_urls: List[str],
@@ -27,6 +29,9 @@ class TechnicianConfig(TableABC, ConfigurationModelABC):
self._wait_for_restart = wait_for_restart self._wait_for_restart = wait_for_restart
self._wait_for_shutdown = wait_for_shutdown self._wait_for_shutdown = wait_for_shutdown
self._cache_max_messages = cache_max_messages self._cache_max_messages = cache_max_messages
self._max_steam_offer_count = max_steam_offer_count
self._maintenance = maintenance
self._feature_flags = feature_flags self._feature_flags = feature_flags
self._technician_ids = technician_ids self._technician_ids = technician_ids
self._ping_urls = ping_urls self._ping_urls = ping_urls
@@ -71,6 +76,14 @@ class TechnicianConfig(TableABC, ConfigurationModelABC):
def cache_max_messages(self, value: int): def cache_max_messages(self, value: int):
self._cache_max_messages = value self._cache_max_messages = value
@property
def max_steam_offer_count(self) -> int:
return self._max_steam_offer_count
@max_steam_offer_count.setter
def max_steam_offer_count(self, value: int):
self._max_steam_offer_count = value
@property @property
def feature_flags(self) -> dict[FeatureFlagsEnum]: def feature_flags(self) -> dict[FeatureFlagsEnum]:
return self._feature_flags return self._feature_flags
@@ -95,6 +108,14 @@ class TechnicianConfig(TableABC, ConfigurationModelABC):
def ping_urls(self, value: List[str]): def ping_urls(self, value: List[str]):
self._ping_urls = value self._ping_urls = value
@property
def maintenance(self) -> bool:
return self._maintenance
@maintenance.setter
def maintenance(self, value: bool):
self._maintenance = value
@staticmethod @staticmethod
def get_select_all_string() -> str: def get_select_all_string() -> str:
return str( return str(
@@ -117,12 +138,13 @@ class TechnicianConfig(TableABC, ConfigurationModelABC):
return str( return str(
f""" f"""
INSERT INTO `CFG_Technician` ( INSERT INTO `CFG_Technician` (
`HelpCommandReferenceUrl`, `WaitForRestart`, `WaitForShutdown`, `CacheMaxMessages`, `FeatureFlags` `HelpCommandReferenceUrl`, `WaitForRestart`, `WaitForShutdown`, `CacheMaxMessages`, `MaxSteamOfferCount`, `FeatureFlags`
) VALUES ( ) VALUES (
'{self._help_command_reference_url}', '{self._help_command_reference_url}',
{self._wait_for_restart}, {self._wait_for_restart},
{self._wait_for_shutdown}, {self._wait_for_shutdown},
{self._cache_max_messages}, {self._cache_max_messages},
{self._max_steam_offer_count},
'{json.dumps(self._feature_flags)}' '{json.dumps(self._feature_flags)}'
); );
""" """
@@ -137,6 +159,7 @@ class TechnicianConfig(TableABC, ConfigurationModelABC):
`WaitForRestart` = {self._wait_for_restart}, `WaitForRestart` = {self._wait_for_restart},
`WaitForShutdown` = {self._wait_for_shutdown}, `WaitForShutdown` = {self._wait_for_shutdown},
`CacheMaxMessages` = {self._cache_max_messages}, `CacheMaxMessages` = {self._cache_max_messages},
`MaxSteamOfferCount` = {self._max_steam_offer_count},
`FeatureFlags` = '{json.dumps(self._feature_flags)}' `FeatureFlags` = '{json.dumps(self._feature_flags)}'
WHERE `Id` = {self._id}; WHERE `Id` = {self._id};
""" """

View File

@@ -124,6 +124,6 @@ class UserJoinedServer(TableABC):
return str( return str(
f""" f"""
DELETE FROM `UserJoinedServers` DELETE FROM `UserJoinedServers`
WHERE `Id` = {self._join_id}; WHERE `JoinId` = {self._join_id};
""" """
) )

View File

@@ -46,11 +46,13 @@ class TechnicianConfigRepositoryService(TechnicianConfigRepositoryABC):
result[2], result[2],
result[3], result[3],
result[4], result[4],
json.loads(result[5]), result[5],
bool(result[6]),
json.loads(result[7]),
self._get_technician_ids(), self._get_technician_ids(),
self._get_technician_ping_urls(), self._get_technician_ping_urls(),
result[6], result[8],
result[7], result[9],
id=result[0], id=result[0],
) )

View File

@@ -20,6 +20,8 @@ from bot_data.migration.fix_updates_migration import FixUpdatesMigration
from bot_data.migration.fix_user_history_migration import FixUserHistoryMigration 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.maintenance_mode_migration import MaintenanceModeMigration
from bot_data.migration.max_steam_offer_count_migration import MaxSteamOfferCountMigration
from bot_data.migration.remove_stats_migration import RemoveStatsMigration from bot_data.migration.remove_stats_migration import RemoveStatsMigration
from bot_data.migration.short_role_name_migration import ShortRoleNameMigration from bot_data.migration.short_role_name_migration import ShortRoleNameMigration
from bot_data.migration.short_role_name_only_highest_migration import ( from bot_data.migration.short_role_name_only_highest_migration import (
@@ -68,3 +70,5 @@ class StartupMigrationExtension(StartupExtensionABC):
services.add_transient(MigrationABC, FixUserHistoryMigration) # 10.10.2023 #401 - 1.2.0 services.add_transient(MigrationABC, FixUserHistoryMigration) # 10.10.2023 #401 - 1.2.0
services.add_transient(MigrationABC, BirthdayMigration) # 10.10.2023 #401 - 1.2.0 services.add_transient(MigrationABC, BirthdayMigration) # 10.10.2023 #401 - 1.2.0
services.add_transient(MigrationABC, SteamSpecialOfferMigration) # 10.10.2023 #188 - 1.2.0 services.add_transient(MigrationABC, SteamSpecialOfferMigration) # 10.10.2023 #188 - 1.2.0
services.add_transient(MigrationABC, MaxSteamOfferCountMigration) # 04.11.2023 #188 - 1.2.0
services.add_transient(MigrationABC, MaintenanceModeMigration) # 06.11.2023 #424 - 1.2.0

View File

@@ -1,6 +1,7 @@
from typing import Callable from typing import Callable
from ariadne import ObjectType from ariadne import ObjectType
from cpl_core.configuration import ConfigurationABC
from cpl_core.dependency_injection import ServiceProviderABC from cpl_core.dependency_injection import ServiceProviderABC
from cpl_core.type import T from cpl_core.type import T
from cpl_discord.service import DiscordBotServiceABC from cpl_discord.service import DiscordBotServiceABC
@@ -10,6 +11,7 @@ from bot_api.exception.service_error_code_enum import ServiceErrorCode
from bot_api.exception.service_exception import ServiceException from bot_api.exception.service_exception import ServiceException
from bot_api.route.route import Route from bot_api.route.route import Route
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
from bot_core.environment_variables import MAINTENANCE
from bot_data.model.achievement import Achievement from bot_data.model.achievement import Achievement
from bot_data.model.auth_role_enum import AuthRoleEnum from bot_data.model.auth_role_enum import AuthRoleEnum
from bot_data.model.auth_user import AuthUser from bot_data.model.auth_user import AuthUser
@@ -75,9 +77,14 @@ class QueryABC(ObjectType):
def get_services(services: ServiceProviderABC) -> ServiceProviderABC: def get_services(services: ServiceProviderABC) -> ServiceProviderABC:
return services return services
@ServiceProviderABC.inject
def get_config(config: ConfigurationABC) -> ConfigurationABC:
return config
services = get_services() services = get_services()
permissions: PermissionService = services.get_service(PermissionService) permissions: PermissionService = services.get_service(PermissionService)
bot: DiscordBotServiceABC = services.get_service(DiscordBotServiceABC) bot: DiscordBotServiceABC = services.get_service(DiscordBotServiceABC)
config = get_config()
if user.auth_role == AuthRoleEnum.admin: if user.auth_role == AuthRoleEnum.admin:
return True return True
@@ -87,6 +94,9 @@ class QueryABC(ObjectType):
if permissions.is_member_technician(guild.get_member(u.discord_id)): if permissions.is_member_technician(guild.get_member(u.discord_id)):
return True return True
if config.get_configuration(MAINTENANCE):
return False
access = False access = False
if type(element) == Achievement: if type(element) == Achievement:
element: Achievement = element element: Achievement = element
@@ -215,7 +225,9 @@ class QueryABC(ObjectType):
return access return access
@ServiceProviderABC.inject @ServiceProviderABC.inject
def _can_user_mutate_data(self, server: Server, permission: UserRoleEnum, services: ServiceProviderABC): def _can_user_mutate_data(
self, server: Server, permission: UserRoleEnum, services: ServiceProviderABC, config: ConfigurationABC
):
permissions: PermissionService = services.get_service(PermissionService) permissions: PermissionService = services.get_service(PermissionService)
bot: DiscordBotServiceABC = services.get_service(DiscordBotServiceABC) bot: DiscordBotServiceABC = services.get_service(DiscordBotServiceABC)
@@ -227,16 +239,20 @@ class QueryABC(ObjectType):
auth_user.users.where(lambda x: x.server.id == server.id).single().discord_id auth_user.users.where(lambda x: x.server.id == server.id).single().discord_id
) )
check_perm = lambda x: True can_edit = lambda x: False
match permission: match permission:
case UserRoleEnum.moderator: case UserRoleEnum.moderator:
check_perm = lambda x: permissions.is_member_moderator(x) can_edit = permissions.is_member_moderator
if config.get_configuration(MAINTENANCE):
can_edit = lambda x: False
case UserRoleEnum.admin: case UserRoleEnum.admin:
check_perm = lambda x: permissions.is_member_admin(x) can_edit = permissions.is_member_admin
if config.get_configuration(MAINTENANCE):
can_edit = lambda x: False
case UserRoleEnum.technician: case UserRoleEnum.technician:
check_perm = lambda x: permissions.is_member_technician(x) can_edit = permissions.is_member_technician
if not check_perm(member): if not can_edit(member):
ex = ServiceException(ServiceErrorCode.Forbidden, f"User not allowed to mutate data") ex = ServiceException(ServiceErrorCode.Forbidden, f"User not allowed to mutate data")
raise ex raise ex

View File

@@ -26,6 +26,7 @@ type Server implements TableWithHistoryQuery {
gameServers: [GameServer] gameServers: [GameServer]
userCount: Int userCount: Int
activeUserCount: Int
users(filter: UserFilter, page: Page, sort: Sort): [User] users(filter: UserFilter, page: Page, sort: Sort): [User]
achievementCount: Int achievementCount: Int
@@ -37,6 +38,8 @@ type Server implements TableWithHistoryQuery {
config: ServerConfig config: ServerConfig
hasFeatureFlag(flag: String): FeatureFlag hasFeatureFlag(flag: String): FeatureFlag
# statistic(date: String): ServerStatistic
createdAt: String createdAt: String
modifiedAt: String modifiedAt: String

View File

@@ -0,0 +1,17 @@
type ServerStatistic {
achievementsAchieved: Int
messageCount: Int
userCount: Int
activeUserCount: Int
userJoinedVoiceChannelCount: Int
userJoinedVoiceChannelOntime: Float
userJoinedGameServerCount: Int
userJoinedGameServerOntime: Float
userWarningCount: Int
activityScore: Int
}

View File

@@ -4,6 +4,8 @@ type TechnicianConfig implements TableWithHistoryQuery {
waitForRestart: Int waitForRestart: Int
waitForShutdown: Int waitForShutdown: Int
cacheMaxMessages: Int cacheMaxMessages: Int
maxSteamOfferCount: Int
maintenance: Boolean
featureFlagCount: Int featureFlagCount: Int
featureFlags: [FeatureFlag] featureFlags: [FeatureFlag]
pingURLs: [String] pingURLs: [String]
@@ -23,6 +25,8 @@ type TechnicianConfigHistory implements HistoryTableQuery {
waitForRestart: Int waitForRestart: Int
waitForShutdown: Int waitForShutdown: Int
cacheMaxMessages: Int cacheMaxMessages: Int
maxSteamOfferCount: Int
maintenance: Boolean
featureFlagCount: Int featureFlagCount: Int
featureFlags: [FeatureFlag] featureFlags: [FeatureFlag]
@@ -59,6 +63,8 @@ input TechnicianConfigInput {
waitForRestart: Int waitForRestart: Int
waitForShutdown: Int waitForShutdown: Int
cacheMaxMessages: Int cacheMaxMessages: Int
maxSteamOfferCount: Int
maintenance: Boolean
featureFlags: [FeatureFlagInput] featureFlags: [FeatureFlagInput]
pingURLs: [String] pingURLs: [String]
technicianIds: [String] technicianIds: [String]

View File

@@ -57,6 +57,7 @@ from bot_graphql.queries.level_query import LevelQuery
from bot_graphql.queries.server_config_query import ServerConfigQuery from bot_graphql.queries.server_config_query import ServerConfigQuery
from bot_graphql.queries.server_history_query import ServerHistoryQuery from bot_graphql.queries.server_history_query import ServerHistoryQuery
from bot_graphql.queries.server_query import ServerQuery from bot_graphql.queries.server_query import ServerQuery
from bot_graphql.queries.server_statistic_query import ServerStatisticQuery
from bot_graphql.queries.short_role_name_history_query import ShortRoleNameHistoryQuery from bot_graphql.queries.short_role_name_history_query import ShortRoleNameHistoryQuery
from bot_graphql.queries.short_role_name_query import ShortRoleNameQuery from bot_graphql.queries.short_role_name_query import ShortRoleNameQuery
from bot_graphql.queries.technician_config_history_query import ( from bot_graphql.queries.technician_config_history_query import (
@@ -138,6 +139,7 @@ class GraphQLModule(ModuleABC):
services.add_transient(QueryABC, ShortRoleNameQuery) services.add_transient(QueryABC, ShortRoleNameQuery)
services.add_transient(QueryABC, UserWarningHistoryQuery) services.add_transient(QueryABC, UserWarningHistoryQuery)
services.add_transient(QueryABC, UserWarningQuery) services.add_transient(QueryABC, UserWarningQuery)
services.add_transient(QueryABC, ServerStatisticQuery)
services.add_transient(QueryABC, DiscordQuery) services.add_transient(QueryABC, DiscordQuery)
services.add_transient(QueryABC, GuildQuery) services.add_transient(QueryABC, GuildQuery)

View File

@@ -0,0 +1,7 @@
from bot_data.model.server import Server
class ServerStatistics:
def __init__(self, server: Server, kwargs: dict):
self.server = server
self.kwargs = kwargs

View File

@@ -1,9 +1,12 @@
from cpl_core.configuration import ConfigurationABC
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 cpl_query.extension import List from cpl_query.extension import List
from bot_api.logging.api_logger import ApiLogger from bot_api.logging.api_logger import ApiLogger
from bot_api.route.route import Route from bot_api.route.route import Route
from bot_core.abc.client_utils_abc import ClientUtilsABC
from bot_core.environment_variables import MAINTENANCE
from bot_core.service.config_service import ConfigService from bot_core.service.config_service import ConfigService
from bot_data.abc.server_repository_abc import ServerRepositoryABC from bot_data.abc.server_repository_abc import ServerRepositoryABC
from bot_data.abc.technician_config_repository_abc import TechnicianConfigRepositoryABC from bot_data.abc.technician_config_repository_abc import TechnicianConfigRepositoryABC
@@ -18,6 +21,7 @@ from bot_graphql.abc.query_abc import QueryABC
class TechnicianConfigMutation(QueryABC): class TechnicianConfigMutation(QueryABC):
def __init__( def __init__(
self, self,
config: ConfigurationABC,
logger: ApiLogger, logger: ApiLogger,
bot: DiscordBotServiceABC, bot: DiscordBotServiceABC,
servers: ServerRepositoryABC, servers: ServerRepositoryABC,
@@ -25,9 +29,11 @@ class TechnicianConfigMutation(QueryABC):
db: DatabaseContextABC, db: DatabaseContextABC,
config_service: ConfigService, config_service: ConfigService,
tech_seeder: TechnicianConfigSeeder, tech_seeder: TechnicianConfigSeeder,
client_utils: ClientUtilsABC,
): ):
QueryABC.__init__(self, "TechnicianConfigMutation") QueryABC.__init__(self, "TechnicianConfigMutation")
self._config = config
self._logger = logger self._logger = logger
self._bot = bot self._bot = bot
self._servers = servers self._servers = servers
@@ -35,6 +41,7 @@ class TechnicianConfigMutation(QueryABC):
self._db = db self._db = db
self._config_service = config_service self._config_service = config_service
self._tech_seeder = tech_seeder self._tech_seeder = tech_seeder
self._client_utils = client_utils
self.set_field("updateTechnicianConfig", self.resolve_update_technician_config) self.set_field("updateTechnicianConfig", self.resolve_update_technician_config)
@@ -59,6 +66,12 @@ class TechnicianConfigMutation(QueryABC):
technician_config.cache_max_messages = ( technician_config.cache_max_messages = (
input["cacheMaxMessages"] if "cacheMaxMessages" in input else technician_config.cache_max_messages input["cacheMaxMessages"] if "cacheMaxMessages" in input else technician_config.cache_max_messages
) )
technician_config.max_steam_offer_count = (
input["maxSteamOfferCount"] if "maxSteamOfferCount" in input else technician_config.max_steam_offer_count
)
technician_config.maintenance = (
input["maintenance"] if "maintenance" in input else technician_config.maintenance
)
old_feature_flags = technician_config.feature_flags old_feature_flags = technician_config.feature_flags
technician_config.feature_flags = ( technician_config.feature_flags = (
dict( dict(
@@ -91,6 +104,9 @@ class TechnicianConfigMutation(QueryABC):
self._update_technician_ids(technician_config) self._update_technician_ids(technician_config)
self._db.save_changes() self._db.save_changes()
if technician_config.maintenance != self._config.get_configuration(MAINTENANCE):
self._bot.loop.create_task(self._client_utils.set_maintenance_mode(technician_config.maintenance))
self._bot.loop.create_task(self._config_service.reload_technician_config()) self._bot.loop.create_task(self._config_service.reload_technician_config())
return technician_config return technician_config

View File

@@ -26,6 +26,7 @@ from bot_graphql.filter.client_filter import ClientFilter
from bot_graphql.filter.level_filter import LevelFilter from bot_graphql.filter.level_filter import LevelFilter
from bot_graphql.filter.short_role_name_filter import ShortRoleNameFilter from bot_graphql.filter.short_role_name_filter import ShortRoleNameFilter
from bot_graphql.filter.user_filter import UserFilter from bot_graphql.filter.user_filter import UserFilter
from bot_graphql.model.server_statistics import ServerStatistics
class ServerQuery(DataQueryWithHistoryABC): class ServerQuery(DataQueryWithHistoryABC):
@@ -76,6 +77,10 @@ class ServerQuery(DataQueryWithHistoryABC):
lambda server, *_: self._levels.get_levels_by_server_id(server.id), lambda server, *_: self._levels.get_levels_by_server_id(server.id),
LevelFilter, LevelFilter,
) )
self.set_field(
"activeUserCount",
lambda server, *_: self._users.get_users_by_server_id(server.id).where(lambda x: not x.left_server).count(),
)
self.add_collection( self.add_collection(
"user", "user",
lambda server, *_: self._users.get_users_by_server_id(server.id), lambda server, *_: self._users.get_users_by_server_id(server.id),
@@ -103,6 +108,7 @@ class ServerQuery(DataQueryWithHistoryABC):
"hasFeatureFlag", "hasFeatureFlag",
lambda server, *_, **kwargs: self._resolve_has_feature_flag(server, *_, **kwargs), lambda server, *_, **kwargs: self._resolve_has_feature_flag(server, *_, **kwargs),
) )
# self.set_field("statistic", lambda server, *_, **kwargs: ServerStatistics(server, kwargs))
@staticmethod @staticmethod
def resolve_id(server: Server, *_): def resolve_id(server: Server, *_):

View File

@@ -0,0 +1,157 @@
import datetime
from cpl_core.configuration import ConfigurationABC
from bot_data.abc.achievement_repository_abc import AchievementRepositoryABC
from bot_data.abc.user_joined_game_server_repository_abc import UserJoinedGameServerRepositoryABC
from bot_data.abc.user_joined_voice_channel_repository_abc import UserJoinedVoiceChannelRepositoryABC
from bot_data.abc.user_message_count_per_hour_repository_abc import UserMessageCountPerHourRepositoryABC
from bot_data.abc.user_repository_abc import UserRepositoryABC
from bot_data.abc.user_warnings_repository_abc import UserWarningsRepositoryABC
from bot_data.model.server_config import ServerConfig
from bot_graphql.abc.query_abc import QueryABC
class ServerStatisticQuery(QueryABC):
def __init__(
self,
config: ConfigurationABC,
users: UserRepositoryABC,
user_joined_voice_channels: UserJoinedVoiceChannelRepositoryABC,
user_joined_game_servers: UserJoinedGameServerRepositoryABC,
user_messages: UserMessageCountPerHourRepositoryABC,
user_warnings: UserWarningsRepositoryABC,
achievements: AchievementRepositoryABC,
):
QueryABC.__init__(self, "ServerStatistic")
self._config = config
self._users = users
self._user_joined_voice_channels = user_joined_voice_channels
self._user_joined_game_servers = user_joined_game_servers
self._user_messages = user_messages
self._user_warnings = user_warnings
self._achievements = achievements
self.set_field(
"achievementsAchieved",
self._resolve_achievements,
)
self.set_field(
"messageCount",
self._resolve_message_count,
)
self.set_field("userCount", lambda server, *_: self._users.get_users_by_server_id(server.server.id).count())
self.set_field("activeUserCount", self._resolve_active_count)
self.set_field("userJoinedVoiceChannelCount", self._resolve_voice_channel_count)
self.set_field("userJoinedVoiceChannelOntime", self._resolve_voice_channel_ontime)
self.set_field("userJoinedGameServerCount", self._resolve_game_server_count)
self.set_field("userJoinedGameServerOntime", self._resolve_game_server_ontime)
self.set_field("userWarningCount", self._resolve_user_warning_count)
self.set_field("activityScore", self._resolve_activity_score)
def _resolve_activity_score(self, server, *_):
days = (datetime.date.today() - self._get_date(**server.kwargs)).days
return int(
(
(
self._resolve_achievements(server, *_)
+ self._resolve_message_count(server, *_)
+ self._resolve_voice_channel_count(server, *_)
+ self._resolve_voice_channel_ontime(server, *_)
+ self._resolve_game_server_count(server, *_)
+ self._resolve_game_server_ontime(server, *_)
- self._resolve_user_warning_count(server, *_)
)
/ self._resolve_active_count(server, *_)
)
/ days
* 1000
)
def _resolve_message_count(self, server, *_):
settings: ServerConfig = self._config.get_configuration(f"ServerConfig_{server.server.discord_id}")
return (
self._user_messages.get_user_message_count_per_hours()
.where(
lambda x: x.user.server.id == server.server.id
and x.created_at.date() >= self._get_date(**server.kwargs)
)
.sum(lambda x: x.xp_count / settings.xp_per_message)
)
def _resolve_active_count(self, server, *_):
return self._users.get_users_by_server_id(server.server.id).where(lambda x: not x.left_server).count()
def _resolve_voice_channel_count(self, server, *_):
return (
self._user_joined_voice_channels.get_user_joined_voice_channels()
.where(
lambda x: x.user.server.id == server.server.id
and x.created_at.date() >= self._get_date(**server.kwargs)
)
.count()
)
def _resolve_voice_channel_ontime(self, server, *_):
return (
self._user_joined_voice_channels.get_user_joined_voice_channels()
.where(
lambda x: x.user.server.id == server.server.id
and x.created_at.date() >= self._get_date(**server.kwargs)
)
.sum(lambda x: x.time)
)
def _resolve_game_server_count(self, server, *_):
return (
self._user_joined_game_servers.get_user_joined_game_servers()
.where(
lambda x: x.user.server.id == server.server.id
and x.created_at.date() >= self._get_date(**server.kwargs)
)
.count()
)
def _resolve_game_server_ontime(self, server, *_):
return (
self._user_joined_game_servers.get_user_joined_game_servers()
.where(
lambda x: x.user.server.id == server.server.id
and x.created_at.date() >= self._get_date(**server.kwargs)
)
.sum(lambda x: x.time)
)
def _resolve_user_warning_count(self, server, *_):
return (
self._user_warnings.get_user_warnings()
.where(
lambda x: x.user.server.id == server.server.id
and x.created_at.date() >= self._get_date(**server.kwargs)
)
.count()
)
def _resolve_achievements(self, server, *_):
return (
self._achievements.get_achievements_by_server_id(server.server.id)
.select_many(lambda x: self._achievements.get_user_got_achievements_by_achievement_id(x.id))
.where(
lambda x: x.user.server.id == server.server.id
and x.created_at.date() >= self._get_date(**server.kwargs)
)
.count()
)
def _get_date(self, **kwargs) -> datetime.date:
if "date" not in kwargs:
return datetime.date.today() - datetime.timedelta(days=7)
return datetime.datetime.strptime(kwargs["date"], "%d.%m.%Y").date()

View File

@@ -14,6 +14,8 @@ class TechnicianConfigHistoryQuery(HistoryQueryABC):
self.set_field("waitForRestart", lambda config, *_: config.wait_for_restart) self.set_field("waitForRestart", lambda config, *_: config.wait_for_restart)
self.set_field("waitForShutdown", lambda config, *_: config.wait_for_shutdown) self.set_field("waitForShutdown", lambda config, *_: config.wait_for_shutdown)
self.set_field("cacheMaxMessages", lambda config, *_: config.cache_max_messages) self.set_field("cacheMaxMessages", lambda config, *_: config.cache_max_messages)
self.set_field("maxSteamOfferCount", lambda config, *_: config.max_steam_offer_count)
self.set_field("maintenance", lambda config, *_: config.maintenance)
self.add_collection( self.add_collection(
"featureFlag", "featureFlag",
lambda config, *_: List( lambda config, *_: List(

View File

@@ -27,6 +27,8 @@ class TechnicianConfigQuery(DataQueryWithHistoryABC):
self.set_field("waitForRestart", lambda config, *_: config.wait_for_restart) self.set_field("waitForRestart", lambda config, *_: config.wait_for_restart)
self.set_field("waitForShutdown", lambda config, *_: config.wait_for_shutdown) self.set_field("waitForShutdown", lambda config, *_: config.wait_for_shutdown)
self.set_field("cacheMaxMessages", lambda config, *_: config.cache_max_messages) self.set_field("cacheMaxMessages", lambda config, *_: config.cache_max_messages)
self.set_field("maxSteamOfferCount", lambda config, *_: config.max_steam_offer_count)
self.set_field("maintenance", lambda config, *_: config.maintenance)
self.add_collection( self.add_collection(
"featureFlag", "featureFlag",
lambda config, *_: List( lambda config, *_: List(

View File

@@ -35,7 +35,7 @@ class AchievementOnMessageEvent(OnMessageABC):
self._servers = servers self._servers = servers
self._users = users self._users = users
@EventChecks.check_is_ready() @EventChecks.check_is_ready
async def on_message(self, message: discord.Message): async def on_message(self, message: discord.Message):
if message.guild is None: if message.guild is None:
return return

View File

@@ -9,6 +9,7 @@ from cpl_discord.service import DiscordBotServiceABC
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.helper.event_checks import EventChecks
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
from bot_data.model.server_config import ServerConfig from bot_data.model.server_config import ServerConfig
@@ -36,6 +37,7 @@ class AchievementOnReactionAddEvent(OnReactionAddABC):
self._servers = servers self._servers = servers
self._users = users self._users = users
@EventChecks.check_is_ready
async def on_reaction_add( async def on_reaction_add(
self, self,
reaction: discord.reaction.Reaction, reaction: discord.reaction.Reaction,

View File

@@ -9,6 +9,7 @@ from cpl_discord.service import DiscordBotServiceABC
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.helper.event_checks import EventChecks
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
from bot_data.model.server_config import ServerConfig from bot_data.model.server_config import ServerConfig
@@ -36,6 +37,7 @@ class AchievementOnReactionRemoveEvent(OnReactionRemoveABC):
self._servers = servers self._servers = servers
self._users = users self._users = users
@EventChecks.check_is_ready
async def on_reaction_remove( async def on_reaction_remove(
self, self,
reaction: discord.reaction.Reaction, reaction: discord.reaction.Reaction,

View File

@@ -7,6 +7,7 @@ from cpl_discord.service import DiscordBotServiceABC
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.helper.event_checks import EventChecks
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
from bot_data.model.server_config import ServerConfig from bot_data.model.server_config import ServerConfig
@@ -34,6 +35,7 @@ class AchievementOnVoiceStateUpdateEvent(OnVoiceStateUpdateABC):
self._servers = servers self._servers = servers
self._users = users self._users = users
@EventChecks.check_is_ready
async def on_voice_state_update( async def on_voice_state_update(
self, self,
member: discord.member.Member, member: discord.member.Member,

View File

@@ -32,7 +32,7 @@ class AutoRoleOnRawReactionAddEvent(OnRawReactionAddABC):
self._auto_roles = auto_roles self._auto_roles = auto_roles
self._reaction_handler = reaction_handler self._reaction_handler = reaction_handler
@EventChecks.check_is_ready() @EventChecks.check_is_ready
async def on_raw_reaction_add(self, payload: RawReactionActionEvent): async def on_raw_reaction_add(self, payload: RawReactionActionEvent):
self._logger.debug(__name__, f"Module {type(self)} started") self._logger.debug(__name__, f"Module {type(self)} started")
server_config: ServerConfig = self._config.get_configuration(f"ServerConfig_{payload.guild_id}") server_config: ServerConfig = self._config.get_configuration(f"ServerConfig_{payload.guild_id}")

View File

@@ -32,7 +32,7 @@ class AutoRoleOnRawReactionRemoveEvent(OnRawReactionRemoveABC):
self._auto_roles = auto_roles self._auto_roles = auto_roles
self._reaction_handler = reaction_handler self._reaction_handler = reaction_handler
@EventChecks.check_is_ready() @EventChecks.check_is_ready
async def on_raw_reaction_remove(self, payload: RawReactionActionEvent): async def on_raw_reaction_remove(self, payload: RawReactionActionEvent):
self._logger.debug(__name__, f"Module {type(self)} started") self._logger.debug(__name__, f"Module {type(self)} started")
server_config: ServerConfig = self._config.get_configuration(f"ServerConfig_{payload.guild_id}") server_config: ServerConfig = self._config.get_configuration(f"ServerConfig_{payload.guild_id}")

View File

@@ -34,7 +34,8 @@ class BirthdayWatcher(TaskABC):
self._message_service = message_service self._message_service = message_service
self._t = t self._t = t
self.watch.start() if not self._is_maintenance():
self.watch.start()
@tasks.loop(time=datetime.time(hour=8, minute=0)) @tasks.loop(time=datetime.time(hour=8, minute=0))
async def watch(self): async def watch(self):

View File

@@ -11,6 +11,7 @@ from discord.ext.commands import Context, CommandError
from bot_core.abc.message_service_abc import MessageServiceABC from bot_core.abc.message_service_abc import MessageServiceABC
from bot_core.exception.check_error import CheckError from bot_core.exception.check_error import CheckError
from bot_core.helper.event_checks import EventChecks
from bot_data.model.technician_config import TechnicianConfig from bot_data.model.technician_config import TechnicianConfig
@@ -32,6 +33,7 @@ class BaseOnCommandErrorEvent(OnCommandErrorABC):
self._time_format_settings = time_format_settings self._time_format_settings = time_format_settings
self._t = translate self._t = translate
@EventChecks.check_is_ready
async def on_command_error(self, ctx: Context, error: CommandError): async def on_command_error(self, ctx: Context, error: CommandError):
if isinstance(error, CheckError): if isinstance(error, CheckError):
return return

View File

@@ -10,6 +10,7 @@ from cpl_translation import TranslatePipe
from discord.ext.commands import Context from discord.ext.commands import Context
from bot_core.abc.message_service_abc import MessageServiceABC from bot_core.abc.message_service_abc import MessageServiceABC
from bot_core.helper.event_checks import EventChecks
from bot_core.logging.command_logger import CommandLogger from bot_core.logging.command_logger import CommandLogger
from bot_data.abc.client_repository_abc import ClientRepositoryABC from bot_data.abc.client_repository_abc import ClientRepositoryABC
from bot_data.abc.server_repository_abc import ServerRepositoryABC from bot_data.abc.server_repository_abc import ServerRepositoryABC
@@ -78,6 +79,7 @@ class BaseOnCommandEvent(OnCommandABC):
self._logger.debug(__name__, f"User {user} sent message. xp: from {old_xp} to {user.xp}") self._logger.debug(__name__, f"User {user} sent message. xp: from {old_xp} to {user.xp}")
@EventChecks.check_is_ready
async def on_command(self, ctx: Context): async def on_command(self, ctx: Context):
self._logger.debug(__name__, f"Module {type(self)} started") self._logger.debug(__name__, f"Module {type(self)} started")
self._logger.info(__name__, f"Received command: {ctx.command} from {ctx.channel}") self._logger.info(__name__, f"Received command: {ctx.command} from {ctx.channel}")

View File

@@ -4,6 +4,7 @@ from cpl_discord.events import OnGuildJoinABC
from cpl_discord.service import DiscordBotServiceABC from cpl_discord.service import DiscordBotServiceABC
from discord import Guild from discord import Guild
from bot_core.helper.event_checks import EventChecks
from bot_data.abc.server_config_repository_abc import ServerConfigRepositoryABC from bot_data.abc.server_config_repository_abc import ServerConfigRepositoryABC
from bot_data.abc.server_repository_abc import ServerRepositoryABC from bot_data.abc.server_repository_abc import ServerRepositoryABC
from bot_data.model.server import Server from bot_data.model.server import Server
@@ -29,6 +30,7 @@ class BaseOnGuildJoinEvent(OnGuildJoinABC):
self._db = db self._db = db
self._seeder = seeder self._seeder = seeder
@EventChecks.check_is_ready
async def on_guild_join(self, guild: Guild): async def on_guild_join(self, guild: Guild):
if self._servers.find_server_by_discord_id(guild.id) is None: if self._servers.find_server_by_discord_id(guild.id) is None:
self._servers.add_server(Server(guild.id)) self._servers.add_server(Server(guild.id))

View File

@@ -101,7 +101,7 @@ class BaseOnMemberJoinEvent(OnMemberJoinABC):
) )
self._logger.trace(__name__, f"Notified team that a member left") self._logger.trace(__name__, f"Notified team that a member left")
@EventChecks.check_is_ready() @EventChecks.check_is_ready
async def on_member_join(self, member: discord.Member): async def on_member_join(self, member: discord.Member):
self._logger.debug(__name__, f"Module {type(self)} started") self._logger.debug(__name__, f"Module {type(self)} started")
if member.bot: if member.bot:

View File

@@ -69,7 +69,7 @@ class BaseOnMemberRemoveEvent(OnMemberRemoveABC):
) )
self._logger.trace(__name__, f"Notified team that a member left") self._logger.trace(__name__, f"Notified team that a member left")
@EventChecks.check_is_ready() @EventChecks.check_is_ready
async def on_member_remove(self, member: discord.Member): async def on_member_remove(self, member: discord.Member):
self._logger.debug(__name__, f"Module {type(self)} started") self._logger.debug(__name__, f"Module {type(self)} started")
await self._remove_user(member) await self._remove_user(member)

View File

@@ -6,6 +6,7 @@ from cpl_core.database.context import DatabaseContextABC
from cpl_discord.events import OnMessageDeleteABC from cpl_discord.events import OnMessageDeleteABC
from cpl_discord.service import DiscordBotServiceABC from cpl_discord.service import DiscordBotServiceABC
from bot_core.helper.event_checks import EventChecks
from bot_core.helper.log_message_helper import LogMessageHelper from bot_core.helper.log_message_helper import LogMessageHelper
from bot_core.logging.message_logger import MessageLogger from bot_core.logging.message_logger import MessageLogger
from bot_data.abc.client_repository_abc import ClientRepositoryABC from bot_data.abc.client_repository_abc import ClientRepositoryABC
@@ -72,6 +73,7 @@ class BaseOnMessageDeleteEvent(OnMessageDeleteABC):
f"Removed message from user {user}. xp: from {old_xp} to {user.xp}", f"Removed message from user {user}. xp: from {old_xp} to {user.xp}",
) )
@EventChecks.check_is_ready
async def on_message_delete(self, message: discord.Message): async def on_message_delete(self, message: discord.Message):
self._logger.debug(__name__, f"Module {type(self)} started") self._logger.debug(__name__, f"Module {type(self)} started")
if message is None or message.guild is None: if message is None or message.guild is None:

View File

@@ -79,7 +79,7 @@ class BaseOnMessageEvent(OnMessageABC):
self._logger.debug(__name__, f"User {user} sent message. xp: from {old_xp} to {user.xp}") self._logger.debug(__name__, f"User {user} sent message. xp: from {old_xp} to {user.xp}")
@EventChecks.check_is_ready() @EventChecks.check_is_ready
async def on_message(self, message: discord.Message): async def on_message(self, message: discord.Message):
self._logger.debug(__name__, f"Module {type(self)} started") self._logger.debug(__name__, f"Module {type(self)} started")
if message is None or message.guild is None: if message is None or message.guild is None:

View File

@@ -26,7 +26,7 @@ class BaseOnRawReactionAddEvent(OnRawReactionAddABC):
self._auto_roles = auto_roles self._auto_roles = auto_roles
self._reaction_handler = reaction_handler self._reaction_handler = reaction_handler
@EventChecks.check_is_ready() @EventChecks.check_is_ready
async def on_raw_reaction_add(self, payload: RawReactionActionEvent): async def on_raw_reaction_add(self, payload: RawReactionActionEvent):
self._logger.debug(__name__, f"Module {type(self)} started") self._logger.debug(__name__, f"Module {type(self)} started")

View File

@@ -26,7 +26,7 @@ class BaseOnRawReactionRemoveEvent(OnRawReactionRemoveABC):
self._auto_roles = auto_roles self._auto_roles = auto_roles
self._reaction_handler = reaction_handler self._reaction_handler = reaction_handler
@EventChecks.check_is_ready() @EventChecks.check_is_ready
async def on_raw_reaction_remove(self, payload: RawReactionActionEvent): async def on_raw_reaction_remove(self, payload: RawReactionActionEvent):
self._logger.debug(__name__, f"Module {type(self)} started") self._logger.debug(__name__, f"Module {type(self)} started")

View File

@@ -4,6 +4,7 @@ from cpl_discord.events.on_scheduled_event_update_abc import OnScheduledEventUpd
from cpl_discord.service import DiscordBotServiceABC from cpl_discord.service import DiscordBotServiceABC
from discord import EventStatus from discord import EventStatus
from bot_core.helper.event_checks import EventChecks
from modules.base.model.active_event import ActiveEvent from modules.base.model.active_event import ActiveEvent
from modules.base.service.event_service import EventService from modules.base.service.event_service import EventService
@@ -21,6 +22,7 @@ class BaseOnScheduledEventUpdateEvent(OnScheduledEventUpdateABC):
self._bot = bot self._bot = bot
self._events = events self._events = events
@EventChecks.check_is_ready
async def on_scheduled_event_update(self, before: discord.ScheduledEvent, after: discord.ScheduledEvent): async def on_scheduled_event_update(self, before: discord.ScheduledEvent, after: discord.ScheduledEvent):
self._logger.debug(__name__, f"Module {type(self)} started") self._logger.debug(__name__, f"Module {type(self)} started")

View File

@@ -83,7 +83,7 @@ class BaseOnVoiceStateUpdateEvent(OnVoiceStateUpdateABC):
except Exception as e: except Exception as e:
self._logger.error(__name__, f"Ontime validation failed", e) self._logger.error(__name__, f"Ontime validation failed", e)
@EventChecks.check_is_ready() @EventChecks.check_is_ready
async def on_voice_state_update( async def on_voice_state_update(
self, self,
member: discord.Member, member: discord.Member,

View File

@@ -42,7 +42,7 @@ class BaseOnVoiceStateUpdateEventHelpChannel(OnVoiceStateUpdateABC):
) )
self._logger.trace(__name__, f"Notified team that a member need help") self._logger.trace(__name__, f"Notified team that a member need help")
@EventChecks.check_is_ready() @EventChecks.check_is_ready
async def on_voice_state_update( async def on_voice_state_update(
self, self,
member: discord.Member, member: discord.Member,

View File

@@ -30,7 +30,7 @@ class BaseOnVoiceStateUpdateEventScheduledEventBonus(OnVoiceStateUpdateABC):
self._logger.info(__name__, f"Module {type(self)} loaded") self._logger.info(__name__, f"Module {type(self)} loaded")
@EventChecks.check_is_ready() @EventChecks.check_is_ready
async def on_voice_state_update( async def on_voice_state_update(
self, self,
member: discord.Member, member: discord.Member,

View File

@@ -82,6 +82,6 @@ class BootLogOnReadyEvent(OnReadyABC):
) )
) )
self._config.add_configuration("IS_READY", "true") self._config.add_configuration("IS_READY", True)
self._logger.info(__name__, "Bot is ready") self._logger.info(__name__, "Bot is ready")
self._logger.trace(__name__, f"Module {type(self)} stopped") self._logger.trace(__name__, f"Module {type(self)} stopped")

View File

@@ -8,6 +8,7 @@ from cpl_core.logging import LoggerABC
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.environment_variables import MIGRATION_ONLY
from bot_core.logging.database_logger import DatabaseLogger from bot_core.logging.database_logger import DatabaseLogger
from bot_data.service.migration_service import MigrationService from bot_data.service.migration_service import MigrationService
@@ -25,6 +26,6 @@ class DatabaseExtension(ApplicationExtensionABC):
config.add_configuration("Database_StartTime", str(datetime.now())) config.add_configuration("Database_StartTime", str(datetime.now()))
migrations: MigrationService = services.get_service(MigrationService) migrations: MigrationService = services.get_service(MigrationService)
migrations.migrate() migrations.migrate()
if config.get_configuration("MIGRATION_ONLY"): if config.get_configuration(MIGRATION_ONLY):
logger.warn(__name__, "Migrations finished. Stopping application...") logger.warn(__name__, "Migrations finished. Stopping application...")
sys.exit() sys.exit()

View File

@@ -0,0 +1,31 @@
{
"DefaultLevel": {
"LevelHeader": "~~~ dev-Level ~~~",
"Levels": [
{
"Name": "dev-Newbie",
"Color": "0x1abc9c",
"MinXp": 0,
"Permissions": 968552209984
},
{
"Name": "dev-Keks",
"Color": "0x2ecc71",
"MinXp": 100,
"Permissions": 1002928856640
},
{
"Name": "dev-Doppelkeks",
"Color": "0x3498db",
"MinXp": 200,
"Permissions": 1071849660224
},
{
"Name": "dev-Auror",
"Color": "0xf1c40f",
"MinXp": 300,
"Permissions": 1089042120513
}
]
}
}

View File

@@ -0,0 +1,31 @@
{
"DefaultLevel": {
"LevelHeader": "~~~ ed-Level ~~~",
"Levels": [
{
"Name": "ed-Newbie",
"Color": "0x1abc9c",
"MinXp": 0,
"Permissions": 968552209984
},
{
"Name": "ed-Keks",
"Color": "0x2ecc71",
"MinXp": 100,
"Permissions": 1002928856640
},
{
"Name": "ed-Doppelkeks",
"Color": "0x3498db",
"MinXp": 200,
"Permissions": 1071849660224
},
{
"Name": "ed-Auror",
"Color": "0xf1c40f",
"MinXp": 300,
"Permissions": 1089042120513
}
]
}
}

View File

@@ -0,0 +1,31 @@
{
"DefaultLevel": {
"LevelHeader": "~~~ ed-Level ~~~",
"Levels": [
{
"Name": "ed-Newbie",
"Color": "0x1abc9c",
"MinXp": 0,
"Permissions": 968552209984
},
{
"Name": "ed-Keks",
"Color": "0x2ecc71",
"MinXp": 100,
"Permissions": 1002928856640
},
{
"Name": "ed-Doppelkeks",
"Color": "0x3498db",
"MinXp": 200,
"Permissions": 1071849660224
},
{
"Name": "ed-Auror",
"Color": "0xf1c40f",
"MinXp": 300,
"Permissions": 1089042120513
}
]
}
}

View File

@@ -0,0 +1,31 @@
{
"DefaultLevel": {
"LevelHeader": "~~~ test-Level ~~~",
"Levels": [
{
"Name": "test-Newbie",
"Color": "0x1abc9c",
"MinXp": 0,
"Permissions": 968552209984
},
{
"Name": "test-Keks",
"Color": "0x2ecc71",
"MinXp": 100,
"Permissions": 1002928856640
},
{
"Name": "test-Doppelkeks",
"Color": "0x3498db",
"MinXp": 200,
"Permissions": 1071849660224
},
{
"Name": "test-Auror",
"Color": "0xf1c40f",
"MinXp": 300,
"Permissions": 1089042120513
}
]
}
}

View File

@@ -17,7 +17,7 @@ class LevelOnMemberJoinEvent(OnMemberJoinABC):
self._logger = logger self._logger = logger
self._level = level self._level = level
@EventChecks.check_is_ready() @EventChecks.check_is_ready
async def on_member_join(self, member: discord.Member): async def on_member_join(self, member: discord.Member):
self._logger.debug(__name__, f"Module {type(self)} started") self._logger.debug(__name__, f"Module {type(self)} started")
server_config: ServerConfig = self._config.get_configuration(f"ServerConfig_{member.guild.id}") server_config: ServerConfig = self._config.get_configuration(f"ServerConfig_{member.guild.id}")

View File

@@ -17,7 +17,7 @@ class LevelOnMessageEvent(OnMessageABC):
self._logger = logger self._logger = logger
self._level = level self._level = level
@EventChecks.check_is_ready() @EventChecks.check_is_ready
async def on_message(self, message: discord.Message): async def on_message(self, message: discord.Message):
self._logger.debug(__name__, f"Module {type(self)} started") self._logger.debug(__name__, f"Module {type(self)} started")
if message.guild is None: if message.guild is None:

View File

@@ -26,7 +26,7 @@ class LevelOnRawReactionAddEvent(OnRawReactionAddABC):
self._bot = bot self._bot = bot
self._level = level self._level = level
@EventChecks.check_is_ready() @EventChecks.check_is_ready
async def on_raw_reaction_add(self, payload: RawReactionActionEvent): async def on_raw_reaction_add(self, payload: RawReactionActionEvent):
self._logger.debug(__name__, f"Module {type(self)} started") self._logger.debug(__name__, f"Module {type(self)} started")
server_config: ServerConfig = self._config.get_configuration(f"ServerConfig_{payload.guild_id}") server_config: ServerConfig = self._config.get_configuration(f"ServerConfig_{payload.guild_id}")

View File

@@ -26,7 +26,7 @@ class LevelOnRawReactionRemoveEvent(OnRawReactionRemoveABC):
self._bot = bot self._bot = bot
self._level = level self._level = level
@EventChecks.check_is_ready() @EventChecks.check_is_ready
async def on_raw_reaction_remove(self, payload: RawReactionActionEvent): async def on_raw_reaction_remove(self, payload: RawReactionActionEvent):
self._logger.debug(__name__, f"Module {type(self)} started") self._logger.debug(__name__, f"Module {type(self)} started")
server_config: ServerConfig = self._config.get_configuration(f"ServerConfig_{payload.guild_id}") server_config: ServerConfig = self._config.get_configuration(f"ServerConfig_{payload.guild_id}")

View File

@@ -19,7 +19,7 @@ class LevelOnVoiceStateUpdateEvent(OnVoiceStateUpdateABC):
self._logger.info(__name__, f"Module {type(self)} loaded") self._logger.info(__name__, f"Module {type(self)} loaded")
@EventChecks.check_is_ready() @EventChecks.check_is_ready
async def on_voice_state_update( async def on_voice_state_update(
self, self,
member: discord.Member, member: discord.Member,

View File

@@ -32,7 +32,9 @@ class LevelModule(ModuleABC):
def configure_configuration(self, config: ConfigurationABC, env: ApplicationEnvironmentABC): def configure_configuration(self, config: ConfigurationABC, env: ApplicationEnvironmentABC):
cwd = env.working_directory cwd = env.working_directory
env.set_working_directory(os.path.dirname(os.path.realpath(__file__))) env.set_working_directory(os.path.dirname(os.path.realpath(__file__)))
config.add_json_file(f"default-level.json", optional=False) config.add_json_file(f"config/default-level.json", optional=False)
config.add_json_file(f"config/default-level.{env.environment_name}.json", optional=True)
config.add_json_file(f"config/default-level.{env.host_name}.json", optional=True)
env.set_working_directory(cwd) env.set_working_directory(cwd)
def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC): def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC):

View File

@@ -1,6 +1,7 @@
import discord import discord
from cpl_discord.events import OnMemberUpdateABC from cpl_discord.events import OnMemberUpdateABC
from bot_core.helper.event_checks import EventChecks
from modules.short_role_name.service.short_role_name_service import ShortRoleNameService from modules.short_role_name.service.short_role_name_service import ShortRoleNameService
@@ -9,6 +10,7 @@ class ShortRoleNameOnMemberUpdateEvent(OnMemberUpdateABC):
OnMemberUpdateABC.__init__(self) OnMemberUpdateABC.__init__(self)
self._service = service self._service = service
@EventChecks.check_is_ready
async def on_member_update(self, before: discord.member.Member, after: discord.member.Member): async def on_member_update(self, before: discord.member.Member, after: discord.member.Member):
if before.roles != after.roles or before.name != after.name: if before.roles != after.roles or before.name != after.name:
await self._service.check_short_role_names(after) await self._service.check_short_role_names(after)

View File

@@ -21,6 +21,7 @@ from bot_data.abc.steam_special_offer_repository_abc import (
) )
from bot_data.model.server_config import ServerConfig from bot_data.model.server_config import ServerConfig
from bot_data.model.steam_special_offer import SteamSpecialOffer from bot_data.model.steam_special_offer import SteamSpecialOffer
from bot_data.model.technician_config import TechnicianConfig
class SteamOfferWatcher(TaskABC): class SteamOfferWatcher(TaskABC):
@@ -33,6 +34,7 @@ class SteamOfferWatcher(TaskABC):
offers: SteamSpecialOfferRepositoryABC, offers: SteamSpecialOfferRepositoryABC,
message_service: MessageService, message_service: MessageService,
t: TranslatePipe, t: TranslatePipe,
tech_config: TechnicianConfig,
): ):
TaskABC.__init__(self) TaskABC.__init__(self)
@@ -43,12 +45,14 @@ class SteamOfferWatcher(TaskABC):
self._bot = bot self._bot = bot
self._message_service = message_service self._message_service = message_service
self._t = t self._t = t
self._tech_config = tech_config
self._is_new = False self._is_new = False
self._urls = {} self._urls = {}
self._image_urls = {} self._image_urls = {}
self.watch.start() if not self._is_maintenance():
self.watch.start()
@staticmethod @staticmethod
def _get_max_count() -> int: def _get_max_count() -> int:
@@ -108,8 +112,7 @@ class SteamOfferWatcher(TaskABC):
def _get_new_game_offers(self) -> List[SteamSpecialOffer]: def _get_new_game_offers(self) -> List[SteamSpecialOffer]:
new_offers = List(SteamSpecialOffer) new_offers = List(SteamSpecialOffer)
# sale_count = self._get_max_count() + 100 sale_count = self._tech_config.max_steam_offer_count
sale_count = 250 # only look at first 500
# todo: let admins change the value # todo: let admins change the value
self._logger.debug(__name__, f"Get special offers from 0 to {sale_count}") self._logger.debug(__name__, f"Get special offers from 0 to {sale_count}")
for i in range(0, sale_count, 100): for i in range(0, sale_count, 100):
@@ -184,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=1) @tasks.loop(hours=4)
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:

View File

@@ -53,4 +53,4 @@
"tslib": "^2.4.1", "tslib": "^2.4.1",
"typescript": "~4.9.5" "typescript": "~4.9.5"
} }
} }

View File

@@ -7,6 +7,8 @@ export interface TechnicianConfig extends DataWithHistory {
waitForRestart?: number; waitForRestart?: number;
waitForShutdown?: number; waitForShutdown?: number;
cacheMaxMessages?: number; cacheMaxMessages?: number;
maxSteamOfferCount?: number;
maintenance?: boolean;
featureFlags: FeatureFlag[]; featureFlags: FeatureFlag[];
pingURLs: string[]; pingURLs: string[];
technicianIds: string[]; technicianIds: string[];

View File

@@ -20,9 +20,11 @@ export interface Server extends Data {
autoRoles?: AutoRole[]; autoRoles?: AutoRole[];
clientCount?: number; clientCount?: number;
clients?: Client[]; clients?: Client[];
statistic?: ServerStatistic;
levelCount?: number; levelCount?: number;
levels?: Level[]; levels?: Level[];
userCount?: number; userCount?: number;
activeUserCount?: number;
users?: User[]; users?: User[];
config?: ServerConfig; config?: ServerConfig;
hasFeatureFlag?: FeatureFlag; hasFeatureFlag?: FeatureFlag;
@@ -34,3 +36,16 @@ export interface ServerFilter {
discordId?: String; discordId?: String;
name?: String; name?: String;
} }
export interface ServerStatistic {
achievementsAchieved?: Number
messageCount?: Number
userCount?: Number
activeUserCount?: Number
userJoinedVoiceChannelCount?: Number
userJoinedVoiceChannelOntime?: Number
userJoinedGameServerCount?: Number
userJoinedGameServerOntime?: Number
userWarningCount?: Number
activityScore?: Number
}

View File

@@ -216,7 +216,7 @@ export class Mutations {
`; `;
static updateTechnicianConfig = ` static updateTechnicianConfig = `
mutation updateTechnicianConfig($id: ID, $helpCommandReferenceUrl: String, $waitForRestart: Int, $waitForShutdown: Int, $cacheMaxMessages: Int, $featureFlags: [FeatureFlagInput], $pingURLs: [String], $technicianIds: [String]) { mutation updateTechnicianConfig($id: ID, $helpCommandReferenceUrl: String, $waitForRestart: Int, $waitForShutdown: Int, $cacheMaxMessages: Int, $maxSteamOfferCount: Int, $maintenance: Boolean, $featureFlags: [FeatureFlagInput], $pingURLs: [String], $technicianIds: [String]) {
technicianConfig { technicianConfig {
updateTechnicianConfig(input: { updateTechnicianConfig(input: {
id: $id, id: $id,
@@ -224,6 +224,8 @@ export class Mutations {
waitForRestart: $waitForRestart, waitForRestart: $waitForRestart,
waitForShutdown: $waitForShutdown, waitForShutdown: $waitForShutdown,
cacheMaxMessages: $cacheMaxMessages, cacheMaxMessages: $cacheMaxMessages,
maxSteamOfferCount: $maxSteamOfferCount,
maintenance: $maintenance,
featureFlags: $featureFlags, featureFlags: $featureFlags,
pingURLs: $pingURLs, pingURLs: $pingURLs,
technicianIds: $technicianIds technicianIds: $technicianIds
@@ -233,6 +235,8 @@ export class Mutations {
waitForRestart waitForRestart
waitForShutdown waitForShutdown
cacheMaxMessages cacheMaxMessages
maxSteamOfferCount
maintenance
featureFlags { featureFlags {
key key
value value

View File

@@ -68,6 +68,20 @@ export class Queries {
name name
iconURL iconURL
userCount userCount
activeUserCount
}
}
`;
static serversDashboardQuery = `
query DetailedServerInfo($filter: ServerFilter, $page: Page, $sort: Sort) {
servers(filter: $filter, page: $page, sort: $sort) {
id
discordId
name
iconURL
userCount
activeUserCount
clients { clients {
id id
discordId discordId
@@ -82,6 +96,19 @@ export class Queries {
} }
`; `;
// statistic {
// achievementsAchieved
// messageCount
// userCount
// activeUserCount
// userJoinedVoiceChannelCount
// userJoinedVoiceChannelOntime
// userJoinedGameServerCount
// userJoinedGameServerOntime
// userWarningCount
// activityScore
// }
static hasServerFeatureFlag = ` static hasServerFeatureFlag = `
query HasServerFeatureFlag($filter: ServerFilter, $flag: String) { query HasServerFeatureFlag($filter: ServerFilter, $flag: String) {
servers(filter: $filter) { servers(filter: $filter) {
@@ -497,6 +524,8 @@ export class Queries {
waitForRestart waitForRestart
waitForShutdown waitForShutdown
cacheMaxMessages cacheMaxMessages
maxSteamOfferCount
maintenance
featureFlags { featureFlags {
key key
value value

View File

@@ -109,7 +109,8 @@
<form [formGroup]="testMailForm" class="content-column"> <form [formGroup]="testMailForm" class="content-column">
<div class="content-data-name"> <div class="content-data-name">
<div class="input-field content-input-field"> <div class="input-field content-input-field">
<input type="email" pInputText formControlName="mail" placeholder="{{'common.email' | translate}}" autocomplete="email"> <input type="email" pInputText formControlName="mail" placeholder="{{'common.email' | translate}}"
autocomplete="email">
</div> </div>
</div> </div>
@@ -136,7 +137,8 @@
<div class="content-column"> <div class="content-column">
<div class="content-data-name">{{'admin.settings.bot.help_url' | translate}}:</div> <div class="content-data-name">{{'admin.settings.bot.help_url' | translate}}:</div>
<div class="content-data-value"> <div class="content-data-value">
<input type="text" pInputText [(ngModel)]="config.helpCommandReferenceUrl" placeholder="{{'admin.settings.bot.help_url' | translate}}"> <input type="text" pInputText [(ngModel)]="config.helpCommandReferenceUrl"
placeholder="{{'admin.settings.bot.help_url' | translate}}">
</div> </div>
</div> </div>
</div> </div>
@@ -145,7 +147,8 @@
<div class="content-column"> <div class="content-column">
<div class="content-data-name">{{'admin.settings.bot.wait_for_restart' | translate}}:</div> <div class="content-data-name">{{'admin.settings.bot.wait_for_restart' | translate}}:</div>
<div class="content-data-value"> <div class="content-data-value">
<input type="number" pInputText [(ngModel)]="config.waitForRestart" placeholder="{{'admin.settings.bot.wait_for_restart' | translate}}"> <input type="number" pInputText [(ngModel)]="config.waitForRestart"
placeholder="{{'admin.settings.bot.wait_for_restart' | translate}}">
</div> </div>
</div> </div>
</div> </div>
@@ -154,7 +157,8 @@
<div class="content-column"> <div class="content-column">
<div class="content-data-name">{{'admin.settings.bot.wait_for_shutdown' | translate}}:</div> <div class="content-data-name">{{'admin.settings.bot.wait_for_shutdown' | translate}}:</div>
<div class="content-data-value"> <div class="content-data-value">
<input type="number" pInputText [(ngModel)]="config.waitForShutdown" placeholder="{{'admin.settings.bot.wait_for_shutdown' | translate}}"> <input type="number" pInputText [(ngModel)]="config.waitForShutdown"
placeholder="{{'admin.settings.bot.wait_for_shutdown' | translate}}">
</div> </div>
</div> </div>
</div> </div>
@@ -163,14 +167,33 @@
<div class="content-column"> <div class="content-column">
<div class="content-data-name">{{'admin.settings.bot.cache_max_messages' | translate}}:</div> <div class="content-data-name">{{'admin.settings.bot.cache_max_messages' | translate}}:</div>
<div class="content-data-value"> <div class="content-data-value">
<input type="number" pInputText [(ngModel)]="config.cacheMaxMessages" placeholder="{{'admin.settings.bot.cache_max_messages' | translate}}"> <input type="number" pInputText [(ngModel)]="config.cacheMaxMessages"
placeholder="{{'admin.settings.bot.cache_max_messages' | translate}}">
</div> </div>
</div> </div>
</div> </div>
<div class="content-row">
<div class="content-column">
<div class="content-data-name">{{'admin.settings.bot.max_steam_offer_count' | translate}}:</div>
<div class="content-data-value">
<input type="number" pInputText [(ngModel)]="config.maxSteamOfferCount"
placeholder="{{'admin.settings.bot.max_steam_offer_count' | translate}}">
</div>
</div>
</div>
<div class="content-row">
<div class="content-column">
<div class="content-data-name">{{'view.server.config.bot.maintenance' | translate}}:</div>
<p-selectButton class="table-edit-input" [options]="stateOptions" [(ngModel)]="config.maintenance"></p-selectButton>
</div>
</div>
<div class="content-divider"></div> <div class="content-divider"></div>
<app-config-list translationKey="admin.settings.bot.ping_urls" [(data)]="config.pingURLs"></app-config-list> <app-config-list translationKey="admin.settings.bot.ping_urls" [(data)]="config.pingURLs"></app-config-list>
<app-config-list translationKey="admin.settings.bot.technician_ids" [options]="possibleTechnicians" optionLabel="name" optionValue="id" <app-config-list translationKey="admin.settings.bot.technician_ids" [options]="possibleTechnicians"
optionLabel="name" optionValue="id"
[(data)]="config.technicianIds"></app-config-list> [(data)]="config.technicianIds"></app-config-list>
<app-feature-flag-list [(data)]="config.featureFlags"></app-feature-flag-list> <app-feature-flag-list [(data)]="config.featureFlags"></app-feature-flag-list>

View File

@@ -56,6 +56,10 @@ export class SettingsComponent implements OnInit {
}; };
possibleTechnicians?: DiscordUser[]; possibleTechnicians?: DiscordUser[];
stateOptions: any[] = [
{ label: this.translate.instant("common.state.off"), value: false },
{ label: this.translate.instant("common.state.on"), value: true }
];
constructor( constructor(
private dataService: DataService, private dataService: DataService,
@@ -156,6 +160,8 @@ export class SettingsComponent implements OnInit {
waitForRestart: this.config.waitForRestart, waitForRestart: this.config.waitForRestart,
waitForShutdown: this.config.waitForShutdown, waitForShutdown: this.config.waitForShutdown,
cacheMaxMessages: this.config.cacheMaxMessages, cacheMaxMessages: this.config.cacheMaxMessages,
maxSteamOfferCount: this.config.maxSteamOfferCount,
maintenance: this.config.maintenance,
featureFlags: this.config.featureFlags, featureFlags: this.config.featureFlags,
pingURLs: this.config.pingURLs, pingURLs: this.config.pingURLs,
technicianIds: this.config.technicianIds technicianIds: this.config.technicianIds

View File

@@ -35,6 +35,7 @@ import { InputSwitchModule } from "primeng/inputswitch";
import { CalendarModule } from "primeng/calendar"; import { CalendarModule } from "primeng/calendar";
import { DataImportAndExportComponent } from './components/data-import-and-export/data-import-and-export.component'; import { DataImportAndExportComponent } from './components/data-import-and-export/data-import-and-export.component';
import { FileUploadModule } from "primeng/fileupload"; import { FileUploadModule } from "primeng/fileupload";
import { SelectButtonModule } from "primeng/selectbutton";
const PrimeNGModules = [ const PrimeNGModules = [
@@ -64,6 +65,7 @@ const PrimeNGModules = [
InputSwitchModule, InputSwitchModule,
CalendarModule, CalendarModule,
FileUploadModule, FileUploadModule,
SelectButtonModule,
] ]
@NgModule({ @NgModule({

View File

@@ -40,6 +40,7 @@
<i class="pi pi-users"></i> <i class="pi pi-users"></i>
{{server.userCount}} {{server.userCount}}
{{'view.dashboard.server.member_count' | translate}} {{'view.dashboard.server.member_count' | translate}}
{{'view.dashboard.server.active_members' | translate}} {{server ? server.activeUserCount : ''}}
</div> </div>
</div> </div>

View File

@@ -18,6 +18,7 @@ import { Mutations } from "../../../../models/graphql/mutations.model";
import { MenuItem } from "primeng/api"; 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";
@Component({ @Component({
selector: "app-profile", selector: "app-profile",
@@ -34,6 +35,7 @@ export class ProfileComponent implements OnInit, OnDestroy {
public isEditingNewUserWarning: boolean = false; public isEditingNewUserWarning: boolean = false;
public isEditing: boolean = false; public isEditing: boolean = false;
public isModerator: boolean = false; public isModerator: boolean = false;
public hasTechnicianAccess: boolean = false;
private unsubscriber = new Subject<void>(); private unsubscriber = new Subject<void>();
@@ -45,7 +47,8 @@ export class ProfileComponent implements OnInit, OnDestroy {
private auth: AuthService, private auth: AuthService,
private toast: ToastService, private toast: ToastService,
private translate: TranslateService, private translate: TranslateService,
private toastService: ToastService private toastService: ToastService,
private sidebarService: SidebarService,
) { ) {
} }
@@ -78,7 +81,8 @@ export class ProfileComponent implements OnInit, OnDestroy {
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;
if (!user || user?.id != params["memberId"] && !user?.isModerator) { this.hasTechnicianAccess = (this.sidebarService.hasFeature("TechnicianFullAccess") && user?.isTechnician) ?? false;
if (!user || user?.id != params["memberId"] && !user?.isModerator && !this.hasTechnicianAccess) {
this.toast.error(this.translate.instant("view.server.profile.permission_denied"), this.translate.instant("view.server.profile.permission_denied_d")); this.toast.error(this.translate.instant("view.server.profile.permission_denied"), this.translate.instant("view.server.profile.permission_denied_d"));
this.spinner.hideSpinner(); this.spinner.hideSpinner();
await this.router.navigate(["/server", server.id]); await this.router.navigate(["/server", server.id]);

View File

@@ -0,0 +1,35 @@
<div class="client">
<div class="client-info client-info-small">
<label class="client-info-header">{{'view.server.dashboard.activity_score' | translate}}</label>
<label class="client-info-value">{{stats?.activityScore}}</label>
</div>
<div class="client-info client-info-small">
<label class="client-info-header">{{'view.server.dashboard.achievements_achieved' | translate}}</label>
<label class="client-info-value">{{stats?.achievementsAchieved}}</label>
</div>
<div class="client-info client-info-small">
<label class="client-info-header">{{'view.server.dashboard.message_count' | translate}}</label>
<label class="client-info-value">{{stats?.messageCount}}</label>
</div>
<div class="client-info client-info-small">
<label class="client-info-header">{{'view.server.dashboard.user_joined_voice_channel_count' | translate}}</label>
<label class="client-info-value">{{stats?.userJoinedVoiceChannelCount}}</label>
</div>
<div class="client-info client-info-small">
<label class="client-info-header">{{'view.server.dashboard.user_joined_voice_channel_ontime' | translate}}</label>
<label class="client-info-value">{{stats?.userJoinedVoiceChannelOntime}}</label>
</div>
<div class="client-info client-info-small">
<label class="client-info-header">{{'view.server.dashboard.user_joined_game_server_count' | translate}}</label>
<label class="client-info-value">{{stats?.userJoinedGameServerCount}}</label>
</div>
<div class="client-info client-info-small">
<label class="client-info-header">{{'view.server.dashboard.user_joined_game_server_ontime' | translate}}</label>
<label class="client-info-value">{{stats?.userJoinedGameServerOntime}}</label>
</div>
<div class="client-info client-info-small">
<label class="client-info-header">{{'view.server.dashboard.user_warning_count' | translate}}</label>
<label class="client-info-value">{{stats?.userWarningCount}}</label>
</div>
</div>

View File

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

View File

@@ -0,0 +1,22 @@
import { Component, Input } from "@angular/core";
import { ServerStatistic } from "../../../../../../models/data/server.model";
@Component({
selector: 'app-server-statistic',
templateUrl: './server-statistic.component.html',
styleUrls: ['./server-statistic.component.scss']
})
export class ServerStatisticComponent {
@Input() stats?: ServerStatistic = {
achievementsAchieved: 0,
messageCount: 0,
userCount: 0,
activeUserCount: 0,
userJoinedVoiceChannelCount: 0,
userJoinedVoiceChannelOntime: 0,
userJoinedGameServerCount: 0,
userJoinedGameServerOntime: 0,
userWarningCount: 0,
activityScore: 0,
};
}

View File

@@ -12,7 +12,7 @@
<div class="content"> <div class="content">
<div class="server-list-wrapper"> <div class="server-list-wrapper">
<div class="server-list"> <div class="server-list">
<div class="server"> <div class="server" *ngIf="server">
<div class="logo"> <div class="logo">
<img *ngIf="server ? server.iconURL : ''" [src]="server ? server.iconURL : ''"> <img *ngIf="server ? server.iconURL : ''" [src]="server ? server.iconURL : ''">
</div> </div>
@@ -23,11 +23,18 @@
</h3> </h3>
<div class="data"> <div class="data">
<i class="pi pi-users"></i> <i class="pi pi-users"></i> {{server ? server.userCount : ''}}
{{server ? server.userCount : ''}}
{{'view.dashboard.server.member_count' | translate}} {{'view.dashboard.server.member_count' | translate}}
{{'view.dashboard.server.active_members' | translate}} {{server ? server.activeUserCount : ''}}
</div> </div>
<div class="content-divider"></div>
<div class="client-data">
<app-server-statistic class="client-component" [stats]="server.statistic"></app-server-statistic>
</div>
<div class="content-divider"></div>
<div class="client-data" <div class="client-data"
*ngFor="let client of server?.clients"> *ngFor="let client of server?.clients">
<app-client class="client-component" [client]="client"></app-client> <app-client class="client-component" [client]="client"></app-client>

View File

@@ -5,6 +5,10 @@ import { DataService } from "src/app/services/data/data.service";
import { SpinnerService } from "src/app/services/spinner/spinner.service"; import { SpinnerService } from "src/app/services/spinner/spinner.service";
import { SidebarService } from "../../../../services/sidebar/sidebar.service"; import { SidebarService } from "../../../../services/sidebar/sidebar.service";
import { ServerService } from "../../../../services/server.service"; import { ServerService } from "../../../../services/server.service";
import { Query } from "../../../../models/graphql/query.model";
import { Queries } from "../../../../models/graphql/queries.model";
import { catchError } from "rxjs/operators";
import { throwError } from "rxjs";
@Component({ @Component({
selector: "app-server-dashboard", selector: "app-server-dashboard",
@@ -27,14 +31,30 @@ export class ServerDashboardComponent implements OnInit {
ngOnInit(): void { ngOnInit(): void {
this.data.getServerFromRoute(this.route).then(server => { this.data.getServerFromRoute(this.route).then(server => {
this.server = server; // this.server = server;
this.loadServer(server.id);
}); });
this.serverService.server$.subscribe(server => { this.serverService.server$.subscribe(server => {
if (!server) { if (!server) {
return; return;
} }
this.server = server; this.loadServer(server.id);
});
}
loadServer(id?: number): void {
this.data.query<Query>(Queries.serversDashboardQuery, {
filter: {
id: id
}
}).pipe(catchError(err => {
this.spinner.hideSpinner();
return throwError(() => err);
})).subscribe(data => {
this.server = data.servers[0];
this.spinner.hideSpinner();
}); });
} }

View File

@@ -6,6 +6,7 @@ import { SharedModule } from "../../shared/shared.module";
import { ProfileComponent } from "./profile/profile.component"; import { ProfileComponent } from "./profile/profile.component";
import { MembersComponent } from "./members/members.component"; import { MembersComponent } from "./members/members.component";
import { ClientComponent } from "./server-dashboard/components/client/client.component"; import { ClientComponent } from "./server-dashboard/components/client/client.component";
import { ServerStatisticComponent } from './server-dashboard/components/server-statistic/server-statistic.component';
@NgModule({ @NgModule({
@@ -14,6 +15,7 @@ import { ClientComponent } from "./server-dashboard/components/client/client.com
ProfileComponent, ProfileComponent,
MembersComponent, MembersComponent,
ClientComponent, ClientComponent,
ServerStatisticComponent,
], ],
imports: [ imports: [
CommonModule, CommonModule,

View File

@@ -29,6 +29,18 @@ export class DataService {
) { ) {
} }
public getServerIdFromRoute(route: ActivatedRoute): Promise<number> {
return new Promise((resolve, reject) => {
this.spinner.showSpinner();
if (!route.snapshot.params["serverId"] || route.snapshot.params["serverId"] == "undefined") {
this.spinner.hideSpinner();
this.router.navigate(["/dashboard"]);
reject();
}
resolve(route.snapshot.params["serverId"]);
});
}
public getServerFromRoute(route: ActivatedRoute): Promise<Server> { public getServerFromRoute(route: ActivatedRoute): Promise<Server> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this.spinner.showSpinner(); this.spinner.showSpinner();
@@ -50,7 +62,6 @@ export class DataService {
resolve(server); resolve(server);
}); });
}); });
} }
public query<T>(query: string, variables?: Variables, f?: Function): Observable<T> { public query<T>(query: string, variables?: Variables, f?: Function): Observable<T> {

View File

@@ -27,6 +27,7 @@
"cache_max_messages": "Anzahl der Nachrichten im cache", "cache_max_messages": "Anzahl der Nachrichten im cache",
"header": "Bot", "header": "Bot",
"help_url": "Befehlsreferenz", "help_url": "Befehlsreferenz",
"max_steam_offer_count": "Maximal zu betrachtende Steam Angebote",
"ping_urls": "Ping Adressen", "ping_urls": "Ping Adressen",
"technician_ids": "Techniker Ids", "technician_ids": "Techniker Ids",
"wait_for_restart": "Wartezeit vor Neustart", "wait_for_restart": "Wartezeit vor Neustart",
@@ -199,6 +200,10 @@
"role": "Rolle", "role": "Rolle",
"rule_count": "Regeln", "rule_count": "Regeln",
"save": "Speichern", "save": "Speichern",
"state": {
"off": "Aus",
"on": "Ein"
},
"user_warnings": "Verwarnungen", "user_warnings": "Verwarnungen",
"users": "Benutzer", "users": "Benutzer",
"value": "Wert", "value": "Wert",
@@ -359,6 +364,7 @@
}, },
"header": "Dashboard", "header": "Dashboard",
"server": { "server": {
"active_members": "davon aktiv:",
"header": "Server", "header": "Server",
"member_count": "Mitglied(er)" "member_count": "Mitglied(er)"
}, },
@@ -431,6 +437,7 @@
"header": "Bot Konfiguration", "header": "Bot Konfiguration",
"help_voice_channel_id": "Sprachkanal für Hilfsbenachrichtung", "help_voice_channel_id": "Sprachkanal für Hilfsbenachrichtung",
"login_message_channel_id": "Kanal für die Nachricht vom Bot nach Start", "login_message_channel_id": "Kanal für die Nachricht vom Bot nach Start",
"maintenance": "Wartungsmodus",
"max_message_xp_per_hour": "Maximale XP pro Stunde durch Nachrichten", "max_message_xp_per_hour": "Maximale XP pro Stunde durch Nachrichten",
"max_voice_state_hours": "Maximale Stunden für eine ontime nach Bot neustart", "max_voice_state_hours": "Maximale Stunden für eine ontime nach Bot neustart",
"message_delete_timer": "Zeit bis zum löschen einer Botnachricht in sekunden", "message_delete_timer": "Zeit bis zum löschen einer Botnachricht in sekunden",
@@ -452,13 +459,21 @@
} }
}, },
"dashboard": { "dashboard": {
"achievements_achieved": "Errungenschaften vergeben",
"activity_score": "Aktivitätsbewertung",
"deleted_message_count": "Gelöschte Nachrichten", "deleted_message_count": "Gelöschte Nachrichten",
"header": "Server dashboard", "header": "Server dashboard",
"message_count": "Anzahl Nachrichten",
"moved_users_count": "Verschobene Benutzer", "moved_users_count": "Verschobene Benutzer",
"name": "Name", "name": "Name",
"received_command_count": "Empfangene Befehle", "received_command_count": "Empfangene Befehle",
"received_message_count": "Empfangene Nachrichten", "received_message_count": "Empfangene Nachrichten",
"sent_message_count": "Gesendete Nachrichten" "sent_message_count": "Gesendete Nachrichten",
"user_joined_game_server_count": "Anzahl Gameserver beitritte",
"user_joined_game_server_ontime": "Gameserver Ontime",
"user_joined_voice_channel_count": "Anzahl Sprachkanal beitritte",
"user_joined_voice_channel_ontime": "Sprachkanal Ontime",
"user_warning_count": "Anzahl Verwarnungen"
}, },
"header": "Server", "header": "Server",
"levels": { "levels": {

View File

@@ -27,6 +27,7 @@
"cache_max_messages": "Number of messages in cache", "cache_max_messages": "Number of messages in cache",
"header": "Bot", "header": "Bot",
"help_url": "Help URL", "help_url": "Help URL",
"max_steam_offer_count": "Maximum monitoring steam offers",
"ping_urls": "Ping addresses", "ping_urls": "Ping addresses",
"technician_ids": "Technician Ids", "technician_ids": "Technician Ids",
"wait_for_restart": "Time to wait before restart", "wait_for_restart": "Time to wait before restart",
@@ -199,6 +200,10 @@
"role": "Role", "role": "Role",
"rule_count": "Rules", "rule_count": "Rules",
"save": "Save", "save": "Save",
"state": {
"off": "Off",
"on": "On"
},
"user_warnings": "User warnings", "user_warnings": "User warnings",
"users": "User", "users": "User",
"value": "Value", "value": "Value",
@@ -359,6 +364,7 @@
}, },
"header": "Dashboard", "header": "Dashboard",
"server": { "server": {
"active_members": "active:",
"header": "Server", "header": "Server",
"member_count": "Member(s)" "member_count": "Member(s)"
}, },
@@ -431,6 +437,7 @@
"header": "Bot configuration", "header": "Bot configuration",
"help_voice_channel_id": "Voicechannel für help notifications", "help_voice_channel_id": "Voicechannel für help notifications",
"login_message_channel_id": "Channel for bot message after start", "login_message_channel_id": "Channel for bot message after start",
"maintenance": "Maintenance mode",
"max_message_xp_per_hour": "Max xp per hour with message", "max_message_xp_per_hour": "Max xp per hour with message",
"max_voice_state_hours": "Max ontime hours after bot restart", "max_voice_state_hours": "Max ontime hours after bot restart",
"message_delete_timer": "Time to wait before delete bot messages", "message_delete_timer": "Time to wait before delete bot messages",
@@ -452,13 +459,21 @@
} }
}, },
"dashboard": { "dashboard": {
"achievements_achieved": "Achievements achieved",
"activity_score": "Activity score",
"deleted_message_count": "Deleted messages", "deleted_message_count": "Deleted messages",
"header": "Server dashboard", "header": "Server dashboard",
"message_count": "Message count",
"moved_users_count": "Moved users", "moved_users_count": "Moved users",
"name": "Name", "name": "Name",
"received_command_count": "Received commands", "received_command_count": "Received commands",
"received_message_count": "Received messages", "received_message_count": "Received messages",
"sent_message_count": "Sent messages" "sent_message_count": "Sent messages",
"user_joined_game_server_count": "Joined Gameserver count",
"user_joined_game_server_ontime": "Gameserver ontime",
"user_joined_voice_channel_count": "Joined voice channel count",
"user_joined_voice_channel_ontime": "Joined voice channel ontime",
"user_warning_count": "User warning count"
}, },
"header": "Server", "header": "Server",
"levels": { "levels": {

View File

Before

Width:  |  Height:  |  Size: 50 KiB

After

Width:  |  Height:  |  Size: 50 KiB

View File

@@ -163,6 +163,14 @@ header {
} }
} }
td,
th {
p-dropdown,
.p-dropdown {
width: 100%;
}
}
.content-row { .content-row {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@@ -811,8 +819,4 @@ p-inputNumber {
gap: 5px; gap: 5px;
} }
} }
p-dropdown {
width: 100%;
}
} }

View File

@@ -45,7 +45,6 @@ header,
.p-panelmenu-header > a { .p-panelmenu-header > a {
border: none !important; border: none !important;
border-radius: unset !important;
font-weight: unset !important; font-weight: unset !important;
transition: none !important; transition: none !important;
} }

View File

@@ -626,6 +626,27 @@
} }
} }
.p-selectbutton {
.p-highlight {
background-color: $primaryHeaderColor !important;
}
.p-button {
border: 1px solid $primaryHeaderColor !important;
&:hover {
background-color: $secondaryHeaderColor !important;
border: 1px solid $secondaryHeaderColor !important;
}
&:focus {
border-color: $primaryHeaderColor !important;
box-shadow: none !important;
}
}
}
.p-multiselect { .p-multiselect {
background-color: $primaryBackgroundColor !important; background-color: $primaryBackgroundColor !important;
color: $primaryTextColor !important; color: $primaryTextColor !important;