PublicPersonal Project

Wakawaka

Wakawaka 是一款將寫程式時間轉換為經驗值的 Programmer 專屬放置型遊戲,滿足累積數據的快樂。

線上網址

技術棧

  • Frontend:React、TypeScript
  • CSS Framework:Tailwind CSS、DaisyUI
  • Authentication:Google OAuth、WakaTime OAuth
  • Database:Google Sheets
  • Backend:Express.js、Node.js、TypeScript
  • Background Jobs:node-cron
  • Containerization:Docker、Docker Compose
  • Project Structure:Monorepo(Frontend、Backend、Docker Compose)
  • Deployment Platform:DigitalOcean
  • Web Server / Reverse Proxy:Nginx
  • DNS / SSL / Proxy:Cloudflare
  • Analytics:Google Analytics 4(GA4)

系統流程

  1. 使用者透過 Google OAuth 登入。
  2. 後端建立或查詢 Google Sheets 中的使用者資料。
  3. 使用者授權並綁定 WakaTime 帳號。
  4. 後端保存 WakaTime 的 access token 與 refresh token。
  5. 每日由 cron job 定時呼叫 WakaTime API。
  6. 將 coding time 與 language statistics 轉換為角色經驗值及程式語言技能等級。
  7. 前端取得角色、敵人、公告與伺服器資料,並渲染遊戲畫面。

實作細節

使用 WakaTime 取得程式活動數據

專案以 WakaTime 作為數據來源,取得玩家每天寫程式的時間,以及各程式語言的使用紀錄。

更多介紹可參考:WakaTime 介紹

後端透過 cron job,在每天的固定時間呼叫 WakaTime API,取得前一天的程式活動資料,再將結果轉換為遊戲中的角色經驗值與語言技能經驗值。

為避免相同日期的資料被重複寫入,角色資料中也會記錄最後一次更新經驗值的日期,在執行每日任務前先確認當日資料是否已經處理。

使用 Google Sheets 儲存資料

選擇 Google Sheets 作為資料儲存方案,除了能縮短初期開發時間,也因為我過去沒有使用過 Google Sheets API,想藉此嘗試不同的資料管理方式。

使用 Google Sheets 的優點是可以直接查看與手動修改資料,即使不在開發環境中,也能快速確認玩家、角色與更新紀錄。

不過,相較於一般資料庫,Google Sheets 在資料結構、權限控管、查詢效率與 API rate limit 等方面都有較多限制,需要由應用程式額外處理。

隨著玩家數量增加,專案也曾遇到 429 Too Many Requests,因此後續調整了資料讀寫方式。

Monorepo 專案架構

前端與後端放在同一個 repository 中管理,並透過 Docker Compose 統一建立及啟動服務。

由於目前前後端會部署在同一台主機上,且後端只需要運行一個 instance,因此沒有將兩者拆分成各自獨立的部署流程。

這樣的架構可以集中管理前端、後端與部署設定,也能降低個人專案的維運複雜度。

Google OAuth 登入

網站使用 Google OAuth 作為主要的登入與註冊方式,使用者不需要另外建立帳號及密碼。

在設定 Google OAuth consent screen 與正式發布應用程式時,需要準備對外公開的網站資訊,包括:

  1. 官方網站首頁
  2. 隱私權政策
  3. 服務條款

OAuth 成功後,後端會根據 Google 回傳的使用者資訊,建立或查詢對應的 Wakawaka 使用者資料。

WakaTime OAuth 授權

Google OAuth 負責 Wakawaka 帳號的登入,而 WakaTime OAuth 則用於取得使用者授權,讓後端能讀取其程式活動資料。

完成授權後,後端會保存 WakaTime 提供的 access token、refresh token 與到期資訊。每日更新資料時,系統會使用 access token 呼叫 WakaTime API;若 token 已失效,則透過 refresh token 重新取得有效的 access token。

將登入與程式數據授權拆成兩個流程,可以讓使用者先建立角色,再決定是否綁定 WakaTime。

JWT 登入狀態

Google OAuth 成功後,後端會簽發 JWT。Payload 僅保存辨識登入狀態所需的基本資料,並將 token 儲存在瀏覽器的 httpOnly cookie 中。

前端無法透過 JavaScript 直接讀取 cookie,而是在發送 API request 時設定:

credentials: 'include';

瀏覽器會自動在請求中帶上 cookie。後端收到請求後,使用 JWT_SECRET 驗證 JWT,再根據其中的 user_id 至 Google Sheets 查詢實際的使用者與角色資料。

相較於將 token 儲存在 localStoragehttpOnly cookie 可以降低 token 被前端 JavaScript 或 XSS 攻擊直接讀取的風險。

此外,cookie 也搭配 SameSiteSecure 設定,降低跨站請求與非 HTTPS 傳輸帶來的風險。

429 Too Many Requests

專案最初遇到 429 Too Many Requests 時,註冊玩家僅有 40 多位,比預期更早碰到 Google Sheets API 的請求限制。

問題主要來自每日任務在短時間內對每位玩家進行多次讀取與寫入。當玩家數量增加後,累積的 API request 很容易超過限制。

後續主要進行了兩項調整:

  1. 將逐筆讀寫改為批次取得與批次更新。
  2. 在不同批次之間加入適當的延遲,避免短時間內集中送出過多請求。

調整後,每日更新任務已能順利處理 200 多位玩家的資料。

專案成果

  • 將 WakaTime 的真實 coding 數據轉換為遊戲中的角色成長數據。
  • 使用 Google Sheets 作為輕量資料庫,縮短初期開發時間並方便手動維護。
  • 以 Monorepo 管理前端、後端與部署設定,並透過 Docker Compose 統一部署。
  • 整合 Google OAuth 與 WakaTime OAuth,完成登入、帳號建立與程式數據授權流程。
  • 使用 JWT 與 httpOnly cookie 維持登入狀態。
  • 透過 cron job 自動取得並更新每日玩家數據。
  • 將 Google Sheets API 操作改為批次處理,解決玩家成長後遇到的 API rate limit 問題。
  • 截至 2026 年 7 月,共有 208 位玩家建立角色。