ESP32-S3 Bench Guide
ESP32‑S3 N16R8 벤치 가이드: 실시간 핀아웃, 강의, 시뮬레이터 및 벤치 로그
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
학원용 포인트 뱅킹 웹앱. React + Vite + Supabase. 아직 적용되지 않았습니다. 이 단계를 먼저 하셔야 앱이 동작합니다. 1. https://supabase.com/dashboard 접속 → 해당 프로젝트 선택 2. 왼쪽 메뉴에서 SQL Editor → New query 3. supabase/schema.sql 파일 내용을 전부 복사해서 붙여넣기 4. Run 클릭 여러 번 실행해도 안전하게 작성되어 있으니, 나중에 스키마를 고칠 때도 같은 방식으로 다시 실행하면 됩니다. 이미 schema.sql을 한 번 실행한 뒤 기능이 추가되면, supabase/ 폴더의 번호 붙은 파일만 따로 실행하면 됩니다. 새 프로젝트를 처음부터 세팅할 때는 schema.sql 하나만 실행하면 됩니다 (마이그레이션 내용이 이미 포함돼 있음). Supabase는 기본적으로 회원가입 시 이메일 인증을 요구합니다. 개발 중에는 꺼두는 게 편합니다. • Dashboard → Authentication → Sign In / Providers → Email → Confirm email 끄기 실제 서비스 오픈 전에는 다시 켜세요.README 전문 보기 ↗
학원용 포인트 뱅킹 웹앱. React + Vite + Supabase.
pointbank/
├─ supabase/schema.sql ← DB 스키마 (SQL Editor에서 실행)
├─ app/ ← React 앱
│ ├─ .env.local ← Supabase URL / anon key (git 커밋 금지)
│ └─ src/
│ ├─ lib/ supabase 클라이언트, API 함수, 타입
│ ├─ context/ 인증 상태, 토스트
│ ├─ components/ 통장 카드
│ └─ pages/ 인증 · 온보딩 · 반별통장 · 순위 · 설정 · 학생화면
└─ point-bank-prototype.html ← 원본 프로토타입 (참고용, 이제 미사용)
아직 적용되지 않았습니다. 이 단계를 먼저 하셔야 앱이 동작합니다.
supabase/schema.sql 파일 내용을 전부 복사해서 붙여넣기여러 번 실행해도 안전하게 작성되어 있으니, 나중에 스키마를 고칠 때도 같은 방식으로 다시 실행하면 됩니다.
이미 schema.sql을 한 번 실행한 뒤 기능이 추가되면, supabase/ 폴더의 번호 붙은 파일만 따로 실행하면 됩니다.
| 파일 | 내용 |
|---|---|
002_daily_board.sql | 일일 마감(settlements) + 결과 보기 집계 함수(ranking_summary) |
003_student_scope.sql | 순위 조회 함수를 선생님/원장 전용으로 제한 |
004_academy_logo.sql | 학원 로고 업로드 (academies.logo_url + logos 스토리지 버킷) |
005_attendance.sql | 출석부 (attendance 테이블 — 등원/하원 체크 + 월별 기록) |
006_disable_student_login.sql | 보안 — 학생 로그인 잠금 (베타 기간 동안 원장·선생님만) |
007_platform_admin.sql | 플랫폼 관리자 ([email protected] 전용 — 가입 학원 목록/삭제) |
008_ladder_order_games.sql | 사다리타기 · 랜덤 공 뽑기 게임 + 게임 배경음악 업로드 |
009_fix_logo_storage.sql | 학원 로고 업로드 RLS 수정 (upsert 시 SELECT 정책 누락 문제) |
010_homework_tracking.sql | 프리셋·거래 내역에 "숙제 관련" 표시(is_homework) 추가 |
011_game_images.sql | 명칭이 있는 다이어그램 · 이미지 퀴즈용 사진 업로드 (game-images 스토리지 버킷) |
새 프로젝트를 처음부터 세팅할 때는 schema.sql 하나만 실행하면 됩니다 (마이그레이션 내용이 이미 포함돼 있음).
Supabase는 기본적으로 회원가입 시 이메일 인증을 요구합니다. 개발 중에는 꺼두는 게 편합니다.
실제 서비스 오픈 전에는 다시 켜세요.
npm run dev --prefix app
+ 학생 추가오늘 마감 → 학생별 오늘 적립분을 확인하고 통장에 적립하기누적 총액을 항상 띄워두면 격차가 고정된 사실처럼 보여서, 뒤처진 학생은 따라잡을 수 없다고 느낍니다. 그래서 수업 중에 보는 화면은 오늘 적립분만 크게 표시하고, 매일 0에서 다시 시작합니다.
취소로 되돌릴 수 있습니다.결과 보기 화면에서 봅니다.포인트는 버튼을 누르는 즉시 DB에 저장됩니다. 마감은 저장이 아니라 "확정 + 잠금"이라, 수업 중에 브라우저가 닫히거나 새로고침돼도 기록이 날아가지 않습니다.
날짜는 선생님 브라우저의 로컬 시간 기준입니다 (transactions.created_at은 UTC로 저장되지만,
조회할 때 로컬 자정을 UTC로 변환해 넘깁니다).
설정 화면의 선생님 초대 코드(6자리)를 전달 → 그 선생님이 회원가입 후 "선생님으로 합류하기"에서 입력.
위 "베타 기간: 학생 로그인 잠금" 참고. 코드는 남아 있고(StudentPage.tsx 등) DB 실행 권한만
회수한 상태라, 다시 열 때 새로 개발할 필요는 없습니다.
| 테이블 | 역할 |
|---|---|
academies | 학원 (테넌트 최상위). 포인트 단위, 선생님 초대 코드 보유 |
classes | 반 |
students | 학생 계좌. claim_code로 학생 계정과 연결 |
profiles | 로그인 사용자 (owner / teacher / student / admin) |
transactions | 거래 원장. 잔액은 저장하지 않고 이 합으로 계산 |
presets | 지급/차감 사유 버튼 |
redemptions | 포인트 상품 교환 내역 (추후 기능용, 테이블만 준비) |
game_templates | 미니게임 템플릿 (추후 기능용, 테이블만 준비) |
잔액을 따로 저장하지 않는 이유: "누가 언제 왜 줬는지"를 항상 추적·정정할 수 있어야 하기 때문입니다. 잔액은 student_balances 뷰가 합산해줍니다.
지금은 원장·선생님만 로그인할 수 있습니다. 학생 온보딩 옵션과 claim_student() 함수 실행
권한을 껐습니다.
이유: academies.invite_code(선생님 초대 코드)와 students.claim_code(학생 통장 연결 코드)는
같은 학원 소속이면 누구나 조회할 수 있는 구조입니다. 학생 계정이 존재하면, 학생이 이 값을 읽어
① 선생님 초대 코드로 재가입해 선생님 권한을 얻거나 ② 다른 학생의 claim_code로 그 학생 계정을
가로챌 수 있었습니다. 이메일 인증까지 꺼둔 상태(현재 베타 세팅)라 실제로 가능한 공격 경로였습니다.
학생 로그인을 다시 열려면:
supabase/006_disable_student_login.sql 맨 아래 주석 처리된 grant execute 실행app/src/pages/OnboardingPage.tsx, AuthPage.tsx, SettingsPage.tsx에서 이 마이그레이션 때
지워진 학생 관련 UI 복구 (git log에서 "학생 로그인 잠금" 커밋 참고).env.local의 anon key는 공개되어도 되는 값입니다. 프론트엔드 번들에 어차피 포함됩니다.schema.sql의 4번 섹션.
create_academy / join_academy_as_teacher / claim_student / claim_admin 네 함수로만 가능합니다. 아무나 임의 학원에 owner로 끼어드는 걸 막기 위해서입니다.supabase/007_platform_admin.sql 실행 후, [email protected]으로 정상 회원가입만 하면 됩니다
(비밀번호는 Supabase 최소 자리수인 6자 이상이어야 합니다). 이 이메일로 로그인하면 온보딩 화면에
"관리자로 시작하기" 버튼이 뜨고, 누르면 학원 소속 없이 /admin으로 들어갑니다.
claim_admin() RPC가 서버에서 auth.users.email을 다시 확인하는 방식이라, 클라이언트가
임의로 admin을 자칭할 수 없습니다. admin_list_academies()/claim_admin() 둘 다 anon 롤의 실행
권한을 명시적으로 회수해뒀습니다 (Supabase는 함수 생성 시 anon에게도 기본 EXECUTE 권한을 자동
부여하므로, authenticated/public만 revoke하면 구멍이 남습니다 — 006_disable_student_login.sql
작업 때 발견한 것과 같은 함정).다섯 게임 모두 game_templates 테이블 하나를 공유합니다 (game_type 컬럼으로 구분, CHECK 제약이
없어 새 게임 종류를 추가해도 스키마 변경이 필요 없습니다). 반 전용/학원 공용 여부, 만들기·이름변경·
삭제 흐름은 다섯 게임이 동일해서 app/src/lib/useGameTemplates.ts 훅 하나로 공통 처리합니다.
app/src/lib/ladder.ts — 같은 행에서 인접한 가로줄이 겹치지 않게만 배치하면 항상 1:1 대응이
된다는 성질을 이용했고, 2~10명 기준 2만 회 시뮬레이션으로 검증했습니다.LotteryMachine.tsx).
순위 이름표("1등"/"1번" 등)는 선생님이 직접 눌러서 수정할 수 있고, 참가자 수와 항상 맞춰
자동으로 늘어나거나 줄어듭니다.app/src/components/TimeBomb.tsx). 정확히 언제
터질지는 아무도(선생님도) 알 수 없습니다 — setTimeout으로 한 번만 무작위 지연을 걸어두는
방식이라 서버 폴링 없이 클라이언트에서 그대로 동작합니다.app/src/components/TimerMatch.tsx). 숫자를 보이게/숨기게 고를 수 있고(숨기면 감으로만 맞혀야
하는 원래 방식), "공용 도전판"(참가자 구분 없이 반복 도전)과 "참가자별 순위"(한 명씩 도전하고
끝나면 오차 순 순위표) 중에서도 고를 수 있습니다. 시간 측정은 performance.now() 기준이라
브라우저 프레임 속도와 무관하게 정확합니다.app/src/components/StudentRosterPicker.tsx, app/src/lib/useGameTemplates.ts).game_templates.config (jsonb)에 각각 따로 저장합니다.
배경음악은 진행 중 반복 재생, 결과 사운드는 결과가 확정되는 순간 한 번 재생됩니다(둘 다 한 번도
안 건드린 게임은 결과 사운드가 "짜잔"으로 기본 재생됨 — resolveResultSound()).
app/src/lib/gameMusic.ts) — 어느 학원에서
써도 저작권 문제가 없도록.game-audio 공개 스토리지 버킷에
<academy_id>/<uuid>.<확장자>로 저장(008_ladder_order_games.sql). 업로드하는 음원의
저작권 책임은 업로드한 학원에 있습니다 (서비스가 기본 제공하는 음원이 아님).Vite 결과물은 정적 파일이라 그대로 올라갑니다. 백엔드는 Supabase에 있으니 서버는 필요 없습니다.
git init 부터)app 으로 지정 ← 저장소 루트가 아니라 app/ 폴더가 프로젝트입니다
.env.local은 git에 안 올라가므로 여기서 따로 넣어야 함)
VITE_SUPABASE_URLVITE_SUPABASE_ANON_KEYDashboard → Authentication → URL Configuration
https://내프로젝트.vercel.app안 하면 이메일 인증 링크나 비밀번호 재설정 링크가 localhost:3000으로 가버립니다.
app/vercel.json — SPA rewrite 설정.
react-router를 쓰기 때문에, 이게 없으면 /board 같은 주소에서 새로고침 시 404가 납니다.
(Vercel이 실제 파일을 찾다가 없으니 404를 내는데, 모든 경로를 index.html로 넘겨야 라우터가 처리합니다)네비게이션의 게임 메뉴를 누르면 게임 목록(/games)이 뜨고, 카드를 눌러 개별 게임으로 들어갑니다.
앞으로 추가되는 미니게임(사다리, 빙고 …)은 전부 이 목록 안에 카드로 들어갑니다 —
네비게이션에 메뉴가 하나씩 늘어나지 않습니다.
game_templates 테이블을 그대로 씁니다(스키마 변경 없음).
설정 → 학원 로고에서 이미지를 올리면 헤더 좌측 브랜드 마크(기본은 🐷)가 학원 로고로 바뀝니다.
logos/<academy_id>/logo.png 로 학원마다 고정. RLS로 자기 학원 폴더에만 쓸 수 있고, 읽기는 공개 버킷이라 누구나 가능합니다 (로그인 화면 등에서도 문제없이 표시하려면 공개가 맞습니다).스마트폰에서 앱처럼 아이콘으로 실행할 수 있습니다.
app/public/manifest.webmanifest, icon-192/512.png, apple-touch-icon.png)을
붙여서 Android Chrome은 자체 설치 배너를 띄우고, iOS Safari는 홈 화면 추가 시 아이콘/이름이 제대로 잡힙니다.InstallBanner 컴포넌트가 기기를 감지해서 안내합니다:
localStorage에 기억해서 다시 안 뜹니다.game_templates.game_type 값만 늘리면 되는 구조.redemptions 활용)