尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證오메추 - 오늘 메뉴 추천 (조건으로 메뉴 추천 + 주변 맛집 찾기)
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗오늘 뭐 먹지? 조건을 고르면 메뉴를 추천하고, 그 메뉴를 파는 주변 식당까지 찾아 주는 웹사이트예요.
🔗 https://cmj1203.github.io/omechu/ (회원가입 · 설치 없이 바로 써요)
| 여기로 가세요 | 이런 분 |
|---|---|
| 바로 써 보기 | 그냥 메뉴 추천을 받고 싶어요 |
| 내 컴퓨터에서 실행하기 | 코드를 내려받아 돌려 보고 싶어요 |
| 식당 정보 새로고침 | 식당 정보가 언제 · 어떻게 바뀌는지 궁금해요 |
| 알아 두면 좋은 것 | 정확도 · 한계가 궁금해요 |
| 개발자용 자세한 정보 | 파일 구조 · DB · 직접 운영하는 법 |
강남역)을 찾고, 목록에서 도로명 주소를 보고 고르면 바로 추천해요. 지금 있는 곳에서 찾으려면 현재 위치로 추천받기를 누르고 브라우저의 위치 권한을 허용해요. 찾은 곳의 주소가 위치 칸에 들어가요.| 기능 | 설명 |
|---|---|
| 🍜 메뉴 추천 | 종류 · 나라 조건에 맞는 메뉴를 골라 줘요. 간식 · 술은 나라 구분 없이 추천해요 |
| 🔁 겹치지 않게 | 최근에 나온 5개는 다시 안 나와요. '이 메뉴 빼기'로 뺀 메뉴는 이 화면을 연 동안만 빠지고 '다시 넣기'로 되돌려요. 늘 뺄 메뉴는 회원이 내 계정에 넣어 둬요 |
| 🍽️ 주변 식당 | 추천 메뉴를 파는 가까운 식당과 도보 시간을 보여 줘요. 2km 안에 없으면 5km까지 넓혀 찾아요. 공무원 맛집과 같은 가게에는 '공무원 단골 · N번' 배지가 붙어요 |
| 📤 공유하기 | 기기의 공유 창(카톡 등)을 열고, 공유 창이 없는 브라우저에서는 내용을 복사해요 |
| 🏛️ 공무원 맛집 | 서울시 본청 · 서울 8개 구(은평 · 서대문 · 관악 · 강남 · 중 · 도봉 · 노원 · 양천) · 행정안전부 · 부산 · 대구 · 대전 · 광주 · 성남 · 용인, 16개 기관 공무원들이 업무추진비로 자주 간 식당 TOP 30과, 위치를 넣으면 그 근처 공무원 맛집. 1인 가격(1.5만·2만·3만 원 이하)으로 거르고, 점심·저녁에 많이 간 순으로도 봐요 (아래 설명) |
| 🗳️ 같이 정하기 | 위 탭 '같이 정하기'나 추천 메뉴 아래 '친구랑 같이 정하기'로 들어가 후보 메뉴 투표 링크를 만들어요. 링크를 받은 사람이 투표하면 결과가 바로 보여요(24시간 뒤 마감) |
| 🔐 관리자 화면 | 메뉴 추가 · 수정 · 삭제, 검색어 미리보기, 폐업 · 엉뚱한 가게 숨기기 |
식당은 우리 DB에 넣어 둔 두 가지 목록에서 찾아요. DB가 멈춰 있으면 OpenStreetMap 공개 서버로 대신 찾아요.
| 출처 | 가게 수 | 설명 |
|---|---|---|
| OpenStreetMap | 약 10만 곳 | 한국의 식당 · 카페 · 술집 · 빵집 |
| 소상공인시장진흥공단 상가(상권)정보 | 약 29만 곳 | 메뉴 검색어가 이름에 들어갔거나 업종이 메뉴에 이어진 음식점만. OpenStreetMap에 이미 있는 가게, 구내식당 · 유흥 주점은 빼요 |
scripts/official_picks.py의 AGENCY_ORDER와 같아요.
| 알아 둘 점 | 설명 |
|---|---|
| 어디에 많아요? | 16개 기관의 청사와 그 지역(서울 9곳, 정부세종청사, 부산 · 대구 · 대전 · 광주 · 성남 · 용인) 근처에 몰려 있어요. 다른 동네에서는 근처 결과가 적거나 없을 수 있어요 |
| 얼마나 이어져요? | 기관마다 공개하는 칸과 가게 이름 적는 방식이 달라서, 가게와 이어지는 비율도 기관마다 달라요. 주소가 있는 서울시 본청이 가장 잘 이어지고, 이름만 있는 기관은 지역 안에서 같은 이름이 여러 곳이면 빠져요. 가져오기 때마다 기관별 받은 건수와 가게 찾은 건수를 로그에 찍어요 |
| 언제 바뀌어요? | 업무추진비는 집행 다음 달에 공개되고, 매달 자동 새로고침 때 함께 다시 모아요. 서대문구 · 중구 · 부산광역시처럼 사용 시각이 없는 기록은 점심 · 저녁 횟수에는 빠져요. 서울시 본청 · 은평구 · 서대문구 · 행정안전부 중 하나라도 못 읽으면 그달은 지우지 않고 지난 목록을 그대로 둬요. 나머지 12개 기관은 하나가 막히거나 40분 안에 끝나지 않으면 그 기관만 건너뛰고 받은 가게만 올리되, 지난 목록은 지우지 않아요. 새로 올린 가게가 전의 절반도 안 되어도 지우기를 건너뛰어요 |
필요한 것은 git과 python3뿐이에요 (맥에는 보통 깔려 있어요). 빌드나 npm install은 필요 없어요.
git clone https://github.com/cmj1203/omechu.git
cd omechu
python3 -m http.server 8000
브라우저에서 http://localhost:8000 을 열어요. (8000번을 이미 쓰고 있으면 다른 숫자를 쓰면 돼요)
admin.html)은 등록된 관리자 계정만 쓸 수 있어요. 일반 회원은 '내 계정'(account.html)에서 가입·로그인하고 즐겨찾기·최근 본 가게를 봐요.npm ci # 처음 한 번 (Playwright, 단위 테스트용 happy-dom)
npx playwright install chromium # 처음 한 번 (브라우저)
npm test # 화면 테스트: 바깥 서비스(Supabase·구글·카카오)는 가짜라 인터넷 없이 돌아요
npm run test:unit # 단위 테스트(bun 필요): tests/*.test.js 만 돌려요. 인자 없는 bun test 는 tests/e2e 까지 집어 실패해요
PGHOST=localhost PGUSER=postgres PGPASSWORD=… npm run test:db # DB 테스트: PostGIS 있는 Postgres에 모든 마이그레이션 + SQL 테스트
GitHub Actions(.github/workflows/test.yml)가 푸시·PR마다 화면 · 단위 · DB 테스트를 돌려요.
GitHub Actions가 매달 자동으로 두 출처의 식당 정보를 다시 받아 DB를 새로 채우고, 공무원 맛집과 폐업 제외는 맥에서 따로 다시 받아요(아래 표). 가져오는 동안에도 기존 정보로 계속 추천돼요.
| 항목 | 내용 |
|---|---|
| 언제 | 한국 시간 매달 2일 새벽 3시 (= 세계 표준시 UTC 매달 1일 18시) |
| 설정 | .github/workflows/refresh-places.yml 의 cron: "0 18 1 * *" |
| 걸리는 시간 | 약 50분 |
| 결과 보기 | 저장소 Actions 탭 → '식당 정보 새로고침' (초록 체크면 성공) |
| 바로 돌리기 | Actions 탭 → '식당 정보 새로고침' → Run workflow |
왜 1일이 아니라 2일이에요? GitHub의 예약 시간(cron)은 한국 시간이 아니라 세계 표준시(UTC)로 적어요. UTC 1일 18시는 한국 시간(UTC보다 9시간 빠름)으로 다음 날인 2일 새벽 3시예요. 사이트를 쓰는 사람이 거의 없는 새벽에 돌리려고 이 시각으로 정했어요.
안전장치: 새로 받은 가게가 이전보다 20% 넘게 적으면 파일이 잘못된 것으로 보고 기존 가게를 지우지 않아요. 두 출처는 따로 관리해서, 한쪽을 새로 가져와도 다른 쪽 가게는 지워지지 않아요.
맥에서 도는 공무원 맛집·폐업 제외. GitHub 러너에서는 전국 인허가가 한 작업 한도(360분) 안에 끝나지 않고 공무원 맛집은 기관이 빠져서, 이 두 가지는 맥에서 돌려요.
| 항목 | 내용 |
|---|---|
| 언제 | 매일 아침 6시에 깨어나서, 2일이 지났고 이번 달 성공 기록이 없는 단계만 돌아요. 맥이 꺼져 있었거나 실패하면 성공할 때까지 다음 날 6시에 다시 해요. 6시에 자고 있었으면 깨어날 때 돌아요 |
| 설치 | brew install uv 후 ./scripts/install-refresh-on-mac.sh (한 번만) |
| 걸리는 시간 | 약 6시간 |
| 결과 보기 | 맥 알림, ~/Library/Logs/omechu-monthly-refresh.log |
| 바로 돌리기 | ~/Library/Application Support/omechu/ 의 official-picks-done, closed-places-done 을 지우고 ./scripts/refresh-on-mac.sh |
| 지우기 | launchctl bootout "gui/$(id -u)/com.omechu.monthly-refresh" 후 ~/Library/LaunchAgents/com.omechu.monthly-refresh.plist 삭제 |
| 궁금한 점 | 답 |
|---|---|
| 식당 정보는 얼마나 새것이에요? | OpenStreetMap은 매달, 상가정보는 분기(3개월)마다 새로 나와요. 문 닫은 가게가 남아 있으면 관리자 화면에서 숨길 수 있어요 |
| 지방에서도 잘 찾아져요? | 메뉴 149개 중 2km 안에 가게가 하나도 없는 메뉴는 강남역 13개, 춘천시청 36개, 전주시청 43개, 세종시청 52개예요 (2026년 10월 기준). 그때는 다른 메뉴 가게나 비슷한 종류의 가게를 추천해요 |
| 왜 어떤 가게는 안 나와요? | 가게 이름에 메뉴 검색어가 들어 있거나 상가정보 업종이 메뉴에 이어져 있어야 그 메뉴 가게로 찾아요. '○○식당'처럼 이름에 메뉴가 안 드러나는 가게는 잘 안 잡혀요 |
| 도보 시간이 정확해요? | 직선거리로 어림한 값이에요. 실제 길은 카카오맵 길찾기 버튼으로 확인하세요 |
| 현재 위치가 안 잡혀요 | 브라우저의 위치 권한을 허용하거나, 위치 칸에 동네 이름을 적으면 돼요 |
| 자동 새로고침이 멈췄어요 | 저장소에 60일 동안 변경이 없으면 GitHub가 예약 실행을 멈춰요. Actions 탭에서 다시 켜거나 Run workflow로 바로 돌리면 돼요 |
| 메뉴를 새로 넣었는데 가게가 적어요 | 새 메뉴의 상가정보 가게는 다음 새로고침 때 들어와요. 바로 넣으려면 Run workflow로 돌리면 돼요 |
아래 제목을 누르면 펼쳐져요.
| 분류 | 기술 |
|---|---|
| 화면 | HTML, CSS, JavaScript (빌드 도구 없음) |
| DB · 로그인 | Supabase (PostgreSQL, Auth, RLS) |
| 식당 · 지도 | OpenStreetMap (Overpass, Nominatim), 소상공인시장진흥공단 상가(상권)정보, 카카오맵 · 구글 지도 링크 |
| 배포 · 자동 작업 | GitHub Pages, GitHub Actions |
화면의 색 · 글자 · 간격 규칙은 DESIGN.md에 있어요.
| 파일 | 설명 |
|---|---|
index.html, js/app.js | 메뉴 추천 화면 |
js/menus.js, data/menus.json | 메뉴 불러오기 (DB에 연결이 안 되면 기본 목록 사용) |
js/nearby.js | 주변 식당 찾기의 시작점 |
js/nearby-location.js | 현재 위치, 동네 이름 → 좌표 |
js/nearby-restaurant-search.js | 추천할 식당 고르기 (DB 먼저, 안 되면 예비 검색) |
js/nearby-database.js | 식당 DB(Supabase) 검색, 반경과 도보 시간 계산 |
js/nearby-overpass.js | DB가 안 될 때 OpenStreetMap 공개 서버로 찾는 예비 검색 |
js/nearby-menu-matching.js | 가게가 메뉴와 맞는지 판단 (가게 종류, 검색어 · 제외어) |
js/nearby-renderer.js | 추천 카드, 다른 가까운 곳 목록, 지도 링크 그리기 |
js/place-rating.js | 구글 평점 · 리뷰 수 찾기, 리뷰 수를 반영한 오메추 평점 (구글 지도 JavaScript 키) |
admin.html, js/admin.js | 관리자 로그인과 메뉴 · 숨긴 가게 관리 |
account.html, js/account.js | 내 계정: 일반 회원 가입 · 로그인, 즐겨찾기 · 최근 본 가게, 탈퇴 |
js/member.js | 회원 즐겨찾기 · 최근 본 가게 저장 (추천 화면에서 씀) |
tests/e2e/, playwright.config.js | 화면 테스트 (가짜 Supabase · 구글 · 카카오) |
tests/*.test.js, tests/helpers/ | 단위 테스트 (bun, 가짜 DOM(happy-dom) · Supabase · 카카오) |
tests/db/ | DB 테스트 (Supabase 흉내 + 마이그레이션 + SQL) |
js/supabase.js | Supabase 주소와 공개(publishable) 키 |
css/style.css | 화면 스타일 |
supabase/migrations/ | DB 표, 권한(RLS), 기본 메뉴, 식당 검색 함수 |
scripts/import-places.sh, scripts/osm_to_places.py | OpenStreetMap 식당을 DB에 (다시) 넣기 |
scripts/import-sbiz-places.sh, scripts/sbiz_to_places.py | 상가(상권)정보 음식점을 DB에 더하기 |
official.html, |
menus (메뉴)
| 필드 | 타입 | 설명 |
|---|---|---|
id | bigint | 번호 (자동) |
name | text | 메뉴 이름 (중복 불가) |
kinds | text[] | 종류(여러 개 가능): 식사 · 간식 · 술 |
cuisine | text | 나라: 한식 · 중식 · 일식 · 양식 · 아시아 |
search_terms | text[] | 식당을 찾을 검색어 (예: 짜장면 → 짜장, 반점, 중화). 비우면 메뉴 이름으로 찾아요 |
exclude_terms | text[] | 제외어: 가게 이름에서 이 말 속에 든 검색어는 안 쳐요 (예: 커리 메뉴에서 '아우어베이커리') |
broad_terms | text[] | 넓은 말: 이 말만 이름에 든 가게는 주력 가게가 없을 때만 골라요 (예: 김치찌개의 '찌개') |
categories | text[] | 이 메뉴가 주력인 상가정보 업종 '중분류/소분류' (예: 치킨의 '기타 간이/치킨') |
broad_categories | text[] | 넓은 업종: 주력 가게가 없을 때만 골라요 (예: 김치찌개의 '한식/국/탕/찌개류') |
places (식당)
| 필드 | 타입 | 설명 |
|---|---|---|
osm_id | text | OpenStreetMap 번호 (상가정보 가게는 sb + 상가업소번호) |
source | text | 어디서 가져왔는지: osm (OpenStreetMap) · sbiz (상가정보) |
category | text | 상가정보 업종 '중분류/소분류' (상가정보 가게만) |
name | text | 가게 이름 |
amenity / shop | text | 가게 종류 (restaurant, cafe, bar, bakery 등) |
cuisine | text | 음식 종류 (있을 때만) |
phone, address | text | 전화, 주소 (있을 때만) |
lat, lng, location | 좌표 | 위치 (거리 계산용 색인) |
official_picks (공무원 맛집)
| 필드 | 타입 | 설명 |
|---|---|---|
place_key | text | 상가정보 가게 번호 (sb + 상가업소번호) |
name, address, category | text | 가게 이름, 주소, 업종 (예: 백반/한정식) |
lat, lng, location | 좌표 | 위치 (근처 찾기용 색인) |
visits, departments | integer | 최근 2년 업무추진비로 간 횟수, 간 부서 수 |
avg_per_person | integer | 1인당 평균 금액(원, 인원을 알 때만) |
lunch_visits, dinner_visits | integer | 점심(11 |
last_visit | date | 최근 방문일 |
agencies | text | 기록을 낸 기관 (예: 서울시 본청, 은평구) |
근처 찾기는 official_picks_near(p_lat, p_lng, p_radius, p_max_price) 함수로 해요 (가까운 순 30곳, 반경 최대 5km, 1인 가격 상한은 빼도 돼요). 메뉴 추천의 식당 검색(nearby_places)은 같은 가게면 official_visits(공무원 단골 횟수)를 같이 돌려줘요.
menu_polls · menu_poll_votes (같이 정하기)
| 필드 | 타입 | 설명 |
|---|---|---|
menu_polls.options | text[] | 후보 메뉴 2 |
menu_polls.expires_at | timestamptz | 마감 시각 (만든 뒤 24시간) |
menu_poll_votes.voter | text | 브라우저에 저장한 무작위 번호 (한 투표에 한 번만) |
menu_poll_votes.option_index | integer | 고른 후보 번호 |
누구나 투표를 만들고 투표할 수 있지만, 고치거나 지울 수는 없어요. 결과는 menu_poll_results(p_poll) 함수로 세요.
hidden_places (관리자가 숨긴 가게)
| 필드 | 타입 | 설명 |
|---|---|---|
osm_id | text | 숨긴 가게의 번호 (식당 정보를 새로 가져와도 그대로 숨겨져요) |
name, address | text | 숨길 때의 가게 이름과 주소 (관리자 화면에 보여 주기용) |
hidden_at | timestamptz | 숨긴 시각 |
admins (관리자)
| 필드 | 타입 | 설명 |
|---|---|---|
user_id | uuid | 관리자로 지정된 Supabase 로그인 계정 |
권한 (RLS)
admins에 등록된 계정만js/supabase.js의 주소와 공개(publishable) 키를 내 프로젝트 것으로 바꿔요..env.local 파일을 만들고 SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, SUPABASE_DB_PASSWORD를 적어요. 이 파일은 git에 올라가지 않아요. 비밀 값은 여기에만 둬요.supabase login
supabase db push --project-ref <프로젝트 ref>
insert into public.admins (user_id) values ('<계정 uuid>');
SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY를 넣어요. 맥에서 도는 공무원 맛집 · 폐업 제외에는 .env.local의 SEOUL_OPENAPI_KEY(서울 열린데이터광장에서 무료로 받는 인증키)와 DATA_GO_KR_KEY가 필요해요.js/place-rating.js의 GOOGLE_MAPS_KEY에 넣어요. 브라우저에 보이는 키라서 키 제한에서 웹사이트(HTTP 리퍼러)를 이 사이트 주소로, API를 그 두 개로 묶어 둬요. 추천 한 번에 가까운 가게 10곳 정도를 찾고(같은 가게는 화면을 연 동안 다시 찾지 않아요), 평점이 있는 장소 검색이라 무료 한도를 넘으면 요금이 나가요. 키를 비워 두면 평점 없이 가까운 순으로 고르고 '평점 우선·거리 우선' 칩도 숨겨요.
https://<내 사이트>/**를 넣어야 가입 확인 메일 링크가 '내 계정'으로 돌아와요.
favorite_places·· 표에 회원별로 저장되고, 자기 것만 읽고 쓸 수 있어요(RLS). 탈퇴()하면 같이 지워져요.새 표나 권한은 supabase/migrations/에 SQL 파일을 추가하고 supabase db push 하면 돼요.
.env.local에 SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY가 있어야 해요. 상가정보 스크립트는 Python 3.11 이상이 필요해요.
brew install osmium-tool # 처음 한 번
./scripts/import-places.sh # OpenStreetMap 식당을 DB에 새로 채우기 (지도 파일 약 290MB)
./scripts/import-sbiz-places.sh # 상가정보 음식점 더하기 (파일 약 350MB)
DRY_RUN=1 ./scripts/import-places.sh # 시험: 내려받고 고르기만 (DB는 안 건드림)
DRY_RUN=1 ./scripts/import-sbiz-places.sh # 시험: 올릴 가게 수만 세기
./scripts/import-official-picks.sh # 공무원 맛집 다시 모으기 (SEOUL_OPENAPI_KEY 필요)
places 표를 새로 채워요.places.source로 나눠서, 각 스크립트는 자기 출처의 오래된 가게만 지워요.식당 정보 © OpenStreetMap 기여자, 소상공인시장진흥공단 상가(상권)정보 (공공데이터포털), 업무추진비 집행내역: 서울시 본청 (서울 열린데이터광장) · 은평구 · 서대문구 · 관악구 · 강남구 · 중구 · 도봉구 · 노원구 · 양천구 · 행정안전부 · 부산광역시 · 대구광역시 · 대전광역시 · 광주 · 성남시 · 용인시 (각 기관 홈페이지, 위 설명에 주소)
예전 버전(JSP + MySQL, 학교 주변 맛집 추천)은 lllllIIlI/jsp에 있어요.
js/official.js| 공무원 맛집 화면 |
scripts/import-official-picks.sh, scripts/official_picks.py | 업무추진비에서 공무원 맛집을 모아 DB에 넣기 |
scripts/local_expenses.py | 은평구 · 서대문구 · 행정안전부 업무추진비 받기 (서대문구 엑셀은 xlrd 필요) |
vote.html, js/vote.js, css/vote.css | 같이 정하기(메뉴 투표) 화면 |
.github/workflows/refresh-places.yml | 매달 자동 새로고침 (OpenStreetMap · 상가정보) |
scripts/refresh-on-mac.sh, scripts/install-refresh-on-mac.sh | 공무원 맛집 · 폐업 제외를 맥에서 매달 다시 받기 |
recent_placesexcluded_menusdelete_my_account