尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證正在載入完整 README。 以下為已儲存的摘錄。
A full-stack Airbnb clone: browse and search homes, see listing details, book dates (no double-bookings), save wishlists, and switch to hosting to manage listings and reservations. The UI was built by measuring airbnb.co.in (sizes, spacing, colours) at 1440 and 1920 px wide. Payments, messaging and login are mocked.
No sign-up needed: open the profile menu (top right) → Log in as… and pick a seeded user.
Try Rohan Gupta (guest, has trips and a wishlist) and Aarav Mehta (Superhost, 7 listings and 30+ reservations).檢視完整 README ↗A full-stack Airbnb clone: browse and search homes, see listing details, book dates (no double-bookings), save wishlists, and switch to hosting to manage listings and reservations. The UI was built by measuring airbnb.co.in (sizes, spacing, colours) at 1440 and 1920 px wide. Payments, messaging and login are mocked.
| Live app | https://airbnb-clone-xk2g.vercel.app |
| Live API | https://airbnbclone-production-beea.up.railway.app (docs at /docs, health at /api/health) |
| Code | https://github.com/Mudrikaaa/Airbnb_clone |
No sign-up needed: open the profile menu (top right) → Log in as… and pick a seeded user. Try Rohan Gupta (guest, has trips and a wishlist) and Aarav Mehta (Superhost, 7 listings and 30+ reservations).
| Layer | Choice | Why |
|---|---|---|
| Frontend | Next.js 16 (App Router), React 19, TypeScript, Tailwind CSS v4 | Routing, Suspense and server metadata out of the box; design tokens in tailwind.config.ts |
| Data fetching | SWR | Cache keys include the user id, simple optimistic updates and infinite loading |
| UI libraries | react-day-picker + date-fns (calendars), lucide-react (icons), sonner (toasts), react-leaflet + OpenStreetMap tiles (map) | Each is used for one job only |
| Backend | Python 3.12, FastAPI, SQLAlchemy 2.0 (typed Mapped[] models), Pydantic v2, Uvicorn | Typed end to end; business rules live in plain service functions |
| Database | SQLite (file on a Railway volume in production) | Zero setup, enough for this scale; see assumptions |
| Tests | pytest (76 tests, in-memory SQLite) | Overlap rules, pricing, booking / host / search APIs, seed invariants, N+1 guards |
| Hosting | Vercel (frontend), Railway (backend + volume) |
You need Python 3.11+ and Node 20+.
cd backend
python -m venv .venv
.venv\Scripts\activate # macOS/Linux: source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # Windows: copy .env.example .env
uvicorn app.main:app --reload --port 8000
On first start the tables are created and the database is seeded automatically (only when it is empty). API docs: http://localhost:8000/docs. Run the tests with pytest.
cd frontend
npm install
cp .env.example .env.local # Windows: copy .env.example .env.local
npm run dev # http://localhost:3000
Checks: npm run lint, npx tsc --noEmit, npm run build.
| Variable | Where | Local value | Production value |
|---|---|---|---|
DATABASE_URL | backend | sqlite:///./airbnb.db | sqlite:////data/airbnb.db (Railway volume mounted at /data) |
FRONTEND_ORIGIN | backend (CORS) | http://localhost:3000 | http://localhost:3000,https://airbnb-clone-xk2g.vercel.app — comma-separated, exact origins, no trailing slash |
NEXT_PUBLIC_API_URL | frontend | http://localhost:8000 | https://airbnbclone-production-beea.up.railway.app — no trailing slash; it is baked in at build time, so redeploy Vercel after changing it |
.env.example files document all of these. Never commit real .env files (they are git-ignored).
Browser ── Next.js (Vercel) ── fetch + X-User-Id header ──▶ FastAPI (Railway) ──▶ SQLite (/data volume)
The frontend never computes money or availability: prices come from GET /listings/{id}/quote, dates from /availability, and the booking endpoint re-checks everything on the server.
backend/
app/
main.py app factory, CORS, error handlers, startup (create tables + seed if empty)
config.py settings from env vars (DATABASE_URL, FRONTEND_ORIGIN)
db.py engine, session, SQLite foreign keys switched on
deps.py get_current_user — mock auth from the X-User-Id header
errors.py domain errors -> {"detail": "..."} with the right status code
models/ one file per aggregate: user, listing (+images, amenities), booking, review, wishlist
schemas/ Pydantic request / response models
routers/ thin: parse input, call a service, return the result
services/ ** the business logic **
availability.py the single definition of "do two stays overlap" (Python + SQL versions)
pricing.py the single source of truth for prices (14% service fee, cleaning fee)
search.py filtered, paginated listing query (NOT EXISTS date filter, amenities = all)
bookings.py / listings.py / reviews.py / wishlists.py / users.py
seed/ idempotent seed (10 users, 49 listings, ~170 bookings, ~165 reviews, wishlists)
scripts/check_images.py HEAD-checks every seed image URL
tests/
frontend/
app/ routes: /, /rooms/[id], /book/[id], /trips, /wishlists, /hosting/*, /experiences, /services
components/ layout/ search/ listing/ listing-detail/ booking/ trips/ wishlist/ host/ ui/ auth/
lib/ api.ts (typed fetch + errors), types.ts, auth-context.tsx, search-params.ts (URL <-> filters), format.ts
Where the rules live
services/availability.py defines overlap as existing.check_in < new.check_out AND existing.check_out > new.check_in (so check-out day is bookable as someone else's check-in). Only confirmed bookings block dates. create_booking checks before and again right after inserting, so two simultaneous requests can't both win; the loser gets 409.services/pricing.py — subtotal = nightly × nights, service fee = 14 % (rounded half up), total = subtotal + cleaning + service.selectinload / one aggregated subquery; tests assert the query count doesn't grow with the number of rows.erDiagram
users ||--o{ listings : "hosts"
users ||--o{ bookings : "books"
users ||--o{ reviews : "writes"
users ||--o{ wishlist_items : "saves"
listings ||--o{ listing_images : "has (cascade delete)"
listings ||--o{ listing_amenities : ""
amenities ||--o{ listing_amenities : ""
listings ||--o{ bookings : "receives"
listings ||--o{ reviews : "receives"
listings ||--o{ wishlist_items : "saved in"
bookings |o--o| reviews : "one review per stay"
users {
int id PK
string name
string email UK
string avatar_url
text bio
bool is_superhost
datetime joined_at
}
listings {
int id PK
int host_id FK
string title
text description
string property_type
string category
string city
string state
string country
string address
float latitude
float longitude
int price_per_night "INR"
int cleaning_fee
int max_guests
int bedrooms
int beds
int bathrooms
bool is_active "false = soft-deleted"
datetime created_at
datetime updated_at
}
listing_images {
int id PK
int listing_id FK
string url
int position
}
amenities {
int id PK
string name UK
string icon "lucide name"
}
listing_amenities {
int listing_id PK, FK
int amenity_id PK, FK
}
bookings {
int id PK
int listing_id FK
int guest_id FK
date check_in
date check_out "CHECK > check_in"
int guests
int nightly_price "snapshot"
int nights
int cleaning_fee "snapshot"
int service_fee "snapshot"
int total_price "snapshot"
string status "confirmed | cancelled"
datetime created_at
}
reviews {
int id PK
int listing_id FK
int author_id FK
int booking_id FK, UK "nullable"
int rating "1-5"
int cleanliness "1-5, nullable"
int accuracy "1-5, nullable"
int check_in "1-5, nullable"
int communication "1-5, nullable"
int location "1-5, nullable"
int value "1-5, nullable"
text comment
datetime created_at
}
wishlist_items {
int user_id PK, FK
int listing_id PK, FK
datetime created_at
}
Integrity is enforced by the database itself, not only by the API: CHECK (check_out > check_in), CHECK (rating BETWEEN 1 AND 5) (and the same for the six category ratings), status CHECK, composite primary keys on the two link tables, a unique reviews.booking_id (one review per stay), foreign keys switched on for SQLite, and an index on bookings(listing_id, check_in, check_out) that serves the overlap query.
nightly_price, cleaning_fee, service_fee, total_price). If a host changes their price later, existing bookings, receipts and payouts must not change — so the numbers are copied at booking time instead of recalculated from the listing.AVG skips NULLs, so reviews without category scores don't pull a category down. A stored average can drift out of sync with the reviews; computing it on read (one grouped subquery joined to the listing list) can't. The trade-off is a little extra work per query, which is negligible at this size; a big site would cache the average on the listing and update it when a review is written.is_active = false). Guests' past trips and reviews still point at the listing, so a hard delete would break them (or erase history). A listing with upcoming confirmed reservations can't be deleted at all (409 with the reason). Deleted listings disappear from search, wishlists and the host's list, but remain in the database.All routes are under /api; errors are always {"detail": "..."} (422 also includes an errors list). "Optional" auth means the route works logged out and adds personal data (like is_wishlisted) when X-User-Id is sent.
| Method | Path | Auth | Purpose |
|---|---|---|---|
| GET | /health | – | Health check (touches the database) |
| GET | /users | – | Seeded users for the "Log in as…" menu (is_host = has at least one active listing) |
| GET | /users/me | required | Current user |
| GET | /meta/amenities · /meta/categories · /meta/property-types | – | Filter vocabularies |
| GET | /listings | optional | Search: location, check_in/check_out, guests, min_price/max_price, property_type (comma list), category, bedrooms/beds/bathrooms (minimums), amenities (must have all), page, page_size |
| GET | /listings/{id} | optional | Detail: images, amenities, host stats, rating summary (overall + six category averages) |
| GET | /listings/{id}/availability | – | Booked date ranges (for the calendar) |
| GET | /listings/{id}/quote | – | Price breakdown + whether the dates are free |
| GET / POST | /listings/{id}/reviews | – / required | Reviews; post only for your own completed stay (category ratings optional) |
| POST | /listings | required | Create a listing (you become its host) |
| PUT / DELETE | /listings/{id} | owner | Replace / soft-delete (409 if upcoming reservations) |
| POST | /bookings | required | Book; price recomputed server-side, 409 on overlap |
| GET | /bookings/me | required | My trips: upcoming / past / cancelled (each with and your ) |
localStorage and sends it as X-User-Id; the backend trusts it. This is fine for a demo, not for production, where this single dependency (deps.get_current_user) would be replaced by sessions or JWTs.Base.metadata.create_all on startup. The seed runs only when the database is empty, so restarts never duplicate data; python -m app.seed.seed --reset wipes and reseeds. Booking and review dates in the seed are generated relative to today, so there are always upcoming and past trips.scripts/check_images.py. Host-added photos are URLs, not uploads./hosting sends them to the intro page.--reset (see docs/DEPLOY.md).next/font) with sizes, line heights and weights measured on airbnb.co.in and kept as named tokens in ./users.pytest.See docs/DEPLOY.md for the exact Vercel and Railway settings, the one-time production reset, and a smoke test for the deployed links.
Header tab icons, the compact search pill's house and the "Guests mention" chip icons are Fluent Emoji 3D images by Microsoft, used under the MIT licence (frontend/public/icons/).
has_reviewreview_rating| POST | /bookings/{id}/cancel | guest | Cancel (up to the day before check-in) |
| GET | /host/listings · /host/bookings | required | My listings; bookings on my listings (with payout) |
| GET | /wishlists | required | Saved listings |
| PUT / DELETE | /wishlists/{listing_id} | required | Save / unsave (idempotent) |
tailwind.config.ts