Update admin theme/layout and refresh README details.
Align the project baseline with the latest admin interface styling and layout structure while documenting setup and usage updates in README.
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
from app.db.base import Base
|
||||
|
||||
__all__ = ["Base"]
|
||||
@@ -0,0 +1,5 @@
|
||||
from sqlalchemy.orm import DeclarativeBase
|
||||
|
||||
|
||||
class Base(DeclarativeBase):
|
||||
pass
|
||||
@@ -0,0 +1,12 @@
|
||||
from app.modules.auth.models import EmailVerificationToken, PasswordResetToken, RefreshToken
|
||||
from app.modules.content.models import ContentPage
|
||||
from app.modules.users.models import User, UserProfile
|
||||
|
||||
__all__ = [
|
||||
"User",
|
||||
"UserProfile",
|
||||
"RefreshToken",
|
||||
"PasswordResetToken",
|
||||
"EmailVerificationToken",
|
||||
"ContentPage",
|
||||
]
|
||||
@@ -0,0 +1,98 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from app.core.config import settings
|
||||
from app.core.security import hash_password
|
||||
from app.modules.content.repository import create_page, get_page_by_slug
|
||||
from app.modules.users.repository import create_user, get_user_by_email
|
||||
|
||||
|
||||
def _ensure_user(email: str, password: str, role: str, is_superuser: bool, status: str):
|
||||
from app.modules.users import repository
|
||||
|
||||
user = get_user_by_email(email)
|
||||
if user:
|
||||
changed = False
|
||||
if user.role != role:
|
||||
user.role = role
|
||||
changed = True
|
||||
if user.is_superuser != is_superuser:
|
||||
user.is_superuser = is_superuser
|
||||
changed = True
|
||||
if user.status != status:
|
||||
user.status = status
|
||||
changed = True
|
||||
if user.email_verified_at is None and status == "active":
|
||||
user.email_verified_at = datetime.now(UTC)
|
||||
changed = True
|
||||
if changed:
|
||||
repository.update_user(user)
|
||||
return user
|
||||
|
||||
user = create_user(
|
||||
email=email,
|
||||
password_hash=hash_password(password),
|
||||
role=role,
|
||||
is_superuser=is_superuser,
|
||||
status=status,
|
||||
)
|
||||
user.email_verified_at = datetime.now(UTC)
|
||||
repository.update_user(user)
|
||||
return user
|
||||
|
||||
|
||||
def run_seed(include_demo_pages: bool = True) -> None:
|
||||
admin = _ensure_user(
|
||||
email="admin@compton.example",
|
||||
password=settings.admin_initial_password,
|
||||
role="admin",
|
||||
is_superuser=True,
|
||||
status="active",
|
||||
)
|
||||
_ensure_user(
|
||||
email="user@compton.example",
|
||||
password=settings.demo_user_password,
|
||||
role="user",
|
||||
is_superuser=False,
|
||||
status="active",
|
||||
)
|
||||
_ensure_user(
|
||||
email="ops@compton.example",
|
||||
password=settings.demo_ops_password,
|
||||
role="admin",
|
||||
is_superuser=False,
|
||||
status="active",
|
||||
)
|
||||
|
||||
if not include_demo_pages:
|
||||
return
|
||||
|
||||
demo_pages = [
|
||||
{
|
||||
"slug": "about",
|
||||
"title": "О бренде",
|
||||
"body": "<p>Compton — платформа Organic Tech.</p>",
|
||||
},
|
||||
{
|
||||
"slug": "privacy",
|
||||
"title": "Политика конфиденциальности",
|
||||
"body": "<p>Мы обрабатываем персональные данные согласно политике.</p>",
|
||||
},
|
||||
{
|
||||
"slug": "terms",
|
||||
"title": "Условия использования",
|
||||
"body": "<p>Используя сервис, вы принимаете условия.</p>",
|
||||
},
|
||||
]
|
||||
|
||||
for page in demo_pages:
|
||||
if get_page_by_slug(page["slug"], include_draft=True):
|
||||
continue
|
||||
create_page(
|
||||
slug=page["slug"],
|
||||
title=page["title"],
|
||||
body=page["body"],
|
||||
status="published",
|
||||
author_id=admin.id,
|
||||
)
|
||||
@@ -0,0 +1,43 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import UTC, datetime, timedelta
|
||||
|
||||
from sqlalchemy import delete, or_
|
||||
|
||||
from app.core.database import session_scope
|
||||
from app.modules.auth.models import EmailVerificationToken, PasswordResetToken, RefreshToken
|
||||
|
||||
|
||||
def cleanup_expired_tokens(retention_days: int = 30) -> dict[str, int]:
|
||||
now = datetime.now(UTC)
|
||||
revoked_cutoff = now - timedelta(days=retention_days)
|
||||
with session_scope() as db:
|
||||
refresh_deleted = db.execute(
|
||||
delete(RefreshToken).where(
|
||||
or_(
|
||||
RefreshToken.expires_at < now,
|
||||
RefreshToken.revoked_at.is_not(None) & (RefreshToken.revoked_at < revoked_cutoff),
|
||||
)
|
||||
)
|
||||
).rowcount or 0
|
||||
reset_deleted = db.execute(
|
||||
delete(PasswordResetToken).where(
|
||||
or_(
|
||||
PasswordResetToken.expires_at < now,
|
||||
PasswordResetToken.used_at.is_not(None) & (PasswordResetToken.used_at < now),
|
||||
)
|
||||
)
|
||||
).rowcount or 0
|
||||
verify_deleted = db.execute(
|
||||
delete(EmailVerificationToken).where(
|
||||
or_(
|
||||
EmailVerificationToken.expires_at < now,
|
||||
EmailVerificationToken.used_at.is_not(None) & (EmailVerificationToken.used_at < now),
|
||||
)
|
||||
)
|
||||
).rowcount or 0
|
||||
return {
|
||||
"refresh_tokens_deleted": int(refresh_deleted),
|
||||
"password_reset_tokens_deleted": int(reset_deleted),
|
||||
"email_verification_tokens_deleted": int(verify_deleted),
|
||||
}
|
||||
Reference in New Issue
Block a user