위저드 2단계부터 백엔드를 부르고, 만든 결과는 사업장·사이트로 계정에 귀속된다. 로그인 없이
걸어온 사람은 3단계쯤에서 '로그인이 만료되었습니다' 를 만나고 그때까지 넣은 걸 잃었다 —
만료가 아니라 처음부터 세션이 없었던 것이다. 문 앞에서 막는 편이 낫다.
- app/router: /builder 를 RequireAuth 뒤로
- app/provider: 자동 로그인(AUTO_LOGIN_ID·PW)을 부팅에서 붙인다. 화면 안(useAutoLogin)은
가드가 먼저 판단하므로 영영 실행되지 않는다 — 그래서 훅을 지웠다
- 자동 로그인이 신원까지 채웠으면 me() 를 두 번 부르지 않는다
⚠️ main 의 969fb67(에디터 진입에서 한 번만 로그인)·22b7623 과 **정면으로 다른 설계**다.
되돌리려면 router 의 RequireAuth 한 겹만 벗기면 된다.
tsc·eslint·vite build 통과.
90 lines
5.6 KiB
Markdown
90 lines
5.6 KiB
Markdown
# 개발 일지
|
|
|
|
무엇을 왜 바꿨는지 날짜순으로 남긴다. 새 항목을 **위에** 추가한다.
|
|
결론과 배경은 각 문서가 단일 출처다 — 여기에는 요약과 링크만 둔다.
|
|
|
|
## 2026-09-02 — 로그인을 붙이고, 빌더를 그 뒤로 넣었다
|
|
|
|
**한 일**
|
|
- id/pw **회원가입**(`POST /v1/auth/signup`) 과 **구글 로그인**(`POST /v1/auth/google`) 추가.
|
|
- `company.users` 에 `provider`(AuthProvider) · `provider_uid`(구글 sub) 추가. `password` 는
|
|
NULL 허용, `id` 는 20 → 64자.
|
|
- `/builder` 를 `RequireAuth` 뒤로 넣었다. 자동 로그인은 화면 안(`useAutoLogin`) 이 아니라
|
|
부팅(`app/provider.tsx`)에서 붙는다 — 가드가 먼저 판단하므로 화면 안은 이제 실행되지 않는다.
|
|
- 로그인 화면에 구글 버튼 + 가입 링크. 내부 운영 화면은 `selfServe={false}` 로 둘 다 안 뜬다.
|
|
|
|
**왜 빌더를 막았나**
|
|
빌더는 "만들어 보기 전에 막지 않으려고" 열려 있었다. 그런데 위저드 2단계부터 백엔드를 부르고,
|
|
만든 결과는 사업장·사이트로 **계정에 귀속**된다. 로그인 없이 걸어온 사람은 3단계쯤에서
|
|
"로그인이 만료되었습니다"를 만나고 그때까지 넣은 걸 잃었다 — 만료가 아니라 처음부터 세션이
|
|
없었던 것이다. 문 앞에서 막는 편이 걸어 들어온 뒤에 막는 것보다 낫다.
|
|
|
|
**왜 가입까지 만들었나**
|
|
빌더가 로그인 뒤로 들어간 순간, 계정을 만들 길이 없으면 제품이 닫힌다. 계정 생성 API 가
|
|
아예 없어서(그동안 `users` 를 손으로 INSERT 했다) 가입 = **새 회사(테넌트) 1개 + 첫 계정 1개**
|
|
로 정의했다. `users.company_id` 가 NOT NULL 이고 모든 도메인이 company 로 스코프되기 때문이다.
|
|
|
|
**밟기 쉬운 자리**
|
|
- **`GOOGLE_CLIENT_ID` 는 백엔드와 프론트가 같아야 한다.** 백엔드는 이 값으로 구글 토큰의
|
|
수신자(`aud`)를 대조한다 — 이 검사가 없으면 **다른 서비스에 발급된 진짜 구글 토큰**으로
|
|
우리 계정에 들어온다. 어긋나면 버튼은 뜨는데 로그인만 계속 거부된다.
|
|
- **같은 이메일이라도 id/pw 계정과 구글 계정을 자동으로 잇지 않는다.** 이으면 계정 선점이
|
|
된다 → [DECISIONS.md 1-5](DECISIONS.md)
|
|
- 소셜 계정은 `password` 가 NULL 이다. id/pw 로그인 경로에서 먼저 끊지 않으면 해시 검증이
|
|
None 을 만나 500 이 난다.
|
|
- `provider` 에 `server_default` 를 같이 줬다. ORM default 는 raw INSERT(테스트 시드)에 안 먹어서
|
|
NOT NULL 컬럼이면 그 경로가 통째로 깨진다.
|
|
|
|
**이미 도는 DB 가 있으면** `postgres-init/init-data/init.sql` 을 다시 적용한다 — 말미의
|
|
"기존 DB 보정(ALTER)" 섹션이 새 컬럼을 채우고 `id` 를 넓힌다. 안 하면 로그인부터 500 이다.
|
|
|
|
**검증** — 백엔드 `pytest` auth 13건 + 구글 토큰 검증 8건(진짜 RSA 서명으로 aud·iss·만료·
|
|
`email_verified` 거절 확인), 프론트 `tsc` · `eslint` · `vite build` 통과.
|
|
|
|
---
|
|
|
|
## 2026-09-01 — 설정을 `.env` 하나로 모았다
|
|
|
|
**한 일**
|
|
- 백엔드 설정을 toml → `pydantic-settings`(FastAPI 공식 방식)로 옮겼다.
|
|
- `config_loader.py` · `config.local.toml.example` · `config.test.toml.example` 삭제.
|
|
- `server_configs.py` 107줄 → 26줄. `_apply_*_env_override` 함수 4개 제거.
|
|
- 호출부 21개 파일은 안 건드렸다 — 같은 이름을 그대로 내보낸다.
|
|
|
|
**왜**
|
|
키마다 `if os.environ.get(...)` 를 손으로 나열하는 구조였다. 하나 빠뜨리면 조용히 틀리는데,
|
|
실제로 `client_url` 이 빠져 있어 **배포 주소의 API 호출이 전부 CORS 로 막혔다**.
|
|
`BaseSettings` 는 필드를 선언하면 환경변수가 자동으로 들어와 이 사고가 구조적으로 안 난다.
|
|
|
|
**하는 김에 잡은 잠재 버그**
|
|
- `.env` 경로가 세 단계라 `solution/.env`(없는 파일)를 보고 있었다. 백엔드를 `solution/` 아래로
|
|
옮길 때 안 고쳐진 자리다. toml 이 값을 들고 있어 로컬에서 안 드러났고, 도커는 compose 가
|
|
환경변수를 직접 넣어 역시 멀쩡했다. toml 을 없앤 지금은 유일한 공급원이라 치명적이었다.
|
|
- 환경변수 이름을 `validation_alias` 로 못 박았다. 안 그러면 `port` 필드가 흔한 `PORT` 를
|
|
주워 먹어 엉뚱한 포트로 뜬다.
|
|
|
|
**결과** — 백엔드 설정 파일은 최상위 `.env` 하나뿐이다. → [DECISIONS.md](DECISIONS.md)
|
|
|
|
---
|
|
|
|
## 2026-08-31 — 킹서버 최초 배포
|
|
|
|
**한 일**
|
|
- `~/data2/o2o-web4ai` 에 배포. DB(`web4ai_db`) 생성 + `init.sql` 적용.
|
|
- 컴포즈 포트를 전부 `.env` 변수로 뽑았다. 로컬 기본값은 그대로다.
|
|
- `deploy.sh` · `log.sh` 추가.
|
|
|
|
**왜 포트를 뽑았나**
|
|
킹서버는 `:80` 을 호스트 nginx 가 이미 물고 있다. 사내망에 열려 있는 건 30xxx 대역뿐이라
|
|
그 안에서 자리를 잡아야 했다. → [SERVERS.md](SERVERS.md)
|
|
|
|
**밟은 함정**
|
|
- `PUBLIC_API_BASE_URL` 은 **브라우저가** 부르는 주소다. `localhost` 로 두면 화면은 뜨고
|
|
API 만 죽는다 — 콘솔을 열기 전엔 안 보인다.
|
|
- 내부 화면의 "빌더 열기" 가 `VITE_SOLUTION_URL` 미주입으로 죽은 링크였다. 로컬에서는
|
|
기본값이 맞는 주소라 서버에 올리기 전까지 드러나지 않았다.
|
|
- `deploy.sh api` 는 worker·api-admin 도 함께 갈아끼운다. 셋이 이미지 한 벌을 나눠 쓰는데
|
|
하나만 바꾸면 옛 코드로 도는 컨테이너가 남고, `ps` 로는 셋 다 살아 있어 구분이 안 된다.
|
|
|
|
**남은 것** — `w4ai.o2o.kr` DNS + 앞단(59.14.81.3) 포워딩. 서버에 sudo 가 없어 인프라 몫이다.
|