File size: 3,639 Bytes
36fc296
 
 
9b6f854
36fc296
9b6f854
36fc296
 
 
 
a3b7c3c
36fc296
 
 
9b6f854
 
36fc296
 
 
1daba9a
edc7b9c
36fc296
 
 
 
 
edc7b9c
 
 
36fc296
 
 
 
 
edc7b9c
9b6f854
 
a3b7c3c
 
9b6f854
edc7b9c
36fc296
9b6f854
edc7b9c
 
 
 
9b6f854
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36fc296
 
 
 
 
9b6f854
 
36fc296
 
9b6f854
a3b7c3c
 
 
 
 
 
36fc296
 
 
edc7b9c
36fc296
 
f6f1db4
9b6f854
36fc296
9b6f854
36fc296
 
 
 
 
 
9b6f854
36fc296
 
 
a3b7c3c
36fc296
 
9b6f854
 
 
36fc296
 
 
 
 
 
 
9b6f854
a3b7c3c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import sys
import os
from werkzeug.serving import run_simple

sys.path.append(os.path.dirname(os.path.abspath(__file__)))

from dotenv import load_dotenv

load_dotenv(dotenv_path=os.path.join(os.path.dirname(__file__), ".env"))

from flask import Flask, send_from_directory
from flask_cors import CORS
from constants import Constants
from api.api_blueprint import api_blueprint
from models.base import db  # 保留原本 import(即使現在沒用到)
from models.combined_labels import CombinedLabels  # 同上

import logging


# --- 1. 確保 sessions 目錄有合理預設值 ---
SESSION_DIR = Constants.SESSIONS_DIR_NAME or "/app/sessions"
Constants.SESSIONS_DIR_NAME = SESSION_DIR


def create_session_dir():
    path = Constants.SESSIONS_DIR_NAME
    if not os.path.isdir(path):
        os.makedirs(path, exist_ok=True)


def create_app():
    create_session_dir()

    # --- 2. 靜態檔:dist/ ---
    # static_folder 指向 Vite build 出來的 dist
    # static_url_path 設成 "",讓 /assets/... 之類的路徑正確
    app = Flask(__name__, static_folder="dist", static_url_path="")

    # --- 3. API 一律掛在 /api ---
    app.register_blueprint(api_blueprint, url_prefix="/api")

    # --- 4. 簡單 health check ---
    @app.route("/api/ping")
    def ping():
        return {"status": "ok"}

    # --- 5. React / Vite SPA:所有非靜態路徑 fallback 到 index.html ---
    @app.route("/", defaults={"path": ""})
    @app.route("/<path:path>")
    def serve_spa(path: str):
        """
        1. 如果 dist 裡真的有這個檔案,就當成靜態檔案回傳
        2. 如果沒有,就回 dist/index.html,交給 React Router 處理
           例如 /case/PanTS_00009391 這種 route
        """
        # 先嘗試當成靜態檔
        if path:
            full_path = os.path.join(app.static_folder, path)
            if os.path.isfile(full_path):
                return send_from_directory(app.static_folder, path)

        # 找不到對應實體檔案 → 交給前端路由
        return send_from_directory(app.static_folder, "index.html")

    # --- 6. Log 過濾:不要一直印 /api/progress ---
    class FilterProgressRequests(logging.Filter):
        def filter(self, record):
            return "/api/progress/" not in record.getMessage()

    logging.getLogger("werkzeug").addFilter(FilterProgressRequests())

    # --- 7. CORS ---
    CORS(app)

    # --- 8. SharedArrayBuffer / WebGL 相關 header ---
    @app.after_request
    def add_security_headers(response):
        response.headers["Cross-Origin-Opener-Policy"] = "same-origin"
        response.headers["Cross-Origin-Embedder-Policy"] = "require-corp"
        return response

    return app


# 給 gunicorn 用的 WSGI app
app = create_app()


# --- 9. 本機開發(可選) ---
def find_watch_files():
    watch_dirs = ["api", "models", "services"]
    base_path = os.path.dirname(__file__)
    all_files = []
    for d in watch_dirs:
        dir_path = os.path.join(base_path, d)
        for root, _, files in os.walk(dir_path):
            for f in files:
                if f.endswith(".py"):
                    all_files.append(os.path.join(root, f))
    return all_files


if __name__ == "__main__":
    use_ssl = os.environ.get("USE_SSL", "false").lower() == "true"
    ssl_context = (
        ("../certs/localhost-cert.pem", "../certs/localhost-key.pem") if use_ssl else None
    )
    run_simple(
        hostname="0.0.0.0",
        port=5001,
        application=app,
        use_debugger=True,
        use_reloader=True,
        extra_files=find_watch_files(),
        ssl_context=ssl_context,
    )