Initial Commit

This commit is contained in:
Daniel Haus 2026-03-06 16:05:24 +03:00
commit 4dbdc7d793
18 changed files with 2384 additions and 0 deletions

View file

@ -0,0 +1,15 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
from .backends import (
MysqlDatabase,
SqliteDatabase,
PostgresqlDatabase,
)
from . import auth
__all__ = [
"auth",
"MysqlDatabase",
"SqliteDatabase",
"PostgresqlDatabase"
]

View file

@ -0,0 +1,10 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
from .role import Role, RoleLevel
from .database import RBACMixin
__all__ = [
"Role",
"RoleLevel",
"RBACMixin"
]

View file

@ -0,0 +1,45 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
from pyqt6_scaffold.core.objects import BaseUser
class RBACMixin:
"""
Role-Based Access Control mixin for AbstractDatabase subclasses.
Provides a can() method that checks user permissions against
a permission table in the database. Table and column names
are configurable as class attributes.
Expected table schema:
permission_table (permission_column, level_column)
Example:
permission_map (perm VARCHAR, min_level INT)
"""
permission_table: str = "permission_map"
permission_column: str = "perm"
level_column: str = "min_level"
def can(self, user: BaseUser, permission: str) -> bool:
"""
Check whether a user has the required permission level.
Args:
user: A BaseUser instance with a role.level attribute.
permission: Permission identifier to look up in the database.
Returns:
True if user.role.level >= required level, False otherwise.
"""
with self.execute(
f"""
SELECT {self.level_column}
FROM {self.permission_table}
WHERE {self.permission_column} = {self.placeholder}
""",
(permission,)
) as cursor:
row = cursor.fetchone()
if not row:
return False
return user.role.level >= row[0]

View file

@ -0,0 +1,29 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
from enum import Enum
from dataclasses import dataclass
@dataclass
class Role:
"""
Represents a user role with a hierarchical access level.
Attributes:
name: Human-readable role name.
level: Numeric access level used for permission checks.
"""
name: str
level: int
class RoleLevel(Enum):
"""
Predefined access levels for common roles.
Use these constants when populating the permission table
or comparing role levels in application logic.
"""
GUEST = 0
CLIENT = 25
EMPLOYEE = 50
MANAGER = 75
ADMIN = 100

View file

@ -0,0 +1,87 @@
# SPDX-License-Identifier: LGPL-3.0-or-later
import os
from pyqt6_scaffold.core.logging import setup_logger
from pyqt6_scaffold.core.database import AbstractDatabase
log = setup_logger(__name__)
class PostgresqlDatabase(AbstractDatabase):
"""
AbstractDatabase implementation for PostgreSQL via psycopg2.
Configuration is read from environment variables:
PG_HOST, PG_PORT, PG_USER, PG_DATABASE, PG_PASSWORD
Requires:
pip install pyqt6-scaffold[postgres]
"""
@property
def placeholder(self) -> str:
return "%s"
def _connect(self):
try:
import psycopg2 as pg
except ImportError:
log.error("Ошибка импорта psycopg2")
raise
DB_CONFIG = {
"host": os.getenv("PG_HOST", "127.0.0.1"),
"port": int(os.getenv("PG_PORT", 5432)),
"user": os.getenv("PG_USER", "postgres"),
"database": os.getenv("PG_DATABASE", "postgres"),
"password": os.getenv("PG_PASSWORD", "postgres")
}
return pg.connect(**DB_CONFIG)
class MysqlDatabase(AbstractDatabase):
"""
AbstractDatabase implementation for MySQL via pymysql.
Configuration is read from environment variables:
MYSQL_HOST, MYSQL_PORT, MYSQL_USER, MYSQL_DATABASE, MYSQL_PASSWORD
Requires:
pip install pyqt6-scaffold[mysql]
"""
@property
def placeholder(self) -> str:
return "%s"
def _connect(self):
try:
import pymysql as pms
except ImportError:
log.error("Ошибка импорта pymysql")
raise
DB_CONFIG = {
"host": os.getenv("MYSQL_HOST", "localhost"),
"port": int(os.getenv("MYSQL_PORT", 3306)),
"user": os.getenv("MYSQL_USER", "root"),
"database": os.getenv("MYSQL_DATABASE", "root"),
"password": os.getenv("MYSQL_PASSWORD", "root")
}
return pms.connect(**DB_CONFIG)
class SqliteDatabase(AbstractDatabase):
"""
AbstractDatabase implementation for SQLite via the stdlib sqlite3 module.
Configuration is read from environment variables:
SQLITE_PATH (default: app.db)
No additional dependencies required.
"""
@property
def placeholder(self) -> str:
return "?"
def _connect(self):
import sqlite3
path = os.getenv("SQLITE_PATH", "app.db")
return sqlite3.connect(path)