Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
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>
3.2 KiB
3.2 KiB
Task 02: Config & MongoDB Store
Objective
Build config loading from env vars and MongoDB token storage for OAuth sessions.
Part A: Config (internal/config/config.go)
Config struct
type Config struct {
Port string
BaseURL string
GHLClientID string
GHLClientSecret string
GHLWebhookPublicKey string // PEM-encoded ECDSA public key
GHLConversationProviderID string
CastAPIKey string
CastAPIURL string
CastSenderID string
MongoURI string
InboundAPIKey string
}
Load function
func Load() (*Config, error)
- Read all vars from
os.Getenv() - Validate required fields:
BASE_URL,GHL_CLIENT_ID,GHL_CLIENT_SECRET,GHL_WEBHOOK_PUBLIC_KEY,GHL_CONVERSATION_PROVIDER_ID,CAST_API_KEY,MONGO_URI - Defaults:
PORT→"3002",CAST_API_URL→"https://api.cast.ph" - Return descriptive error listing ALL missing vars (not just the first)
Part B: MongoDB Store (internal/store/mongo.go)
TokenRecord struct
type TokenRecord struct {
LocationID string `bson:"location_id"`
CompanyID string `bson:"company_id"`
AccessToken string `bson:"access_token"`
RefreshToken string `bson:"refresh_token"`
ExpiresAt time.Time `bson:"expires_at"`
InstalledAt time.Time `bson:"installed_at"`
UpdatedAt time.Time `bson:"updated_at"`
}
Store struct and methods
type Store struct {
collection *mongo.Collection
}
func NewStore(ctx context.Context, uri string) (*Store, error)
// Connects to MongoDB, returns Store with "oauth_tokens" collection
// Creates unique index on location_id
func (s *Store) SaveToken(ctx context.Context, record *TokenRecord) error
// Upsert by location_id (insert or replace)
func (s *Store) GetToken(ctx context.Context, locationID string) (*TokenRecord, error)
// Find by location_id. Return nil, nil if not found.
func (s *Store) UpdateToken(ctx context.Context, locationID, accessToken, refreshToken string, expiresAt time.Time) error
// Update access_token, refresh_token, expires_at, updated_at for a location
func (s *Store) DeleteToken(ctx context.Context, locationID string) error
// Remove token on app uninstall
func (s *Store) Close(ctx context.Context) error
// Disconnect from MongoDB
Key behaviors
- Use
context.Contexton all operations - Set
updated_attotime.Now()on every write SaveTokenuses MongoDBReplaceOnewithupsert: trueGetTokenreturns(nil, nil)when not found (not an error)- Connection timeout: 10 seconds
- Create index:
{ location_id: 1 }unique
Acceptance Criteria
go build ./cmd/server/succeedsConfig.Load()returns error listing all missing required varsConfig.Load()applies defaults for PORT and CAST_API_URLStore.NewStore()connects to MongoDB with 10s timeoutStore.SaveToken()upserts by location_idStore.GetToken()returns nil when not foundStore.UpdateToken()updates token fields + updated_atStore.DeleteToken()removes the record- Unique index on location_id
- All methods accept context.Context