Skip to main content

Buddy Invites

The buddy system enables players to build and manage a golf buddy list, with support for inviting contacts via email or phone.

Architecture

┌─────────────────┐     ┌──────────────────┐     ┌─────────────────┐
│ Player App │────▶│ BuddyService │────▶│ PlayerBuddy │
│ /players/me │ │ │ │ Repository │
└─────────────────┘ └──────────────────┘ └─────────────────┘


┌──────────────────┐
│ BuddyInvite │
│ Notification │──▶ WhatsApp
└──────────────────┘

Sources:

  • libs/tee-time-services/src/lib/buddy.service.ts
  • libs/tee-time-services/src/lib/buddy-invite-notification.service.ts

Invite Workflow

Step 1: Player Sends Invite

POST /players/me/buddies/invite

// Request
{
"email": "friend@example.com",
"phone": "+27821234567",
"name": "John Smith" // Optional display name
}

// Response (existing player found)
{
"status": "added",
"buddyId": "player-123"
}

// Response (no existing player)
{
"status": "pending",
"inviteId": "invite-456"
}

Step 2: Resolution Flow

Input: email/phone


┌─────────────────┐
│ Check phone │──► Player found? ──► Create buddy ──► Return "added"
│ (E.164 format) │
└─────────────────┘
│ Not found

┌─────────────────┐
│ Check email │──► Player found? ──► Create buddy ──► Return "added"
│ (case-insensitive)│
└─────────────────┘
│ Not found

┌─────────────────┐
│ Create pending │──► Send WhatsApp ──► Return "pending"
│ invite │
└─────────────────┘

Step 3: Invite Acceptance

When invited contact signs up:

  1. System discovers pending invites by email/phone
  2. Auto-creates buddy relationships
  3. Marks invites as accepted
  4. Sends confirmation to inviters via WhatsApp

Phone Normalization

All phone numbers normalized to E.164 format:

InputOutput
0821234567+27821234567
082 123 4567+27821234567
27821234567+27821234567
+27821234567+27821234567
0027821234567+27821234567
+1 415 555 0000+14155550000

Default country code: 27 (South Africa)

WhatsApp Integration

Invite Notification (Phase 1 - Text)

// Message template
"Hi! {inviterName} has added you as a golf buddy on TeeTime.
Download the app to connect: {downloadUrl}"

Invite Notification (Phase 2 - Template)

// Template: buddy_invite_v7
{
header: "TeeTime Buddy Request",
body: "You were added as a golf buddy in TeeTime.",
button: "Review Buddy Request" → deep link
}

Acceptance Notification

// Template: buddy_accepted_v7
{
header: "Buddy List Updated",
body: "{buddyName} has been added as your buddy.",
button: "View Buddy List"
}

Configuration

interface BuddyInviteNotificationOptions {
downloadUrl?: string; // Default: 'https://teetime.app'
locale?: string; // Default: 'en-US'
enableWhatsApp?: boolean; // Default: true
useTemplateMessages?: boolean; // Default: false
}

API Endpoints

Player Endpoints (Authenticated)

MethodEndpointDescription
GET/players/me/buddiesList my buddies
POST/players/me/buddiesAdd buddy by ID
POST/players/me/buddies/inviteInvite by email/phone
DELETE/players/me/buddies/:buddyIdRemove buddy
GET/players/me/buddies/groupsGenerate booking groups

Public Endpoints (No Auth)

MethodEndpointDescription
GET/buddy-invites/:tokenGet invite details

Admin Endpoints

MethodEndpointDescription
GET/admin/players/:playerId/buddiesList player's buddies
POST/admin/players/:playerId/buddiesAdd buddy for player
DELETE/admin/players/:playerId/buddies/:buddyIdRemove buddy

Invite Details

Get invite status without authentication:

GET /buddy-invites/:token

// Response
{
"token": "invite-456",
"status": "pending", // or "accepted"
"inviterName": "Jane Doe",
"createdAt": "2025-12-15T08:00:00Z"
}

Buddy List Management

List Buddies

GET /players/me/buddies

// Response
[
{
"id": "buddy-rel-123",
"playerId": "my-player-id",
"buddyId": "player-456",
"createdAt": "2025-12-15T08:00:00Z",
"buddy": {
"id": "player-456",
"firstName": "John",
"lastName": "Smith",
"fullName": "John Smith",
"email": "john@example.com",
"phoneNumber": "+27821234567"
}
}
]

Add Buddy (Direct)

POST /players/me/buddies

// Request
{
"buddyId": "player-456"
}

Remove Buddy

DELETE /players/me/buddies/:buddyId

Buddy Groups

Generate groups for booking:

GET /players/me/buddies/groups?size=4&pad=OPEN

// Response
[
{
"group": [
{ "id": "player-1", "name": "John Smith" },
{ "id": "player-2", "name": "Jane Doe" },
{ "id": "player-3", "name": "Bob Wilson" },
{ "type": "OPEN" } // Padded slot
]
}
]

Data Model

PlayerBuddy

interface PlayerBuddy {
id: string;
playerId: string;
buddyId: string;
createdAt: Date;
}

PendingBuddyInvite

interface PendingBuddyInvite {
id: string; // Also serves as token
inviterPlayerId: string;
inviteeEmail?: string;
inviteePhone?: string;
inviteeName?: string;
status: 'pending' | 'accepted';
acceptedAt?: Date;
createdAt: Date;
}

Error Handling

ErrorCause
BadRequestExceptionNeither email nor phone provided
NotFoundExceptionPlayer not found (for direct add)
ConflictExceptionAlready buddies

Notification Delivery

Fire-and-Forget Pattern

  • WhatsApp sends don't block API responses
  • Errors logged but non-fatal
  • Graceful degradation if messaging unavailable

Delivery Events

EventNotification
Invite createdWhatsApp to invitee (if phone provided)
Invite acceptedWhatsApp to inviter

Access Control

Player Endpoints

  • JWT authentication required
  • Identity resolved from token (sub, userId, email, phone)
  • Auto-creates player on first use

Admin Endpoints

  • JWT authentication required
  • teetime-admin audience required
  • Can manage any player's buddies

Troubleshooting

Invite Not Matching on Signup

  1. Check email case sensitivity
  2. Verify phone normalization format
  3. Ensure invite is still pending (not accepted)

WhatsApp Not Sending

  1. Check enableWhatsApp configuration
  2. Verify phone number format
  3. Review WhatsApp messenger logs
  4. Check template approval status (Phase 2)

Duplicate Buddy Relationships

  1. Service handles duplicates gracefully
  2. Returns existing relationship instead of error
  3. Check database constraint on (playerId, buddyId)