diff --git a/app/database/crud/web_api_token.py b/app/database/crud/web_api_token.py
index e8c3c71f..c84b9426 100644
--- a/app/database/crud/web_api_token.py
+++ b/app/database/crud/web_api_token.py
@@ -6,7 +6,6 @@ from typing import Iterable, List, Optional
from sqlalchemy import select, update
from sqlalchemy.ext.asyncio import AsyncSession
-from sqlalchemy.orm import selectinload
from app.database.models import WebApiToken
@@ -15,12 +14,8 @@ async def list_tokens(
db: AsyncSession,
*,
include_inactive: bool = False,
- user_id: Optional[int] = None,
) -> List[WebApiToken]:
- query = select(WebApiToken).options(selectinload(WebApiToken.user))
-
- if user_id is not None:
- query = query.where(WebApiToken.user_id == user_id)
+ query = select(WebApiToken)
if not include_inactive:
query = query.where(WebApiToken.is_active.is_(True))
@@ -32,20 +27,12 @@ async def list_tokens(
async def get_token_by_id(db: AsyncSession, token_id: int) -> Optional[WebApiToken]:
- query = (
- select(WebApiToken)
- .options(selectinload(WebApiToken.user))
- .where(WebApiToken.id == token_id)
- )
- result = await db.execute(query)
- return result.scalar_one_or_none()
+ return await db.get(WebApiToken, token_id)
async def get_token_by_hash(db: AsyncSession, token_hash: str) -> Optional[WebApiToken]:
- query = (
- select(WebApiToken)
- .options(selectinload(WebApiToken.user))
- .where(WebApiToken.token_hash == token_hash)
+ query = select(WebApiToken).where(
+ WebApiToken.token_hash == token_hash
)
result = await db.execute(query)
return result.scalar_one_or_none()
@@ -60,10 +47,8 @@ async def create_token(
description: Optional[str] = None,
expires_at: Optional[datetime] = None,
created_by: Optional[str] = None,
- user_id: Optional[int] = None,
) -> WebApiToken:
token = WebApiToken(
- user_id=user_id,
name=name,
token_hash=token_hash,
token_prefix=token_prefix,
diff --git a/app/database/models.py b/app/database/models.py
index 8ac07338..1780d75a 100644
--- a/app/database/models.py
+++ b/app/database/models.py
@@ -387,7 +387,6 @@ class User(Base):
referral_earnings = relationship("ReferralEarning", foreign_keys="ReferralEarning.user_id", back_populates="user")
discount_offers = relationship("DiscountOffer", back_populates="user")
promo_offer_logs = relationship("PromoOfferLog", back_populates="user")
- web_api_tokens = relationship("WebApiToken", back_populates="user")
lifetime_used_traffic_bytes = Column(BigInteger, default=0)
auto_promo_group_assigned = Column(Boolean, nullable=False, default=False)
auto_promo_group_threshold_kopeks = Column(BigInteger, nullable=False, default=0)
@@ -1239,7 +1238,6 @@ class WebApiToken(Base):
__tablename__ = "web_api_tokens"
id = Column(Integer, primary_key=True, index=True)
- user_id = Column(Integer, ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True)
name = Column(String(255), nullable=False)
token_hash = Column(String(128), nullable=False, unique=True, index=True)
token_prefix = Column(String(32), nullable=False, index=True)
@@ -1251,7 +1249,6 @@ class WebApiToken(Base):
last_used_ip = Column(String(64), nullable=True)
is_active = Column(Boolean, default=True, nullable=False)
created_by = Column(String(255), nullable=True)
- user = relationship("User", back_populates="web_api_tokens")
def __repr__(self) -> str:
status = "active" if self.is_active else "revoked"
diff --git a/app/database/universal_migration.py b/app/database/universal_migration.py
index f6e112d0..90d637ad 100644
--- a/app/database/universal_migration.py
+++ b/app/database/universal_migration.py
@@ -2434,7 +2434,6 @@ async def create_web_api_tokens_table() -> bool:
create_sql = """
CREATE TABLE web_api_tokens (
id INTEGER PRIMARY KEY AUTOINCREMENT,
- user_id INTEGER NULL,
name VARCHAR(255) NOT NULL,
token_hash VARCHAR(128) NOT NULL UNIQUE,
token_prefix VARCHAR(32) NOT NULL,
@@ -2445,19 +2444,16 @@ async def create_web_api_tokens_table() -> bool:
last_used_at DATETIME NULL,
last_used_ip VARCHAR(64) NULL,
is_active BOOLEAN NOT NULL DEFAULT 1,
- created_by VARCHAR(255) NULL,
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
+ created_by VARCHAR(255) NULL
);
CREATE INDEX idx_web_api_tokens_active ON web_api_tokens(is_active);
CREATE INDEX idx_web_api_tokens_prefix ON web_api_tokens(token_prefix);
CREATE INDEX idx_web_api_tokens_last_used ON web_api_tokens(last_used_at);
- CREATE INDEX ix_web_api_tokens_user_id ON web_api_tokens(user_id);
"""
elif db_type == "postgresql":
create_sql = """
CREATE TABLE web_api_tokens (
id SERIAL PRIMARY KEY,
- user_id INTEGER NULL,
name VARCHAR(255) NOT NULL,
token_hash VARCHAR(128) NOT NULL UNIQUE,
token_prefix VARCHAR(32) NOT NULL,
@@ -2468,19 +2464,16 @@ async def create_web_api_tokens_table() -> bool:
last_used_at TIMESTAMP NULL,
last_used_ip VARCHAR(64) NULL,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
- created_by VARCHAR(255) NULL,
- CONSTRAINT fk_web_api_tokens_user_id FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
+ created_by VARCHAR(255) NULL
);
CREATE INDEX idx_web_api_tokens_active ON web_api_tokens(is_active);
CREATE INDEX idx_web_api_tokens_prefix ON web_api_tokens(token_prefix);
CREATE INDEX idx_web_api_tokens_last_used ON web_api_tokens(last_used_at);
- CREATE INDEX ix_web_api_tokens_user_id ON web_api_tokens(user_id);
"""
else:
create_sql = """
CREATE TABLE web_api_tokens (
id INT AUTO_INCREMENT PRIMARY KEY,
- user_id INT NULL,
name VARCHAR(255) NOT NULL,
token_hash VARCHAR(128) NOT NULL UNIQUE,
token_prefix VARCHAR(32) NOT NULL,
@@ -2491,8 +2484,7 @@ async def create_web_api_tokens_table() -> bool:
last_used_at TIMESTAMP NULL,
last_used_ip VARCHAR(64) NULL,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
- created_by VARCHAR(255) NULL,
- CONSTRAINT fk_web_api_tokens_user_id FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL
+ created_by VARCHAR(255) NULL
) ENGINE=InnoDB;
CREATE INDEX idx_web_api_tokens_active ON web_api_tokens(is_active);
CREATE INDEX idx_web_api_tokens_prefix ON web_api_tokens(token_prefix);
@@ -2508,71 +2500,6 @@ async def create_web_api_tokens_table() -> bool:
return False
-async def ensure_web_api_tokens_user_column() -> bool:
- column_exists = await check_column_exists("web_api_tokens", "user_id")
- index_name = "ix_web_api_tokens_user_id"
- constraint_name = "fk_web_api_tokens_user_id"
-
- try:
- async with engine.begin() as conn:
- db_type = await get_database_type()
-
- if not column_exists:
- if db_type == "sqlite":
- await conn.execute(text("ALTER TABLE web_api_tokens ADD COLUMN user_id INTEGER"))
- elif db_type == "postgresql":
- await conn.execute(text("ALTER TABLE web_api_tokens ADD COLUMN user_id INTEGER"))
- elif db_type == "mysql":
- await conn.execute(text("ALTER TABLE web_api_tokens ADD COLUMN user_id INT NULL"))
- else:
- logger.error(f"Неподдерживаемый тип БД для web_api_tokens.user_id: {db_type}")
- return False
- logger.info("Добавлена колонка web_api_tokens.user_id")
-
- if db_type in {"postgresql", "mysql"}:
- constraint_exists = await check_constraint_exists("web_api_tokens", constraint_name)
- if not constraint_exists:
- try:
- await conn.execute(
- text(
- "ALTER TABLE web_api_tokens "
- "ADD CONSTRAINT fk_web_api_tokens_user_id "
- "FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL"
- )
- )
- logger.info("Добавлено ограничение fk_web_api_tokens_user_id")
- except Exception as constraint_error:
- logger.warning(
- "Не удалось создать ограничение fk_web_api_tokens_user_id: %s",
- constraint_error,
- )
-
- index_exists = await check_index_exists("web_api_tokens", index_name)
- if not index_exists:
- try:
- async with engine.begin() as conn:
- db_type = await get_database_type()
- if db_type == "sqlite":
- await conn.execute(
- text("CREATE INDEX IF NOT EXISTS ix_web_api_tokens_user_id ON web_api_tokens(user_id)")
- )
- elif db_type == "postgresql":
- await conn.execute(
- text("CREATE INDEX IF NOT EXISTS ix_web_api_tokens_user_id ON web_api_tokens(user_id)")
- )
- elif db_type == "mysql":
- await conn.execute(text("CREATE INDEX ix_web_api_tokens_user_id ON web_api_tokens(user_id)"))
- logger.info("Создан индекс ix_web_api_tokens_user_id")
- except Exception as index_error:
- logger.warning(f"Не удалось создать индекс ix_web_api_tokens_user_id: {index_error}")
-
- return True
-
- except Exception as error:
- logger.error(f"Ошибка обновления web_api_tokens.user_id: {error}")
- return False
-
-
async def create_privacy_policies_table() -> bool:
table_exists = await check_table_exists("privacy_policies")
if table_exists:
@@ -2872,7 +2799,6 @@ async def run_universal_migration():
web_api_tokens_ready = await create_web_api_tokens_table()
if web_api_tokens_ready:
logger.info("✅ Таблица web_api_tokens готова")
- await ensure_web_api_tokens_user_column()
else:
logger.warning("⚠️ Проблемы с таблицей web_api_tokens")
diff --git a/app/handlers/menu.py b/app/handlers/menu.py
index 10a2e6b1..479d5dfb 100644
--- a/app/handlers/menu.py
+++ b/app/handlers/menu.py
@@ -12,12 +12,10 @@ from app.keyboards.inline import (
get_main_menu_keyboard,
get_language_selection_keyboard,
get_info_menu_keyboard,
- get_api_access_keyboard,
)
from app.localization.texts import get_texts, get_rules
-from app.database.models import User, WebApiToken
+from app.database.models import User
from app.database.crud.user_message import get_random_active_message
-from app.database.crud import web_api_token as web_api_token_crud
from app.services.subscription_checkout_service import (
has_subscription_checkout_draft,
should_offer_checkout_resume,
@@ -31,8 +29,6 @@ from app.utils.promo_offer import (
from app.services.privacy_policy_service import PrivacyPolicyService
from app.services.public_offer_service import PublicOfferService
from app.services.faq_service import FaqService
-from app.services.web_api_token_service import web_api_token_service
-from app.utils.formatters import format_datetime
logger = logging.getLogger(__name__)
@@ -136,150 +132,6 @@ async def show_info_menu(
await callback.answer()
-def _build_api_access_caption(
- texts,
- token: WebApiToken | None,
- *,
- has_active_token: bool,
-) -> str:
- header = texts.t("API_ACCESS_HEADER", "🔑 API доступ")
-
- parts: list[str] = [header]
-
- if not token:
- parts.append(
- texts.t(
- "API_ACCESS_NO_TOKEN",
- (
- "У вас еще нет активного API ключа.\n"
- "Нажмите кнопку ниже, чтобы выпустить первый ключ."
- ),
- )
- )
- else:
- created_at = format_datetime(token.created_at) if token.created_at else "—"
- last_used = (
- format_datetime(token.last_used_at)
- if token.last_used_at
- else texts.t("API_ACCESS_LAST_USED_NEVER", "еще не использовался")
- )
- last_ip_line = ""
- if token.last_used_ip:
- last_ip_line = "\n" + texts.t(
- "API_ACCESS_LAST_IP_LINE",
- "Последний IP: {ip}",
- ).format(ip=token.last_used_ip)
-
- template_key = "API_ACCESS_ACTIVE_TOKEN" if has_active_token else "API_ACCESS_INACTIVE_TOKEN"
- parts.append(
- texts.t(
- template_key,
- (
- "Ваш текущий API ключ выпущен {created_at}.\n"
- "Первые символы: {prefix}.\n"
- "Последнее использование: {last_used}.{last_ip_line}\n"
- "Чтобы выпустить новый ключ, нажмите кнопку ниже — предыдущий будет отключен."
- )
- if has_active_token
- else (
- "Последний ключ выпущен {created_at}.\n"
- "Первые символы: {prefix}.\n"
- "Последнее использование: {last_used}.{last_ip_line}\n"
- "Чтобы получить доступ, создайте новый ключ."
- ),
- ).format(
- created_at=created_at,
- prefix=token.token_prefix,
- last_used=last_used,
- last_ip_line=last_ip_line,
- )
- )
-
- parts.append(
- texts.t(
- "API_ACCESS_SECURITY_HINT",
- "Берегите ключ и не передавайте его другим людям.",
- )
- )
-
- return "\n\n".join(part for part in parts if part)
-
-
-async def show_api_access(
- callback: types.CallbackQuery,
- db_user: User,
- db: AsyncSession,
- *,
- skip_callback_answer: bool = False,
-) -> None:
- texts = get_texts(db_user.language)
- tokens = await web_api_token_crud.list_tokens(
- db,
- include_inactive=True,
- user_id=db_user.id,
- )
-
- active_token = next((token for token in tokens if token.is_active), None)
- latest_token = tokens[0] if tokens else None
- token_to_show = active_token or latest_token
-
- caption = _build_api_access_caption(
- texts,
- token_to_show,
- has_active_token=bool(active_token),
- )
-
- await edit_or_answer_photo(
- callback=callback,
- caption=caption,
- keyboard=get_api_access_keyboard(db_user.language),
- parse_mode="HTML",
- )
-
- if not skip_callback_answer:
- await callback.answer()
-
-
-async def generate_api_access_token(
- callback: types.CallbackQuery,
- db_user: User,
- db: AsyncSession,
-) -> None:
- texts = get_texts(db_user.language)
-
- token_value, token = await web_api_token_service.issue_user_token(db, db_user)
- await db.commit()
-
- logger.info(
- "🔑 Пользователь %s выпустил новый API ключ (token_id=%s)",
- db_user.telegram_id,
- token.id,
- )
-
- message_text = texts.t(
- "API_ACCESS_NEW_TOKEN",
- (
- "Ваш новый API ключ:\n"
- "{token}\n\n"
- "Сохраните его — повторно показать не получится."
- ),
- ).format(token=token_value)
-
- await callback.message.answer(message_text, parse_mode="HTML")
-
- await show_api_access(
- callback,
- db_user,
- db,
- skip_callback_answer=True,
- )
-
- await callback.answer(
- texts.t("API_ACCESS_GENERATED_ALERT", "Новый API ключ создан"),
- show_alert=True,
- )
-
-
async def show_faq_pages(
callback: types.CallbackQuery,
db_user: User,
@@ -930,16 +782,6 @@ def register_handlers(dp: Dispatcher):
F.data == "menu_info",
)
- dp.callback_query.register(
- show_api_access,
- F.data == "menu_api_access",
- )
-
- dp.callback_query.register(
- generate_api_access_token,
- F.data == "api_access_generate",
- )
-
dp.callback_query.register(
show_faq_pages,
F.data == "menu_faq",
diff --git a/app/keyboards/inline.py b/app/keyboards/inline.py
index 847c2ff5..0d86e119 100644
--- a/app/keyboards/inline.py
+++ b/app/keyboards/inline.py
@@ -314,13 +314,6 @@ def get_info_menu_keyboard(
)
])
- buttons.append([
- InlineKeyboardButton(
- text=texts.t("MENU_API_ACCESS", "🔑 API доступ"),
- callback_data="menu_api_access",
- )
- ])
-
buttons.append([
InlineKeyboardButton(text=texts.MENU_RULES, callback_data="menu_rules")
])
@@ -354,22 +347,6 @@ def get_info_menu_keyboard(
return InlineKeyboardMarkup(inline_keyboard=buttons)
-def get_api_access_keyboard(language: str = DEFAULT_LANGUAGE) -> InlineKeyboardMarkup:
- texts = get_texts(language)
-
- buttons = [
- [
- InlineKeyboardButton(
- text=texts.t("API_ACCESS_GENERATE_BUTTON", "🔄 Выпустить ключ"),
- callback_data="api_access_generate",
- )
- ],
- [InlineKeyboardButton(text=texts.BACK, callback_data="menu_info")],
- ]
-
- return InlineKeyboardMarkup(inline_keyboard=buttons)
-
-
def get_happ_download_button_row(texts) -> Optional[List[InlineKeyboardButton]]:
if not settings.is_happ_download_button_enabled():
return None
diff --git a/app/localization/locales/en.json b/app/localization/locales/en.json
index 767340fb..ffa15770 100644
--- a/app/localization/locales/en.json
+++ b/app/localization/locales/en.json
@@ -305,7 +305,6 @@
"MENU_INFO": "ℹ️ Info",
"MENU_INFO_HEADER": "ℹ️ Info",
"MENU_INFO_PROMPT": "Choose a section:",
- "MENU_API_ACCESS": "🔑 API key",
"MENU_PROMOCODE": "🎫 Promo code",
"MENU_REFERRALS": "🤝 Referral program",
"MENU_RULES": "📋 Service rules",
@@ -760,13 +759,3 @@
"ADMIN_PUBLIC_OFFER_RETURN_TO_EDIT": "⬅️ Back to editing",
"ADMIN_PUBLIC_OFFER_EDIT_CANCELLED": "Offer editing cancelled."
}
- "API_ACCESS_HEADER": "🔑 API access",
- "API_ACCESS_NO_TOKEN": "You don't have an active API key yet.\nTap the button below to issue your first key.\n\nEach new key disables the previous one.",
- "API_ACCESS_LAST_USED_NEVER": "not used yet",
- "API_ACCESS_LAST_IP_LINE": "Last IP: {ip}",
- "API_ACCESS_ACTIVE_TOKEN": "Your current API key was issued on {created_at}.\nFirst characters: {prefix}.\nLast used: {last_used}.{last_ip_line}\nTap the button below to issue a new key — the previous one will be revoked.",
- "API_ACCESS_INACTIVE_TOKEN": "The last key was issued on {created_at}.\nFirst characters: {prefix}.\nLast used: {last_used}.{last_ip_line}\nIssue a new key to regain access — old keys no longer work.",
- "API_ACCESS_SECURITY_HINT": "Store the key securely and never share it with anyone.",
- "API_ACCESS_GENERATE_BUTTON": "🔄 Issue key",
- "API_ACCESS_NEW_TOKEN": "Here is your new API key:\n{token}\n\nSave it now — it cannot be shown again. Previous keys have been revoked.",
- "API_ACCESS_GENERATED_ALERT": "New API key issued",
diff --git a/app/localization/locales/ru.json b/app/localization/locales/ru.json
index 9de7e32e..06d7a051 100644
--- a/app/localization/locales/ru.json
+++ b/app/localization/locales/ru.json
@@ -236,17 +236,6 @@
"MENU_INFO_PROMPT": "Выберите раздел:",
"MENU_PRIVACY_POLICY": "🛡️ Политика конф.",
"MENU_PUBLIC_OFFER": "📄 Оферта",
- "MENU_API_ACCESS": "🔑 API доступ",
- "API_ACCESS_HEADER": "🔑 API доступ",
- "API_ACCESS_NO_TOKEN": "У вас ещё нет активного API ключа.\nНажмите кнопку ниже, чтобы выпустить первый ключ.\n\nКаждый новый ключ отключает предыдущие.",
- "API_ACCESS_LAST_USED_NEVER": "ещё не использовался",
- "API_ACCESS_LAST_IP_LINE": "Последний IP: {ip}",
- "API_ACCESS_ACTIVE_TOKEN": "Ваш текущий API ключ выпущен {created_at}.\nПервые символы: {prefix}.\nПоследнее использование: {last_used}.{last_ip_line}\nЧтобы выпустить новый ключ, нажмите кнопку ниже — предыдущий будет отключен.",
- "API_ACCESS_INACTIVE_TOKEN": "Последний ключ выпущен {created_at}.\nПервые символы: {prefix}.\nПоследнее использование: {last_used}.{last_ip_line}\nЧтобы получить доступ, создайте новый ключ — старые больше не работают.",
- "API_ACCESS_SECURITY_HINT": "Храните ключ в безопасном месте и не передавайте его другим людям.",
- "API_ACCESS_GENERATE_BUTTON": "🔄 Выпустить ключ",
- "API_ACCESS_NEW_TOKEN": "Вот ваш новый API ключ:\n{token}\n\nСохраните его сейчас — повторно показать не получится. Предыдущие ключи отключены.",
- "API_ACCESS_GENERATED_ALERT": "Новый API ключ создан",
"PRIVACY_POLICY_HEADER": "🛡️ Политика конфиденциальности",
"PRIVACY_POLICY_NOT_AVAILABLE": "Политика конфиденциальности временно недоступна.",
"PRIVACY_POLICY_EMPTY_ALERT": "Политика конфиденциальности ещё не заполнена.",
diff --git a/app/services/web_api_token_service.py b/app/services/web_api_token_service.py
index d08b6dcf..6beb06fd 100644
--- a/app/services/web_api_token_service.py
+++ b/app/services/web_api_token_service.py
@@ -8,7 +8,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from app.config import settings
from app.database.crud import web_api_token as crud
-from app.database.models import User, WebApiToken
+from app.database.models import WebApiToken
from app.database.universal_migration import ensure_default_web_api_token
from app.utils.security import generate_api_token, hash_api_token
@@ -66,14 +66,10 @@ class WebApiTokenService:
expires_at: Optional[datetime] = None,
created_by: Optional[str] = None,
token_value: Optional[str] = None,
- user: Optional[User] = None,
- user_id: Optional[int] = None,
) -> Tuple[str, WebApiToken]:
plain_token = token_value or generate_api_token()
token_hash = self.hash_token(plain_token)
- resolved_user_id = user.id if user else user_id
-
token = await crud.create_token(
db,
name=name,
@@ -82,43 +78,6 @@ class WebApiTokenService:
description=description,
expires_at=expires_at,
created_by=created_by,
- user_id=resolved_user_id,
- )
-
- return plain_token, token
-
- async def issue_user_token(
- self,
- db: AsyncSession,
- user: User,
- ) -> Tuple[str, WebApiToken]:
- existing_tokens = await crud.list_tokens(
- db,
- include_inactive=True,
- user_id=user.id,
- )
-
- now = datetime.utcnow()
- has_updates = False
- for token in existing_tokens:
- if token.is_active:
- token.is_active = False
- token.updated_at = now
- has_updates = True
-
- if has_updates:
- await db.flush()
-
- token_name = f"User {user.telegram_id}"
- description = "Generated via Telegram bot"
- created_by = f"telegram:{user.telegram_id}"
-
- plain_token, token = await self.create_token(
- db,
- name=token_name,
- description=description,
- created_by=created_by,
- user=user,
)
return plain_token, token
diff --git a/app/webapi/routes/tokens.py b/app/webapi/routes/tokens.py
index 55908be4..cef1fd91 100644
--- a/app/webapi/routes/tokens.py
+++ b/app/webapi/routes/tokens.py
@@ -1,11 +1,8 @@
from __future__ import annotations
-from typing import Optional
-
-from fastapi import APIRouter, Depends, HTTPException, Query, Response, Security, status
+from fastapi import APIRouter, Depends, HTTPException, Response, Security, status
from sqlalchemy.ext.asyncio import AsyncSession
-from app.database.crud.user import get_user_by_id
from app.database.crud.web_api_token import (
delete_token,
get_token_by_id,
@@ -33,8 +30,6 @@ def _serialize(token: WebApiToken) -> TokenResponse:
last_used_at=token.last_used_at,
last_used_ip=token.last_used_ip,
created_by=token.created_by,
- user_id=token.user_id,
- user_telegram_id=getattr(token.user, "telegram_id", None),
)
@@ -42,13 +37,8 @@ def _serialize(token: WebApiToken) -> TokenResponse:
async def get_tokens(
_: WebApiToken = Security(require_api_token),
db: AsyncSession = Depends(get_db_session),
- user_id: Optional[int] = Query(default=None, description="Фильтр по ID пользователя"),
) -> list[TokenResponse]:
- tokens = await list_tokens(
- db,
- include_inactive=True,
- user_id=user_id,
- )
+ tokens = await list_tokens(db, include_inactive=True)
return [_serialize(token) for token in tokens]
@@ -58,19 +48,12 @@ async def create_token(
actor: WebApiToken = Security(require_api_token),
db: AsyncSession = Depends(get_db_session),
) -> TokenCreateResponse:
- target_user = None
- if payload.user_id is not None:
- target_user = await get_user_by_id(db, payload.user_id)
- if not target_user:
- raise HTTPException(status.HTTP_404_NOT_FOUND, "User not found")
-
token_value, token = await web_api_token_service.create_token(
db,
name=payload.name.strip(),
description=payload.description,
expires_at=payload.expires_at,
created_by=actor.name,
- user=target_user,
)
await db.commit()
diff --git a/app/webapi/schemas/tokens.py b/app/webapi/schemas/tokens.py
index 0019a29f..d923ab65 100644
--- a/app/webapi/schemas/tokens.py
+++ b/app/webapi/schemas/tokens.py
@@ -18,24 +18,12 @@ class TokenResponse(BaseModel):
last_used_at: Optional[datetime] = None
last_used_ip: Optional[str] = None
created_by: Optional[str] = None
- user_id: Optional[int] = Field(
- default=None,
- description="ID пользователя, которому принадлежит токен",
- )
- user_telegram_id: Optional[int] = Field(
- default=None,
- description="Telegram ID владельца токена",
- )
class TokenCreateRequest(BaseModel):
name: str
description: Optional[str] = None
expires_at: Optional[datetime] = None
- user_id: Optional[int] = Field(
- default=None,
- description="ID пользователя, для которого выпускается токен",
- )
class TokenCreateResponse(TokenResponse):
diff --git a/migrations/alembic/versions/d2a9443a9f47_add_user_link_to_web_api_tokens.py b/migrations/alembic/versions/d2a9443a9f47_add_user_link_to_web_api_tokens.py
deleted file mode 100644
index 90cef86d..00000000
--- a/migrations/alembic/versions/d2a9443a9f47_add_user_link_to_web_api_tokens.py
+++ /dev/null
@@ -1,72 +0,0 @@
-"""add user link to web api tokens"""
-
-from typing import Sequence, Union
-
-from alembic import op
-import sqlalchemy as sa
-from sqlalchemy.engine import reflection
-
-revision: str = "d2a9443a9f47"
-down_revision: Union[str, None] = "8fd1e338eb45"
-branch_labels: Union[str, Sequence[str], None] = None
-depends_on: Union[str, Sequence[str], None] = None
-
-TABLE_NAME = "web_api_tokens"
-COLUMN_NAME = "user_id"
-FK_NAME = "fk_web_api_tokens_user_id"
-INDEX_NAME = "ix_web_api_tokens_user_id"
-
-
-def _column_exists(inspector: reflection.Inspector) -> bool:
- return COLUMN_NAME in [column["name"] for column in inspector.get_columns(TABLE_NAME)]
-
-
-def _fk_exists(inspector: reflection.Inspector) -> bool:
- return any(fk.get("name") == FK_NAME for fk in inspector.get_foreign_keys(TABLE_NAME))
-
-
-def _index_exists(inspector: reflection.Inspector) -> bool:
- return any(index.get("name") == INDEX_NAME for index in inspector.get_indexes(TABLE_NAME))
-
-
-def upgrade() -> None:
- bind = op.get_bind()
- inspector = reflection.Inspector.from_engine(bind)
-
- if not _column_exists(inspector):
- op.add_column(TABLE_NAME, sa.Column(COLUMN_NAME, sa.Integer(), nullable=True))
- inspector = reflection.Inspector.from_engine(bind)
-
- if bind.dialect.name in {"postgresql", "mysql"} and not _fk_exists(inspector):
- try:
- op.create_foreign_key(
- FK_NAME,
- TABLE_NAME,
- "users",
- [COLUMN_NAME],
- ["id"],
- ondelete="SET NULL",
- )
- except Exception:
- # Constraint creation can fail if duplicates exist; skip to keep migration resilient.
- pass
-
- inspector = reflection.Inspector.from_engine(bind)
- if not _index_exists(inspector):
- op.create_index(INDEX_NAME, TABLE_NAME, [COLUMN_NAME])
-
-
-def downgrade() -> None:
- bind = op.get_bind()
- inspector = reflection.Inspector.from_engine(bind)
-
- if _index_exists(inspector):
- op.drop_index(INDEX_NAME, table_name=TABLE_NAME)
-
- inspector = reflection.Inspector.from_engine(bind)
- if _fk_exists(inspector):
- op.drop_constraint(FK_NAME, TABLE_NAME, type_="foreignkey")
-
- inspector = reflection.Inspector.from_engine(bind)
- if _column_exists(inspector):
- op.drop_column(TABLE_NAME, COLUMN_NAME)