- All frontend pages, labels, notices and errors; html lang=uk, uk-UA money format - Brand "Assistant System" in the top bar and page title - Backend error details returned to the UI (auth, orders, receipts, cash registers, Checkbox/CRM/NP errors) and CLI output - Tests updated for the new messages Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
73 lines
2.3 KiB
Python
73 lines
2.3 KiB
Python
"""Служебные команды.
|
|
|
|
python -m app.cli bootstrap # создать первого администратора
|
|
python -m app.cli gen-keys # сгенерировать SECRET_KEY и ENCRYPTION_KEY
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import asyncio
|
|
import secrets
|
|
import sys
|
|
|
|
from sqlalchemy import select
|
|
|
|
from app.core.config import settings
|
|
from app.core.security import hash_password
|
|
from app.db.models.user import User, UserRole
|
|
from app.db.session import session_scope
|
|
|
|
|
|
async def bootstrap() -> int:
|
|
"""Создаёт первого администратора из переменных окружения.
|
|
|
|
Идемпотентна: повторный запуск ничего не меняет и пароль не сбрасывает.
|
|
"""
|
|
if not settings.first_admin_password:
|
|
print("FIRST_ADMIN_PASSWORD не задано в .env", file=sys.stderr)
|
|
return 1
|
|
|
|
email = settings.first_admin_email.strip().lower()
|
|
|
|
async with session_scope() as session:
|
|
existing = await session.scalar(select(User).where(User.email == email))
|
|
if existing is not None:
|
|
print(f"Користувач {email} уже існує — нічого не змінено.")
|
|
return 0
|
|
|
|
session.add(
|
|
User(
|
|
email=email,
|
|
full_name=settings.first_admin_name,
|
|
password_hash=hash_password(settings.first_admin_password),
|
|
role=UserRole.ADMIN,
|
|
)
|
|
)
|
|
|
|
print(f"Адміністратора {email} створено.")
|
|
print("Змініть пароль після першого входу та приберіть FIRST_ADMIN_PASSWORD з .env.")
|
|
return 0
|
|
|
|
|
|
def gen_keys() -> int:
|
|
from cryptography.fernet import Fernet
|
|
|
|
print(f"SECRET_KEY={secrets.token_urlsafe(64)}")
|
|
print(f"ENCRYPTION_KEY={Fernet.generate_key().decode()}")
|
|
return 0
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(prog="app.cli", description="Службові команди lux_fiscal")
|
|
parser.add_argument("command", choices=["bootstrap", "gen-keys"])
|
|
args = parser.parse_args()
|
|
|
|
if args.command == "bootstrap":
|
|
return asyncio.run(bootstrap())
|
|
return gen_keys()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|