мини фиксы
This commit is contained in:
@@ -2,6 +2,7 @@ APP__ORIGINS=["http://locahost:3000"]
|
|||||||
DB__URL=postgres://user:password@localhost:5432/tgex
|
DB__URL=postgres://user:password@localhost:5432/tgex
|
||||||
|
|
||||||
LOGGER__APP_NAME=tgex-backend
|
LOGGER__APP_NAME=tgex-backend
|
||||||
|
LOGGER__APP_VERSION=0.0.0
|
||||||
LOGGER__PRETTY_CONSOLE=true
|
LOGGER__PRETTY_CONSOLE=true
|
||||||
LOGGER__LEVEL=info
|
LOGGER__LEVEL=info
|
||||||
|
|
||||||
|
|||||||
4
Makefile
4
Makefile
@@ -12,7 +12,7 @@ run:
|
|||||||
uv run uvicorn src.main:app
|
uv run uvicorn src.main:app
|
||||||
|
|
||||||
up:
|
up:
|
||||||
GIT_COMMIT=$$(git rev-parse --short HEAD) docker compose up --build -d --force-recreate
|
GIT_COMMIT_COUNT=$$(git rev-list --count HEAD 2>/dev/null) docker compose up --build -d --force-recreate
|
||||||
docker compose logs -f
|
docker compose logs -f
|
||||||
|
|
||||||
migrate-init:
|
migrate-init:
|
||||||
@@ -35,5 +35,3 @@ clean:
|
|||||||
@docker network rm $$(docker network ls -q | grep -vE 'bridge|host|none') 2>/dev/null || true
|
@docker network rm $$(docker network ls -q | grep -vE 'bridge|host|none') 2>/dev/null || true
|
||||||
@docker system prune --volumes -f
|
@docker system prune --volumes -f
|
||||||
@echo "${GREEN}Docker очистка завершена. Базовые образы из registry сохранены.${NC}"
|
@echo "${GREEN}Docker очистка завершена. Базовые образы из registry сохранены.${NC}"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ services:
|
|||||||
DB__URL: "postgres://user:password@postgres:5432/tgex"
|
DB__URL: "postgres://user:password@postgres:5432/tgex"
|
||||||
S3__ENDPOINT_URL: "http://minio:9000"
|
S3__ENDPOINT_URL: "http://minio:9000"
|
||||||
PARSER__URL: "http://tg_parser:8080"
|
PARSER__URL: "http://tg_parser:8080"
|
||||||
|
GIT_COMMIT_COUNT: "${GIT_COMMIT_COUNT:-}"
|
||||||
env_file:
|
env_file:
|
||||||
- .env
|
- .env
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|||||||
@@ -1,30 +1,39 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import subprocess
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import pydantic
|
import pydantic
|
||||||
from pydantic.fields import Field
|
|
||||||
|
|
||||||
from .console_formatter import ColoredConsoleFormatter
|
from .console_formatter import ColoredConsoleFormatter
|
||||||
from .json_formatter import JSONFormatter
|
from .json_formatter import JSONFormatter
|
||||||
|
|
||||||
|
|
||||||
def get_git_commit() -> str:
|
|
||||||
if commit := os.getenv('GIT_COMMIT'):
|
|
||||||
return commit
|
|
||||||
try:
|
|
||||||
return subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD'], text=True).strip()
|
|
||||||
except Exception:
|
|
||||||
return 'unknown'
|
|
||||||
|
|
||||||
|
|
||||||
class LoggerConfig(pydantic.BaseModel):
|
class LoggerConfig(pydantic.BaseModel):
|
||||||
APP_NAME: str
|
APP_NAME: str
|
||||||
APP_VERSION: str = Field(default_factory=get_git_commit)
|
APP_VERSION: str
|
||||||
LEVEL: int = logging.INFO
|
LEVEL: int = logging.INFO
|
||||||
PRETTY_CONSOLE: bool = False
|
PRETTY_CONSOLE: bool = False
|
||||||
|
|
||||||
|
@pydantic.field_validator('APP_VERSION')
|
||||||
|
@classmethod
|
||||||
|
def normalize_app_version(cls, v: str) -> str:
|
||||||
|
parts = v.removeprefix('v').split('.')
|
||||||
|
if len(parts) < 2 or any(not part.isdigit() for part in parts):
|
||||||
|
raise ValueError('APP_VERSION must look like 1.2.0')
|
||||||
|
commit_count = _get_git_commit_count()
|
||||||
|
if not commit_count:
|
||||||
|
return f'v{".".join(parts)}'
|
||||||
|
parts[-1] = commit_count
|
||||||
|
return f'v{".".join(parts)}'
|
||||||
|
|
||||||
|
|
||||||
|
def _get_git_commit_count() -> str | None:
|
||||||
|
commit_count = os.getenv('GIT_COMMIT_COUNT')
|
||||||
|
if commit_count is None:
|
||||||
|
return None
|
||||||
|
normalized = commit_count.strip()
|
||||||
|
return normalized or None
|
||||||
|
|
||||||
|
|
||||||
def init(config: LoggerConfig) -> None:
|
def init(config: LoggerConfig) -> None:
|
||||||
root_logger = logging.getLogger()
|
root_logger = logging.getLogger()
|
||||||
|
|||||||
@@ -56,9 +56,8 @@ fetch_placement_post_worker = FetchPlacementPostWorker(config=WorkerConfig(INTER
|
|||||||
app = FastAPI(
|
app = FastAPI(
|
||||||
lifespan=lifespan,
|
lifespan=lifespan,
|
||||||
version=settings.logger.APP_VERSION,
|
version=settings.logger.APP_VERSION,
|
||||||
swagger_ui_parameters={
|
# Сворачиваем Schemas
|
||||||
'defaultModelsExpandDepth': 0, # Сворачиваем Schemas
|
swagger_ui_parameters={'defaultModelsExpandDepth': 0},
|
||||||
},
|
|
||||||
)
|
)
|
||||||
|
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
|
|||||||
@@ -20,28 +20,11 @@ type exec struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var botUsername string
|
var botUsername string
|
||||||
var botUsernameOnce sync.Once
|
|
||||||
|
|
||||||
func SetBotUsername(username string) { botUsername = username }
|
func SetBotUsername(username string) { botUsername = username }
|
||||||
|
|
||||||
func BotUsername() string { return botUsername }
|
func BotUsername() string { return botUsername }
|
||||||
|
|
||||||
func ensureBotUsername(api echotron.API) {
|
|
||||||
if botUsername != "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
botUsernameOnce.Do(func() {
|
|
||||||
me, err := api.GetMe()
|
|
||||||
if err != nil {
|
|
||||||
log.Error().Err(err).Msg("GetMe failed, bot username will be empty")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if me.Result != nil {
|
|
||||||
botUsername = me.Result.Username
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type lastRender struct {
|
type lastRender struct {
|
||||||
textHash string
|
textHash string
|
||||||
kbHash string
|
kbHash string
|
||||||
@@ -77,7 +60,6 @@ func NewBot(chatID int64, token string, commandRouter func(string) State, backen
|
|||||||
}
|
}
|
||||||
|
|
||||||
api := echotron.NewAPI(token)
|
api := echotron.NewAPI(token)
|
||||||
ensureBotUsername(api)
|
|
||||||
|
|
||||||
return &Bot{
|
return &Bot{
|
||||||
ChatID: chatID,
|
ChatID: chatID,
|
||||||
|
|||||||
@@ -34,6 +34,13 @@ func main() {
|
|||||||
|
|
||||||
// Регистрируем команды бота в Telegram
|
// Регистрируем команды бота в Telegram
|
||||||
api := echotron.NewAPI(botToken)
|
api := echotron.NewAPI(botToken)
|
||||||
|
if me, err := api.GetMe(); err != nil {
|
||||||
|
log.Error().Err(err).Msg("Failed to get bot profile")
|
||||||
|
} else if me.Result != nil {
|
||||||
|
bot.SetBotUsername(me.Result.Username)
|
||||||
|
log.Info().Str("username", me.Result.Username).Msg("Telegram bot authorized")
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := api.SetMyCommands(nil, botCommands...); err != nil {
|
if _, err := api.SetMyCommands(nil, botCommands...); err != nil {
|
||||||
log.Error().Err(err).Msg("Failed to set bot commands")
|
log.Error().Err(err).Msg("Failed to set bot commands")
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -18,18 +18,18 @@ type ChannelInput struct {
|
|||||||
|
|
||||||
// Парсеры для разных форматов
|
// Парсеры для разных форматов
|
||||||
var (
|
var (
|
||||||
// Username без @: 5-32 символа, начинается с буквы
|
// Username без @: 4-32 символа, начинается с буквы
|
||||||
channelUsernameRe = regexp.MustCompile(`^[A-Za-z][A-Za-z0-9_]{4,31}$`)
|
channelUsernameRe = regexp.MustCompile(`^[A-Za-z][A-Za-z0-9_]{3,31}$`)
|
||||||
|
|
||||||
// Invite link: t.me/+xxxxx или t.me/joinchat/xxxxx
|
// Invite link: t.me/+xxxxx или t.me/joinchat/xxxxx
|
||||||
inviteLinkRe = regexp.MustCompile(`^t\.me/\+[a-zA-Z0-9_-]+$`)
|
inviteLinkRe = regexp.MustCompile(`^t\.me/\+[a-zA-Z0-9_-]+$`)
|
||||||
inviteLinkJoinRe = regexp.MustCompile(`^t\.me/joinchat/[a-zA-Z0-9_-]+$`)
|
inviteLinkJoinRe = regexp.MustCompile(`^t\.me/joinchat/[a-zA-Z0-9_-]+$`)
|
||||||
|
|
||||||
// TGStat: tgstat.ru/channel_name или tgstat.ru/channel/@username
|
// TGStat: tgstat.ru/channel_name или tgstat.ru/channel/@username
|
||||||
tgstatRe = regexp.MustCompile(`^(?:https?://)?(?:www\.)?tgstat\.ru/(?:channel/)?@?([A-Za-z][A-Za-z0-9_.]{4,31})`)
|
tgstatRe = regexp.MustCompile(`^(?:https?://)?(?:www\.)?tgstat\.ru/(?:channel/)?@?([A-Za-z][A-Za-z0-9_.]{3,31})`)
|
||||||
|
|
||||||
// Telemetr: telemetr.io/channel/username или telemetr.io/channel/@username
|
// Telemetr: telemetr.io/channel/username или telemetr.io/channel/@username
|
||||||
telemetrRe = regexp.MustCompile(`^(?:https?://)?(?:www\.)?telemetr\.io/(?:channel|channels)/@?([A-Za-z][A-Za-z0-9_.]{4,31})`)
|
telemetrRe = regexp.MustCompile(`^(?:https?://)?(?:www\.)?telemetr\.io/(?:channel|channels)/@?([A-Za-z][A-Za-z0-9_.]{3,31})`)
|
||||||
)
|
)
|
||||||
|
|
||||||
// ParseChannelInput парсит ввод пользователя и определяет тип канала
|
// ParseChannelInput парсит ввод пользователя и определяет тип канала
|
||||||
|
|||||||
Reference in New Issue
Block a user