From 87569fd364c77b77b75dbd79e8dbc15a81680be1 Mon Sep 17 00:00:00 2001 From: Stefano Pigozzi Date: Wed, 6 May 2020 21:22:48 +0200 Subject: [PATCH] Change the backpack database schema --- royalnet/__main__.py | 5 ++ royalnet/backpack/commands/__init__.py | 10 ++- royalnet/backpack/commands/royalnetaliases.py | 24 +++++++ royalnet/backpack/commands/royalnetroles.py | 24 +++++++ .../commands/{link.py => royalnetsync.py} | 4 +- .../{version.py => royalnetversion.py} | 4 +- royalnet/backpack/tables/__init__.py | 2 + royalnet/backpack/tables/aliases.py | 13 +--- royalnet/backpack/tables/roles.py | 32 +++++++++ royalnet/backpack/tables/users.py | 70 ++++++++++++++++--- 10 files changed, 160 insertions(+), 28 deletions(-) create mode 100644 royalnet/backpack/commands/royalnetaliases.py create mode 100644 royalnet/backpack/commands/royalnetroles.py rename royalnet/backpack/commands/{link.py => royalnetsync.py} (97%) rename royalnet/backpack/commands/{version.py => royalnetversion.py} (89%) create mode 100644 royalnet/backpack/tables/roles.py diff --git a/royalnet/__main__.py b/royalnet/__main__.py index 5ddcdb9d..6e50c41e 100644 --- a/royalnet/__main__.py +++ b/royalnet/__main__.py @@ -157,14 +157,19 @@ def run(config_filename: str): log.info("All processes started!") if constellation_process is not None: + log.info("Waiting for Constellation to stop...") constellation_process.join() if telegram_process is not None: + log.info("Waiting for Serf.Telegram to stop...") telegram_process.join() if discord_process is not None: + log.info("Waiting for Serf.Discord to stop...") discord_process.join() if matrix_process is not None: + log.info("Waiting for Serf.Matrix to stop...") matrix_process.join() if herald_process is not None: + log.info("Waiting for Herald to stop...") herald_process.join() diff --git a/royalnet/backpack/commands/__init__.py b/royalnet/backpack/commands/__init__.py index 50c1aa68..67afda9d 100644 --- a/royalnet/backpack/commands/__init__.py +++ b/royalnet/backpack/commands/__init__.py @@ -1,9 +1,15 @@ # Imports go here! -from .version import VersionCommand +from .royalnetversion import RoyalnetversionCommand +from .royalnetsync import RoyalnetsyncCommand +from .royalnetroles import RoyalnetrolesCommand +from .royalnetaliases import RoyalnetaliasesCommand # Enter the commands of your Pack here! available_commands = [ - VersionCommand, + RoyalnetversionCommand, + RoyalnetsyncCommand, + RoyalnetrolesCommand, + RoyalnetaliasesCommand, ] # Don't change this, it should automatically generate __all__ diff --git a/royalnet/backpack/commands/royalnetaliases.py b/royalnet/backpack/commands/royalnetaliases.py new file mode 100644 index 00000000..3ddab36f --- /dev/null +++ b/royalnet/backpack/commands/royalnetaliases.py @@ -0,0 +1,24 @@ +from typing import * +import royalnet +import royalnet.commands as rc +import royalnet.utils as ru +from ..tables.telegram import Telegram +from ..tables.discord import Discord + + +class RoyalnetaliasesCommand(rc.Command): + name: str = "royalnetaliases" + + description: str = "Display your Royalnet aliases." + + syntax: str = "" + + async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None: + author = await data.get_author(error_if_none=True) + + msg = [ + "👤 You currently have these aliases:", + *list(map(lambda r: f"- {r}", author.aliases)) + ] + + await data.reply("\n".join(msg)) diff --git a/royalnet/backpack/commands/royalnetroles.py b/royalnet/backpack/commands/royalnetroles.py new file mode 100644 index 00000000..cef7918e --- /dev/null +++ b/royalnet/backpack/commands/royalnetroles.py @@ -0,0 +1,24 @@ +from typing import * +import royalnet +import royalnet.commands as rc +import royalnet.utils as ru +from ..tables.telegram import Telegram +from ..tables.discord import Discord + + +class RoyalnetrolesCommand(rc.Command): + name: str = "royalnetroles" + + description: str = "Display your Royalnet roles." + + syntax: str = "" + + async def run(self, args: rc.CommandArgs, data: rc.CommandData) -> None: + author = await data.get_author(error_if_none=True) + + msg = [ + "👤 You currently have these roles:", + *list(map(lambda r: f"- {r}", author.roles)) + ] + + await data.reply("\n".join(msg)) diff --git a/royalnet/backpack/commands/link.py b/royalnet/backpack/commands/royalnetsync.py similarity index 97% rename from royalnet/backpack/commands/link.py rename to royalnet/backpack/commands/royalnetsync.py index 30c38730..1d382ca2 100644 --- a/royalnet/backpack/commands/link.py +++ b/royalnet/backpack/commands/royalnetsync.py @@ -6,8 +6,8 @@ from ..tables.telegram import Telegram from ..tables.discord import Discord -class SyncCommand(rc.Command): - name: str = "sync" +class RoyalnetsyncCommand(rc.Command): + name: str = "royalnetsync" description: str = "Connect your chat account to Royalnet!" diff --git a/royalnet/backpack/commands/version.py b/royalnet/backpack/commands/royalnetversion.py similarity index 89% rename from royalnet/backpack/commands/version.py rename to royalnet/backpack/commands/royalnetversion.py index 6b72d368..c4583194 100644 --- a/royalnet/backpack/commands/version.py +++ b/royalnet/backpack/commands/royalnetversion.py @@ -2,8 +2,8 @@ import royalnet.version from royalnet.commands import * -class VersionCommand(Command): - name: str = "version" +class RoyalnetversionCommand(Command): + name: str = "royalnetversion" description: str = "Display the current Royalnet version." diff --git a/royalnet/backpack/tables/__init__.py b/royalnet/backpack/tables/__init__.py index 3a18b96f..06c75994 100644 --- a/royalnet/backpack/tables/__init__.py +++ b/royalnet/backpack/tables/__init__.py @@ -5,6 +5,7 @@ from .discord import Discord from .matrix import Matrix from .aliases import Alias from .tokens import Token +from .roles import Role # Enter the tables of your Pack here! available_tables = { @@ -14,6 +15,7 @@ available_tables = { Matrix, Alias, Token, + Role, } # Don't change this, it should automatically generate __all__ diff --git a/royalnet/backpack/tables/aliases.py b/royalnet/backpack/tables/aliases.py index 8c2bd672..5e4dad09 100644 --- a/royalnet/backpack/tables/aliases.py +++ b/royalnet/backpack/tables/aliases.py @@ -1,11 +1,9 @@ -from typing import * from sqlalchemy import Column, \ Integer, \ String, \ ForeignKey from sqlalchemy.orm import relationship from sqlalchemy.ext.declarative import declared_attr -import royalnet.utils as ru class Alias: @@ -21,16 +19,9 @@ class Alias: @declared_attr def user(self): - return relationship("User", backref="aliases") + return relationship("User", backref="_aliases") - @classmethod - async def find_user(cls, alchemy, session, alias: Union[str, int]): - result = await ru.asyncify(session.query(alchemy.get(cls)).filter_by(alias=alias.lower()).one_or_none) - if result is not None: - result = result.user - return result - - def __init__(self, user: str, alias: str): + def __init__(self, user, alias: str): self.user = user self.alias = alias.lower() diff --git a/royalnet/backpack/tables/roles.py b/royalnet/backpack/tables/roles.py new file mode 100644 index 00000000..f8dcbc45 --- /dev/null +++ b/royalnet/backpack/tables/roles.py @@ -0,0 +1,32 @@ +from sqlalchemy import Column, \ + Integer, \ + String, \ + ForeignKey +from sqlalchemy.orm import relationship +from sqlalchemy.ext.declarative import declared_attr + + +class Role: + __tablename__ = "role" + + @declared_attr + def user_id(self): + return Column(Integer, ForeignKey("users.uid"), primary_key=True) + + @declared_attr + def role(self): + return Column(String, primary_key=True) + + @declared_attr + def user(self): + return relationship("User", backref="_roles") + + def __init__(self, user, role: str): + self.user = user + self.role = role.lower() + + def __repr__(self): + return f"" + + def __str__(self): + return f"{self.role}->{self.user_id}" diff --git a/royalnet/backpack/tables/users.py b/royalnet/backpack/tables/users.py index d63a33a5..d267efa3 100644 --- a/royalnet/backpack/tables/users.py +++ b/royalnet/backpack/tables/users.py @@ -1,9 +1,15 @@ +from typing import * import bcrypt +import royalnet.utils as ru from sqlalchemy import Column, \ Integer, \ String, \ - LargeBinary + LargeBinary, \ + inspect from sqlalchemy.ext.declarative import declared_attr +from .roles import Role +from .aliases import Alias +from ...utils import JSON, asyncify # noinspection PyAttributeOutsideInit @@ -18,36 +24,78 @@ class User: def username(self): return Column(String, unique=True, nullable=False) + @declared_attr + def email(self): + return Column(String, unique=True) + @declared_attr def password(self): return Column(LargeBinary) @declared_attr - def role(self): - return Column(String, nullable=False) + def avatar_url(self): + return Column(String) - @declared_attr - def avatar(self): - return Column(LargeBinary) + @staticmethod + async def find_user(alchemy, session, alias: Union[str, int]): + result = await ru.asyncify(session.query(alchemy.get(Alias)).filter_by(alias=alias.lower()).one_or_none) + if result is not None: + result = result.user + return result - def json(self): + def json(self) -> JSON: return { "uid": self.uid, "username": self.username, - "role": self.role, - "avatar": self.avatar + "email": self.email, + "password_set": self.password is not None, + "avatar_url": self.avatar_url, + "roles": self.roles, + "aliases": self.aliases } - def set_password(self, password: str): + def set_password(self, password: str) -> None: byte_password: bytes = bytes(password, encoding="UTF8") self.password = bcrypt.hashpw(byte_password, bcrypt.gensalt(14)) - def test_password(self, password: str): + def test_password(self, password: str) -> bool: if self.password is None: raise ValueError("No password is set") byte_password: bytes = bytes(password, encoding="UTF8") return bcrypt.checkpw(byte_password, self.password) + @property + def roles(self) -> list: + return list(map(lambda a: a.role, self._roles)) + + def add_role(self, alchemy, role: str) -> None: + role = role.lower() + session = inspect(self).session + session.add(alchemy.get(Role)(user=self, role=role)) + + async def delete_role(self, alchemy, role: str) -> None: + role = role.lower() + session = inspect(self).session + role = await asyncify(session.query(alchemy.get(Role)).filter_by(user=self, role=role).one_or_none) + if role is not None: + session.delete(role) + + @property + def aliases(self) -> list: + return list(map(lambda a: a.alias, self._aliases)) + + def add_alias(self, alchemy, alias: str) -> None: + alias = alias.lower() + session = inspect(self).session + session.add(alchemy.get(Alias)(user=self, alias=alias)) + + async def delete_alias(self, alchemy, alias: str) -> None: + alias = alias.lower() + session = inspect(self).session + alias = await asyncify(session.query(alchemy.get(Alias)).filter_by(user=self, alias=alias).one_or_none) + if alias is not None: + session.delete(alias) + def __repr__(self): return f"<{self.__class__.__qualname__} {self.username}>"