diff --git a/migrations/versions/0763d677d453_.py b/migrations/versions/0763d677d453_.py deleted file mode 100644 index 88964176f1..0000000000 --- a/migrations/versions/0763d677d453_.py +++ /dev/null @@ -1,35 +0,0 @@ -"""empty message - -Revision ID: 0763d677d453 -Revises: -Create Date: 2025-02-25 14:47:16.337069 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '0763d677d453' -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('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') - ) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('user') - # ### end Alembic commands ### 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..16af9799f0 100644 --- a/src/api/models.py +++ b/src/api/models.py @@ -1,19 +1,158 @@ +import enum from flask_sqlalchemy import SQLAlchemy -from sqlalchemy import String, Boolean -from sqlalchemy.orm import Mapped, mapped_column +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, datetime db = SQLAlchemy() +# --- ENUMS --- +class StateTypes(enum.Enum): + FINISHED = "finished" + ONGOING = "ongoing" + PLANNING = "planning" + +# --- MODELS --- + 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) - is_active: Mapped[bool] = mapped_column(Boolean(), nullable=False) + + # 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 { + "id": self.id, + "name": self.name, + "last_name": self.last_name, + "email": self.email + } + +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) + 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) + + # 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 { + "id": self.id, + "title": self.title, + "destination": self.destination, + "state": self.state.value, + "starting_date": str(self.starting_date), + "ending_date": str(self.ending_date), + "budget": self.budget, + "notes": self.notes + } + +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) + + user = relationship("User", back_populates="travelers") + trip = relationship("Trip", back_populates="travelers") + + def serialize(self): + return { + "user_id": self.user_id, + "trip_id": self.trip_id + } +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) + trip_id: Mapped[int] = mapped_column(ForeignKey("trip.id", ondelete="CASCADE")) + + trip = relationship("Trip", back_populates="itineraries") def serialize(self): return { "id": self.id, - "email": self.email, - # do not serialize the password, its a security breach - } \ No newline at end of file + "title": self.title, + "hour": str(self.hour), + "trip_id": self.trip_id + } + +class Expense(db.Model): + __tablename__ = 'expense' + id: Mapped[int] = mapped_column(primary_key=True) + 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")) + + 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, "amount": self.amount, "description": self.description} + +class Debt(db.Model): + __tablename__ = 'debt' + id: Mapped[int] = mapped_column(primary_key=True) + 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")) + + 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 Document(db.Model): + __tablename__ = 'document' + id: Mapped[int] = mapped_column(primary_key=True) + title: Mapped[str] = mapped_column(String(50), nullable=False) + url: Mapped[str] = mapped_column(String(250), nullable=False) + trip_id: Mapped[int] = mapped_column(ForeignKey("trip.id", ondelete="CASCADE")) + + trip = relationship("Trip", back_populates="documents") + +class Chat(db.Model): + __tablename__ = 'chat' + id: Mapped[int] = mapped_column(primary_key=True) + title: Mapped[str] = mapped_column(String(50), nullable=True) # <-- Título añadido + trip_id: Mapped[int] = mapped_column(ForeignKey("trip.id", ondelete="CASCADE")) + + trip = relationship("Trip", back_populates="chat") + messages = relationship("Message", back_populates="chat") + +class Message(db.Model): + __tablename__ = 'message' + id: Mapped[int] = mapped_column(primary_key=True) + 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="messages") + author = relationship("User", back_populates="messages") \ No newline at end of file