Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

remove interfaces, classes in support of db inserts #2

Merged
merged 7 commits into from
May 11, 2017
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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