Javis OS
Javis OS는 기업가를 돕는 에이전시 AI로, 업무를 지원하고 함께 성장합니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Sehyun-Selene/Hackathon-service
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
해커톤 음식 주문 & 코치 호출 서비스 PRD(해커톤_주문_호출_서비스_PRD.md) 기반 구현. 두 개의 완전히 독립된 웹 앱으로 구성됩니다. 두 앱은 코드/빌드/배포가 완전히 분리되어 있고, 오직 공유 데이터 저장소를 통해서만 간접적으로 연동됩니다 (직접 API 호출 없음). ⚠️ 아티팩트 shared storage 관련 정정: 처음엔 PRD 3.3의 "Claude 아티팩트 영속 저장소(window.storage, shared 모드)"를 그대로 쓰려 했으나, 실제로 아티팩트를 발행해 검증한 결과 이 환경에는 window.storage API 자체가 없었습니다 (지원 capability 목록에 storage 없음 — downloads/mcp/self만 있음). 그래서 shared-api/라는 초소형 API 서버를 대신 두었고, 키 설계(3.3)는 그대로 유지했습니다. ⚙️ TBD 설정값 — config.js 한 곳에서 관리 PRD에서 "추후 확정(TBD)"으로 표시된 값은 전부 아래 파일에 모여 있습니다. • participant-app/src/config.js • admin-app/src/config.jsREADME 전문 보기 ↗
PRD(해커톤_주문_호출_서비스_PRD.md) 기반 구현. 두 개의 완전히 독립된 웹 앱으로 구성됩니다.
해커톤 티오더/
├─ participant-app/ 참가자용 모바일 웹 (QR 스캔 → 주문/호출) — 독립 프로젝트
├─ admin-app/ 운영진(관리자)용 웹 (주문 현황/호출 처리) — 독립 프로젝트
├─ shared-api/ 두 앱이 공유하는 초경량 KV API (의존성 없음) — 실제 배포 시 필요
├─ preview-server.js 로컬 연동 테스트용 정적 서버 (의존성 없음)
└─ README.md
두 앱은 코드/빌드/배포가 완전히 분리되어 있고, 오직 공유 데이터 저장소를 통해서만 간접적으로 연동됩니다 (직접 API 호출 없음).
⚠️ 아티팩트 shared storage 관련 정정: 처음엔 PRD 3.3의 "Claude 아티팩트 영속 저장소(window.storage, shared 모드)"를 그대로 쓰려 했으나, 실제로 아티팩트를 발행해 검증한 결과 이 환경에는
window.storageAPI 자체가 없었습니다 (지원 capability 목록에 storage 없음 — downloads/mcp/self만 있음). 그래서shared-api/라는 초소형 API 서버를 대신 두었고, 키 설계(3.3)는 그대로 유지했습니다.
PRD에서 "추후 확정(TBD)"으로 표시된 값은 전부 아래 파일에 모여 있습니다.
participant-app/src/config.jsadmin-app/src/config.js| 설정 | 상수 | 현재 임시값 |
|---|---|---|
| 식사별 주문 가능 시작/종료·식사 시각 | MEALS | PRD 4.2 예시 시각 (9/21~22) |
| 코치 호출 횟수 제한 | CALL_LIMIT_PER_TEAM | 5회 |
| 호출 사유 목록 | CALL_REASONS | 멘토링 요청 / 기술 문제 / 기타(직접 입력 가능) |
| 코치 개인별 담당 팀 번호 | COACH_ASSIGNMENTS | 자리 4개, name·teamNumbers 전부 비어있음(TBD) |
| 알레르기 선택지 | ALLERGY_OPTIONS | 계란·우유·땅콩·견과류 등 (+ 직접 입력) |
| 메뉴(음식/음료, 뱃지·사진·알레르기 설명 포함) | MENUS | 샘플 메뉴 (image·allergyNote는 TBD, 빈 값) |
| 팀 번호 범위, 인원 상한, 폴링 주기 | TEAM_LEAGUES 등 | E-01 |
| 공유 API 서버 주소 | API_BASE_URL | 비어 있음(로컬 폴백) — 배포 후 채워야 함 |
| 참가자 다크모드 시간대 | DARK_MODE_HOURS | 20시 ~ 익일 7시 |
⚠️ 두 앱의
config.js는 반드시 동일한 내용으로 유지하세요. 값 변경 시 두 파일을 함께 수정해야 합니다 (앱이 완전히 분리되어 있어 자동 동기화되지 않음).
src/lib/storage.js 어댑터가 저장소를 추상화합니다.
API_BASE_URL이 설정된 경우: shared-api/(작은 공유 KV 서버)를 호출 — 참가자/관리자 앱이 서로 다른 배포 URL(다른 origin)이어도 실제로 데이터가 공유됨. 실제 해커톤 배포 시 반드시 이 방식을 사용하세요.localStorage 폴백 (hackathon-torder: 접두사) — 같은 브라우저 안에서만 유효. 로컬 개발/시연용으로만 사용 (실제 참가자 500명은 각자 다른 기기라 이 모드로는 연동되지 않음).| 키 | 내용 | 쓰는 쪽 |
|---|---|---|
team:{teamId} | 팀 정보(인원수·알레르기) | 참가자(등록) |
team-roster | 등록된 팀 id 목록 (관리자 열거용) | 참가자 |
order:{teamId} | 팀의 식사별 주문 내역 | 참가자 |
call:{teamId} | 호출 목록(사유/상태/타임스탬프/담당코치) | 참가자(생성), 관리자(상태 변경) |
call-count:{teamId} | 누적 호출 횟수 (제한 검증) | 참가자 |
coach-roster | 등록 코치 목록 {coaches: [{id, name, groupId, group}]} | 코치(관리자 앱) |
soldout | 품절 메뉴 map | 관리자 |
alert-state | 슬랙 알림 발송 이력 (중복 발송 방지) | 공유 API 서버 |
팀별 키 분리로 동시 쓰기 충돌을 최소화합니다.
모든 팀이 같은 QR/URL을 씁니다. 참가자가 처음 접속하면 팀 정보를 직접 입력합니다.
team.allergies = [[사람1 알레르기...], [사람2 알레르기...]]). 관리자 화면엔 "1인(땅콩·견과류), 2인(우유)"처럼 표시되어 대체 메뉴를 몇 개·어떤 조합으로 준비할지 바로 판단 가능계열사 대신, 팀 번호로 코치 개인을 배정합니다. config.COACH_ASSIGNMENTS에 코치별로 이름과 담당 팀 번호를 채워두면(예: { name: '김민준', teamNumbers: [1, 2, 3] }), 그 팀들의 호출은 그 코치가 우선 응답합니다. 지금은 이름·팀 번호 둘 다 비어 있어(TBD) 전부 미배정 상태 — 코치 명단과 팀 편성이 확정되면 값만 채우면 됩니다.
shared-api 폴더를 연결 (Root Directory: shared-api)node server.jshttps://hackathon-torder-api.onrender.com)https://<그 주소>/health 접속해서 {"ok":true,...} 뜨는지 확인무료 티어 서버는 ① 무요청 15분이면 잠들고 ② 재시작/재배포 때 프로세스가 새로 뜨며 ③ 영구 디스크가 없습니다. 아무 설정 없이 쓰면 그때마다 팀 등록·주문·호출 기록이 전부 사라집니다. 아래 두 가지를 같이 해두세요. (하나만 해서는 막히지 않습니다.)
(a) 데이터를 서버 밖에 보관 — 재시작해도 살아남게
upstash.com 가입 → Redis 데이터베이스 생성 (무료 플랜, 카드 불필요)
대시보드의 REST API 항목에서 UPSTASH_REDIS_REST_URL 과 UPSTASH_REDIS_REST_TOKEN 복사
Render 서비스 → Environment 에 아래 환경변수 등록 (토큰은 코드에 넣지 말고 반드시 여기에)
| 이름 | 값 | 필수 |
|---|---|---|
UPSTASH_REDIS_REST_URL | Upstash에서 복사한 REST URL | ✅ |
UPSTASH_REDIS_REST_TOKEN | Upstash에서 복사한 REST 토큰 | ✅ |
ADMIN_TOKEN | 초기화용 비밀 문자열(직접 지정) | 선택 |
CALL_LIMIT_PER_TEAM | 팀당 호출 제한. 기본 5 | 선택 |
TEAM_LEAGUES | 리그별 마지막 테이블 번호. 기본 E:208,G:31 | 선택 |
MENU_STOCK | 메뉴별 준비 수량. 기본 md-a:300,md-b:300,bf-a:200,bf-b:200 | 선택 |
COACH_ACTIVE_TTL_MS | 메이트 접속 현황 유지 시간. 기본 120000(2분) | 선택 |
⚠️
CALL_LIMIT_PER_TEAM은 앱config.js의CALL_LIMIT_PER_TEAM과 같은 값이어야 합니다. 화면에서만 막으면 두 기기로 열어두면 넘길 수 있어 서버가 최종 판단하기 때문입니다. 제한을 바꿀 때는 config와 이 환경변수를 함께 고치세요.같은 이유로
TEAM_LEAGUES는 config의LEAGUES,MENU_STOCK은 config의MENUS[].stock과 맞춰야 합니다. 세 값 모두 서버 기본값이 현재 config와 같게 박혀 있으니, config만 바꿀 때는 환경변수를 아예 등록하지 않는 편이 안전합니다 (등록해둔 옛 값이 코드보다 우선하기 때문입니다).
재배포 후 /health 가 "persist":{"mode":"redis", ...} 로 보이면 정상.
"mode":"memory" 면 환경변수가 안 먹은 것이고, lastError 에 값이 있으면 연결 실패입니다.
환경변수를 비워두면 예전처럼 메모리 전용으로 동작합니다 (로컬 개발용). 코드 수정은 필요 없습니다.
(b) 서버가 잠들지 않게 — 콜드스타트 지연 방지
cron-job.org 같은 무료 크론 서비스에 아래를 등록합니다. 코드 수정 없음.
https://<서버주소>/health(c) 해커톤 전 테스트 데이터 초기화
(a)를 적용하면 테스트 기록도 계속 남습니다. 실서비스 직전에 한 번 비우세요. ADMIN_TOKEN 을 설정한 경우에만 동작합니다.
curl -X POST https://<서버주소>/api/reset -H "Content-Type: application/json" -d "{\"token\":\"<ADMIN_TOKEN>\"}"
보통은 아무것도 하지 않아도 됩니다. Render 서비스가 이 저장소에 연결돼 있어
main 에 푸시하면 자동으로 다시 배포됩니다 (1~3분).
반영됐는지 확인하는 방법 — 새로 만든 엔드포인트를 직접 찔러봅니다:
curl -X POST https://hackathon-api-hi55.onrender.com/api/stock -d "{}"
준비 수량이 담긴 JSON이 오면 새 코드가 올라간 것이고, 404 면 아직 옛 코드입니다.
서버 상태 전반은 /health 로 봅니다.
손으로 배포해야 하는 경우는 하나뿐입니다 — 환경변수를 바꿨을 때. Render의 Environment 화면에서 값을 고치고 Save only 만 누르면 이미 돌고 있는 프로세스에는 적용되지 않습니다. 아래까지 해야 실제로 바뀝니다.
hackathon-api-hi55) → Manual Deploy → Deploy latest commit
[persist] 복원 완료 — N개 키 가 뜨고 N이 0이 아닌지 확인
(0이면 Redis 환경변수가 안 먹은 것이라 데이터가 날아갑니다)/health 로 마무리 확인 — "persist":{"mode":"redis"} 이어야 정상해커톤 당일 슬랙 알림을 켤 때가 정확히 이 경우입니다.
SLACK_WEBHOOK_URL을 등록한 뒤 반드시 Manual Deploy 나 Restart 까지 하고,/health의slack.enabled가true로 바뀌었는지 눈으로 확인하세요.
관리자 페이지를 켜두지 않아도 담당 마스터 메이트 폰으로 호출 알림이 가게 합니다.
(a) 슬랙 Incoming Webhook 발급
#해커톤-호출-알림)회사 워크스페이스는 앱 설치에 관리자 승인이 필요할 수 있습니다. 승인을 기다리는 동안 개인 워크스페이스를 만들어 URL을 받아 테스트하고, 승인 후 URL만 교체하면 됩니다.
(b) Render 환경변수 등록
| 이름 | 값 | 기본값 |
|---|---|---|
SLACK_WEBHOOK_URL | (a)에서 받은 Webhook URL. 없으면 알림 기능 전체가 꺼짐 | 없음 |
SLACK_LEAD_USER_ID | 호출 전반을 관리하는 운영 총괄의 슬랙 멤버 ID | 없음 |
ALERT_UNCLAIMED_MIN | 이 시간(분) 넘게 미처리면 @channel 로 채널에 공개. off 면 안 보냄 | off |
ALERT_LEAD_MIN | 이 시간(분) 넘게 미처리면 운영 총괄에게 알림. off 면 안 보냄 | off |
ALERT_LEAD_REPEAT_MIN | 운영 총괄 재알림 간격(분) | 10 |
ALERT_IN_PROGRESS_MIN | 처리 시작 후 이 시간(분) 넘게 미완료면 총괄에게 알림. off 면 안 보냄 | off |
ALERT_UNCLAIMED_MENTION | 미처리 알림의 호출 방식 — channel / here / none | channel |
(c) 담당자별 슬랙 멤버 ID 채우기
config.js의 COACH_ASSIGNMENTS 각 항목에 slackUserId를 넣으면 그 사람만 멘션되어 개인 알림을 받습니다.
{ id: 'coach-1', name: '김OO', teamNumbers: [1, 2, 3], slackUserId: 'U01ABCDEF' },
⋯) → 멤버 ID 복사slackUserId를 채우지 않으면 담당자 폰에 알림이 가지 않습니다. 슬랙은 멘션이 없는 채널 메시지에 대해 기본적으로 푸시 알림을 보내지 않기 때문에, 채널에 글만 쌓이고 아무도 알아채지 못합니다. 앱은 ID가 없어도 정상 동작하지만(이름만 표기), "관리자 페이지를 켜두지 않아도 알림을 받는다"는 목적을 달성하려면 ID 입력이 필수입니다.@channel 이 붙습니다. 이게 없으면 "여유 있는 분이 받아주세요" 메시지를 아무도 못 봅니다.멘션 종류별 도달 범위 — 이 서비스는 슬랙을 푸시 알림 통로로만 쓰고 실제 작업은 관리자 페이지에서 하는 구조입니다. 메이트가 슬랙 채팅방에 상주하지 않으므로 멘션 선택이 중요합니다.
| 멘션 | 슬랙을 닫아둔 사람에게 푸시 | 용도 |
|---|---|---|
<@멤버ID> 개인 멘션 | ✅ | 신규 호출 — 담당자 1명만 |
<!channel> | ✅ | 미처리 호출 — 채널 전원 (기본값) |
<!here> | ❌ 슬랙에 '활동 중'인 사람만 | 슬랙을 상시 보는 조직에서만 적합 |
@here 는 활동 상태에 의존해, 슬랙을 닫아두거나 앱을 백그라운드로 내려둔 메이트에게는 알림이 가지 않습니다. 그래서 기본값을 channel 로 둡니다. 너무 시끄러우면 ALERT_UNCLAIMED_MIN 을 늘리는 편이 here 로 바꾸는 것보다 안전합니다.
⚠️ 워크스페이스 설정에서
@channel사용을 제한해 둔 경우, 멘션이 표시만 되고 알림이 가지 않을 수 있습니다. 해커톤 전에 실제로 알림이 오는지 한 번 확인하세요.
(d) 알림 단계
| 시점 | 슬랙에 뜨는 것 | 개인 알림 대상 | 기본값 |
|---|---|---|---|
| 호출 즉시 | 🔔 팀 47 호출 · 담당 @김OO + 사유 | 담당 메이트 1명 | 켜짐 |
ALERT_UNCLAIMED_MIN 경과 | ⏳ @channel 팀 47 16분째 미처리 — 여유 있는 분이 받아주세요 | 채널 멤버 전원 | 꺼짐 |
ALERT_LEAD_MIN 경과 | 🚨 @총괄 장시간 미처리 호출 2건 (묶음) | 운영 총괄 1명 | 꺼짐 |
| 처리 시작·완료 | 보내지 않음 | — | — |
@channel 은 지금 호출과 무관한 사람의 폰까지 울립니다. 놓친 호출은 관리자 화면에 미처리로 남아 있고, 처리하려면 어차피 그 화면으로 와야 합니다.30, 총괄 25 입니다 — 메이트가 한 팀에 머무는 시간이 15분이라 그보다 짧게 잡으면 정상적으로 멘토링 중인 담당자를 계속 재촉하게 됩니다. 리테일 조는 알림을 받는 사람이 조장 둘뿐이라(groupLead) 놓쳤을 때 메울 곳이 없어, 최대한 조용하되 놓친 것은 반드시 드러나는 선으로 30분을 잡았습니다./health 의 slack 에서 봅니다 (0 = 꺼짐)./health 의 "slack":{"enabled":true, ... "lastError":null}슬랙 알림은
<!channel>/<!here>/<@멤버ID>형식으로 보내야 실제로 전달됩니다. 텍스트에@channel이라고 적으면 글자로만 표시되고 아무에게도 알림이 가지 않습니다.
Incoming Webhook은 보낸 메시지의 식별자를 돌려주지 않아 스레드 답글·이모지 반응·버튼을 쓸 수 없습니다. 그 기능들은 봇 토큰(승인 문턱이 더 높음)이 필요합니다. 현재 설계는 "호출됐다"는 신호만 전달하고, 처리 상태는 관리자 화면에서 확인하는 방식입니다.
참가자가 남긴 호출 사유를 구글 스프레드시트에 한 줄씩 쌓습니다. 서버 저장소(Redis)는 해커톤이 끝나면 초기화하므로, 회고에 쓸 기록은 여기만 남습니다.
시트 칸: 호출 시각 · 팀 번호 · 팀명 · 소속 · 리그 · 호출 사유 · 담당 마스터 메이트 · 상태 · 처리 시작 · 완료 · 처리자 · 호출 ID
구글 시트 API 대신 시트에 붙은 Apps Script를 웹앱으로 씁니다. 서비스 계정·JWT 서명이 필요 없어 이 서버의 "의존성 없음" 원칙을 지킬 수 있습니다(슬랙을 Incoming Webhook 하나로 끝낸 것과 같은 이유).
설치 — 10분
Code.gs 내용을 지우고 shared-api/sheets-archive.gs 전체를 붙여 넣습니다.SHEETS_TOKEN / 값: 아무나 못 맞출 임의의 문자열(32자 정도)…/exec)을 Render 환경변수에 넣습니다.| 환경변수 | 값 |
|---|---|
SHEETS_WEBHOOK_URL | 4)에서 받은 https://script.google.com/macros/s/…/exec |
SHEETS_TOKEN | 3)에서 정한 값과 똑같이 |
확인
curl -s https://hackathon-api-hi55.onrender.com/health
"sheets":{"enabled":true,"sent":0,"failed":0,"lastError":null} 이면 연결된 것입니다. 호출을 한 건 넣어 보고 sent 가 오르는지, 시트에 줄이 생기는지 보세요.
동작
/health 의 failed·lastError 에만 남습니다.config.js 의 TEAMS). 명단을 고치면 그 뒤의 호출부터 반영됩니다.스크립트를 고친 뒤에는 배포 → 배포 관리 → 연필(수정) → 버전 '새 버전' → 배포. '새 배포'를 누르면 주소가 바뀌어 서버가 옛 주소로 계속 보냅니다.
participant-app/src/config.js, admin-app/src/config.js 양쪽 다 아래 값을 1)에서 받은 주소로 변경:
export const API_BASE_URL = 'https://hackathon-api-hi55.onrender.com'
Vercel/Netlify 등에 participant-app, admin-app을 각각 별도 프로젝트로 배포 (build: npm run build, output: dist). 두 앱 모두 1)의 같은 API 서버를 바라보므로, 서로 다른 도메인이어도 데이터가 공유됩니다.
⚠️ Render 무료 티어는 일정 시간 무요청 시 슬립 상태가 됩니다. 1-1)의 (a)(b)를 반드시 먼저 적용하세요. 적용 전이라면 해커톤 시작 전에
/health로 미리 한 번 깨워둬야 첫 요청이 느리지 않습니다.
125팀·메이트 40명이 같은 시각에 쓰는 화면이라, 공유 목록을 앱에서 "읽고 → 고치고 → 쓰면" 나중 쓰기가 앞 쓰기를 지웁니다(분실 갱신). 30팀이 동시에 등록하는 실험에서 실제로 1팀만 남았습니다.
그래서 목록을 건드리는 동작은 서버 전용 엔드포인트로 처리합니다. 서버는 단일 스레드라, 읽고 고쳐 쓰는 일을 서버 안에서 하면 경쟁이 원천적으로 사라집니다.
| 엔드포인트 | 하는 일 | 막는 사고 |
|---|---|---|
POST /api/roster-add | 팀 목록에 번호 추가 | 팀이 관리자 화면에서 사라짐 → 음식 미배부 |
POST /api/coach-upsert | 메이트 목록 갱신 | 입장한 메이트가 현황에서 누락 |
POST /api/call-add | 제한검사 + 호출추가 + 횟수증가를 한 번에 | 호출 유실·중복 호출·제한 초과 |
POST /api/call-status | 호출 하나만 상태 변경 | 새 호출이 사라짐 / 완료가 대기로 되돌아감 |
POST /api/flag-set | 품절·배부 표시 하나만 변경 | 서로 다른 토글이 지워짐 |
POST /api/snapshot | 관리자 화면 전체 데이터를 한 번에 조회 | 관리자 40명의 반복 요청 폭증 |
앱은 이 엔드포인트가 없으면(404) 예전 방식으로 물러납니다. 서버 배포가 앱보다 늦어도 멈추지 않고, 서버가 갱신되면 자동으로 원자적 경로를 씁니다.
같은 팀의 두 기기가 각각 주문을 담은 경우는 서버가 판단할 수 없어(둘 다 정당한 주문), 저장 직전에 서버 기록과 비교해 덮어쓸지 물어봅니다. 애초에 한 명만 담도록 팀 등록 화면에 안내도 넣었습니다.
자동 회귀 테스트는 다음 한 줄로 실행합니다.
cd shared-api && npm test
메이트 명단을 채운 뒤에는 아래 검사로 1~125팀 단일 배정, 이름·Slack ID 중복, 누락 팀, 호출 총관리자 1명을 한 번에 확인합니다. 명단이 비어 있는 동안은 대기 메시지만 출력하고 실패시키지 않습니다.
cd shared-api && npm run validate-config
shared-api 를 띄우고 동시 요청을 날려 확인합니다.
CALL_LIMIT_PER_TEAM=5 PORT=3011 node shared-api/server.js
배포는 끝났고, 해커톤 당일까지 남은 확인 항목입니다. 위에서 아래 순서로 진행하세요.
| # | 항목 | 상태 |
|---|---|---|
| 1 | 공유 API 서버 배포 (shared-api) | ✅ 완료 |
| 2 | Upstash 영속화 환경변수 적용 (/health → "mode":"redis") | ✅ 완료 |
| 3 | 두 앱 config.js의 API_BASE_URL 반영 | ✅ 완료 |
| 4 | 참가자·관리자 앱 Vercel 재배포 확인 | ✅ 완료 |
| 5 | 실제 화면 왕복 테스트 (참가자 호출 → 관리자 확인) | ✅ 완료 |
| 6 | 크론으로 슬립 방지 등록 (/health, 10분 주기) | ✅ 완료 |
| 7 | 재시작 후 데이터 생존 확인 (Manual Deploy → /health) | ✅ 완료 |
| 8 | 구 Render 서비스 삭제 (hackathon-service-6uf0) | ✅ 완료 |
| 9 | 슬랙 호출 알림 — 코드 구현 | ✅ 완료 |
| 10 | 슬랙 Webhook 승인 · 실서버 알림 검증 | ✅ 검증 완료 |
| 10-1 | SLACK_WEBHOOK_URL — 해커톤 전에는 비워둡니다 (테스트 알림 방지) | ⬜ 당일 등록 |
| 11 | 담당자별 slackUserId 채우기 — 미입력 시 개인 알림 안 감 | ✅ 45명 전원 |
| 12 | config.js 실제 값 (메뉴·명단·담당 팀·날짜) | ✅ 완료 |
| 13 | 담당 팀 배정 최종 확정 후 node shared-api/validate-config.js 재실행 | ⬜ |
| 14 | 외부 연계 채널 새로 만들기 → 새 Webhook → 환경변수 교체 → 재배포 | ⬜ 아래 참고 |
| 15 | 그 채널에서 멘션 실제 확인 (캐롯글로벌 11명은 워크스페이스 멤버가 아님) | ⬜ |
| 16 | 알림 채널에 마스터 메이트 초대 + 알림 설정 안내 (아래 참고) | ⬜ |
| 17 | 테스트 데이터 초기화 (/api/reset) | ⬜ 해커톤 직전 |
외부에서 도와주는 마스터 메이트가 있어 알림 채널을 Slack Connect(외부 연계) 방으로 새로 만듭니다. 앱이 그 채널에 글을 올리는 것도, 멘션으로 푸시를 보내는 것도 됩니다. 다만 웹훅은 채널 하나에 고정이라 지금 URL로는 새 채널에 안 갑니다. 아래를 순서대로 하세요.
SLACK_WEBHOOK_URL 을 새 값으로 교체/health 에서 slack.enabled: true 확인@이름 으로 멘션해 보고, 파란 이름으로 뜨고
그분에게 알림이 가는지 확인. 그 다음 프로필 → ⋮ → 멤버 ID 복사 로
가져온 값이 config의 slackUserId 와 같은지 대조 — 다르면 그 채널 기준
값으로 바꿔야 합니다.⚠️ 캐롯글로벌 11명은 52g 워크스페이스 멤버 목록에 없던 분들입니다. 지금 넣어둔 ID가 외부 연계 채널에서도 그대로 통하는지는 채널을 만든 뒤에야 알 수 있습니다. 멘션이 풀리지 않으면 그 사람 폰은 울리지 않습니다 — 알림 자체는 채널에 올라오고 이름도 함께 적히므로(
담당 <@U...> (조현아)) 채널을 보고 있는 사람은 누구 담당인지 알 수 있습니다. 다만 미처리 재촉 단계는 기본으로 꺼져 있으므로, 멘션이 풀리지 않으면 그분 폰은 끝까지 울리지 않습니다 — 채널을 만든 직후 테스트 호출로 꼭 확인하세요.
Vercel이 GitHub과 연동돼 있으면 푸시 시 자동 재배포됩니다. 배포 완료 후:
보이지 않으면 브라우저 개발자도구 Network에서 요청 주소가 hackathon-api-hi55인지 확인하세요. 옛 주소면 재배포가 안 된 것입니다.
cron-job.org 등에 아래를 등록합니다. 코드 수정은 필요 없습니다.
https://hackathon-api-hi55.onrender.com/health가장 중요한 검증입니다. 이게 통과해야 해커톤 중 데이터 소멸 위험이 사라집니다.
/health의 keys 값을 기억)[persist] 복원 완료 — N개 키 에서 N이 0이 아니면 성공/health의 keys 값이 재시작 전과 같은지 확인복원 완료 — 0개 키 가 뜨면 영속화가 동작하지 않는 것이니 환경변수를 다시 확인하세요.
hackathon-service-6uf0.onrender.com 는 다른 계정(개인 GitHub 로그인)에 남아 있는 이전 배포입니다. 같은 저장소에 연결돼 있어 푸시할 때마다 자동 배포가 계속 돌아갑니다. 동작에 해는 없지만 어느 서버가 실제 운영본인지 혼동을 유발하므로 해당 계정으로 로그인해 삭제하세요.
슬랙은 PC에서 활동 중인 사람에게는 모바일 푸시를 보내지 않을 수 있습니다. 노트북을 켜둔 채 자리를 비우면 휴대폰 알림이 늦을 수 있습니다.
이 채널은 알림만 받는 방이 아니라 대화도 하는 방이라, 기본 설정은 "내 담당 팀 호출을 나에게만" 하나뿐입니다. 놓친 호출을 채널 전원에게 알리는 단계는 꺼져 있습니다 — 미처리 호출은 관리자 화면에 그대로 남아 있고, 처리하려면 어차피 그 화면으로 와야 합니다.
채널에 마스터 메이트를 초대하면서 다음을 공지하세요.
📢 마스터 메이트 호출 알림 안내
- 이 채널 알림을 꼭 켜두세요 (음소거하지 말아주세요)
- 휴대폰 슬랙 앱에도 로그인해두세요
- 🔔 @내이름 → 내 담당 팀의 호출입니다. 바로 이동해주세요
- 처리는 관리자 페이지에서 합니다 (슬랙에서 답글을 달 필요는 없습니다)
- 처리는 관리자 페이지에서 [처리 시작] → [완료 처리]
(슬랙 메시지에는 버튼이 없습니다)
※ 노트북 슬랙을 켜둔 채 자리를 비우면 휴대폰 알림이 오지 않을 수 있습니다.
휴대폰 슬랙 → 알림 설정에서 "컴퓨터에서 활동하지 않을 때" 대기 시간을
가장 짧게(즉시 또는 2분) 설정해두시면 좋습니다.
/health 의 slack 항목으로 상태를 확인할 수 있습니다.
"slack": { "enabled": true, "unclaimedMin": 15, "leadMin": 25, "unclaimedMention": "channel", "sent": 3, "lastError": null }
sent 는 메모리 카운터라 서버가 재시작되면 0으로 돌아갑니다 (이상 아님).lastError 에 값이 있으면 슬랙 전송이 실패한 것입니다 (slack 404 → URL 오류, slack 403 → 웹훅 취소).200으로 받았는데도 휴대폰이 울리지 않으면, 대부분 위 12)의 PC 활동 중 푸시 억제 때문입니다.Node.js 18+ 필요.
# 1) 공유 API 서버 (터미널 1)
cd shared-api ; npm run start # http://localhost:3001
# 2) 참가자/관리자 앱 — 각 config.js의 API_BASE_URL을 http://localhost:3001 로 설정 후
cd participant-app ; npm install ; npm run dev # http://localhost:5173/
cd admin-app ; npm install ; npm run dev # http://localhost:5174/
API_BASE_URL을 채우면 5173/5174처럼 포트(origin)가 달라도 shared-api를 통해 실제로 데이터가 공유됩니다 (Vercel에 각각 배포했을 때와 동일한 상황을 로컬에서 재현). API_BASE_URL을 비워두면 localStorage 폴백만 쓰므로, 같은 브라우저의 두 탭이라도 무관하게 동작 — 이 경우는 아래처럼 같은 origin으로 묶어서 봐야 합니다.
# API 서버 없이 UI만 빠르게 훑어볼 때 (같은 origin으로 묶어서 localStorage 공유)
cd participant-app ; npm run build
cd ../admin-app ; npm run build
cd .. ; node preview-server.js
# → http://localhost:4173/participant/
# → http://localhost:4173/admin/
해커톤 일정(2026-09-21~22) 밖에서도 테스트할 수 있도록 URL에 ?now= 파라미터를 지원합니다.
⚠️ 로컬 개발 서버에서만 동작합니다. 배포판(
npm run build)에서는 아예 빠집니다. 살려두면 참가자가 URL에?now=를 붙여 주문 시간대를 임의로 열 수 있기 때문입니다. 배포된 주소로 주문 시간대를 리허설해야 하면,config.js의 시간을 임시로 당겨 한 번 배포한 뒤 되돌리세요.
/participant/?now=2026-09-21T14:30 ← 저녁 주문 시간대로 시뮬레이션
/admin/?now=2026-09-21T17:30 ← 저녁+야식 내역 겹침 구간
오프셋 방식이라 카운트다운/시간 흐름도 정상 동작합니다. 파라미터가 없으면 실제 현재 시각을 사용합니다.
위 "실제 배포 순서"를 마친 뒤, 참가자 앱 배포 URL 하나로 공통 QR을 만들면 됩니다 (팀별로 다르게 만들 필요 없음).
예: https://<참가자앱-배포-URL>/ — 스캔한 참가자가 앱에서 직접 팀 번호를 선택/입력합니다.
참가자: 공통 QR → 팀 등록(팀번호·인원수·알레르기, 계열사 선택 없음) · 이용 안내 섹션 · 시간대 자동 전환 단일 화면 · 마감 카운트다운 · 음식/음료 카테고리 + 식이 뱃지 + 사진 자리 · 인원수만큼 음식 수량 제한 · 품절 반영 · 주문 수정/취소 + 하단 고정 요약바 · 주문 내역 노출 규칙(orderStart~eatAt, 겹침 표시) · 코치 호출(사유 선택, 팀 번호 기준 담당 조 안내, 상태 표시 + 진동, 횟수 제한) · 시간대별 다크모드
관리자: 이름 + 담당 조 입장 · 팀별 주문(담당 조·인원수·과다 표시) + 시간대 필터 + 팀 번호 검색 · 메뉴별 합산 집계 · 알레르기 현황 패널 · 품절 토글 · CSV 내보내기(담당 조·인원수 포함) · 호출 알림(사운드/진동, 담당 조 강조 + "우리 조만" 필터, 대기중→처리중→완료, 예상 처리시간) · 코치 현황(조별 대기/대응 상태, 전원 대응 시 경고) · 통계 대시보드 · 자동 폴링 + 수동 새로고침
공통: 서로 다른 origin 간 shared-api 통한 실시간 연동 · 서버 통신 실패 시 무음 폴백이 아닌 명확한 오류 알림/배너