From b95b7caefd24652c1bf12ddcd4a41e04e8334bb1 Mon Sep 17 00:00:00 2001 From: TheNoxium Date: Fri, 28 Mar 2025 17:32:57 +0500 Subject: [PATCH] feat: added sql tables and db connection --- api/api/config/default.py | 20 +++++ api/api/db/connection/session.py | 63 +++++++++++++++ api/api/db/tables/__init__.py | 1 + api/api/db/tables/account.py | 57 +++++++++++++ api/api/db/tables/events.py | 29 +++++++ api/api/db/tables/process.py | 80 +++++++++++++++++++ api/api/schemas/account/__init__.py | 0 api/api/schemas/account/account.py | 30 +++++++ api/api/schemas/account/account_keyring.py | 27 +++++++ api/api/schemas/events/__init__.py | 0 api/api/schemas/events/list_events.py | 25 ++++++ api/api/schemas/process/__init__.py | 0 api/api/schemas/process/node_link.py | 20 +++++ api/api/schemas/process/process_schema.py | 20 +++++ .../process/process_version_archive.py | 11 +++ api/api/schemas/process/ps_node.py | 23 ++++++ 16 files changed, 406 insertions(+) create mode 100644 api/api/db/connection/session.py create mode 100644 api/api/db/tables/account.py create mode 100644 api/api/db/tables/events.py create mode 100644 api/api/db/tables/process.py create mode 100644 api/api/schemas/account/__init__.py create mode 100644 api/api/schemas/account/account.py create mode 100644 api/api/schemas/account/account_keyring.py create mode 100644 api/api/schemas/events/__init__.py create mode 100644 api/api/schemas/events/list_events.py create mode 100644 api/api/schemas/process/__init__.py create mode 100644 api/api/schemas/process/node_link.py create mode 100644 api/api/schemas/process/process_schema.py create mode 100644 api/api/schemas/process/process_version_archive.py create mode 100644 api/api/schemas/process/ps_node.py diff --git a/api/api/config/default.py b/api/api/config/default.py index 65483b5..3b3b025 100644 --- a/api/api/config/default.py +++ b/api/api/config/default.py @@ -43,6 +43,26 @@ class DefaultSettings(BaseSettings): REDIS_DB: int = int(environ.get("REDIS_DB", "0")) REDIS_PASSWORD: str = environ.get("REDIS_PASSWORD", "hackme") + @property + def database_settings(self) -> dict: + """Get all settings for connection with database.""" + return { + "database": self.MYSQL_DB, + "user": self.MYSQL_USER, + "password": self.MYSQL_PASSWORD, + "host": self.MYSQL_HOST, + "port": self.MYSQL_PORT, + } + + @property + def database_uri(self) -> str: + """Get uri for connection with database.""" + uri = "mysql+aiomysql://{user}:{password}@{host}:{port}/{database}".format( + **self.database_settings, + ) + print("database_uri", uri) + return uri + class Config: # env_file = "../.env" env_file_encoding = "utf-8" diff --git a/api/api/db/connection/session.py b/api/api/db/connection/session.py new file mode 100644 index 0000000..1137f39 --- /dev/null +++ b/api/api/db/connection/session.py @@ -0,0 +1,63 @@ +import contextlib +import json +import os +from typing import Any, AsyncGenerator + +import asyncio + +import sqlalchemy +from loguru import logger +from sqlalchemy.ext.asyncio import AsyncConnection, AsyncEngine, create_async_engine +from sqlalchemy import URL,create_engine, text + + +from api.config import get_settings +from api.config.default import DbCredentialsSchema + + + +class SessionManager: + engines: Any + + + def __init__(self, database_uri=get_settings().database_uri) -> None: + self.database_uri = database_uri + self.refresh(database_uri) + # self.reflect() + + def __new__(cls, database_uri=get_settings().database_uri): + if not hasattr(cls, "instance"): + cls.instance = super(SessionManager, cls).__new__(cls) + cls.instance.engines = {} + return cls.instance + + def refresh(self, database_uri) -> None: + # if not self.engines: + # self.engines = {} + if database_uri not in self.engines: + self.engines[database_uri] = create_async_engine( + database_uri, + echo=True, + future=True, + # json_serializer=serializer, + pool_recycle=1800, + pool_size=get_settings().CONNECTION_POOL_SIZE, + max_overflow=get_settings().CONNECTION_OVERFLOW, + ) + def get_engine_by_db_uri(self, database_uri) -> AsyncEngine: + return self.engines[database_uri] + +@contextlib.asynccontextmanager +async def get_connection( + database_uri=None, +) -> AsyncGenerator[AsyncConnection, None]: + if not database_uri: + database_uri = get_settings().database_uri + engine = SessionManager(database_uri).get_engine_by_db_uri(database_uri) + logger.debug(f"engine {engine} {SessionManager(database_uri).engines}") + async with engine.connect() as conn: + yield conn + +async def get_connection_dep() -> AsyncConnection: + async with get_connection() as conn: + yield conn diff --git a/api/api/db/tables/__init__.py b/api/api/db/tables/__init__.py index e69de29..d531ebd 100644 --- a/api/api/db/tables/__init__.py +++ b/api/api/db/tables/__init__.py @@ -0,0 +1 @@ +from . import account,events,process diff --git a/api/api/db/tables/account.py b/api/api/db/tables/account.py new file mode 100644 index 0000000..07ffe3f --- /dev/null +++ b/api/api/db/tables/account.py @@ -0,0 +1,57 @@ +from sqlalchemy import Table, Column, Integer, String, Enum as SQLAEnum, JSON, ForeignKey, DateTime, Index, MetaData +from sqlalchemy.sql import func +from enum import Enum, auto +from api.db import metadata + + +class AccountRole(str,Enum): + OWNER = auto() + ADMIN = auto() + EDITOR = auto() + VIEWER = auto() + +class AccountStatus(str,Enum): + ACTIVE = auto() + DISABLED = auto() + BLOCKED = auto() + DELETED = auto() + + + +account_table = Table( + 'account', metadata, + Column('id', Integer, primary_key=True, autoincrement=True), + Column('name', String(100), nullable=False), + Column('login', String(100), nullable=False), + Column('email', String(100), nullable=True), + Column('bind_tenant_id', String(40), nullable=True), + Column('role', SQLAEnum(AccountRole), nullable=False), + Column('meta', JSON, default={}), + Column('creator_id', Integer, ForeignKey('account.id'), nullable=True), + Column('created_at', DateTime(timezone=True), server_default=func.now()), + Column('status', SQLAEnum(AccountStatus), nullable=False), + + Index('idx_login', 'login'), + Index('idx_name', 'name'), +) + +class KeyType(str,Enum): + PASSWORD = auto() + ACCESS_TOKEN = auto() + REFRESH_TOKEN = auto() + API_KEY = auto() + +class KeyStatus(str,Enum): + ACTIVE = auto() + EXPIRED = auto() + DELETED = auto() + +account_keyring_table = Table( + 'account_keyring', metadata, + Column('owner_id', Integer, ForeignKey('account.id'), primary_key=True, nullable=False), + Column('key_type', SQLAEnum(KeyType), primary_key=True, nullable=False), + Column('key_id', String(40), default=None), + Column('key_value', String(64), nullable=False), + Column('created_at', DateTime(timezone=True), server_default=func.now()), + Column('expiry', DateTime(timezone=True), nullable=True), + Column('status', SQLAEnum(KeyStatus), nullable=False), ) diff --git a/api/api/db/tables/events.py b/api/api/db/tables/events.py new file mode 100644 index 0000000..51179cf --- /dev/null +++ b/api/api/db/tables/events.py @@ -0,0 +1,29 @@ +from sqlalchemy import Table, Column, Integer, String, Enum as SQLAEnum, JSON, ForeignKey, DateTime, Index +from sqlalchemy.sql import func +from enum import Enum, auto +from api.db import metadata + +class EventState(str, Enum): + AUTO = auto() + DESCRIPTED = auto() + +class EventStatus(str, Enum): + ACTIVE = auto() + DISABLED = auto() + DELETED = auto() + + +list_events_table = Table( + 'list_events', metadata, + Column('id', Integer, primary_key=True, autoincrement=True), + Column('name', String(40, collation='latin1_bin'), nullable=False), + Column('title', String(64), nullable=False), + Column('creator_id', Integer, ForeignKey('account.id'), nullable=False), + Column('created_at', DateTime(timezone=True), server_default=func.now()), + Column('schema', JSON, default={}), + Column('state', SQLAEnum(EventState), nullable=False), + Column('status', SQLAEnum(EventStatus), nullable=False), + + + Index('UNIQUE_name', 'name', unique=True) +) diff --git a/api/api/db/tables/process.py b/api/api/db/tables/process.py new file mode 100644 index 0000000..0d98353 --- /dev/null +++ b/api/api/db/tables/process.py @@ -0,0 +1,80 @@ +from sqlalchemy import Table, Column, Integer, String, Text, Enum as SQLAEnum, JSON, ForeignKey, DateTime, Index, PrimaryKeyConstraint +from sqlalchemy.sql import func +from enum import Enum, auto +from api.db import metadata + + +# Определение перечислений для статуса процесса +class ProcessStatus(str, Enum): + ACTIVE = auto() + STOPPING = auto() + STOPPED = auto() + DELETED = auto() + +# Определение таблицы process_schema +process_schema_table = Table( + 'process_schema', metadata, + Column('id', Integer, primary_key=True, autoincrement=True), + Column('title', String(100), nullable=False), + Column('description', Text, nullable=False), + Column('owner_id', Integer, ForeignKey('account.id'), nullable=False), + Column('creator_id', Integer, ForeignKey('account.id'), nullable=False), + Column('created_at', DateTime(timezone=True), server_default=func.now()), + Column('settings', JSON, default={}), + Column('status', SQLAEnum(ProcessStatus), nullable=False), + + Index('idx_owner_id', 'owner_id',) +) + +process_version_archive_table = Table( + 'process_version_archive', metadata, + Column('id', Integer, autoincrement=True, nullable=False), + Column('version', Integer, default=1, nullable=False), + Column('snapshot', JSON, default={}), + Column('owner_id', Integer, ForeignKey('account.id'), nullable=False), + Column('created_at', DateTime(timezone=True), server_default=func.now()), + Column('is_last', Integer, default=0), + PrimaryKeyConstraint('id', 'version') ) + +class NodeStatus(str, Enum): + ACTIVE = auto() + DISABLED = auto() + DELETED = auto() + +class NodeType(Enum): + TYPE1 = 'Type1' + TYPE2 = 'Type2' + TYPE3 = 'Type3' + +ps_node_table = Table( + 'ps_node', metadata, + Column('id', Integer, autoincrement=True, primary_key=True, nullable=False), + Column('ps_id', Integer, ForeignKey('process_schema.id'), nullable=False), + Column('node_type', SQLAEnum(NodeType), nullable=False), + Column('settings', JSON, default={}), + Column('creator_id', Integer, ForeignKey('account.id'), nullable=False), + Column('created_at', DateTime(timezone=True), server_default=func.now()), + Column('status', SQLAEnum(NodeStatus), nullable=False), + + Index('idx_ps_id', 'ps_id') +) + +class NodeLinkStatus(str, Enum): + ACTIVE = auto() + STOPPING = auto() + STOPPED = auto() + DELETED = auto() + +node_link_table = Table( + 'node_link', metadata, + Column('id', Integer, autoincrement=True, primary_key=True, nullable=False), + Column('link_name', String(20), nullable=False), + Column('node_id', Integer, ForeignKey('ps_node.id'), nullable=False), + Column('next_node_id', Integer, ForeignKey('ps_node.id'), nullable=False), + Column('settings', JSON, default={}), + Column('creator_id', Integer, ForeignKey('account.id'), nullable=False), + Column('created_at', DateTime(timezone=True), server_default=func.now()), + Column('status', SQLAEnum(NodeLinkStatus),nullable=False), + + Index('idx_node_id', 'node_id'), + Index('idx_next_node_id', 'next_node_id')) diff --git a/api/api/schemas/account/__init__.py b/api/api/schemas/account/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/api/schemas/account/account.py b/api/api/schemas/account/account.py new file mode 100644 index 0000000..0abe918 --- /dev/null +++ b/api/api/schemas/account/account.py @@ -0,0 +1,30 @@ +import datetime +from enum import Enum + +from pydantic import BaseModel, EmailStr, Field + + +class Role(Enum): + OWNER = "Owner" + ADMIN = "Admin" + EDITOR = "Editor" + VIEWER = "Viewer" + +class Status(Enum): + ACTIVE = "Active" + DISABLED = "Disabled" + BLOCKED = "Blocked" + DELETED = "Deleted" + +class User(BaseModel): + id: int + name: str = Field(..., max_length=100) + login: str = Field(..., max_length=100) + email: EmailStr = Field(..., max_length=100) + bind_tenant_id: str = Field(..., max_length=40) + role: Role + meta: dict + creator_id: int + is_active: bool + created_at: datetime + status: Status diff --git a/api/api/schemas/account/account_keyring.py b/api/api/schemas/account/account_keyring.py new file mode 100644 index 0000000..8e2a0f0 --- /dev/null +++ b/api/api/schemas/account/account_keyring.py @@ -0,0 +1,27 @@ +import datetime + +from enum import Enum + +from pydantic import BaseModel, Field +from datetime import datetime + + +class Type(Enum): + PASSWORD = "password" + ACCESS_TOKEN = "access_token" + REFRESH_TOKEN = "refresh_token" + API_KEY = "api_key" + +class Status(Enum): + ACTIVE = "Active" + EXPIRED = "Expired" + DELETED = "Deleted" + +class AccountKeyring(BaseModel): + owner_id: int + key_type: Type + key_id: str = Field(..., max_length=40) + key_value: str = Field(..., max_length=64) + created_at: datetime + expiry: datetime + status: Status diff --git a/api/api/schemas/events/__init__.py b/api/api/schemas/events/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/api/schemas/events/list_events.py b/api/api/schemas/events/list_events.py new file mode 100644 index 0000000..f70ddf5 --- /dev/null +++ b/api/api/schemas/events/list_events.py @@ -0,0 +1,25 @@ +from pydantic import BaseModel, Field +from typing import Dict, Any +from datetime import datetime +from enum import Enum + + +class State(Enum): + AUTO = "Auto" + DESCRIPTED = "Descripted" + + +class Status(Enum): + ACTIVE = "Active" + DISABLED = "Disabled" + DELETED = "Deleted" + +class ListEvent(BaseModel): + id: int + name: str = Field(..., max_length=40) + title: str = Field(..., max_length=64) + creator_id: int + created_at: datetime + schema: Dict[str, Any] + state: State + status: Status diff --git a/api/api/schemas/process/__init__.py b/api/api/schemas/process/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/api/api/schemas/process/node_link.py b/api/api/schemas/process/node_link.py new file mode 100644 index 0000000..794391d --- /dev/null +++ b/api/api/schemas/process/node_link.py @@ -0,0 +1,20 @@ +from pydantic import BaseModel, Field, conint +from typing import Dict, Any +from datetime import datetime +from enum import Enum + +class Status(Enum): + ACTIVE = "Active" + STOPPING = "Stopping" + STOPPED = "Stopped" + DELETED = "Deleted" + +class MyModel(BaseModel): + id: int + link_name: str = Field(..., max_length=20) + node_id: int + next_node_id: int + settings: Dict[str, Any] + creator_id: int + created_at: datetime + status: Status diff --git a/api/api/schemas/process/process_schema.py b/api/api/schemas/process/process_schema.py new file mode 100644 index 0000000..d4150a4 --- /dev/null +++ b/api/api/schemas/process/process_schema.py @@ -0,0 +1,20 @@ +from pydantic import BaseModel, Field +from typing import Dict, Any +from datetime import datetime +from enum import Enum + +class Status(Enum): + ACTIVE = "Active" + STOPPING = "Stopping" + STOPPED = "Stopped" + DELETED = "Deleted" + +class ProcessSchema(BaseModel): + id: int + title: str = Field(..., max_length=100) + description: str + owner_id: int + creator_id: int + created_at: datetime + settings: Dict[str, Any] + status: Status diff --git a/api/api/schemas/process/process_version_archive.py b/api/api/schemas/process/process_version_archive.py new file mode 100644 index 0000000..556e2ff --- /dev/null +++ b/api/api/schemas/process/process_version_archive.py @@ -0,0 +1,11 @@ +from pydantic import BaseModel, Field +from typing import Dict, Any +from datetime import datetime + +class ProcessStatusSchema(BaseModel): + id: int + version: int + snapshot: Dict[str, Any] + owner_id: int + created_at: datetime + is_last: int diff --git a/api/api/schemas/process/ps_node.py b/api/api/schemas/process/ps_node.py new file mode 100644 index 0000000..e9e6c40 --- /dev/null +++ b/api/api/schemas/process/ps_node.py @@ -0,0 +1,23 @@ +from pydantic import BaseModel +from datetime import datetime +from typing import Dict, Any +from enum import Enum + + +class NodeType(Enum): + + pass + +class Status(Enum): + ACTIVE = "Active" + DISABLED = "Disabled" + DELETED = "Deleted" + +class Ps_Node(BaseModel): + id: int + ps_id: int + node_type: NodeType + settings: dict + creator_id: Dict[str, Any] + created_at: datetime + status: Status