# Backend Integration Notes: Firebase Push Notifications

> **Last Updated**: 2024-12-30 (Phase 5 Complete)

This document outlines what the backend needs to know and implement to properly send push notifications to the BuyerKiosk Team mobile app.

---

## Mobile App Implementation Status

| Phase | Feature | Status |
|-------|---------|--------|
| Phase 1 | Firebase SDK Integration | ✅ Complete |
| Phase 2 | Push Notification Service | ✅ Complete |
| Phase 3 | Notification Provider & State | ✅ Complete |
| Phase 4 | Auth Integration & Token Registration | ✅ Complete |
| Phase 5 | Deep Link Navigation | ✅ Complete |
| Phase 6 | Notification Preferences UI | 🔄 Pending |
| Phase 7 | E2E Testing | 🔄 Pending |

---

## Overview

The mobile app uses **Firebase Cloud Messaging (FCM)** to receive push notifications. The backend is responsible for:
1. Storing FCM tokens per user/device
2. Determining WHEN to send notifications based on business logic
3. Sending notifications via the Firebase Admin SDK
4. Respecting user notification preferences

---

## API Endpoints to Implement

### 1. Register Device Token

```
POST /api/mobile/scheduling/me/devices
Authorization: Bearer <jwt>
```

**Request Body:**
```json
{
  "fcm_token": "dGhpcyBpcyBhIHNhbXBsZSBGQ00gdG9rZW4...",
  "device_id": "unique-device-uuid",
  "platform": "ios",
  "app_version": "1.0.0"
}
```

**Response (Success):**
```json
{
  "registered": true,
  "device_id": "unique-device-uuid"
}
```

**Mobile App Behavior:**
- ✅ **Called after successful login** (non-blocking - login succeeds even if registration fails)
- ✅ **Called on FCM token refresh** (tokens can change)
- `device_id` is a UUID generated once per app install and stored locally
- `platform` is `"ios"` or `"android"`
- `app_version` is retrieved from `package_info_plus` (e.g., "1.0.0")

**Backend Notes:**
- Store the FCM token associated with the user and device
- A user may have multiple devices (multiple tokens)
- Token may change over time - **UPSERT** if device_id already exists
- FCM tokens are not sensitive but should be stored securely to prevent spam
- Return `200` even on upsert (update existing record)

---

### 2. Remove Device Token (Logout)

```
DELETE /api/mobile/scheduling/me/devices/{device_id}
Authorization: Bearer <jwt>
```

**Response (Success):**
```json
{
  "removed": true
}
```

**Mobile App Behavior:**
- ✅ **Called on logout** with the stored device_id
- ✅ **Ignores 404 errors** (device may already be removed)
- Non-blocking - logout succeeds even if removal fails

**Backend Notes:**
- Remove the device from receiving notifications
- **Return 200 even if device not found** (idempotent)
- Do not return 404 - the mobile app handles missing devices gracefully

---

### 3. Get Notification Preferences

```
GET /api/mobile/scheduling/me/notification-preferences
Authorization: Bearer <jwt>
```

**Response:**
```json
{
  "preferences": {
    "schedule_enabled": true,
    "clock_reminders_enabled": true,
    "open_shifts_enabled": true,
    "chat_enabled": true,
    "shift_reminder_minutes": 15,
    "do_not_disturb": false,
    "quiet_hours_start": null,
    "quiet_hours_end": null
  }
}
```

---

### 4. Update Notification Preferences

```
PUT /api/mobile/scheduling/me/notification-preferences
Authorization: Bearer <jwt>
```

**Request Body:**
```json
{
  "schedule_enabled": true,
  "clock_reminders_enabled": true,
  "open_shifts_enabled": false,
  "chat_enabled": true,
  "shift_reminder_minutes": 30,
  "do_not_disturb": false,
  "quiet_hours_start": "22:00",
  "quiet_hours_end": "07:00"
}
```

**Notes:**
- All fields are optional - only update provided fields
- Backend MUST check these preferences before sending notifications

---

## Notification Payload Format

All notifications should use FCM's **data-only messages** for consistent handling across foreground/background states. The mobile app parses the `data` payload.

### Standard Payload Structure

```json
{
  "to": "<fcm_token>",
  "priority": "high",
  "content_available": true,
  "data": {
    "type": "schedule",
    "action": "view_schedule",
    "title": "Schedule Updated",
    "body": "Your Tuesday shift changed to 2pm-8pm",
    "target_route": "/schedule",
    ...type_specific_data
  },
  "notification": {
    "title": "Schedule Updated",
    "body": "Your Tuesday shift changed to 2pm-8pm"
  }
}
```

**Important Fields:**
- `type`: One of `schedule`, `clock`, `open_shift`, `chat` **(REQUIRED)**
- `action`: What the user can do (see action types below)
- `title`: Short notification title
- `body`: Notification message body
- `target_route`: Deep link route in the app (optional - mobile app can derive from type)

### ✅ Implemented Deep Link Routes (Phase 5)

| Notification Type | Route | Parameters | Example |
|-------------------|-------|------------|---------|
| `schedule` | `/schedule` | `?date=YYYY-MM-DD` (optional) | `/schedule?date=2025-01-15` |
| `clock` | `/home` | None | `/home` |
| `open_shift` | `/home/open-shifts` or `/home/open-shifts/{shift_id}` | `shift_id` (optional) | `/home/open-shifts/abc123` |
| `chat` | `/chat` or `/chat/{conversation_id}` | `conversation_id` (optional) | `/chat/conv-456` |

**Mobile App Routing Logic:**
```
1. Parse `type` from data payload
2. Extract relevant IDs from data (shift_id, conversation_id, date)
3. Navigate to appropriate route:
   - schedule: /schedule?date={date}
   - clock: /home
   - open_shift: /home/open-shifts/{shift_id} or /home/open-shifts
   - chat: /chat/{conversation_id} or /chat
   - unknown type: /home (fallback)
```

**⚠️ Important:** The `type` field is **required**. Notifications without a valid type will be ignored by the mobile app.

### Data Field Key Names

The mobile app accepts both **snake_case** and **camelCase** for data fields:

| Field | snake_case | camelCase |
|-------|------------|-----------|
| Shift ID | `shift_id` | `shiftId` |
| Conversation ID | `conversation_id` | `conversationId` |
| Store Type Num | `store_type_num` | `storeTypeNum` |

**Recommendation:** Use snake_case consistently for backend payloads.

---

## Notification Types & When to Send

### 1. Schedule Notifications (`type: "schedule"`)

| Trigger Event | Title Example | Body Example | Data Payload |
|---------------|---------------|--------------|--------------|
| Shift added to schedule | "New Shift Added" | "You have a new shift on Tuesday, Jan 15" | `shift_id`, `date`, `change_type: "added"` |
| Shift time changed | "Schedule Updated" | "Your Tuesday shift changed to 2pm-8pm" | `shift_id`, `date`, `change_type: "modified"` |
| Shift removed | "Shift Removed" | "Your Tuesday shift has been cancelled" | `shift_id`, `date`, `change_type: "removed"` |
| Upcoming shift reminder | "Shift Reminder" | "Your shift starts in 15 minutes" | `shift_id`, `date`, `change_type: "reminder"` |

**Data Payload:**
```json
{
  "type": "schedule",
  "action": "view_schedule",
  "shift_id": "shift_uuid_123",
  "date": "2025-01-15",
  "change_type": "added",
  "message": "Your Tuesday shift changed to 2pm-8pm"
}
```

**When to Send:**
- Immediately when schedule is published/modified
- At `shift_reminder_minutes` before shift start (user preference, default 15)
- Only if `schedule_enabled` preference is true

---

### 2. Clock Notifications (`type: "clock"`)

| Trigger Event | Title Example | Body Example | Data Payload |
|---------------|---------------|--------------|--------------|
| Clock-in reminder | "Time to Clock In" | "Your shift starts in 15 minutes" | `action_type: "clock_in_reminder"` |
| Clock-out reminder | "Clock Out Reminder" | "Your shift ended 5 minutes ago" | `action_type: "clock_out_reminder"` |
| Overtime alert | "Overtime Alert" | "You're approaching 40 hours this week" | `action_type: "overtime_alert"` |
| Missed punch | "Missed Punch" | "You clocked in but never clocked out yesterday" | `action_type: "missed_punch"` |

**Data Payload:**
```json
{
  "type": "clock",
  "action": "clock_in",
  "shift_id": "shift_uuid_123",
  "store_type_num": "12345",
  "action_type": "clock_in_reminder",
  "geofence_required": true
}
```

**When to Send:**
- Clock-in reminder: `shift_reminder_minutes` before shift start, if user not already clocked in
- Clock-out reminder: 5 minutes after scheduled shift end, if user still clocked in
- Overtime alert: When approaching overtime threshold (e.g., 38 hours)
- Missed punch: Morning after a shift where user clocked in but never out
- Only if `clock_reminders_enabled` preference is true
- **Cancel reminder if user clocks in/out before reminder time**

---

### 3. Open Shift Notifications (`type: "open_shift"`)

| Trigger Event | Title Example | Body Example | Data Payload |
|---------------|---------------|--------------|--------------|
| New open shift posted | "Open Shift Available" | "Wed 10am-4pm at Store #123" | `shift_id`, `date`, `start_time`, `end_time` |
| Shift claim approved | "Shift Claimed!" | "Your request for Wed 10am-4pm was approved" | `shift_id`, `status: "approved"` |
| Shift claim denied | "Shift Unavailable" | "The Wed 10am-4pm shift was already claimed" | `shift_id`, `status: "denied"` |

**Data Payload:**
```json
{
  "type": "open_shift",
  "action": "claim_shift",
  "shift_id": "shift_uuid_456",
  "store_type_num": "12345",
  "date": "2025-01-17",
  "start_time": "10:00",
  "end_time": "16:00"
}
```

**When to Send:**
- Immediately when new open shift is posted
- Only to users who:
  - Have `open_shifts_enabled` preference true
  - Are qualified for the position
  - Have indicated availability for that time
  - Aren't already scheduled for that time

---

### 4. Chat Notifications (`type: "chat"`)

| Trigger Event | Title Example | Body Example | Data Payload |
|---------------|---------------|--------------|--------------|
| New team message | "Team Chat: Store #123" | "Sarah: Can anyone cover tomorrow?" | `conversation_id`, `sender_name`, `is_direct_message: false` |
| Direct message | "Sarah Johnson" | "Hey, can you swap shifts with me?" | `conversation_id`, `sender_name`, `is_direct_message: true` |
| @mention | "You were mentioned" | "John mentioned you in Store #123 chat" | `conversation_id`, `mentioned_by` |

**Data Payload:**
```json
{
  "type": "chat",
  "action": "reply",
  "conversation_id": "conv_uuid_789",
  "sender_name": "Sarah Johnson",
  "message_preview": "Can anyone cover tomorrow?",
  "is_direct_message": false
}
```

**When to Send:**
- Immediately on new message (consider batching rapid messages)
- Only if `chat_enabled` preference is true
- Don't send to the user who sent the message
- Consider grouping multiple messages from same conversation

---

## Quiet Hours / Do Not Disturb

If user has set quiet hours:
- Check `quiet_hours_start` and `quiet_hours_end` before sending
- If within quiet hours AND `do_not_disturb` is true:
  - Option A: Queue notification for after quiet hours
  - Option B: Send with `priority: "normal"` (silent delivery)
  - Urgent notifications (shift cancellation) can override

---

## FCM Priority Levels

| Notification Type | Priority | Rationale |
|-------------------|----------|-----------|
| Schedule changes | high | Time-sensitive |
| Clock reminders | high | Needs immediate attention |
| Open shifts | high | First-come-first-served |
| Chat messages | normal | Can wait |
| Shift reminders | high | Time-critical |

---

## Error Handling

### Invalid/Expired FCM Token
When FCM returns `INVALID_ARGUMENT` or `NOT_REGISTERED`:
- Remove the token from the database
- User will re-register on next app open

### Rate Limiting
FCM has quotas:
- ~5000 messages per second per project
- If hitting limits, queue and batch

---

## Testing Notifications

### Firebase Console
You can send test notifications from Firebase Console > Cloud Messaging:
1. Enter a FCM token
2. Set custom data payload
3. Send test message

### Recommended Test Scenarios

1. **Schedule Change**: Update a user's schedule, verify notification received
2. **Clock Reminder**: Wait for reminder time, verify notification (or trigger manually)
3. **Open Shift**: Post an open shift, verify qualified users receive notification
4. **Chat**: Send message in team chat, verify other members receive notification
5. **Preference Respect**: Disable a category, verify no notifications for that type
6. **Multi-device**: Log into multiple devices, verify all receive notifications
7. **Logout**: Log out of one device, verify that device stops receiving

---

## Data Model Suggestions

### devices table
```sql
CREATE TABLE user_devices (
  id UUID PRIMARY KEY,
  user_id UUID REFERENCES users(id),
  fcm_token TEXT NOT NULL,
  device_id TEXT NOT NULL,
  platform TEXT NOT NULL CHECK (platform IN ('ios', 'android')),
  app_version TEXT,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW(),
  UNIQUE(user_id, device_id)
);
```

### notification_preferences table
```sql
CREATE TABLE notification_preferences (
  user_id UUID PRIMARY KEY REFERENCES users(id),
  schedule_enabled BOOLEAN DEFAULT true,
  clock_reminders_enabled BOOLEAN DEFAULT true,
  open_shifts_enabled BOOLEAN DEFAULT true,
  chat_enabled BOOLEAN DEFAULT true,
  shift_reminder_minutes INTEGER DEFAULT 15,
  do_not_disturb BOOLEAN DEFAULT false,
  quiet_hours_start TIME,
  quiet_hours_end TIME,
  updated_at TIMESTAMP DEFAULT NOW()
);
```

---

## Foreground vs Background Handling

The mobile app handles notifications differently based on app state:

### Background/Terminated State
- FCM SDK shows system notification automatically
- User taps notification → app opens → `onMessageOpenedApp` fires
- Mobile app parses payload and navigates to correct screen

### Foreground State
- **System notification is suppressed** (iOS behavior with FCM)
- Mobile app shows **in-app banner** that slides down from top
- Banner auto-dismisses after 5 seconds or can be tapped/dismissed
- Tap navigates to relevant screen

### What This Means for Backend
- **Always include `notification` block** for background display title/body
- **Always include `data` block** with `type` for routing
- The `data.title` and `data.body` are used for in-app banner
- The `notification.title` and `notification.body` are used for system notification

---

## Summary Checklist for Backend Team

### API Endpoints (Required for Mobile Integration)

Base path: `/api/mobile/scheduling`

- [ ] Set up Firebase Admin SDK in backend
- [ ] Implement `POST /api/mobile/scheduling/me/devices` - Register device token
  - Accepts: `fcm_token`, `device_id`, `platform`, `app_version`
  - Upsert behavior (update if device_id exists)
- [ ] Implement `DELETE /api/mobile/scheduling/me/devices/{device_id}` - Remove device
  - Return 200 even if not found (idempotent)
- [ ] Implement `GET /api/mobile/scheduling/me/notification-preferences` - Get preferences
- [ ] Implement `PUT /api/mobile/scheduling/me/notification-preferences` - Update preferences

### Notification Triggers (Business Logic)

- [ ] Schedule published/modified → notify affected users
  - Include: `type: "schedule"`, `date`, `shift_id`, `change_type`
- [ ] Clock-in reminder → check upcoming shifts, notify if not clocked in
  - Include: `type: "clock"`, `action_type: "clock_in_reminder"`, `shift_id`
- [ ] Clock-out reminder → check ended shifts, notify if still clocked in
  - Include: `type: "clock"`, `action_type: "clock_out_reminder"`, `shift_id`
- [ ] Open shift posted → notify qualified users
  - Include: `type: "open_shift"`, `shift_id`, `date`, `start_time`, `end_time`
- [ ] Chat message → notify conversation members
  - Include: `type: "chat"`, `conversation_id`, `sender_name`

### Quality & Compliance

- [ ] Respect user preferences before sending any notification
- [ ] Handle FCM token errors (remove invalid tokens on `NOT_REGISTERED`)
- [ ] Monitor delivery rates in Firebase Console
- [ ] Test all notification types end-to-end

### Mobile App File References

For understanding mobile implementation:
- `lib/core/services/push_notification_service.dart` - FCM handling
- `lib/core/services/notification_navigation_service.dart` - Deep link routing
- `lib/presentation/providers/notification_provider.dart` - State management
- `lib/presentation/widgets/notifications/in_app_notification_banner.dart` - Foreground UI
- `lib/core/constants/notification_constants.dart` - Type constants

---

*Generated for spec 005-firebase-push-notifications*
*Last updated: 2024-12-30 (Phase 5 Complete)*
