Pular para o conteúdo

Listings Worker

The listings worker handles property listing CRUD operations and image uploads to R2.

API Endpoints

MethodPathAuthDescription
POST/api/listings/upload-image✅Upload a listing image to R2
POST/api/listings✅Create a new listing
GET/api/listings—List public listings (paginated)
GET/api/listings/me✅List my listings
GET/api/listings/user/:id—List user’s public listings
GET/api/listings/:id—Get a single listing
PUT/api/listings/:id✅Update my listing
DELETE/api/listings/:id✅Delete my listing

Full Flow: Create a Listing

1. POST /api/listings/upload-image (multipart/form-data, auth required)
2. POST /api/listings (JSON with image URLs, auth required)

POST /api/listings/upload-image

Upload a property image to R2. Requires auth.

Request

multipart/form-data with field image.

Validation

RuleValue
FormatsJPEG, PNG, WebP, GIF
Max file size5 MB
Min dimensions200×200 px
Max dimensions8192×8192 px
ValidationMagic-byte (extension not trusted)

Response 200 OK

{
"url": "https://{r2}/listings/temp/{userId}/{uuid}.jpg",
"width": 1200,
"height": 800,
"size": 123456
}
  • Images are stored temporarily under listings/temp/{userId}/
  • On listing creation, they are moved to listings/images/{listingId}/
  • Rate limit: 10 uploads / 60 sec per user

POST /api/listings

Create a new property listing. Requires auth.

Request body

{
"title": "Casa com 3 quartos em Florianópolis",
"description": "Linda casa com piscina e vista para o mar...",
"price": 850000,
"property_type": "casa",
"city": "Florianópolis",
"state_code": "SC",
"bedrooms": 3,
"bathrooms": 2,
"area_m2": 180,
"phone": "48999999999",
"whatsapp": "48999999999",
"images": [
"https://{r2}/listings/temp/{userId}/{uuid1}.jpg"
]
}
FieldTypeRequiredRules
titlestring✅5–200 characters
descriptionstring✅10–5000 characters
pricenumber✅> 0 (in BRL, whole number)
property_typestring✅casa, apartamento, terreno, comercial, chacara
citystring❌Free text
state_codestring❌2-letter Brazilian UF
bedroomsnumber❌Integer
bathroomsnumber❌Integer
area_m2number❌Positive number
phonestring❌10–13 digits
whatsappstring❌10–13 digits
imagesstring[]✅At least 1 R2 URL required

Business Rules

  • 1 free listing per user. Additional listings require an active subscription.
  • Returns 402 Payment Required with code SUBSCRIPTION_REQUIRED if limit exceeded.
  • Images are moved from temp/ to listings/images/{listingId}/ on creation.

Response 201 Created

{
"listing": {
"id": "uuid",
"user_id": "uuid",
"title": "Casa com 3 quartos em Florianópolis",
"description": "Linda casa com piscina...",
"price": 850000,
"property_type": "casa",
"city": "Florianópolis",
"state_code": "SC",
"bedrooms": 3,
"bathrooms": 2,
"area_m2": 180,
"phone": "48999999999",
"whatsapp": "48999999999",
"images": ["https://{r2}/listings/images/{listingId}/{uuid}.jpg"],
"status": "active",
"created_at": "2026-01-01T00:00:00.000Z",
"updated_at": "2026-01-01T00:00:00.000Z"
}
}

GET /api/listings

List public listings. No auth required.

Query params

ParamDefaultMax
page1—
limit2050

Response 200 OK

{
"listings": [ ... ],
"total": 100,
"page": 1,
"limit": 20,
"totalPages": 5
}
  • Only returns listings with status = 'active'
  • Rate limit: 30 req / 60 sec per IP

GET /api/listings/me

List all listings for the authenticated user. Requires auth.

Response 200 OK

{
"listings": [ ...full Listing objects... ]
}
  • Returns ALL statuses (active, sold, pending, paused)
  • Ordered by created_at DESC

GET /api/listings/user/:userId

List public listings for a specific user. No auth required.

Query params

Same as GET /api/listings (page, limit).

Response 200 OK

Paginated listing list, only status = 'active'.


GET /api/listings/:id

Get a single listing.

Response 200 OK

{
"listing": { ... }
}
  • If status !== 'active', requires auth and ownership to view
  • Returns 404 for hidden listings

PUT /api/listings/:id

Update a listing. Requires auth + ownership.

Request body

Same schema as create, but all fields optional. You may also include:

FieldValues
statusactive, sold, pending, paused

Response 200 OK

Updated listing object.

  • Replaces images in listing_images table
  • Rate limit: 10 updates / 60 sec per user

DELETE /api/listings/:id

Delete a listing. Requires auth + ownership.

Response 200 OK

{
"message": "Anúncio excluído com sucesso"
}
  • Also cleans up R2 objects under listings/images/{id}/

Data Model

Listings are stored in the listings table in D1:

CREATE TABLE listings (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL,
title TEXT NOT NULL,
description TEXT,
price INTEGER,
city_id INTEGER,
state_code TEXT,
property_type_id INTEGER,
status TEXT DEFAULT 'active',
images TEXT, -- JSON array of image URLs
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

Development

Terminal window
cd listings-worker
npm run dev

Deployment

Terminal window
cd listings-worker && npm run deploy