
- Set up FastAPI project structure with modular architecture - Create comprehensive database models for users, properties, messages, notifications, and payments - Implement JWT-based authentication with role-based access control (Seeker, Agent, Landlord, Admin) - Build property listings CRUD with advanced search and filtering capabilities - Add dedicated affordable housing endpoints for Nigerian market focus - Create real-time messaging system between users - Implement admin dashboard with property approval workflow and analytics - Add notification system for user alerts - Integrate Paystack payment gateway for transactions - Set up SQLite database with Alembic migrations - Include comprehensive health check and API documentation - Add proper error handling and validation throughout - Follow FastAPI best practices with Pydantic schemas and dependency injection
162 lines
9.3 KiB
Python
162 lines
9.3 KiB
Python
"""Initial migration
|
|
|
|
Revision ID: 001
|
|
Revises:
|
|
Create Date: 2024-01-01 00:00:00.000000
|
|
|
|
"""
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
|
|
# revision identifiers, used by Alembic.
|
|
revision = '001'
|
|
down_revision = None
|
|
branch_labels = None
|
|
depends_on = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
# Create users table
|
|
op.create_table('users',
|
|
sa.Column('id', sa.Integer(), nullable=False),
|
|
sa.Column('username', sa.String(length=50), nullable=False),
|
|
sa.Column('email', sa.String(length=100), nullable=False),
|
|
sa.Column('phone_number', sa.String(length=20), nullable=False),
|
|
sa.Column('hashed_password', sa.String(length=128), nullable=False),
|
|
sa.Column('role', sa.Enum('SEEKER', 'AGENT', 'LANDLORD', 'ADMIN', name='userrole'), nullable=False),
|
|
sa.Column('is_verified', sa.Boolean(), nullable=True),
|
|
sa.Column('is_active', sa.Boolean(), nullable=True),
|
|
sa.Column('date_joined', sa.DateTime(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=True),
|
|
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
|
|
sa.PrimaryKeyConstraint('id')
|
|
)
|
|
op.create_index(op.f('ix_users_email'), 'users', ['email'], unique=True)
|
|
op.create_index(op.f('ix_users_id'), 'users', ['id'], unique=False)
|
|
op.create_index(op.f('ix_users_phone_number'), 'users', ['phone_number'], unique=True)
|
|
op.create_index(op.f('ix_users_username'), 'users', ['username'], unique=True)
|
|
|
|
# Create profiles table
|
|
op.create_table('profiles',
|
|
sa.Column('id', sa.Integer(), nullable=False),
|
|
sa.Column('user_id', sa.Integer(), nullable=False),
|
|
sa.Column('bio', sa.Text(), nullable=True),
|
|
sa.Column('profile_image', sa.String(length=255), nullable=True),
|
|
sa.Column('contact_address', sa.Text(), nullable=True),
|
|
sa.Column('is_verified', sa.Boolean(), nullable=True),
|
|
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=True),
|
|
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
|
|
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
|
sa.PrimaryKeyConstraint('id'),
|
|
sa.UniqueConstraint('user_id')
|
|
)
|
|
op.create_index(op.f('ix_profiles_id'), 'profiles', ['id'], unique=False)
|
|
|
|
# Create property_listings table
|
|
op.create_table('property_listings',
|
|
sa.Column('id', sa.Integer(), nullable=False),
|
|
sa.Column('owner_id', sa.Integer(), nullable=False),
|
|
sa.Column('title', sa.String(length=200), nullable=False),
|
|
sa.Column('description', sa.Text(), nullable=False),
|
|
sa.Column('price', sa.Numeric(precision=15, scale=2), nullable=False),
|
|
sa.Column('location', sa.String(length=255), nullable=False),
|
|
sa.Column('state', sa.String(length=50), nullable=False),
|
|
sa.Column('lga', sa.String(length=100), nullable=False),
|
|
sa.Column('property_type', sa.Enum('APARTMENT', 'HOUSE', 'DUPLEX', 'BUNGALOW', 'FLAT', 'ROOM', 'SELF_CONTAIN', 'SHOP', 'OFFICE', 'WAREHOUSE', name='propertytype'), nullable=False),
|
|
sa.Column('status', sa.Enum('FOR_RENT', 'FOR_SALE', 'SHORTLET', name='propertystatus'), nullable=False),
|
|
sa.Column('bedrooms', sa.Integer(), nullable=True),
|
|
sa.Column('bathrooms', sa.Integer(), nullable=True),
|
|
sa.Column('toilets', sa.Integer(), nullable=True),
|
|
sa.Column('amenities', sa.JSON(), nullable=True),
|
|
sa.Column('images', sa.JSON(), nullable=True),
|
|
sa.Column('is_affordable', sa.Boolean(), nullable=True),
|
|
sa.Column('is_approved', sa.Boolean(), nullable=True),
|
|
sa.Column('is_active', sa.Boolean(), nullable=True),
|
|
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=True),
|
|
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
|
|
sa.ForeignKeyConstraint(['owner_id'], ['users.id'], ),
|
|
sa.PrimaryKeyConstraint('id')
|
|
)
|
|
op.create_index(op.f('ix_property_listings_id'), 'property_listings', ['id'], unique=False)
|
|
op.create_index(op.f('ix_property_listings_is_active'), 'property_listings', ['is_active'], unique=False)
|
|
op.create_index(op.f('ix_property_listings_is_affordable'), 'property_listings', ['is_affordable'], unique=False)
|
|
op.create_index(op.f('ix_property_listings_is_approved'), 'property_listings', ['is_approved'], unique=False)
|
|
op.create_index(op.f('ix_property_listings_lga'), 'property_listings', ['lga'], unique=False)
|
|
op.create_index(op.f('ix_property_listings_location'), 'property_listings', ['location'], unique=False)
|
|
op.create_index(op.f('ix_property_listings_price'), 'property_listings', ['price'], unique=False)
|
|
op.create_index(op.f('ix_property_listings_property_type'), 'property_listings', ['property_type'], unique=False)
|
|
op.create_index(op.f('ix_property_listings_state'), 'property_listings', ['state'], unique=False)
|
|
op.create_index(op.f('ix_property_listings_status'), 'property_listings', ['status'], unique=False)
|
|
op.create_index(op.f('ix_property_listings_title'), 'property_listings', ['title'], unique=False)
|
|
|
|
# Create messages table
|
|
op.create_table('messages',
|
|
sa.Column('id', sa.Integer(), nullable=False),
|
|
sa.Column('sender_id', sa.Integer(), nullable=False),
|
|
sa.Column('receiver_id', sa.Integer(), nullable=False),
|
|
sa.Column('content', sa.Text(), nullable=False),
|
|
sa.Column('is_read', sa.Boolean(), nullable=True),
|
|
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=True),
|
|
sa.ForeignKeyConstraint(['receiver_id'], ['users.id'], ),
|
|
sa.ForeignKeyConstraint(['sender_id'], ['users.id'], ),
|
|
sa.PrimaryKeyConstraint('id')
|
|
)
|
|
op.create_index(op.f('ix_messages_id'), 'messages', ['id'], unique=False)
|
|
|
|
# Create notifications table
|
|
op.create_table('notifications',
|
|
sa.Column('id', sa.Integer(), nullable=False),
|
|
sa.Column('user_id', sa.Integer(), nullable=False),
|
|
sa.Column('title', sa.String(length=200), nullable=False),
|
|
sa.Column('message', sa.Text(), nullable=False),
|
|
sa.Column('is_read', sa.Boolean(), nullable=True),
|
|
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=True),
|
|
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
|
sa.PrimaryKeyConstraint('id')
|
|
)
|
|
op.create_index(op.f('ix_notifications_id'), 'notifications', ['id'], unique=False)
|
|
|
|
# Create payments table
|
|
op.create_table('payments',
|
|
sa.Column('id', sa.Integer(), nullable=False),
|
|
sa.Column('user_id', sa.Integer(), nullable=False),
|
|
sa.Column('amount', sa.Numeric(precision=15, scale=2), nullable=False),
|
|
sa.Column('transaction_ref', sa.String(length=100), nullable=False),
|
|
sa.Column('status', sa.Enum('PENDING', 'SUCCESS', 'FAILED', 'CANCELLED', name='paymentstatus'), nullable=False),
|
|
sa.Column('payment_method', sa.String(length=50), nullable=True),
|
|
sa.Column('description', sa.String(length=255), nullable=True),
|
|
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('(CURRENT_TIMESTAMP)'), nullable=True),
|
|
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
|
|
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
|
sa.PrimaryKeyConstraint('id')
|
|
)
|
|
op.create_index(op.f('ix_payments_id'), 'payments', ['id'], unique=False)
|
|
op.create_index(op.f('ix_payments_transaction_ref'), 'payments', ['transaction_ref'], unique=True)
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.drop_index(op.f('ix_payments_transaction_ref'), table_name='payments')
|
|
op.drop_index(op.f('ix_payments_id'), table_name='payments')
|
|
op.drop_table('payments')
|
|
op.drop_index(op.f('ix_notifications_id'), table_name='notifications')
|
|
op.drop_table('notifications')
|
|
op.drop_index(op.f('ix_messages_id'), table_name='messages')
|
|
op.drop_table('messages')
|
|
op.drop_index(op.f('ix_property_listings_title'), table_name='property_listings')
|
|
op.drop_index(op.f('ix_property_listings_status'), table_name='property_listings')
|
|
op.drop_index(op.f('ix_property_listings_state'), table_name='property_listings')
|
|
op.drop_index(op.f('ix_property_listings_property_type'), table_name='property_listings')
|
|
op.drop_index(op.f('ix_property_listings_price'), table_name='property_listings')
|
|
op.drop_index(op.f('ix_property_listings_location'), table_name='property_listings')
|
|
op.drop_index(op.f('ix_property_listings_lga'), table_name='property_listings')
|
|
op.drop_index(op.f('ix_property_listings_is_approved'), table_name='property_listings')
|
|
op.drop_index(op.f('ix_property_listings_is_affordable'), table_name='property_listings')
|
|
op.drop_index(op.f('ix_property_listings_is_active'), table_name='property_listings')
|
|
op.drop_index(op.f('ix_property_listings_id'), table_name='property_listings')
|
|
op.drop_table('property_listings')
|
|
op.drop_index(op.f('ix_profiles_id'), table_name='profiles')
|
|
op.drop_table('profiles')
|
|
op.drop_index(op.f('ix_users_username'), table_name='users')
|
|
op.drop_index(op.f('ix_users_phone_number'), table_name='users')
|
|
op.drop_index(op.f('ix_users_id'), table_name='users')
|
|
op.drop_index(op.f('ix_users_email'), table_name='users')
|
|
op.drop_table('users') |