- 시스템 관리자 화면: 「시스템 설정」을 리소스 현황 아래로 · 제목 줄 오른쪽에 회사 고르기(기본 자기 회사) - 고른 회사 = sessionStorage aislo.adminCompany · 프로젝트 · 사용자 관리 · 가입 요청 · 회사 관리 · 팀원 추가가 그 회사 것 - 카드 해시에 &company=<id> · 회사 관리자 · 개인 화면은 그대로 · 임시 보관함은 사람 것이라 그대로 - 서버: /admin/projects · /admin/join-requests · /admin/members(POST · invite)에 company_id(_scope_company 한 곳 · 시스템 관리자만 남의 회사) · /admin/users 에 회사 거르기 - 라우터 700줄 넘어 나눔: 권한 도우미 B01_Dashboard_Scope · 회사 자산 B01_Dashboard_Router_Assets(본체가 include) - 시험 test_b01_settings_cards 보탬 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ly2oHAaTDYRjcniAmHR81z
130 lines
5.1 KiB
Python
130 lines
5.1 KiB
Python
"""B01_Dashboard 회사 공유 도면 자산(로고·서명) API — 본체 라우터가 include 함."""
|
|
|
|
import mimetypes
|
|
import os
|
|
from typing import Any
|
|
|
|
from fastapi import APIRouter, Depends, File, Form, HTTPException, Query, Response, UploadFile
|
|
|
|
from common_util.common_util_auth import verify_session
|
|
from common_util.common_util_storage import read_stored_asset
|
|
|
|
from .B01_Dashboard_Repository_Assets import (
|
|
ASSET_EXTENSIONS,
|
|
ASSET_KINDS,
|
|
ASSET_MAX_BYTES,
|
|
create_company_asset,
|
|
delete_company_asset,
|
|
get_company_asset,
|
|
list_company_assets,
|
|
update_company_asset,
|
|
write_company_asset_file,
|
|
)
|
|
from .B01_Dashboard_Repository_Members import list_company_members, set_company_logo
|
|
from .B01_Dashboard_Schema import UpdateCompanyAssetRequest, UpdateCompanyLogoRequest
|
|
from .B01_Dashboard_Scope import _scope_company, require_company_admin
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
async def _company_asset(session: dict[str, Any], asset_id: int) -> dict[str, Any]:
|
|
asset = await get_company_asset(asset_id)
|
|
if not asset:
|
|
raise HTTPException(status_code=404, detail="자산을 찾을 수 없습니다.")
|
|
_scope_company(session, int(asset["company_id"]))
|
|
return asset
|
|
|
|
|
|
# ---- 회사 공유 도면 자산(로고·서명) — 회사 구성원 누구나 작성·수정, 시스템관리자는 전체 ----
|
|
|
|
|
|
@router.get("/company/assets")
|
|
async def company_assets(
|
|
company_id: int | None = Query(None, gt=0),
|
|
session: dict[str, Any] = Depends(verify_session),
|
|
):
|
|
return {
|
|
"status": "success",
|
|
"assets": await list_company_assets(_scope_company(session, company_id)),
|
|
}
|
|
|
|
|
|
@router.post("/company/assets")
|
|
async def company_add_asset(
|
|
kind: str = Form(...),
|
|
label: str = Form(...),
|
|
user_id: int | None = Form(None, gt=0),
|
|
company_id: int | None = Form(None, gt=0),
|
|
file: UploadFile = File(...),
|
|
session: dict[str, Any] = Depends(verify_session),
|
|
):
|
|
kind = kind.upper()
|
|
if kind not in ASSET_KINDS:
|
|
raise HTTPException(status_code=400, detail="자산 종류는 LOGO 또는 SIGNATURE 입니다.")
|
|
label = label.strip()[:100]
|
|
if not label:
|
|
raise HTTPException(status_code=400, detail="이름을 넣어 주세요.")
|
|
suffix = os.path.splitext(file.filename or "")[1].lower()
|
|
if suffix not in ASSET_EXTENSIONS:
|
|
raise HTTPException(status_code=400, detail="png·jpg·webp·svg 그림만 올릴 수 있습니다.")
|
|
blob = await file.read()
|
|
if not blob or len(blob) > ASSET_MAX_BYTES:
|
|
raise HTTPException(status_code=400, detail="그림은 2MB 이하여야 합니다.")
|
|
scoped = _scope_company(session, company_id)
|
|
if user_id and user_id not in {m["id"] for m in await list_company_members(scoped)}:
|
|
raise HTTPException(status_code=400, detail="주인은 같은 회사 구성원이어야 합니다.")
|
|
path = write_company_asset_file(scoped, kind, suffix, blob)
|
|
asset_id = await create_company_asset(
|
|
scoped, kind, label, path, user_id, int(session["user_id"])
|
|
)
|
|
return {"status": "success", "asset_id": asset_id}
|
|
|
|
|
|
@router.put("/company/assets/{asset_id}")
|
|
async def company_update_asset(
|
|
asset_id: int,
|
|
payload: UpdateCompanyAssetRequest,
|
|
session: dict[str, Any] = Depends(verify_session),
|
|
):
|
|
await _company_asset(session, asset_id)
|
|
await update_company_asset(asset_id, payload.label.strip(), payload.user_id)
|
|
return {"status": "success"}
|
|
|
|
|
|
@router.delete("/company/assets/{asset_id}")
|
|
async def company_delete_asset(asset_id: int, session: dict[str, Any] = Depends(verify_session)):
|
|
await _company_asset(session, asset_id)
|
|
await delete_company_asset(asset_id)
|
|
return {"status": "success"}
|
|
|
|
|
|
@router.put("/company/logo")
|
|
async def company_set_logo(
|
|
payload: UpdateCompanyLogoRequest,
|
|
company_id: int | None = Query(None, gt=0),
|
|
session: dict[str, Any] = Depends(require_company_admin),
|
|
):
|
|
"""회사 대표 로고를 지정·변경한다 (2026-09-02 사용자 확정).
|
|
|
|
프로젝트가 따로 고르지 않으면 도면은 이 로고를 쓴다.
|
|
"""
|
|
scoped = _scope_company(session, company_id)
|
|
if payload.logo_asset_id is not None:
|
|
asset = await get_company_asset(payload.logo_asset_id)
|
|
if not asset or asset["company_id"] != scoped or asset["kind"] != "LOGO":
|
|
raise HTTPException(status_code=400, detail="같은 회사의 로고 자산이어야 합니다.")
|
|
if not await set_company_logo(scoped, payload.logo_asset_id):
|
|
raise HTTPException(status_code=404, detail="회사를 찾을 수 없습니다.")
|
|
return {"status": "success"}
|
|
|
|
|
|
@router.get("/company/assets/{asset_id}/file")
|
|
async def company_asset_file(asset_id: int, session: dict[str, Any] = Depends(verify_session)):
|
|
"""목록 미리보기용 그림. 도면에는 B07 이 같은 파일을 data URL 로 심는다."""
|
|
asset = await _company_asset(session, asset_id)
|
|
blob = read_stored_asset(asset["file_path"])
|
|
if blob is None:
|
|
raise HTTPException(status_code=404, detail="그림 파일이 없습니다.")
|
|
media_type = mimetypes.guess_type(asset["file_path"])[0] or "application/octet-stream"
|
|
return Response(content=blob, media_type=media_type)
|