Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 43 additions & 27 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,36 +1,52 @@
# 用于构建和设置变量
FROM python:3.12-alpine AS builder
# Single-stage build for Python application
FROM python:3.12-slim

# 设置时区为Asia/Shanghai, DOCKER_MODE为1
# Set environment variables
ENV TZ=Asia/Shanghai \
DOCKER_MODE=1 \
PUID=0 \
PGID=0 \
UMASK=000 \
PYTHONWARNINGS="ignore:semaphore_tracker:UserWarning" \
WORKDIR="/app"

# 设置默认工作目录
DOCKER_MODE=1 \
PUID=0 \
PGID=0 \
UMASK=000 \
PYTHONWARNINGS="ignore:semaphore_tracker:UserWarning" \
WORKDIR="/app" \
PATH="/root/.local/bin:${PATH}"

# Set working directory
WORKDIR ${WORKDIR}

#复制uv lockfile到工作目录中
COPY uv.lock ${WORKDIR}

# 安装必要的环境
RUN apk add --no-cache --virtual .build-deps gcc git musl-dev \
&& wget -qO- https://astral.sh/uv/install.sh | sh \
&& source /root/.local/bin/env \
&& uv sync \
&& uv cache clean \
&& apk del --purge .build-deps \
&& rm -rf /tmp/* /root/.cache /var/cache/apk/*

# 将从构建上下文目录中的文件和目录复制到新的一层的镜像内的工作目录中
# Copy requirements files first for better caching
COPY pyproject.toml uv.lock .python-version ./

# Install uv and application dependencies
# Use bash explicitly to support 'source' command
RUN apt-get update && \
apt-get install -y --no-install-recommends \
gcc \
git \
wget \
ca-certificates \
bash \
libc6-dev \
python3-dev && \
# Log Python version from .python-version file
echo "Target Python version from .python-version: $(cat .python-version)" && \
# Install uv
wget -qO- https://astral.sh/uv/install.sh | bash && \
# Make uv available without source
bash -c 'export PATH="/root/.local/bin:$PATH" && \
# Install project dependencies from pyproject.toml
/root/.local/bin/uv sync' && \
# Clean up build dependencies
apt-get purge -y --auto-remove gcc git wget && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /root/.cache

# Copy the rest of the application
COPY . .

# 将应用日志输出到stdout
# Redirect logs to stdout
RUN ln -sf /dev/stdout /app/default.log

# 定义容器启动时执行的默认命令
ENTRYPOINT ["/root/.local/bin/uv","run","app.py"]
# Define entrypoint using uv
ENTRYPOINT ["/root/.local/bin/uv", "run", "app.py"]

57 changes: 27 additions & 30 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,54 +3,54 @@
from datetime import datetime

import pytz
from py_tools.connections.db.mysql import DBManager, BaseOrmTable, SQLAlchemyManager
from sqlalchemy import text
from sqlalchemy.ext.asyncio import create_async_engine

from bot.bot_client import BotClient
from bot.commands import CommandHandler
from config import config
from core.emby_api import EmbyApi, EmbyRouterAPI
from services import UserService
from models.database import (
init_db,
create_database_if_not_exists,
create_tables,
)

# Initialize logger
logger = logging.getLogger(__name__)


async def create_database_if_not_exists() -> None:
"""创建数据库。"""
engine_without_db = create_async_engine(
f"mysql+asyncmy://{config.db_user}:{config.db_pass}@{config.db_host}:{config.db_port}/",
echo=True,
)
async with engine_without_db.begin() as conn:
query = f"CREATE DATABASE IF NOT EXISTS {config.db_name}"
logger.info(f"SQL Query: {query}, Context: Creating database")
await conn.execute(text(query))
await engine_without_db.dispose()
# <CURRENT_CURSOR_POSITION>


async def _init_db() -> None:
"""初始化数据库连接并创建表。"""
await create_database_if_not_exists()
# Create database if it doesn't exist
await create_database_if_not_exists(
host=config.db_host,
port=config.db_port,
user=config.db_user,
password=config.db_pass,
db_name=config.db_name,
)

db_client = SQLAlchemyManager(
# Initialize the engine and session factory
await init_db(
host=config.db_host,
port=config.db_port,
user=config.db_user,
password=config.db_pass,
db_name=config.db_name,
echo=True,
)
db_client.init_mysql_engine()
DBManager.init_db_client(db_client)

async with DBManager.connection() as conn:
logger.info("Context: Creating tables")
await conn.run_sync(BaseOrmTable.metadata.create_all)
# Create all tables
await create_tables()


def _init_logger() -> None:
"""初始化日志记录器。"""
# Clear any existing handlers to prevent duplicates
logger.handlers = []

# Create handlers
handler = logging.StreamHandler() # 输出到终端
fmt = "%(levelname)s [%(asctime)s] %(name)s - %(message)s"
datefmt = "%Y-%m-%d %H:%M:%S"
Expand All @@ -60,16 +60,13 @@ def _init_logger() -> None:
)
handler.setFormatter(formatter)

# 设置日志默认值
logging.basicConfig(format=fmt, datefmt=datefmt, level=config.log_level)
# Set log level
logger.setLevel(config.log_level)

# 添加流处理器
# Add handler (don't use basicConfig if you're manually configuring)
logger.addHandler(handler)

# 设置日志级别
logger.setLevel(config.log_level)

# 如果你需要将日志写入文件,可以继续保持原有的文件配置:
# Add file handler if needed
file_handler = logging.FileHandler("default.log")
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
Expand Down
3 changes: 0 additions & 3 deletions models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +0,0 @@
from .user_model import User
from .config_model import Config
from .invite_code_model import InviteCode
69 changes: 62 additions & 7 deletions models/config_model.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import logging
from py_tools.connections.db.mysql import DBManager
from py_tools.connections.db.mysql.orm_model import BaseOrmTableWithTS
from sqlalchemy import Integer, BigInteger
from sqlalchemy import Integer, BigInteger, select
from sqlalchemy.orm import mapped_column, Mapped

from .database import Base, BaseModelWithTS, DbOperations, get_session
from .invite_code_model import InviteCode

logger = logging.getLogger(__name__)


class Config(BaseOrmTableWithTS):
class Config(Base, BaseModelWithTS):
__tablename__ = "config"

total_register_user: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
Expand All @@ -17,8 +18,62 @@ class Config(BaseOrmTableWithTS):
register_public_time: Mapped[int] = mapped_column(BigInteger, nullable=True)


class ConfigOrm(DBManager):
orm_table = Config
class ConfigRepository:
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: Duplicate invite code repository methods in ConfigRepository.

ConfigRepository is intended to manage Config objects only. It contains methods for creating and updating invite codes, which are also implemented in InviteCodeRepository. Consolidating all invite code operations in InviteCodeRepository would reduce duplication and ease maintenance.

"""Replaces ConfigOrm to handle Config database operations"""

@staticmethod
async def create_config(**kwargs):
return await DbOperations.create(Config, **kwargs)

@staticmethod
async def get_by_id(config_id: int):
return await DbOperations.get_by_id(Config, config_id)

@staticmethod
async def get_first_config():
"""Get the first (and typically only) config record"""
async for session in get_session():
result = await session.execute(select(Config).limit(1))
return result.scalars().first()

@staticmethod
async def update_config(config_id: int, **kwargs):
return await DbOperations.update(Config, config_id, **kwargs)

@staticmethod
async def create_invite_code(**kwargs):
return await DbOperations.create(InviteCode, **kwargs)

# @staticmethod
# async def get_by_id(code_id: int):
# return await DbOperations.get_by_id(InviteCode, code_id)

@staticmethod
async def get_by_code(code: str):
async for session in get_session():
result = await session.execute(
select(InviteCode).where(InviteCode.code == code)
)
return result.scalars().first()

@staticmethod
async def get_by_telegram_id(telegram_id: int):
async for session in get_session():
result = await session.execute(
select(InviteCode).where(InviteCode.telegram_id == telegram_id)
)
return result.scalars().all()

@staticmethod
async def update_invite_code(code_id: int, **kwargs):
return await DbOperations.update(InviteCode, code_id, **kwargs)

logger.info("Config model initialized")
@staticmethod
async def mark_as_used(code_id: int, used_time: int, used_user_id: int):
return await DbOperations.update(
InviteCode,
code_id,
is_used=True,
used_time=used_time,
used_user_id=used_user_id,
)
Loading
Loading