Initial project commit
This commit is contained in:
@@ -0,0 +1,608 @@
|
||||
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
|
||||
@@ -0,0 +1,42 @@
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class AppNavConfigOut(BaseModel):
|
||||
id: int
|
||||
key: str
|
||||
label: str
|
||||
page_path: str
|
||||
icon: str
|
||||
active_icon: str
|
||||
color: str
|
||||
active_color: str
|
||||
is_active: bool
|
||||
sort_order: int
|
||||
updated_at: datetime | None = None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class AppNavConfigCreate(BaseModel):
|
||||
key: str = Field(min_length=1, max_length=50)
|
||||
label: str = Field(min_length=1, max_length=50)
|
||||
page_path: str = Field(min_length=1, max_length=200)
|
||||
icon: str = Field(min_length=1, max_length=50)
|
||||
active_icon: str = Field(min_length=1, max_length=50)
|
||||
color: str = Field(min_length=4, max_length=20)
|
||||
active_color: str = Field(min_length=4, max_length=20)
|
||||
is_active: bool = True
|
||||
sort_order: int = 0
|
||||
|
||||
|
||||
class AppNavConfigUpdate(BaseModel):
|
||||
label: str | None = Field(default=None, min_length=1, max_length=50)
|
||||
page_path: str | None = Field(default=None, min_length=1, max_length=200)
|
||||
icon: str | None = Field(default=None, min_length=1, max_length=50)
|
||||
active_icon: str | None = Field(default=None, min_length=1, max_length=50)
|
||||
color: str | None = Field(default=None, min_length=4, max_length=20)
|
||||
active_color: str | None = Field(default=None, min_length=4, max_length=20)
|
||||
is_active: bool | None = None
|
||||
sort_order: int | None = None
|
||||
@@ -0,0 +1,26 @@
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.schemas.user import UserInfo
|
||||
|
||||
|
||||
class CommentCreate(BaseModel):
|
||||
content: str = Field(..., min_length=1, max_length=500)
|
||||
parent_id: int | None = None
|
||||
|
||||
|
||||
class CommentOut(BaseModel):
|
||||
id: int
|
||||
spot_id: int
|
||||
user: UserInfo | None = None
|
||||
parent_id: int | None = None
|
||||
content: str
|
||||
created_at: datetime | None = None
|
||||
replies: list["CommentOut"] = []
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class ReportCreate(BaseModel):
|
||||
reason: str = Field(..., min_length=1, max_length=500)
|
||||
@@ -0,0 +1,20 @@
|
||||
from typing import Generic, TypeVar
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class ResponseBase(BaseModel):
|
||||
code: int = 0
|
||||
message: str = "success"
|
||||
|
||||
|
||||
class PageParams(BaseModel):
|
||||
page: int = 1
|
||||
page_size: int = Field(default=20, le=100)
|
||||
|
||||
|
||||
class PageResponse(ResponseBase, Generic[T]):
|
||||
total: int
|
||||
items: list[T]
|
||||
@@ -0,0 +1,24 @@
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.schemas.user import UserInfo
|
||||
|
||||
|
||||
class CorrectionCreate(BaseModel):
|
||||
field_name: str = Field(..., max_length=50)
|
||||
suggested_value: str = Field(..., min_length=1)
|
||||
reason: str | None = None
|
||||
|
||||
|
||||
class CorrectionOut(BaseModel):
|
||||
id: int
|
||||
spot_id: int
|
||||
user: UserInfo | None = None
|
||||
field_name: str
|
||||
suggested_value: str
|
||||
reason: str | None = None
|
||||
status: str
|
||||
created_at: datetime | None = None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
@@ -0,0 +1,82 @@
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.schemas.user import UserInfo
|
||||
|
||||
|
||||
class EventCreate(BaseModel):
|
||||
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 = 0
|
||||
spot_id: int | None = None
|
||||
|
||||
|
||||
class EventUpdate(BaseModel):
|
||||
title: str | None = None
|
||||
city: str | None = None
|
||||
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 | None = None
|
||||
spot_id: int | None = None
|
||||
|
||||
|
||||
class EventBrief(BaseModel):
|
||||
id: int
|
||||
title: str
|
||||
city: str
|
||||
cover_url: str | None = None
|
||||
location_name: str | None = None
|
||||
start_time: datetime | None = None
|
||||
end_time: datetime | None = None
|
||||
status: str = "upcoming"
|
||||
audit_status: str = "pending"
|
||||
creator: UserInfo | None = None
|
||||
registration_count: int = 0
|
||||
max_participants: int = 0
|
||||
created_at: datetime | None = None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class EventDetail(EventBrief):
|
||||
description: str | None = None
|
||||
spot_id: int | None = None
|
||||
reject_reason: str | None = None
|
||||
has_registered: bool = False
|
||||
photos: list["EventPhotoOut"] = []
|
||||
|
||||
|
||||
class EventPhotoOut(BaseModel):
|
||||
id: int
|
||||
image_url: str
|
||||
caption: str | None = None
|
||||
uploader: UserInfo | None = None
|
||||
spot_id: int | None = None
|
||||
created_at: datetime | None = None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class EventPhotoCreate(BaseModel):
|
||||
image_url: str
|
||||
caption: str | None = None
|
||||
spot_id: int | None = None
|
||||
|
||||
|
||||
class RegistrationOut(BaseModel):
|
||||
id: int
|
||||
event_id: int
|
||||
user: UserInfo | None = None
|
||||
status: str = "registered"
|
||||
created_at: datetime | None = None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
@@ -0,0 +1,31 @@
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class MembershipPlanOut(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
description: str | None = None
|
||||
duration_days: int
|
||||
price: float
|
||||
benefits: str | None = None
|
||||
extra_uploads: int = 0
|
||||
extra_top_count: int = 0
|
||||
sort_order: int = 0
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class UserMembershipOut(BaseModel):
|
||||
id: int
|
||||
plan: MembershipPlanOut | None = None
|
||||
start_date: datetime
|
||||
end_date: datetime
|
||||
is_active: bool = True
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class PurchaseMembership(BaseModel):
|
||||
plan_id: int
|
||||
@@ -0,0 +1,19 @@
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class PointBalance(BaseModel):
|
||||
balance: int
|
||||
|
||||
|
||||
class PointRecord(BaseModel):
|
||||
id: int
|
||||
change: int
|
||||
balance: int
|
||||
reason: str
|
||||
ref_type: str | None = None
|
||||
ref_id: int | None = None
|
||||
created_at: datetime | None = None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
@@ -0,0 +1,60 @@
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class PromotionOut(BaseModel):
|
||||
id: int
|
||||
title: str
|
||||
image_url: str
|
||||
link_type: str = "spot"
|
||||
link_id: int | None = None
|
||||
link_url: str | None = None
|
||||
position: str = "home_banner"
|
||||
sort_order: int = 0
|
||||
spot_id: int | None = None
|
||||
event_id: int | None = None
|
||||
shooting_id: int | None = None
|
||||
start_time: datetime | None = None
|
||||
end_time: datetime | None = None
|
||||
is_active: bool = True
|
||||
impressions: int = 0
|
||||
clicks: int = 0
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class PromotionClick(BaseModel):
|
||||
promotion_id: int
|
||||
|
||||
|
||||
class PromotionCreate(BaseModel):
|
||||
title: str = Field(min_length=1, max_length=200)
|
||||
image_url: str = Field(min_length=1, max_length=500)
|
||||
link_type: str = Field(pattern="^(spot|event|shooting|url)$")
|
||||
link_id: int | None = None
|
||||
spot_id: int | None = None
|
||||
event_id: int | None = None
|
||||
shooting_id: int | None = None
|
||||
link_url: str | None = Field(default=None, max_length=500)
|
||||
position: str = Field(default="home_banner", min_length=1, max_length=30)
|
||||
sort_order: int = 0
|
||||
start_time: datetime | None = None
|
||||
end_time: datetime | None = None
|
||||
is_active: bool = True
|
||||
|
||||
|
||||
class PromotionUpdate(BaseModel):
|
||||
title: str | None = Field(default=None, min_length=1, max_length=200)
|
||||
image_url: str | None = Field(default=None, min_length=1, max_length=500)
|
||||
link_type: str | None = Field(default=None, pattern="^(spot|event|shooting|url)$")
|
||||
link_id: int | None = None
|
||||
spot_id: int | None = None
|
||||
event_id: int | None = None
|
||||
shooting_id: int | None = None
|
||||
link_url: str | None = Field(default=None, max_length=500)
|
||||
position: str | None = Field(default=None, min_length=1, max_length=30)
|
||||
sort_order: int | None = None
|
||||
start_time: datetime | None = None
|
||||
end_time: datetime | None = None
|
||||
is_active: bool | None = None
|
||||
@@ -0,0 +1,21 @@
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from app.schemas.user import UserInfo
|
||||
|
||||
|
||||
class RatingCreate(BaseModel):
|
||||
score: int = Field(..., ge=1, le=5)
|
||||
short_comment: str | None = None
|
||||
|
||||
|
||||
class RatingOut(BaseModel):
|
||||
id: int
|
||||
spot_id: int
|
||||
user: UserInfo | None = None
|
||||
score: int
|
||||
short_comment: str | None = None
|
||||
created_at: datetime | None = None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
@@ -0,0 +1,78 @@
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.schemas.user import UserInfo
|
||||
|
||||
|
||||
class ShootingRequestCreate(BaseModel):
|
||||
title: str
|
||||
city: str
|
||||
description: str | None = None
|
||||
style: str | None = None
|
||||
shoot_date: datetime | None = None
|
||||
is_free: bool = False
|
||||
budget_min: float | None = None
|
||||
budget_max: float | None = None
|
||||
role_needed: str = "photographer"
|
||||
max_applicants: int = 1
|
||||
contact_info: str | None = None
|
||||
spot_id: int | None = None
|
||||
|
||||
|
||||
class ShootingRequestUpdate(BaseModel):
|
||||
title: str | None = None
|
||||
city: str | None = None
|
||||
description: str | None = None
|
||||
style: str | None = None
|
||||
shoot_date: datetime | None = None
|
||||
is_free: bool | None = None
|
||||
budget_min: float | None = None
|
||||
budget_max: float | None = None
|
||||
role_needed: str | None = None
|
||||
max_applicants: int | None = None
|
||||
contact_info: str | None = None
|
||||
spot_id: int | None = None
|
||||
|
||||
|
||||
class ShootingRequestBrief(BaseModel):
|
||||
id: int
|
||||
title: str
|
||||
city: str
|
||||
style: str | None = None
|
||||
shoot_date: datetime | None = None
|
||||
is_free: bool = False
|
||||
budget_min: float | None = None
|
||||
budget_max: float | None = None
|
||||
role_needed: str = "photographer"
|
||||
status: str = "open"
|
||||
audit_status: str = "pending"
|
||||
creator: UserInfo | None = None
|
||||
application_count: int = 0
|
||||
created_at: datetime | None = None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class ShootingRequestDetail(ShootingRequestBrief):
|
||||
description: str | None = None
|
||||
max_applicants: int = 1
|
||||
contact_info: str | None = None
|
||||
spot_id: int | None = None
|
||||
reject_reason: str | None = None
|
||||
has_applied: bool = False
|
||||
|
||||
|
||||
class ApplicationCreate(BaseModel):
|
||||
message: str | None = None
|
||||
|
||||
|
||||
class ApplicationOut(BaseModel):
|
||||
id: int
|
||||
request_id: int
|
||||
applicant: UserInfo | None = None
|
||||
message: str | None = None
|
||||
status: str = "pending"
|
||||
created_at: datetime | None = None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
@@ -0,0 +1,82 @@
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.schemas.tag import TagOut
|
||||
from app.schemas.user import UserInfo
|
||||
|
||||
|
||||
class SpotCreate(BaseModel):
|
||||
title: str
|
||||
city: str
|
||||
longitude: float
|
||||
latitude: float
|
||||
description: str | None = None
|
||||
transport: str | None = None
|
||||
best_time: str | None = None
|
||||
difficulty: str | None = None
|
||||
is_free: bool = True
|
||||
price_min: float | None = None
|
||||
price_max: float | None = None
|
||||
image_urls: list[str] = []
|
||||
tag_ids: list[int] = []
|
||||
|
||||
|
||||
class SpotUpdate(BaseModel):
|
||||
title: str | None = None
|
||||
city: str | None = None
|
||||
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 | None = None
|
||||
price_min: float | None = None
|
||||
price_max: float | None = None
|
||||
tag_ids: list[int] | None = None
|
||||
|
||||
|
||||
class SpotImageOut(BaseModel):
|
||||
id: int
|
||||
image_url: str
|
||||
is_cover: bool
|
||||
sort_order: int
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class SpotBrief(BaseModel):
|
||||
id: int
|
||||
title: str
|
||||
city: str
|
||||
longitude: float | None = None
|
||||
latitude: float | None = None
|
||||
cover_image_url: str | None = None
|
||||
audit_status: str
|
||||
avg_rating: float | None = None
|
||||
favorite_count: int = 0
|
||||
is_free: bool = True
|
||||
price_min: float | None = None
|
||||
price_max: float | None = None
|
||||
created_at: datetime | None = None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class SpotDetail(SpotBrief):
|
||||
description: str | None = None
|
||||
transport: str | None = None
|
||||
best_time: str | None = None
|
||||
difficulty: str | None = None
|
||||
creator: UserInfo | None = None
|
||||
images: list[SpotImageOut] = []
|
||||
tags: list[TagOut] = []
|
||||
rating_count: int = 0
|
||||
is_favorited: bool = False
|
||||
|
||||
|
||||
class SpotImageCreate(BaseModel):
|
||||
image_url: str
|
||||
is_cover: bool = False
|
||||
sort_order: int = 0
|
||||
@@ -0,0 +1,15 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class TagOut(BaseModel):
|
||||
id: int
|
||||
name: str
|
||||
category: str | None = None
|
||||
usage_count: int = 0
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class TagCreate(BaseModel):
|
||||
name: str
|
||||
category: str | None = None
|
||||
@@ -0,0 +1,17 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class UploadResponse(BaseModel):
|
||||
url: str
|
||||
filename: str
|
||||
|
||||
|
||||
class PresignedUrlRequest(BaseModel):
|
||||
filename: str
|
||||
content_type: str = "image/jpeg"
|
||||
|
||||
|
||||
class PresignedUrlResponse(BaseModel):
|
||||
upload_url: str
|
||||
file_key: str
|
||||
public_url: str
|
||||
@@ -0,0 +1,47 @@
|
||||
from datetime import datetime
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class UserRegister(BaseModel):
|
||||
phone: str | None = None
|
||||
email: str | None = None
|
||||
password: str = Field(..., min_length=6)
|
||||
nickname: str = Field(..., min_length=1, max_length=50)
|
||||
city: str | None = None
|
||||
identity: str | None = "both"
|
||||
|
||||
|
||||
class UserLogin(BaseModel):
|
||||
account: str
|
||||
password: str
|
||||
|
||||
|
||||
class TokenResponse(BaseModel):
|
||||
access_token: str
|
||||
refresh_token: str
|
||||
token_type: str = "bearer"
|
||||
|
||||
|
||||
class RefreshTokenRequest(BaseModel):
|
||||
refresh_token: str
|
||||
|
||||
|
||||
class UserInfo(BaseModel):
|
||||
id: int
|
||||
nickname: str
|
||||
avatar_url: str | None = None
|
||||
city: str | None = None
|
||||
bio: str | None = None
|
||||
identity: str | None = None
|
||||
created_at: datetime | None = None
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class UserUpdate(BaseModel):
|
||||
nickname: str | None = None
|
||||
avatar_url: str | None = None
|
||||
city: str | None = None
|
||||
bio: str | None = None
|
||||
identity: str | None = None
|
||||
Reference in New Issue
Block a user