Initial project commit

This commit is contained in:
2026-05-09 16:40:29 +08:00
commit 02b0259a9e
267 changed files with 54891 additions and 0 deletions
View File
+608
View File
@@ -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
+42
View File
@@ -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
+26
View File
@@ -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)
+20
View File
@@ -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]
+24
View File
@@ -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}
+82
View File
@@ -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}
+31
View File
@@ -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
+19
View File
@@ -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}
+60
View File
@@ -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
+21
View File
@@ -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}
+78
View File
@@ -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}
+82
View File
@@ -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
+15
View File
@@ -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
+17
View File
@@ -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
+47
View File
@@ -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