Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
51a8477
added jwt authentication, endpoints, routes, and added some pages for…
Morgabor Aug 28, 2025
42ace33
Merge pull request #1 from 4GeeksAcademy/jake
Morgabor Aug 28, 2025
942b8d7
did a quick add for preview page and route
Morgabor Aug 28, 2025
b9d3e65
Merge pull request #2 from 4GeeksAcademy/jake
Morgabor Aug 28, 2025
28a4641
initial
tocasuche Aug 28, 2025
73c7b7f
Merge pull request #3 from 4GeeksAcademy/Jose
tocasuche Aug 29, 2025
69c587f
fixed some problems works now
Morgabor Aug 29, 2025
46c36b6
Merge pull request #4 from 4GeeksAcademy/jake
Morgabor Aug 29, 2025
48fd14f
test
tocasuche Sep 3, 2025
4c82a0c
Merge branch 'main' into Jose
Morgabor Sep 4, 2025
077868e
Merge pull request #5 from 4GeeksAcademy/Jose
Morgabor Sep 4, 2025
1096cd0
completed the geoLocation hook
Morgabor Sep 4, 2025
94ceae5
l
Morgabor Sep 4, 2025
d61a8a0
Account.jsx about 70% complete
tocasuche Sep 4, 2025
dcf255d
Merge remote-tracking branch 'origin' into Jose
tocasuche Sep 4, 2025
4081b56
f
Morgabor Sep 4, 2025
dd83767
Merge pull request #6 from 4GeeksAcademy/jake
Morgabor Sep 4, 2025
8c50bce
workin' on it
tocasuche Sep 5, 2025
b1d9ebd
correct Login and Account
tocasuche Sep 6, 2025
75b5082
Merge branch 'main' into Jose
tocasuche Sep 6, 2025
204e8c6
Merge pull request #7 from 4GeeksAcademy/Jose
DevKeria Sep 6, 2025
baeb1e9
landing page
DevKeria Sep 8, 2025
8eba319
Merge branch 'main' into Keria
Morgabor Sep 8, 2025
de98e90
Merge pull request #8 from 4GeeksAcademy/Keria
Morgabor Sep 8, 2025
c55658a
before pull
Morgabor Sep 8, 2025
8d5c0b9
Merge remote-tracking branch 'origin' into jake
Morgabor Sep 8, 2025
67983d2
finished sign up feature
Morgabor Sep 8, 2025
01cc4fc
Merge pull request #9 from 4GeeksAcademy/jake
Morgabor Sep 8, 2025
76719fa
mission and video
DevKeria Sep 9, 2025
2e8ffd8
most uptodate file
tocasuche Sep 9, 2025
7c1f69e
Merge pull request #10 from 4GeeksAcademy/Jose
Morgabor Sep 9, 2025
15bf2de
delete password
DevKeria Sep 11, 2025
91b81dc
password
DevKeria Sep 11, 2025
5dd9532
password
DevKeria Sep 11, 2025
94cfc69
created forgot password page
tocasuche Sep 11, 2025
5d10fa6
.env updated to test calendar api
tocasuche Sep 15, 2025
8ff3219
test
Morgabor Sep 17, 2025
e382f9d
Merge remote-tracking branch 'origin' into jake
Morgabor Sep 17, 2025
cf97098
set up the geolocation hook with a yelp api and it now shows nearby r…
Morgabor Sep 17, 2025
dae7500
made a limiter for only 5
Morgabor Sep 17, 2025
c2fe0de
noted out everything that can be used on home just to have a template…
Morgabor Sep 17, 2025
23a9ff0
updated it againnnn
Morgabor Sep 17, 2025
5941ff6
Merge pull request #11 from 4GeeksAcademy/Jose
Morgabor Sep 19, 2025
985cff2
before pull
Morgabor Sep 19, 2025
63a6f1f
Merge remote-tracking branch 'origin' into jake
Morgabor Sep 19, 2025
79e0d36
Merge pull request #12 from 4GeeksAcademy/Keria
Morgabor Sep 19, 2025
3b5ab5d
yeah i guess
Morgabor Sep 19, 2025
201e408
Merge remote-tracking branch 'origin' into jake
Morgabor Sep 19, 2025
0438414
pull
Morgabor Sep 19, 2025
9c482c8
Merge pull request #13 from 4GeeksAcademy/jake
tocasuche Sep 19, 2025
555e43b
login
DevKeria Sep 19, 2025
b779d6b
Merge pull request #14 from 4GeeksAcademy/Keria
tocasuche Sep 19, 2025
9c03497
working on calendar api
tocasuche Sep 20, 2025
d84d877
Only pic pending
tocasuche Sep 20, 2025
bdf8063
completed Account page
tocasuche Sep 21, 2025
613367d
pull request ready Account.jsx
tocasuche Sep 21, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,21 @@ verify_ssl = true
[packages]
flask = "*"
flask-sqlalchemy = "*"
flask-migrate = "*"
flask-swagger = "*"
psycopg2-binary = "*"
python-dotenv = "*"
flask-cors = "*"
gunicorn = "*"
cloudinary = "*"
flask-admin = "*"
typing-extensions = "*"
flask-jwt-extended = "==4.6.0"
wtforms = "==3.1.2"
sqlalchemy = "*"
flask-migrate = "*"
pytz = "*"
icalendar = "*"
requests = "*"
flask-cors = "*"

[requires]
python_version = "3.13"
Expand Down
452 changes: 287 additions & 165 deletions Pipfile.lock

Large diffs are not rendered by default.

35 changes: 0 additions & 35 deletions migrations/versions/0763d677d453_.py

This file was deleted.

73 changes: 73 additions & 0 deletions migrations/versions/21f5540bab2c_.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""empty message

Revision ID: 21f5540bab2c
Revises:
Create Date: 2025-09-20 16:17:04.733283

"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '21f5540bab2c'
down_revision = None
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('users',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('email', sa.String(length=120), nullable=False),
sa.Column('password', sa.String(length=255), nullable=False),
sa.Column('is_active', sa.Boolean(), nullable=False),
sa.Column('security_question', sa.String(length=255), nullable=True),
sa.Column('jpeg', sa.LargeBinary(), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('email')
)
op.create_table('listings',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.Column('booking_id', sa.Integer(), nullable=True),
sa.Column('airbnb_address', sa.String(length=255), nullable=False),
sa.Column('airbnb_zipcode', sa.String(length=15), nullable=True),
sa.ForeignKeyConstraint(['booking_id'], ['bookings.id'], name='fk_listings_current_booking', use_alter=True),
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id')
)
op.create_table('bookings',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('google_calendar_id', sa.String(length=255), nullable=True),
sa.Column('listing_id', sa.Integer(), nullable=False),
sa.Column('airbnb_guest_first_name', sa.String(length=120), nullable=True),
sa.Column('airbnb_guest_last_name', sa.String(length=120), nullable=True),
sa.Column('airbnb_checkin', sa.Date(), nullable=True),
sa.Column('airbnb_checkout', sa.Date(), nullable=True),
sa.Column('airbnb_guestpic_url', sa.String(length=500), nullable=True),
sa.Column('airbnb_guestpic', sa.LargeBinary(), nullable=True),
sa.Column('reservation_url', sa.String(length=500), nullable=True),
sa.Column('phone_last4', sa.String(length=4), nullable=True),
sa.Column('needs_manual_details', sa.Boolean(), nullable=False),
sa.CheckConstraint('(airbnb_checkin IS NULL OR airbnb_checkout IS NULL) OR (airbnb_checkout >= airbnb_checkin)', name='ck_booking_checkout_after_checkin'),
sa.ForeignKeyConstraint(['listing_id'], ['listings.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('listing_id', 'google_calendar_id', name='uq_booking_listing_googleid')
)
with op.batch_alter_table('bookings', schema=None) as batch_op:
batch_op.create_index(batch_op.f('ix_bookings_google_calendar_id'), ['google_calendar_id'], unique=False)

# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('bookings', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_bookings_google_calendar_id'))

op.drop_table('bookings')
op.drop_table('listings')
op.drop_table('users')
# ### end Alembic commands ###
132 changes: 132 additions & 0 deletions migrations/versions/ef02e5ef8fa8_.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
"""empty message

Revision ID: ef02e5ef8fa8
Revises: 21f5540bab2c
Create Date: 2025-09-20 18:47:08.506715

"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql

# revision identifiers, used by Alembic.
revision = 'ef02e5ef8fa8'
down_revision = '21f5540bab2c'
branch_labels = None
depends_on = None


def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('bookings', schema=None) as batch_op:
batch_op.add_column(sa.Column('created_at', sa.DateTime(), nullable=False))
batch_op.add_column(sa.Column('updated_at', sa.DateTime(), nullable=False))
batch_op.alter_column('listing_id',
existing_type=sa.INTEGER(),
nullable=True)
batch_op.alter_column('airbnb_checkin',
existing_type=sa.DATE(),
type_=sa.DateTime(),
existing_nullable=True)
batch_op.alter_column('airbnb_checkout',
existing_type=sa.DATE(),
type_=sa.DateTime(),
existing_nullable=True)
batch_op.alter_column('reservation_url',
existing_type=sa.VARCHAR(length=500),
type_=sa.String(length=1024),
existing_nullable=True)
batch_op.alter_column('airbnb_guestpic_url',
existing_type=sa.VARCHAR(length=500),
type_=sa.String(length=1024),
existing_nullable=True)
batch_op.drop_constraint(batch_op.f('uq_booking_listing_googleid'), type_='unique')
batch_op.drop_constraint(batch_op.f('bookings_listing_id_fkey'), type_='foreignkey')
batch_op.drop_column('airbnb_guestpic')
batch_op.drop_column('phone_last4')

with op.batch_alter_table('listings', schema=None) as batch_op:
batch_op.add_column(sa.Column('name', sa.String(length=255), nullable=True))
batch_op.add_column(sa.Column('street', sa.String(length=255), nullable=True))
batch_op.add_column(sa.Column('city', sa.String(length=120), nullable=True))
batch_op.add_column(sa.Column('state', sa.String(length=80), nullable=True))
batch_op.add_column(sa.Column('image_url', sa.String(length=1024), nullable=True))
batch_op.add_column(sa.Column('created_at', sa.DateTime(), nullable=False))
batch_op.add_column(sa.Column('updated_at', sa.DateTime(), nullable=False))
batch_op.drop_constraint(batch_op.f('listings_user_id_fkey'), type_='foreignkey')
batch_op.drop_column('booking_id')
batch_op.drop_column('user_id')
batch_op.drop_column('airbnb_address')
batch_op.drop_column('airbnb_zipcode')

with op.batch_alter_table('users', schema=None) as batch_op:
batch_op.add_column(sa.Column('created_at', sa.DateTime(), nullable=False))
batch_op.add_column(sa.Column('updated_at', sa.DateTime(), nullable=False))
batch_op.alter_column('email',
existing_type=sa.VARCHAR(length=120),
type_=sa.String(length=255),
existing_nullable=False)
batch_op.drop_constraint(batch_op.f('users_email_key'), type_='unique')
batch_op.create_index(batch_op.f('ix_users_email'), ['email'], unique=True)
batch_op.drop_column('security_question')
batch_op.drop_column('jpeg')

# ### end Alembic commands ###


def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('users', schema=None) as batch_op:
batch_op.add_column(sa.Column('jpeg', postgresql.BYTEA(), autoincrement=False, nullable=True))
batch_op.add_column(sa.Column('security_question', sa.VARCHAR(length=255), autoincrement=False, nullable=True))
batch_op.drop_index(batch_op.f('ix_users_email'))
batch_op.create_unique_constraint(batch_op.f('users_email_key'), ['email'], postgresql_nulls_not_distinct=False)
batch_op.alter_column('email',
existing_type=sa.String(length=255),
type_=sa.VARCHAR(length=120),
existing_nullable=False)
batch_op.drop_column('updated_at')
batch_op.drop_column('created_at')

with op.batch_alter_table('listings', schema=None) as batch_op:
batch_op.add_column(sa.Column('airbnb_zipcode', sa.VARCHAR(length=15), autoincrement=False, nullable=True))
batch_op.add_column(sa.Column('airbnb_address', sa.VARCHAR(length=255), autoincrement=False, nullable=False))
batch_op.add_column(sa.Column('user_id', sa.INTEGER(), autoincrement=False, nullable=False))
batch_op.add_column(sa.Column('booking_id', sa.INTEGER(), autoincrement=False, nullable=True))
batch_op.create_foreign_key(batch_op.f('listings_user_id_fkey'), 'users', ['user_id'], ['id'], ondelete='CASCADE')
batch_op.drop_column('updated_at')
batch_op.drop_column('created_at')
batch_op.drop_column('image_url')
batch_op.drop_column('state')
batch_op.drop_column('city')
batch_op.drop_column('street')
batch_op.drop_column('name')

with op.batch_alter_table('bookings', schema=None) as batch_op:
batch_op.add_column(sa.Column('phone_last4', sa.VARCHAR(length=4), autoincrement=False, nullable=True))
batch_op.add_column(sa.Column('airbnb_guestpic', postgresql.BYTEA(), autoincrement=False, nullable=True))
batch_op.create_foreign_key(batch_op.f('bookings_listing_id_fkey'), 'listings', ['listing_id'], ['id'], ondelete='CASCADE')
batch_op.create_unique_constraint(batch_op.f('uq_booking_listing_googleid'), ['listing_id', 'google_calendar_id'], postgresql_nulls_not_distinct=False)
batch_op.alter_column('airbnb_guestpic_url',
existing_type=sa.String(length=1024),
type_=sa.VARCHAR(length=500),
existing_nullable=True)
batch_op.alter_column('reservation_url',
existing_type=sa.String(length=1024),
type_=sa.VARCHAR(length=500),
existing_nullable=True)
batch_op.alter_column('airbnb_checkout',
existing_type=sa.DateTime(),
type_=sa.DATE(),
existing_nullable=True)
batch_op.alter_column('airbnb_checkin',
existing_type=sa.DateTime(),
type_=sa.DATE(),
existing_nullable=True)
batch_op.alter_column('listing_id',
existing_type=sa.INTEGER(),
nullable=False)
batch_op.drop_column('updated_at')
batch_op.drop_column('created_at')

# ### end Alembic commands ###
118 changes: 109 additions & 9 deletions src/api/models.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,119 @@
from __future__ import annotations

from datetime import datetime
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import String, Boolean
from sqlalchemy.orm import Mapped, mapped_column

db = SQLAlchemy()


# ---- User -------------------------------------------------------------------
# Minimal User model so api/admin.py can `from .models import db, User`
class User(db.Model):
id: Mapped[int] = mapped_column(primary_key=True)
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)
__tablename__ = "users"

id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(255), unique=True, nullable=False, index=True)
# store hashed password
password = db.Column(db.String(255), nullable=False)
is_active = db.Column(db.Boolean, nullable=False, default=True)

created_at = db.Column(db.DateTime, nullable=False,
default=datetime.utcnow)
updated_at = db.Column(
db.DateTime, nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow
)

def __repr__(self) -> str: # pragma: no cover
return f"<User {self.id} {self.email}>"

def serialize(self):
def serialize(self) -> dict:
return {
"id": self.id,
"email": self.email,
# do not serialize the password, its a security breach
}
"is_active": self.is_active,
}


# ---- Listing ----------------------------------------------------------------
# Lightweight Listing model; safe to have even if you’re not using it yet.
class Listing(db.Model):
__tablename__ = "listings"

id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(255), nullable=True)
street = db.Column(db.String(255), nullable=True)
city = db.Column(db.String(120), nullable=True)
state = db.Column(db.String(80), nullable=True)
image_url = db.Column(db.String(1024), nullable=True)

created_at = db.Column(db.DateTime, nullable=False,
default=datetime.utcnow)
updated_at = db.Column(
db.DateTime, nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow
)

def __repr__(self) -> str: # pragma: no cover
return f"<Listing {self.id} {self.name or ''}>"

def serialize(self) -> dict:
return {
"id": self.id,
"name": self.name,
"street": self.street,
"city": self.city,
"state": self.state,
"image_url": self.image_url,
}


# ---- Booking ----------------------------------------------------------------
# No phone/last4 anywhere. Dates & optional guest picture supported.
class Booking(db.Model):
__tablename__ = "bookings"

id = db.Column(db.Integer, primary_key=True)

# Google event UID
google_calendar_id = db.Column(db.String(255), index=True, nullable=True)

# Optional association to a listing (kept simple: no FK constraint required)
listing_id = db.Column(db.Integer, nullable=True)

# Optional guest names (manual)
airbnb_guest_first_name = db.Column(db.String(120), nullable=True)
airbnb_guest_last_name = db.Column(db.String(120), nullable=True)

# Reservation dates
airbnb_checkin = db.Column(db.DateTime, nullable=True)
airbnb_checkout = db.Column(db.DateTime, nullable=True)

# Reservation link (e.g., from calendar description)
reservation_url = db.Column(db.String(1024), nullable=True)

# Optional image per booking (if you save one later)
airbnb_guestpic_url = db.Column(db.String(1024), nullable=True)

# Bookkeeping
needs_manual_details = db.Column(db.Boolean, nullable=False, default=True)
created_at = db.Column(db.DateTime, nullable=False,
default=datetime.utcnow)
updated_at = db.Column(
db.DateTime, nullable=False, default=datetime.utcnow, onupdate=datetime.utcnow
)

def __repr__(self) -> str: # pragma: no cover
return f"<Booking {self.id} {self.google_calendar_id or ''}>"

def serialize(self) -> dict:
return {
"id": self.id,
"google_calendar_id": self.google_calendar_id,
"listing_id": self.listing_id,
"airbnb_guest_first_name": self.airbnb_guest_first_name,
"airbnb_guest_last_name": self.airbnb_guest_last_name,
"airbnb_checkin": self.airbnb_checkin.isoformat() if self.airbnb_checkin else None,
"airbnb_checkout": self.airbnb_checkout.isoformat() if self.airbnb_checkout else None,
"reservation_url": self.reservation_url,
"airbnb_guestpic_url": self.airbnb_guestpic_url,
"needs_manual_details": self.needs_manual_details,
}
Loading