개발팀 운영 노트
내부 팀원이 알고 있어야 하는 것들입니다. 대부분 한 번 데였던 것이라 같은 자리에 다시 빠지지 않기 위해 적어 둡니다.
이 문서는 공개되어 있습니다
키·접속 정보·개인정보는 여기에 적지 않습니다. 그런 것은 사내 비공개 채널에 둡니다.
시간대 — 저장은 UTC, 판단은 KST
getMonth() · getDate() 는 실행되는 곳의 시간대를 따릅니다. 서버는 UTC 로 도니, 서버가 그린 첫 화면과 브라우저가 다시 그린 값이 하루 어긋납니다.
- 저장은 UTC
- 표시·판정은
Asia/Seoul - 변환은 한 파일에만 둡니다. 예전에 두 군데에 같은 함수가 있어 각각 고쳐야 했습니다
날짜 폴더(사진 업로드 경로)도 KST 기준입니다 — UTC 로 하면 새벽 업로드가 전날 폴더로 갑니다.
조회는 조용히 잘린다
데이터베이스가 한 번에 주는 행 수에는 상한이 있습니다. 넘치면 오류가 아니라 그냥 잘립니다. 화면만 봐서는 알아챌 수 없습니다.
실제로 있었던 일:
- 지점 추이의 체크리스트 완료율이 8/19 이후 통째로 비어 보임
- 전 지점 재고 KPI 에서 재고가 있는 지점 3곳이 0건으로 표시
- 고객센터의 사진 개수가 549건인데 0으로 표시
행 수가 지점 수·기간에 비례해 늘어나는 조회는 반드시 페이징 헬퍼로 감쌉니다.
빈 값을 걸러서 던진다
.in('id', ids) 에 null 이 하나라도 섞이면 요청 전체가 거절됩니다. 계정을 지우면 작성자 참조가 비는데(아래 참고), 그 빈 값이 목록 조회를 통째로 500 으로 만든 적이 있습니다. 지점 3곳 28건이 「없음」으로 보였습니다.
계정을 지워도 「누가」는 남긴다
계정을 지우면 그 사람이 남긴 기록의 작성자 참조만 비웁니다. 기록은 남습니다.
- 앱의 **「계정 지우기」**는 행을 지우지 않고 보관 처리합니다 — 이름은 남고 로그인 수단과 개인정보는 지웁니다
- 데이터베이스에서 직접 지우면 이름까지 사라집니다. 되돌릴 수 없습니다
- 화면에서는 「삭제된 계정」(회색)으로 보이고 이름은 툴팁에서만 드러냅니다
실패를 남긴다
「눌렀는데 기록이 없다」를 가르려면 거절된 요청도 남아야 합니다. 사진 방식 출퇴근은 실패해도 화면에 잠깐 뜨고 사라질 뿐이라, 원인을 되짚을 근거가 없었습니다.
지금은 서버가 되돌려보낸 출퇴근 요청을 사유와 함께 남기고, 관리자 지점 상세에서 **「실패한 출퇴근 시도」**로 봅니다. 진단 기록이므로 남기지 못해도 사용자 흐름을 막지 않습니다 — 던지지 않고 기다리지도 않습니다.
화면을 만들 때
규격은 한 곳에만
색·크기 표를 두 벌 만들면 반드시 어긋납니다. 토큰 파일과 서비스 안 /dashboard/design-system 갤러리가 단일 출처입니다.
rem 을 쓰지 않는다
html { font-size: 14px } 이라 Tailwind 의 rem 클래스가 전부 어긋납니다. p-3 은 12px 이 아니라 10.5px, w-5 는 20px 이 아니라 17.5px 입니다. px 로 적습니다.
표는 PC 것이다
좁은 화면은 카드로 쌓습니다. 열을 나란히 비교해야 하는 표만 예외로 조밀 규격을 씁니다.
못 불러온 것과 없는 것은 다르다
조회가 실패했는데 「없습니다」라고 적으면 데이터가 사라진 것처럼 읽힙니다. 오류와 「다시 시도」를 보여줍니다.
배포와 기록
main에 올라가면 자동 배포됩니다- 데이터베이스 구조 변경은 마이그레이션 파일로 남기고 순서대로 적용합니다
- 사용자에게 보이는 변화는 서비스 안 **「변경 내역」**에 함께 적습니다. 코드만 바뀌고 기록이 없으면, 나중에 언제 무엇이 바뀌었는지 아무도 모릅니다
되풀이하지 않기 위한 한 줄들
- 숫자를 조용히 맞추지 않는다 — 이상하면 이유를 드러낸다
- 기본은 「전체」, 좁히는 것은 필터가 한다
- 필터는 숨기는 칸이 아니라 찾는 칸이다
- 색은 아껴 쓴다. 다 칠하면 정작 위험한 것이 묻힌다
- 아이콘만 있는 버튼은 만들지 않는다
알림톡·문자 · 무료 트라이얼
- 키는 코드에 없다. 운영값은 환경설정 > 알림톡 설정(
org_messaging_settings), 폴백은 서버 환경변수. 비밀키는 AES-256-GCM 으로 잠가 저장하고, 잠금 키는SETTINGS_ENCRYPTION_KEY(없으면JWT_SECRET파생). JWT_SECRET 을 바꾸면 저장된 비밀키가 풀리지 않는다 — 다시 입력해야 한다. - 발송 로그
message_logs에는 번호를 가려서 남긴다(솔라피 콘솔에 원본이 있다). 인증번호는 해시만 저장(phone_verifications). - 크론:
trial/expire00:05 KST ·trial/notify-d310:00 KST.CRON_SECRET헤더 또는 HQ_ADMIN 토큰으로 수동 호출 가능. - 차단 판단은
lib/trial-guard.ts하나 — 로그인 API 와 지점 목록 API 가 같이 쓴다. 날짜 계산은lib/trial.ts(KST 문자열로만, 테스트 12개). - 가입 인증 남용 제한: 같은 번호 60초 1회 · 1시간 5회 · 5회 오답 잠금 · 코드 5분 · 인증 토큰 30분·일회용.