Initial commit: site monorepo with API, web, and infra.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
влад
2026-07-16 10:11:54 +03:00
co-authored by Cursor
commit 016910ffb7
447 changed files with 73972 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
from app.db.base import Base
__all__ = ["Base"]
+5
View File
@@ -0,0 +1,5 @@
from sqlalchemy.orm import DeclarativeBase
class Base(DeclarativeBase):
pass
+80
View File
@@ -0,0 +1,80 @@
from app.modules.auth.models import EmailVerificationToken, PasswordResetToken, RefreshToken
from app.modules.content.models import ContentPage
from app.modules.sync.models import (
Enterprise,
EnterpriseMember,
FarmHub,
HubCredential,
HubPairingSession,
ReportSyncState,
SyncAppliedEvent,
SyncConflict,
SyncCursor,
SyncEventLog,
SyncOutbox,
SyncReconcileRun,
SyncRecordState,
UserFarmAccess,
)
from app.modules.users.models import User, UserProfile
from app.modules.zootech.settings_models import ZootechFeedQualitySettings, ZootechOrgSettings
from app.modules.zootech.catalog_models import (
ZootechDailyComponentNormAdjustment,
ZootechDailyIngredientReplacement,
ZootechDailyIngredientSkip,
ZootechDailyTripSkip,
ZootechDailyUnloadingGroupSkip,
ZootechFeedDispenser,
ZootechFeedingLocation,
ZootechFeedingPeriod,
ZootechFeedingPoint,
ZootechFeedMixer,
ZootechPeriodRecipe,
ZootechTrip,
ZootechUnloadingGroup,
)
from app.modules.zootech.report_models import ZootechFeedAlert, ZootechLoadingReport, ZootechUnloadingReport
__all__ = [
"User",
"UserProfile",
"RefreshToken",
"PasswordResetToken",
"EmailVerificationToken",
"ContentPage",
"Enterprise",
"EnterpriseMember",
"FarmHub",
"UserFarmAccess",
"HubCredential",
"HubPairingSession",
"SyncOutbox",
"SyncEventLog",
"SyncAppliedEvent",
"SyncCursor",
"SyncRecordState",
"SyncConflict",
"SyncReconcileRun",
"ReportSyncState",
"ZootechComponent",
"ZootechRecipe",
"ZootechIngredient",
"ZootechUnloadingGroup",
"ZootechFeedMixer",
"ZootechFeedDispenser",
"ZootechFeedingLocation",
"ZootechFeedingPeriod",
"ZootechFeedingPoint",
"ZootechPeriodRecipe",
"ZootechTrip",
"ZootechDailyTripSkip",
"ZootechDailyIngredientSkip",
"ZootechDailyUnloadingGroupSkip",
"ZootechDailyIngredientReplacement",
"ZootechDailyComponentNormAdjustment",
"ZootechLoadingReport",
"ZootechUnloadingReport",
"ZootechFeedAlert",
"ZootechOrgSettings",
"ZootechFeedQualitySettings",
]
+99
View File
@@ -0,0 +1,99 @@
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",
)
if settings.seed_demo_users and settings.app_env.lower() != "production":
_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,
)
+43
View File
@@ -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),
}