Initial project commit
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
from app.models.user import User
|
||||
from app.models.spot import Spot, SpotImage
|
||||
from app.models.favorite import Favorite
|
||||
from app.models.point_ledger import PointLedger
|
||||
from app.models.audit_log import AuditLog
|
||||
from app.models.comment import Comment
|
||||
from app.models.report import Report
|
||||
from app.models.rating import Rating
|
||||
from app.models.tag import Tag, SpotTag
|
||||
from app.models.correction import Correction
|
||||
from app.models.notification import Notification
|
||||
from app.models.shooting import ShootingRequest, ShootingApplication
|
||||
from app.models.event import Event, EventRegistration, EventPhoto
|
||||
from app.models.promotion import Promotion
|
||||
from app.models.membership import MembershipPlan, UserMembership
|
||||
from app.models.app_nav_config import AppNavConfig
|
||||
from app.models.system_config import SystemConfig
|
||||
|
||||
__all__ = [
|
||||
"User", "Spot", "SpotImage", "Favorite", "PointLedger", "AuditLog",
|
||||
"Comment", "Report", "Rating", "Tag", "SpotTag", "Correction", "Notification",
|
||||
"ShootingRequest", "ShootingApplication",
|
||||
"Event", "EventRegistration", "EventPhoto",
|
||||
"Promotion", "MembershipPlan", "UserMembership",
|
||||
"AppNavConfig", "SystemConfig",
|
||||
]
|
||||
@@ -0,0 +1,26 @@
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import Boolean, DateTime, Integer, String, func
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.db.base import Base
|
||||
|
||||
|
||||
class AppNavConfig(Base):
|
||||
__tablename__ = "app_nav_configs"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||
key: Mapped[str] = mapped_column(String(50), nullable=False, unique=True, index=True)
|
||||
label: Mapped[str] = mapped_column(String(50), nullable=False)
|
||||
page_path: Mapped[str] = mapped_column(String(200), nullable=False)
|
||||
icon: Mapped[str] = mapped_column(String(50), nullable=False)
|
||||
active_icon: Mapped[str] = mapped_column(String(50), nullable=False)
|
||||
color: Mapped[str] = mapped_column(String(20), default="#999999")
|
||||
active_color: Mapped[str] = mapped_column(String(20), default="#6366f1")
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, default=True, index=True)
|
||||
sort_order: Mapped[int] = mapped_column(Integer, default=0)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<AppNavConfig {self.key}>"
|
||||
@@ -0,0 +1,27 @@
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import DateTime, ForeignKey, Integer, String, Text, func
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from app.db.base import Base
|
||||
|
||||
|
||||
class AuditLog(Base):
|
||||
__tablename__ = "audit_logs"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||
operator_id: Mapped[int] = mapped_column(
|
||||
Integer, ForeignKey("users.id"), nullable=False
|
||||
)
|
||||
action: Mapped[str] = mapped_column(String(100), nullable=False, index=True)
|
||||
target_type: Mapped[str] = mapped_column(String(50), nullable=False)
|
||||
target_id: Mapped[int | None] = mapped_column(Integer)
|
||||
detail: Mapped[str | None] = mapped_column(Text)
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), server_default=func.now()
|
||||
)
|
||||
|
||||
operator = relationship("User", lazy="selectin")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<AuditLog {self.id} {self.action} by={self.operator_id}>"
|
||||
@@ -0,0 +1,35 @@
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import DateTime, ForeignKey, Integer, String, Text, func
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from app.db.base import Base
|
||||
|
||||
|
||||
class Comment(Base):
|
||||
__tablename__ = "comments"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||
spot_id: Mapped[int] = mapped_column(Integer, ForeignKey("spots.id"), nullable=False, index=True)
|
||||
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), nullable=False)
|
||||
parent_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("comments.id"), nullable=True)
|
||||
content: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
audit_status: Mapped[str] = mapped_column(String(20), default="approved")
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
user = relationship("User", lazy="selectin")
|
||||
replies = relationship(
|
||||
"Comment",
|
||||
back_populates="parent",
|
||||
lazy="noload",
|
||||
foreign_keys=[parent_id],
|
||||
)
|
||||
parent = relationship(
|
||||
"Comment",
|
||||
remote_side=[id],
|
||||
back_populates="replies",
|
||||
lazy="noload",
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Comment {self.id} spot={self.spot_id}>"
|
||||
@@ -0,0 +1,27 @@
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import DateTime, ForeignKey, Integer, String, Text, func
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from app.db.base import Base
|
||||
|
||||
|
||||
class Correction(Base):
|
||||
__tablename__ = "corrections"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||
spot_id: Mapped[int] = mapped_column(Integer, ForeignKey("spots.id"), nullable=False)
|
||||
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), nullable=False)
|
||||
field_name: Mapped[str] = mapped_column(String(50), nullable=False)
|
||||
suggested_value: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
reason: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
status: Mapped[str] = mapped_column(String(20), default="pending")
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), server_default=func.now()
|
||||
)
|
||||
|
||||
user = relationship("User", lazy="joined")
|
||||
spot = relationship("Spot", lazy="joined")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Correction {self.id} spot={self.spot_id} field={self.field_name}>"
|
||||
@@ -0,0 +1,74 @@
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import (
|
||||
Boolean, DateTime, ForeignKey, Integer,
|
||||
String, Text, func,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from app.db.base import Base
|
||||
|
||||
|
||||
class Event(Base):
|
||||
__tablename__ = "events"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||
creator_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), nullable=False, index=True)
|
||||
title: Mapped[str] = mapped_column(String(200), nullable=False)
|
||||
city: Mapped[str] = mapped_column(String(100), nullable=False, index=True)
|
||||
description: Mapped[str | None] = mapped_column(Text)
|
||||
cover_url: Mapped[str | None] = mapped_column(String(500))
|
||||
location_name: Mapped[str | None] = mapped_column(String(200))
|
||||
start_time: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
||||
end_time: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
||||
max_participants: Mapped[int] = mapped_column(Integer, default=0)
|
||||
spot_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("spots.id"), nullable=True)
|
||||
status: Mapped[str] = mapped_column(String(20), default="upcoming", index=True)
|
||||
audit_status: Mapped[str] = mapped_column(String(20), default="pending")
|
||||
reject_reason: Mapped[str | None] = mapped_column(String(500))
|
||||
registration_count: Mapped[int] = mapped_column(Integer, default=0)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
||||
|
||||
creator = relationship("User", lazy="selectin")
|
||||
spot = relationship("Spot", lazy="selectin")
|
||||
registrations = relationship("EventRegistration", back_populates="event", lazy="selectin")
|
||||
photos = relationship("EventPhoto", back_populates="event", lazy="selectin")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Event {self.id} {self.title}>"
|
||||
|
||||
|
||||
class EventRegistration(Base):
|
||||
__tablename__ = "event_registrations"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||
event_id: Mapped[int] = mapped_column(Integer, ForeignKey("events.id"), nullable=False, index=True)
|
||||
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), nullable=False, index=True)
|
||||
status: Mapped[str] = mapped_column(String(20), default="registered")
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
event = relationship("Event", back_populates="registrations")
|
||||
user = relationship("User", lazy="selectin")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<EventRegistration {self.id}>"
|
||||
|
||||
|
||||
class EventPhoto(Base):
|
||||
__tablename__ = "event_photos"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||
event_id: Mapped[int] = mapped_column(Integer, ForeignKey("events.id"), nullable=False, index=True)
|
||||
uploader_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), nullable=False)
|
||||
image_url: Mapped[str] = mapped_column(String(500), nullable=False)
|
||||
caption: Mapped[str | None] = mapped_column(String(200))
|
||||
spot_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("spots.id"), nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
event = relationship("Event", back_populates="photos")
|
||||
uploader = relationship("User", lazy="selectin")
|
||||
spot = relationship("Spot", lazy="selectin")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<EventPhoto {self.id}>"
|
||||
@@ -0,0 +1,28 @@
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import DateTime, ForeignKey, Integer, UniqueConstraint, func
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from app.db.base import Base
|
||||
|
||||
|
||||
class Favorite(Base):
|
||||
__tablename__ = "favorites"
|
||||
__table_args__ = (UniqueConstraint("user_id", "spot_id", name="uq_user_spot"),)
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||
user_id: Mapped[int] = mapped_column(
|
||||
Integer, ForeignKey("users.id"), nullable=False
|
||||
)
|
||||
spot_id: Mapped[int] = mapped_column(
|
||||
Integer, ForeignKey("spots.id"), nullable=False
|
||||
)
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), server_default=func.now()
|
||||
)
|
||||
|
||||
user = relationship("User", lazy="selectin")
|
||||
spot = relationship("Spot", lazy="selectin")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Favorite user={self.user_id} spot={self.spot_id}>"
|
||||
@@ -0,0 +1,48 @@
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import (
|
||||
Boolean, DateTime, ForeignKey, Integer, Numeric,
|
||||
String, Text, func,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from app.db.base import Base
|
||||
|
||||
|
||||
class MembershipPlan(Base):
|
||||
"""会员方案"""
|
||||
__tablename__ = "membership_plans"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||
name: Mapped[str] = mapped_column(String(100), nullable=False)
|
||||
description: Mapped[str | None] = mapped_column(Text)
|
||||
duration_days: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
price: Mapped[float] = mapped_column(Numeric(10, 2), nullable=False)
|
||||
benefits: Mapped[str | None] = mapped_column(Text)
|
||||
extra_uploads: Mapped[int] = mapped_column(Integer, default=0)
|
||||
extra_top_count: Mapped[int] = mapped_column(Integer, default=0)
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
|
||||
sort_order: Mapped[int] = mapped_column(Integer, default=0)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<MembershipPlan {self.id} {self.name}>"
|
||||
|
||||
|
||||
class UserMembership(Base):
|
||||
"""用户会员记录"""
|
||||
__tablename__ = "user_memberships"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), nullable=False, index=True)
|
||||
plan_id: Mapped[int] = mapped_column(Integer, ForeignKey("membership_plans.id"), nullable=False)
|
||||
start_date: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
||||
end_date: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
user = relationship("User", lazy="selectin")
|
||||
plan = relationship("MembershipPlan", lazy="selectin")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<UserMembership {self.id} user={self.user_id}>"
|
||||
@@ -0,0 +1,25 @@
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import Boolean, DateTime, ForeignKey, Integer, String, Text, func
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from app.db.base import Base
|
||||
|
||||
|
||||
class Notification(Base):
|
||||
__tablename__ = "notifications"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), nullable=False, index=True)
|
||||
type: Mapped[str] = mapped_column(String(50), nullable=False, index=True)
|
||||
title: Mapped[str] = mapped_column(String(200), nullable=False)
|
||||
content: Mapped[str | None] = mapped_column(Text)
|
||||
ref_type: Mapped[str | None] = mapped_column(String(50))
|
||||
ref_id: Mapped[int | None] = mapped_column(Integer)
|
||||
is_read: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
user = relationship("User", lazy="selectin")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Notification {self.id} user={self.user_id} type={self.type}>"
|
||||
@@ -0,0 +1,28 @@
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import DateTime, ForeignKey, Integer, String, func
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from app.db.base import Base
|
||||
|
||||
|
||||
class PointLedger(Base):
|
||||
__tablename__ = "point_ledger"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||
user_id: Mapped[int] = mapped_column(
|
||||
Integer, ForeignKey("users.id"), nullable=False
|
||||
)
|
||||
change: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
balance: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
reason: Mapped[str] = mapped_column(String(200), nullable=False)
|
||||
ref_type: Mapped[str | None] = mapped_column(String(50))
|
||||
ref_id: Mapped[int | None] = mapped_column(Integer)
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), server_default=func.now()
|
||||
)
|
||||
|
||||
user = relationship("User", lazy="selectin")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<PointLedger {self.id} user={self.user_id} change={self.change}>"
|
||||
@@ -0,0 +1,38 @@
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import (
|
||||
DateTime, ForeignKey, Integer, String, Text, func,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from app.db.base import Base
|
||||
|
||||
|
||||
class Promotion(Base):
|
||||
"""推广位/Banner"""
|
||||
__tablename__ = "promotions"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||
title: Mapped[str] = mapped_column(String(200), nullable=False)
|
||||
image_url: Mapped[str] = mapped_column(String(500), nullable=False)
|
||||
link_type: Mapped[str] = mapped_column(String(20), default="spot")
|
||||
link_id: Mapped[int | None] = mapped_column(Integer)
|
||||
spot_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("spots.id"), nullable=True)
|
||||
event_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("events.id"), nullable=True)
|
||||
shooting_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("shooting_requests.id"), nullable=True)
|
||||
link_url: Mapped[str | None] = mapped_column(String(500))
|
||||
position: Mapped[str] = mapped_column(String(30), default="home_banner", index=True)
|
||||
sort_order: Mapped[int] = mapped_column(Integer, default=0)
|
||||
start_time: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
||||
end_time: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
||||
is_active: Mapped[bool] = mapped_column(default=True, index=True)
|
||||
impressions: Mapped[int] = mapped_column(Integer, default=0)
|
||||
clicks: Mapped[int] = mapped_column(Integer, default=0)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
spot = relationship("Spot", lazy="selectin")
|
||||
event = relationship("Event", lazy="selectin")
|
||||
shooting = relationship("ShootingRequest", lazy="selectin")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Promotion {self.id} {self.title}>"
|
||||
@@ -0,0 +1,23 @@
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import DateTime, ForeignKey, Integer, String, UniqueConstraint, func
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from app.db.base import Base
|
||||
|
||||
|
||||
class Rating(Base):
|
||||
__tablename__ = "ratings"
|
||||
__table_args__ = (UniqueConstraint("user_id", "spot_id", name="uq_user_spot_rating"),)
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||
spot_id: Mapped[int] = mapped_column(Integer, ForeignKey("spots.id"), nullable=False, index=True)
|
||||
user_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), nullable=False)
|
||||
score: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
short_comment: Mapped[str | None] = mapped_column(String(200))
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
user = relationship("User", lazy="selectin")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Rating {self.id} spot={self.spot_id} score={self.score}>"
|
||||
@@ -0,0 +1,27 @@
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import DateTime, ForeignKey, Integer, String, Text, func
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from app.db.base import Base
|
||||
|
||||
|
||||
class Report(Base):
|
||||
__tablename__ = "reports"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||
reporter_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), nullable=False)
|
||||
target_type: Mapped[str] = mapped_column(String(50), nullable=False, index=True)
|
||||
target_id: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
reason: Mapped[str] = mapped_column(Text, nullable=False)
|
||||
status: Mapped[str] = mapped_column(String(20), default="pending")
|
||||
handler_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("users.id"), nullable=True)
|
||||
conclusion: Mapped[str | None] = mapped_column(Text)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
resolved_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
||||
|
||||
reporter = relationship("User", foreign_keys=[reporter_id], lazy="selectin")
|
||||
handler = relationship("User", foreign_keys=[handler_id], lazy="selectin")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Report {self.id} {self.target_type}:{self.target_id}>"
|
||||
@@ -0,0 +1,57 @@
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import (
|
||||
Boolean, DateTime, Float, ForeignKey, Integer, Numeric,
|
||||
String, Text, func,
|
||||
)
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from app.db.base import Base
|
||||
|
||||
|
||||
class ShootingRequest(Base):
|
||||
__tablename__ = "shooting_requests"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||
creator_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), nullable=False, index=True)
|
||||
title: Mapped[str] = mapped_column(String(200), nullable=False)
|
||||
city: Mapped[str] = mapped_column(String(100), nullable=False, index=True)
|
||||
description: Mapped[str | None] = mapped_column(Text)
|
||||
style: Mapped[str | None] = mapped_column(String(100))
|
||||
shoot_date: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
||||
is_free: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
budget_min: Mapped[float | None] = mapped_column(Numeric(10, 2))
|
||||
budget_max: Mapped[float | None] = mapped_column(Numeric(10, 2))
|
||||
role_needed: Mapped[str] = mapped_column(String(20), default="photographer")
|
||||
max_applicants: Mapped[int] = mapped_column(Integer, default=1)
|
||||
contact_info: Mapped[str | None] = mapped_column(String(200))
|
||||
spot_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("spots.id"), nullable=True)
|
||||
status: Mapped[str] = mapped_column(String(20), default="open", index=True)
|
||||
audit_status: Mapped[str] = mapped_column(String(20), default="pending")
|
||||
reject_reason: Mapped[str | None] = mapped_column(String(500))
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
||||
|
||||
creator = relationship("User", lazy="selectin")
|
||||
spot = relationship("Spot", lazy="selectin")
|
||||
applications = relationship("ShootingApplication", back_populates="request", lazy="selectin")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<ShootingRequest {self.id} {self.title}>"
|
||||
|
||||
|
||||
class ShootingApplication(Base):
|
||||
__tablename__ = "shooting_applications"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||
request_id: Mapped[int] = mapped_column(Integer, ForeignKey("shooting_requests.id"), nullable=False, index=True)
|
||||
applicant_id: Mapped[int] = mapped_column(Integer, ForeignKey("users.id"), nullable=False, index=True)
|
||||
message: Mapped[str | None] = mapped_column(Text)
|
||||
status: Mapped[str] = mapped_column(String(20), default="pending")
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
request = relationship("ShootingRequest", back_populates="applications")
|
||||
applicant = relationship("User", lazy="selectin")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<ShootingApplication {self.id} req={self.request_id}>"
|
||||
@@ -0,0 +1,88 @@
|
||||
from datetime import datetime
|
||||
|
||||
from geoalchemy2 import Geometry
|
||||
from sqlalchemy import Boolean, DateTime, Float, ForeignKey, Integer, Numeric, String, Text, func
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from app.db.base import Base
|
||||
|
||||
|
||||
class Spot(Base):
|
||||
__tablename__ = "spots"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||
title: Mapped[str] = mapped_column(String(200), nullable=False, index=True)
|
||||
city: Mapped[str] = mapped_column(String(100), nullable=False, index=True)
|
||||
location: Mapped[str] = mapped_column(
|
||||
Geometry("POINT", srid=4326), nullable=False
|
||||
)
|
||||
description: Mapped[str | None] = mapped_column(Text)
|
||||
transport: Mapped[str | None] = mapped_column(Text)
|
||||
best_time: Mapped[str | None] = mapped_column(String(200))
|
||||
difficulty: Mapped[str | None] = mapped_column(Text)
|
||||
is_free: Mapped[bool] = mapped_column(Boolean, default=True)
|
||||
price_min: Mapped[float | None] = mapped_column(Numeric(10, 2), nullable=True)
|
||||
price_max: Mapped[float | None] = mapped_column(Numeric(10, 2), nullable=True)
|
||||
audit_status: Mapped[str] = mapped_column(String(20), default="pending")
|
||||
reject_reason: Mapped[str | None] = mapped_column(String(500))
|
||||
avg_rating: Mapped[float | None] = mapped_column(Float, default=None)
|
||||
rating_count: Mapped[int] = mapped_column(Integer, default=0)
|
||||
favorite_count: Mapped[int] = mapped_column(Integer, default=0)
|
||||
creator_id: Mapped[int] = mapped_column(
|
||||
Integer, ForeignKey("users.id"), nullable=False
|
||||
)
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), server_default=func.now()
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
|
||||
)
|
||||
|
||||
creator = relationship("User", lazy="selectin")
|
||||
images = relationship(
|
||||
"SpotImage", back_populates="spot", lazy="selectin", order_by="SpotImage.sort_order"
|
||||
)
|
||||
tags = relationship("Tag", secondary="spot_tags", lazy="selectin")
|
||||
|
||||
@property
|
||||
def longitude(self) -> float | None:
|
||||
if self.location is None:
|
||||
return None
|
||||
# WKBElement → use ST_X via a query; for serialisation we parse the WKT
|
||||
from geoalchemy2.shape import to_shape
|
||||
|
||||
point = to_shape(self.location)
|
||||
return point.x
|
||||
|
||||
@property
|
||||
def latitude(self) -> float | None:
|
||||
if self.location is None:
|
||||
return None
|
||||
from geoalchemy2.shape import to_shape
|
||||
|
||||
point = to_shape(self.location)
|
||||
return point.y
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Spot {self.id} {self.title}>"
|
||||
|
||||
|
||||
class SpotImage(Base):
|
||||
__tablename__ = "spot_images"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||
spot_id: Mapped[int] = mapped_column(
|
||||
Integer, ForeignKey("spots.id"), nullable=False
|
||||
)
|
||||
image_url: Mapped[str] = mapped_column(String(500), nullable=False)
|
||||
is_cover: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
audit_status: Mapped[str] = mapped_column(String(20), default="pending")
|
||||
sort_order: Mapped[int] = mapped_column(Integer, default=0)
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), server_default=func.now()
|
||||
)
|
||||
|
||||
spot = relationship("Spot", back_populates="images")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<SpotImage {self.id} spot={self.spot_id}>"
|
||||
@@ -0,0 +1,25 @@
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import Boolean, DateTime, Integer, String, Text, func
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.db.base import Base
|
||||
|
||||
|
||||
class SystemConfig(Base):
|
||||
__tablename__ = "system_configs"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||
config_key: Mapped[str] = mapped_column(String(100), nullable=False, unique=True, index=True)
|
||||
category: Mapped[str] = mapped_column(String(50), nullable=False, index=True)
|
||||
title: Mapped[str] = mapped_column(String(200), nullable=False)
|
||||
config_json: Mapped[str] = mapped_column(Text, nullable=False, default="{}")
|
||||
description: Mapped[str | None] = mapped_column(Text)
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, default=True, index=True)
|
||||
sort_order: Mapped[int] = mapped_column(Integer, default=0)
|
||||
updated_by: Mapped[int | None] = mapped_column(Integer)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<SystemConfig {self.config_key}>"
|
||||
@@ -0,0 +1,30 @@
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import Boolean, DateTime, ForeignKey, Integer, String, UniqueConstraint, func
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.db.base import Base
|
||||
|
||||
|
||||
class Tag(Base):
|
||||
__tablename__ = "tags"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||
name: Mapped[str] = mapped_column(String(50), unique=True, nullable=False)
|
||||
category: Mapped[str | None] = mapped_column(String(50))
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
|
||||
usage_count: Mapped[int] = mapped_column(Integer, default=0)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Tag {self.id} {self.name}>"
|
||||
|
||||
|
||||
class SpotTag(Base):
|
||||
__tablename__ = "spot_tags"
|
||||
__table_args__ = (UniqueConstraint("spot_id", "tag_id", name="uq_spot_tag"),)
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||
spot_id: Mapped[int] = mapped_column(Integer, ForeignKey("spots.id"), nullable=False)
|
||||
tag_id: Mapped[int] = mapped_column(Integer, ForeignKey("tags.id"), nullable=False)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
|
||||
@@ -0,0 +1,31 @@
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import Boolean, DateTime, String, Text, func
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from app.db.base import Base
|
||||
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = "users"
|
||||
|
||||
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
|
||||
phone: Mapped[str | None] = mapped_column(String(20), unique=True)
|
||||
email: Mapped[str | None] = mapped_column(String(255), unique=True)
|
||||
password_hash: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
nickname: Mapped[str] = mapped_column(String(50), nullable=False)
|
||||
avatar_url: Mapped[str | None] = mapped_column(String(500))
|
||||
city: Mapped[str | None] = mapped_column(String(100))
|
||||
bio: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
identity: Mapped[str] = mapped_column(String(20), default="both")
|
||||
role: Mapped[str] = mapped_column(String(20), default="user")
|
||||
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), server_default=func.now()
|
||||
)
|
||||
updated_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<User {self.id} {self.nickname}>"
|
||||
Reference in New Issue
Block a user