dsyoon 5ea6d6e5b0 init
2026-02-25 21:20:33 +09:00
2026-02-25 21:20:33 +09:00
2026-02-25 21:20:33 +09:00
2026-02-25 21:10:12 +09:00
2026-02-25 19:13:29 +09:00
2026-02-25 19:13:29 +09:00
2026-02-25 19:13:29 +09:00
2026-02-25 19:13:29 +09:00
2026-02-25 19:13:29 +09:00
2026-02-25 19:13:29 +09:00
2026-02-25 19:13:29 +09:00

Links (개인 링크 홈)

정적 파일(HTML/CSS/JS)만으로 만든 개인 링크 대시보드입니다.

사용법

  • 가장 간단한 방법: index.html을 브라우저로 열기

    • 즐겨찾기/추가/편집/삭제/정렬/검색/가져오기/내보내기 기능은 정상 동작합니다.
    • 기본 링크 목록은 index.html 내부의 linksData(JSON)에서 읽기 때문에 파이썬 실행 없이도 순서가 그대로 반영됩니다.
  • (선택) links.json을 별도 파일로 운용하고 싶다면 로컬 서버로 실행

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회)

psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -f db/schema.sql

2) 실행 방법 A: (로컬/간단) venv로 실행

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회 설치

cd /path/to/home
conda activate ncue
python -m pip install -r requirements.txt
python -m pip install gunicorn

B-2) 단독 실행(테스트)

conda activate ncue
cd /path/to/home
gunicorn -w 2 -b 127.0.0.1:8023 flask_app:app

확인:

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:

[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) 로 통일하는 것을 권장합니다.

적용/재시작:

sudo systemctl daemon-reload
sudo systemctl enable --now ncue-flask
sudo systemctl restart ncue-flask
sudo systemctl status ncue-flask --no-pager

로그 확인:

sudo journalctl -u ncue-flask -n 200 --no-pager

4) Apache 설정(정적은 Apache, /api/*만 8023 프록시)

필요 모듈(Ubuntu/Debian):

sudo a2enmod proxy proxy_http headers
sudo systemctl reload apache2

가상호스트 예시:

<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>

적용:

sudo apachectl -t && sudo systemctl reload apache2

5) 503(Service Unavailable) 트러블슈팅 체크리스트

브라우저 콘솔에서 503이 뜨면 대부분 Apache가 127.0.0.1:8023 백엔드로 프록시했는데 백엔드가 응답을 못하는 상태입니다.

  • 백엔드가 살아있는지:
curl -i http://127.0.0.1:8023/healthz
curl -i http://127.0.0.1:8023/api/config/auth
  • 서비스 로그:
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.htmlwindow.AUTH_CONFIG.apiBase에 API 주소를 넣고, Flask에서는 CORS_ORIGINS로 허용 도메인을 지정하세요.

최초 로그인 사용자는 DB에 저장되며 기본값은 is_admin=false입니다. 관리자 승격 방법:

  • (권장) 서버 .envADMIN_EMAILS를 설정하면, 해당 이메일 사용자가 로그인할 때 /api/auth/sync에서 자동으로 is_admin=true로 승격됩니다. (프론트로 노출되지 않음)

  • API(서버 .envCONFIG_TOKEN 설정 후):

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로 직접:
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가 남아있는 경우.
    • 해결(콘솔에서 실행):
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:[...]})을 다시 불러옵니다.
Description
No description provided
Readme 192 KiB
Languages
JavaScript 42.3%
HTML 31.7%
Python 15.6%
CSS 10.4%