feat: _empty_state — current_stage + topics 키 추가
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
57
app.py
57
app.py
@@ -33,10 +33,11 @@ _lock = threading.RLock()
|
||||
def _empty_state():
|
||||
return {
|
||||
"people": [],
|
||||
"settings": {"voting_open": True},
|
||||
"settings": {"voting_open": True, "current_stage": "intro"},
|
||||
"titles": {},
|
||||
"tie_breaks": {},
|
||||
"votes": [],
|
||||
"topics": {"categories": []},
|
||||
}
|
||||
|
||||
|
||||
@@ -55,6 +56,10 @@ def load_data():
|
||||
base.update(data)
|
||||
for k, v in _empty_state().items():
|
||||
base.setdefault(k, v)
|
||||
# 한 단계 deep merge — 기존 데이터에 누락된 nested 키 보강
|
||||
for nested_key in ("settings", "topics"):
|
||||
for k, default_v in _empty_state()[nested_key].items():
|
||||
base[nested_key].setdefault(k, default_v)
|
||||
return base
|
||||
|
||||
|
||||
@@ -360,6 +365,7 @@ def render_admin():
|
||||
f"""
|
||||
- 👥 **참가자 투표**: [/](/)
|
||||
- 🎉 **시상식 (큰 화면)**: [/?mode=ceremony&token=...](?mode=ceremony&token={ADMIN_TOKEN})
|
||||
- 📦 **JSON 원본 조회**: [/?mode=raw&token=...](?mode=raw&token={ADMIN_TOKEN})
|
||||
|
||||
호스트에서 LAN IP 포함 모든 URL 보기:
|
||||
```bash
|
||||
@@ -368,6 +374,20 @@ def render_admin():
|
||||
"""
|
||||
)
|
||||
|
||||
with st.expander("💾 데이터 백업 (hackathon.json 다운로드)"):
|
||||
try:
|
||||
raw_bytes = Path(DATA_PATH).read_bytes()
|
||||
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
st.download_button(
|
||||
"📥 hackathon.json 다운로드",
|
||||
raw_bytes,
|
||||
file_name=f"hackathon_{ts}.json",
|
||||
mime="application/json",
|
||||
)
|
||||
st.caption(f"파일 경로: `{DATA_PATH}` ({len(raw_bytes):,} bytes)")
|
||||
except FileNotFoundError:
|
||||
st.warning(f"파일 없음: {DATA_PATH}")
|
||||
|
||||
voting_open = is_voting_open()
|
||||
cur_label = "🟢 투표 진행 중" if voting_open else "🔴 투표 마감됨"
|
||||
st.markdown(f"### 투표 상태: {cur_label}")
|
||||
@@ -692,6 +712,39 @@ def render_ceremony():
|
||||
st.rerun()
|
||||
|
||||
|
||||
def render_raw():
|
||||
"""JSON 원본 조회 — admin token 필요."""
|
||||
token = st.query_params.get("token", "")
|
||||
if token != ADMIN_TOKEN:
|
||||
st.error("권한 없음. ?mode=raw&token=... 형식 필요.")
|
||||
return
|
||||
|
||||
st.title("📦 hackathon.json 원본")
|
||||
try:
|
||||
raw_text = Path(DATA_PATH).read_text(encoding="utf-8")
|
||||
except FileNotFoundError:
|
||||
st.error(f"파일 없음: {DATA_PATH}")
|
||||
return
|
||||
|
||||
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
col1, col2 = st.columns(2)
|
||||
with col1:
|
||||
st.download_button(
|
||||
"📥 다운로드",
|
||||
raw_text,
|
||||
file_name=f"hackathon_{ts}.json",
|
||||
mime="application/json",
|
||||
use_container_width=True,
|
||||
)
|
||||
with col2:
|
||||
st.caption(f"`{DATA_PATH}` — {len(raw_text):,} bytes")
|
||||
|
||||
try:
|
||||
st.json(json.loads(raw_text))
|
||||
except json.JSONDecodeError:
|
||||
st.code(raw_text, language="json")
|
||||
|
||||
|
||||
def main():
|
||||
st.set_page_config(page_title="해커톤 투표", page_icon="🗳", layout="wide")
|
||||
mode = st.query_params.get("mode", "vote")
|
||||
@@ -699,6 +752,8 @@ def main():
|
||||
render_admin()
|
||||
elif mode == "ceremony":
|
||||
render_ceremony()
|
||||
elif mode == "raw":
|
||||
render_raw()
|
||||
else:
|
||||
render_voter()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user