Cixo Develop 6 сар өмнө
parent
commit
4e9afdfa84

+ 10 - 1
assets/__init__.py

@@ -10,6 +10,14 @@ from .validator import apikey_validator
 
 """ Exceptions. """
 from .exception import validator_exception
+from .exception import config_exception
+from .exception import exists_exception
+from .exception import bad_request_exception
+from .exception import not_found_exception
+from .exception import not_ready_exception
+from .exception import in_collection_exception
+from .exception import access_denied_exception
+from .exception import incomplete_request_exception
 
 """ Product and helpers. """
 from .product import product
@@ -31,6 +39,7 @@ from .user import user_exporter
 """ User loaders and collections. """
 from .users_collection import users_collection
 from .users_loader import users_loader
+from .users_saver import users_saver
 
 """ Configs and loaders. """
 from .config import config
@@ -39,4 +48,4 @@ from .config import config_generator
 
 """ App config and resources. """
 from .app_config import app_config
-from .app_resources import app_resources
+from .app_resources import app_resources

+ 11 - 2
assets/config.py

@@ -66,9 +66,18 @@ class config_generator:
             raise TypeError("Config is skeleton class for configs.")
 
         self.__config = target().defaults
-        self.__json_config = json.dumps(self.__config)
+    
+    def modify(self, key: str, value: str | int | float) -> object:
+        if not key in self.__config:
+            raise config_exception("Key \"" + key + "\" not exists.")
+
+        self.__config[key] = value 
+
+        return self
 
     def save(self, where: pathlib.Path) -> None:
+        json_config = json.dumps(self.__config)
+
         if where.exists():
             content = "Can not create config file \"" + str(where) + "\" "
             content = content + "because it already exists in filesystem."
@@ -76,7 +85,7 @@ class config_generator:
             raise config_exception(content)
 
         with where.open("w") as handler:
-            handler.write(self.__json_config)
+            handler.write(json_config)
 
 class config_loader:
     def __init__(self, target: type) -> None:

+ 20 - 2
assets/user.py

@@ -26,7 +26,6 @@ class user:
         self.__nick = nick
         self.__password = password
         self.__apikey = apikey
-        self.__result = None
 
     @property
     def nick(self) -> str:
@@ -46,6 +45,14 @@ class user:
 
         return self.__apikey
 
+    @property
+    def flat(self) -> dict:
+        return {
+            "nick": self.nick,
+            "password": self.password,
+            "apikey": self.apikey
+        }
+
     def __str__(self) -> str:
         """ 
         This create dump of the user.
@@ -95,6 +102,17 @@ class user_factory:
 
         return self.__apikey
 
+    def refresh_apikey(self) -> object:
+        """
+        This function drop current ApiKey to logout user.
+        
+        Returns:
+            (object): Instance itself
+        """
+
+        self.__apikey = None
+        return self
+
     @property
     def nick(self) -> str | None:
         """ This return nick of the factory. """
@@ -280,4 +298,4 @@ class user_exporter:
             "nick": self.target.nick,
             "password": self.target.password,
             "apikey": self.target.apikey
-        }
+        }

+ 42 - 2
assets/users_collection.py

@@ -17,6 +17,36 @@ class users_collection:
         self.__by_apikey = dict()
         self.__by_nick = dict()
 
+    def get_by_nick(self, nick: str) -> user | None:
+        """
+        This function return user by nick.
+
+        Parameters:
+            nick (str): Nick of the user to get
+
+        Returns:
+            (user | None): User with that nick, or None
+        """
+
+        if self.exists(nick):
+            return self.__by_nick[nick]
+
+        return None
+
+    def exists(self, nick: str) -> bool:
+        """
+        This function check that user with given nick exists in the 
+        collection.
+
+        Parameters:
+            nick (str): Nick of the user to check
+
+        Returns:    
+            (bool): True when user exists, or False if not
+        """
+
+        return nick in self.__by_nick
+    
     @property
     def all(self) -> list:
         """
@@ -26,13 +56,16 @@ class users_collection:
 
         return list(self.__by_apikey.values())
     
-    def add(self, target: user) -> None:
+    def add(self, target: user) -> object:
         """
         This add new user to the collection. When ApiKey or login already
         exists in the collection, then error had been raised.
 
         Parameters:
             target (user): New user to add
+
+        Returns:
+            (object): This object itself
         """
 
         if target.apikey in self.__by_apikey:
@@ -50,6 +83,8 @@ class users_collection:
         self.__by_apikey[target.apikey] = target
         self.__by_nick[target.nick] = target
 
+        return self
+
     def login(self, nick: str, password: str) -> user | None:
         """
         This try to login user by nick and password. When nick or password 
@@ -75,12 +110,15 @@ class users_collection:
 
         return target
 
-    def remove(self, target: user) -> None:
+    def remove(self, target: user) -> object:
         """
         This function remove target user from collection.
 
         Parameters:
             target (user): Target user to remove from collection
+
+        Returns:
+            (object): This object itself
         """
 
         exists = target.nick in self.__by_nick
@@ -98,6 +136,8 @@ class users_collection:
         self.__by_nick = by_nick
         self.__by_apikey = by_apikey
 
+        return self
+
     def get(self, apikey: str) -> user | None:
         """
         This try to load user by ApiKey. When user with that ApiKey exists

+ 2 - 2
assets/users_saver.py

@@ -27,7 +27,7 @@ class users_saver:
     def users(self) -> list:
         """ This return list of users in setup collection. """
 
-        return self.__collection.all
+        return [ count.flat for count in self.__collection.all ]
 
     def drop(self, where: pathlib.Path) -> object:
         """
@@ -77,7 +77,7 @@ class users_saver:
             raise RuntimeError("Where is not set.")
 
         if where.exists():
-            content = "Config file \"" + str(where) + "\" already exists.")
+            content = "Config file \"" + str(where) + "\" already exists."
             
             raise config_exception(content)
 

+ 181 - 0
core.py

@@ -0,0 +1,181 @@
+import typer
+import enum
+import pathlib
+import getpass
+import json
+
+from assets import *
+
+default_config = pathlib.Path("config.json")
+default_users_db = pathlib.Path("users.json")
+default_db = pathlib.Path("database.db")
+
+config_help = "This is configuration file of the app to use."
+users_db_help = "This is location of the users json database."
+db_help = "This is location of SQLite3 database file."
+
+description = "This is core reservationer package. It could be used to host "
+description = description + "app, or manage configuration or database."
+
+app = typer.Typer(help = description)
+
+class user_command(str, enum.Enum):
+    """
+    That commands could be used in the user subcommand.
+    """
+
+    add = "register"
+    delete = "delete"
+    password = "password-change"
+    logout = "full-logout"
+
+def password_prompt() -> str:
+    while True: 
+        first = getpass.getpass("Password: ")
+        second = getpass.getpass("Repeat password: ")
+
+        if first == second:
+            return first
+
+        print("Passwords do not match.")
+
[email protected]()
+def server(
+    port: int = typer.Option(8080, help = "Port to listen on."),
+    address: str = typer.Option("0.0.0.0", help = "Address to listen on."),
+    config: pathlib.Path = typer.Option(default_config, help = config_help)
+) -> None:
+    """
+    Start app on selected port and interfaces.
+    """
+
+    print(port)
+    print(address)
+
[email protected]()
+def user(
+    command: user_command = typer.Argument(help = "Command to run on users."),
+    nick: str = typer.Argument(help = "Nick of the user to work on."),
+    config: pathlib.Path = typer.Option(default_config, help = config_help)
+) -> None:
+    """
+    Modify user database.
+    """
+    
+    try:
+        loader = config_loader(app_config).load(config)
+        collection = loader.resources.users
+
+        # User adding 
+        if command == user_command.add:
+            
+            # Check that user exists
+            if collection.exists(nick):
+                raise Exception("User with that nick already exists.")
+
+            # Create new user
+            creator = user_factory()
+            creator.nick = nick
+            creator.password = password_prompt()
+            
+            # Add it
+            collection.add(creator.result)
+            
+            print("Adding \"" + nick + "\" to the database.")
+
+        # User remove
+        elif command == user_command.delete:
+            
+            # Load from database
+            target = collection.get_by_nick(nick)
+
+            # Check that user exists
+            if target is None:
+                raise Exception("User with given nick not exists.")
+            
+            # When exists remove it
+            collection.remove(target)
+
+        # Change user password
+        elif command == user_command.password:
+                
+            # Load user by nick 
+            target = collection.get_by_nick(nick)
+            
+            # Check that user exists
+            if target is None:
+                raise Exception("User not exists, can not change password.")
+
+            # Change password
+            handler = user_factory(target)
+            handler.password = password_prompt()
+            modified = handler.result
+            
+            # Store it in collection
+            collection.remove(target).add(modified)
+        
+        # Logout user from all devices
+        elif command == user_command.logout:
+            
+            # Load user from database
+            target = collection.get_by_nick(nick)
+            
+            # Check that exists
+            if target is None:
+                raise Exception("User not exists, can not logout.")
+
+            # Refresh apikey
+            modified = user_factory(target) \
+            .refresh_apikey() \
+            .result
+            
+            # Store result
+            collection.remove(target).add(modified)
+
+        # Save collection to file
+        users_saver(collection) \
+        .drop(loader.result.users_path) \
+        .save()
+
+        print("Users database saved success.")
+
+    except validator_exception as error:
+        print("Password is not correct, too easy to break.")
+
+    except json.JSONDecodeError as error:
+        print("User JSON has syntax exception.")
+        print(str(error))
+
+    except Exception as error:
+        print("Can not done work.")
+        print(str(error))
+
[email protected]()
+def initialize(
+    config: pathlib.Path = typer.Option(default_config, help = config_help),
+    users: pathlib.Path = typer.Option(default_users_db, help = users_db_help),
+    database: pathlib.Path = typer.Option(default_db, help = db_help)
+) -> None:
+    """
+    Initialize app configuration.
+    """
+
+    try:
+        
+        # Generating config file
+        config_generator(app_config) \
+        .modify("users_file", str(users)) \
+        .modify("database_uri", "sqlite:///" + str(database)) \
+        .save(config)
+        
+        # Generating new blank users database
+        users_saver(users_collection()).save(users)
+        
+        print("Config file is being created.")
+    
+    except Exception as error:
+        print("Config initialization failed.")
+        print(str(error))
+
+if __name__ == "__main__":
+    app()

+ 1 - 0
requirements.txt

@@ -1,2 +1,3 @@
 sqlmodel
 fastapi
+typer-slim