# Google Calendar Integration

The events calendar is backed by a real Google Calendar so whoever manages neighborhood events can just use the Google Calendar app on their phone. The site pulls events on a schedule and caches them in MySQL, so the homepage never waits on Google's API.

**Use a service account, not OAuth.** A service account needs no human consent, no refresh token, and never expires — which is exactly what you want for a background sync.

---

## Step 1 — Create the calendar

1. Go to **https://calendar.google.com**
2. Left sidebar → **Other calendars** → **+** → **Create new calendar**
3. Name: `Paddlers Cove Community Events`
4. Description: `Neighborhood events, meetings, and gatherings`
5. Time zone: **(GMT-05:00) Eastern Time - New York**
6. **Create calendar**

---

## Step 2 — Get the Calendar ID

1. **Settings** → select the new calendar under "Settings for my calendars"
2. Scroll to **Integrate calendar**
3. Copy **Calendar ID** — it looks like:
   ```
   c_abc123def456@group.calendar.google.com
   ```

```ini
GCAL_CALENDAR_ID=c_abc123def456@group.calendar.google.com
```

---

## Step 3 — Create a Google Cloud service account

You can reuse the same Google Cloud project you made for Google Sign-In.

1. **https://console.cloud.google.com/** → select `Paddlers Cove Community Site`
2. **APIs & Services** → **Library** → search **Google Calendar API** → **Enable**
3. **APIs & Services** → **Credentials** → **+ Create Credentials** → **Service account**
4. Service account name: `paddlers-calendar-reader`
5. **Create and Continue** → skip role assignment (not needed) → **Done**

---

## Step 4 — Create and download the key

1. Click the new service account → **Keys** tab
2. **Add Key** → **Create new key** → **JSON** → **Create**
3. A JSON file downloads. Upload it to:
   ```
   /home/USER/paddlerscove/config/keys/gcal-service-account.json
   ```
4. `chmod 600` it. Never put it under `public/`.

```ini
GCAL_SERVICE_ACCOUNT_FILE=config/keys/gcal-service-account.json
```

---

## Step 5 — Share the calendar with the service account

**This is the step everyone forgets, and it fails silently.**

1. Open the service account JSON and find the `client_email` value, e.g.
   `paddlers-calendar-reader@paddlers-cove.iam.gserviceaccount.com`
2. Back in **Google Calendar** → Settings for the community calendar
3. **Share with specific people or groups** → **+ Add people**
4. Paste the service account email
5. Permission: **See all event details**
6. **Send**

Without this, every API call returns 404 "Not Found" even though the calendar exists.

---

## Step 6 — Make the calendar public (optional)

If you want non-members to see events on the public events page:

Settings → **Access permissions for events** → check **Make available to public** → **See all event details**

---

## Step 7 — Schedule the sync

The app auto-refreshes when the cache is older than `GCAL_CACHE_MINUTES` (default 60), so a cron job is technically optional. But a cron job means the first visitor of the day doesn't eat the API latency.

**cPanel → Cron Jobs → Add New Cron Job**

- Common settings: **Once per hour**
- Command:
  ```
  /usr/local/bin/php /home/USER/paddlerscove/bin/sync-events.php >/dev/null 2>&1
  ```

Or hit the web endpoint (protected by `APP_KEY`):
```
curl -s "https://paddlerscove.org/events/sync?key=YOUR_APP_KEY"
```

---

## Step 8 — Test

Add a test event in Google Calendar, then:

```bash
php bin/sync-events.php
# 2026-09-21T12:00:00-04:00 synced 1 events
```

Visit `/events` and confirm it appears.

---

## Troubleshooting

| Symptom | Cause | Fix |
|---|---|---|
| "Not Found" 404 | Calendar not shared with the service account | Step 5 |
| "Calendar API has not been used" | API not enabled in the project | Step 3.2 |
| Recurring events show once | `singleEvents` not set | Already set to `true` in `EventRepository` |
| Times off by hours | Calendar timezone vs `APP_TIMEZONE` | Set both to America/New_York |
| Deleted events still showing | Purge only covers the last month forward | Run the sync again; older cached rows are left alone by design |
