Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .github/workflows/firebase-hosting-merge.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# This file was auto-generated by the Firebase CLI
# https://github.com/firebase/firebase-tools

name: Deploy to Firebase Hosting on merge
on:
push:
branches:
- master
jobs:
build_and_deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
version: 10
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- run: "pnpm install --frozen-lockfile && pnpm -r run build "
- uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: ${{ secrets.GITHUB_TOKEN }}
firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_TIL_ALARM }}
channelId: live
projectId: til-alarm
28 changes: 28 additions & 0 deletions .github/workflows/firebase-hosting-pull-request.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# This file was auto-generated by the Firebase CLI
# https://github.com/firebase/firebase-tools

name: Deploy to Firebase Hosting on PR
on: pull_request
permissions:
checks: write
contents: read
pull-requests: write
jobs:
build_and_preview:
if: ${{ github.event.pull_request.head.repo.full_name == github.repository }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
version: 10
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- run: "pnpm install --frozen-lockfile && pnpm -r run build "
- uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: ${{ secrets.GITHUB_TOKEN }}
firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_TIL_ALARM }}
projectId: til-alarm
14 changes: 7 additions & 7 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
node_modules

# testing
/coverage

# production
/build
app/dist
functions/dist

# misc
.DS_Store
Expand All @@ -19,6 +18,7 @@
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*
.env
.env

# firebase
.firebase/
110 changes: 87 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,96 @@
# today-i-learned-alarm
에빙하우스 망각곡선(1일, 7일, 30일)에 의거하여 공부한 내용을 매일 아침 PUSH 알림을 받을 수 있도록 해주는 프로그램
<br><br>
<div align=center>
<img src="./public/til-alram.gif" width=200>
</div>
# Today I Learned Alarm

<br/>
GitHub 커밋 데이터를 기반으로 학습용 플래시카드를 생성하는 PWA 애플리케이션입니다.

## 기술 스택
## 🏗️ 프로젝트 구조

<div align=left>
<img src="https://img.shields.io/badge/react v18-61DAFB?style=for-the-badge&logo=React&logoColor=white">
<img src="https://img.shields.io/badge/PWA-5A0FC8?style=for-the-badge&logo=PWA&logoColor=white">
<img src="https://img.shields.io/badge/Firebase-FFCA28?style=for-the-badge&logo=Firebase&logoColor=black">
<img src="https://img.shields.io/badge/GitHub-181717?style=for-the-badge&logo=GitHub&logoColor=white">
<img src="https://img.shields.io/badge/Markdown-000000?style=for-the-badge&logo=Markdown&logoColor=white">
</div>
<br>
```
repo/
├── app/ # React + Vite + TypeScript (PWA)
├── functions/ # Cloud Functions (TypeScript + tsup)
├── package.json # 워크스페이스 루트
├── pnpm-workspace.yaml
└── firebase.json
```

## 기술 블로그
## 🚀 기술 스택

- [안드로이드, IOS 지식 없이 SENS로 알림(PUSH) 서비스 개발하기](https://chucoding.tistory.com/129)
- [네이버클라우드 서비스를 활용한 알림(PUSH) 앱 배포하기](https://chucoding.tistory.com/130)
- **패키지 매니저**: pnpm (워크스페이스 지원)
- **프론트엔드**: Vite + React + TypeScript + PWA
- **백엔드**: Firebase Cloud Functions (TypeScript + tsup)
- **데이터베이스**: IndexedDB (클라이언트)
- **배포**: Firebase Hosting + Functions
- **스케줄링**: Firebase Functions v2 onSchedule

## 📦 설치 및 실행

<br>
### 사전 요구사항
- Node.js 20+
- pnpm 9+
- Firebase CLI

## 라이센스
### 설치
```bash
# 의존성 설치
pnpm install

<a href="https://www.linkedin.com/in/chucoding/" target="_blank">Prod By. 외계공룡</a><br/>
Copyright &copy; today-i-learned-alarm<br/>All Rights Reserved.</p>
# 개발 서버 실행
pnpm dev

# 빌드
pnpm build

# 배포
pnpm deploy
```

### 환경 변수 설정
`app/env.example`을 참고하여 `.env` 파일을 생성하세요:

```bash
cp app/env.example app/.env
```

## 🔧 개발

### 웹 앱 개발
```bash
cd app
pnpm dev
```

### Functions 개발
```bash
cd functions
pnpm serve # 에뮬레이터 실행
```

## 📱 PWA 기능

- 오프라인 지원
- 웹 푸시 알림
- 설치 가능한 앱
- 백그라운드 동기화

## 🔔 알림 기능

- 매일 오전 8시(KST) 자동 알림
- Firebase Cloud Messaging 사용
- 토픽 기반 브로드캐스트

## 🚀 배포

### Firebase 설정
```bash
firebase login
firebase init hosting functions
```

### CI/CD
GitHub Actions를 통한 자동 배포:
- `main` 브랜치 푸시 시 자동 배포
- Firebase Hosting + Functions 동시 배포

## 📝 라이선스

MIT License
16 changes: 16 additions & 0 deletions app/env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Firebase 설정
VITE_API_KEY=your_firebase_api_key_here
VITE_AUTH_DOMAIN=your_project.firebaseapp.com
VITE_PROJECT_ID=your_project_id
VITE_STORAGE_BUCKET=your_project.appspot.com
VITE_MESSAGING_SENDER_ID=your_sender_id
VITE_APP_ID=your_app_id
VITE_MEASUREMENT_ID=your_measurement_id
VITE_VAPID_KEY=your_vapid_key_here

# 사용자 설정
VITE_USER_ID=your_user_id_here
VITE_SCHEDULE_CODE=your_schedule_code_here

# Naver Cloud Platform
VITE_NCLOUD_HYPERCLOVAX_URL=your_ncloud_hyperclovax_url_here
13 changes: 13 additions & 0 deletions app/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Today I Learned Alarm</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
31 changes: 31 additions & 0 deletions app/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "app",
"private": true,
"version": "2.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build"
},
"dependencies": {
"firebase": "^10.4.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-indexed-db-hook": "^1.0.14",
"react-markdown": "^9.0.0",
"react-slick": "^0.30.2",
"react-syntax-highlighter": "^15.5.0",
"remark-gfm": "^4.0.0",
"slick-carousel": "^1.8.1"
},
"devDependencies": {
"@types/react": "^18.2.0",
"@types/react-dom": "^18.2.0",
"@types/react-slick": "^0.23.13",
"@types/react-syntax-highlighter": "^15.5.11",
"@vitejs/plugin-react": "^4.0.0",
"typescript": "^5.0.0",
"vite": "^5.0.0",
"vite-plugin-pwa": "^0.17.0"
}
}
Binary file added app/public/favicon.ico
Binary file not shown.
30 changes: 30 additions & 0 deletions app/public/firebase-messaging-sw.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Firebase Messaging Service Worker
importScripts('https://www.gstatic.com/firebasejs/10.4.0/firebase-app-compat.js');
importScripts('https://www.gstatic.com/firebasejs/10.4.0/firebase-messaging-compat.js');

// Firebase 설정
const firebaseConfig = {
apiKey: "your_api_key",
authDomain: "your_project.firebaseapp.com",
projectId: "your_project_id",
storageBucket: "your_project.appspot.com",
messagingSenderId: "your_sender_id",
appId: "your_app_id"
};

firebase.initializeApp(firebaseConfig);

const messaging = firebase.messaging();

// 백그라운드 메시지 처리
messaging.onBackgroundMessage((payload) => {
console.log('Received background message ', payload);

const notificationTitle = payload.notification.title;
const notificationOptions = {
body: payload.notification.body,
icon: '/logo192.png'
};

self.registration.showNotification(notificationTitle, notificationOptions);
});
File renamed without changes.
File renamed without changes
27 changes: 9 additions & 18 deletions src/App.js → app/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import React, { useEffect, useState } from 'react';

import { initDB, useIndexedDB } from "react-indexed-db-hook";

import { DBConfig } from './DBConfig';
//import setupAlarm from './setupAlarm';
import { chatCompletions } from './api/ncloud-api';
import { getCurrentDate } from './modules/utils';
import FlashCardViewer from './pages/FlashCardViewer';
Expand All @@ -12,34 +10,27 @@ import { getGithubData } from './services/github-service';
initDB(DBConfig);
const dates = [1, 7, 30]; // days ago list

const App = () => {

const App: React.FC = () => {
const { add, getByID } = useIndexedDB("data");
const [loading, setLoading] = useState(true);
/*
const isInStandaloneMode = () => window.matchMedia('(display-mode: standalone)').matches;
if (isInStandaloneMode()) {
setupAlarm();
}
*/
const [loading, setLoading] = useState<boolean>(true);

useEffect(()=>{
useEffect(() => {
(async () => {
const todayData = await getByID(getCurrentDate());
if (todayData) {
setLoading(false);
return;
}

let list = [];
let list: Array<{question: string, answer: string}> = [];
for (const ago of dates) {
try {
const githubData = await getGithubData(ago);
if (githubData) {
const result = await chatCompletions(githubData);
const questions = JSON.parse(result.body.result.message.content);
for (let ncloudData of questions) {
list.push({question:ncloudData, answer:githubData});
list.push({question: ncloudData, answer: githubData});
}
}
} catch (error) {
Expand All @@ -48,18 +39,18 @@ const App = () => {
}

if (list.length > 0) {
add({date:getCurrentDate(), data:list });
add({date: getCurrentDate(), data: list });
}
setLoading(false);
})();
}, []);
}, [add, getByID]);

if(loading) return null;
return (
<main>
<FlashCardViewer />
</main>
);
}
};

export default App;
export default App;
2 changes: 1 addition & 1 deletion src/DBConfig.js → app/src/DBConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ export const DBConfig = {
]
}
]
};
};
Loading