Closed3

出席アプリを作ってデータをGoogle Spreadsheetsに保存する

grafumografumo

お断り

js 初心者なので,何やってるのかあまり理解できていないです.ご容赦ください.

どんなアプリ?

出席アプリを作って,名前とタイムスタンプをGoogle Spreadsheetに保存する方法をChatGPT+αで作成したので,記録としてここに残しておく.
名前は自由入力ではなく,選択式.表記ゆれを防ぐため.

ファイル構造

index.html
index.js
package.json
credentials.json 

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>QR Code Scanner</title>
    <script src="https://unpkg.com/html5-qrcode" type="text/javascript"></script>
</head>
<body>
    <div id="reader" style="width: 300px;"></div>
    <select id="nameSelect">
        <option value="John">John</option>
        <option value="Jane">Jane</option>
        <option value="Doe">Doe</option>
    </select>
    <button onclick="submitData()">Submit</button>

    <script>
        function onScanSuccess(decodedText) {
            console.log(`Scanned result: ${decodedText}`);
            // Optional: You can use decodedText for additional logic
        }

        function onScanFailure(error) {
            console.warn(`QR scan failed: ${error}`);
        }

        const html5QrcodeScanner = new Html5QrcodeScanner(
            "reader", { fps: 10, qrbox: 250 });
        html5QrcodeScanner.render(onScanSuccess, onScanFailure);

        async function submitData() {
            const name = document.getElementById("nameSelect").value;
            const timestamp = new Date().toISOString();
            const response = await fetch('/submit', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ name, timestamp })
            });
            const result = await response.json();
            alert(result.message);
        }
    </script>
</body>
</html>

index.js

const express = require('express');
const { google } = require('googleapis');
const bodyParser = require('body-parser');
const app = express();

const path = require('path');

// Serve the index.html file on the root path
app.get('/', (req, res) => {
    res.sendFile(path.join(__dirname, 'index.html'));
});

app.use(bodyParser.json());

require('dotenv').config();
const credentials = require(process.env.GOOGLE_CREDENTIALS);

// const credentials = require('./credentials.json'); // Service account credentials
const spreadsheetId = 'sheet-id'; // Replace with your spreadsheet ID

async function appendToSheet(data) {
    const auth = new google.auth.GoogleAuth({
        credentials,
        scopes: ['https://www.googleapis.com/auth/spreadsheets'],
    });
    const sheets = google.sheets({ version: 'v4', auth });
    const response = await sheets.spreadsheets.values.append({
        spreadsheetId,
        range: 'Sheet1',
        valueInputOption: 'RAW',
        resource: {
            values: [data],
        },
    });
    return response;
}

app.post('/submit', async (req, res) => {
    const { name, timestamp } = req.body;
    if (!name || !timestamp) {
        return res.status(400).json({ message: 'Name and timestamp are required' });
    }
    try {
        await appendToSheet([timestamp, name]);
        res.json({ message: 'Data saved successfully!' });
    } catch (error) {
        res.status(500).json({ message: 'Error saving data', error: error.message });
    }
});

app.listen(3000, () => {
    console.log('Server running on http://localhost:3000');
});

package.json

{
    "name": "qr-code-scanner-service",
    "version": "1.0.0",
    "description": "A simple QR code scanning service that saves data to Google Sheets",
    "main": "index.js",
    "scripts": {
        "start": "node index.js",
        "dev": "nodemon index.js"
    },
    "dependencies": {
        "body-parser": "^1.20.1",
        "dotenv": "^16.4.7",
        "express": "^4.18.2",
        "googleapis": "^105.0.0"
    },
    "devDependencies": {
        "nodemon": "^3.1.9"
    },
    "keywords": [
        "qr-code",
        "google-sheets",
        "express",
        "nodejs"
    ],
    "author": "Your Name",
    "license": "MIT"
}
grafumografumo

Google Sheets API

  1. Google Could Platform (旧名: Google Cloud Console)へアクセス.
  2. 新しいプロジェクトを作成.
  3. Google Cloud > APIs & Services > Enable APIs and Services > Sheetと検索 > Google Sheets APIをEnable.
  4. Google Cloud > IAM & Admin > Service Accounts > + create service account > service account name を適当な名前にすると,service account ID を勝手に埋めてくれる > done
  5. service account がGoogle Spreadsheetを編集できるようにする: IAM & Admin > IAM > grant access > New principalsにservice accountのメアドを指定.Assign roles でEditor入力. > save
  6. credentialを取得: Google Cloud > IAM & Admin > Service Accounts > 使用するservice accountをクリック > keys > add key > create new key > json > create
  7. 保存したcredential (XXX.json) をcredentials.jsonへと名前変更し,プロジェクトのディレクトリへ移す.

※principalが全然わからなかったのですが,ざっというとアカウントのことでした.APIを使うときにservice accountを使うので,それのことです.ちなみに,個人のアカウントでプロジェクトを作成すると,自分のアカウントがownerロールをもったprincipalです.参照:https://cloud.google.com/iam/docs/overview

Google Spreadsheetsにて

  1. 出力したいSpreadsheetsにアクセス
  2. Spreadsheetsのid を取る:URLのhttps://docs.google.com/spreadsheets/d/{ここがID}/edit?gid=0#gid=0XXXXXXXX
  3. 共有 > service accountをeditor権限で追加

localでdeploy (win/powershell)

※nodeは入れておく

npm install
npm install dotnev
$env:GOOGLE_CREDENTIALS = "C:\path\to\credentials.json"
node index.js

※環境変数の確認: $env:GOOGLE_CREDENTIALS

grafumografumo

結果

localhost:3000にアクセスすると,名前とタイムスタンプをGoogle Spreadsheets に保存できた.

QRコードを読み込むと,出席を報告できる画面に遷移するというUIを想定してChatGPTにプロンプトしたので,QRコードを読み込めるけど何もしない謎機能が入ってしまった.

可能性1

QRリーダーの使い道として,毎回キーを作成して,それをQRコードとして表示して,読み込む,その情報もGoogleSpreadsheetsへ保存し,キーが正解キーと違う場合は欠席扱いにする.e.g)毎ゼミ,キーを作成: zemi-12r4t6z!.これをQRコードにして,毎回ゼミ前に画面に表示.各位はそれをスキャンして出席報告
問題

  1. 毎回違うキーを作成しないといけない.予測できそうなキーだと不正される e.g.) 'zemi-1215' など日付と簡単な規則
  2. 毎回検証しないといけない:これはそんなに大きな負担じゃないかも.簡単なスクリプトでできそう
  3. 出席率の共有がしづらいかも:各々がどれだけ出席したのかの可視化に別の機構が必要になる

可能性2

QRコードをスキャンして,特定のキーであれば名前選択&送信ができるようになる.
-> 可能性1課題2は解決できる.

問題

  1. 現状の構造だと,htmlから直接backendに情報を送信しているので,そこに検証を挟む必要がある.
  2. 可能性1にも共通する問題だが,この出席確認アプリにアクセスするためにQRコードを使用すると思うので,キーをこのアプリのURLにするのが現実的,だとすると,キーが固定になっちゃう?
  • ->URLのパラメータにキーをいれて,キーをURLから抽出するのはどうか?(可能性1 課題1)
このスクラップは2025/01/06にクローズされました