diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml new file mode 100644 index 0000000..72b3cee --- /dev/null +++ b/.github/workflows/verify.yml @@ -0,0 +1,50 @@ +name: Verify + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +jobs: + structure-and-secrets: + name: Structure & Secrets + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Required files + run: | + for f in README.md LICENSE app/build.gradle build.gradle settings.gradle gradle.properties; do + if [ ! -f "$f" ]; then + echo "::error::Missing required file: $f" + exit 1 + fi + done + + - name: No committed API keys (Food Safety OpenAPI uses base64-like service keys) + run: | + # Sanity check: no hardcoded service keys in source + if grep -rEn 'FOOD_API_KEY[[:space:]]*=[[:space:]]*"[A-Za-z0-9+/=]{20,}"' app/src/ 2>/dev/null; then + echo "::error::Hardcoded Food Safety API key in source" + exit 1 + fi + + - name: .gitignore covers known secret paths + run: | + if ! grep -Fq "local.properties" .gitignore; then + echo "::warning::local.properties should be in .gitignore" + fi + + - name: README links resolve + run: | + missing=0 + for link in $(grep -oE '\]\(\./[^)]+\)' README.md | sed 's|](\./||; s|)$||' | sort -u); do + if [ ! -e "$link" ]; then + echo "::warning::README links to missing path: $link" + missing=$((missing+1)) + fi + done + if [ "$missing" -gt 0 ]; then + echo "::warning::$missing README link(s) point to non-existent paths" + fi diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e5d8a55 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 jumincho + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 55235bf..0b8737d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,23 @@ -# Beating Yesterday (어제의 나 이기기) +
+ +# beating-yesterday + +**어제의 나와 경쟁하며 자기 성장을 동기부여하는 Android 앱** +**Android app that gamifies daily self-improvement against yesterday's record** + +![Platform](https://img.shields.io/badge/platform-Android-3DDC84?logo=android&logoColor=white) +![Language](https://img.shields.io/badge/language-Java-007396?logo=java&logoColor=white) +![Min SDK](https://img.shields.io/badge/minSdk-21-blue) +![License](https://img.shields.io/badge/license-MIT-green) +![Year](https://img.shields.io/badge/year-2021-blue) + +**한국어** · [English](#english) + +
+ +--- + +## 개요 > 어제의 나와 매일 경쟁하며 자기 성장을 동기부여하는 Android 앱 @@ -59,17 +78,26 @@ app/src/main/ └── ... ``` +## 시크릿 처리 + +빌드 시스템은 `local.properties` 에서 식품안전나라 OpenAPI 키를 읽어 Gradle +`buildConfigField` 를 통해 `BuildConfig.FOOD_API_KEY` 로 주입합니다. 키 문자열은 +소스에 포함되지 않으며, `local.properties` 는 `.gitignore` 에 포함되어 있습니다. + +| `local.properties` 키 | `BuildConfig` 필드 | 사용처 | +| --- | --- | --- | +| `FOOD_API_KEY` | `BuildConfig.FOOD_API_KEY` | 식품안전나라 칼로리 조회 | + ## 빌드 방법 -1. 식품안전나라 OpenAPI 키를 발급받습니다 (아래 "보안 주의사항" 참고). -2. 프로젝트 루트에 `local.properties` 파일을 두고 다음 줄을 추가합니다. +1. [식품안전나라 OpenAPI](https://various.foodsafetykorea.go.kr/nutrient/) 에서 API 키 발급 +2. 프로젝트 루트의 `local.properties` 에 다음 줄 추가 ```properties - FOOD_API_KEY=YOUR_KEY_HERE + FOOD_API_KEY=발급받은_키 ``` - `local.properties`는 `.gitignore`에 포함되어 있어 커밋되지 않습니다. -3. Android Studio에서 프로젝트를 열고 `Run`을 실행합니다. +3. Android Studio 에서 프로젝트 열기 → `Run` ```bash ./gradlew assembleDebug @@ -80,15 +108,6 @@ app/src/main/ - JDK 8 이상 - Android SDK 32 -## 보안 주의사항 - -- 이전 커밋(예: `fae2f77` 등 머지된 history)에 식품안전나라 OpenAPI 키 문자열이 - 소스 코드에 하드코딩되어 있었습니다. 해당 키는 이미 공개 git history에 노출되었으므로 - **식품안전나라 사이트에서 즉시 키를 재발급**받기를 권장합니다. - (재발급 후 기존 키는 사용 불가 상태가 되도록 폐기 처리해야 합니다.) -- 이후로는 API 키를 `local.properties`에만 보관하며, Gradle `buildConfigField`를 통해 - `BuildConfig.FOOD_API_KEY` 로 주입합니다. 소스 코드에 직접 키를 적지 마세요. - ## 발표 자료 - 발표 영상: @@ -97,3 +116,109 @@ app/src/main/ ## 스크린샷 앱 스크린샷 + +## 라이선스 + +[MIT License](./LICENSE) + +--- + + + +## English + +> Android app that gamifies daily self-improvement against yesterday's record. + +Logs daily calorie intake and study time, then judges today's record against yesterday's. +Calorie scoring is BMI-aware, so this is less a diet tracker than a personalized +self-management tool that frames each day as a contest with yesterday's you. + +### Features + +- **User profile** — name, sex, age, height, weight → auto-computed BMI. +- **Diet** — log breakfast / lunch / dinner; the Korean Food Safety OpenAPI returns calories, and a BMI-bracket-aware (under / normal / over) calorie score is computed. +- **Study timer** — circular timer with drag-to-set hours / minutes / seconds, plus a stopwatch. +- **TODO** — SQLite-backed list with swipe-to-refresh. +- **Daily contest** — yesterday's calorie score + study time vs today's, reported as "did you beat yesterday?" + +### Screens + +Bottom navigation with three tabs: + +| Tab | Description | +| --- | --- | +| Diet | Food entry · weight management | +| Home | Profile · daily yesterday-vs-today contest | +| Productivity | TODO · study timer | + +### Tech stack + +- **Language**: Java +- **Platform**: Android (minSdk 21 / targetSdk 32) +- **Architecture**: Fragment + ViewModel +- **Storage**: SharedPreferences (profile, daily records) + SQLite (TODO) +- **Network**: HttpURLConnection + Korean Food Safety OpenAPI +- **UI**: Material Components, ConstraintLayout, RecyclerView, Navigation Component + +### Layout + +``` +app/src/main/ +├── java/com/jumincho/beatingyesterday/ +│ ├── MainActivity.java +│ ├── data/ # data layer +│ │ ├── FoodCalorieApi.java # Food Safety API client +│ │ ├── Note.java # TODO model +│ │ ├── NoteAdapter.java +│ │ └── NoteDatabase.java # SQLite helper +│ └── ui/ +│ ├── ProfileSetupActivity.java # first-launch profile entry +│ ├── home/ # home (contest result) +│ ├── diet/ # diet / weight management +│ └── productivity/ # TODO + timer +│ ├── CircularTimerView.java # custom circular timer view +│ └── TimerMode.java +└── res/ + ├── layout/ # screen layouts + ├── navigation/ # bottom-tab navigation graph + └── ... +``` + +### Secrets handling + +The build system reads the Food Safety OpenAPI key from `local.properties` and +injects it via Gradle `buildConfigField` as `BuildConfig.FOOD_API_KEY`. No +secrets are present in source. `local.properties` is gitignored. + +| `local.properties` key | `BuildConfig` field | Use site | +| --- | --- | --- | +| `FOOD_API_KEY` | `BuildConfig.FOOD_API_KEY` | Food Safety calorie lookup | + +### Build + +1. Get an API key from the [Korean Food Safety OpenAPI](https://various.foodsafetykorea.go.kr/nutrient/). +2. Add a `local.properties` entry at the repo root: + + ```properties + FOOD_API_KEY=your_key_here + ``` + +3. Open the project in Android Studio → `Run`. + +```bash +./gradlew assembleDebug +``` + +Requirements: +- Android Studio Bumblebee+ +- JDK 8+ +- Android SDK 32 + +### Materials + +- Demo video: +- Slides: [`docs/presentation.pptx`](docs/presentation.pptx) + +### License + +[MIT License](./LICENSE)