Maison Property API

Database Configuration

The API uses PostgreSQL database named 'property_db' hosted on Azure.

Properties API

A Flask-based REST API for managing property listings.

API Endpoints

Properties

GET /api/properties

Get a list of all properties

Example:

# Get all properties
curl https://maison-api.jollybush-a62cec71.uksouth.azurecontainerapps.io/api/properties

# Get all properties including sold and under offer
curl https://maison-api.jollybush-a62cec71.uksouth.azurecontainerapps.io/api/properties?include_all=true

# Filter properties
curl https://maison-api.jollybush-a62cec71.uksouth.azurecontainerapps.io/api/properties?min_price=350000
curl https://maison-api.jollybush-a62cec71.uksouth.azurecontainerapps.io/api/properties?property_type=semi-detached
curl https://maison-api.jollybush-a62cec71.uksouth.azurecontainerapps.io/api/properties?min_bedrooms=3
curl https://maison-api.jollybush-a62cec71.uksouth.azurecontainerapps.io/api/properties?postcode=SW1

Response:

[
  {
    "property_id": "123e4567-e89b-12d3-a456-426614174000",
    "price": 350000,
    "bedrooms": 3,
    "bathrooms": 2,
    "main_image_url": "https://example.com/image.jpg",
    "created_at": "2024-02-22T12:00:00Z",
    "seller_id": "3613c096-f41f-479f-a09f-7e0ab53b4eda",
    "status": "for_sale",
    "address": {
      "street": "Sample Street",
      "city": "London",
      "postcode": "SW1 1AA"
    },
    "specs": {
      "property_type": "semi-detached",
      "square_footage": 1200.0
    }
  },
  // ... more properties
]

GET /api/properties/

Get details of a specific property

Example:

curl https://maison-api.jollybush-a62cec71.uksouth.azurecontainerapps.io/api/properties/123e4567-e89b-12d3-a456-426614174000

Response:

{
  "property_id": "123e4567-e89b-12d3-a456-426614174000",
  "price": 350000,
  "bedrooms": 3,
  "bathrooms": 2,
  "main_image_url": "https://example.com/main.jpg",
  "image_urls": ["https://example.com/img1.jpg", "https://example.com/img2.jpg"],
  "floorplan_url": "https://example.com/floorplan.jpg",
  "created_at": "2024-02-22T12:00:00Z",
  "last_updated": "2024-02-22T12:00:00Z",
  "seller_id": "3613c096-f41f-479f-a09f-7e0ab53b4eda",
  "status": "for_sale",
  "address": {
    "house_number": "123",
    "street": "Sample Street",
    "city": "London",
    "postcode": "SW1 1AA",
    "latitude": 51.5074,
    "longitude": -0.1278
  },
  "specs": {
    "bedrooms": 3,
    "bathrooms": 2,
    "reception_rooms": 2,
    "square_footage": 1200.5,
    "property_type": "semi-detached",
    "epc_rating": "B"
  },
  "details": {
    "description": "Beautiful family home",
    "property_type": "residential",
    "construction_year": 1990,
    "parking_spaces": 2,
    "heating_type": "gas central"
  },
  "features": {
    "has_garden": true,
    "garden_size": 100.5,
    "has_garage": true,
    "parking_spaces": 2
  }
}

Error Response:

{
  "error": "Property not found"
}

GET /api/properties/user/{user_id}

Get all properties for a specific user

Example:

curl https://maison-api.jollybush-a62cec71.uksouth.azurecontainerapps.io/api/properties/user/1

Response:

[
  {
    "property_id": "123e4567-e89b-12d3-a456-426614174000",
    "price": 350000,
    "bedrooms": 3,
    "bathrooms": 2,
    "main_image_url": "https://example.com/image.jpg",
    "created_at": "2024-02-22T12:00:00Z",
    "seller_id": 1,
    "status": "for_sale",
    "address": {
      "street": "Sample Street",
      "city": "London",
      "postcode": "SW1 1AA"
    },
    "specs": {
      "property_type": "semi-detached",
      "square_footage": 1200.0
    }
  },
  // ... more properties owned by user 1
]

Error Response (User not found):

{
  "error": "User not found"
}

POST /api/properties

Create a new property listing

Required fields:
- price (integer)
- user_id (integer) - address (object with house_number, street, city, postcode)
- specs (object with bedrooms, bathrooms, reception_rooms, square_footage, property_type, epc_rating)

Optional fields:
- main_image_url (string)
- images (array of image files, max 5MB each, formats: JPG, JPEG, PNG, GIF)
- details (object with description, property_type, construction_year, parking_spaces, heating_type)
- features (object with has_garden, garden_size, has_garage, parking_spaces)
- media (array of objects with image_url, image_type, display_order)

Example with JSON:

  curl -X POST https://maison-api.jollybush-a62cec71.uksouth.azurecontainerapps.io/api/properties \
  -H "Content-Type: application/json" \
  -d '{
    "price": 350000,
    "seller_id": "3613c096-f41f-479f-a09f-7e0ab53b4eda",
    "address": {
      "house_number": "123",
      "street": "Sample Street",
      "city": "London",
      "postcode": "SW1 1AA"
    },
    "specs": {
      "bedrooms": 3,
      "bathrooms": 2,
      "reception_rooms": 2,
      "square_footage": 1200.5,
      "property_type": "semi-detached",
      "epc_rating": "B"
    },
    "details": {
      "description": "Beautiful family home",
      "property_type": "residential",
      "construction_year": 1990,
      "parking_spaces": 2,
      "heating_type": "gas central"
    },
    "features": {
      "has_garden": true,
      "garden_size": 100.5,
      "has_garage": true,
      "parking_spaces": 2
    }
  }'

Example with images (multipart form data):

curl -X POST https://maison-api.jollybush-a62cec71.uksouth.azurecontainerapps.io/api/properties \
  -F 'data={
    "price": 350000,
    "seller_id": "3613c096-f41f-479f-a09f-7e0ab53b4eda",
    "address": {
      "house_number": "180",
      "street": "Queen'\''s Gate",
      "city": "London",
      "postcode": "SW72AZ"
    },
    "specs": {
      "bedrooms": 5,
      "bathrooms": 3,
      "reception_rooms": 2,
      "square_footage": 2300,
      "property_type": "semi-detached",
      "epc_rating": "A"
    },
    "details": {
      "description": "Beautiful family home",
      "construction_year": 1990,
      "heating_type": "gas central"
    },
    "features": {
      "has_garden": true,
      "garden_size": 100,
      "has_garage": true,
      "parking_spaces": 2
    }
  }' \
  -F "main_image=@/Users/roblovegrove/Documents/maison/properties_api/Image Examples/HouseTwo/Brittany-Jones-Personal-01.webp" \
  -F "additional_image=@/Users/roblovegrove/Documents/maison/properties_api/Image Examples/HouseTwo/istockphoto-1494221275-612x612.jpg" \
  -F "additional_image=@/Users/roblovegrove/Documents/maison/properties_api/Image Examples/HouseTwo/Blog-Post-1-1024x684.jpg" \
  -F "additional_image=@/Users/roblovegrove/Documents/maison/properties_api/Image Examples/HouseTwo/Cap-dAntibes-beautiful-bedroom.jpeg"

Response with images:

{
  "id": "123e4567-e89b-12d3-a456-426614174000",
  "message": "Property created successfully",
  "warnings": [],
  "image_urls": [
    "https://maisonblobstorage.blob.core.windows.net/property-images/abc123.jpg",
    "https://maisonblobstorage.blob.core.windows.net/property-images/def456.jpg"
  ]
}

Error Response:

{
  "errors": [
    "Price must be a positive number",
    "Address street is required"
  ]
}

PUT /api/properties/

Update an existing property

Example:

curl -X PUT https://maison-api.jollybush-a62cec71.uksouth.azurecontainerapps.io/api/properties/123e4567-e89b-12d3-a456-426614174000 \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${FIREBASE_ID_TOKEN}" \
  -d '{
    "price": 375000,
    "specs": {
      "bedrooms": 4,
      "bathrooms": 2,
      "reception_rooms": 2,
      "square_footage": 1500.0,
      "property_type": "semi-detached",
      "epc_rating": "A"
    }
  }'

Response:

{
  "message": "Property updated successfully"
}

Error Response:

{
  "error": "Property not found"
}

DELETE /api/properties/

Delete a property (Protected - Requires authentication)

Example:

curl -X DELETE https://maison-api.jollybush-a62cec71.uksouth.azurecontainerapps.io/api/properties/123e4567-e89b-12d3-a456-426614174000 \
  -H "Authorization: Bearer ${FIREBASE_ID_TOKEN}"

Response:

{
  "message": "Property deleted successfully"
}

Error Response:

{
  "error": "Property not found"
}

Users

POST /api/users

Create a new user account

Required fields:
- user_id (UUID): User's Firebase UUID - first_name (string): User's first name
- last_name (string): User's last name
- email (string): User's email address

Optional fields:
- phone_number (string): User's phone number
- roles (array): User roles (defaults to ["buyer"] if not specified)

Example:

  curl -X POST https://maison-api.jollybush-a62cec71.uksouth.azurecontainerapps.io/api/users \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "RY34xpmeKTWHXstoZKdX4JrKchL2",
    "first_name": "Teej",
    "last_name": "Amosu",
    "email": "Teej@maisonai.co.uk",
    "phone_number": "07700900000",
    "roles": [
      {"role_type": "buyer"},
      {"role_type": "seller"}
    ]
  }'

Response:

{
  "message": "User created successfully",
  "user": {
    "user_id": "bd70f994-5834-45b9-a6f0-8731e51ff0e6",
    "first_name": "John",
    "last_name": "Smith",
    "email": "john.smith@example.com",
    "phone_number": "07700900000",
    "roles": [
      {"role_type": "buyer"},
      {"role_type": "seller"}
    ]
  }
}

Error Responses:

{
  "error": "Email already registered"
}
{
  "error": {
    "email": ["Not a valid email address"],
    "first_name": ["Length must be between 1 and 50"]
  }
}

PUT /api/users

Update a user account

All fields are optional
Partial Updates are allowed
Returns both success message and updated user data

Example:

curl -X PUT https://maison-api.jollybush-a62cec71.uksouth.azurecontainerapps.io/api/users/3613c096-f41f-479f-a09f-7e0ab53b4eda \
  -H "Content-Type: application/json" \
  -d '{
    "first_name": "Updated First Name",
    "last_name": "Updated Last Name",
    "email": "newemail@example.com",
    "phone_number": "07700900001"
  }'

Response:

{
  "message": "User updated successfully",
  "user": {
    "id": "3613c096-f41f-479f-a09f-7e0ab53b4eda",
    "first_name": "Updated First Name",
    "last_name": "Updated Last Name",
    "email": "newemail@example.com",
    "phone_number": "07700900001"
  }
}

Error Response:

{
  "error": "User not found"
}

Error Response:

{
  "error": "Email already registered"
}

GET /api/users/{user_id}/dashboard

Get a user's dashboard data including their properties, offers, and saved listings.

Example:

curl https://maison-api.jollybush-a62cec71.uksouth.azurecontainerapps.io/api/users/3613c096-f41f-479f-a09f-7e0ab53b4eda/dashboard

Response:

{
  "listed_properties": [],
  "negotiations_as_buyer": [
    {
      "awaiting_response_from": "buyer",
      "buyer_id": "bd70f994-5834-45b9-a6f0-8731e51ff0e6",
      "created_at": "2025-03-02T14:40:59.226953+00:00",
      "current_offer": 360000,
      "last_offer_by": "3613c096-f41f-479f-a09f-7e0ab53b4eda",
      "negotiation_id": "98de7487-c36f-4111-ba80-388f48a1f614",
      "property_id": "fe08df1c-d24e-4f18-9c7b-cfbe842175f1",
      "status": "active",
      "transaction_history": [
        {
          "created_at": "2025-03-02T14:40:59.240797+00:00",
          "made_by": "bd70f994-5834-45b9-a6f0-8731e51ff0e6",
          "offer_amount": 350000
        },
        {
          "created_at": "2025-03-02T14:45:34.356241+00:00",
          "made_by": "3613c096-f41f-479f-a09f-7e0ab53b4eda",
          "offer_amount": 360000
        }
      ],
      "updated_at": "2025-03-02T14:45:34.345590+00:00"
    }
  ],
  "negotiations_as_seller": [],
  "offered_properties": [
    {
      "property_id": "fe08df1c-d24e-4f18-9c7b-cfbe842175f1",
      "price": 425000,
      "bedrooms": 4,
      "bathrooms": 2,
      "main_image_url": "https://maisonblobstorage.blob.core.windows.net/property-images/2ea1d64a-18da-4752-9b1e-fa40dccd8da9.jpg",
      "created_at": "2025-03-02T13:56:00.576177+00:00",
      "seller_id": "3613c096-f41f-479f-a09f-7e0ab53b4eda",
      "status": "for_sale",
      "address": {
        "house_number": "123",
        "street": "Oak Avenue",
        "city": "Manchester",
        "postcode": "M20 3PS"
      },
      "specs": {
        "property_type": "detached",
        "square_footage": 2100.5
      },
      "latest_offer": {
        "amount": 360000,
        "status": "active",
        "last_updated": "2025-03-02T14:45:34.345590+00:00"
      }
    }
  ],
  "roles": [
    {
      "role_type": "buyer"
    }
  ],
  "saved_properties": [
    {
      "address": {
        "city": "London",
        "postcode": "SW1 1AA",
        "street": "Sample Street"
      },
      "main_image_url": "https://maisonblobstorage.blob.core.windows.net/property-images/2ea1d64a-18da-4752-9b1e-fa40dccd8da9.jpg",
      "notes": "Great location, need to book viewing",
      "price": 350000,
      "property_id": "5ea12e39-bff9-48e2-b593-eb13b5908959",
      "saved_at": "2025-03-02T12:45:25.214553+00:00",
      "specs": {
        "bathrooms": 2,
        "bedrooms": 3,
        "property_type": "semi-detached"
      },
      "status": "for_sale"
    },
    {
      "address": {
        "city": "Manchester",
        "postcode": "M20 3PS",
        "street": "Oak Avenue"
      },
      "main_image_url": "https://maisonblobstorage.blob.core.windows.net/property-images/2ea1d64a-18da-4752-9b1e-fa40dccd8da9.jpg",
      "notes": "Great location, need to book viewing",
      "price": 425000,
      "property_id": "fe08df1c-d24e-4f18-9c7b-cfbe842175f1",
      "saved_at": "2025-03-02T13:56:00.576177+00:00",
      "specs": {
        "bathrooms": 2,
        "bedrooms": 4,
        "property_type": "detached"
      },
      "status": "for_sale"
    }
  ],
  "total_properties_listed": 0,
  "total_saved_properties": 2,
  "user": {
    "email": "misty16@example.org",
    "first_name": "Wesley",
    "last_name": "Watson",
    "phone_number": "+446479882571",
    "id": "bd70f994-5834-45b9-a6f0-8731e51ff0e6"
  }
}

Error Response:

{
  "error": "User not found"
}

GET /api/users

Get a list of all users with their roles and counts

Example:

curl https://maison-api.jollybush-a62cec71.uksouth.azurecontainerapps.io/api/users

Response:

{
  "sellers": [
    "3613c096-f41f-479f-a09f-7e0ab53b4eda",
    "4723d197-g52f-580f-b10f-8e0ab53b5fdb"
  ],
  "buyers": [
    "3613c096-f41f-479f-a09f-7e0ab53b4eda",
    "5834e298-h63g-691g-c21g-9f1bc64c6gec",
    "6945f399-i74h-702h-d32h-0g2cd75d7hfd"
  ],
  "counts": {
    "total_buyers": 3,
    "total_sellers": 2
  }
}

Note: Some users may appear in both sellers and buyers lists if they have both roles.

Error Response:

{
  "error": "Failed to fetch users"
}

POST /api/users/{user_id}/saved-properties

Save a property for a buyer

Required fields:
- property_id (UUID): The ID of the property to save

Optional fields:
- notes (string): Any notes about the saved property

Example:

# Save property with notes
curl -X POST https://maison-api.jollybush-a62cec71.uksouth.azurecontainerapps.io/api/users/bd70f994-5834-45b9-a6f0-8731e51ff0e6/saved-properties \
  -H "Content-Type: application/json" \
  -d '{
    "property_id": "fe08df1c-d24e-4f18-9c7b-cfbe842175f1",
    "notes": "Great location, need to book viewing"
  }'

# Save property without notes
curl -X POST https://maison-api.jollybush-a62cec71.uksouth.azurecontainerapps.io/api/users/bd70f994-5834-45b9-a6f0-8731e51ff0e6/saved-properties \
  -H "Content-Type: application/json" \
  -d '{
    "property_id": "fe08df1c-d24e-4f18-9c7b-cfbe842175f1"
  }'

Response:

{
  "message": "Property saved successfully",
  "saved_property": {
    "user_id": "bd70f994-5834-45b9-a6f0-8731e51ff0e6",
    "property_id": "fe08df1c-d24e-4f18-9c7b-cfbe842175f1",
    "notes": "Great location, need to book viewing",  // Will be null if not provided
    "created_at": "2024-02-25T15:30:00Z"
  }
}

Error Responses:

{
  "error": "User must be a buyer to save properties"
}
{
  "error": "Property already saved"
}
{
  "error": "Property not found"
}
{
  "error": "property_id is required"
}

DELETE /api/users/{user_id}/saved-properties/{property_id}

Remove a property from a buyer's saved properties

Example:

curl -X DELETE https://maison-api.jollybush-a62cec71.uksouth.azurecontainerapps.io/api/users/bd70f994-5834-45b9-a6f0-8731e51ff0e6/saved-properties/fe08df1c-d24e-4f18-9c7b-cfbe842175f1

Response:

{
  "message": "Property removed from saved properties",
  "removed_property": {
    "user_id": "bd70f994-5834-45b9-a6f0-8731e51ff0e6",
    "property_id": "fe08df1c-d24e-4f18-9c7b-cfbe842175f1"
  }
}

Error Responses:

{
  "error": "User must be a buyer to manage saved properties"
}
{
  "error": "Property not found in saved properties"
}

PATCH /api/users/{user_id}/saved-properties/{property_id}/notes

Update notes for a saved property

Required fields:
- notes (string): The new notes for the saved property

Example:

curl -X PATCH https://maison-api.jollybush-a62cec71.uksouth.azurecontainerapps.io/api/users/bd70f994-5834-45b9-a6f0-8731e51ff0e6/saved-properties/fe08df1c-d24e-4f18-9c7b-cfbe842175f1/notes \
  -H "Content-Type: application/json" \
  -d '{
    "notes": "Great location, close to schools. Viewing scheduled for next week."
  }'

Response:

{
  "message": "Notes updated successfully",
  "saved_property": {
    "user_id": "bd70f994-5834-45b9-a6f0-8731e51ff0e6",
    "property_id": "fe08df1c-d24e-4f18-9c7b-cfbe842175f1",
    "notes": "Great location, close to schools. Viewing scheduled for next week.",
    "updated_at": "2024-02-25T16:30:00Z"
  }
}

Error Responses:

{
  "error": "User must be a buyer to update saved properties"
}
{
  "error": "Property not found in saved properties"
}
{
  "error": "notes field is required"
}

Offer Management

POST /api/users/{user_id}/offers

Create a new offer or counter-offer on a property

Required fields:
- property_id (UUID): The ID of the property - offer_amount (integer): The amount being offered

Optional fields for counter-offers:
- negotiation_id (UUID): Required when making a counter-offer

Example - New Offer:

curl -X POST https://maison-api.jollybush-a62cec71.uksouth.azurecontainerapps.io/api/users/bd70f994-5834-45b9-a6f0-8731e51ff0e6/offers \
  -H "Content-Type: application/json" \
  -d '{
    "property_id": "fe08df1c-d24e-4f18-9c7b-cfbe842175f1",
    "offer_amount": 350000
  }'

Example - Counter Offer:

curl -X POST https://maison-api.jollybush-a62cec71.uksouth.azurecontainerapps.io/api/users/3613c096-f41f-479f-a09f-7e0ab53b4eda/offers \
  -H "Content-Type: application/json" \
  -d '{
    "property_id": "fe08df1c-d24e-4f18-9c7b-cfbe842175f1",
    "offer_amount": 360000,
    "negotiation_id": "98de7487-c36f-4111-ba80-388f48a1f614"
  }'

Response:

{
  "message": "Offer submitted successfully",
  "negotiation": {
    "negotiation_id": "789fcdeb-51a2-4bc1-9e8d-326417400000",
    "property_id": "fe08df1c-d24e-4f18-9c7b-cfbe842175f1",
    "buyer_id": "bd70f994-5834-45b9-a6f0-8731e51ff0e6",
    "current_offer": 350000,
    "status": "active",
    "created_at": "2024-02-25T15:30:00Z",
    "last_offer_by": "bd70f994-5834-45b9-a6f0-8731e51ff0e6",
    "awaiting_response_from": "seller"
  }
}

Error Responses:

{
  "error": "User must be a buyer to make offers"
}
{
  "error": "Cannot make offer on your own property"
}
{
  "error": "Active negotiation already exists"
}
{
  "error": "Property not found"
}
{
  "error": "Unauthorized to make counter-offer"
}

PUT /api/users/{user_id}/offers/{negotiation_id}

Update an offer's status (accept/reject/cancel)

Required fields:
- action (string): Must be one of: "accept", "reject", "cancel"

Rules:
- Only the buyer or seller involved in the negotiation can update its status
- Can only accept/reject offers made by the other party
- Can only cancel your own most recent offer
- If buyer cancels their first offer, the entire negotiation is cancelled
- If cancelling a counter-offer, reverts to the previous offer from the other party

Examples:

# Accept an offer
curl -X PUT https://maison-api.jollybush-a62cec71.uksouth.azurecontainerapps.io/api/users/seller_id/offers/negotiation_id \
  -H "Content-Type: application/json" \
  -d '{
    "action": "accept"
  }'

# Reject an offer
curl -X PUT https://maison-api.jollybush-a62cec71.uksouth.azurecontainerapps.io/api/users/seller_id/offers/negotiation_id \
  -H "Content-Type: application/json" \
  -d '{
    "action": "reject"
  }'

# Cancel your offer/counter-offer
curl -X PUT https://maison-api.jollybush-a62cec71.uksouth.azurecontainerapps.io/api/users/user_id/offers/negotiation_id \
  -H "Content-Type: application/json" \
  -d '{
    "action": "cancel"
  }'

Response:

{
  "message": "Counter-offer cancelled, reverted to previous offer",
  "negotiation": {
    "negotiation_id": "123e4567-e89b-12d3-a456-426614174000",
    "property_id": "123e4567-e89b-12d3-a456-426614174001",
    "buyer_id": "123e4567-e89b-12d3-a456-426614174002",
    "seller_id": "123e4567-e89b-12d3-a456-426614174003",
    "current_offer_amount": 350000,
    "status": "active",
    "updated_at": "2024-02-22T12:00:00Z",
    "action_by": "123e4567-e89b-12d3-a456-426614174002",
    "property_status": "for_sale",
    "last_offer_by": "123e4567-e89b-12d3-a456-426614174002"
  }
}

Error Responses:

{
  "error": "Cannot accept/reject your own offer. Waiting for buyer response"
}
{
  "error": "Can only cancel your own most recent offer"
}
{
  "error": "Cannot update: negotiation is already accepted"
}

Query Parameters

Parameter Type Description Example
include_all bool Include all properties regardless of status ?include_all=true
min_price int Minimum price ?min_price=200000
max_price int Maximum price ?max_price=500000
bedrooms int Number of bedrooms ?bedrooms=3
bathrooms int Number of bathrooms ?bathrooms=2
city string City location ?city=London
property_type string Type of property ?property_type=semi-detached
has_garden bool Has garden ?has_garden=true
parking_spaces int Minimum parking spaces ?parking_spaces=2
status string Property status ?status=for_sale

Property Status Updates

Properties can have one of three statuses: for_sale, under_offer, or sold. Status transitions follow these rules:

Update Property Status

# Mark property as under offer
curl -X PUT https://maison-api.jollybush-a62cec71.uksouth.azurecontainerapps.io/api/properties/123e4567-e89b-12d3-a456-426614174000 \
  -H "Content-Type: application/json" \
  -d '{
    "status": "under_offer"
  }'

# Mark property as sold
curl -X PUT https://maison-api.jollybush-a62cec71.uksouth.azurecontainerapps.io/api/properties/123e4567-e89b-12d3-a456-426614174000 \
  -H "Content-Type: application/json" \
  -d '{
    "status": "sold"
  }'

# Return property to for_sale (only valid from under_offer)
curl -X PUT https://maison-api.jollybush-a62cec71.uksouth.azurecontainerapps.io/api/properties/123e4567-e89b-12d3-a456-426614174000 \
  -H "Content-Type: application/json" \
  -d '{
    "status": "for_sale"
  }'

Response:

{
  "message": "Property updated successfully",
  "status": "under_offer"
}

Error Response (Invalid Status):

{
  "error": "Invalid status transition from for_sale to sold"
}

Error Response (Updating Sold Property):

{
  "error": "Cannot update status of sold property"
}

Filter Properties by Status

# Get only properties for sale (default behavior)
curl https://maison-api.jollybush-a62cec71.uksouth.azurecontainerapps.io/api/properties

# Get properties under offer
curl https://maison-api.jollybush-a62cec71.uksouth.azurecontainerapps.io/api/properties?status=under_offer

# Get sold properties
curl https://maison-api.jollybush-a62cec71.uksouth.azurecontainerapps.io/api/properties?status=sold

# Get all properties regardless of status
curl https://maison-api.jollybush-a62cec71.uksouth.azurecontainerapps.io/api/properties?include_all=true

Recent Updates

  1. UUID Implementation

  2. Enhanced Validation

  3. Geocoding Support

  4. Required User Association

  5. Error Handling

  6. Azure Blob Storage Integration

Setup

Requirements

Installation

# Create virtual environment
python -m venv .venv
source .venv/bin/activate  # Linux/Mac
.venv\Scripts\activate     # Windows

# Install dependencies
pip install -r requirements.txt

# Set up environment variables
cp .env.example .env
# Edit .env with your database credentials

# Initialize database
flask db upgrade

Running Tests

pytest tests/

Database Structure

The database consists of several related tables:
- properties: Core property information
- addresses: Property location details
- property_specs: Property specifications
- property_features: Additional property features
- property_details: Detailed property information
- property_media: Property images and floorplans

API Response Codes

The API returns standard HTTP status codes:

Error responses include a message:

{
    "error": "Detailed error message"
}

Running Locally

  1. Build the Docker image:
docker build -t maison_property_api .
  1. Run the container:
docker run -d \
  -p 8000:8080 \
  -e DATABASE_URL="your_database_url" \
  -e FLASK_APP="wsgi.py" \
  -e FLASK_ENV="production" \
  --name maison_api \
  maison_property_api
  1. Test the API:
curl https://maison-api.jollybush-a62cec71.uksouth.azurecontainerapps.io/health

Error Responses

No error responses currently documented

Azure Container App Configuration

Setting Environment Variables and Secrets

  1. Set basic environment variables:
az containerapp update -n maison-api -g maison-rg \
  --set-env-vars \
  DATABASE_URL="postgresql://maisondbadmin:password@maison-db.postgres.database.azure.com/property_db?sslmode=require" \
  FLASK_APP="wsgi.py" \
  FLASK_ENV="production"
  1. Set the Azure Storage secret:
az containerapp secret set -n maison-api -g maison-rg \
  --secrets azure-storage-connection="your-connection-string"