feat: init
This commit is contained in:
4
src/utils/__init__.py
Normal file
4
src/utils/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from .env import env
|
||||
from .logging import logger
|
||||
|
||||
__all__ = ["env", "logger"]
|
||||
15
src/utils/db/__init__.py
Normal file
15
src/utils/db/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from beanie import init_beanie
|
||||
from pymongo import AsyncMongoClient
|
||||
|
||||
from utils.env import env
|
||||
|
||||
client = AsyncMongoClient(env.db.connection_url)
|
||||
|
||||
|
||||
async def init_db():
|
||||
from .models import Channel, Invite, PendingPost, User
|
||||
|
||||
await init_beanie(
|
||||
database=client[env.db.db_name],
|
||||
document_models=[User, Channel, Invite, PendingPost],
|
||||
)
|
||||
6
src/utils/db/models/__init__.py
Normal file
6
src/utils/db/models/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from .channel import Channel, ChannelMember
|
||||
from .invite import Invite
|
||||
from .pending_post import PendingPost
|
||||
from .user import User
|
||||
|
||||
__all__ = ["Channel", "ChannelMember", "Invite", "PendingPost", "User"]
|
||||
58
src/utils/db/models/channel.py
Normal file
58
src/utils/db/models/channel.py
Normal file
@@ -0,0 +1,58 @@
|
||||
from typing import Literal
|
||||
|
||||
from beanie import Document
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ChannelMember(BaseModel):
|
||||
user_id: int
|
||||
display_name: str
|
||||
mode: Literal["instant", "approval"] = "approval"
|
||||
|
||||
|
||||
class Channel(Document):
|
||||
id: int
|
||||
owner_id: int
|
||||
title: str
|
||||
members: list[ChannelMember] = []
|
||||
|
||||
class Settings:
|
||||
name = "channels"
|
||||
|
||||
@classmethod
|
||||
async def get_by_id(cls, id_: int) -> "Channel | None":
|
||||
return await cls.find_one(cls.id == id_)
|
||||
|
||||
@classmethod
|
||||
async def get_user_channels(cls, user_id: int) -> list["Channel"]:
|
||||
return await cls.find(
|
||||
{"$or": [{"owner_id": user_id}, {"members.user_id": user_id}]}
|
||||
).to_list()
|
||||
|
||||
def get_member(self, user_id: int) -> ChannelMember | None:
|
||||
for m in self.members:
|
||||
if m.user_id == user_id:
|
||||
return m
|
||||
return None
|
||||
|
||||
async def add_member(
|
||||
self, user_id: int, display_name: str, mode: Literal["instant", "approval"]
|
||||
) -> None:
|
||||
if self.get_member(user_id) is not None:
|
||||
return
|
||||
self.members.append(
|
||||
ChannelMember(user_id=user_id, display_name=display_name, mode=mode)
|
||||
)
|
||||
await self.save()
|
||||
|
||||
async def remove_member(self, user_id: int) -> None:
|
||||
self.members = [m for m in self.members if m.user_id != user_id]
|
||||
await self.save()
|
||||
|
||||
async def set_member_mode(
|
||||
self, user_id: int, mode: Literal["instant", "approval"]
|
||||
) -> None:
|
||||
member = self.get_member(user_id)
|
||||
if member is not None:
|
||||
member.mode = mode
|
||||
await self.save()
|
||||
17
src/utils/db/models/invite.py
Normal file
17
src/utils/db/models/invite.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from typing import Literal
|
||||
|
||||
from beanie import Document
|
||||
|
||||
|
||||
class Invite(Document):
|
||||
code: str
|
||||
channel_id: int
|
||||
mode: Literal["instant", "approval"]
|
||||
used: bool = False
|
||||
|
||||
class Settings:
|
||||
name = "invites"
|
||||
|
||||
@classmethod
|
||||
async def get_by_code(cls, code: str) -> "Invite | None":
|
||||
return await cls.find_one(cls.code == code, cls.used == False) # noqa: E712
|
||||
15
src/utils/db/models/pending_post.py
Normal file
15
src/utils/db/models/pending_post.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from typing import Literal
|
||||
|
||||
from beanie import Document
|
||||
|
||||
|
||||
class PendingPost(Document):
|
||||
source_chat_id: int
|
||||
source_message_id: int
|
||||
channel_id: int
|
||||
contributor_id: int
|
||||
is_forwarded: bool
|
||||
status: Literal["pending", "accepted", "rejected"] = "pending"
|
||||
|
||||
class Settings:
|
||||
name = "pending_posts"
|
||||
21
src/utils/db/models/user.py
Normal file
21
src/utils/db/models/user.py
Normal file
@@ -0,0 +1,21 @@
|
||||
from beanie import Document
|
||||
|
||||
|
||||
class User(Document):
|
||||
id: int
|
||||
active_channel_id: int | None = None
|
||||
|
||||
class Settings:
|
||||
name = "users"
|
||||
|
||||
@classmethod
|
||||
async def get_by_id(cls, id_: int) -> "User | None":
|
||||
return await cls.find_one(cls.id == id_)
|
||||
|
||||
@classmethod
|
||||
async def get_or_create(cls, id_: int) -> "User":
|
||||
user = await cls.get_by_id(id_)
|
||||
if user is None:
|
||||
user = cls(id=id_)
|
||||
await user.insert()
|
||||
return user
|
||||
39
src/utils/env.py
Normal file
39
src/utils/env.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from pydantic import SecretStr
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
class BotSettings(BaseSettings):
|
||||
token: SecretStr
|
||||
|
||||
|
||||
class DatabaseSettings(BaseSettings):
|
||||
host: str = "mongodb"
|
||||
port: int = 27017
|
||||
user: str = "user"
|
||||
password: str = "password"
|
||||
db_name: str = "prod"
|
||||
connection_params: str = "?authSource=admin"
|
||||
|
||||
@property
|
||||
def connection_url(self) -> str:
|
||||
return f"mongodb://{self.user}:{self.password}@{self.host}:{self.port}/{self.db_name}{self.connection_params}"
|
||||
|
||||
|
||||
class LogSettings(BaseSettings):
|
||||
level: str = "INFO"
|
||||
level_external: str = "WARNING"
|
||||
show_time: bool = False
|
||||
console_width: int = 150
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
bot: BotSettings
|
||||
db: DatabaseSettings
|
||||
log: LogSettings
|
||||
|
||||
model_config = SettingsConfigDict(
|
||||
case_sensitive=False, env_file=".env", env_nested_delimiter="__", extra="ignore"
|
||||
)
|
||||
|
||||
|
||||
env = Settings() # ty:ignore[missing-argument]
|
||||
36
src/utils/logging.py
Normal file
36
src/utils/logging.py
Normal file
@@ -0,0 +1,36 @@
|
||||
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:
|
||||
from aiogram.dispatcher import router
|
||||
|
||||
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,
|
||||
tracebacks_suppress=[router],
|
||||
)
|
||||
],
|
||||
)
|
||||
install(console=console, show_locals=True)
|
||||
|
||||
|
||||
logger = logging.getLogger("post-proposal-bot")
|
||||
logger.setLevel(env.log.level)
|
||||
Reference in New Issue
Block a user