Head of Product & Engineering a40a4aa626
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
feat: initial implementation of Cast GHL Provider
Complete MVP implementation of the Cast GHL Conversation Provider bridge:
- Go module setup with chi router and mongo-driver dependencies
- Config loading with env var validation and defaults
- MongoDB token store with upsert, get, update, delete operations
- Cast.ph SMS client with 429 retry logic and typed errors
- Phone number normalization (E.164 ↔ Philippine local format)
- GHL OAuth 2.0 install/callback/refresh flow
- GHL webhook handler with ECDSA signature verification (async dispatch)
- GHL API client for message status updates and inbound message stubs
- Multi-stage Dockerfile, docker-compose with MongoDB, Woodpecker CI pipeline
- Unit tests for phone normalization, Cast client, GHL webhook, and OAuth handlers

Co-Authored-By: SideKx <sidekx.ai@sds.dev>
2026-04-04 17:27:05 +02:00

71 lines
1.9 KiB
Go

package phone
import (
"errors"
"regexp"
"strings"
)
var nonDigit = regexp.MustCompile(`[^\d]`)
// ToLocal converts a phone number to Philippine local format (09XXXXXXXXX).
func ToLocal(e164 string) (string, error) {
if e164 == "" {
return "", errors.New("invalid Philippine phone number: empty input")
}
digits := nonDigit.ReplaceAllString(e164, "")
if digits == "" {
return "", errors.New("invalid Philippine phone number: no digits found")
}
var local string
switch {
case strings.HasPrefix(digits, "63") && len(digits) == 12:
local = "0" + digits[2:]
case strings.HasPrefix(digits, "9") && len(digits) == 10:
local = "0" + digits
case strings.HasPrefix(digits, "0") && len(digits) == 11:
local = digits
default:
return "", errors.New("invalid Philippine phone number")
}
if len(local) != 11 || !strings.HasPrefix(local, "09") {
return "", errors.New("invalid Philippine phone number")
}
return local, nil
}
// ToE164 converts a phone number to E.164 format (+63XXXXXXXXXX).
func ToE164(local string) (string, error) {
if local == "" {
return "", errors.New("invalid Philippine phone number: empty input")
}
// preserve leading + before stripping
hasPlus := strings.HasPrefix(local, "+")
digits := nonDigit.ReplaceAllString(local, "")
if digits == "" {
return "", errors.New("invalid Philippine phone number: no digits found")
}
var e164 string
switch {
case hasPlus && strings.HasPrefix(digits, "63") && len(digits) == 12:
e164 = "+" + digits
case !hasPlus && strings.HasPrefix(digits, "63") && len(digits) == 12:
e164 = "+" + digits
case strings.HasPrefix(digits, "0") && len(digits) == 11:
e164 = "+63" + digits[1:]
case strings.HasPrefix(digits, "9") && len(digits) == 10:
e164 = "+63" + digits
default:
return "", errors.New("invalid Philippine phone number")
}
if !regexp.MustCompile(`^\+63\d{10}$`).MatchString(e164) {
return "", errors.New("invalid Philippine phone number")
}
return e164, nil
}