o2o-negosium-original/negodata/backend
2026-07-06 16:36:00 +09:00
..
common [feat] negodata: 결렬 폐지→개찰 마감 개편 + 낙찰기준 견적 단위 이관 + 세팅 컬럼 정리 (백엔드+DB) 2026-07-06 16:36:00 +09:00
config [feat] negodata: 견적 목표가 산정 개편 + 협상 초청메일 + 카드 사용구분 2026-06-29 13:33:08 +09:00
crud [feat] negodata: 결렬 폐지→개찰 마감 개편 + 낙찰기준 견적 단위 이관 + 세팅 컬럼 정리 (백엔드+DB) 2026-07-06 16:36:00 +09:00
docs [feat] negodata/backend: 상품 이미지 업로드(Azure Blob) 엔드포인트 추가 2026-06-18 16:21:58 +09:00
loadtest requirement.dev.txt 삭제 . 2026-06-15 15:58:06 +09:00
router [feat] negodata: 결렬 폐지→개찰 마감 개편 + 낙찰기준 견적 단위 이관 + 세팅 컬럼 정리 (백엔드+DB) 2026-07-06 16:36:00 +09:00
scheduler [feat] negodata: 결렬 폐지→개찰 마감 개편 + 낙찰기준 견적 단위 이관 + 세팅 컬럼 정리 (백엔드+DB) 2026-07-06 16:36:00 +09:00
scripts [feat] negodata: 결렬 폐지→개찰 마감 개편 + 낙찰기준 견적 단위 이관 + 세팅 컬럼 정리 (백엔드+DB) 2026-07-06 16:36:00 +09:00
services [feat] negodata: 결렬 폐지→개찰 마감 개편 + 낙찰기준 견적 단위 이관 + 세팅 컬럼 정리 (백엔드+DB) 2026-07-06 16:36:00 +09:00
tests [feat] negodata: 결렬 폐지→개찰 마감 개편 + 낙찰기준 견적 단위 이관 + 세팅 컬럼 정리 (백엔드+DB) 2026-07-06 16:36:00 +09:00
.dockerignore requirement.dev.txt 삭제 . 2026-06-15 15:58:06 +09:00
conftest.py [test] negodata: 백엔드 테스트 스위트 구축 + 공통 픽스처(conftest) 정비 2026-07-01 15:06:55 +09:00
Dockerfile Agent 서버 구축: 멀티테넌트 협상 PoC (UCB Q-Table 학습 + /chat + 14 API + 학습검증 하네스), config 단일화(local.toml) + 빌드 경량화 2026-06-17 11:10:03 +09:00
pytest.ini requirement.dev.txt 삭제 . 2026-06-15 15:58:06 +09:00
README.md [docs] negodata: README 갱신 — 테스트 실행법·API 도메인·프론트 포트 2026-07-01 15:06:55 +09:00
requirements.txt [feat] negodata: 견적 목표가 산정 개편 + 협상 초청메일 + 카드 사용구분 2026-06-29 13:33:08 +09:00
web_main.py [wip] negodata/backend: 견적 마감 스케줄러 2026-06-22 17:38:02 +09:00

Negodata Backend

DerbyMasters_Server 아키텍처를 이식한 FastAPI 백엔드. 인증(JWT) 위에 견적·협력사·상품·견적설정·대시보드·알림·협상카드·회사유저관리 도메인과 견적 마감 스케줄러를 구현. negosium-backend 와 동일 구조이며, 실행·테스트·벤치마크 종합은 레포 최상위 README 참고.

디렉토리 구조

negodata/backend/
├── web_main.py                  # 엔트리포인트 (uvicorn)
├── config/                      # 환경설정 (APP_ENV 별 toml 로드)
├── conftest.py, tests/          # pytest (test DB 자동 create/drop) — 아래 '테스트'
├── common/
│   ├── enums.py                 # ErrorType / 코드값 enum / EXCEPTION_*
│   ├── models/gmodel.py         # 프로토콜 베이스 (WebPacketProtocol 등)
│   └── database/
│       ├── db_session_manager.py# ★ DB Read/Write + 람다 실행 핵심
│       └── model/models.py      # ORM 모델 (companies·users·quotations·sessions·items·suppliers·notifications·cards …)
├── crud/                        # 도메인별 DB 접근(I*CRUD 인터페이스+구현): quotation·supplier·item·dashboard·notification·card·user …
├── services/                    # 비즈니스 로직: quotation·supplier·item·dashboard·notification·company_user·auth·email …
├── scheduler/                   # 견적 마감 크론 잡(만료 마감 · 협상종결 마감)
└── router/
    ├── router.py                # FastAPI app (CORS 등)
    └── v1/                      # 도메인별 라우터: auth·quotation·quotation_setting·supplier·item·card·dashboard·notification·company
        └── validator/dependencies.py    # ★ JWT 발급·검증, 해시(bcrypt), RemoveNoneResponse, RequireOwner

핵심 패턴

  • MVC: router(컨트롤러) → service(로직) → crud(쿼리). crud 는 인터페이스/구현 분리.
  • Depends 주입: service 가 IUserCRUD = Depends(UserCRUD), router 가 Depends(AuthService), 인증은 Depends(IsValidAccessToken).
  • DB Read/Write 분리: DBType × DBWRType 조합마다 별도 async 엔진(조회=Read, 변경=Write).
  • 람다 DB 실행: service 는 세션을 직접 열지 않고 람다를 매니저에 넘긴다. 세션/트랜잭션은 매니저가 책임.
    err, acc = await DB_SESSION_MNG.execute_lambda(           # 단일 조회
        tbl_account.DBType(), DBWRType.DB_READ.value, lambda s: crud.get_account_by_id(s, id))
    err = await DB_SESSION_MNG.execute_lambda_run(            # 여러 변경 = 1 트랜잭션
        [tbl_account.DBType()], [lambda s: crud.update_last_login(s, uid)])
    
  • 비동기: 전 계층 async/await, SQLAlchemy async + asyncpg, task 단위 scoped session.
  • Protocol 규약: 모든 패킷 WebPacketProtocol 상속, Req_*/Res_*(응답엔 result: ErrorInfo), 라우터별 protocol.py.
  • ResponseNone: 응답의 None 필드 재귀 제거(RemoveNoneResponse).
  • bcrypt 비차단: GetHashedPW/VerifyPWasyncio.to_thread 로 오프로드(이벤트 루프 비차단). → 벤치마크

API 도메인 (/v1/*)

전체 스펙은 실행 후 http://localhost:9400/docs (Swagger). 주요 도메인:

prefix 요약
/v1/auth 로그인 · access 토큰 재발급 · 내 정보(me). ※ 무인증 계정 생성은 제거됨
/v1/company/user 최고관리자(OWNER) 전용 — 자기 회사 직원 계정 생성·관리
/v1/quotation 견적 생성·목록·단건·마감·재생성 + 세션·채팅·낙찰결과·카드·초청메일
/v1/quotation-setting 견적 설정(마진율 등) — 유저별 소유
/v1/supplier, /v1/item 협력사 / 상품 CRUD (회사 스코프)
/v1/card 협상 카드
/v1/dashboard 요약(회사 전체 + 내 견적)
/v1/notification 알림함(목록 · 읽음 처리)

인증 헤더: Authorization: Bearer <access_token>. 회사 소유 자원은 토큰의 회사로 스코프되고, 계정 관리는 OWNER 만 가능.

실행

cd negodata/backend
pip install -r requirements.txt
python web_main.py                      # APP_ENV 기본 local → http://localhost:9400/docs

환경: config.{local,test,prod}.toml (APP_ENV 로 선택).

테스트

테스트는 도커가 아니라 호스트(venv)에서 돌린다 — DB(PostgreSQL)만 도커(negosium-db, 127.0.0.1:5432)면 되고, 앱 컨테이너 안엔 pytest 가 없다. test DB(negosium_test_db)는 알아서 만들어졌다 지워지므로 수동 세팅이 필요 없다.

cd negodata/backend
python3 -m venv .venv && source .venv/bin/activate   # 최초 1회 (venv 없을 때)
pip install -r requirements.txt                      # httpx 포함
pip install pytest pytest-asyncio                    # 테스트 도구(requirements 에 없음)

python -m pytest                         # 전체 (venv 활성화 상태)
python -m pytest -v                      # 테스트별 PASS/FAIL
python -m pytest tests/test_company_scope.py   # 파일 하나만
python -m pytest -k scope                # 이름에 'scope' 든 것만

venv 를 활성화(source .venv/bin/activate)하지 않으면 .venv/bin/python -m pytest 로 직접 지정한다. (시스템에 python 명령이 없거나 pytest 가 venv 밖에 없으면 맨 python -m pytest 는 실패한다.) 정상이면 마지막 줄에 NN passed.

동작 방식 (전부 conftest.py 가 자동 처리 — 손댈 것 없음):

  • APP_ENVtest 로 자동 설정 → config.test.tomlnegosium_test_db 사용(dev DB negosium_db 와 완전 분리).
  • 세션 시작 시 test DB 를 새로 만들고(CREATE), 끝나면 내린다(DROP). 매번 현재 모델로 새로 빌드돼 스키마가 낡을 일이 없다. 남는 DB 도 없음.
  • 테이블은 create_all 로 자동 생성, 매 테스트 전 TRUNCATE 로 비워 격리.
  • 안전가드: 이름에 test 없는 DB 는 만들지도 지우지도 않는다(실 DB 보호).

즉 새로 clone 받은 팀원도 Postgres 만 켜져 있으면 python -m pytest 한 방이면 끝.