Skip to content

Navigation Menu

Sign in
Sign up
This repository was archived by the owner on Aug 2, 2026. It is now read-only.

Repository files navigation

Beauty Salons Warsaw

A full-stack web app for browsing beauty salons in Warsaw. Built as a recruitment exercise.

Salon list Salon detail Salon edit

Stack

Layer Technology
Frontend React 19, Vite, Tailwind CSS
Backend NestJS, TypeORM
Database PostgreSQL
Data collection Google Places API (New) via Application Default Credentials
Monorepo pnpm workspaces

Prerequisites

  • Node.js 20+
  • pnpm 11+
  • Docker (for PostgreSQL)
  • A Google Cloud project with the Places API (New) enabled
  • gcloud CLI authenticated: gcloud auth application-default login

Setup

1. Clone and install

git clone <repo>
cd BeautySalons
pnpm install

2. Environment variables

cp .env.example .env

The defaults work with the Docker setup out of the box. No API key is needed. Authentication uses Application Default Credentials (ADC).

3. Start the database

docker compose up -d

4. Build shared types

pnpm --filter @beauty-salons/shared build

5. Seed the database

Either place the provided salons.json into packages/collector/, or run the full data pipeline (see below), then seed:

pnpm --filter @beauty-salons/backend seed

6. Run the app

pnpm dev

Frontend: http://localhost:5173
Backend API: http://localhost:3000


Data pipeline

Required before seeding. Collected data is not included in the repository.

Ensure ADC is set up first: gcloud auth application-default login

# 1. Collect salons from Google Places API
pnpm collect
# 2. Cache photos locally (downloads to packages/frontend/public/photos/)
pnpm cache-photos
# 3. Re-seed the database
pnpm --filter @beauty-salons/backend seed

Project structure

packages/
 shared/ # TypeScript interfaces shared between frontend and backend
 collector/ # One-shot script: queries Google Places API → salons.json
 backend/ # NestJS REST API
 frontend/ # React SPA

Design decisions

Data source: Google Places API (New)

Google Places provides the most complete and accurate data for Polish businesses — structured fields (address, phone, website, opening hours, price level), ratings with review counts, and photos with author attributions. The alternative would be scraping, which is fragile.

ADC is used instead of an API key so credentials are never embedded in source code or sent to the browser. The backend proxies photo requests via /api/photos/* for any references not yet cached locally.

Data quality

  • Deduplication by placeId — Google's stable identifier. Re-running the collector updates existing records rather than creating duplicates.
  • District extraction — derived from the address via regex matching Warsaw district names, since the Places API doesn't return an administrative district field directly.
  • Services — mapped from Google's place types (e.g. nail_salon, hair_care) to a normalised list.
  • Missing fields — nullable throughout. The UI degrades gracefully when rating, phone, website, or photos are absent.
  • Raw data preserved — the full API response is stored in _raw in salons.json, allowing the schema to be extended without re-collecting.

Photo caching

Google's ToS (section 5e) permits caching content as long as cached copies are not kept longer than allowed by the cache headers on the response. Photos are downloaded once via cache-photos and served as static files from public/photos/. In a production setup this would move server-side — a background job would check cache header expiry and refresh photos accordingly, storing them in object storage rather than the local filesystem.


Known limitations & what I'd improve with more time

  • Phone validation — the edit form accepts any string. A proper implementation would validate format (e.g. E.164) and normalise Polish numbers.
  • Server-side photo caching — currently photos are cached manually via CLI. A production version would have a background job that respects cache header expiry and refreshes photos automatically, storing them in object storage (S3/GCS) rather than the local filesystem.
  • No authentication — the edit endpoint is open. Would add auth before any real deployment.
  • Coverage — currently Warsaw only, queried district by district.

How would you scale to all of Poland?

The current collector queries fixed district centre coordinates with a 2.5km radius. Scaling to all of Poland would require:

  1. City list — a dataset of Polish cities/towns with coordinates (GUS provides this). Roughly 900 cities and thousands of smaller towns.
  2. Grid tiling — for dense cities, a single radius query misses results. A hexagonal grid tiling (e.g. H3) ensures full coverage without excessive overlap.
  3. Quota management — the Places API has per-minute and per-day quotas. The collector would need rate limiting, retry with backoff, and resumable state so interrupted runs don't restart from scratch.
  4. Incremental updates — rather than re-collecting everything, track lastCollectedAt per area and re-query on a schedule (weekly or monthly).
  5. Deduplication across areas — already handled by placeId, but a place near a tile boundary may be returned by multiple queries.

About

A simple full stack application for browsing Warsaw beauty salons. Uses google places api as the source of data.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

AltStyle によって変換されたページ (->オリジナル) /