- Public site (Home/Artists/Exhibitions/News/About/Contact) with EN/KO/JA i18n - Admin panel with login, CRUD, image upload, multilingual editing - Exhibition slider/lightbox view - FastAPI + MongoDB backend, JWT auth - Docker Compose deployment, behind nginx at jimi.yakenator.io
4.3 KiB
Jimi Gallery
Contemporary art gallery website prototype — public site + admin panel. Design language inspired by Jack Shainman, Crossing Art, and Derek Eller.
Stack
- Frontend: Vanilla HTML/CSS/JS (no build step). i18n in EN/KO/JA. Exhibition slider. Per-field image upload with client-side downscale.
- Backend: FastAPI + Motor (async MongoDB driver) + PyJWT.
- Database: MongoDB 7.
- Deploy: Docker Compose — single-command bring-up.
Brand mark: purple circle with serif JM monogram (inline SVG in assets/app.js
and admin/admin.js), paired with a JIMI GALLERY wordmark in Cinzel.
Run it
cd /Users/jungwoochoi/Desktop/prototype/gallery
docker compose up --build
Then visit:
- Public site → http://localhost:5891
- Admin → http://localhost:5891/admin/ (password:
admin)
Ports (deliberately uncommon to avoid local collisions):
| Service | Host port | Container port |
|---|---|---|
| API + static | 5891 | 8000 |
| MongoDB | 47017 | 27017 |
First boot auto-seeds the demo data. Subsequent boots persist to the mongo_data volume.
Environment
Override via shell env or a .env at the repo root:
ADMIN_PASSWORD=your-password
JWT_SECRET=change-me-to-a-long-random-string
backend/.env.example lists all knobs.
Structure
gallery/
├── index.html, artists.html, ... public site pages
├── assets/
│ ├── data.js Store (API-backed, cached) + Auth (JWT)
│ ├── i18n.js EN/KO/JA dictionary, detection, switcher helpers
│ ├── app.js nav + footer + slider helpers
│ └── styles.css
├── admin/
│ ├── index.html login
│ ├── dashboard.html, artists.html, exhibitions.html, news.html, settings.html
│ ├── admin.js sidebar, image field, multilingual field, export/import
│ └── admin.css
├── backend/
│ ├── main.py FastAPI routes + static mount
│ ├── db.py Motor client
│ ├── auth.py JWT issue/verify
│ ├── seed.py demo seed
│ ├── requirements.txt
│ ├── Dockerfile
│ └── .env.example
└── docker-compose.yml
API surface
All writes require Authorization: Bearer <jwt>; reads are public.
POST /api/auth/login → {token}
GET /api/auth/me → {ok: true}
GET /api/settings
PUT /api/settings
GET /api/artists
GET /api/artists/{id}
PUT /api/artists/{id}
DELETE /api/artists/{id}
GET /api/exhibitions
GET /api/exhibitions/{id}
PUT /api/exhibitions/{id}
DELETE /api/exhibitions/{id}
GET /api/news
GET /api/news/{id}
PUT /api/news/{id}
DELETE /api/news/{id}
POST /api/admin/seed reset all collections to demo seed
GET /api/admin/export full DB snapshot
POST /api/admin/import replace full DB with uploaded snapshot
Interactive docs (Swagger UI) at http://localhost:5891/docs.
Frontend data access pattern
// On every page — inline script pattern:
(async () => {
await Store.load(); // fetches /api/settings + /api/artists + /api/exhibitions + /api/news, caches
renderChrome("home");
// ... accessors below are sync reads from cache ...
Store.artists();
Store.exhibition(id);
})();
// Admin mutations are async:
await Store.upsert("artists", artist);
await Store.remove("news", id);
await Store.updateSettings({...});
await Store.reset();
await Store.importAll(json);
await Store.exportAll();
Auth:
await Auth.login(password); // posts to /api/auth/login, stores JWT in localStorage
Auth.isAuthed(); // sync — presence check; API returns 401 if expired
Auth.logout(); // clears token
Dev (no Docker)
If you want to iterate on the backend without rebuilding the image:
# Mongo (container only — still uncommon port)
docker run -d -p 47017:27017 --name jimi-mongo mongo:7
# Backend
cd backend
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
MONGO_URL=mongodb://localhost:47017 STATIC_DIR=.. \
uvicorn main:app --reload --port 5891
Hot-reload on Python changes, frontend edits land on browser refresh.