refactor: promote 8081 design system and served app structure
This commit is contained in:
@@ -21,7 +21,7 @@ import ezdxf
|
||||
from ezdxf import recover
|
||||
from fastapi import FastAPI, File, Form, Header, HTTPException, Request, UploadFile
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import FileResponse, HTMLResponse
|
||||
from fastapi.responses import FileResponse, HTMLResponse, Response
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from openpyxl import load_workbook
|
||||
from pydantic import BaseModel, Field
|
||||
@@ -42,6 +42,11 @@ app.add_middleware(
|
||||
|
||||
LEGACY_STATIC_DIR = LEGACY_DIR / "static"
|
||||
INCOMING_FILES_DIR = BASE_DIR / "incoming-files"
|
||||
INCOMING_SERVED_DIR = INCOMING_FILES_DIR / "served"
|
||||
INCOMING_REFERENCE_DIR = INCOMING_FILES_DIR / "reference"
|
||||
BUSINESS_DASHBOARD_DIR = INCOMING_FILES_DIR / "사업관리대장"
|
||||
BUSINESS_LEDGER_SERVED_DIR = INCOMING_SERVED_DIR / "ledger"
|
||||
BUSINESS_LEDGER_INDEX_PATH = BUSINESS_LEDGER_SERVED_DIR / "index.html"
|
||||
FIXED_OFFICE_SOURCE_KEY = "technical-development-center"
|
||||
FIXED_OFFICE_CONFIGS = {
|
||||
"technical-development-center": {
|
||||
@@ -61,6 +66,7 @@ FIXED_OFFICE_CONFIGS = {
|
||||
},
|
||||
}
|
||||
_fixed_office_cache: dict[str, dict[str, object]] = {}
|
||||
BUSINESS_LEDGER_DEFAULT_SOURCE_KEY = "business_ledger_default"
|
||||
AUTH_DEFAULT_PASSWORD = "1111"
|
||||
AUTH_PASSWORD_ITERATIONS = 390000
|
||||
AUTH_SESSION_HOURS = 12
|
||||
@@ -82,6 +88,66 @@ MH_HEADER_ORDER = [
|
||||
"사업 종류", "연장근무 프로젝트 코드", "연장근무 프로젝트명", "연장근무 서브코드", "연장근무 시간(실제)", "연장근무 시간(가공)"
|
||||
]
|
||||
|
||||
def sync_default_business_ledger_source(cur) -> None:
|
||||
cur.execute("SELECT to_regclass('public.integration_binary_sources') IS NOT NULL AS table_exists")
|
||||
row = cur.fetchone()
|
||||
table_exists = bool(row["table_exists"]) if row is not None else False
|
||||
if not table_exists:
|
||||
return
|
||||
candidates = [
|
||||
BUSINESS_LEDGER_SERVED_DIR / "사업관리대장-1.xlsx",
|
||||
BUSINESS_DASHBOARD_DIR / "사업관리대장-1.xlsx",
|
||||
BUSINESS_DASHBOARD_DIR / "사업관리 대장-1.xlsx",
|
||||
BUSINESS_DASHBOARD_DIR / "사업관리대장.xlsx",
|
||||
BUSINESS_DASHBOARD_DIR / "사업관리 대장.xlsx",
|
||||
]
|
||||
source_path = next((candidate for candidate in candidates if candidate.exists()), None)
|
||||
if source_path is None:
|
||||
return
|
||||
content = source_path.read_bytes()
|
||||
content_sha256 = hashlib.sha256(content).hexdigest()
|
||||
meta_json = {
|
||||
"byte_size": len(content),
|
||||
"source_path": str(source_path),
|
||||
"synced_from": "startup",
|
||||
}
|
||||
cur.execute(
|
||||
"""
|
||||
INSERT INTO integration_binary_sources (
|
||||
source_key, source_name, filename, mime_type, content, content_sha256, meta_json, imported_at
|
||||
)
|
||||
VALUES (%s, %s, %s, %s, %s, %s, %s::jsonb, NOW())
|
||||
ON CONFLICT (source_key) DO UPDATE
|
||||
SET source_name = EXCLUDED.source_name,
|
||||
filename = EXCLUDED.filename,
|
||||
mime_type = EXCLUDED.mime_type,
|
||||
content = EXCLUDED.content,
|
||||
content_sha256 = EXCLUDED.content_sha256,
|
||||
meta_json = EXCLUDED.meta_json,
|
||||
imported_at = NOW()
|
||||
WHERE integration_binary_sources.content_sha256 IS DISTINCT FROM EXCLUDED.content_sha256
|
||||
OR integration_binary_sources.filename IS DISTINCT FROM EXCLUDED.filename
|
||||
OR integration_binary_sources.mime_type IS DISTINCT FROM EXCLUDED.mime_type
|
||||
OR integration_binary_sources.meta_json IS DISTINCT FROM EXCLUDED.meta_json
|
||||
""",
|
||||
(
|
||||
BUSINESS_LEDGER_DEFAULT_SOURCE_KEY,
|
||||
"사업관리대장 기본 원본",
|
||||
source_path.name,
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
content,
|
||||
content_sha256,
|
||||
json.dumps(meta_json, ensure_ascii=False),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
app.mount(
|
||||
"/integrations/ledger-assets",
|
||||
StaticFiles(directory=str(BUSINESS_LEDGER_SERVED_DIR), check_dir=False),
|
||||
name="integration-ledger-assets",
|
||||
)
|
||||
|
||||
|
||||
class MemberPayload(BaseModel):
|
||||
id: int | None = None
|
||||
@@ -3910,6 +3976,7 @@ def startup() -> None:
|
||||
init_db()
|
||||
with get_conn() as conn:
|
||||
with conn.cursor() as cur:
|
||||
sync_default_business_ledger_source(cur)
|
||||
sync_auth_users_from_members(cur)
|
||||
conn.commit()
|
||||
|
||||
@@ -3939,6 +4006,37 @@ def health() -> dict[str, object]:
|
||||
}
|
||||
|
||||
|
||||
@app.get("/api/integration/business-ledger-default")
|
||||
def integration_business_ledger_default() -> Response:
|
||||
with get_conn() as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
"""
|
||||
SELECT filename, mime_type, content
|
||||
FROM integration_binary_sources
|
||||
WHERE source_key = %s
|
||||
ORDER BY imported_at DESC
|
||||
LIMIT 1
|
||||
""",
|
||||
(BUSINESS_LEDGER_DEFAULT_SOURCE_KEY,),
|
||||
)
|
||||
row = cur.fetchone()
|
||||
if not row:
|
||||
raise HTTPException(status_code=404, detail="Business ledger default source not found.")
|
||||
filename = str(row["filename"] or "사업관리대장-1.xlsx")
|
||||
headers = {
|
||||
"Content-Disposition": 'inline; filename="business-ledger-default.xlsx"',
|
||||
"X-Source-Filename": "business-ledger-default.xlsx",
|
||||
"Cache-Control": "no-store, no-cache, must-revalidate, max-age=0",
|
||||
"Pragma": "no-cache",
|
||||
}
|
||||
return Response(
|
||||
content=bytes(row["content"]),
|
||||
media_type=str(row["mime_type"] or "application/octet-stream"),
|
||||
headers=headers,
|
||||
)
|
||||
|
||||
|
||||
@app.post("/api/auth/login")
|
||||
def auth_login(
|
||||
request: Request,
|
||||
@@ -4500,15 +4598,30 @@ def legacy_organization_backup() -> FileResponse:
|
||||
|
||||
@app.get("/integrations/payment")
|
||||
def integration_payment() -> FileResponse:
|
||||
target = INCOMING_FILES_DIR / "payment.html"
|
||||
# 8081 phase-1 cleanup: integration HTML is served only from incoming-files/served.
|
||||
target = INCOMING_SERVED_DIR / "payment.html"
|
||||
if not target.exists():
|
||||
raise HTTPException(status_code=404, detail="Payment integration file not found.")
|
||||
return FileResponse(target)
|
||||
|
||||
|
||||
@app.get("/integrations/ledger")
|
||||
def integration_ledger() -> FileResponse:
|
||||
# #21 phase-1: runtime no longer decodes reference wrapper HTML. Serve the promoted
|
||||
# ledger entry file from incoming-files/served/ledger only.
|
||||
target = BUSINESS_LEDGER_INDEX_PATH
|
||||
if not target.exists():
|
||||
raise HTTPException(status_code=404, detail="Business ledger integration file not found.")
|
||||
response = FileResponse(target)
|
||||
response.headers["Cache-Control"] = "no-store, no-cache, must-revalidate, max-age=0"
|
||||
response.headers["Pragma"] = "no-cache"
|
||||
return response
|
||||
|
||||
|
||||
@app.get("/integrations/mh")
|
||||
def integration_mh() -> FileResponse:
|
||||
target = INCOMING_FILES_DIR / "mh.html"
|
||||
# Keep the served path explicit so comparison/reference copies are never picked up by accident.
|
||||
target = INCOMING_SERVED_DIR / "mh.html"
|
||||
if not target.exists():
|
||||
raise HTTPException(status_code=404, detail="MH integration file not found.")
|
||||
return FileResponse(target)
|
||||
|
||||
Reference in New Issue
Block a user