From 3dd99d8dd21b80f619a3ee4bfd52bea404f717d6 Mon Sep 17 00:00:00 2001 From: Ruben Alba Gonzalez <39350323+Ruben-Alba-Gonzalez@users.noreply.github.com> Date: Mon, 6 Apr 2026 17:43:02 +0000 Subject: [PATCH 1/6] GDV-2: Added model User in the backend --- .../{0763d677d453_.py => 1a8004bec489_.py} | 9 +- package-lock.json | 155 ------------------ src/api/models.py | 7 +- 3 files changed, 10 insertions(+), 161 deletions(-) rename migrations/versions/{0763d677d453_.py => 1a8004bec489_.py} (76%) diff --git a/migrations/versions/0763d677d453_.py b/migrations/versions/1a8004bec489_.py similarity index 76% rename from migrations/versions/0763d677d453_.py rename to migrations/versions/1a8004bec489_.py index 88964176f1..8b86ff3d8a 100644 --- a/migrations/versions/0763d677d453_.py +++ b/migrations/versions/1a8004bec489_.py @@ -1,8 +1,8 @@ """empty message -Revision ID: 0763d677d453 +Revision ID: 1a8004bec489 Revises: -Create Date: 2025-02-25 14:47:16.337069 +Create Date: 2026-04-06 17:41:33.021558 """ from alembic import op @@ -10,7 +10,7 @@ # revision identifiers, used by Alembic. -revision = '0763d677d453' +revision = '1a8004bec489' down_revision = None branch_labels = None depends_on = None @@ -20,9 +20,10 @@ def upgrade(): # ### commands auto generated by Alembic - please adjust! ### op.create_table('user', sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=20), nullable=False), + sa.Column('last_name', sa.String(length=50), nullable=False), sa.Column('email', sa.String(length=120), nullable=False), sa.Column('password', sa.String(), nullable=False), - sa.Column('is_active', sa.Boolean(), nullable=False), sa.PrimaryKeyConstraint('id'), sa.UniqueConstraint('email') ) diff --git a/package-lock.json b/package-lock.json index 8d43d98ab7..398b3c017c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -877,19 +877,6 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", @@ -997,14 +984,6 @@ "@babel/types": "^7.20.7" } }, - "node_modules/@types/node": { - "version": "16.11.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.12.tgz", - "integrity": "sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/@types/prop-types": { "version": "15.7.14", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", @@ -1307,14 +1286,6 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -3915,29 +3886,6 @@ "node": ">=0.10.0" } }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-support/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/string.prototype.matchall": { "version": "4.0.12", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", @@ -4074,35 +4022,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/terser": { - "version": "5.38.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.38.1.tgz", - "integrity": "sha512-GWANVlPM/ZfYzuPHjq0nxT+EbOEDDN3Jwhwdg1D8TU8oSkktp8w64Uq4auuGLxFSoNTRDncTq2hQHX1Ld9KHkA==", - "dev": true, - "license": "BSD-2-Clause", - "optional": true, - "peer": true, - "dependencies": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "optional": true, - "peer": true - }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -4944,18 +4863,6 @@ "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", "dev": true }, - "@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25" - } - }, "@jridgewell/sourcemap-codec": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", @@ -5044,14 +4951,6 @@ "@babel/types": "^7.20.7" } }, - "@types/node": { - "version": "16.11.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.12.tgz", - "integrity": "sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw==", - "dev": true, - "optional": true, - "peer": true - }, "@types/prop-types": { "version": "15.7.14", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz", @@ -5252,14 +5151,6 @@ "update-browserslist-db": "^1.1.1" } }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, - "optional": true, - "peer": true - }, "call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -6982,28 +6873,6 @@ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true, - "peer": true - } - } - }, "string.prototype.matchall": { "version": "4.0.12", "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", @@ -7094,30 +6963,6 @@ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true }, - "terser": { - "version": "5.38.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.38.1.tgz", - "integrity": "sha512-GWANVlPM/ZfYzuPHjq0nxT+EbOEDDN3Jwhwdg1D8TU8oSkktp8w64Uq4auuGLxFSoNTRDncTq2hQHX1Ld9KHkA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "optional": true, - "peer": true - } - } - }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", diff --git a/src/api/models.py b/src/api/models.py index da515f6a1a..96f1c354eb 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -6,14 +6,17 @@ class User(db.Model): id: Mapped[int] = mapped_column(primary_key=True) + name: Mapped[str] = mapped_column(String(20), nullable=False) + last_name: Mapped[str] = mapped_column(String(50), nullable=False) email: Mapped[str] = mapped_column(String(120), unique=True, nullable=False) password: Mapped[str] = mapped_column(nullable=False) - is_active: Mapped[bool] = mapped_column(Boolean(), nullable=False) + def serialize(self): return { "id": self.id, + "name": self.name, + "last_name": self.last_name, "email": self.email, - # do not serialize the password, its a security breach } \ No newline at end of file From fbc0ab6cdcb111c614d064414955834a114e36ef Mon Sep 17 00:00:00 2001 From: Ruben Alba Gonzalez <39350323+Ruben-Alba-Gonzalez@users.noreply.github.com> Date: Mon, 6 Apr 2026 18:11:19 +0000 Subject: [PATCH 2/6] GDV-3: Added model Viajes in the backend --- migrations/versions/dd2187fa5765_.py | 38 ++++++++++++++++++++++++++++ src/api/models.py | 35 +++++++++++++++++++++++-- 2 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 migrations/versions/dd2187fa5765_.py diff --git a/migrations/versions/dd2187fa5765_.py b/migrations/versions/dd2187fa5765_.py new file mode 100644 index 0000000000..935774eaaf --- /dev/null +++ b/migrations/versions/dd2187fa5765_.py @@ -0,0 +1,38 @@ +"""empty message + +Revision ID: dd2187fa5765 +Revises: 1a8004bec489 +Create Date: 2026-04-06 18:09:50.559822 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'dd2187fa5765' +down_revision = '1a8004bec489' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('viaje', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('title', sa.String(length=30), nullable=False), + sa.Column('destination', sa.String(length=50), nullable=False), + sa.Column('state', sa.Enum('FINISHED', 'ONGOING', 'PLANNING', name='statetypes'), nullable=False), + sa.Column('starting_date', sa.Date(), nullable=False), + sa.Column('ending_date', sa.Date(), nullable=False), + sa.Column('budget', sa.Float(), nullable=False), + sa.Column('notes', sa.String(length=150), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('viaje') + # ### end Alembic commands ### diff --git a/src/api/models.py b/src/api/models.py index 96f1c354eb..1f3d472843 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -1,9 +1,16 @@ +import enum from flask_sqlalchemy import SQLAlchemy -from sqlalchemy import String, Boolean +from sqlalchemy import String, Boolean, Enum, Date, Integer, ForeignKey, Float from sqlalchemy.orm import Mapped, mapped_column +from datetime import date db = SQLAlchemy() +class StateTypes(enum.Enum): + FINISHED = "finished" + ONGOING = "ongoing" + PLANNING = "planning" + class User(db.Model): id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] = mapped_column(String(20), nullable=False) @@ -19,4 +26,28 @@ def serialize(self): "name": self.name, "last_name": self.last_name, "email": self.email, - } \ No newline at end of file + } + + +class Viaje(db.Model): + id: Mapped[int] = mapped_column(primary_key=True) + title: Mapped[str] = mapped_column(String(30), nullable=False) + destination: Mapped[str] = mapped_column(String(50), nullable=False) + state: Mapped[StateTypes] = mapped_column(Enum(StateTypes), nullable=False) + starting_date: Mapped[date] = mapped_column(Date(), nullable=False) + ending_date: Mapped[date] = mapped_column(Date(), nullable=False) + budget: Mapped[float] = mapped_column(Float, nullable=False) + notes: Mapped[str] = mapped_column(String(150), nullable=False) + + + def serialize(self): + return { + "id": self.id, + "title": self.title, + "destination": self.destination, + "state": self.state, + "starting_date": self.starting_date, + "ending_date": self.ending_date, + "budget": self.budget, + "notes": self.notes, + } From a69475998d84f620b684abd4f7d606e90b295e42 Mon Sep 17 00:00:00 2001 From: Ruben Alba Gonzalez <39350323+Ruben-Alba-Gonzalez@users.noreply.github.com> Date: Mon, 6 Apr 2026 19:10:26 +0000 Subject: [PATCH 3/6] GDV-4: Added model Viajero, updated models Viaje and User --- migrations/versions/f9b6109c894e_.py | 34 ++++++++++++++++++++++++++++ src/api/models.py | 21 +++++++++++++++-- 2 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 migrations/versions/f9b6109c894e_.py diff --git a/migrations/versions/f9b6109c894e_.py b/migrations/versions/f9b6109c894e_.py new file mode 100644 index 0000000000..7adc99119c --- /dev/null +++ b/migrations/versions/f9b6109c894e_.py @@ -0,0 +1,34 @@ +"""empty message + +Revision ID: f9b6109c894e +Revises: dd2187fa5765 +Create Date: 2026-04-06 19:09:26.923084 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'f9b6109c894e' +down_revision = 'dd2187fa5765' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('viajero', + sa.Column('id_user', sa.Integer(), nullable=False), + sa.Column('id_viaje', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['id_user'], ['user.id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['id_viaje'], ['viaje.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id_user', 'id_viaje') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('viajero') + # ### end Alembic commands ### diff --git a/src/api/models.py b/src/api/models.py index 1f3d472843..6af85b64d0 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -1,7 +1,7 @@ import enum from flask_sqlalchemy import SQLAlchemy from sqlalchemy import String, Boolean, Enum, Date, Integer, ForeignKey, Float -from sqlalchemy.orm import Mapped, mapped_column +from sqlalchemy.orm import Mapped, mapped_column, relationship from datetime import date db = SQLAlchemy() @@ -18,7 +18,7 @@ class User(db.Model): email: Mapped[str] = mapped_column(String(120), unique=True, nullable=False) password: Mapped[str] = mapped_column(nullable=False) - + viajeros = relationship("Viajero", back_populates="users") def serialize(self): return { @@ -39,6 +39,8 @@ class Viaje(db.Model): budget: Mapped[float] = mapped_column(Float, nullable=False) notes: Mapped[str] = mapped_column(String(150), nullable=False) + viajeros = relationship("Viajero", back_populates="viajes") + def serialize(self): return { @@ -51,3 +53,18 @@ def serialize(self): "budget": self.budget, "notes": self.notes, } + + +class Viajero(db.Model): + id_user: Mapped[int] = mapped_column(ForeignKey("user.id", ondelete="CASCADE"), primary_key=True) + id_viaje: Mapped[int] = mapped_column(ForeignKey("viaje.id", ondelete="CASCADE"), primary_key=True) + + + users = relationship("User", back_populates="viajeros") + viajes = relationship("Viaje", back_populates="viajeros") + + def serialize(self): + return { + "id_user": self.id_user, + "id_viaje": self.id_viaje, + } \ No newline at end of file From e3bea01bf6f038f727df6e0824f006bf33cace28 Mon Sep 17 00:00:00 2001 From: Ruben Alba Gonzalez <39350323+Ruben-Alba-Gonzalez@users.noreply.github.com> Date: Mon, 6 Apr 2026 19:23:23 +0000 Subject: [PATCH 4/6] GDV-5: Added model Itinerario, updated model Viaje, updated all models serialize --- migrations/versions/a3ae6e547e98_.py | 38 ++++++++++++++++++++++++++++ src/api/models.py | 38 +++++++++++++++++++++++----- 2 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 migrations/versions/a3ae6e547e98_.py diff --git a/migrations/versions/a3ae6e547e98_.py b/migrations/versions/a3ae6e547e98_.py new file mode 100644 index 0000000000..016bf1d28d --- /dev/null +++ b/migrations/versions/a3ae6e547e98_.py @@ -0,0 +1,38 @@ +"""empty message + +Revision ID: a3ae6e547e98 +Revises: f9b6109c894e +Create Date: 2026-04-06 19:22:10.304592 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'a3ae6e547e98' +down_revision = 'f9b6109c894e' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('itinerario', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('title', sa.String(length=30), nullable=False), + sa.Column('destination', sa.String(length=50), nullable=False), + sa.Column('hour', sa.Time(), nullable=False), + sa.Column('starting_date', sa.Date(), nullable=False), + sa.Column('notes', sa.String(length=150), nullable=False), + sa.Column('id_viaje', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['id_viaje'], ['viaje.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id', 'id_viaje') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('itinerario') + # ### end Alembic commands ### diff --git a/src/api/models.py b/src/api/models.py index 6af85b64d0..c7863223e9 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -1,8 +1,8 @@ import enum from flask_sqlalchemy import SQLAlchemy -from sqlalchemy import String, Boolean, Enum, Date, Integer, ForeignKey, Float +from sqlalchemy import String, Boolean, Enum, Date, Integer, ForeignKey, Float, Time from sqlalchemy.orm import Mapped, mapped_column, relationship -from datetime import date +from datetime import date, time db = SQLAlchemy() @@ -25,7 +25,7 @@ def serialize(self): "id": self.id, "name": self.name, "last_name": self.last_name, - "email": self.email, + "email": self.email } @@ -40,6 +40,7 @@ class Viaje(db.Model): notes: Mapped[str] = mapped_column(String(150), nullable=False) viajeros = relationship("Viajero", back_populates="viajes") + itinerarios = relationship("Itinerario", back_populates="viajes") def serialize(self): @@ -51,7 +52,7 @@ def serialize(self): "starting_date": self.starting_date, "ending_date": self.ending_date, "budget": self.budget, - "notes": self.notes, + "notes": self.notes } @@ -66,5 +67,30 @@ class Viajero(db.Model): def serialize(self): return { "id_user": self.id_user, - "id_viaje": self.id_viaje, - } \ No newline at end of file + "id_viaje": self.id_viaje + } + + +class Itinerario(db.Model): + id: Mapped[int] = mapped_column(primary_key=True) + title: Mapped[str] = mapped_column(String(30), nullable=False) + destination: Mapped[str] = mapped_column(String(50), nullable=False) + hour: Mapped[time] = mapped_column(Time, nullable=False) + starting_date: Mapped[date] = mapped_column(Date(), nullable=False) + notes: Mapped[str] = mapped_column(String(150), nullable=False) + id_viaje: Mapped[int] = mapped_column(ForeignKey("viaje.id", ondelete="CASCADE"), primary_key=True) + + + viajes = relationship("Viaje", back_populates="itinerarios") + + + def serialize(self): + return { + "id": self.id, + "title": self.title, + "destination": self.destination, + "hour": self.hour, + "starting_date": self.starting_date, + "notes": self.notes, + "id_viaje": self.id_viaje + } From 687d3287c0970bbb203fb908949503e845dfd0dd Mon Sep 17 00:00:00 2001 From: DLMARTINEZ <122575833+Estimatus@users.noreply.github.com> Date: Wed, 8 Apr 2026 16:44:29 +0000 Subject: [PATCH 5/6] GDV-7: Added model deudas, gastos, documento, chat, mensajes --- migrations/versions/52838e37ed17_.py | 76 ++++++++++++++++++++++ src/api/models.py | 95 +++++++++++++++++++++++----- 2 files changed, 154 insertions(+), 17 deletions(-) create mode 100644 migrations/versions/52838e37ed17_.py diff --git a/migrations/versions/52838e37ed17_.py b/migrations/versions/52838e37ed17_.py new file mode 100644 index 0000000000..84ea140bf6 --- /dev/null +++ b/migrations/versions/52838e37ed17_.py @@ -0,0 +1,76 @@ +"""empty message + +Revision ID: 52838e37ed17 +Revises: a3ae6e547e98 +Create Date: 2026-04-08 16:40:49.119717 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '52838e37ed17' +down_revision = 'a3ae6e547e98' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('chat', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('id_viaje', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['id_viaje'], ['viaje.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('documento', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('titulo', sa.String(length=50), nullable=False), + sa.Column('url', sa.String(length=250), nullable=False), + sa.Column('id_viaje', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['id_viaje'], ['viaje.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('gasto', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('monto', sa.Float(), nullable=False), + sa.Column('descripcion', sa.String(length=100), nullable=False), + sa.Column('id_viaje', sa.Integer(), nullable=False), + sa.Column('id_pagador', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['id_pagador'], ['user.id'], ), + sa.ForeignKeyConstraint(['id_viaje'], ['viaje.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('deuda', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('importe', sa.Float(), nullable=False), + sa.Column('id_deudor', sa.Integer(), nullable=False), + sa.Column('id_acreedor', sa.Integer(), nullable=False), + sa.Column('id_gasto', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['id_acreedor'], ['user.id'], ), + sa.ForeignKeyConstraint(['id_deudor'], ['user.id'], ), + sa.ForeignKeyConstraint(['id_gasto'], ['gasto.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('mensaje', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('contenido', sa.String(length=500), nullable=False), + sa.Column('fecha_hora', sa.DateTime(), nullable=False), + sa.Column('id_chat', sa.Integer(), nullable=False), + sa.Column('id_usuario', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['id_chat'], ['chat.id'], ondelete='CASCADE'), + sa.ForeignKeyConstraint(['id_usuario'], ['user.id'], ), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('mensaje') + op.drop_table('deuda') + op.drop_table('gasto') + op.drop_table('documento') + op.drop_table('chat') + # ### end Alembic commands ### diff --git a/src/api/models.py b/src/api/models.py index c7863223e9..6e5b79d508 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -1,24 +1,33 @@ import enum from flask_sqlalchemy import SQLAlchemy -from sqlalchemy import String, Boolean, Enum, Date, Integer, ForeignKey, Float, Time +from sqlalchemy import String, Boolean, Enum, Date, Integer, ForeignKey, Float, Time, DateTime from sqlalchemy.orm import Mapped, mapped_column, relationship -from datetime import date, time +from datetime import date, time, datetime db = SQLAlchemy() +# --- ENUMS --- class StateTypes(enum.Enum): FINISHED = "finished" ONGOING = "ongoing" PLANNING = "planning" +# --- MODELOS --- + class User(db.Model): + __tablename__ = 'user' id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] = mapped_column(String(20), nullable=False) last_name: Mapped[str] = mapped_column(String(50), nullable=False) email: Mapped[str] = mapped_column(String(120), unique=True, nullable=False) password: Mapped[str] = mapped_column(nullable=False) + # Relaciones viajeros = relationship("Viajero", back_populates="users") + gastos_pagados = relationship("Gasto", back_populates="pagador") + mensajes = relationship("Mensaje", back_populates="autor") + deudas_pendientes = relationship("Deuda", foreign_keys="[Deuda.id_deudor]", back_populates="deudor") + deudas_a_cobrar = relationship("Deuda", foreign_keys="[Deuda.id_acreedor]", back_populates="acreedor") def serialize(self): return { @@ -27,9 +36,9 @@ def serialize(self): "last_name": self.last_name, "email": self.email } - class Viaje(db.Model): + __tablename__ = 'viaje' id: Mapped[int] = mapped_column(primary_key=True) title: Mapped[str] = mapped_column(String(30), nullable=False) destination: Mapped[str] = mapped_column(String(50), nullable=False) @@ -39,28 +48,30 @@ class Viaje(db.Model): budget: Mapped[float] = mapped_column(Float, nullable=False) notes: Mapped[str] = mapped_column(String(150), nullable=False) + # Relaciones viajeros = relationship("Viajero", back_populates="viajes") itinerarios = relationship("Itinerario", back_populates="viajes") - + gastos = relationship("Gasto", back_populates="viajes") + documentos = relationship("Documento", back_populates="viajes") + chat = relationship("Chat", back_populates="viajes", uselist=False) def serialize(self): return { "id": self.id, "title": self.title, "destination": self.destination, - "state": self.state, - "starting_date": self.starting_date, - "ending_date": self.ending_date, + "state": self.state.value, + "starting_date": str(self.starting_date), + "ending_date": str(self.ending_date), "budget": self.budget, "notes": self.notes } - class Viajero(db.Model): + __tablename__ = 'viajero' id_user: Mapped[int] = mapped_column(ForeignKey("user.id", ondelete="CASCADE"), primary_key=True) id_viaje: Mapped[int] = mapped_column(ForeignKey("viaje.id", ondelete="CASCADE"), primary_key=True) - users = relationship("User", back_populates="viajeros") viajes = relationship("Viaje", back_populates="viajeros") @@ -69,28 +80,78 @@ def serialize(self): "id_user": self.id_user, "id_viaje": self.id_viaje } - class Itinerario(db.Model): + __tablename__ = 'itinerario' id: Mapped[int] = mapped_column(primary_key=True) title: Mapped[str] = mapped_column(String(30), nullable=False) destination: Mapped[str] = mapped_column(String(50), nullable=False) hour: Mapped[time] = mapped_column(Time, nullable=False) starting_date: Mapped[date] = mapped_column(Date(), nullable=False) notes: Mapped[str] = mapped_column(String(150), nullable=False) - id_viaje: Mapped[int] = mapped_column(ForeignKey("viaje.id", ondelete="CASCADE"), primary_key=True) - + id_viaje: Mapped[int] = mapped_column(ForeignKey("viaje.id", ondelete="CASCADE")) viajes = relationship("Viaje", back_populates="itinerarios") - def serialize(self): return { "id": self.id, "title": self.title, - "destination": self.destination, - "hour": self.hour, - "starting_date": self.starting_date, - "notes": self.notes, + "hour": str(self.hour), "id_viaje": self.id_viaje } + +class Gasto(db.Model): + __tablename__ = 'gasto' + id: Mapped[int] = mapped_column(primary_key=True) + monto: Mapped[float] = mapped_column(Float, nullable=False) + descripcion: Mapped[str] = mapped_column(String(100), nullable=False) + id_viaje: Mapped[int] = mapped_column(ForeignKey("viaje.id", ondelete="CASCADE")) + id_pagador: Mapped[int] = mapped_column(ForeignKey("user.id")) + + viajes = relationship("Viaje", back_populates="gastos") + pagador = relationship("User", back_populates="gastos_pagados") + deudas = relationship("Deuda", back_populates="gastos") + + def serialize(self): + return {"id": self.id, "monto": self.monto, "descripcion": self.descripcion} + +class Deuda(db.Model): + __tablename__ = 'deuda' + id: Mapped[int] = mapped_column(primary_key=True) + importe: Mapped[float] = mapped_column(Float, nullable=False) + id_deudor: Mapped[int] = mapped_column(ForeignKey("user.id")) + id_acreedor: Mapped[int] = mapped_column(ForeignKey("user.id")) + id_gasto: Mapped[int] = mapped_column(ForeignKey("gasto.id")) + + gastos = relationship("Gasto", back_populates="deudas") + deudor = relationship("User", foreign_keys=[id_deudor], back_populates="deudas_pendientes") + acreedor = relationship("User", foreign_keys=[id_acreedor], back_populates="deudas_a_cobrar") + +class Documento(db.Model): + __tablename__ = 'documento' + id: Mapped[int] = mapped_column(primary_key=True) + titulo: Mapped[str] = mapped_column(String(50), nullable=False) + url: Mapped[str] = mapped_column(String(250), nullable=False) + id_viaje: Mapped[int] = mapped_column(ForeignKey("viaje.id", ondelete="CASCADE")) + + viajes = relationship("Viaje", back_populates="documentos") + +class Chat(db.Model): + __tablename__ = 'chat' + id: Mapped[int] = mapped_column(primary_key=True) + id_viaje: Mapped[int] = mapped_column(ForeignKey("viaje.id", ondelete="CASCADE")) + + viajes = relationship("Viaje", back_populates="chat") + mensajes = relationship("Mensaje", back_populates="chat") + +class Mensaje(db.Model): + __tablename__ = 'mensaje' + id: Mapped[int] = mapped_column(primary_key=True) + contenido: Mapped[str] = mapped_column(String(500), nullable=False) + fecha_hora: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) + id_chat: Mapped[int] = mapped_column(ForeignKey("chat.id", ondelete="CASCADE")) + id_usuario: Mapped[int] = mapped_column(ForeignKey("user.id")) + + chat = relationship("Chat", back_populates="mensajes") + autor = relationship("User", back_populates="mensajes") \ No newline at end of file From c10e82bbf9c32c587bc16e136001fffe0103b005 Mon Sep 17 00:00:00 2001 From: DLMARTINEZ <122575833+Estimatus@users.noreply.github.com> Date: Wed, 8 Apr 2026 17:48:52 +0000 Subject: [PATCH 6/6] GDV-7: Translated models to English and added Chat title --- migrations/versions/1a8004bec489_.py | 36 -------- migrations/versions/52838e37ed17_.py | 76 ---------------- migrations/versions/a3ae6e547e98_.py | 38 -------- migrations/versions/dd2187fa5765_.py | 38 -------- migrations/versions/f9b6109c894e_.py | 34 ------- src/api/models.py | 127 ++++++++++++++------------- 6 files changed, 64 insertions(+), 285 deletions(-) delete mode 100644 migrations/versions/1a8004bec489_.py delete mode 100644 migrations/versions/52838e37ed17_.py delete mode 100644 migrations/versions/a3ae6e547e98_.py delete mode 100644 migrations/versions/dd2187fa5765_.py delete mode 100644 migrations/versions/f9b6109c894e_.py diff --git a/migrations/versions/1a8004bec489_.py b/migrations/versions/1a8004bec489_.py deleted file mode 100644 index 8b86ff3d8a..0000000000 --- a/migrations/versions/1a8004bec489_.py +++ /dev/null @@ -1,36 +0,0 @@ -"""empty message - -Revision ID: 1a8004bec489 -Revises: -Create Date: 2026-04-06 17:41:33.021558 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '1a8004bec489' -down_revision = None -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('user', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('name', sa.String(length=20), nullable=False), - sa.Column('last_name', sa.String(length=50), nullable=False), - sa.Column('email', sa.String(length=120), nullable=False), - sa.Column('password', sa.String(), nullable=False), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('email') - ) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('user') - # ### end Alembic commands ### diff --git a/migrations/versions/52838e37ed17_.py b/migrations/versions/52838e37ed17_.py deleted file mode 100644 index 84ea140bf6..0000000000 --- a/migrations/versions/52838e37ed17_.py +++ /dev/null @@ -1,76 +0,0 @@ -"""empty message - -Revision ID: 52838e37ed17 -Revises: a3ae6e547e98 -Create Date: 2026-04-08 16:40:49.119717 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '52838e37ed17' -down_revision = 'a3ae6e547e98' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('chat', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('id_viaje', sa.Integer(), nullable=False), - sa.ForeignKeyConstraint(['id_viaje'], ['viaje.id'], ondelete='CASCADE'), - sa.PrimaryKeyConstraint('id') - ) - op.create_table('documento', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('titulo', sa.String(length=50), nullable=False), - sa.Column('url', sa.String(length=250), nullable=False), - sa.Column('id_viaje', sa.Integer(), nullable=False), - sa.ForeignKeyConstraint(['id_viaje'], ['viaje.id'], ondelete='CASCADE'), - sa.PrimaryKeyConstraint('id') - ) - op.create_table('gasto', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('monto', sa.Float(), nullable=False), - sa.Column('descripcion', sa.String(length=100), nullable=False), - sa.Column('id_viaje', sa.Integer(), nullable=False), - sa.Column('id_pagador', sa.Integer(), nullable=False), - sa.ForeignKeyConstraint(['id_pagador'], ['user.id'], ), - sa.ForeignKeyConstraint(['id_viaje'], ['viaje.id'], ondelete='CASCADE'), - sa.PrimaryKeyConstraint('id') - ) - op.create_table('deuda', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('importe', sa.Float(), nullable=False), - sa.Column('id_deudor', sa.Integer(), nullable=False), - sa.Column('id_acreedor', sa.Integer(), nullable=False), - sa.Column('id_gasto', sa.Integer(), nullable=False), - sa.ForeignKeyConstraint(['id_acreedor'], ['user.id'], ), - sa.ForeignKeyConstraint(['id_deudor'], ['user.id'], ), - sa.ForeignKeyConstraint(['id_gasto'], ['gasto.id'], ), - sa.PrimaryKeyConstraint('id') - ) - op.create_table('mensaje', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('contenido', sa.String(length=500), nullable=False), - sa.Column('fecha_hora', sa.DateTime(), nullable=False), - sa.Column('id_chat', sa.Integer(), nullable=False), - sa.Column('id_usuario', sa.Integer(), nullable=False), - sa.ForeignKeyConstraint(['id_chat'], ['chat.id'], ondelete='CASCADE'), - sa.ForeignKeyConstraint(['id_usuario'], ['user.id'], ), - sa.PrimaryKeyConstraint('id') - ) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('mensaje') - op.drop_table('deuda') - op.drop_table('gasto') - op.drop_table('documento') - op.drop_table('chat') - # ### end Alembic commands ### diff --git a/migrations/versions/a3ae6e547e98_.py b/migrations/versions/a3ae6e547e98_.py deleted file mode 100644 index 016bf1d28d..0000000000 --- a/migrations/versions/a3ae6e547e98_.py +++ /dev/null @@ -1,38 +0,0 @@ -"""empty message - -Revision ID: a3ae6e547e98 -Revises: f9b6109c894e -Create Date: 2026-04-06 19:22:10.304592 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'a3ae6e547e98' -down_revision = 'f9b6109c894e' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('itinerario', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('title', sa.String(length=30), nullable=False), - sa.Column('destination', sa.String(length=50), nullable=False), - sa.Column('hour', sa.Time(), nullable=False), - sa.Column('starting_date', sa.Date(), nullable=False), - sa.Column('notes', sa.String(length=150), nullable=False), - sa.Column('id_viaje', sa.Integer(), nullable=False), - sa.ForeignKeyConstraint(['id_viaje'], ['viaje.id'], ondelete='CASCADE'), - sa.PrimaryKeyConstraint('id', 'id_viaje') - ) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('itinerario') - # ### end Alembic commands ### diff --git a/migrations/versions/dd2187fa5765_.py b/migrations/versions/dd2187fa5765_.py deleted file mode 100644 index 935774eaaf..0000000000 --- a/migrations/versions/dd2187fa5765_.py +++ /dev/null @@ -1,38 +0,0 @@ -"""empty message - -Revision ID: dd2187fa5765 -Revises: 1a8004bec489 -Create Date: 2026-04-06 18:09:50.559822 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'dd2187fa5765' -down_revision = '1a8004bec489' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('viaje', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('title', sa.String(length=30), nullable=False), - sa.Column('destination', sa.String(length=50), nullable=False), - sa.Column('state', sa.Enum('FINISHED', 'ONGOING', 'PLANNING', name='statetypes'), nullable=False), - sa.Column('starting_date', sa.Date(), nullable=False), - sa.Column('ending_date', sa.Date(), nullable=False), - sa.Column('budget', sa.Float(), nullable=False), - sa.Column('notes', sa.String(length=150), nullable=False), - sa.PrimaryKeyConstraint('id') - ) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('viaje') - # ### end Alembic commands ### diff --git a/migrations/versions/f9b6109c894e_.py b/migrations/versions/f9b6109c894e_.py deleted file mode 100644 index 7adc99119c..0000000000 --- a/migrations/versions/f9b6109c894e_.py +++ /dev/null @@ -1,34 +0,0 @@ -"""empty message - -Revision ID: f9b6109c894e -Revises: dd2187fa5765 -Create Date: 2026-04-06 19:09:26.923084 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'f9b6109c894e' -down_revision = 'dd2187fa5765' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('viajero', - sa.Column('id_user', sa.Integer(), nullable=False), - sa.Column('id_viaje', sa.Integer(), nullable=False), - sa.ForeignKeyConstraint(['id_user'], ['user.id'], ondelete='CASCADE'), - sa.ForeignKeyConstraint(['id_viaje'], ['viaje.id'], ondelete='CASCADE'), - sa.PrimaryKeyConstraint('id_user', 'id_viaje') - ) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('viajero') - # ### end Alembic commands ### diff --git a/src/api/models.py b/src/api/models.py index 6e5b79d508..16af9799f0 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -12,7 +12,7 @@ class StateTypes(enum.Enum): ONGOING = "ongoing" PLANNING = "planning" -# --- MODELOS --- +# --- MODELS --- class User(db.Model): __tablename__ = 'user' @@ -22,12 +22,12 @@ class User(db.Model): email: Mapped[str] = mapped_column(String(120), unique=True, nullable=False) password: Mapped[str] = mapped_column(nullable=False) - # Relaciones - viajeros = relationship("Viajero", back_populates="users") - gastos_pagados = relationship("Gasto", back_populates="pagador") - mensajes = relationship("Mensaje", back_populates="autor") - deudas_pendientes = relationship("Deuda", foreign_keys="[Deuda.id_deudor]", back_populates="deudor") - deudas_a_cobrar = relationship("Deuda", foreign_keys="[Deuda.id_acreedor]", back_populates="acreedor") + # Relationships + travelers = relationship("Traveler", back_populates="user") + expenses_paid = relationship("Expense", back_populates="payer") + messages = relationship("Message", back_populates="author") + debts_owed = relationship("Debt", foreign_keys="[Debt.debtor_id]", back_populates="debtor") + debts_to_receive = relationship("Debt", foreign_keys="[Debt.creditor_id]", back_populates="creditor") def serialize(self): return { @@ -37,8 +37,8 @@ def serialize(self): "email": self.email } -class Viaje(db.Model): - __tablename__ = 'viaje' +class Trip(db.Model): + __tablename__ = 'trip' id: Mapped[int] = mapped_column(primary_key=True) title: Mapped[str] = mapped_column(String(30), nullable=False) destination: Mapped[str] = mapped_column(String(50), nullable=False) @@ -48,12 +48,12 @@ class Viaje(db.Model): budget: Mapped[float] = mapped_column(Float, nullable=False) notes: Mapped[str] = mapped_column(String(150), nullable=False) - # Relaciones - viajeros = relationship("Viajero", back_populates="viajes") - itinerarios = relationship("Itinerario", back_populates="viajes") - gastos = relationship("Gasto", back_populates="viajes") - documentos = relationship("Documento", back_populates="viajes") - chat = relationship("Chat", back_populates="viajes", uselist=False) + # Relationships + travelers = relationship("Traveler", back_populates="trip") + itineraries = relationship("Itinerary", back_populates="trip") + expenses = relationship("Expense", back_populates="trip") + documents = relationship("Document", back_populates="trip") + chat = relationship("Chat", back_populates="trip", uselist=False) def serialize(self): return { @@ -67,91 +67,92 @@ def serialize(self): "notes": self.notes } -class Viajero(db.Model): - __tablename__ = 'viajero' - id_user: Mapped[int] = mapped_column(ForeignKey("user.id", ondelete="CASCADE"), primary_key=True) - id_viaje: Mapped[int] = mapped_column(ForeignKey("viaje.id", ondelete="CASCADE"), primary_key=True) +class Traveler(db.Model): + __tablename__ = 'traveler' + user_id: Mapped[int] = mapped_column(ForeignKey("user.id", ondelete="CASCADE"), primary_key=True) + trip_id: Mapped[int] = mapped_column(ForeignKey("trip.id", ondelete="CASCADE"), primary_key=True) - users = relationship("User", back_populates="viajeros") - viajes = relationship("Viaje", back_populates="viajeros") + user = relationship("User", back_populates="travelers") + trip = relationship("Trip", back_populates="travelers") def serialize(self): return { - "id_user": self.id_user, - "id_viaje": self.id_viaje + "user_id": self.user_id, + "trip_id": self.trip_id } -class Itinerario(db.Model): - __tablename__ = 'itinerario' +class Itinerary(db.Model): + __tablename__ = 'itinerary' id: Mapped[int] = mapped_column(primary_key=True) title: Mapped[str] = mapped_column(String(30), nullable=False) destination: Mapped[str] = mapped_column(String(50), nullable=False) hour: Mapped[time] = mapped_column(Time, nullable=False) starting_date: Mapped[date] = mapped_column(Date(), nullable=False) notes: Mapped[str] = mapped_column(String(150), nullable=False) - id_viaje: Mapped[int] = mapped_column(ForeignKey("viaje.id", ondelete="CASCADE")) + trip_id: Mapped[int] = mapped_column(ForeignKey("trip.id", ondelete="CASCADE")) - viajes = relationship("Viaje", back_populates="itinerarios") + trip = relationship("Trip", back_populates="itineraries") def serialize(self): return { "id": self.id, "title": self.title, "hour": str(self.hour), - "id_viaje": self.id_viaje + "trip_id": self.trip_id } -class Gasto(db.Model): - __tablename__ = 'gasto' +class Expense(db.Model): + __tablename__ = 'expense' id: Mapped[int] = mapped_column(primary_key=True) - monto: Mapped[float] = mapped_column(Float, nullable=False) - descripcion: Mapped[str] = mapped_column(String(100), nullable=False) - id_viaje: Mapped[int] = mapped_column(ForeignKey("viaje.id", ondelete="CASCADE")) - id_pagador: Mapped[int] = mapped_column(ForeignKey("user.id")) + amount: Mapped[float] = mapped_column(Float, nullable=False) + description: Mapped[str] = mapped_column(String(100), nullable=False) + trip_id: Mapped[int] = mapped_column(ForeignKey("trip.id", ondelete="CASCADE")) + payer_id: Mapped[int] = mapped_column(ForeignKey("user.id")) - viajes = relationship("Viaje", back_populates="gastos") - pagador = relationship("User", back_populates="gastos_pagados") - deudas = relationship("Deuda", back_populates="gastos") + trip = relationship("Trip", back_populates="expenses") + payer = relationship("User", back_populates="expenses_paid") + debts = relationship("Debt", back_populates="expense") def serialize(self): - return {"id": self.id, "monto": self.monto, "descripcion": self.descripcion} + return {"id": self.id, "amount": self.amount, "description": self.description} -class Deuda(db.Model): - __tablename__ = 'deuda' +class Debt(db.Model): + __tablename__ = 'debt' id: Mapped[int] = mapped_column(primary_key=True) - importe: Mapped[float] = mapped_column(Float, nullable=False) - id_deudor: Mapped[int] = mapped_column(ForeignKey("user.id")) - id_acreedor: Mapped[int] = mapped_column(ForeignKey("user.id")) - id_gasto: Mapped[int] = mapped_column(ForeignKey("gasto.id")) + amount: Mapped[float] = mapped_column(Float, nullable=False) + debtor_id: Mapped[int] = mapped_column(ForeignKey("user.id")) + creditor_id: Mapped[int] = mapped_column(ForeignKey("user.id")) + expense_id: Mapped[int] = mapped_column(ForeignKey("expense.id")) - gastos = relationship("Gasto", back_populates="deudas") - deudor = relationship("User", foreign_keys=[id_deudor], back_populates="deudas_pendientes") - acreedor = relationship("User", foreign_keys=[id_acreedor], back_populates="deudas_a_cobrar") + expense = relationship("Expense", back_populates="debts") + debtor = relationship("User", foreign_keys=[debtor_id], back_populates="debts_owed") + creditor = relationship("User", foreign_keys=[creditor_id], back_populates="debts_to_receive") -class Documento(db.Model): - __tablename__ = 'documento' +class Document(db.Model): + __tablename__ = 'document' id: Mapped[int] = mapped_column(primary_key=True) - titulo: Mapped[str] = mapped_column(String(50), nullable=False) + title: Mapped[str] = mapped_column(String(50), nullable=False) url: Mapped[str] = mapped_column(String(250), nullable=False) - id_viaje: Mapped[int] = mapped_column(ForeignKey("viaje.id", ondelete="CASCADE")) + trip_id: Mapped[int] = mapped_column(ForeignKey("trip.id", ondelete="CASCADE")) - viajes = relationship("Viaje", back_populates="documentos") + trip = relationship("Trip", back_populates="documents") class Chat(db.Model): __tablename__ = 'chat' id: Mapped[int] = mapped_column(primary_key=True) - id_viaje: Mapped[int] = mapped_column(ForeignKey("viaje.id", ondelete="CASCADE")) + title: Mapped[str] = mapped_column(String(50), nullable=True) # <-- Título añadido + trip_id: Mapped[int] = mapped_column(ForeignKey("trip.id", ondelete="CASCADE")) - viajes = relationship("Viaje", back_populates="chat") - mensajes = relationship("Mensaje", back_populates="chat") + trip = relationship("Trip", back_populates="chat") + messages = relationship("Message", back_populates="chat") -class Mensaje(db.Model): - __tablename__ = 'mensaje' +class Message(db.Model): + __tablename__ = 'message' id: Mapped[int] = mapped_column(primary_key=True) - contenido: Mapped[str] = mapped_column(String(500), nullable=False) - fecha_hora: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) - id_chat: Mapped[int] = mapped_column(ForeignKey("chat.id", ondelete="CASCADE")) - id_usuario: Mapped[int] = mapped_column(ForeignKey("user.id")) + content: Mapped[str] = mapped_column(String(500), nullable=False) + date_time: Mapped[datetime] = mapped_column(DateTime, default=datetime.utcnow) + chat_id: Mapped[int] = mapped_column(ForeignKey("chat.id", ondelete="CASCADE")) + user_id: Mapped[int] = mapped_column(ForeignKey("user.id")) - chat = relationship("Chat", back_populates="mensajes") - autor = relationship("User", back_populates="mensajes") \ No newline at end of file + chat = relationship("Chat", back_populates="messages") + author = relationship("User", back_populates="messages") \ No newline at end of file