Skip to content

Commit

Permalink
Merge pull request #2 from nyaruka/no-interfaces
Browse files Browse the repository at this point in the history
remove interfaces, classes in support of db inserts
  • Loading branch information
nicpottier authored May 11, 2017
2 parents f74d45e + cafef13 commit 3ac28a9
Show file tree
Hide file tree
Showing 23 changed files with 594 additions and 319 deletions.
5 changes: 1 addition & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
# Courier

[![Build Status](https://travis-ci.org/nyaruka/courier.svg?branch=master)](https://travis-ci.org/nyaruka/courier)
[![Coverage Status](https://coveralls.io/repos/github/nyaruka/courier/badge.svg?branch=master)](https://coveralls.io/github/nyaruka/courier?branch=master)
# Courier [![Build Status](https://travis-ci.org/nyaruka/courier.svg?branch=master)](https://travis-ci.org/nyaruka/courier) [![Coverage Status](https://coveralls.io/repos/github/nyaruka/courier/badge.svg?branch=master)](https://coveralls.io/github/nyaruka/courier?branch=master) [![Go Report Card](https://goreportcard.com/badge/github.com/nyaruka/courier)](https://goreportcard.com/report/github.com/nyaruka/courier)

Install Courier in your workspace with:

Expand Down
84 changes: 48 additions & 36 deletions channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,30 @@ import (
)

const (
// ConfigAuthToken is our constant key used in channel configs for auth tokens
ConfigAuthToken = "auth_token"
)

// ChannelID is our SQL type for a channel's id
type ChannelID struct {
sql.NullInt64
}

// NilChannelID is our nil value for ChannelIDs
var NilChannelID = ChannelID{sql.NullInt64{Int64: 0, Valid: false}}

// ChannelType is our typing of the two char channel types
type ChannelType string

// ChannelUUID is our typing of a channel's UUID
type ChannelUUID struct {
uuid.UUID
}

// NilChannelUUID is our nil value for channel UUIDs
var NilChannelUUID = ChannelUUID{uuid.Nil}

// NewChannelUUID creates a new ChannelUUID for the passed in string
func NewChannelUUID(u string) (ChannelUUID, error) {
channelUUID, err := uuid.FromString(strings.ToLower(u))
if err != nil {
Expand All @@ -32,13 +45,14 @@ func NewChannelUUID(u string) (ChannelUUID, error) {
return ChannelUUID{channelUUID}, nil
}

type Channel interface {
UUID() ChannelUUID
ChannelType() ChannelType
Address() string
Country() string
GetConfig(string) string
}
// ErrChannelExpired is returned when our cached channel has outlived it's TTL
var ErrChannelExpired = errors.New("channel expired")

// ErrChannelNotFound is returned when we fail to find a channel in the db
var ErrChannelNotFound = errors.New("channel not found")

// ErrChannelWrongType is returned when we find a channel with the set UUID but with a different type
var ErrChannelWrongType = errors.New("channel type wrong")

// ChannelFromUUID will look up the channel with the passed in UUID and channel type.
// It will return an error if the channel does not exist or is not active.
Expand All @@ -50,7 +64,7 @@ type Channel interface {
// it locally if found
// 3) Postgres Lookup, we will lookup the value in our database, caching the result
// both locally and in Redis
func ChannelFromUUID(s *server, channelType ChannelType, uuidStr string) (Channel, error) {
func ChannelFromUUID(s *server, channelType ChannelType, uuidStr string) (*Channel, error) {
channelUUID, err := NewChannelUUID(uuidStr)
if err != nil {
return nil, err
Expand Down Expand Up @@ -88,12 +102,13 @@ func ChannelFromUUID(s *server, channelType ChannelType, uuidStr string) (Channe
return channel, nil
}

const lookupChannelFromUUIDSQL = `SELECT uuid, channel_type, address, country, config
const lookupChannelFromUUIDSQL = `
SELECT org_id, id, uuid, channel_type, address, country, config
FROM channels_channel
WHERE channel_type = $1 AND uuid = $2 AND is_active = true`

// ChannelForUUID attempts to look up the channel with the passed in UUID, returning it
func loadChannelFromDB(s *server, channel *channel, channelType ChannelType, uuid ChannelUUID) error {
func loadChannelFromDB(s *server, channel *Channel, channelType ChannelType, uuid ChannelUUID) error {
// select just the fields we need
err := s.db.Get(channel, lookupChannelFromUUIDSQL, channelType, uuid)

Expand All @@ -115,22 +130,18 @@ func loadChannelFromDB(s *server, channel *channel, channelType ChannelType, uui
}

var cacheMutex sync.RWMutex
var channelCache = make(map[ChannelUUID]*channel)

var ErrChannelExpired = errors.New("channel expired")
var ErrChannelNotFound = errors.New("channel not found")
var ErrChannelWrongType = errors.New("channel type wrong")
var channelCache = make(map[ChannelUUID]*Channel)

// getLocalChannel returns a Channel object for the passed in type and UUID.
func getLocalChannel(channelType ChannelType, uuid ChannelUUID) (*channel, error) {
func getLocalChannel(channelType ChannelType, uuid ChannelUUID) (*Channel, error) {
// first see if the channel exists in our local cache
cacheMutex.RLock()
channel, found := channelCache[uuid]
cacheMutex.RUnlock()

if found {
// if it was found but the type is wrong, that's an error
if channel.ChannelType() != channelType {
if channel.ChannelType != channelType {
return newChannel(channelType, uuid), ErrChannelWrongType
}

Expand All @@ -145,13 +156,13 @@ func getLocalChannel(channelType ChannelType, uuid ChannelUUID) (*channel, error
return newChannel(channelType, uuid), ErrChannelNotFound
}

func cacheLocalChannel(channel *channel) {
func cacheLocalChannel(channel *Channel) {
// set our expiration
channel.expiration = time.Now().Add(localTTL * time.Second)

// first write to our local cache
cacheMutex.Lock()
channelCache[channel.UUID()] = channel
channelCache[channel.UUID] = channel
cacheMutex.Unlock()
}

Expand All @@ -168,35 +179,36 @@ const localTTL = 60
// Channel implementation
//-----------------------------------------------------------------------------

type channel struct {
UUID_ ChannelUUID `db:"uuid" json:"uuid"`
ChannelType_ ChannelType `db:"channel_type" json:"channel_type"`
Address_ string `db:"address" json:"address"`
Country_ string `db:"country" json:"country"`
Config_ string `db:"config" json:"config"`
// Channel is our struct for json and db representations of our channel
type Channel struct {
OrgID OrgID `json:"org_id" db:"org_id"`
ID ChannelID `json:"id" db:"id"`
UUID ChannelUUID `json:"uuid" db:"uuid"`
ChannelType ChannelType `json:"channel_type" db:"channel_type"`
Address string `json:"address" db:"address"`
Country string `json:"country" db:"country"`
Config string `json:"config" db:"config"`

expiration time.Time
config map[string]string
}

func (c *channel) UUID() ChannelUUID { return c.UUID_ }
func (c *channel) ChannelType() ChannelType { return c.ChannelType_ }
func (c *channel) Address() string { return c.Address_ }
func (c *channel) Country() string { return c.Country_ }
func (c *channel) GetConfig(key string) string { return c.config[key] }
// GetConfig returns the value of the passed in config key
func (c *Channel) GetConfig(key string) string { return c.config[key] }

func (c *channel) parseConfig() {
func (c *Channel) parseConfig() {
c.config = make(map[string]string)

if c.Config_ != "" {
err := json.Unmarshal([]byte(c.Config_), &c.config)
if c.Config != "" {
err := json.Unmarshal([]byte(c.Config), &c.config)
if err != nil {
log.Printf("ERROR parsing channel config '%s': %s", c.Config_, err)
log.Printf("ERROR parsing channel config '%s': %s", c.Config, err)
}
}
}

func newChannel(channelType ChannelType, uuid ChannelUUID) *channel {
// Constructor to create a new empty channel
func newChannel(channelType ChannelType, uuid ChannelUUID) *Channel {
config := make(map[string]string)
return &channel{ChannelType_: channelType, UUID_: uuid, config: config}
return &Channel{ChannelType: channelType, UUID: uuid, config: config}
}
100 changes: 100 additions & 0 deletions contact.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package courier

import (
"time"

"database/sql"

"github.com/jmoiron/sqlx"
uuid "github.com/satori/go.uuid"
)

// ContactID is our representation of our database contact id
type ContactID struct {
sql.NullInt64
}

// NilContactID represents our nil value for ContactID
var NilContactID = ContactID{sql.NullInt64{Int64: 0, Valid: false}}

// Contact is our struct for a contact in the database
type Contact struct {
Org OrgID `db:"org_id"`
ID ContactID `db:"id"`
UUID string `db:"uuid"`
Name string `db:"name"`

URN ContactURNID `db:"urn_id"`

CreatedOn time.Time `db:"created_on"`
ModifiedOn time.Time `db:"modified_on"`

CreatedBy int `db:"created_by_id"`
ModifiedBy int `db:"modified_by_id"`
}

const lookupContactFromURNSQL = `
SELECT c.org_id, c.id, c.uuid, c.name, u.id as "urn_id"
FROM contacts_contact AS c, contacts_contacturn AS u
WHERE u.urn = $1 AND u.contact_id = c.id AND u.org_id = $2 AND c.is_active = TRUE AND c.is_test = FALSE
`

const insertContactSQL = `
INSERT INTO contacts_contact(org_id, is_active, is_blocked, is_test, is_stopped, uuid, created_on, modified_on, created_by_id, modified_by_id, name)
VALUES(:org_id, TRUE, FALSE, FALSE, FALSE, :uuid, :created_on, :modified_on, :created_by_id, :modified_by_id, :name)
RETURNING id
`

// InsertContact inserts the passed in contact, the id field will be populated with the result on success
func InsertContact(db *sqlx.DB, contact *Contact) error {
rows, err := db.NamedQuery(insertContactSQL, contact)
if err != nil {
return err
}
if rows.Next() {
rows.Scan(&contact.ID)
}
return err
}

// ContactForURN first tries to look up a contact for the passed in URN, if not finding one then creating one
func ContactForURN(db *sqlx.DB, org OrgID, channel ChannelID, urn URN, name string) (*Contact, error) {
// try to look up our contact by URN
var contact Contact
err := db.Get(&contact, lookupContactFromURNSQL, urn, org)
if err != nil && err != sql.ErrNoRows {
return nil, err
}

// we found it, return it
if err != sql.ErrNoRows {
return &contact, nil
}

// didn't find it, we need to create it instead
contact.Org = org
contact.UUID = uuid.NewV4().String()
contact.Name = name

// TODO: Set these to a system user
contact.CreatedBy = 1
contact.ModifiedBy = 1

// Insert it
err = InsertContact(db, &contact)
if err != nil {
return nil, err
}

// now find our URN
contactURN, err := ContactURNForURN(db, org, channel, contact.ID, urn)
if err != nil {
return nil, err
}

// save this URN on our contact
contact.URN = contactURN.ID

// and return it
return &contact, err
}
11 changes: 6 additions & 5 deletions handlers/africastalking/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ type handler struct {
handlers.BaseHandler
}

func NewHandler() *handler {
// NewHandler returns a new Africa's Talking handler
func NewHandler() courier.ChannelHandler {
return &handler{handlers.NewBaseHandler(courier.ChannelType("AT"), "Africas Talking")}
}

Expand Down Expand Up @@ -55,7 +56,7 @@ func (h *handler) Initialize(s courier.Server) error {
}

// ReceiveMessage is our HTTP handler function for incoming messages
func (h *handler) ReceiveMessage(channel courier.Channel, w http.ResponseWriter, r *http.Request) error {
func (h *handler) ReceiveMessage(channel *courier.Channel, w http.ResponseWriter, r *http.Request) error {
// get our params
atMsg := &messageRequest{}
err := handlers.DecodeAndValidateForm(atMsg, r)
Expand All @@ -71,10 +72,10 @@ func (h *handler) ReceiveMessage(channel courier.Channel, w http.ResponseWriter,
}

// create our URN
urn := courier.NewTelURN(atMsg.From, channel.Country())
urn := courier.NewTelURN(atMsg.From, channel.Country)

// build our msg
msg := courier.NewMsg(channel, urn, atMsg.Text).WithExternalID(atMsg.ID).WithDate(date)
msg := courier.NewMsg(channel, urn, atMsg.Text).WithExternalID(atMsg.ID).WithReceivedOn(date)
defer msg.Release()

// and finally queue our message
Expand All @@ -87,7 +88,7 @@ func (h *handler) ReceiveMessage(channel courier.Channel, w http.ResponseWriter,
}

// StatusMessage is our HTTP handler function for status updates
func (h *handler) StatusMessage(channel courier.Channel, w http.ResponseWriter, r *http.Request) error {
func (h *handler) StatusMessage(channel *courier.Channel, w http.ResponseWriter, r *http.Request) error {
// get our params
atStatus := &statusRequest{}
err := handlers.DecodeAndValidateForm(atStatus, r)
Expand Down
2 changes: 1 addition & 1 deletion handlers/africastalking/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
. "github.com/nyaruka/courier/handlers"
)

var testChannels = []courier.Channel{
var testChannels = []*courier.Channel{
courier.NewMockChannel("8eb23e93-5ecb-45ba-b726-3b064e0c56ab", "AT", "2020", "US", nil),
}

Expand Down
Loading

0 comments on commit 3ac28a9

Please sign in to comment.