Files
Mplan e75833ee4b 重构表结构:合并 profiles→users,展平 API 响应,新增计数器字段
- models: 删除 Profile 模型,字段合并到 User;User 新增云朵/公开/收藏计数器;Cloud 新增 favorite_count
- schemas/serializers: 展平 UserOut/AuthOut/MeOut/AdminUserOut,移除嵌套 profile
- deps: user.profile.role→user.role,移除 selectinload profile
- auth/register: 注册时直接设置 user 字段,不再创建 Profile
- clouds: 创建/删除云朵时同步 user.cloud_count 计数器,状态变化时同步 public_cloud_count
- favorites: 点赞/取消时同步 cloud.favorite_count 计数器
- admin: 审批/隐藏/批量删时同步 public_cloud_count 计数器
- profiles/stats: 用计数器替代实时 COUNT 查询
- alembic: 新增 migration 合并 profiles 到 users,初始化 counters
2026-07-28 23:48:57 +08:00

91 lines
2.9 KiB
Python

from app.config import settings
from app.models import Cloud, CloudType, User
from app.schemas import (
CloudOut,
CloudOwnerOut,
CloudTypeOut,
CloudTypeSummary,
UserOut,
)
def media_url(path: str | None) -> str | None:
if not path:
return None
return f"{settings.media_base_url}/{path.lstrip('/')}"
def user_out(user: User) -> UserOut:
return UserOut(
id=user.id,
email=user.email,
email_verified=user.email_verified_at is not None,
username=user.username,
avatar_url=media_url(user.avatar_path),
role=user.role,
is_disabled=user.is_disabled,
cloud_count=user.cloud_count,
public_cloud_count=user.public_cloud_count,
collection_count=user.collection_count,
created_at=user.created_at,
)
def cloud_type_summary(cloud_type: CloudType) -> CloudTypeSummary:
return CloudTypeSummary(
id=cloud_type.id,
name=cloud_type.name,
name_en=cloud_type.name_en,
rarity=cloud_type.rarity,
)
def cloud_type_out(cloud_type: CloudType) -> CloudTypeOut:
return CloudTypeOut(
id=cloud_type.id,
name=cloud_type.name,
name_en=cloud_type.name_en,
genus=cloud_type.genus,
rarity=cloud_type.rarity,
description=cloud_type.description,
icon_url=cloud_type.icon_url,
created_at=cloud_type.created_at,
)
def cloud_out(
cloud: Cloud,
*,
include_private: bool = True,
favorite_count: int | None = None,
is_favorited: bool = False,
) -> CloudOut:
cloud_type = cloud.cloud_type
owner = cloud.user
type_name = cloud_type.name if cloud_type else (cloud.custom_cloud_type or "未知")
rarity = cloud_type.rarity if cloud_type else "common"
return CloudOut(
id=cloud.id,
user_id=cloud.user_id,
cloud_type_id=cloud.cloud_type_id,
custom_cloud_type=cloud.custom_cloud_type,
image_url=media_url(cloud.image_path) or "",
thumbnail_url=media_url(cloud.thumbnail_path) or "",
latitude=float(cloud.latitude) if cloud.latitude is not None else None,
longitude=float(cloud.longitude) if cloud.longitude is not None else None,
location_name=cloud.location_name,
description=cloud.description,
captured_at=cloud.captured_at,
status=cloud.status if include_private else "approved",
is_hidden=cloud.is_hidden if include_private else False,
created_at=cloud.created_at,
updated_at=cloud.updated_at,
cloud_type=cloud_type_summary(cloud_type) if cloud_type else None,
owner=CloudOwnerOut(id=owner.id, username=owner.username),
cloud_type_name=type_name,
cloud_type_rarity=rarity,
username=owner.username,
favorite_count=favorite_count if favorite_count is not None else 0,
is_favorited=is_favorited,
)