from datetime import datetime from pydantic import BaseModel, Field class AdminLoginRequest(BaseModel): account: str = Field(min_length=1, max_length=100) password: str = Field(min_length=1, max_length=200) class AdminUserOut(BaseModel): id: int nickname: str phone: str | None = None email: str | None = None role: str model_config = {"from_attributes": True} class AdminLoginResponse(BaseModel): access_token: str refresh_token: str token_type: str = "bearer" user: AdminUserOut class AdminSpotListItem(BaseModel): id: int title: str city: str audit_status: str reject_reason: str | None = None creator_id: int model_config = {"from_attributes": True} class AdminSpotImageItem(BaseModel): id: int image_url: str is_cover: bool sort_order: int model_config = {"from_attributes": True} class AdminSpotDetailItem(BaseModel): id: int title: str city: str longitude: float | None = None latitude: float | None = None description: str | None = None transport: str | None = None best_time: str | None = None difficulty: str | None = None is_free: bool price_min: float | None = None price_max: float | None = None audit_status: str reject_reason: str | None = None creator_id: int tag_ids: list[int] = [] image_urls: list[str] = [] images: list[AdminSpotImageItem] = [] model_config = {"from_attributes": True} class AdminSpotCreateRequest(BaseModel): title: str = Field(min_length=1, max_length=200) city: str = Field(min_length=1, max_length=100) longitude: float latitude: float description: str | None = None transport: str | None = None best_time: str | None = Field(default=None, max_length=200) difficulty: str | None = None is_free: bool = True price_min: float | None = None price_max: float | None = None audit_status: str = Field(default="pending", pattern="^(pending|approved|rejected|deleted)$") reject_reason: str | None = Field(default=None, max_length=500) creator_id: int image_urls: list[str] = [] tag_ids: list[int] = [] class AdminSpotUpdateRequest(BaseModel): title: str | None = Field(default=None, min_length=1, max_length=200) city: str | None = Field(default=None, min_length=1, max_length=100) longitude: float | None = None latitude: float | None = None description: str | None = None transport: str | None = None best_time: str | None = Field(default=None, max_length=200) difficulty: str | None = None is_free: bool | None = None price_min: float | None = None price_max: float | None = None audit_status: str | None = Field(default=None, pattern="^(pending|approved|rejected|deleted)$") reject_reason: str | None = Field(default=None, max_length=500) creator_id: int | None = None image_urls: list[str] | None = None tag_ids: list[int] | None = None class AdminSpotAuditRequest(BaseModel): audit_status: str = Field(pattern="^(pending|approved|rejected|deleted)$") reject_reason: str | None = Field(default=None, max_length=500) class AdminBatchAuditRequest(BaseModel): ids: list[int] = Field(min_length=1) audit_status: str = Field(pattern="^(pending|approved|rejected|deleted)$") reject_reason: str | None = Field(default=None, max_length=500) class AdminDashboardStats(BaseModel): spots_total: int spots_pending: int spots_approved: int spots_rejected: int users_total: int events_total: int shooting_total: int class AdminEventListItem(BaseModel): id: int title: str city: str status: str audit_status: str reject_reason: str | None = None creator_id: int registration_count: int max_participants: int model_config = {"from_attributes": True} class AdminEventRegistrationItem(BaseModel): id: int event_id: int user_id: int user_nickname: str status: str created_at: datetime | None = None class AdminEventPhotoItem(BaseModel): id: int event_id: int uploader_id: int uploader_nickname: str image_url: str caption: str | None = None spot_id: int | None = None created_at: datetime | None = None class AdminEventPhotoInput(BaseModel): uploader_id: int image_url: str = Field(min_length=1, max_length=500) caption: str | None = Field(default=None, max_length=200) spot_id: int | None = None class AdminEventDetailItem(BaseModel): id: int title: str city: str description: str | None = None cover_url: str | None = None location_name: str | None = None start_time: datetime | None = None end_time: datetime | None = None max_participants: int spot_id: int | None = None status: str audit_status: str reject_reason: str | None = None registration_count: int creator_id: int registration_user_ids: list[int] = [] photos: list[AdminEventPhotoItem] = [] registrations: list[AdminEventRegistrationItem] = [] model_config = {"from_attributes": True} class AdminEventCreateRequest(BaseModel): creator_id: int title: str = Field(min_length=1, max_length=200) city: str = Field(min_length=1, max_length=100) description: str | None = None cover_url: str | None = Field(default=None, max_length=500) location_name: str | None = Field(default=None, max_length=200) start_time: datetime | None = None end_time: datetime | None = None max_participants: int = Field(default=0, ge=0) spot_id: int | None = None status: str = Field(default="upcoming", pattern="^(upcoming|ongoing|ended|cancelled)$") audit_status: str = Field(default="pending", pattern="^(pending|approved|rejected|deleted)$") reject_reason: str | None = Field(default=None, max_length=500) registration_user_ids: list[int] = [] photos: list[AdminEventPhotoInput] = [] class AdminEventUpdateRequest(BaseModel): creator_id: int | None = None title: str | None = Field(default=None, min_length=1, max_length=200) city: str | None = Field(default=None, min_length=1, max_length=100) description: str | None = None cover_url: str | None = Field(default=None, max_length=500) location_name: str | None = Field(default=None, max_length=200) start_time: datetime | None = None end_time: datetime | None = None max_participants: int | None = Field(default=None, ge=0) spot_id: int | None = None status: str | None = Field(default=None, pattern="^(upcoming|ongoing|ended|cancelled)$") audit_status: str | None = Field(default=None, pattern="^(pending|approved|rejected|deleted)$") reject_reason: str | None = Field(default=None, max_length=500) registration_user_ids: list[int] | None = None photos: list[AdminEventPhotoInput] | None = None class AdminEventRegistrationCreateRequest(BaseModel): user_id: int status: str = Field(default="registered", pattern="^(registered|cancelled)$") class AdminEventRegistrationUpdateRequest(BaseModel): status: str = Field(pattern="^(registered|cancelled)$") class AdminEventPhotoCreateRequest(BaseModel): uploader_id: int image_url: str = Field(min_length=1, max_length=500) caption: str | None = Field(default=None, max_length=200) spot_id: int | None = None class AdminEventPhotoUpdateRequest(BaseModel): uploader_id: int | None = None image_url: str | None = Field(default=None, min_length=1, max_length=500) caption: str | None = Field(default=None, max_length=200) spot_id: int | None = None class AdminShootingListItem(BaseModel): id: int title: str city: str status: str audit_status: str reject_reason: str | None = None creator_id: int role_needed: str max_applicants: int is_free: bool model_config = {"from_attributes": True} class AdminShootingApplicationItem(BaseModel): id: int request_id: int applicant_id: int applicant_nickname: str message: str | None = None status: str created_at: datetime | None = None class AdminShootingDetailItem(BaseModel): id: int title: str city: str description: str | None = None style: str | None = None shoot_date: datetime | None = None is_free: bool budget_min: float | None = None budget_max: float | None = None role_needed: str max_applicants: int contact_info: str | None = None spot_id: int | None = None status: str audit_status: str reject_reason: str | None = None creator_id: int applications: list[AdminShootingApplicationItem] = [] application_user_ids: list[int] = [] model_config = {"from_attributes": True} class AdminShootingApplicationInput(BaseModel): applicant_id: int message: str | None = None status: str = Field(default="pending", pattern="^(pending|accepted|rejected|cancelled)$") class AdminShootingCreateRequest(BaseModel): creator_id: int title: str = Field(min_length=1, max_length=200) city: str = Field(min_length=1, max_length=100) description: str | None = None style: str | None = Field(default=None, max_length=100) shoot_date: datetime | None = None is_free: bool = False budget_min: float | None = None budget_max: float | None = None role_needed: str = Field(default="photographer", pattern="^(photographer|cosplayer|both)$") max_applicants: int = Field(default=1, ge=1, le=100) contact_info: str | None = Field(default=None, max_length=200) spot_id: int | None = None status: str = Field(default="open", pattern="^(open|matched|closed|cancelled)$") audit_status: str = Field(default="pending", pattern="^(pending|approved|rejected|deleted)$") reject_reason: str | None = Field(default=None, max_length=500) applications: list[AdminShootingApplicationInput] = [] class AdminShootingUpdateRequest(BaseModel): creator_id: int | None = None title: str | None = Field(default=None, min_length=1, max_length=200) city: str | None = Field(default=None, min_length=1, max_length=100) description: str | None = None style: str | None = Field(default=None, max_length=100) shoot_date: datetime | None = None is_free: bool | None = None budget_min: float | None = None budget_max: float | None = None role_needed: str | None = Field(default=None, pattern="^(photographer|cosplayer|both)$") max_applicants: int | None = Field(default=None, ge=1, le=100) contact_info: str | None = Field(default=None, max_length=200) spot_id: int | None = None status: str | None = Field(default=None, pattern="^(open|matched|closed|cancelled)$") audit_status: str | None = Field(default=None, pattern="^(pending|approved|rejected|deleted)$") reject_reason: str | None = Field(default=None, max_length=500) applications: list[AdminShootingApplicationInput] | None = None class AdminShootingApplicationCreateRequest(BaseModel): applicant_id: int message: str | None = None status: str = Field(default="pending", pattern="^(pending|accepted|rejected|cancelled)$") class AdminShootingApplicationUpdateRequest(BaseModel): message: str | None = None status: str = Field(pattern="^(pending|accepted|rejected|cancelled)$") class AdminAuditRequest(BaseModel): audit_status: str = Field(pattern="^(pending|approved|rejected|deleted)$") reject_reason: str | None = Field(default=None, max_length=500) class AdminModuleCrudCoverage(BaseModel): create: bool read: bool update: bool delete: bool class AdminModuleDesignItem(BaseModel): module_key: str module_name: str models: list[str] api_endpoint_prefixes: list[str] coverage: AdminModuleCrudCoverage status: str notes: str class AdminModuleDesignResponse(BaseModel): total_modules: int full_covered: int partial_covered: int missing_covered: int items: list[AdminModuleDesignItem] class AdminUserListItem(BaseModel): id: int nickname: str phone: str | None = None email: str | None = None city: str | None = None identity: str | None = None role: str is_active: bool model_config = {"from_attributes": True} class AdminUserCreateRequest(BaseModel): nickname: str = Field(min_length=1, max_length=50) password: str = Field(min_length=6, max_length=200) phone: str | None = Field(default=None, max_length=20) email: str | None = Field(default=None, max_length=255) city: str | None = Field(default=None, max_length=100) identity: str = Field(default="both", pattern="^(photographer|cosplayer|both)$") role: str = Field(default="user", pattern="^(user|moderator|admin)$") is_active: bool = True class AdminUserUpdateRequest(BaseModel): nickname: str | None = Field(default=None, min_length=1, max_length=50) phone: str | None = Field(default=None, max_length=20) email: str | None = Field(default=None, max_length=255) city: str | None = Field(default=None, max_length=100) identity: str | None = Field(default=None, pattern="^(photographer|cosplayer|both)$") role: str | None = Field(default=None, pattern="^(user|moderator|admin)$") is_active: bool | None = None password: str | None = Field(default=None, min_length=6, max_length=200) class AdminMembershipPlanItem(BaseModel): id: int name: str description: str | None = None duration_days: int price: float benefits: str | None = None extra_uploads: int extra_top_count: int is_active: bool sort_order: int model_config = {"from_attributes": True} class AdminMembershipPlanCreateRequest(BaseModel): name: str = Field(min_length=1, max_length=100) description: str | None = None duration_days: int = Field(ge=1, le=3650) price: float = Field(ge=0) benefits: str | None = None extra_uploads: int = Field(default=0, ge=0) extra_top_count: int = Field(default=0, ge=0) is_active: bool = True sort_order: int = 0 class AdminMembershipPlanUpdateRequest(BaseModel): name: str | None = Field(default=None, min_length=1, max_length=100) description: str | None = None duration_days: int | None = Field(default=None, ge=1, le=3650) price: float | None = Field(default=None, ge=0) benefits: str | None = None extra_uploads: int | None = Field(default=None, ge=0) extra_top_count: int | None = Field(default=None, ge=0) is_active: bool | None = None sort_order: int | None = None class AdminUserMembershipItem(BaseModel): id: int user_id: int user_nickname: str plan_id: int plan_name: str start_date: datetime end_date: datetime is_active: bool class AdminUserMembershipCreateRequest(BaseModel): user_id: int plan_id: int start_date: datetime end_date: datetime is_active: bool = True class AdminUserMembershipUpdateRequest(BaseModel): plan_id: int | None = None start_date: datetime | None = None end_date: datetime | None = None is_active: bool | None = None class AdminPointLedgerItem(BaseModel): id: int user_id: int user_nickname: str change: int balance: int reason: str ref_type: str | None = None ref_id: int | None = None rolled_back: bool = False created_at: datetime class AdminPointAdjustRequest(BaseModel): user_id: int change: int = Field(ne=0) reason: str = Field(min_length=1, max_length=200) ref_type: str | None = Field(default=None, max_length=50) ref_id: int | None = None class AdminNotificationItem(BaseModel): id: int user_id: int user_nickname: str type: str title: str content: str | None = None ref_type: str | None = None ref_id: int | None = None is_read: bool created_at: datetime class AdminNotificationCreateRequest(BaseModel): user_id: int type: str = Field(min_length=1, max_length=50) title: str = Field(min_length=1, max_length=200) content: str | None = None ref_type: str | None = Field(default=None, max_length=50) ref_id: int | None = None class AdminNotificationUpdateRequest(BaseModel): title: str | None = Field(default=None, min_length=1, max_length=200) content: str | None = None is_read: bool | None = None class AdminAuditLogItem(BaseModel): id: int operator_id: int operator_nickname: str action: str target_type: str target_id: int | None = None detail: str | None = None created_at: datetime class AdminAuditLogCreateRequest(BaseModel): action: str = Field(min_length=1, max_length=100) target_type: str = Field(min_length=1, max_length=50) target_id: int | None = None detail: str | None = None class AdminReportItem(BaseModel): id: int reporter_id: int reporter_nickname: str target_type: str target_id: int reason: str status: str handler_id: int | None = None handler_nickname: str | None = None conclusion: str | None = None created_at: datetime resolved_at: datetime | None = None class AdminReportUpdateRequest(BaseModel): status: str | None = Field(default=None, pattern="^(pending|processing|resolved|rejected)$") handler_id: int | None = None conclusion: str | None = None class AdminSystemConfigItem(BaseModel): id: int config_key: str category: str title: str config_json: str description: str | None = None is_active: bool sort_order: int updated_by: int | None = None updated_at: datetime | None = None model_config = {"from_attributes": True} class AdminSystemConfigCreateRequest(BaseModel): config_key: str = Field(min_length=1, max_length=100) category: str = Field(min_length=1, max_length=50) title: str = Field(min_length=1, max_length=200) config_json: str = Field(min_length=2) description: str | None = None is_active: bool = True sort_order: int = 0 class AdminSystemConfigUpdateRequest(BaseModel): category: str | None = Field(default=None, min_length=1, max_length=50) title: str | None = Field(default=None, min_length=1, max_length=200) config_json: str | None = Field(default=None, min_length=2) description: str | None = None is_active: bool | None = None sort_order: int | None = None