Skip to content

Commit 9cbbfd9

Browse files
committed
✨ Trip multi-users - BETA
1 parent cc72972 commit 9cbbfd9

File tree

13 files changed

+780
-109
lines changed

13 files changed

+780
-109
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
"""Trip multi-users
2+
3+
Revision ID: 26c89b7466f2
4+
Revises: 60a9bb641d8a
5+
Create Date: 2025-08-18 23:19:37.457354
6+
7+
"""
8+
9+
import sqlalchemy as sa
10+
import sqlmodel.sql.sqltypes
11+
from alembic import op
12+
13+
# revision identifiers, used by Alembic.
14+
revision = "26c89b7466f2"
15+
down_revision = "60a9bb641d8a"
16+
branch_labels = None
17+
depends_on = None
18+
19+
20+
def upgrade():
21+
op.create_table(
22+
"tripmember",
23+
sa.Column("id", sa.Integer(), nullable=False),
24+
sa.Column("user", sqlmodel.sql.sqltypes.AutoString(), nullable=False),
25+
sa.Column("invited_by", sqlmodel.sql.sqltypes.AutoString(), nullable=True),
26+
sa.Column("invited_at", sa.DateTime(), nullable=False),
27+
sa.Column("joined_at", sa.DateTime(), nullable=True),
28+
sa.Column("trip_id", sa.Integer(), nullable=False),
29+
sa.ForeignKeyConstraint(
30+
["invited_by"],
31+
["user.username"],
32+
name=op.f("fk_tripmember_invited_by_user"),
33+
ondelete="SET NULL",
34+
),
35+
sa.ForeignKeyConstraint(
36+
["trip_id"], ["trip.id"], name=op.f("fk_tripmember_trip_id_trip"), ondelete="CASCADE"
37+
),
38+
sa.ForeignKeyConstraint(
39+
["user"], ["user.username"], name=op.f("fk_tripmember_user_user"), ondelete="CASCADE"
40+
),
41+
sa.PrimaryKeyConstraint("id", name=op.f("pk_tripmember")),
42+
)
43+
with op.batch_alter_table("tripchecklistitem", schema=None) as batch_op:
44+
batch_op.drop_constraint(batch_op.f("fk_tripchecklistitem_user_user"), type_="foreignkey")
45+
batch_op.drop_column("user")
46+
47+
with op.batch_alter_table("trippackinglistitem", schema=None) as batch_op:
48+
batch_op.drop_constraint(batch_op.f("fk_trippackinglistitem_user_user"), type_="foreignkey")
49+
batch_op.drop_column("user")
50+
51+
with op.batch_alter_table("tripitem", schema=None) as batch_op:
52+
batch_op.drop_constraint(batch_op.f("fk_tripitem_day_id_tripday"), type_="foreignkey")
53+
54+
with op.batch_alter_table("tripday", schema=None) as batch_op:
55+
batch_op.drop_constraint(batch_op.f("fk_tripday_user_user"), type_="foreignkey")
56+
batch_op.drop_column("user")
57+
58+
with op.batch_alter_table("tripitem", schema=None) as batch_op:
59+
batch_op.create_foreign_key(
60+
batch_op.f("fk_tripitem_day_id_tripday"),
61+
"tripday",
62+
["day_id"],
63+
["id"],
64+
ondelete="CASCADE",
65+
)
66+
67+
68+
def downgrade():
69+
with op.batch_alter_table("trippackinglistitem", schema=None) as batch_op:
70+
batch_op.add_column(sa.Column("user", sa.VARCHAR(), nullable=False))
71+
batch_op.create_foreign_key(
72+
batch_op.f("fk_trippackinglistitem_user_user"),
73+
"user",
74+
["user"],
75+
["username"],
76+
ondelete="CASCADE",
77+
)
78+
79+
with op.batch_alter_table("tripday", schema=None) as batch_op:
80+
batch_op.add_column(sa.Column("user", sa.VARCHAR(), nullable=False))
81+
batch_op.create_foreign_key(
82+
batch_op.f("fk_tripday_user_user"), "user", ["user"], ["username"], ondelete="CASCADE"
83+
)
84+
85+
with op.batch_alter_table("tripchecklistitem", schema=None) as batch_op:
86+
batch_op.add_column(sa.Column("user", sa.VARCHAR(), nullable=False))
87+
batch_op.create_foreign_key(
88+
batch_op.f("fk_tripchecklistitem_user_user"), "user", ["user"], ["username"], ondelete="CASCADE"
89+
)
90+
91+
op.drop_table("tripmember")

backend/trip/models/models.py

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ class Trip(TripBase, table=True):
261261
shares: list["TripShare"] = Relationship(back_populates="trip", cascade_delete=True)
262262
packing_items: list["TripPackingListItem"] = Relationship(back_populates="trip", cascade_delete=True)
263263
checklist_items: list["TripChecklistItem"] = Relationship(back_populates="trip", cascade_delete=True)
264+
memberships: list["TripMember"] = Relationship(back_populates="trip", cascade_delete=True)
264265

265266

266267
class TripCreate(TripBase):
@@ -279,6 +280,7 @@ class TripReadBase(TripBase):
279280
image: str | None
280281
image_id: int | None
281282
days: int
283+
collaborators: list["TripMemberRead"]
282284

283285
@classmethod
284286
def serialize(cls, obj: Trip) -> "TripRead":
@@ -289,6 +291,7 @@ def serialize(cls, obj: Trip) -> "TripRead":
289291
image=_prefix_assets_url(obj.image.filename) if obj.image else None,
290292
image_id=obj.image_id,
291293
days=len(obj.days),
294+
collaborators=[TripMemberRead.serialize(m) for m in obj.memberships],
292295
)
293296

294297

@@ -298,6 +301,7 @@ class TripRead(TripBase):
298301
image_id: int | None
299302
days: list["TripDayRead"]
300303
places: list["PlaceRead"]
304+
collaborators: list["TripMemberRead"]
301305

302306
@classmethod
303307
def serialize(cls, obj: Trip) -> "TripRead":
@@ -309,17 +313,49 @@ def serialize(cls, obj: Trip) -> "TripRead":
309313
image_id=obj.image_id,
310314
days=[TripDayRead.serialize(day) for day in obj.days],
311315
places=[PlaceRead.serialize(place) for place in obj.places],
316+
collaborators=[TripMemberRead.serialize(m) for m in obj.memberships],
312317
)
313318

314319

320+
class TripMember(SQLModel, table=True):
321+
id: int | None = Field(default=None, primary_key=True)
322+
user: str = Field(foreign_key="user.username", ondelete="CASCADE")
323+
invited_by: str | None = Field(default=None, foreign_key="user.username", ondelete="SET NULL")
324+
invited_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
325+
joined_at: datetime | None = None
326+
327+
trip_id: int = Field(foreign_key="trip.id", ondelete="CASCADE")
328+
trip: Trip | None = Relationship(back_populates="memberships")
329+
330+
331+
class TripMemberCreate(BaseModel):
332+
user: str
333+
334+
335+
class TripMemberRead(BaseModel):
336+
user: str
337+
invited_by: str | None = None
338+
invited_at: datetime | None = None
339+
joined_at: datetime | None = None
340+
341+
@classmethod
342+
def serialize(cls, obj: TripMember) -> "TripMemberRead":
343+
return cls(
344+
user=obj.user, invited_by=obj.invited_by, invited_at=obj.invited_at, joined_at=obj.joined_at
345+
)
346+
347+
348+
class TripInvitationRead(TripReadBase):
349+
invited_by: str | None = None
350+
invited_at: datetime
351+
352+
315353
class TripDayBase(SQLModel):
316354
label: str
317355

318356

319357
class TripDay(TripDayBase, table=True):
320358
id: int | None = Field(default=None, primary_key=True)
321-
user: str = Field(foreign_key="user.username", ondelete="CASCADE")
322-
323359
trip_id: int = Field(foreign_key="trip.id", ondelete="CASCADE")
324360
trip: Trip | None = Relationship(back_populates="days")
325361

@@ -420,7 +456,6 @@ class TripPackingListItemBase(SQLModel):
420456

421457
class TripPackingListItem(TripPackingListItemBase, table=True):
422458
id: int | None = Field(default=None, primary_key=True)
423-
user: str = Field(foreign_key="user.username", ondelete="CASCADE")
424459

425460
trip_id: int = Field(foreign_key="trip.id", ondelete="CASCADE")
426461
trip: Trip | None = Relationship(back_populates="packing_items")

0 commit comments

Comments
 (0)