45 Commits

Author SHA1 Message Date
069c701a05 Fixed tests #139 2022-11-27 17:38:22 +01:00
5efcddf983 Fixed purge test #139 2022-11-27 16:08:35 +01:00
5beaf24bdc Fixed afk test #139 2022-11-27 16:01:43 +01:00
4bbcd387c6 Added /purge test #139 2022-11-27 15:11:19 +01:00
117e1aeda8 Added /afk test #139 2022-11-27 14:01:05 +01:00
7260ed0164 Added /info test #139 2022-11-27 11:05:20 +01:00
77d723b9da Improved test architecture #139 2022-11-27 10:56:02 +01:00
75c316f2d2 Improved test architecture #139 2022-11-27 00:42:05 +01:00
de78cec96c Added test settings & improved test architecture #139 2022-11-26 23:09:15 +01:00
9de66d4fd4 Added ui tests for discord bot #139 2022-11-26 18:37:13 +01:00
4a0f5c28c1 Merge pull request '0.3 - Bei message Member.name -> Member.mentions oder so (#100)' (#137) from #100 into 0.3
Reviewed-on: sh-edraft.de/kd_discord_bot#137
Reviewed-by: Ebola-Chan <nick.jungmann@gmail.com>
Closes #100
2022-11-22 18:16:38 +01:00
d2f99003ff Merge pull request '0.3 - Nachrichten sollen länger gezeigt werden (#135)' (#138) from #135 into 0.3
Reviewed-on: sh-edraft.de/kd_discord_bot#138
Reviewed-by: Ebola-Chan <nick.jungmann@gmail.com>
Closes #135
2022-11-22 18:16:21 +01:00
9dd3fd4b8e Updated configs #135 2022-11-21 20:35:55 +01:00
d18500b96c Changed .name -> .mention #100 2022-11-21 20:29:50 +01:00
91fdf34d32 Merge pull request 'Added mass-move command' (#136) from #20 into 0.3
Reviewed-on: sh-edraft.de/kd_discord_bot#136
Reviewed-by: Sven Heidemann <sven.heidemann@sh-edraft.de>
Closes #136
2022-11-21 19:00:34 +01:00
12369cdbe3 Removed unused code for mass-move #20 2022-11-21 18:20:23 +01:00
25c698273a Fixed sending message with translation pipe #20 2022-11-21 00:30:49 +01:00
2868b1afe2 Added messaging to mass-move #20 2022-11-20 23:27:22 +01:00
0d1c15b31d Added mass-move command #20 2022-11-20 19:18:17 +01:00
840da350e4 Merge pull request '0.3 - Login per Discord (#128)' (#129) from #128 into 0.3
Reviewed-on: sh-edraft.de/kd_discord_bot#129
Reviewed-by: Ebola-Chan <nick.jungmann@gmail.com>
Closes #128
2022-11-20 16:54:03 +01:00
bd94c42eae Added discord login & removed discord register #128 2022-11-20 16:09:20 +01:00
c7a925b997 Merge pull request 'Added presence command #18' (#126) from #18 into 0.3
Reviewed-on: sh-edraft.de/kd_discord_bot#126
Reviewed-by: Sven Heidemann <sven.heidemann@sh-edraft.de>
Closes #126
2022-11-20 15:41:26 +01:00
7fb6d22c3f Added requested changes to presence command #18 2022-11-20 15:39:34 +01:00
c5b5297058 Added presence command #18 2022-11-20 06:21:51 +01:00
9ed66c2560 Merge branch '0.3' of https://git.sh-edraft.de/sh-edraft.de/kd_discord_bot into 0.3
 Conflicts:
	kdb-bot/src/modules/technician/command/log_command.py
2022-11-18 15:39:36 +01:00
6e6157ccf2 Fixed log command 2022-11-18 15:34:25 +01:00
f136d6164e Fixed log command 2022-11-18 15:05:39 +01:00
9b5033b80e Fixed some on member join stuff 2022-11-18 14:33:54 +01:00
f5a71a8450 Fixed some on member join stuff 2022-11-18 14:14:01 +01:00
d3279eb7c7 Updated config 2022-11-18 10:23:40 +01:00
ec7aeb8712 Added icmplib 2022-11-18 10:16:59 +01:00
fd609eb923 Merge remote-tracking branch 'origin/0.3' into 0.3
# Conflicts:
#	kdb-bot/cpl-workspace.json
2022-11-18 09:58:28 +01:00
a7dbc75d2e Updated configs 2022-11-18 09:58:05 +01:00
b0459567f4 Fixed workspace 2022-11-18 09:52:27 +01:00
25b7b18013 Fixed workspace 2022-11-18 09:51:06 +01:00
87350cba1a Moved dockerfile 2022-11-18 09:50:06 +01:00
864d181de0 Fixed project files 2022-11-18 09:33:50 +01:00
90011be760 Updated api config ? 2022-11-18 09:30:44 +01:00
47dd6fdc2d Improved build version stuff 2022-11-18 09:30:29 +01:00
8445c23e7f Merge pull request '0.3 - Log Befehl (#44)' (#125) from #44 into 0.3
Reviewed-on: sh-edraft.de/kd_discord_bot#125
Reviewed-by: Sven Heidemann <sven.heidemann@sh-edraft.de>
Closes #44
2022-11-17 23:03:33 +01:00
e6fc41090a Refactored code #44 2022-11-17 23:02:27 +01:00
7c79c6f992 Merge branch '0.3' into #44
# Conflicts:
#	kdb-bot/src/bot/config
2022-11-17 22:54:01 +01:00
7b8dca64bf Finished log command #44 2022-11-17 22:45:10 +01:00
2c7f4647af [WIP] Added log command #44 2022-11-16 21:04:07 +01:00
53604706c2 Added technician module #44 2022-11-14 22:29:43 +01:00
79 changed files with 1605 additions and 358 deletions

3
.gitignore vendored
View File

@@ -143,4 +143,5 @@ PythonImportHelper-v2-Completion.json
deploy/
# idea
.idea/
.idea/
selenium-data/

View File

@@ -1,2 +1,14 @@
# kd_discord_bot
## Test Bot
To test the bot run unittests or call ```cpl test```.
Configure test instance by creating the file ./test/ui_tests/.env and set following environment variables:
```sh
KDB_TEST_DB_PASSWORD=
KDB_TEST_NAME=
KDB_TEST_TOKEN=
KDB_TEST_DISCORD_MAIL=
KDB_TEST_DISCORD_PASSWORD=
```

View File

@@ -13,32 +13,32 @@
"level": "src/modules/level/level.json",
"permission": "src/modules/permission/permission.json",
"stats": "src/modules/stats/stats.json",
"technician": "src/modules/technician/technician.json",
"ui-tests": "test/ui_tests/ui-tests.json",
"ui-tests-shared": "test/ui_tests_shared/ui-tests-shared.json",
"ui-tests-tests": "test/ui_tests_tests/ui-tests-tests.json",
"get-version": "tools/get_version/get-version.json",
"post-build": "tools/post_build/post-build.json",
"set-version": "tools/set_version/set-version.json"
},
"Scripts": {
"sv": "cpl set-version",
"test": "export $(cat test/ui_tests/.env); export PYTHONPATH=$PWD/src:$PYTHONPATH; cpl run ui-tests",
"sv": "cpl set-version $ARGS",
"set-version": "cpl run set-version $ARGS; echo '';",
"gv": "cpl get-version",
"get-version": "export VERSION=$(cpl run get-version); echo $VERSION;",
"pre-build": "cpl set-version $ARGS",
"post-build": "cpl run post-build",
"pre-prod": "cpl build",
"prod": "export KDB_ENVIRONMENT=production; export KDB_NAME=KDB-Prod; cpl start;",
"pre-stage": "cpl build",
"stage": "export KDB_ENVIRONMENT=staging; export KDB_NAME=KDB-Stage; cpl start;",
"pre-dev": "cpl build",
"dev": "export KDB_ENVIRONMENT=development; export KDB_NAME=KDB-Dev; cpl start;",
"docker-build": "cpl b; docker-compose down; docker build -t kdb-bot/kdb-bot:$(cpl gv) .",
"docker-compose": "docker-compose up -d",
"docker": "cpl docker-build; cpl docker-compose;"
"docker-build": "cpl build $ARGS; docker build -t kdb-bot/kdb-bot:$(cpl gv) .;",
"dc-up": "docker-compose up -d",
"dc-down": "docker-compose down",
"docker": "cpl dc-down; cpl docker-build; cpl dc-up;"
}
}
}

17
kdb-bot/dockerfile Normal file
View File

@@ -0,0 +1,17 @@
# syntax=docker/dockerfile:1
FROM python:3.10.4-alpine
WORKDIR /app
COPY ./dist/bot/build/ .
RUN python -m pip install --upgrade pip
RUN apk update
RUN apk add --update alpine-sdk linux-headers
RUN apk add bash
RUN apk add nano
RUN pip install -r requirements.txt --extra-index-url https://pip.sh-edraft.de
RUN pip install flask[async]
CMD [ "bash", "/app/bot/bot"]

View File

@@ -16,7 +16,7 @@
"LicenseName": "MIT",
"LicenseDescription": "MIT, see LICENSE for more details.",
"Dependencies": [
"cpl-core==2022.10.0.post7",
"cpl-core==2022.10.0.post9",
"cpl-translation==2022.10.0.post2",
"cpl-query==2022.10.0.post2",
"cpl-discord==2022.10.0.post6",
@@ -27,7 +27,8 @@
"waitress==2.1.2",
"Flask-SocketIO==5.3.1",
"eventlet==0.33.1",
"requests-oauthlib==1.3.1"
"requests-oauthlib==1.3.1",
"icmplib==3.0.3"
],
"DevDependencies": [
"cpl-cli==2022.10.0"
@@ -59,9 +60,9 @@
"../modules/boot_log/boot-log.json",
"../modules/database/database.json",
"../modules/level/level.json",
"../modules/permission/level.json",
"../modules/permission/permission.json",
"../modules/permission/stats.json"
"../modules/stats/stats.json",
"../modules/technician/technician.json"
]
}
}

View File

@@ -11,6 +11,7 @@ from modules.database.database_module import DatabaseModule
from modules.level.level_module import LevelModule
from modules.permission.permission_module import PermissionModule
from modules.stats.stats_module import StatsModule
from modules.technician.technician_module import TechnicianModule
class ModuleList:
@@ -21,13 +22,14 @@ class ModuleList:
return List(type, [
CoreModule, # has to be first!
DataModule,
PermissionModule,
DatabaseModule,
AutoRoleModule,
BaseModule,
DatabaseModule,
LevelModule,
PermissionModule,
ApiModule,
StatsModule,
TechnicianModule,
# has to be last!
BootLogModule,
CoreExtensionModule,

View File

@@ -72,11 +72,6 @@
}
},
"modules": {
"admin": {
"restart_message": "Bin gleich wieder da :D",
"shutdown_message": "Trauert nicht um mich, es war eine logische Entscheidung. Das Wohl von Vielen, es wiegt schwerer als das Wohl von Wenigen oder eines Einzelnen. Ich war es und ich werde es immer sein, Euer Freund. Lebt lange und in Frieden :)",
"deploy_message": "Der neue Stand wurde hochgeladen."
},
"auto_role": {
"list": {
"title": "Beobachtete Nachrichten:",
@@ -156,6 +151,15 @@
},
"footer": ""
},
"mass_move": {
"moved": "Alle Personen aus {} wurden nach {} verschoben.",
"channel_from_error": "Du musst dich in einem Voicechannel befinden oder die Option \"channel_from\" mit angeben."
},
"presence": {
"changed": "Presence wurde geändert.",
"removed": "Presence wurde entfernt.",
"max_char_count_exceeded": "Der Text darf nicht mehr als 128 Zeichen lang sein!"
},
"user_info": {
"fields": {
"id": "Id",
@@ -249,6 +253,11 @@
"failed": "Statistik kann nicht gelöscht werden :(",
"success": "Statistik wurde gelöscht :D"
}
},
"technician": {
"restart_message": "Bin gleich wieder da :D",
"shutdown_message": "Trauert nicht um mich, es war eine logische Entscheidung. Das Wohl von Vielen, es wiegt schwerer als das Wohl von Wenigen oder eines Einzelnen. Ich war es und ich werde es immer sein, Euer Freund. Lebt lange und in Frieden :)",
"log_message": "Hier sind deine Logdateien! :)"
}
},
"api": {

View File

@@ -70,6 +70,9 @@ class AuthServiceABC(ABC):
@abstractmethod
async def login_async(self, user_dto: AuthUserDTO) -> TokenDTO: pass
@abstractmethod
async def login_discord_async(self, oauth_dto: AuthUserDTO) -> TokenDTO: pass
@abstractmethod
async def refresh_async(self, token_dto: TokenDTO) -> TokenDTO: pass

View File

@@ -82,8 +82,18 @@ class AuthDiscordController:
), response['id'])
return jsonify(result.to_dict())
@Route.post(f'{BasePath}/register')
async def discord_register(self):
dto: OAuthDTO = JSONProcessor.process(OAuthDTO, request.get_json(force=True, silent=True))
await self._auth_service.add_auth_user_by_oauth_async(dto)
return '', 200
@Route.get(f'{BasePath}/login')
async def discord_login(self) -> Response:
response = self._get_user_from_discord_response()
dto = AuthUserDTO(
0,
response['username'],
response['discriminator'],
response['email'],
str(uuid.uuid4()),
None,
AuthRoleEnum.normal
)
result = await self._auth_service.login_discord_async(dto)
return jsonify(result.to_dict())

View File

@@ -460,6 +460,24 @@ class AuthService(AuthServiceABC):
self._db.save_changes()
return TokenDTO(token, refresh_token)
async def login_discord_async(self, user_dto: AuthUserDTO) -> TokenDTO:
if user_dto is None:
raise ServiceException(ServiceErrorCode.InvalidData, 'User not set')
db_user = self._auth_users.find_auth_user_by_email(user_dto.email)
if db_user is None:
await self.add_auth_user_async(user_dto)
# raise ServiceException(ServiceErrorCode.InvalidUser, f'User not found')
db_user = self._auth_users.get_auth_user_by_email(user_dto.email)
token = self.generate_token(db_user)
refresh_token = self._create_and_save_refresh_token(db_user)
if db_user.forgot_password_id is not None:
db_user.forgot_password_id = None
self._db.save_changes()
return TokenDTO(token, refresh_token)
async def refresh_async(self, token_dto: TokenDTO) -> TokenDTO:
if token_dto is None:
raise ServiceException(ServiceErrorCode.InvalidData, f'Token not set')

View File

@@ -13,10 +13,14 @@ class CustomFileLoggerABC(Logger, ABC):
@abstractmethod
def __init__(self, key: str, config: ConfigurationABC, time_format: TimeFormatSettings, env: ApplicationEnvironmentABC):
self._key = key
settings: LoggingSettings = config.get_configuration(f'{FileLoggingSettings.__name__}_{key}')
Logger.__init__(self, settings, time_format, env)
self._settings: LoggingSettings = config.get_configuration(f'{FileLoggingSettings.__name__}_{key}')
Logger.__init__(self, self._settings, time_format, env)
self._begin_log()
@property
def settings(self) -> LoggingSettings:
return self._settings
def _begin_log(self):
console_level = self._console.value
self._console = LoggingLevelEnum.OFF

View File

@@ -28,4 +28,4 @@ class MessageServiceABC(ABC):
async def send_ctx_msg(self, ctx: Context, message: Union[str, discord.Embed], file: discord.File = None, is_persistent: bool = False, wait_before_delete: int = None, without_tracking=True): pass
@abstractmethod
async def send_interaction_msg(self, interaction: Interaction, message: Union[str, discord.Embed], is_persistent: bool = False, wait_before_delete: int = None, without_tracking=True): pass
async def send_interaction_msg(self, interaction: Interaction, message: Union[str, discord.Embed], is_persistent: bool = False, wait_before_delete: int = None, without_tracking=True, **kwargs): pass

View File

@@ -119,7 +119,7 @@ class MessageService(MessageServiceABC):
if ctx.guild is not None:
await self.delete_message(msg, without_tracking)
async def send_interaction_msg(self, interaction: Interaction, message: Union[str, discord.Embed], is_persistent: bool = False, wait_before_delete: int = None, without_tracking=False):
async def send_interaction_msg(self, interaction: Interaction, message: Union[str, discord.Embed], is_persistent: bool = False, wait_before_delete: int = None, without_tracking=False, **kwargs):
if interaction is None:
self._logger.warn(__name__, 'Message context is empty')
self._logger.debug(__name__, f'Message: {message}')
@@ -128,9 +128,9 @@ class MessageService(MessageServiceABC):
self._logger.debug(__name__, f'Try to send message\t\t{message}\n\tto: {interaction.channel}')
try:
if isinstance(message, discord.Embed):
await interaction.response.send_message(embed=message)
await interaction.response.send_message(embed=message, **kwargs)
else:
await interaction.response.send_message(message)
await interaction.response.send_message(message, **kwargs)
except Exception as e:
self._logger.error(__name__, f'Send message to channel {interaction.channel.id} failed', e)
else:

View File

@@ -98,7 +98,7 @@ class AutoRoleGroup(DiscordCommandABC):
message = List(discord.Message, [message async for message in channel.history(limit=50)]).where(lambda m: m.id == int(message_id)).single_or_default()
if message is None:
self._logger.debug(__name__, f'Message with id {message_id} not found in {channel.name}')
await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.auto_role.add.error.not_found').format(message_id, channel.name))
await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.auto_role.add.error.not_found').format(message_id, channel.mention))
self._logger.trace(__name__, f'Finished command auto-role add')
return
@@ -250,7 +250,7 @@ class AutoRoleGroup(DiscordCommandABC):
except Exception as e:
self._logger.error(__name__, f'Cannot add reaction {rule.emoji_name} to message: {auto_role_from_db.discord_message_id}', e)
await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.auto_role.rule.add.success').format(emoji, role.name, auto_role))
await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.auto_role.rule.add.success').format(emoji, role.mention, auto_role))
self._logger.trace(__name__, f'Finished command auto-role rule add')
@add.autocomplete('auto_role')

View File

@@ -10,10 +10,10 @@ from modules.base.abc.base_helper_abc import BaseHelperABC
from modules.base.command.afk_command import AFKCommand
from modules.base.command.help_command import HelpCommand
from modules.base.command.info_command import InfoCommand
from modules.base.command.mass_move_command import MassMoveCommand
from modules.base.command.ping_command import PingCommand
from modules.base.command.presence_command import PresenceCommand
from modules.base.command.purge_command import PurgeCommand
from modules.base.command.restart_command import RestartCommand
from modules.base.command.shutdown_command import ShutdownCommand
from modules.base.command.user_group import UserGroup
from modules.base.events.base_on_command_error_event import BaseOnCommandErrorEvent
from modules.base.events.base_on_command_event import BaseOnCommandEvent
@@ -43,10 +43,10 @@ class BaseModule(ModuleABC):
self._dc.add_command(AFKCommand)
self._dc.add_command(HelpCommand)
self._dc.add_command(InfoCommand)
self._dc.add_command(MassMoveCommand)
self._dc.add_command(PingCommand)
self._dc.add_command(PresenceCommand)
self._dc.add_command(RestartCommand)
self._dc.add_command(ShutdownCommand)
self._dc.add_command(PurgeCommand)
self._dc.add_command(UserGroup)
# events

View File

@@ -0,0 +1,49 @@
import asyncio
import discord
from cpl_discord.command import DiscordCommandABC
from cpl_discord.service import DiscordBotServiceABC
from cpl_translation import TranslatePipe
from discord.ext import commands
from discord.ext.commands import Context
from bot_core.abc.message_service_abc import MessageServiceABC
from bot_core.helper.command_checks import CommandChecks
from bot_core.logging.command_logger import CommandLogger
class MassMoveCommand(DiscordCommandABC):
def __init__(
self,
logger: CommandLogger,
message_service: MessageServiceABC,
bot: DiscordBotServiceABC,
translate: TranslatePipe,
):
DiscordCommandABC.__init__(self)
self._logger = logger
self._message_service = message_service
self._bot = bot
self._t = translate
@commands.hybrid_command(name='mass-move')
@CommandChecks.check_is_ready()
@CommandChecks.check_is_member_moderator()
async def mass_move(self, ctx: Context, channel_to: discord.VoiceChannel,
channel_from: discord.VoiceChannel = None):
self._logger.debug(__name__, f'Received command mass-move {ctx}')
if channel_from is None and ctx.author.voice is None:
await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.base.mass_move.channel_from_error'))
return
if channel_from is None:
channel_from = ctx.author.voice.channel
moves = [member.move_to(channel_to) for member in channel_from.members]
await asyncio.gather(*moves)
await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.base.mass_move.moved').format(channel_from.mention, channel_to.mention))
self._logger.trace(__name__, f'Finished mass-move command')

View File

@@ -0,0 +1,48 @@
import discord
from cpl_discord.command import DiscordCommandABC
from cpl_discord.service import DiscordBotServiceABC
from cpl_translation import TranslatePipe
from discord.ext import commands
from discord.ext.commands import Context
from bot_core.abc.message_service_abc import MessageServiceABC
from bot_core.helper.command_checks import CommandChecks
from bot_core.logging.command_logger import CommandLogger
class PresenceCommand(DiscordCommandABC):
def __init__(
self,
logger: CommandLogger,
message_service: MessageServiceABC,
bot: DiscordBotServiceABC,
translate: TranslatePipe,
):
DiscordCommandABC.__init__(self)
self._logger = logger
self._message_service = message_service
self._bot = bot
self._t = translate
@commands.hybrid_command()
@commands.guild_only()
@CommandChecks.check_is_ready()
@CommandChecks.check_is_member_moderator()
async def presence(self, ctx: Context, text: str = ''):
self._logger.debug(__name__, f'Received command presence {ctx}')
if text == '':
await self._bot.change_presence(activity=None)
await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.base.presence.removed'))
return
if len(text) > 128:
await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.base.presence.max_char_count_exceeded'))
return
await self._bot.change_presence(activity=discord.Game(name=text))
await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.base.presence.changed'))
self._logger.trace(__name__, f'Finished presence command')

View File

@@ -70,17 +70,17 @@ class BaseOnMemberJoinEvent(OnMemberJoinABC):
await self._messenger.send_dm_message(self._t.transform('modules.base.welcome_message').format(member.guild.name), member)
for admin in self._permission_service.get_admins(member.guild.id):
await self._messenger.send_dm_message(self._t.transform('modules.base.welcome_message_for_team').format(member.name), admin)
await self._messenger.send_dm_message(self._t.transform('modules.base.welcome_message_for_team').format(member.mention), admin)
for moderator in self._permission_service.get_moderators(member.guild.id):
await self._messenger.send_dm_message(self._t.transform('modules.base.welcome_message_for_team').format(member.name), moderator)
await self._messenger.send_dm_message(self._t.transform('modules.base.welcome_message_for_team').format(member.mention), moderator)
try:
server = self._servers.get_server_by_discord_id(member.guild.id)
user = self._users.find_user_by_discord_id_and_server_id(member.id, server.server_id)
if user is not None:
self._user_joins.add_user_joined_server(UserJoinedServer(user, datetime.now()))
self._db.save_changes()
return
self._logger.debug(__name__, f'Add user: {member.id}')

View File

@@ -46,7 +46,7 @@ class BaseOnVoiceStateUpdateEventHelpChannel(OnVoiceStateUpdateABC):
mods = [*self._permissions.get_admins(member.guild.id), *self._permissions.get_moderators(member.guild.id)]
for a in mods:
await self._message_service.send_dm_message(
self._t.transform('modules.base.member_joined_help_voice_channel').format(member.name),
self._t.transform('modules.base.member_joined_help_voice_channel').format(member.mention),
a,
)

View File

@@ -0,0 +1 @@
# imports

View File

@@ -303,7 +303,7 @@ class LevelGroup(DiscordCommandABC):
levels = self._levels.get_levels_by_server_id(server.server_id).order_by(lambda l: l.min_xp)
if level == levels.first():
await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.down.already_first').format(member.name))
await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.down.already_first').format(member.mention))
self._logger.trace(__name__, f'Finished command level down')
return
@@ -312,11 +312,11 @@ class LevelGroup(DiscordCommandABC):
user.xp = new_level.min_xp
self._users.update_user(user)
self._db.save_changes()
await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.down.success').format(member.name, new_level.name))
await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.down.success').format(member.mention, new_level.name))
await self._level_service.set_level(user)
except Exception as e:
self._logger.error(__name__, f'Cannot level down {member.name} with level {level.name}', e)
await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.down.failed').format(member.name))
await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.down.failed').format(member.mention))
self._logger.trace(__name__, f'Finished command level down')
@@ -336,7 +336,7 @@ class LevelGroup(DiscordCommandABC):
levels = self._levels.get_levels_by_server_id(server.server_id).order_by(lambda l: l.min_xp)
if level.name == levels.last().name:
await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.up.already_last').format(member.name))
await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.up.already_last').format(member.mention))
self._logger.trace(__name__, f'Finished command level up')
return
@@ -345,11 +345,11 @@ class LevelGroup(DiscordCommandABC):
user.xp = new_level.min_xp
self._users.update_user(user)
self._db.save_changes()
await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.up.success').format(member.name, new_level.name))
await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.up.success').format(member.mention, new_level.name))
await self._level_service.set_level(user)
except Exception as e:
self._logger.error(__name__, f'Cannot level up {member.name} with level {level.name}', e)
await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.up.failed').format(member.name))
await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.up.failed').format(member.mention))
self._logger.trace(__name__, f'Finished command level up')
@@ -374,7 +374,7 @@ class LevelGroup(DiscordCommandABC):
return
if current_level.name == level:
await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.set.already_level').format(member.name, level))
await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.set.already_level').format(member.mention, level))
self._logger.trace(__name__, f'Finished command level set')
return
@@ -382,11 +382,11 @@ class LevelGroup(DiscordCommandABC):
user.xp = new_level.min_xp
self._users.update_user(user)
self._db.save_changes()
await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.set.success').format(member.name, new_level.name))
await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.set.success').format(member.mention, new_level.name))
await self._level_service.set_level(user)
except Exception as e:
self._logger.error(__name__, f'Cannot set level {level} for {member.name}', e)
await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.set.failed').format(member.name))
await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.level.set.failed').format(member.mention))
self._logger.trace(__name__, f'Finished command level set')

View File

@@ -0,0 +1,23 @@
import discord
from cpl_discord.events import OnMemberJoinABC
from bot_core.helper.event_checks import EventChecks
from bot_core.logging.message_logger import MessageLogger
from modules.level.service.level_service import LevelService
class LevelOnMemberJoinEvent(OnMemberJoinABC):
def __init__(
self,
logger: MessageLogger,
level: LevelService
):
OnMemberJoinABC.__init__(self)
self._logger = logger
self._level = level
@EventChecks.check_is_ready()
async def on_member_join(self, member: discord.Member):
self._logger.debug(__name__, f'Module {type(self)} started')
await self._level.check_level(member)

View File

@@ -22,9 +22,7 @@
"cpl-cli>=2022.10.1"
],
"PythonVersion": ">=3.10.4",
"PythonPath": {
"linux": ""
},
"PythonPath": {},
"Classifiers": []
},
"BuildSettings": {

View File

@@ -9,6 +9,7 @@ from cpl_discord.service.discord_collection_abc import DiscordCollectionABC
from bot_core.abc.module_abc import ModuleABC
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
from modules.level.command.level_group import LevelGroup
from modules.level.events.level_on_member_join_event import LevelOnMemberJoinEvent
from modules.level.events.level_on_message_event import LevelOnMessageEvent
from modules.level.events.level_on_voice_state_update_event import LevelOnVoiceStateUpdateEvent
from modules.level.level_seeder import LevelSeeder
@@ -36,3 +37,4 @@ class LevelModule(ModuleABC):
# events
self._dc.add_event(DiscordEventTypesEnum.on_message.value, LevelOnMessageEvent)
self._dc.add_event(DiscordEventTypesEnum.on_voice_state_update.value, LevelOnVoiceStateUpdateEvent)
self._dc.add_event(DiscordEventTypesEnum.on_member_join.value, LevelOnMemberJoinEvent)

View File

@@ -88,5 +88,6 @@ class LevelService:
user = self._users.find_user_by_discord_id_and_server_id(member.id, server.server_id)
if user is None:
self._logger.warn(__name__, f'User not found {member.guild.name}@{member.name}')
return
await self.set_level(user)

View File

@@ -22,9 +22,7 @@
"cpl-cli>=2022.10.1"
],
"PythonVersion": ">=3.10.4",
"PythonPath": {
"linux": ""
},
"PythonPath": {},
"Classifiers": []
},
"BuildSettings": {

View File

@@ -0,0 +1 @@
# imports:

View File

@@ -0,0 +1,116 @@
import os
from string import Template
from zipfile import ZipFile
import discord
from cpl_core.dependency_injection import ServiceProviderABC
from cpl_core.environment import ApplicationEnvironmentABC
from cpl_core.logging import LoggingSettings
from cpl_core.time import TimeFormatSettings
from cpl_discord.command import DiscordCommandABC
from cpl_query.extension import List
from cpl_translation import TranslatePipe
from discord.ext import commands
from discord.ext.commands import Context
from bot_core.abc.client_utils_service_abc import ClientUtilsServiceABC
from bot_core.abc.custom_file_logger_abc import CustomFileLoggerABC
from bot_core.abc.message_service_abc import MessageServiceABC
from bot_core.helper.command_checks import CommandChecks
from bot_core.logging.command_logger import CommandLogger
from modules.permission.abc.permission_service_abc import PermissionServiceABC
class LogCommand(DiscordCommandABC):
def __init__(
self,
logger: CommandLogger,
logging_settings: LoggingSettings,
services: ServiceProviderABC,
message_service: MessageServiceABC,
client_utils: ClientUtilsServiceABC,
translate: TranslatePipe,
permissions: PermissionServiceABC,
time_format: TimeFormatSettings,
env: ApplicationEnvironmentABC
):
DiscordCommandABC.__init__(self)
self._logger = logger
self._logging_settings = logging_settings
self._services = services
self._message_service = message_service
self._client_utils = client_utils
self._t = translate
self._permissions = permissions
self._env = env
self._log_settings: LoggingSettings = logging_settings
self._time_format_settings: TimeFormatSettings = time_format
self._logger.trace(__name__, f'Loaded command service: {type(self).__name__}')
def _reduce_path(self, p: str) -> str:
if len(p.split('/')) == 1 or p == '':
return p
return self._reduce_path(os.path.dirname(p))
@commands.hybrid_command()
@commands.guild_only()
@CommandChecks.check_is_ready()
@CommandChecks.check_is_member_technician()
async def log(self, ctx: Context):
self._logger.debug(__name__, f'Received command log {ctx}')
possible_log_paths = List(str)
possible_log_paths.append(self._reduce_path(self._logging_settings.path))
file_extensions = List(str)
if '.' in self._logging_settings.filename:
split_filename = self._logging_settings.filename.split(".")
file_extensions.append(f'.{split_filename[len(split_filename) - 1]}')
for subclass in CustomFileLoggerABC.__subclasses__():
logger: CustomFileLoggerABC = self._services.get_service(subclass)
if logger is None:
continue
path = self._reduce_path(logger.settings.path)
if '.' in logger.settings.filename:
split_filename = logger.settings.filename.split(".")
file_extension = f'.{split_filename[len(split_filename) - 1]}'
if file_extension not in file_extensions:
file_extensions.append(file_extension)
if path in possible_log_paths:
continue
possible_log_paths.append(path)
files_str = "\n\t".join(possible_log_paths.to_list())
self._logger.debug(__name__, f'Possible log files: \n\t{files_str}')
files = List(str)
for possible_path in possible_log_paths:
for r, d, f in os.walk(possible_path):
for file in f:
if '.' not in file:
continue
split_filename = file.split(".")
if f'.{split_filename[len(split_filename) - 1]}' not in file_extensions:
continue
files.append(os.path.join(r, file))
files_str = "\n\t".join(files.to_list())
self._logger.debug(__name__, f'Log files: \n\t{files_str}')
zip_file = ZipFile('logs.zip', 'w')
files.for_each(lambda x: zip_file.write(x))
zip_file.close()
await self._message_service.send_interaction_msg(ctx.interaction, self._t.transform('modules.technician.log_message'), file=discord.File(zip_file.filename, 'logs.zip'), ephemeral=True)
os.remove(zip_file.filename)
self._logger.trace(__name__, f'Finished log command')

View File

@@ -44,13 +44,13 @@ class RestartCommand(DiscordCommandABC):
@commands.hybrid_command()
@commands.guild_only()
@CommandChecks.check_is_ready()
@CommandChecks.check_is_member_moderator()
@CommandChecks.check_is_member_technician()
async def restart(self, ctx: Context):
self._logger.debug(__name__, f'Received command restart {ctx}')
self._config.add_configuration('IS_RESTART', 'true')
await self._client_utils.presence_game('common.presence.restart')
await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.admin.restart_message'))
await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.technician.restart_message'))
await asyncio.sleep(self._settings.wait_for_restart)
await self._bot.stop_async()

View File

@@ -45,12 +45,12 @@ class ShutdownCommand(DiscordCommandABC):
@commands.hybrid_command()
@commands.guild_only()
@CommandChecks.check_is_ready()
@CommandChecks.check_is_member_moderator()
@CommandChecks.check_is_member_technician()
async def shutdown(self, ctx: Context):
self._logger.debug(__name__, f'Received command shutdown {ctx}')
await self._client_utils.presence_game('common.presence.shutdown')
await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.admin.shutdown_message'))
await self._message_service.send_ctx_msg(ctx, self._t.transform('modules.technician.shutdown_message'))
await asyncio.sleep(self._settings.wait_for_shutdown)
await self._bot.stop_async()

View File

@@ -0,0 +1,44 @@
{
"ProjectSettings": {
"Name": "technician",
"Version": {
"Major": "0",
"Minor": "0",
"Micro": "0"
},
"Author": "",
"AuthorEmail": "",
"Description": "",
"LongDescription": "",
"URL": "",
"CopyrightDate": "",
"CopyrightName": "",
"LicenseName": "",
"LicenseDescription": "",
"Dependencies": [
"cpl-core>=2022.10.0.post7"
],
"DevDependencies": [
"cpl-cli>=2022.10.0"
],
"PythonVersion": ">=3.10.6",
"PythonPath": {},
"Classifiers": []
},
"BuildSettings": {
"ProjectType": "library",
"SourcePath": "",
"OutputPath": "../../dist",
"Main": "technician.main",
"EntryPoint": "technician",
"IncludePackageData": false,
"Included": [],
"Excluded": [
"*/__pycache__",
"*/logs",
"*/tests"
],
"PackageData": {},
"ProjectReferences": []
}
}

View File

@@ -0,0 +1,29 @@
from cpl_core.configuration import ConfigurationABC
from cpl_core.dependency_injection import ServiceCollectionABC
from cpl_core.environment import ApplicationEnvironmentABC
from cpl_discord.service.discord_collection_abc import DiscordCollectionABC
from bot_core.abc.module_abc import ModuleABC
from bot_core.configuration.feature_flags_enum import FeatureFlagsEnum
from modules.base.abc.base_helper_abc import BaseHelperABC
from modules.technician.command.log_command import LogCommand
from modules.technician.command.restart_command import RestartCommand
from modules.technician.command.shutdown_command import ShutdownCommand
from modules.base.service.base_helper_service import BaseHelperService
class TechnicianModule(ModuleABC):
def __init__(self, dc: DiscordCollectionABC):
ModuleABC.__init__(self, dc, FeatureFlagsEnum.base_module)
def configure_configuration(self, config: ConfigurationABC, env: ApplicationEnvironmentABC):
pass
def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC):
services.add_transient(BaseHelperABC, BaseHelperService)
# commands
self._dc.add_command(RestartCommand)
self._dc.add_command(ShutdownCommand)
self._dc.add_command(LogCommand)
# events

View File

@@ -0,0 +1 @@
# imports:

View File

@@ -0,0 +1,16 @@
{
"DatabaseSettings": {
"Host": "localhost",
"User": "kd_kdb",
"Password": "",
"Database": "keksdose_bot_dev",
"Charset": "utf8mb4",
"UseUnicode": "true",
"Buffered": "true",
"AuthPlugin": "mysql_native_password"
},
"DiscordBot": {
"Token": "",
"Prefix": "!kab-e "
}
}

View File

@@ -0,0 +1,54 @@
{
"TimeFormatSettings": {
"DateFormat": "%Y-%m-%d",
"TimeFormat": "%H:%M:%S",
"DateTimeFormat": "%Y-%m-%d %H:%M:%S.%f",
"DateTimeLogFormat": "%Y-%m-%d_%H-%M-%S"
},
"LoggingSettings": {
"Path": "logs/$date_now/",
"Filename": "bot.log",
"ConsoleLogLevel": "ERROR",
"FileLogLevel": "WARN"
},
"BotLoggingSettings": {
"Api": {
"Path": "logs/$date_now/",
"Filename": "api.log",
"ConsoleLogLevel": "INFO",
"FileLogLevel": "DEBUG"
},
"Command": {
"Path": "logs/$date_now/",
"Filename": "commands.log",
"ConsoleLogLevel": "INFO",
"FileLogLevel": "DEBUG"
},
"Database": {
"Path": "logs/$date_now/",
"Filename": "database.log",
"ConsoleLogLevel": "INFO",
"FileLogLevel": "DEBUG"
},
"Message": {
"Path": "logs/$date_now/",
"Filename": "message.log",
"ConsoleLogLevel": "INFO",
"FileLogLevel": "DEBUG"
}
},
"Translation": {
"DefaultLanguage": "de",
"Languages": [
"de"
]
},
"TestSettings": {
"LoginUrl": "https://discord.com/login",
"MePageUrl": "https://discord.com/channels/@me",
"CmdURL": "https://discord.com/channels/910199451145076828/911578636899987526",
"GuildId": 910199451145076828,
"BotId": 998159802393964594,
"TestUserId": 401941112010571777
}
}

View File

@@ -0,0 +1,34 @@
import asyncio
from cpl_core.application import ApplicationABC
from cpl_core.application import ApplicationBuilder
from startup_test_extension import StartupTestExtension
from ui_tests.startup import Startup
from ui_tests_shared.declarations import Declarations
try:
from test_application import TestApplication
except ImportError:
from .test_application import TestApplication
def get_app() -> ApplicationABC:
app_builder = ApplicationBuilder(TestApplication) \
.use_extension(StartupTestExtension) \
.use_startup(Startup)
app = app_builder.build()
Declarations.app = app
return app
async def main():
app = get_app()
await app.run_async()
if __name__ == '__main__':
import nest_asyncio
nest_asyncio.apply()
asyncio.run(main())

View File

@@ -0,0 +1,118 @@
import os
from typing import Optional
from cpl_core.application import StartupABC
from cpl_core.configuration import ConfigurationABC
from cpl_core.database import DatabaseSettings
from cpl_core.dependency_injection import ServiceCollectionABC
from cpl_core.dependency_injection import ServiceProviderABC
from cpl_core.environment import ApplicationEnvironment
from cpl_discord import get_discord_collection
from cpl_discord.discord_event_types_enum import DiscordEventTypesEnum
from dotenv import load_dotenv
from bot.startup_settings_extension import StartupSettingsExtension
from bot_core.abc.client_utils_service_abc import ClientUtilsServiceABC
from bot_core.abc.custom_file_logger_abc import CustomFileLoggerABC
from bot_core.abc.message_service_abc import MessageServiceABC
from bot_core.configuration.bot_logging_settings import BotLoggingSettings
from bot_core.logging.command_logger import CommandLogger
from bot_core.logging.database_logger import DatabaseLogger
from bot_core.logging.message_logger import MessageLogger
from bot_core.pipes.date_time_offset_pipe import DateTimeOffsetPipe
from bot_core.service.client_utils_service import ClientUtilsService
from bot_core.service.message_service import MessageService
from bot_data.abc.auth_user_repository_abc import AuthUserRepositoryABC
from bot_data.abc.auto_role_repository_abc import AutoRoleRepositoryABC
from bot_data.abc.client_repository_abc import ClientRepositoryABC
from bot_data.abc.known_user_repository_abc import KnownUserRepositoryABC
from bot_data.abc.level_repository_abc import LevelRepositoryABC
from bot_data.abc.server_repository_abc import ServerRepositoryABC
from bot_data.abc.statistic_repository_abc import StatisticRepositoryABC
from bot_data.abc.user_joined_server_repository_abc import UserJoinedServerRepositoryABC
from bot_data.abc.user_joined_voice_channel_abc import UserJoinedVoiceChannelRepositoryABC
from bot_data.abc.user_repository_abc import UserRepositoryABC
from bot_data.db_context import DBContext
from bot_data.service.auth_user_repository_service import AuthUserRepositoryService
from bot_data.service.auto_role_repository_service import AutoRoleRepositoryService
from bot_data.service.client_repository_service import ClientRepositoryService
from bot_data.service.known_user_repository_service import KnownUserRepositoryService
from bot_data.service.level_repository_service import LevelRepositoryService
from bot_data.service.seeder_service import SeederService
from bot_data.service.server_repository_service import ServerRepositoryService
from bot_data.service.statistic_repository_service import StatisticRepositoryService
from bot_data.service.user_joined_server_repository_service import UserJoinedServerRepositoryService
from bot_data.service.user_joined_voice_channel_service import UserJoinedVoiceChannelRepositoryService
from bot_data.service.user_repository_service import UserRepositoryService
from ui_tests.test_on_ready_event import TestOnReadyEvent
class Startup(StartupABC):
def __init__(self):
StartupABC.__init__(self)
self._config: Optional[ConfigurationABC] = None
def configure_configuration(self, configuration: ConfigurationABC, environment: ApplicationEnvironment) -> ConfigurationABC:
load_dotenv()
configuration.add_environment_variables('KDB_TEST_')
configuration.add_environment_variables('DISCORD_')
cwd = os.path.dirname(os.path.realpath(__file__))
configuration.add_json_file(f'{cwd}/config/appsettings.json', optional=False)
configuration.add_json_file(f'{cwd}/config/appsettings.{environment.host_name}.json', optional=True)
StartupSettingsExtension._configure_settings_with_sub_settings(configuration, BotLoggingSettings, lambda x: x.files, lambda x: x.key)
self._config = configuration
return configuration
def configure_services(self, services: ServiceCollectionABC, environment: ApplicationEnvironment) -> ServiceProviderABC:
services.add_logging()
services.add_singleton(CustomFileLoggerABC, CommandLogger)
services.add_singleton(CustomFileLoggerABC, DatabaseLogger)
services.add_singleton(CustomFileLoggerABC, MessageLogger)
services.add_translation()
db_settings: DatabaseSettings = self._config.get_configuration(DatabaseSettings)
db_settings_with_pw = DatabaseSettings()
pw = self._config.get_configuration('DB_PASSWORD')
db_settings_with_pw.from_dict({
"Host": db_settings.host,
"User": db_settings.user,
"Password": '' if pw is None else pw,
"Database": db_settings.database,
"Charset": db_settings.charset,
"UseUnicode": db_settings.use_unicode,
"Buffered": db_settings.buffered,
"AuthPlugin": db_settings.auth_plugin
})
services.add_db_context(DBContext, db_settings_with_pw)
services.add_discord()
dc = get_discord_collection(services)
dc.add_event(DiscordEventTypesEnum.on_ready.value, TestOnReadyEvent)
# bot_core stuff
services.add_transient(MessageServiceABC, MessageService)
services.add_transient(ClientUtilsServiceABC, ClientUtilsService)
# pipes
services.add_transient(DateTimeOffsetPipe)
# data stuff
services.add_transient(AuthUserRepositoryABC, AuthUserRepositoryService)
services.add_transient(ServerRepositoryABC, ServerRepositoryService)
services.add_transient(UserRepositoryABC, UserRepositoryService)
services.add_transient(ClientRepositoryABC, ClientRepositoryService)
services.add_transient(KnownUserRepositoryABC, KnownUserRepositoryService)
services.add_transient(UserJoinedServerRepositoryABC, UserJoinedServerRepositoryService)
services.add_transient(UserJoinedVoiceChannelRepositoryABC, UserJoinedVoiceChannelRepositoryService)
services.add_transient(AutoRoleRepositoryABC, AutoRoleRepositoryService)
services.add_transient(LevelRepositoryABC, LevelRepositoryService)
services.add_transient(StatisticRepositoryABC, StatisticRepositoryService)
services.add_transient(SeederService)
return services.build_service_provider()

View File

@@ -0,0 +1,20 @@
import os
from cpl_core.application import StartupExtensionABC
from cpl_core.configuration import ConfigurationABC
from cpl_core.dependency_injection import ServiceCollectionABC
from cpl_core.environment import ApplicationEnvironmentABC
class StartupTestExtension(StartupExtensionABC):
def __init__(self):
StartupExtensionABC.__init__(self)
def configure_configuration(self, configuration: ConfigurationABC, environment: ApplicationEnvironmentABC):
# this shit has to be done here because we need settings in subsequent startup extensions
environment.set_working_directory(os.path.dirname(os.path.realpath(__file__)))
environment.set_working_directory('../../src/bot/')
def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironmentABC):
pass

View File

@@ -0,0 +1,64 @@
from cpl_core.configuration import ConfigurationABC
from cpl_core.dependency_injection import ServiceProviderABC
from cpl_discord.application import DiscordBotApplicationABC
from cpl_discord.configuration import DiscordBotSettings
from cpl_discord.service import DiscordBotServiceABC
from cpl_translation import TranslationSettings, TranslationServiceABC
from bot_core.configuration.bot_settings import BotSettings
class TestApplication(DiscordBotApplicationABC):
def __init__(self, config: ConfigurationABC, services: ServiceProviderABC):
DiscordBotApplicationABC.__init__(self, config, services)
self._config = config
self._services = services
self._bot: DiscordBotServiceABC = services.get_service(DiscordBotServiceABC)
self._translation: TranslationServiceABC = services.get_service(TranslationServiceABC)
discord_bot_settings: DiscordBotSettings = config.get_configuration(DiscordBotSettings)
self._discord_settings = self._get_settings(discord_bot_settings)
def _get_settings(self, settings_from_config: DiscordBotSettings) -> DiscordBotSettings:
new_settings = DiscordBotSettings()
token = None if settings_from_config is None else settings_from_config.token
prefix = None if settings_from_config is None else settings_from_config.prefix
env_token = self._config.get_configuration('TOKEN')
env_prefix = self._config.get_configuration('PREFIX')
new_settings.from_dict({
'Token': env_token if token is None or token == '' else token,
'Prefix': ('! ' if self._is_string_invalid(env_prefix) else env_prefix) if self._is_string_invalid(prefix) else prefix
})
if new_settings.token is None or new_settings.token == '':
raise Exception('You have to configure discord token by appsettings or environment variables')
return new_settings
@staticmethod
def _is_string_invalid(x):
return x is None or x == ''
@property
def config(self) -> ConfigurationABC:
return self._config
@property
def services(self) -> ServiceProviderABC:
return self._services
async def configure(self):
self._translation.load_by_settings(self._configuration.get_configuration(TranslationSettings))
async def main(self):
if self._config.get_configuration('IS_UNITTEST'):
await self._bot.login(self._discord_settings.token)
self._bot.loop.create_task(self._bot.connect())
return
await self._bot.start_async()
async def stop_async(self):
await self._bot.close()

View File

@@ -0,0 +1,47 @@
import os
import unittest
from cpl_core.configuration import ConfigurationABC
from cpl_core.console import Console, ForegroundColorEnum
from cpl_core.dependency_injection import ServiceProviderABC
from cpl_core.logging import LoggerABC
from cpl_discord.events import OnReadyABC
from cpl_discord.service import DiscordBotServiceABC
from cpl_translation import TranslatePipe
from bot_core.abc.client_utils_service_abc import ClientUtilsServiceABC
class TestOnReadyEvent(OnReadyABC):
def __init__(
self,
config: ConfigurationABC,
bot: DiscordBotServiceABC,
services: ServiceProviderABC,
client_utils: ClientUtilsServiceABC,
t: TranslatePipe
):
OnReadyABC.__init__(self)
self._config = config
self._bot = bot
self._services = services
self._client_utils = client_utils
self._t = t
async def on_ready(self):
if self._config.get_configuration('IS_UNITTEST'):
return
Console.write_line('\nStarting tests:\n')
loader = unittest.TestLoader()
path = f'{os.path.dirname(os.path.realpath(__file__))}/../'
tests = loader.discover(path, pattern='*_test_case.py')
runner = unittest.TextTestRunner()
runner.run(tests)
# for cls in CommandTestABC.__subclasses__():
# service: CommandTestABC = self._services.get_service(cls)
# await service.run(self._tests)
await self._bot.close()

View File

@@ -0,0 +1,49 @@
{
"ProjectSettings": {
"Name": "ui-tests",
"Version": {
"Major": "0",
"Minor": "0",
"Micro": "0"
},
"Author": "",
"AuthorEmail": "",
"Description": "",
"LongDescription": "",
"URL": "",
"CopyrightDate": "",
"CopyrightName": "",
"LicenseName": "",
"LicenseDescription": "",
"Dependencies": [
"cpl-core>=2022.10.0.post9",
"PyNaCl==1.5.0",
"cffi==1.15.1",
"pycparser==2.21",
"selenium==4.6.1",
"webdriver-manager==3.8.5"
],
"DevDependencies": [
"cpl-cli>=2022.10.0"
],
"PythonVersion": ">=3.10.4",
"PythonPath": {},
"Classifiers": []
},
"BuildSettings": {
"ProjectType": "console",
"SourcePath": "",
"OutputPath": "../../dist",
"Main": "ui_tests_shared.main",
"EntryPoint": "ui-tests-shared",
"IncludePackageData": false,
"Included": [],
"Excluded": [
"*/__pycache__",
"*/logs",
"*/tests"
],
"PackageData": {},
"ProjectReferences": []
}
}

View File

@@ -0,0 +1 @@
# imports:

View File

@@ -0,0 +1,15 @@
from enum import Enum
class CommandSelectorsEnum(Enum):
cmd_chat = '/html/body/div[1]/div[2]/div/div[1]/div/div[2]/div/div[1]/div/div/div[3]/div[2]/main/form/div/div[1]/div/div[3]/div/div[2]/div'
cmd_chat_input = '/html/body/div[1]/div[2]/div/div[1]/div/div[2]/div/div[1]/div/div/div[3]/div/main/form/div/div[2]/div/div[2]/div/div'
msg_input = '/html/body/div[1]/div[2]/div/div[1]/div/div[2]/div/div[1]/div/div/div[3]/div[2]/main/form/div/div[1]/div/div[3]/div/div/div'
kdb_test = "//*[contains (text(), 'Krümmelmonster-test')]"
ping = kdb_test
info = kdb_test
help = kdb_test
afk = kdb_test
purge = kdb_test

View File

@@ -0,0 +1,62 @@
import time
from typing import Optional
from cpl_discord.service import DiscordBotServiceABC
from cpl_translation import TranslatePipe
from selenium.common import NoSuchElementException, StaleElementReferenceException
from selenium.webdriver import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
from ui_tests_shared.command_selectors_enum import CommandSelectorsEnum
from ui_tests_shared.test_case_with_app import TestCaseWithApp
from ui_tests_shared.ui import UI
class CommandTestCaseWithApp(TestCaseWithApp):
_cmd: WebElement
_bot: Optional[DiscordBotServiceABC] = None
_t: Optional[TranslatePipe] = None
@classmethod
def setUpClass(cls):
TestCaseWithApp.setUpClass()
cls._bot = cls._services.get_service(DiscordBotServiceABC)
cls._t = cls._services.get_service(TranslatePipe)
@classmethod
def send_message(cls, msg: str):
if UI.driver.current_url != cls._test_settings.cmd_url:
UI.driver.get(cls._test_settings.cmd_url)
time.sleep(2)
cmd_element_ident = (By.XPATH, CommandSelectorsEnum.cmd_chat.value)
cmd_element = UI.driver.find_element(*cmd_element_ident)
cmd_element.send_keys(msg)
time.sleep(2)
UI.driver.find_element(By.XPATH, CommandSelectorsEnum.msg_input.value).send_keys(Keys.ENTER)
@classmethod
def send_command(cls, cmd: str, selector: CommandSelectorsEnum, reload=True):
if reload or UI.driver.current_url != cls._test_settings.cmd_url:
UI.driver.get(cls._test_settings.cmd_url)
time.sleep(2)
cmd_element_ident = (By.XPATH, CommandSelectorsEnum.cmd_chat.value)
cmd_element = UI.driver.find_element(*cmd_element_ident)
cmd_element.send_keys(f'/{cmd}')
time.sleep(2)
ignored_exceptions = (NoSuchElementException, StaleElementReferenceException,)
WebDriverWait(UI.driver, 20, ignored_exceptions=ignored_exceptions).until(
expected_conditions.presence_of_element_located((
By.XPATH,
selector.value
))
).click()
time.sleep(2)
UI.driver.find_element(By.XPATH, CommandSelectorsEnum.cmd_chat_input.value).send_keys(Keys.ENTER)

View File

@@ -0,0 +1 @@
# imports

View File

@@ -0,0 +1,53 @@
import traceback
from cpl_core.configuration.configuration_model_abc import ConfigurationModelABC
from cpl_core.console import Console
class TestSettings(ConfigurationModelABC):
def __init__(self):
ConfigurationModelABC.__init__(self)
self._login_url = ''
self._me_page_url = ''
self._cmd_url = ''
self._guild_id = 0
self._bot_id = 0
self._test_user_id = 0
@property
def login_url(self) -> str:
return self._login_url
@property
def me_page_url(self) -> str:
return self._me_page_url
@property
def cmd_url(self) -> str:
return self._cmd_url
@property
def guild_id(self) -> int:
return self._guild_id
@property
def bot_id(self) -> int:
return self._bot_id
@property
def test_user_id(self) -> int:
return self._test_user_id
def from_dict(self, settings: dict):
try:
self._login_url = settings['LoginUrl']
self._me_page_url = settings['MePageUrl']
self._cmd_url = settings['CmdURL']
self._guild_id = settings['GuildId']
self._bot_id = settings['BotId']
self._test_user_id = settings['TestUserId']
except Exception as e:
Console.error(f'[ ERROR ] [ {__name__} ]: Reading error in {type(self).__name__} settings')
Console.error(f'[ EXCEPTION ] [ {__name__} ]: {e} -> {traceback.format_exc()}')

View File

@@ -0,0 +1,12 @@
from typing import Optional
from cpl_core.environment import ApplicationEnvironmentABC
from ui_tests.test_application import TestApplication
from ui_tests_shared.configuration.test_settings import TestSettings
class Declarations:
app: Optional[TestApplication] = None
env: Optional[ApplicationEnvironmentABC] = None
test_settings: Optional[TestSettings] = None

View File

@@ -0,0 +1,18 @@
import asyncio
class Async:
_loop = None
@classmethod
def async_func(cls, coro):
def wrapper(*args, **kwargs):
try:
if cls._loop is None:
cls._loop = asyncio.get_event_loop()
return cls._loop.run_until_complete(coro(*args, **kwargs))
except Exception as e:
return
return wrapper

View File

@@ -0,0 +1,44 @@
import asyncio
import time
import unittest
from typing import Optional
from cpl_core.configuration import ConfigurationABC
from cpl_core.dependency_injection import ServiceProviderABC
from ui_tests.main import get_app
from ui_tests_shared.configuration.test_settings import TestSettings
from ui_tests_shared.declarations import Declarations
from ui_tests_shared.ui import UI
class TestCaseWithApp(unittest.TestCase):
_config: Optional[ConfigurationABC] = None
_services: Optional[ServiceProviderABC] = None
_test_settings: Optional[TestSettings] = None
_loop = None
@classmethod
def setUpClass(cls):
if Declarations.app is None:
app = get_app()
import nest_asyncio
nest_asyncio.apply()
if cls._loop is None:
cls._loop = asyncio.get_event_loop()
cls._loop.run_until_complete(app.run_async())
# asyncio.run(app.run_async())
cls._config = Declarations.app.config
cls._services = Declarations.app.services
cls._test_settings: TestSettings = cls._config.get_configuration(TestSettings)
UI.set_settings(cls._test_settings)
UI.login(cls._config.get_configuration('DISCORD_MAIL'), cls._config.get_configuration('DISCORD_PASSWORD'))
@classmethod
def tearDownClass(cls):
if cls._config.get_configuration('IS_UNITTEST'):
loop = asyncio.get_event_loop()
loop.run_until_complete(Declarations.app.stop_async())

View File

@@ -0,0 +1,44 @@
{
"ProjectSettings": {
"Name": "ui-tests-shared",
"Version": {
"Major": "0",
"Minor": "0",
"Micro": "0"
},
"Author": "",
"AuthorEmail": "",
"Description": "",
"LongDescription": "",
"URL": "",
"CopyrightDate": "",
"CopyrightName": "",
"LicenseName": "",
"LicenseDescription": "",
"Dependencies": [
"cpl-core>=2022.10.0.post9"
],
"DevDependencies": [
"cpl-cli>=2022.10.0"
],
"PythonVersion": ">=3.10.4",
"PythonPath": {},
"Classifiers": []
},
"BuildSettings": {
"ProjectType": "console",
"SourcePath": "",
"OutputPath": "../../dist",
"Main": "ui_tests_shared.main",
"EntryPoint": "ui-tests-shared",
"IncludePackageData": false,
"Included": [],
"Excluded": [
"*/__pycache__",
"*/logs",
"*/tests"
],
"PackageData": {},
"ProjectReferences": []
}
}

View File

@@ -0,0 +1,69 @@
import time
import unittest
from typing import Optional
from cpl_core.configuration import ConfigurationABC
from cpl_core.dependency_injection import ServiceProviderABC
from selenium import webdriver
from selenium.webdriver import Keys
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait
from ui_tests.main import get_app
from ui_tests_shared.configuration.test_settings import TestSettings
from ui_tests_shared.declarations import Declarations
class UI:
_test_settings: Optional[TestSettings] = None
options = webdriver.ChromeOptions()
options.add_argument("user-data-dir=selenium-data")
options.add_experimental_option('useAutomationExtension', False)
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_experimental_option("prefs", {
"profile.default_content_setting_values.media_stream_mic": 1,
"profile.default_content_setting_values.media_stream_camera": 1,
"profile.default_content_setting_values.geolocation": 1,
"profile.default_content_setting_values.notifications": 1
})
driver = webdriver.Chrome(options=options)
_is_logged_in = False
@classmethod
def set_settings(cls, test_settings: TestSettings):
cls._test_settings = test_settings
@classmethod
def login(cls, mail: str, password: str):
if cls._is_logged_in:
return
# use full xpath: https://stackoverflow.com/questions/71179006/how-can-selenium-python-chrome-find-web-elements-visible-in-dev-tools-but-no
cls.driver.get(cls._test_settings.login_url)
try:
WebDriverWait(cls.driver, 10).until(expected_conditions.url_matches(cls._test_settings.me_page_url))
cls._is_logged_in = True
return
except Exception as e:
WebDriverWait(cls.driver, 20).until(expected_conditions.presence_of_element_located((By.NAME, 'email')))
mail_element = cls.driver.find_element(By.NAME, 'email')
mail_element.clear()
mail_element.send_keys(mail)
mail_element.send_keys(Keys.RETURN)
pw_element = cls.driver.find_element(By.NAME, 'password')
pw_element.clear()
pw_element.send_keys(password)
pw_element.send_keys(Keys.RETURN)
time.sleep(1)
pw_element.send_keys(Keys.RETURN)
WebDriverWait(cls.driver, 20).until(expected_conditions.url_matches(cls._test_settings.me_page_url))
time.sleep(4)
cls._is_logged_in = True

View File

@@ -0,0 +1 @@
# imports:

View File

@@ -0,0 +1,15 @@
{
"TimeFormatSettings": {
"DateFormat": "%Y-%m-%d",
"TimeFormat": "%H:%M:%S",
"DateTimeFormat": "%Y-%m-%d %H:%M:%S.%f",
"DateTimeLogFormat": "%Y-%m-%d_%H-%M-%S"
},
"LoggingSettings": {
"Path": "logs/",
"Filename": "log_$start_time.log",
"ConsoleLogLevel": "ERROR",
"FileLogLevel": "WARN"
}
}

View File

@@ -0,0 +1,48 @@
import discord
from selenium.webdriver.common.by import By
from ui_tests_shared.command_selectors_enum import CommandSelectorsEnum
from ui_tests_shared.command_test_case_with_app import CommandTestCaseWithApp
from ui_tests_shared.decorators import Async
from ui_tests_shared.ui import UI
class AFKCommandTestCase(CommandTestCaseWithApp):
@classmethod
def tearDownClass(cls):
btn = UI.driver.find_elements(By.XPATH, '/html/body/div[1]/div[2]/div/div[1]/div/div[2]/div/div[1]/div/div/div[1]/section/div[1]/div/div[1]/div[2]/button')
if len(btn) == 0:
return
btn[0].click()
@Async.async_func
async def test_error_message(self):
correct_response = self._t.transform('modules.base.afk_command_channel_missing_message')
self.assertIsNotNone(correct_response)
self.send_command('afk', CommandSelectorsEnum.afk, reload=False)
def check(m: discord.Message):
return m.author.id == self._test_settings.bot_id
response = await self._bot.wait_for('message', check=check, timeout=10)
self.assertEqual(response.content, correct_response)
@Async.async_func
async def test_move(self):
# correct_response = self._t.transform('modules.base.pong')
# self.assertIsNotNone(correct_response)
UI.driver.find_element(By.XPATH, '//*[@id="channels"]/ul/li[20]/div/div/div/a').click()
def check1(member: discord.Member, before: discord.VoiceState, after: discord.VoiceState):
return member.id == self._test_settings.test_user_id and after is not None and after.channel.id == 911578760476762153
res = await self._bot.wait_for('voice_state_update', check=check1, timeout=10)
self.send_command('afk', CommandSelectorsEnum.afk, reload=False)
def check2(member: discord.Member, before: discord.VoiceState, after: discord.VoiceState):
return member.id == self._test_settings.test_user_id and after is not None and after.channel.id == 910199452915093594
member, before, after = await self._bot.wait_for('voice_state_update', check=check2, timeout=10)
self.assertIsNotNone(after.channel)
self.assertEqual(after.channel.id, 910199452915093594)
# self.assertEqual(response.content, correct_response)

View File

@@ -0,0 +1,20 @@
import discord
from ui_tests_shared.command_selectors_enum import CommandSelectorsEnum
from ui_tests_shared.command_test_case_with_app import CommandTestCaseWithApp
from ui_tests_shared.decorators import Async
class HelpCommandTestCase(CommandTestCaseWithApp):
@Async.async_func
async def test_help(self):
correct_response = 'https://git.sh-edraft.de/sh-edraft.de/kd_discord_bot/wiki/Befehle'
self.send_command('help', CommandSelectorsEnum.help)
def check(m: discord.Message):
return m.author.id == self._test_settings.bot_id
response = await self._bot.wait_for('message', check=check, timeout=10)
self.assertEqual(response.content, correct_response)
await self._bot.close()

View File

@@ -0,0 +1,53 @@
from datetime import datetime
from unittest import skip
import discord
import bot
from bot_core.abc.client_utils_service_abc import ClientUtilsServiceABC
from ui_tests_shared.command_selectors_enum import CommandSelectorsEnum
from ui_tests_shared.command_test_case_with_app import CommandTestCaseWithApp
from ui_tests_shared.decorators import Async
class InfoCommandTestCase(CommandTestCaseWithApp):
def get_embed(self) -> discord.Embed:
client_utils: ClientUtilsServiceABC = self._services.get_service(ClientUtilsServiceABC)
client = client_utils.get_client(self._test_settings.bot_id, self._test_settings.guild_id)
embed = discord.Embed(
title=self._t.transform('modules.base.info.title'),
description=self._t.transform('modules.base.info.description'),
color=int('ef9d0d', 16)
)
embed.add_field(name=self._t.transform('modules.base.info.fields.version'), value=bot.__version__)
# start_time = self._config.get_configuration('Bot_StartTime')
start_time = str(datetime.now())
ontime = round((datetime.now() - datetime.strptime(start_time, '%Y-%m-%d %H:%M:%S.%f')).total_seconds() / 3600, 2)
embed.add_field(name=self._t.transform('modules.base.info.fields.ontime'), value=f'{ontime}h')
embed.add_field(name=self._t.transform('modules.base.info.fields.sent_message_count'), value=client.sent_message_count, inline=False)
embed.add_field(name=self._t.transform('modules.base.info.fields.received_message_count'), value=client.received_message_count)
embed.add_field(name=self._t.transform('modules.base.info.fields.deleted_message_count'), value=client.deleted_message_count, inline=False)
embed.add_field(name=self._t.transform('modules.base.info.fields.received_command_count'), value=client.received_command_count)
embed.add_field(name=self._t.transform('modules.base.info.fields.moved_users_count'), value=client.moved_users_count)
from bot.module_list import ModuleList
modules = ModuleList.get_modules()
modules = modules.select(lambda x: x.__name__.replace('Module', ''))
embed.add_field(name=self._t.transform('modules.base.info.fields.modules'), value='\n'.join(modules), inline=False)
return embed
@skip('stage db not ready yet')
@Async.async_func
async def test_info(self):
correct_response = self.get_embed()
self.send_command('info', CommandSelectorsEnum.info)
def check(m: discord.Message):
return m.embeds[0] == correct_response and m.author.id == self._test_settings.bot_id
response: discord.Message = await self._bot.wait_for('message', check=check, timeout=10)
self.assertEqual(len(response.embeds), 1)
self.assertEqual(response.embeds[0], correct_response)
await self._bot.close()

View File

@@ -0,0 +1,20 @@
import discord
from ui_tests_shared.command_selectors_enum import CommandSelectorsEnum
from ui_tests_shared.command_test_case_with_app import CommandTestCaseWithApp
from ui_tests_shared.decorators import Async
class PingCommandTestCase(CommandTestCaseWithApp):
@Async.async_func
async def test_ping(self):
correct_response = self._t.transform('modules.base.pong')
self.assertIsNotNone(correct_response)
self.send_command('ping', CommandSelectorsEnum.ping)
def check(m: discord.Message):
return m.author.id == self._test_settings.bot_id
response = await self._bot.wait_for('message', check=check, timeout=10)
self.assertEqual(response.content, correct_response)

View File

@@ -0,0 +1,34 @@
import time
import discord
from ui_tests_shared.command_selectors_enum import CommandSelectorsEnum
from ui_tests_shared.command_test_case_with_app import CommandTestCaseWithApp
from ui_tests_shared.decorators import Async
class PurgeCommandTestCase(CommandTestCaseWithApp):
def setUp(self):
self.send_message('test nachricht 1')
self.send_message('test nachricht 2')
self.send_message('test nachricht 3')
self.send_message('test nachricht 4')
@Async.async_func
async def test_purge(self):
correct_response = self._t.transform('modules.moderator.purge_message')
self.assertIsNotNone(correct_response)
self.send_command('purge', CommandSelectorsEnum.purge)
def check(m: discord.Message):
return m.author.id == self._test_settings.bot_id
response = await self._bot.wait_for('message', check=check, timeout=10)
self.assertEqual(response.content, correct_response)
time.sleep(20)
channel = self._bot.get_channel(911578680998895687)
message_count = 0
async for _ in channel.history(limit=None):
message_count += 1
self.assertEqual(message_count, 0)

View File

@@ -0,0 +1,44 @@
{
"ProjectSettings": {
"Name": "ui-tests-tests",
"Version": {
"Major": "0",
"Minor": "0",
"Micro": "0"
},
"Author": "",
"AuthorEmail": "",
"Description": "",
"LongDescription": "",
"URL": "",
"CopyrightDate": "",
"CopyrightName": "",
"LicenseName": "",
"LicenseDescription": "",
"Dependencies": [
"cpl-core>=2022.10.0.post9"
],
"DevDependencies": [
"cpl-cli>=2022.10.0"
],
"PythonVersion": ">=3.10.4",
"PythonPath": {},
"Classifiers": []
},
"BuildSettings": {
"ProjectType": "console",
"SourcePath": "",
"OutputPath": "../../dist",
"Main": "ui_tests__tests.main",
"EntryPoint": "ui-tests_tests",
"IncludePackageData": false,
"Included": [],
"Excluded": [
"*/__pycache__",
"*/logs",
"*/tests"
],
"PackageData": {},
"ProjectReferences": []
}
}

View File

@@ -46,7 +46,7 @@ class Application(ApplicationABC):
return
if len(args) == 1:
suffix = f'.{args[0]}'
suffix = args[0]
try:
branch = self._git_service.get_active_branch_name()
@@ -67,8 +67,12 @@ class Application(ApplicationABC):
version[VersionSettingsNameEnum.major.value] = branch.split('.')[0]
version[VersionSettingsNameEnum.minor.value] = branch.split('.')[1]
if len(branch.split('.')) == 2:
version[VersionSettingsNameEnum.micro.value] = f'0{suffix}'
if suffix == '':
suffix = '0'
version[VersionSettingsNameEnum.micro.value] = f'{suffix}'
else:
if not suffix.startswith('.') and suffix != '':
suffix = f'.{suffix}'
version[VersionSettingsNameEnum.micro.value] = f'{branch.split(".")[2]}{suffix}'
except Exception as e:
Console.error(f'Branch {branch} does not contain valid version')

View File

@@ -1,12 +1,12 @@
{
"name": "kdb-web",
"version": "0.3.dev70",
"version": "0.3.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "kdb-web",
"version": "0.3.dev70",
"version": "0.3.0",
"dependencies": {
"@angular/animations": "^14.0.0",
"@angular/common": "^14.0.0",
@@ -25,7 +25,6 @@
"primeng": "^14.1.2",
"rxjs": "~7.5.0",
"socket.io-client": "^4.5.3",
"tslib": "^2.3.0",
"zone.js": "~0.11.4"
},
"devDependencies": {
@@ -33,14 +32,14 @@
"@angular/cli": "~14.0.0",
"@angular/compiler-cli": "^14.0.0",
"@types/jasmine": "~4.0.0",
"@types/node": "^18.8.3",
"@types/node": "^18.11.9",
"jasmine-core": "~4.1.0",
"karma": "~6.3.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.0.0",
"karma-jasmine-html-reporter": "~1.7.0",
"ts-node": "~8.3.0",
"tslib": "^2.4.1",
"typescript": "~4.7.2"
}
},
@@ -222,6 +221,12 @@
"integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
"dev": true
},
"node_modules/@angular-devkit/build-angular/node_modules/tslib": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==",
"dev": true
},
"node_modules/@angular-devkit/build-webpack": {
"version": "0.1402.6",
"resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1402.6.tgz",
@@ -3200,9 +3205,9 @@
"dev": true
},
"node_modules/@types/node": {
"version": "18.11.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.0.tgz",
"integrity": "sha512-IOXCvVRToe7e0ny7HpT/X9Rb2RYtElG1a+VshjwT00HxrM2dWBApHQoqsI6WiY7Q03vdf2bCrIGzVrkF/5t10w==",
"version": "18.11.9",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz",
"integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==",
"dev": true
},
"node_modules/@types/parse-json": {
@@ -3705,12 +3710,6 @@
"node": "^12.13.0 || ^14.15.0 || >=16.0.0"
}
},
"node_modules/arg": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
"dev": true
},
"node_modules/argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
@@ -4982,15 +4981,6 @@
"integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==",
"dev": true
},
"node_modules/diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
"dev": true,
"engines": {
"node": ">=0.3.1"
}
},
"node_modules/dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -7881,12 +7871,6 @@
"semver": "bin/semver.js"
}
},
"node_modules/make-error": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"dev": true
},
"node_modules/make-fetch-happen": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz",
@@ -11730,32 +11714,10 @@
"tree-kill": "cli.js"
}
},
"node_modules/ts-node": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.3.0.tgz",
"integrity": "sha512-dyNS/RqyVTDcmNM4NIBAeDMpsAdaQ+ojdf0GOLqE6nwJOgzEkdRNzJywhDfwnuvB10oa6NLVG1rUJQCpRN7qoQ==",
"dev": true,
"dependencies": {
"arg": "^4.1.0",
"diff": "^4.0.1",
"make-error": "^1.1.1",
"source-map-support": "^0.5.6",
"yn": "^3.0.0"
},
"bin": {
"ts-node": "dist/bin.js"
},
"engines": {
"node": ">=4.2.0"
},
"peerDependencies": {
"typescript": ">=2.0"
}
},
"node_modules/tslib": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
},
"node_modules/type-fest": {
"version": "0.21.3",
@@ -12510,15 +12472,6 @@
"node": ">=12"
}
},
"node_modules/yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
"dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/zone.js": {
"version": "0.11.8",
"resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.11.8.tgz",
@@ -12659,6 +12612,12 @@
"dev": true
}
}
},
"tslib": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==",
"dev": true
}
}
},
@@ -14719,9 +14678,9 @@
"dev": true
},
"@types/node": {
"version": "18.11.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.0.tgz",
"integrity": "sha512-IOXCvVRToe7e0ny7HpT/X9Rb2RYtElG1a+VshjwT00HxrM2dWBApHQoqsI6WiY7Q03vdf2bCrIGzVrkF/5t10w==",
"version": "18.11.9",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.9.tgz",
"integrity": "sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==",
"dev": true
},
"@types/parse-json": {
@@ -15153,12 +15112,6 @@
"readable-stream": "^3.6.0"
}
},
"arg": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
"dev": true
},
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
@@ -16095,12 +16048,6 @@
"integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==",
"dev": true
},
"diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
"dev": true
},
"dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -18187,12 +18134,6 @@
}
}
},
"make-error": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"dev": true
},
"make-fetch-happen": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz",
@@ -20975,23 +20916,10 @@
"integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
"dev": true
},
"ts-node": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.3.0.tgz",
"integrity": "sha512-dyNS/RqyVTDcmNM4NIBAeDMpsAdaQ+ojdf0GOLqE6nwJOgzEkdRNzJywhDfwnuvB10oa6NLVG1rUJQCpRN7qoQ==",
"dev": true,
"requires": {
"arg": "^4.1.0",
"diff": "^4.0.1",
"make-error": "^1.1.1",
"source-map-support": "^0.5.6",
"yn": "^3.0.0"
}
},
"tslib": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz",
"integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ=="
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.1.tgz",
"integrity": "sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA=="
},
"type-fest": {
"version": "0.21.3",
@@ -21518,12 +21446,6 @@
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"dev": true
},
"yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
"dev": true
},
"zone.js": {
"version": "0.11.8",
"resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.11.8.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "kdb-web",
"version": "0.3.0",
"version": "0.3.dev128",
"scripts": {
"ng": "ng",
"update-version": "ts-node-esm update-version.ts",
@@ -33,7 +33,6 @@
"primeng": "^14.1.2",
"rxjs": "~7.5.0",
"socket.io-client": "^4.5.3",
"tslib": "^2.3.0",
"zone.js": "~0.11.4"
},
"devDependencies": {
@@ -41,13 +40,14 @@
"@angular/cli": "~14.0.0",
"@angular/compiler-cli": "^14.0.0",
"@types/jasmine": "~4.0.0",
"@types/node": "^18.8.3",
"@types/node": "^18.11.9",
"jasmine-core": "~4.1.0",
"karma": "~6.3.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.0.0",
"karma-jasmine-html-reporter": "~1.7.0",
"tslib": "^2.4.1",
"typescript": "~4.7.2"
}
}

View File

@@ -1,60 +1,65 @@
<section class="login-wrapper">
<div class="login-form-wrapper">
<div class="login-form">
<form [formGroup]="loginForm">
<h1>{{'auth.header' | translate}}</h1>
<div class="input-field">
<input type="email" pInputText formControlName="email" placeholder="{{'auth.login.e_mail' | translate}}" [ngClass]="{ 'invalid-feedback-input': submitted && (
<div class="login-form-wrapper">
<div class="login-form">
<form [formGroup]="loginForm">
<h1>{{'auth.header' | translate}}</h1>
<div class="input-field">
<input type="email" pInputText formControlName="email" placeholder="{{'auth.login.e_mail' | translate}}" [ngClass]="{ 'invalid-feedback-input': submitted && (
(loginForm.controls.email.errors && loginForm.controls.email.errors['required'] || authUserAtrErrors.email.required) ||
(authUserAtrErrors.email.wrongData) ||
(authUserAtrErrors.email.notConfirmed)
)}" autocomplete="username email">
<div *ngIf="submitted" class="invalid-feedback">
<div
*ngIf="loginForm.controls.email.errors && loginForm.controls.email.errors['required'] || authUserAtrErrors.email.required">
{{'auth.login.e_mail_required' | translate}}</div>
<div *ngIf="authUserAtrErrors.email.wrongData">{{'auth.login.user_not_found' | translate}}</div>
<div *ngIf="authUserAtrErrors.email.notConfirmed">{{'auth.login.e_mail_not_confirmed' | translate}}</div>
</div>
</div>
<div class="input-field">
<!--
!! WARNING !!
Bugfix from https://github.com/primefaces/primeng/issues/10788
styleClass="p-password p-component p-inputwrapper p-input-icon-right"
Remove after update!
-->
<p-password type="password" formControlName="password" placeholder="{{'auth.login.password' | translate}}" [ngClass]="{ 'invalid-feedback-input': submitted && (
<div *ngIf="submitted" class="invalid-feedback">
<div
*ngIf="loginForm.controls.email.errors && loginForm.controls.email.errors['required'] || authUserAtrErrors.email.required">
{{'auth.login.e_mail_required' | translate}}</div>
<div *ngIf="authUserAtrErrors.email.wrongData">{{'auth.login.user_not_found' | translate}}</div>
<div *ngIf="authUserAtrErrors.email.notConfirmed">{{'auth.login.e_mail_not_confirmed' | translate}}</div>
</div>
</div>
<div class="input-field">
<!--
!! WARNING !!
Bugfix from https://github.com/primefaces/primeng/issues/10788
styleClass="p-password p-component p-inputwrapper p-input-icon-right"
Remove after update!
-->
<p-password type="password" formControlName="password" placeholder="{{'auth.login.password' | translate}}" [ngClass]="{ 'invalid-feedback-input': submitted && (
(loginForm.controls.password.errors && loginForm.controls.password.errors['required'] || authUserAtrErrors.password.required) ||
(authUserAtrErrors.password.wrongData)
)}" autocomplete="current-password" [toggleMask]="true" [feedback]="false"
styleClass="p-password p-component p-inputwrapper p-input-icon-right"
></p-password>
<div *ngIf="submitted" class="invalid-feedback">
<div
*ngIf="loginForm.controls.password.errors && loginForm.controls.password.errors['required'] || authUserAtrErrors.password.required">
{{'auth.login.password_required' | translate}}</div>
<div *ngIf="authUserAtrErrors.password.wrongData">{{'auth.login.wrong_password' | translate}}</div>
</div>
</div>
<div class="login-form-submit">
<button pButton label="{{'auth.login.login' | translate}}" class="btn login-form-submit-btn" (click)="login()"
[disabled]="loginForm.invalid"></button>
</div>
<div class="login-form-sub-button-wrapper">
<div class="login-form-sub-btn-wrapper">
<button pButton label="{{'auth.login.register' | translate}}" class="btn login-form-sub-btn"
(click)="register()"></button>
</div>
<div class="login-form-sub-btn-wrapper">
<button pButton label="{{'auth.login.forgot_password' | translate}}"
class="btn login-form-sub-btn login-form-sub-login-btn p-button-text"
(click)="forgotPassword()"></button>
</div>
</div>
</form>
styleClass="p-password p-component p-inputwrapper p-input-icon-right"
></p-password>
<div *ngIf="submitted" class="invalid-feedback">
<div
*ngIf="loginForm.controls.password.errors && loginForm.controls.password.errors['required'] || authUserAtrErrors.password.required">
{{'auth.login.password_required' | translate}}</div>
<div *ngIf="authUserAtrErrors.password.wrongData">{{'auth.login.wrong_password' | translate}}</div>
</div>
</div>
<div class="login-form-submit">
<button pButton label="{{'auth.login.login' | translate}}" class="btn login-form-submit-btn" (click)="login()"
[disabled]="loginForm.invalid"></button>
</div>
<div class="login-form-sub-button-wrapper" *ngIf="!code && !state">
<div class="login-form-sub-btn-wrapper">
<button pButton label="{{'auth.login.login_with_discord' | translate}}" class="btn login-form-sub-btn" (click)="discordLogin()"></button>
</div>
</div>
<div class="login-form-sub-button-wrapper">
<div class="login-form-sub-btn-wrapper">
<button pButton label="{{'auth.login.register' | translate}}" class="btn login-form-sub-btn"
(click)="register()"></button>
</div>
<div class="login-form-sub-btn-wrapper">
<button pButton label="{{'auth.login.forgot_password' | translate}}"
class="btn login-form-sub-btn login-form-sub-login-btn p-button-text"
(click)="forgotPassword()"></button>
</div>
</div>
</form>
</div>
</div>
<app-auth-header></app-auth-header>
</section>
<app-auth-header></app-auth-header>
</section>

View File

@@ -1,20 +1,21 @@
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
import { AuthService } from 'src/app/services/auth/auth.service';
import { AuthUserDTO } from 'src/app/models/auth/auth-user.dto';
import { Router } from '@angular/router';
import { catchError } from 'rxjs/operators';
import { ErrorDTO } from 'src/app/models/error/error-dto';
import { AuthErrorMessages } from 'src/app/models/auth/auth-error-messages.enum';
import { ServiceErrorCode } from 'src/app/models/error/service-error-code.enum';
import { AuthUserAtrErrors } from 'src/app/models/auth/auth-user-atr-errors';
import { SpinnerService } from 'src/app/services/spinner/spinner.service';
import { ThemeService } from 'src/app/services/theme/theme.service';
import { Component, OnInit } from "@angular/core";
import { FormBuilder, FormControl, FormGroup, Validators } from "@angular/forms";
import { AuthService } from "src/app/services/auth/auth.service";
import { AuthUserDTO } from "src/app/models/auth/auth-user.dto";
import { ActivatedRoute, Router } from "@angular/router";
import { catchError } from "rxjs/operators";
import { ErrorDTO } from "src/app/models/error/error-dto";
import { AuthErrorMessages } from "src/app/models/auth/auth-error-messages.enum";
import { ServiceErrorCode } from "src/app/models/error/service-error-code.enum";
import { AuthUserAtrErrors } from "src/app/models/auth/auth-user-atr-errors";
import { SpinnerService } from "src/app/services/spinner/spinner.service";
import { ThemeService } from "src/app/services/theme/theme.service";
import { throwError } from "rxjs";
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.scss']
selector: "app-login",
templateUrl: "./login.component.html",
styleUrls: ["./login.component.scss"]
})
export class LoginComponent implements OnInit {
@@ -25,45 +26,83 @@ export class LoginComponent implements OnInit {
submitted = false;
authUserAtrErrors!: AuthUserAtrErrors;
code!: string;
state!: string;
user!: AuthUserDTO;
oAuthId!: string;
constructor(
private authService: AuthService,
private formBuilder: FormBuilder,
private router: Router,
private spinnerService: SpinnerService,
private themeService: ThemeService
) { }
private themeService: ThemeService,
private route: ActivatedRoute
) {
}
ngOnInit(): void {
this.spinnerService.showSpinner();
this.authService.isUserLoggedInAsync().then(result => {
if (result) {
this.router.navigate(['/dashboard']);
this.router.navigate(["/dashboard"]);
return;
}
this.checkDiscordLogin();
this.initLoginForm();
this.resetStateFlags();
this.spinnerService.hideSpinner();
});
}
checkDiscordLogin() {
this.route.queryParams.pipe(catchError(err => {
this.spinnerService.hideSpinner();
this.router.navigate(["auth", "login"]).then(() => {
});
return throwError(() => err);
})).subscribe(params => {
if (!params["code"] || !params["state"]) {
this.spinnerService.hideSpinner();
return;
}
this.code = params["code"];
this.state = params["state"];
this.authService.discordLogin(this.code, this.state).pipe(catchError(err => {
this.spinnerService.hideSpinner();
this.router.navigate(["auth", "login"]).then(() => {
});
return throwError(() => err);
})).subscribe(token => {
this.authService.saveToken(token);
this.themeService.loadTheme();
this.themeService.loadMenu();
this.spinnerService.hideSpinner();
this.router.navigate(["/dashboard"]);
});
}
);
}
resetStateFlags(): void {
this.authUserAtrErrors = new AuthUserAtrErrors();
}
initLoginForm(): void {
this.loginForm = this.formBuilder.group({
email: ['', [Validators.required, Validators.email]],
password: ['', [Validators.required, Validators.minLength(8)]]
email: ["", [Validators.required, Validators.email]],
password: ["", [Validators.required, Validators.minLength(8)]]
});
}
register(): void {
this.router.navigate(['/auth/register']);
this.router.navigate(["/auth/register"]);
}
forgotPassword(): void {
this.router.navigate(['/auth/forgot-password']);
this.router.navigate(["/auth/forgot-password"]);
}
login(): void {
@@ -76,8 +115,8 @@ export class LoginComponent implements OnInit {
this.spinnerService.showSpinner();
const user: AuthUserDTO = {
firstName: '',
lastName: '',
firstName: "",
lastName: "",
email: this.loginForm.value.email ?? null,
password: this.loginForm.value.password ?? null
};
@@ -107,8 +146,14 @@ export class LoginComponent implements OnInit {
this.themeService.loadTheme();
this.themeService.loadMenu();
this.spinnerService.hideSpinner();
this.router.navigate(['/dashboard']);
this.router.navigate(["/dashboard"]);
});
}
discordLogin() {
this.authService.getDiscordAuthURL().subscribe(url => {
window.location.href = url.loginUrl;
});
}
}

View File

@@ -72,12 +72,6 @@
[disabled]="loginForm.invalid"></button>
</div>
<div class="login-form-sub-button-wrapper" *ngIf="!code && !state">
<div class="login-form-sub-btn-wrapper">
<button pButton label="{{'auth.register.register_with_discord' | translate}}" class="btn login-form-sub-btn" (click)="discordLogin()"></button>
</div>
</div>
<div class="login-form-sub-button-wrapper">
<div class="login-form-sub-btn-wrapper">
<button pButton label="{{'auth.register.login' | translate}}" class="btn login-form-sub-btn" (click)="login()"></button>

View File

@@ -34,14 +34,6 @@ export class RegistrationComponent implements OnInit {
password: false
};
showEMailConfirmation = false;
showEMailConfirmationError = false;
code!: string;
state!: string;
user!: AuthUserDTO;
oAuthId!: string;
constructor(
private authService: AuthService,
private formBuilder: FormBuilder,
@@ -62,48 +54,7 @@ export class RegistrationComponent implements OnInit {
this.confirmEMail();
this.initData();
this.spinnerService.showSpinner();
this.route.queryParams.pipe(catchError(err => {
this.spinnerService.hideSpinner();
this.router.navigate(["auth", "login"]).then(() => {
});
return throwError(() => err);
})).subscribe(params => {
if (!params["code"] || !params["state"]) {
this.spinnerService.hideSpinner();
return;
}
if (this.user) {
this.spinnerService.hideSpinner();
return;
}
this.code = params["code"];
this.state = params["state"];
this.authService.discordCreateUser(this.code, this.state).pipe(catchError(err => {
this.spinnerService.hideSpinner();
this.router.navigate(["auth", "login"]).then(() => {
});
return throwError(() => err);
})).subscribe(oAuthDTO => {
if (oAuthDTO) {
this.user = oAuthDTO.user;
this.oAuthId = oAuthDTO.oAuthId;
}
if (this.user && !this.oAuthId) {
this.router.navigate(["auth", "login"]).then(() => {
});
return;
}
this.router.navigate(["auth", "register"]).then(() => {
});
this.initData();
}
);
this.initData();
}
);
this.initData();
}
initData() {
@@ -126,18 +77,13 @@ export class RegistrationComponent implements OnInit {
initLoginForm(): void {
this.loginForm = this.formBuilder.group({
firstName: [this.user ? this.user.firstName : "", Validators.required],
lastName: [this.user ? this.user.lastName : "", Validators.required],
email: [this.user ? this.user.email : "", [Validators.required, Validators.email]],
emailRepeat: [this.user ? this.user.email : "", [Validators.required, Validators.email]],
password: [this.user ? this.user.password : "", [Validators.required, Validators.minLength(8)]],
passwordRepeat: [this.user ? this.user.password : "", [Validators.required, Validators.minLength(8)]]
firstName: ["", Validators.required],
lastName: ["", Validators.required],
email: ["", [Validators.required, Validators.email]],
emailRepeat: ["", [Validators.required, Validators.email]],
password: ["", [Validators.required, Validators.minLength(8)]],
passwordRepeat: ["", [Validators.required, Validators.minLength(8)]]
});
if (this.user) {
this.loginForm.controls.email.disable();
this.loginForm.controls.emailRepeat.disable();
}
}
register(): void {
@@ -160,42 +106,10 @@ export class RegistrationComponent implements OnInit {
}
this.spinnerService.showSpinner();
if (this.user && this.oAuthId) {
this.registerWithOAuth();
return;
}
this.registerLocal();
}
private registerWithOAuth() {
const oAuthDTO: OAuthDTO = {
user: {
firstName: this.loginForm.value.firstName ?? this.user.firstName,
lastName: this.loginForm.value.lastName ?? this.user.lastName,
email: this.loginForm.value.email ?? this.user.email,
password: this.loginForm.value.password ?? null
},
oAuthId: this.oAuthId
};
this.authService.discordRegister(oAuthDTO)
.pipe(catchError(error => {
if (error.error !== null) {
const err: ErrorDTO = error.error;
if (err.errorCode === ServiceErrorCode.InvalidUser && err.message === AuthErrorMessages.UserAlreadyExists) {
this.authUserAtrErrors.email.wrongData = true;
}
}
this.spinnerService.hideSpinner();
throw error;
}))
.subscribe(resp => {
this.spinnerService.hideSpinner();
this.router.navigate(["/auth/login"]);
});
}
private registerLocal() {
const user: AuthUserDTO = {
firstName: this.loginForm.value.firstName ?? null,
@@ -237,10 +151,4 @@ export class RegistrationComponent implements OnInit {
});
}
}
discordLogin() {
this.authService.getDiscordAuthURL().subscribe(url => {
window.location.href = url.loginUrl;
});
}
}

View File

@@ -0,0 +1,13 @@
import { NgModule } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
import { MembersComponent } from "./component/members/members.component";
const routes: Routes = [
{ path: '', component: MembersComponent },
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class MembersRoutingModule { }

View File

@@ -1,9 +1,11 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { ServerDashboardComponent } from './server-dashboard/server-dashboard.component';
import { AuthGuard } from "../../shared/guards/auth/auth.guard";
const routes: Routes = [
{ path: '', component: ServerDashboardComponent }
{ path: '', component: ServerDashboardComponent },
{ path: 'members', loadChildren: () => import('./members/members.module').then(m => m.MembersModule), canActivate: [AuthGuard] },
];
@NgModule({

View File

@@ -167,6 +167,7 @@ export class AuthService {
})
});
}
discordCreateUser(code: string, state: string) {
return this.http.get<OAuthDTO>(`${this.appsettings.getApiURL()}/api/auth/discord/create-user?code=${code}&state=${state}`, {
headers: new HttpHeaders({
@@ -175,6 +176,14 @@ export class AuthService {
});
}
discordLogin(code: string, state: string): Observable<TokenDTO> {
return this.http.get<TokenDTO>(`${this.appsettings.getApiURL()}/api/auth/discord/login?code=${code}&state=${state}`, {
headers: new HttpHeaders({
'Content-Type': 'application/json'
})
});
}
// /api/auth/discord/register?code=
discordRegister(oAuthDTO: OAuthDTO) {
return this.http.post(`${this.appsettings.getApiURL()}/api/auth/discord/register`, oAuthDTO, {

View File

@@ -3,7 +3,7 @@
"WebVersion": {
"Major": "0",
"Minor": "3",
"Micro": "dev70"
"Micro": "dev128"
},
"Themes": [
{

View File

@@ -93,6 +93,7 @@
"e_mail": "E-Mail",
"password": "Passwort",
"login": "Einloggen",
"login_with_discord": "Mit Discord Einloggen",
"register": "Registrieren",
"forgot_password": "Passwort vergessen?",
"e_mail_required": "E-Mail benötigt",

View File

@@ -373,7 +373,7 @@ footer {
.login-form-wrapper,
.auth-header {
width: 350px;
height: 350px;
height: 450px;
display: flex;
justify-content: center;