feat: create backend skeleton
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
from .env import env
|
||||
from .logging import logger, setup_logging
|
||||
from .storage import ContentAddressedStorage
|
||||
|
||||
__all__ = ["ContentAddressedStorage", "env", "logger", "setup_logging"]
|
||||
@@ -0,0 +1,3 @@
|
||||
from . import models
|
||||
|
||||
__all__ = ["models"]
|
||||
@@ -0,0 +1,50 @@
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import BigInteger, Column, DateTime, func
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
from sqlmodel import Field, SQLModel
|
||||
|
||||
|
||||
class Account(SQLModel, table=True):
|
||||
__tablename__ = "accounts"
|
||||
|
||||
account_id: int | None = Field(default=None, primary_key=True)
|
||||
tg_user_id: int | None = Field(
|
||||
default=None, sa_column=Column(BigInteger, unique=True)
|
||||
)
|
||||
label: str | None = None
|
||||
phone: str | None = None
|
||||
session_name: str
|
||||
is_active: bool = True
|
||||
raw: dict[str, Any] = Field(
|
||||
default_factory=dict, sa_column=Column(JSONB, nullable=False)
|
||||
)
|
||||
created_at: datetime = Field(
|
||||
sa_column=Column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now()
|
||||
)
|
||||
)
|
||||
updated_at: datetime = Field(
|
||||
sa_column=Column(
|
||||
DateTime(timezone=True),
|
||||
nullable=False,
|
||||
server_default=func.now(),
|
||||
onupdate=func.now(),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class Message(SQLModel, table=True):
|
||||
__tablename__ = "messages"
|
||||
|
||||
account_id: int = Field(primary_key=True)
|
||||
chat_id: int = Field(sa_column=Column(BigInteger, primary_key=True))
|
||||
message_id: int = Field(sa_column=Column(BigInteger, primary_key=True))
|
||||
date: datetime = Field(sa_column=Column(DateTime(timezone=True), primary_key=True))
|
||||
raw: dict[str, Any] = Field(
|
||||
default_factory=dict, sa_column=Column(JSONB, nullable=False)
|
||||
)
|
||||
deleted_at: datetime | None = Field(
|
||||
default=None, sa_column=Column(DateTime(timezone=True))
|
||||
)
|
||||
@@ -0,0 +1,63 @@
|
||||
import os
|
||||
|
||||
from pydantic import Field, SecretStr
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class DatabaseSettings(BaseSettings):
|
||||
host: str = "postgres"
|
||||
port: int = 5432
|
||||
user: str = "beavergram"
|
||||
password: SecretStr = SecretStr("beavergram")
|
||||
db_name: str = "beavergram"
|
||||
min_pool_size: int = 5
|
||||
max_pool_size: int = 20
|
||||
scripts_connection_url: str = (
|
||||
"postgresql://beavergram:beavergram@localhost:5433/beavergram"
|
||||
)
|
||||
|
||||
@property
|
||||
def connection_url(self) -> str:
|
||||
if os.getenv("RUN_ENVIRONMENT") != "prod":
|
||||
return self.scripts_connection_url
|
||||
return (
|
||||
f"postgresql://{self.user}:{self.password.get_secret_value()}"
|
||||
f"@{self.host}:{self.port}/{self.db_name}"
|
||||
)
|
||||
|
||||
|
||||
class TelegramSettings(BaseSettings):
|
||||
session_name: str = "beavergram"
|
||||
sessions_dir: str = "sessions"
|
||||
|
||||
|
||||
class ApiSettings(BaseSettings):
|
||||
host: str = "0.0.0.0" # noqa: S104
|
||||
port: int = 8080
|
||||
|
||||
|
||||
class StorageSettings(BaseSettings):
|
||||
root: str = "storage"
|
||||
shard_depth: int = 2
|
||||
|
||||
|
||||
class LogSettings(BaseSettings):
|
||||
level: str = "INFO"
|
||||
level_external: str = "WARNING"
|
||||
show_time: bool = False
|
||||
console_width: int = 150
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
db: DatabaseSettings = Field(default_factory=DatabaseSettings)
|
||||
tg: TelegramSettings = Field(default_factory=TelegramSettings)
|
||||
api: ApiSettings = Field(default_factory=ApiSettings)
|
||||
storage: StorageSettings = Field(default_factory=StorageSettings)
|
||||
log: LogSettings = Field(default_factory=LogSettings)
|
||||
|
||||
model_config = SettingsConfigDict(
|
||||
case_sensitive=False, env_file=".env", env_nested_delimiter="__", extra="ignore"
|
||||
)
|
||||
|
||||
|
||||
env = Settings()
|
||||
@@ -0,0 +1,33 @@
|
||||
import logging
|
||||
|
||||
from rich.console import Console
|
||||
from rich.logging import RichHandler
|
||||
from rich.traceback import install
|
||||
|
||||
from .env import env
|
||||
|
||||
console = Console(width=env.log.console_width, color_system="auto", force_terminal=True)
|
||||
|
||||
|
||||
def setup_logging() -> None:
|
||||
logging.basicConfig(
|
||||
level=env.log.level_external,
|
||||
format="",
|
||||
datefmt=None,
|
||||
handlers=[
|
||||
RichHandler(
|
||||
console=console,
|
||||
markup=True,
|
||||
rich_tracebacks=True,
|
||||
enable_link_path=False,
|
||||
tracebacks_show_locals=True,
|
||||
omit_repeated_times=False,
|
||||
show_time=env.log.show_time,
|
||||
)
|
||||
],
|
||||
)
|
||||
install(console=console, show_locals=True)
|
||||
|
||||
|
||||
logger = logging.getLogger("beavergram")
|
||||
logger.setLevel(env.log.level)
|
||||
@@ -0,0 +1,29 @@
|
||||
import hashlib
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class ContentAddressedStorage:
|
||||
def __init__(self, root: str | Path, shard_depth: int = 2) -> None:
|
||||
self._root = Path(root)
|
||||
self._shard_depth = shard_depth
|
||||
|
||||
def _path(self, key: str) -> Path:
|
||||
shards = [key[i * 2 : i * 2 + 2] for i in range(self._shard_depth)]
|
||||
return self._root.joinpath(*shards, key)
|
||||
|
||||
def put(self, data: bytes) -> str:
|
||||
key = hashlib.sha256(data).hexdigest()
|
||||
path = self._path(key)
|
||||
if not path.exists():
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_bytes(data)
|
||||
return key
|
||||
|
||||
def get(self, key: str) -> bytes:
|
||||
return self._path(key).read_bytes()
|
||||
|
||||
def exists(self, key: str) -> bool:
|
||||
return self._path(key).exists()
|
||||
|
||||
def url(self, key: str) -> str:
|
||||
return str(self._path(key))
|
||||
Reference in New Issue
Block a user