Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings
Open more actions menu

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
6 Commits
 
 
 
 
 
 
 
 

Repository files navigation

GAS-FireBase-FullStack-Chat


プロジェクト概要

このプロジェクトは、Google Apps Script(GAS)をフロントエンド・バックエンド・フレームワークに、Firebase Realtime Database を使ってチャットメッセージを保存・同期し、Cloudinary を使って画像のアップロード・表示を行う、フルスタックのチャットアプリ


使用したもの

技術 用途
Google App Script フロントエンド・バックエンド・フレームワーク
Firebase Realtime Database メッセージの保存・リアルタイム同期
Firebase Authentication Googleログインによるユーザー認証
Cloudinary 画像のアップロード・ホスティング
HTML/CSS(Style属性として使用) UI構築とスタイリング
JavaScript クライアント側の動的処理

ファイルの内容

GAS-FireBase-FullStack-Chat/
├── Code.gs
└── index.html

🔹 Code.gs

  • GASのメインスクリプト。
  • doGet() によるWebアプリのエントリーポイント。
  • Firebase Realtime Databaseへの読み書き処理。
  • Cloudinary APIを呼び出して画像URLを取得・保存。

🔹 index.html

  • チャット画面のUI。
  • メッセージ表示、画像表示、入力フォーム、送信ボタンなどを含む。
  • JavaScriptでFirebaseとCloudinaryを管理。

Realtime Databaseの活用

  • メッセージは push() メソッドで Realtime Database に保存。
  • on("child_added") イベントで新しいメッセージをリアルタイムに取得。
  • データは JSON 形式で保存され、すべてのクライアントに即時反映。

以下はセキュリティーノート

{
 "rules": {
   ".read": "auth != null",
   ".write": "auth != null",

   "users": {
     "$uid": {
       ".read": "auth !== null && auth.uid === $uid",
       ".write": "auth !== null && auth.uid === $uid",

       "username": { ".validate": "newData.isString() && newData.val().length > 0" },
       "username_lower": { ".validate": "newData.isString() && newData.val().length > 0" },
       "color": { ".validate": "newData.isString()" },
       "bio": { ".validate": "newData.isString()" },
       "email": { ".validate": "newData.isString() && newData.val().length > 0" },
       "photoURL": { ".validate": "newData.isString() && newData.val().contains('https://res.cloudinary.com')" },

       "location": { ".validate": "newData.isString()" },
       "phone": { ".validate": "newData.isString()" },
       "website": { ".validate": "newData.isString()" },
       "profileBgImage": { ".validate": "newData.isString() && newData.val().contains('https://res.cloudinary.com')" },

       "blockedUsers": {
         ".read": "auth.uid === $uid",
         ".write": "auth.uid === $uid",
         "$blockedUid": { ".validate": "newData.isBoolean()" }
       }
     },
     ".read": "auth != null"
   },

   "rooms": {
     "$roomId": {
       ".read": "data.child('members/' + auth.uid).val() === true && (data.child('type').val() === 'group' || (data.child('type').val() === 'dm' && !root.child('users/' + auth.uid + '/blockedUsers').hasChild($roomId.replace('dm_','').replace(auth.uid,'').replace('_','')) && !root.child('users/' + $roomId.replace('dm_','').replace(auth.uid,'').replace('_','') + '/blockedUsers').hasChild(auth.uid)))",
       ".write": "auth != null && (data.child('members/' + auth.uid).val() === true || (!data.exists() && newData.child('members/' + auth.uid).val() === true))",

       "members": {
         ".read": "data.parent().child('members/' + auth.uid).val() === true",
         ".write": "data.parent().child('members/' + auth.uid).val() === true",
         "$memberId": { ".validate": "newData.isBoolean()" }
       },

       "messages": {
         ".read": "data.parent().child('members/' + auth.uid).val() === true",

         "$messageId": {
           ".write": "data.parent().parent().child('members/' + auth.uid).val() === true && (data.parent().parent().child('type').val() === 'group' || (data.parent().parent().child('type').val() === 'dm' && !root.child('users/' + auth.uid + '/blockedUsers').hasChild($roomId.replace('dm_','').replace(auth.uid,'').replace('_','')) && !root.child('users/' + $roomId.replace('dm_','').replace(auth.uid,'').replace('_','') + '/blockedUsers').hasChild(auth.uid)))",
           ".validate": "newData.hasChildren(['username','uid','timestamp','type']) && newData.child('uid').val() === auth.uid",

           "username": { ".validate": "newData.isString()" },
           "uid": { ".validate": "newData.isString() && newData.val() === auth.uid" },
           "timestamp": { ".validate": "newData.isNumber()" },
           "message": { ".validate": "newData.isString()" },
           "type": { ".validate": "newData.isString() && (newData.val()==='text' || newData.val()==='image' || newData.val()==='video')" },
           "fileUrl": { ".validate": "(newData.parent().child('type').val()==='image' || newData.parent().child('type').val()==='video') && newData.isString() && newData.val().contains('https://res.cloudinary.com')" }
         }
       },

       "lastRead": {
         ".read": "data.parent().child('members/' + auth.uid).val() === true",
         ".write": "data.parent().child('members/' + auth.uid).val() === true",
         "$uid": { ".validate": "$uid === auth.uid" }
       },

       "deletionRequest": {
         ".read": "data.parent().child('members/' + auth.uid).val() === true",
         ".write": "data.parent().child('members/' + auth.uid).val() === true",
         ".validate": "newData.hasChildren(['requestedBy','requestedAt','votes'])",

         "votes": {
           "$voterUid": { ".validate": "newData.isBoolean() && $voterUid === auth.uid" }
         }
       },

       "callOffers": {
         "$uid": {
           ".read": "data.parent().parent().child('members').hasChild(auth.uid)",
           ".write": "data.parent().parent().child('members').hasChild(auth.uid)",
           ".validate": "newData.hasChildren(['type','sdp']) && newData.child('type').val()==='offer'"
         }
       },

       "callAnswers": {
         "$uid": {
           ".read": "data.parent().parent().child('members').hasChild(auth.uid)",
           ".write": "data.parent().parent().child('members').hasChild(auth.uid)",
           ".validate": "newData.hasChildren(['type','sdp']) && newData.child('type').val()==='answer'"
         }
       },

       "iceCandidates": {
         "$uid": {
           ".read": "data.parent().parent().child('members').hasChild(auth.uid)",
           ".write": "data.parent().parent().child('members').hasChild(auth.uid)"
         }
       },

       "type": { ".validate": "newData.isString() && (newData.val()==='dm' || newData.val()==='group')" },
       "name": { ".validate": "newData.isString()" },
       "createdAt": { ".validate": "newData.isNumber()" },
       "createdBy": { ".validate": "newData.isString()" },
       "iconType": { ".validate": "newData.isString() && (newData.val()==='initial' || newData.val()==='emoji')" },
       "iconCode": { ".validate": "newData.isString()" },
       "iconColor": { ".validate": "newData.isString()" }
     }
   }
 }
}

Cloudinaryによる画像管理

  • ユーザーが画像を選択すると、Cloudinary API経由でアップロード。
  • Cloudinaryから返される画像URLを Realtime Database に保存。
  • チャット画面では画像URLを使って <img> タグで表示。

ライセンス

このプロジェクトは、クレジット表記を必須とする MIT License (Modified) の下で公開されています。 本ソフトウェアを使用または改変して利用する場合は、以下のクレジット表記を含めてください:

"This project includes software originally created by GitHub user: TatsuyaM2667."

License

MIT License (Attribution Required — © 2025 TatsuyaM2667)

👨‍💻 作者

About

このプロジェクトは、Google Apps Script(GAS)をフロントエンド・バックエンド・フレームワークに、Firebase Realtime Database を使ってチャットメッセージを保存・同期し、Cloudinary を使って画像のアップロード・表示を行う、フルスタックのチャットアプリ。

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Used by

Contributors

Languages

Morty Proxy This is a proxified and sanitized view of the page, visit original site.