# Google Cloud Setup — Automate Workshop Reflection Forms

**Purpose**: Create all MHP DE Workshop reflection Google Forms from
[google-forms-manifest.yaml](google-forms-manifest.yaml) using the official
[Google Forms API](https://developers.google.com/workspace/forms/api/reference/rest).

**Related**:

- [google-forms-reflection-design.md](google-forms-reflection-design.md) — question design
- [google-forms-reflection-design.html](google-forms-reflection-design.html) — design (HTML)
- Script: [create-forms.mjs](create-forms.mjs)

---

## What you get after setup

| Output | Location |
|--------|----------|
| 10 Google Forms (Mod 0–7 + optional 8–9) | Your Google Drive |
| Form URLs (edit + responder) | `workshop-2026-v2/trainer/google-forms/forms-output.json` |
| OAuth token (reused on later runs) | `workshop-2026-v2/trainer/google-forms/token.json` *(gitignored)* |

You still **link each form to Google Sheets manually** (one click per form in the Form UI) unless you add a follow-up Drive API script.

---

## Prerequisites

| Requirement | Notes |
|-------------|-------|
| Google account | Gmail or Google Workspace |
| Node.js **18+** | `node --version` |
| npm | Bundled with Node |
| Browser | For one-time OAuth consent |
| ~20 minutes | First-time GCP + OAuth setup |

> **Cost**: Creating a GCP project, enabling the Forms API, and running this script for a one-time workshop rollout is **free** under [standard Forms API quotas](https://developers.google.com/workspace/forms/api/limits). You do **not** need a billing account for typical use. Google may require billing only if you exceed elevated quotas later in 2026.

---

## Step 1 — Create a Google Cloud project

1. Open [Google Cloud Console](https://console.cloud.google.com/).
2. Click the project picker → **New Project**.
3. Name: `mhp-de-workshop-forms` (any name is fine).
4. Click **Create** and select the new project.

No billing account is required for this workflow.

---

## Step 2 — Enable APIs (Forms + Drive)

Enable **both** in the **same** GCP project as your OAuth client (`credentials.json`).

### Google Forms API (required)

1. **APIs & Services** → **Library**.
2. Search **Google Forms API** → **Enable**.

### Google Drive API (required for `--replace` delete + banner)

1. **APIs & Services** → **Library**.
2. Search **Google Drive API** → **Enable**.

Direct link (replace project ID if needed):  
https://console.cloud.google.com/apis/library/drive.googleapis.com

Or use the link from the error message, e.g.  
https://console.developers.google.com/apis/api/drive.googleapis.com/overview?project=YOUR_PROJECT_ID

After enabling, wait **2–5 minutes**, then re-run the script. **No new `credentials.json`** needed.

---

## Step 3 — Configure the OAuth consent screen

1. Go to **APIs & Services** → **OAuth consent screen**.
2. User type:
   - **Internal** — if you use Google Workspace and only colleagues will run the script.
   - **External** — if you use a personal Gmail account (add yourself as a test user).
3. Fill required fields:
   - App name: `MHP DE Workshop Forms`
   - User support email: your email
   - Developer contact: your email
4. **Scopes** → **Add or remove scopes** → add:
   - `https://www.googleapis.com/auth/forms.body`
   - `https://www.googleapis.com/auth/drive` *(banner upload + `--replace` delete; `drive.file` alone often returns Insufficient Permission)*
5. **Test users** (External only) — **required for personal Gmail**:
   - Click **+ ADD USERS**
   - Add **every** Google account that will run `create-forms.mjs` or open the OAuth link
   - Example: `juewei.jin@gmail.com` must be listed if you sign in with that address
   - Click **Save**
6. On the consent screen summary, confirm **Publishing status** shows **Testing** (that is OK).
7. Save through the summary screen.

> **Warning:** While the app is in "Testing", refresh tokens expire after 7 days unless the app is published or the user type is Internal. For a one-day workshop setup, run the script once before class; re-auth if needed.

> **Do not** click **Publish app** unless you are ready for Google’s full app verification (weeks; not needed for this workshop script).

---

## Step 4 — Create OAuth client credentials

### Where to find it in Google Cloud Console (2025+ UI)

Google moved OAuth settings. Use **one** of these paths:

| Path | Steps |
|------|--------|
| **A — Direct link** | Open [Google Auth Platform → Clients](https://console.cloud.google.com/auth/clients) (pick your project at the top if prompted) |
| **B — Left menu** | ☰ Menu → **Google Auth platform** → **Clients** |
| **C — Legacy menu** | ☰ Menu → **APIs & Services** → **Credentials** → under **OAuth 2.0 Client IDs**, click your client name |

If you do not see **Google Auth platform**, use path **C** or the direct link in **A**.

### Create the client

1. On the **Clients** page, click **+ Create client** (or **Create Credentials** → **OAuth client ID** on the legacy Credentials page).
2. Application type: **Desktop app** (not “Web application”).
3. Name: `MHP Forms CLI`.
4. Click **Create** → **Download JSON** (download immediately—secrets may not be shown again).

### Redirect URIs — Desktop vs Web (read this)

| Client type | “Authorized redirect URIs” in console? | What you do |
|-------------|----------------------------------------|-------------|
| **Desktop app** | **Usually not shown** — Google’s docs say the console does not require redirect URIs for desktop apps | **Nothing to add.** Loopback (`http://127.0.0.1:PORT`) is allowed automatically. Our script listens on port `53187`. |
| **Web application** | **Yes** — you will see **Authorized redirect URIs** | Add `http://127.0.0.1:53187/oauth2callback` **or** recreate the client as **Desktop app** (recommended). |

If you cannot find **Authorized redirect URIs**, your client is probably **Desktop**—that is **normal**. Skip redirect URI setup and go to Step 6.

If the client type column says **Web application**, either add the redirect URI on that client’s edit page **or** create a new **Desktop app** client and download a new `credentials.json`.

5. Save the JSON file as:

```
workshop-2026-v2/trainer/google-forms/credentials.json
```

> **Warning:** Never commit `credentials.json` or `token.json` to git. They are listed in `.gitignore`.

---

## Step 5 — Install script dependencies

From the repository root:

```powershell
cd <repo-root>/workshop-2026-v2/trainer/google-forms
npm install
```

This installs `googleapis` and `js-yaml`.

---

## Step 6 — First run (OAuth + create forms)

```powershell
node create-forms.mjs
```

**First run only:**

1. The script prints a URL and tries to open your browser (keep the terminal running).
2. Sign in with the **same Gmail address** you added under **Test users** (e.g. `juewei.jin@gmail.com`).
3. Click **Allow** (scope: edit Forms). If you see **Access blocked** / **Error 403: access_denied**, see [Fix: Access blocked (403)](#fix-access-blocked-403-access_denied) below.
4. After **Allow**, Google redirects to `127.0.0.1:53187` — you should see **Authorization successful** in the browser (not *connection refused*).
5. Return to the terminal; the script saves `token.json` and creates all forms.

If the browser shows **This site can’t be reached / localhost refused to connect**, see [Fix: localhost refused to connect](#fix-localhost-refused-to-connect) below.

**Later runs** reuse `token.json` — no browser step unless the token expired.

### Dry run (no API calls)

```powershell
node create-forms.mjs --dry-run
```

Prints how many questions would be created per form.

### Single module only

```powershell
node create-forms.mjs --module 4
node create-forms.mjs --module final
```

### Replace previous forms (recommended after manifest changes)

```powershell
node create-forms.mjs --replace
```

Deletes every form listed in `forms-output.json`, then recreates from the manifest (quiz keys, banner, Drive folder).

### Move existing forms into one Drive folder

```powershell
node create-forms.mjs --organize-folder
```

Uses `drive_folder` from [google-forms-manifest.yaml](google-forms-manifest.yaml). Does not create new forms.

### Regenerate trainer answer key (Markdown)

```powershell
node sync-grading-artifacts.mjs
```

Reads [google-forms-grading.yaml](google-forms-grading.yaml) → updates [google-forms-reflection-answer-key.md](google-forms-reflection-answer-key.md).

---

## Step 7 — Review output

Open `forms-output.json` (includes `driveFolder.folderUrl` — all forms are moved into that Drive folder):

```json
{
  "generatedAt": "2026-05-31T12:00:00.000Z",
  "forms": [
    {
      "module": 0,
      "title": "MHP DE Workshop — Mod 0 Reflection",
      "formId": "1FAIpQLS...",
      "editUrl": "https://docs.google.com/forms/d/1FAIpQLS.../edit",
      "responderUrl": "https://docs.google.com/forms/d/1FAIpQLS.../viewform"
    }
  ]
}
```

| URL | Use in class |
|-----|----------------|
| `editUrl` | Trainer edits questions or settings |
| `responderUrl` | Short link / QR for trainees |

Share **responder** URLs after each module reflection (see [module-delivery-pattern.md](../module-delivery-pattern.md)).

---

## Step 8 — Link responses to Google Sheets (manual)

For each form:

1. Open `editUrl` from `forms-output.json`.
2. **Responses** tab → **Link to Sheets** → create or select a spreadsheet.
3. Suggested Sheet name: `MHP-DE-2026-Mod{NN}-Responses`.

Repeat for Mod 0–7 (and 8–9 if created).

---

## Step 9 — Publish forms for trainees

In each form **Settings**:

- Turn off **Restrict to users in your organization** if trainees use personal Gmail.
- Choose whether to collect **email addresses** (or rely on the "Full name" question in the manifest).
- Confirm **Accepting responses** is on.

Generate QR codes from each `responderUrl` (any QR generator).

---

## Updating forms after manifest changes

1. Edit [google-forms-manifest.yaml](google-forms-manifest.yaml).
2. **Option A (recommended)** — Replace via script (uses `forms-output.json` from your last successful run):

   ```powershell
   node create-forms.mjs --replace --dry-run   # preview deletes
   node create-forms.mjs --replace             # delete + recreate all
   node create-forms.mjs --module 4 --replace  # one module only
   ```

   Deletes go to **Google Drive trash** (Forms file). Linked Sheets keep historical responses; point Sheets at the new forms if needed.

3. **Option B** — `node create-forms.mjs --delete-only` then create without `--replace`.
4. **Option C** — Manually edit existing forms in the Google Forms UI.

The script does **not** update existing forms in place; without `--replace` it **adds** new forms each run.

### Quiz mode and auto-grading (Mod 0–9)

Reflection forms are created as **quizzes** using [google-forms-grading.yaml](google-forms-grading.yaml):

- Correct answers and point values (radio = 1 pt, checkbox = 2 pt by default)
- **Correct/incorrect** feedback (`when_right` / `when_wrong`) shown after submit
- Scales, grids, Story risk question (0.7), and **final survey** are **not** auto-graded

Regenerate trainer answer key after editing grading:

```powershell
node sync-grading-artifacts.mjs
```

In Google Forms → **Settings → Quizzes**, set **Release grade** (e.g. immediately after submission).

---

## Fix: localhost refused to connect

After you click **Continue**, the browser may show:

> *This site can’t be reached* — *localhost refused to connect*

That means Google redirected to `localhost` but **the script was not running** a local server yet (or the terminal was closed).

### Fix (Desktop client — most users)

1. Confirm OAuth client type is **Desktop app** on [Clients](https://console.cloud.google.com/auth/clients) — you do **not** need to add redirect URIs.
2. Delete old token: remove `trainer/google-forms/token.json`.
3. Run the script and **keep the terminal open**:

```powershell
cd <repo-root>/workshop-2026-v2/trainer/google-forms
node create-forms.mjs
```

4. Wait until the terminal prints: `OAuth: waiting for browser callback on 127.0.0.1:53187 ...`
5. Complete sign-in in the browser → you should see **Authorization successful** (not connection refused).

### Fix (only if your client type is **Web application**)

1. [Clients](https://console.cloud.google.com/auth/clients) → click the **Web** client → **Authorized redirect URIs** → add:
   ```
   http://127.0.0.1:53187/oauth2callback
   ```
2. Save, delete `token.json`, run `node create-forms.mjs` again.

### Still failing?

| Cause | Action |
|-------|--------|
| Terminal closed before Allow | Script must stay running until browser shows success |
| Wrong client type | Use **Desktop app** + new `credentials.json` |
| `redirect_uri_mismatch` | Web client: add URI above; Desktop: use Desktop credentials only |
| Firewall / VPN | Allow local `127.0.0.1` |

---

## Fix: Access blocked (403 access_denied)

If the browser shows:

> *Access blocked: … has not completed the Google verification process*  
> *Error 403: access_denied*

the OAuth app is in **Testing** mode and your Google account is **not** on the **Test users** list (or you signed in with a different account).

### Fix in Google Cloud Console (2 minutes)

1. Open [Google Cloud Console](https://console.cloud.google.com/) → select project **mhp-de-workshop-forms** (or your project name).
2. **APIs & Services** → **OAuth consent screen**.
3. Scroll to **Test users** → **+ ADD USERS**.
4. Enter the **exact** email you use in the browser, e.g. `juewei.jin@gmail.com` → **Save**.
5. Wait **1–2 minutes**, then run `node create-forms.mjs` again and open a **new** OAuth URL (or incognito window).
6. Sign in **only** as that test user—not a work account alias unless that alias is also added.

### Checklist

| Check | Action |
|-------|--------|
| Correct GCP project | OAuth client and consent screen must be in the **same** project as `credentials.json` |
| External + Testing | Normal for Gmail; you **must** use Test users, not “Publish app” |
| Email matches | Login email = one of the Test users (character-for-character) |
| Stale browser session | Try incognito or sign out of other Google accounts first |
| Workspace org | If your company blocks unverified apps, use **Internal** user type on Workspace, or create forms manually |

### If you have Google Workspace (company account)

On **OAuth consent screen**, if **Internal** is available:

1. Switch user type to **Internal** (org-only; no Test users list).
2. Only accounts in your organization can authorize—use your `@company.com` account consistently.

### What not to do

- **Publish app** to production without verification—you will hit Google’s review process; unnecessary for a trainer script.
- Add scopes beyond `forms.body` unless you need them—extra scopes slow consent and verification.

---

## Troubleshooting

| Symptom | Fix |
|---------|-----|
| `credentials.json not found` | Download OAuth Desktop JSON to `trainer/google-forms/credentials.json` |
| **localhost refused to connect** | [Fix section above](#fix-localhost-refused-to-connect)—run script first; redirect URI only for **Web** clients |
| **Cannot find redirect URIs** | Normal for **Desktop app**—use [Clients](https://console.cloud.google.com/auth/clients) and verify type is Desktop |
| **Access blocked** / `403 access_denied` | [Fix section above](#fix-access-blocked-403-access_denied)—add `juewei.jin@gmail.com` (or your login) under **Test users** |
| `access_denied` on consent screen | Same as above; email must be on Test users for External apps |
| `Google Forms API has not been used...` | Enable **Google Forms API** in the correct GCP project |
| `Google Drive API has not been used...` / `it is disabled` | Enable **Google Drive API** in the same project → wait a few minutes → re-run (no new credentials) |
| `invalid_grant` / token expired | Delete `token.json` and run `node create-forms.mjs` again |
| `Insufficient Permission` (delete or banner) | Add scope **`drive`** (not only `drive.file`) on consent screen → delete `token.json` → run `node create-forms.mjs` again and complete OAuth |
| `Failed to fetch image from source_uri` | Delete `banner-drive-cache.json`, re-run (script picks a Forms-compatible Drive URL). Or set `banner_source_uri` in manifest to any **public** image URL. Or add banner manually in Form editor |
| Banner missing on forms | Same as above; or add image manually in each Form editor |
| `--replace` delete failed | Old forms still in Drive — trash them at [Google Drive](https://drive.google.com) (filter: Google Forms); new URLs are in `forms-output.json` |
| Enterprise blocks third-party apps | Ask IT to allow the OAuth client or use manual Form creation |
| Grid questions missing options | Check manifest `GRID_RADIO` has `rows` and `columns` arrays |

---

## Security checklist

- [ ] `credentials.json` and `token.json` are **not** committed
- [ ] `forms-output.json` contains only public form IDs (safe to share with co-trainers)
- [ ] Workshop forms owned by a **team** Google account if possible
- [ ] Revoke access later: [Google Account → Third-party access](https://myaccount.google.com/permissions)

---

## File layout

```
workshop-2026-v2/trainer/google-forms/
├── google-forms-manifest.yaml          ← source of truth for questions
├── google-forms-grading.yaml           ← quiz keys + feedback (Mod 0–9)
├── google-forms-reflection-design.md
├── google-forms-reflection-design.html
├── google-forms-reflection-answer-key.md
├── google-forms-gcp-setup-guide.md     ← this document
├── google-forms-gcp-setup-guide.html
├── create-forms.mjs                    ← automation entry point
├── sync-grading-artifacts.mjs          ← answer-key.md from grading YAML
├── package.json
├── credentials.json                    ← you download (gitignored)
├── credentials.json.example
├── token.json                          ← created on first auth (gitignored)
└── forms-output.json                   ← URLs after successful run
```

---

## Quick reference commands

```powershell
cd <repo-root>/workshop-2026-v2/trainer/google-forms
npm install
node create-forms.mjs --dry-run
node create-forms.mjs
node create-forms.mjs --replace
node create-forms.mjs --module 7 --replace
node create-forms.mjs --delete-only
node create-forms.mjs --organize-folder
node sync-grading-artifacts.mjs
```

Regenerate HTML companion for this guide:

```powershell
python <repo-root>/workshop-2026-v2/trainer/build_trainer_html.py google-forms/google-forms-gcp-setup-guide.md
```

---

## Document history

| Date | Change |
|------|--------|
| 2026-05-31 | Initial GCP + Forms API automation setup guide |
| 2026-05-31 | HTML companion: [google-forms-gcp-setup-guide.html](google-forms-gcp-setup-guide.html) |
| 2026-05-31 | Consolidated under `trainer/google-forms/` |
| 2026-05-31 | Added 403 access_denied / Test users fix section |
| 2026-05-31 | Local OAuth callback on 127.0.0.1:53187; localhost troubleshooting |
| 2026-05-31 | Console navigation: Google Auth Platform → Clients; Desktop has no redirect URI field |
