みんラジ

https://github.com/orgs/progate-hackathon-usagi/repositories

TypeScript

Figma

PostgreSQL

Flutter

Dart

みんなでリアルタイムにラジオ体操するアプリです!

yotu

urassh

藤堂光

エリンギしめじ

推しアイデア

みんなで同時にラジオ体操できる!

作った背景

「夏休みに薄っぺらいラジオ体操カードを首にかけて蒸し暑い公園で朝一番から眠気MAXでやったラジオ体操」を思い出したかったので

推し技術

リアルタイム処理やバックエンドの全てなど、たくさん supabase の機能を使っているところ!

プロジェクト詳細

なにするアプリ

ラジオ体操ができます!!!

機能

みんなでラジオ体操

みんなで同時にラジオ体操ができます! 他の参加者のアイコンが見えて楽しいです。

スタンプカード

ラジオ体操した日にはスタンプが押されます! 現在の継続日数や累計日数も確認することができます。

技術

Supabaseテーマのハッカソンなので、 Supabase がサポートする以下5つの機能全て使いました!✨️ image image

フロントエンド(flutter)

Supabase Realtime

みんなでラジオ体操を行う機能で使用しています。 ルーム参加者を固有のチャンネルへ登録し、各ユーザーの状態(途中抜け・切断してないか)を Presence で監視しています。 Socket.io に近い触り心地でかなり楽でした。

状態管理(Riverpod)

リアルタイムな機能を複数ページにわたって展開する都合上、同じルームの参加者などの状態は分離して保持しておく必要がありました。 今回は Riverpod を使用し、状態保持を Provider に一任することで解決しました。

Supabase Storage

ユーザのプロフィール画像を保存 / 取得するのに使いました。 保存時は File をアップロード、表示は URL を用いています。 ファイル名から public URL を取得できる関数が用意されていていいかんじでした。

Supabase Auth

supabase Auth を介して google アカウントのサインインを実装しました。 モバイル OS ごとに個別対応が必要だと踏んでいましたが、意外と軽微に抑えられていてよかったです。

バックエンド(TypeScript)

バックエンドはすべてSupabase Edge Functions, Supabase Databaseを使用しています✨️

DB設計

ER図: https://github.com/progate-hackathon-usagi/progate-usagi/blob/main/er.md

プロフィール情報のprofileテーブルと、ラジオ体操記録を保存するexercise_logテーブルがあります。 また継続・累計日数集計用にv_user_profile_with_exercise_daysビュー(DDL)を用意しました。

REST API

機能に合わせて必要なCRUDを実装しています。

API設計・実装において以下の点を考慮しました。

  • 不要なHTTPメソッドを許可しない
  • CRUDはHTTPメソッドで表現しリソース指向のURLにする
  • リクエスト状況がわかるよう随所にconsole.log()を差し込む
  • create処理成功のステータスコードは201 Createdを使用する
  • create/update処理はレスポンスボディに作成/更新されたリソースを返す
  • レスポンスボディはuser_profile: {...}のように名前をつけて返す
  • create処理のリクエストボディは、リクエスト時点では未作成のリソースなので、ネストせず直接JSON{...}を受け取る

継続・累計日数の集計関数

GitHub: https://github.com/progate-hackathon-usagi/edge-functions/blob/main/supabase/functions/profile/index.ts#L48-L138

日付単位で重複削除されたv_user_profile_with_exercise_daysビューを用いて集計を行っています。

累計日数は、v_user_profile_with_exercise_daysビューの行数を数えています。 select count(*)でもよかったのですが、継続日数取得がSQLでの実装が難しくコードでカバーすることを決めたため、集計操作がシンプルになるよう同じくコード上で集計を行っています。

継続日数は、v_user_profile_with_exercise_daysビューから取得したデータに対し、「昨日または今日ラジオ体操をしている」ユーザの「昨日または今日から遡って連続してラジオ体操した日付の行数」を数えています。 継続日数取得は1行ずつ操作するためO(N)の計算量が必要ですが、100年続けてもせいぜい36500行なので実用に耐えうると判断しました。

普段はdayjsに甘えるのですが、今回は組み込み型のDateを使用し、日付の比較処理等を書きました。日付の「一致」「一日前」を判定するだけで苦労したり、getMonth0-11を返したりなど、非常に不便でした。

yotu

@yotu