Skip to content
developerashkanPublic

About

No description, website, or topics provided.

Resources

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

Repository files navigation

رسته · Raste

An original, production-minded Persian classifieds marketplace (the genre of Divar / Craigslist): local listings by city and category, rich per-category attributes, phone + OTP sign-in, buyer–seller chat, moderation, and a security posture designed in from the start.

  • Backend: Python 3.12, FastAPI, SQLite (WAL + FTS5), Pillow
  • Frontend: dependency-free ES-module SPA (no build step), RTL-first, light/dark, mobile-first
  • Tests: 43 pytest tests (security + functional)

Quick start

.\run.ps1            # Windows: creates .venv, installs deps, seeds demo data, starts the server
./run.sh             # macOS / Linux

Open http://127.0.0.1:8000. Re-seed with .\run.ps1 -Reset (~1.5 min for 180 listings with images).

Demo accounts (in development the OTP is shown in the sign-in dialog; nothing is sent by SMS):

Role Phone
Admin 09120000000
Seller 09121111111
Buyer 09122222222

Run the tests: .venv\Scripts\python -m pytest -q tests API explorer (dev only): http://127.0.0.1:8000/api/docs


Features

Buyers

  • City picker (asked on first visit, remembered), category tree, full-text Persian search with category suggestions
  • Filters generated from each category's schema: districts, price range (shown in words, e.g. «۱۲ میلیون تومان»), area/year/mileage ranges, multi-select options, "has elevator", "has photos"
  • Sort by newest / cheapest / most expensive; infinite scroll with keyset pagination; back button restores the list and scroll position
  • Ad page: swipeable gallery + lightbox, key-facts grid, features, safety warnings, similar ads, share, bookmark, report
  • Contact reveal (login + rate-limited) or in-app chat with read receipts
  • Recently-viewed list kept only on the device

Sellers

  • Category-first posting wizard with dynamic fields, inline validation mirrored from the server, Persian-digit input, price-in-words
  • Multi-image upload: drag & drop, client-side downscale, progress, retry, choose cover
  • Draft autosave, edit, delete, renew near expiry (ads live 30 days), status badges, rejection reasons, view counts
  • Choose contact channels (phone and/or chat)

Account & safety

  • Phone + 6-digit OTP sign-in, active-device list with remote sign-out
  • Auto-moderation (blocked contact info, banned terms → review queue), user reports
  • Admin console: review queue with approve/reject reasons, reports, bans (revokes sessions, hides ads), all audited

UX quality

  • True RTL using CSS logical properties; Vazirmatn font; Persian digits & dates (Intl fa-IR)
  • Responsive: sidebar layout on desktop, bottom navigation + bottom sheets on phones
  • Accessible: semantic landmarks, skip link, focus-trapped dialogs with inert background, ARIA combobox search, keyboard-navigable gallery, visible focus, prefers-reduced-motion
  • Skeleton loaders, empty/error states with retry, optimistic chat sends

Architecture

raste/
├─ app/
│  ├─ main.py          app factory: middleware, error mapping, static files, SPA + OG-meta injection, janitor task
│  ├─ config.py        env-driven settings with safe production defaults
│  ├─ security.py      tokens, OTP HMAC, rate limiter, origin check, CSP & security headers
│  ├─ deps.py          DB connection, session resolution, CSRF check, role guards
│  ├─ db.py / schema.sql   SQLite schema, transactions, FTS indexing, audit log
│  ├─ catalog.py       categories + attribute schemas + cities (single source of truth)
│  ├─ text.py          Persian normalisation, phone parsing, contact-info detection, safe FTS queries
│  ├─ serializers.py   row → API shapes
│  └─ routers/         auth · account(meta, me) · posts · media · chat · admin
├─ web/
│  ├─ index.html
│  └─ assets/
│     ├─ css/app.css   design tokens, components, light/dark themes
│     └─ js/
│        ├─ core/      dom (safe builder), api, router, store, format
│        ├─ components/ shell, auth, ui (modal/toast), gallery, uploader, postCard, icons, states
│        └─ pages/     list, post, editor, my, chat, admin, static
├─ scripts/seed.py     demo data with generated images
└─ tests/              pytest suite

Key decisions

  • Schema-driven categories. catalog.py defines each category's attributes once; the API validates against it (unknown keys dropped) and the client renders both the posting form and the search filters from the same definition served by /api/meta.
  • Keyset pagination (bumped_at,id / price,id cursors) keeps infinite scroll stable and O(1) per page.
  • Persian search normalises ي/ی, ك/ک, digits, ZWNJ and diacritics identically on index and query, with prefix matching.
  • No build step, no runtime third-party JS — the whole client is first-party ES modules, which makes a strict script-src 'self' CSP possible.
  • SQLite is right for a single node (WAL, FTS5, JSON1). The data layer is plain SQL behind small helpers, so moving to PostgreSQL (+ pg_trgm/OpenSearch) is mechanical.

API overview

Method Path Notes
POST /api/auth/otp/request · /api/auth/otp/verify phone sign-in
GET/POST /api/auth/me · /api/auth/logout · /api/auth/sessions session & devices
GET /api/meta categories (with attribute schemas) and cities
GET /api/posts?city&category&q&district&price_min&price_max&has_image&sort&cursor&a.<attr>… search
GET /api/posts/{id} · /api/posts/{id}/similar detail
POST/PUT/DELETE /api/posts · /api/posts/{id} create / edit / delete (owner)
POST /api/posts/{id}/contact · /bookmark · /report · /renew interactions
POST /api/media/images upload (re-encoded)
GET/POST /api/chat/conversations[/{id}/messages] · /api/chat/unread chat
GET/PATCH /api/me · /api/me/posts · /api/me/bookmarks my area
* /api/admin/… moderation (admin only)

Errors are uniform: {"error": {"code", "message" (Persian, user-facing), "fields"?: {field: message}, "retry_after"?}}.


Production deployment

export RASTE_ENV=production
export RASTE_SECRET_KEY="$(python -c 'import secrets;print(secrets.token_urlsafe(48))')"
export RASTE_ALLOWED_ORIGINS=https://raste.example
export RASTE_ADMIN_PHONES=09xxxxxxxxx
export RASTE_TRUST_PROXY=1          # only behind your own reverse proxy
python -m uvicorn app.main:_app_factory --factory --host 127.0.0.1 --port 8000 --proxy-headers --workers 1

In production mode: OTP codes are never returned by the API, cookies are Secure, HSTS is sent, and /api/docs is disabled.

Checklist before real traffic:

  1. Put it behind HTTPS (nginx/Caddy) with request-size and connection limits.
  2. Wire send_sms() in app/routers/auth.py to an SMS provider.
  3. Move rate limiting to Redis if you run more than one process (RateLimiter has a single hit() method to swap).
  4. Serve /media from object storage/CDN; keep the nosniff header.
  5. Back up data/raste.db (e.g. Litestream) or migrate to PostgreSQL when write volume grows.
  6. Self-host the Vazirmatn font to drop the Google Fonts allowance from the CSP.

See SECURITY.md for the threat model and controls.

About

No description, website, or topics provided.

Resources

Security policy

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages