feat: initial Jimi Gallery prototype
- 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
This commit is contained in:
152
README.md
Normal file
152
README.md
Normal file
@ -0,0 +1,152 @@
|
||||
# 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
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
```js
|
||||
// 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:
|
||||
|
||||
```js
|
||||
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:
|
||||
|
||||
```bash
|
||||
# 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.
|
||||
Reference in New Issue
Block a user