- ncue_user에 is_admin 추가 및 can_manage 호환 유지 - /api/auth/sync 및 관리 API를 DB is_admin 기반으로 변경 - index.html 폴백에서 관리자 이메일 하드코딩 제거 - .env로 관리자 부트스트랩(ADMIN_EMAILS) 지원 Co-authored-by: Cursor <cursoragent@cursor.com>
258 lines
8.2 KiB
Markdown
258 lines
8.2 KiB
Markdown
# Links (개인 링크 홈)
|
|
|
|
정적 파일(HTML/CSS/JS)만으로 만든 개인 링크 대시보드입니다.
|
|
|
|
## 사용법
|
|
|
|
- **가장 간단한 방법**: `index.html`을 브라우저로 열기
|
|
- 즐겨찾기/추가/편집/삭제/정렬/검색/가져오기/내보내기 기능은 정상 동작합니다.
|
|
- 기본 링크 목록은 `index.html` 내부의 `linksData`(JSON)에서 읽기 때문에 **파이썬 실행 없이도** 순서가 그대로 반영됩니다.
|
|
|
|
- (선택) `links.json`을 별도 파일로 운용하고 싶다면 로컬 서버로 실행
|
|
|
|
```bash
|
|
python3 -m http.server 8000
|
|
```
|
|
|
|
그 후 브라우저에서 `http://localhost:8000`으로 접속합니다.
|
|
|
|
## 백엔드(Flask) + PostgreSQL 사용자 저장
|
|
|
|
로그인 후 사용자 정보를 `ncue_user`에 저장하고(Upsert), 로그인/로그아웃 시간을 기록하며,
|
|
`/api/config/auth`로 Auth0 설정을 공유하려면 백엔드 서버가 필요합니다.
|
|
|
|
현재 백엔드는 **Python Flask(기본 포트 8023)** 로 제공합니다. (정적 HTML/JS는 그대로 사용 가능)
|
|
|
|
### 핵심 엔드포인트
|
|
|
|
- `GET /healthz`: DB 연결 헬스체크
|
|
- `GET /api/config/auth`: Auth0 설정 조회(우선순위: `.env` → DB `ncue_app_config`)
|
|
- `POST /api/auth/sync`: 로그인 시 `ncue_user` upsert(최초/마지막 로그인 시각, `can_manage` 갱신)
|
|
- `POST /api/auth/logout`: 로그아웃 시각 기록
|
|
|
|
### 1) DB 테이블 생성(서버에서 1회)
|
|
|
|
```bash
|
|
psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -f db/schema.sql
|
|
```
|
|
|
|
### 2) 실행 방법 A: (로컬/간단) venv로 실행
|
|
|
|
```bash
|
|
python3 -m venv .venv
|
|
source .venv/bin/activate
|
|
pip install -r requirements.txt
|
|
PORT=8023 python flask_app.py
|
|
```
|
|
|
|
### 3) 실행 방법 B: (운영/권장) Miniconda 환경 `ncue` + gunicorn + systemd
|
|
|
|
#### B-1) 최초 1회 설치
|
|
|
|
```bash
|
|
cd /path/to/home
|
|
conda activate ncue
|
|
python -m pip install -r requirements.txt
|
|
python -m pip install gunicorn
|
|
```
|
|
|
|
#### B-2) 단독 실행(테스트)
|
|
|
|
```bash
|
|
conda activate ncue
|
|
cd /path/to/home
|
|
gunicorn -w 2 -b 127.0.0.1:8023 flask_app:app
|
|
```
|
|
|
|
확인:
|
|
|
|
```bash
|
|
curl -s http://127.0.0.1:8023/healthz
|
|
curl -s http://127.0.0.1:8023/api/config/auth
|
|
```
|
|
|
|
#### B-3) systemd 서비스(예시)
|
|
|
|
`/etc/systemd/system/ncue-flask.service`:
|
|
|
|
```ini
|
|
[Unit]
|
|
Description=NCUE Flask API (gunicorn)
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=www-data
|
|
Group=www-data
|
|
WorkingDirectory=/path/to/home
|
|
|
|
# miniconda 경로는 설치 위치에 맞게 수정하세요.
|
|
ExecStart=/bin/bash -lc 'source /opt/miniconda3/etc/profile.d/conda.sh && conda activate ncue && gunicorn -w 2 -b 127.0.0.1:8023 flask_app:app'
|
|
Restart=always
|
|
RestartSec=3
|
|
|
|
# (권장) .env 파일을 systemd가 읽도록 설정
|
|
EnvironmentFile=/path/to/home/.env
|
|
|
|
# (선택) DB 연결 옵션(문제 발생 시 조정)
|
|
Environment=DB_SSLMODE=prefer
|
|
Environment=DB_CONNECT_TIMEOUT=5
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
```
|
|
|
|
> 중요:
|
|
> - `Environment=`는 반드시 `[Service]` 섹션에 있어야 합니다. `[Install]` 아래에 두면 무시됩니다.
|
|
> - `Environment=`로 Auth0/DB 값을 적어두면 `.env`보다 우선할 수 있으니, 운영에서는 **한 군데(.env)** 로 통일하는 것을 권장합니다.
|
|
|
|
적용/재시작:
|
|
|
|
```bash
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl enable --now ncue-flask
|
|
sudo systemctl restart ncue-flask
|
|
sudo systemctl status ncue-flask --no-pager
|
|
```
|
|
|
|
로그 확인:
|
|
|
|
```bash
|
|
sudo journalctl -u ncue-flask -n 200 --no-pager
|
|
```
|
|
|
|
### 4) Apache 설정(정적은 Apache, `/api/*`만 8023 프록시)
|
|
|
|
필요 모듈(Ubuntu/Debian):
|
|
|
|
```bash
|
|
sudo a2enmod proxy proxy_http headers
|
|
sudo systemctl reload apache2
|
|
```
|
|
|
|
가상호스트 예시:
|
|
|
|
```apacheconf
|
|
<VirtualHost *:80>
|
|
ServerName ncue.net
|
|
|
|
DocumentRoot /path/to/home
|
|
<Directory /path/to/home>
|
|
Require all granted
|
|
</Directory>
|
|
|
|
ProxyPreserveHost On
|
|
RequestHeader set X-Forwarded-Proto "https"
|
|
|
|
ProxyPass /api/ http://127.0.0.1:8023/api/
|
|
ProxyPassReverse /api/ http://127.0.0.1:8023/api/
|
|
ProxyPass /healthz http://127.0.0.1:8023/healthz
|
|
ProxyPassReverse /healthz http://127.0.0.1:8023/healthz
|
|
</VirtualHost>
|
|
```
|
|
|
|
적용:
|
|
|
|
```bash
|
|
sudo apachectl -t && sudo systemctl reload apache2
|
|
```
|
|
|
|
### 5) 503(Service Unavailable) 트러블슈팅 체크리스트
|
|
|
|
브라우저 콘솔에서 503이 뜨면 대부분 **Apache가 127.0.0.1:8023 백엔드로 프록시했는데 백엔드가 응답을 못하는 상태**입니다.
|
|
|
|
- 백엔드가 살아있는지:
|
|
|
|
```bash
|
|
curl -i http://127.0.0.1:8023/healthz
|
|
curl -i http://127.0.0.1:8023/api/config/auth
|
|
```
|
|
|
|
- 서비스 로그:
|
|
|
|
```bash
|
|
sudo journalctl -u ncue-flask -n 200 --no-pager
|
|
```
|
|
|
|
- Apache 프록시 로그:
|
|
- Ubuntu/Debian: `/var/log/apache2/error.log`
|
|
- RHEL/CentOS: `/var/log/httpd/error_log`
|
|
|
|
> 참고: `flask_app.py`는 DB가 일시적으로 죽어도 앱 임포트 단계에서 바로 죽지 않도록(DB pool lazy 생성) 개선되어,
|
|
> “백엔드 프로세스가 안 떠서 Apache가 503”인 케이스를 줄였습니다.
|
|
|
|
관리 권한은 DB 사용자 테이블(`ncue_user`)의 `is_admin=true`로 부여됩니다.
|
|
(`can_manage`는 호환을 위해 남겨두며, 서버는 `is_admin`을 기준으로 동작합니다.)
|
|
|
|
### (선택) 역프록시/분리 배포
|
|
|
|
- Flask가 정적까지 함께 서빙: `http://ncue.net:8023`로 접속 (same-origin)
|
|
- 정적은 별도 호스팅 + API만 Flask: `index.html`의 `window.AUTH_CONFIG.apiBase`에 API 주소를 넣고,
|
|
Flask에서는 `CORS_ORIGINS`로 허용 도메인을 지정하세요.
|
|
|
|
최초 로그인 사용자는 DB에 저장되며 기본값은 `is_admin=false`입니다. 관리자 승격 방법:
|
|
|
|
- (권장) 서버 `.env`에 `ADMIN_EMAILS`를 설정하면, 해당 이메일 사용자가 로그인할 때 `/api/auth/sync`에서 자동으로
|
|
`is_admin=true`로 승격됩니다. (프론트로 노출되지 않음)
|
|
|
|
- API(서버 `.env`에 `CONFIG_TOKEN` 설정 후):
|
|
|
|
```bash
|
|
curl -sS -X POST http://127.0.0.1:8023/api/admin/users/set_admin \
|
|
-H "Content-Type: application/json" \
|
|
-H "X-Config-Token: $CONFIG_TOKEN" \
|
|
-d '{"email":"me@example.com","isAdmin":true}'
|
|
```
|
|
|
|
- 또는 SQL로 직접:
|
|
|
|
```sql
|
|
update ncue_user set is_admin = true, can_manage = true where email = 'me@example.com';
|
|
```
|
|
|
|
## 로그인(관리 기능 잠금)
|
|
|
|
이 프로젝트는 **정적 사이트**에서 동작하도록, 관리 기능(추가/편집/삭제/가져오기)을 **로그인 후(관리자 이메일)** 에만 활성화할 수 있습니다.
|
|
|
|
- **지원 방식**: Auth0 SPA SDK + Auth0 Universal Login
|
|
- **구글/카카오/네이버**: Auth0 대시보드에서 Social/Custom OAuth 연결로 구성합니다.
|
|
|
|
설정 방법(.env):
|
|
|
|
1. Auth0에서 **Single Page Application** 생성
|
|
2. `.env`에 아래 값을 설정
|
|
- `AUTH0_DOMAIN`
|
|
- `AUTH0_CLIENT_ID`
|
|
- `AUTH0_GOOGLE_CONNECTION` (예: `google-oauth2`)
|
|
- (선택) `CONFIG_TOKEN` (관리자 승격 API 보호용)
|
|
3. Auth0 Application 설정에서 아래 URL들을 등록
|
|
- Allowed Callback URLs: `https://ncue.net/` 와 `https://ncue.net`
|
|
- Allowed Logout URLs: `https://ncue.net/` 와 `https://ncue.net`
|
|
- Allowed Web Origins: `https://ncue.net`
|
|
- Allowed Origins (CORS): `https://ncue.net`
|
|
4. Applications → (해당 앱) → Connections 탭에서 `google-oauth2`를 Enable(ON)
|
|
|
|
### 자주 발생하는 오류
|
|
|
|
- `https://ncue.net/authorize ... Not Found`
|
|
- 원인: `AUTH0_DOMAIN`을 사이트 도메인(`ncue.net`)로 넣은 경우. Auth0 테넌트 도메인(예: `dev-xxxx.us.auth0.com`)을 넣어야 합니다.
|
|
- `invalid_request: Unknown client: ...`
|
|
- 원인: `AUTH0_DOMAIN`(테넌트)와 `AUTH0_CLIENT_ID`(앱)가 서로 다른 Auth0 테넌트에 속해 매칭이 안 되는 경우.
|
|
- 값 변경 후에도 로그인 URL이 바뀌지 않음
|
|
- 원인: 브라우저 `localStorage`에 이전 설정 override가 남아있는 경우.
|
|
- 해결(콘솔에서 실행):
|
|
|
|
```js
|
|
localStorage.removeItem("links_home_auth_override_v1");
|
|
localStorage.removeItem("links_home_auth_tokens_v1");
|
|
sessionStorage.removeItem("links_home_auth_pkce_v1");
|
|
location.reload();
|
|
```
|
|
|
|
## 데이터 저장
|
|
|
|
- 기본 링크: `links.json`
|
|
- 사용자가 추가/편집/삭제한 내용: 브라우저 `localStorage`에 저장됩니다.
|
|
- 내보내기: 현재 화면 기준 링크를 JSON으로 다운로드합니다.
|
|
- 가져오기: 내보내기 JSON(배열 또는 `{links:[...]}`)을 다시 불러옵니다.
|