200 lines
6.6 KiB
Python
200 lines
6.6 KiB
Python
from dotenv import load_dotenv
|
||
load_dotenv()
|
||
|
||
from fastapi import FastAPI, Request
|
||
from fastapi.responses import JSONResponse, RedirectResponse, Response
|
||
from pydantic import ValidationError
|
||
from fastapi.middleware.cors import CORSMiddleware
|
||
from starlette.datastructures import MutableHeaders
|
||
from starlette.types import ASGIApp, Receive, Scope, Send
|
||
|
||
# FastAPI アプリケーション作成
|
||
app = FastAPI(
|
||
title="日本小規模企業向け会計システム"
|
||
)
|
||
|
||
|
||
class SecurityHeadersMiddleware:
|
||
"""キャッシュ禁止・セキュリティヘッダーを全レスポンスに付与(ASGIネイティブ実装)”"""
|
||
def __init__(self, app: ASGIApp) -> None:
|
||
self.app = app
|
||
|
||
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
|
||
if scope["type"] != "http":
|
||
await self.app(scope, receive, send)
|
||
return
|
||
|
||
async def send_with_headers(message):
|
||
if message["type"] == "http.response.start":
|
||
headers = MutableHeaders(scope=message)
|
||
headers["Cache-Control"] = "no-store, no-cache, must-revalidate, private"
|
||
headers["Pragma"] = "no-cache"
|
||
headers["Expires"] = "0"
|
||
headers["X-Frame-Options"] = "DENY"
|
||
headers["X-Content-Type-Options"] = "nosniff"
|
||
headers["X-XSS-Protection"] = "1; mode=block"
|
||
await send(message)
|
||
|
||
await self.app(scope, receive, send_with_headers)
|
||
|
||
app.add_middleware(SecurityHeadersMiddleware)
|
||
|
||
app.add_middleware(
|
||
CORSMiddleware,
|
||
allow_origins=["*"],
|
||
allow_credentials=False,
|
||
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
||
allow_headers=["Content-Type", "Authorization"],
|
||
)
|
||
|
||
# ─────────────────────────
|
||
# 自動データベース遷移
|
||
# ─────────────────────────
|
||
@app.on_event("startup")
|
||
async def run_startup_migration():
|
||
"""アプリケーション起動時にデータベス遷移を実行"""
|
||
try:
|
||
from app.db_auto_migration import run_auto_migration
|
||
run_auto_migration()
|
||
except Exception as e:
|
||
print(f"⚠️ 遷移エラー(無視): {e}")
|
||
|
||
@app.exception_handler(ValidationError)
|
||
async def validation_exception_handler(request: Request, exc: ValidationError):
|
||
# ここでは最初のエラーだけ簡潔に返す(必要に応じて整形)
|
||
first = exc.errors()[0]
|
||
msg = first.get("msg", "入力値が不正です。")
|
||
return JSONResponse(status_code=400, content={"detail": msg})
|
||
|
||
# ─────────────────────────
|
||
# ルート(稼働確認)
|
||
# ─────────────────────────
|
||
@app.get("/", include_in_schema=False)
|
||
def root():
|
||
return RedirectResponse("/login.html")
|
||
# ─────────────────────────
|
||
# ルーター登録(※ 必ず app 定義の後)
|
||
# ─────────────────────────
|
||
from app.modules.users.router import router as users_router
|
||
app.include_router(users_router)
|
||
|
||
from app.modules.accounts.router import router as accounts_router
|
||
app.include_router(accounts_router)
|
||
|
||
|
||
from app.modules.journals.router import router as journals_router
|
||
app.include_router(journals_router)
|
||
|
||
|
||
from app.modules.ledger.router import router as ledger_router
|
||
app.include_router(ledger_router)
|
||
|
||
|
||
from app.modules.trial_balance.router import router as trial_balance_router
|
||
app.include_router(trial_balance_router)
|
||
|
||
from app.modules.opening_balances.router import router as opening_router
|
||
app.include_router(opening_router)
|
||
|
||
from app.modules.general_ledger.router import router as general_ledger_router
|
||
app.include_router(general_ledger_router)
|
||
|
||
from app.modules.opening_balances.lock_router import router as opening_balance_lock_router
|
||
app.include_router(opening_balance_lock_router)
|
||
|
||
from app.routers import journal_entries
|
||
from app.routers import cash
|
||
from app.routers import accounts
|
||
|
||
app.include_router(journal_entries.router)
|
||
app.include_router(cash.router)
|
||
app.include_router(accounts.router)
|
||
|
||
|
||
from app.routers import month_locks
|
||
app.include_router(month_locks.router)
|
||
|
||
from app.routers import year_locks
|
||
app.include_router(year_locks.router)
|
||
|
||
from app.routers import cash
|
||
app.include_router(cash.router)
|
||
|
||
from app.routers import file_upload
|
||
app.include_router(file_upload.router)
|
||
|
||
from app.modules.egov.router import router as egov_router
|
||
app.include_router(egov_router)
|
||
|
||
# ─────────────────────────
|
||
# 給与モジュール (Payroll Module)
|
||
# ─────────────────────────
|
||
from app.payroll.employees.router import router as payroll_employees_router
|
||
app.include_router(payroll_employees_router)
|
||
|
||
from app.payroll.settings.router import router as payroll_settings_router
|
||
app.include_router(payroll_settings_router)
|
||
|
||
from app.payroll.settings.standard_remuneration_router import router as standard_remuneration_router
|
||
app.include_router(standard_remuneration_router)
|
||
|
||
from app.payroll.calculation.router import router as payroll_calculation_router
|
||
app.include_router(payroll_calculation_router)
|
||
|
||
from app.payroll.bonus.router import router as payroll_bonus_router
|
||
app.include_router(payroll_bonus_router)
|
||
|
||
from app.payroll.vouchers.router import router as payroll_vouchers_router
|
||
app.include_router(payroll_vouchers_router)
|
||
|
||
from app.payroll.withholding_slip.router import router as withholding_slip_router
|
||
app.include_router(withholding_slip_router)
|
||
|
||
from app.modules.nenkin_portal.router import router as nenkin_portal_router
|
||
app.include_router(nenkin_portal_router)
|
||
|
||
from fastapi.staticfiles import StaticFiles
|
||
import os
|
||
from pathlib import Path
|
||
|
||
# アップロードファイル用の静的ファイル配信
|
||
uploads_dir = Path("uploads")
|
||
uploads_dir.mkdir(exist_ok=True)
|
||
app.mount(
|
||
"/uploads",
|
||
StaticFiles(directory="uploads"),
|
||
name="uploads",
|
||
)
|
||
|
||
# 静的ファイル配信(コメントアウト - ディレクトリが存在しない)
|
||
# app.mount(
|
||
# "/static",
|
||
# StaticFiles(directory="app/static", html=True),
|
||
# name="static",
|
||
# )
|
||
|
||
# フロントエンドファイル配信
|
||
from fastapi.staticfiles import StaticFiles
|
||
from pathlib import Path
|
||
|
||
# フロントエンド配信(/app/frontend)
|
||
BASE_DIR = Path(__file__).resolve().parent.parent # /app
|
||
frontend_path = BASE_DIR / "frontend"
|
||
|
||
print("Frontend path =", frontend_path)
|
||
|
||
if frontend_path.exists():
|
||
app.mount(
|
||
"/",
|
||
StaticFiles(directory=frontend_path),
|
||
name="frontend",
|
||
)
|
||
else:
|
||
print(f"Frontend path not found: {frontend_path}")
|
||
|
||
|
||
|
||
|
||
|
||
|