First commit

This commit is contained in:
2019-05-08 16:38:12 +08:00
commit d504997def
172 changed files with 66423 additions and 0 deletions

389
tapit-backend/auth.go Normal file
View File

@@ -0,0 +1,389 @@
package main
import (
"net/http"
"strings"
"io/ioutil"
"encoding/json"
"github.com/jinzhu/gorm"
"math/rand"
"time"
"golang.org/x/crypto/bcrypt"
)
type UserJson struct {
Username string `json:"username"`
Password string `json:"password"`
Name string `json:"name"`
Email string `json:"email"`
SecretCode string `json:"secretCode"`
}
type User struct {
gorm.Model
Username string
PasswordHash string
Name string
Email string
}
type Session struct {
gorm.Model
SessionID string
UserID uint
}
func (tapit *Tapit) login(w http.ResponseWriter, r *http.Request) {
if strings.ToUpper(r.Method) == "POST" {
// start doing work
requestBody, err:= ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "Bad request", 400)
return
}
userJson := UserJson{}
err = json.Unmarshal(requestBody, &userJson)
if err != nil {
http.Error(w, "Bad request", 400)
return
}
currUser := User{}
tapit.db.Where(&User{Username:userJson.Username}).First(&currUser)
// user exists
if currUser.Username == userJson.Username {
// checking hash...
if checkPasswordHash(currUser.PasswordHash, userJson.Password) {
userJson.Password = ""
userJson.Name = currUser.Name
userJson.Email = currUser.Email
messageOutput := NotificationJson{
Text: "Successfully logged in!",
ResultType: "success",
Payload: userJson,
}
jsonResults, err := json.Marshal(messageOutput)
if err!=nil {
http.Error(w, "Internal server error", 500)
return
}
w.Header().Set("Content-Type", "application/json")
authCookie := tapit.generateCookie(currUser)
http.SetCookie(w, &authCookie)
w.Write(jsonResults)
return
} else {
notifyPopup(w, r, "failure", "Username or password is incorrect", nil)
return
}
} else {
tapit.hashPassword("nothing-to-do-waste-time")
notifyPopup(w, r, "failure", "Username or password is incorrect", nil)
return
}
} else {
http.Error(w, "HTTP method not implemented", 400)
return
}
}
func (tapit *Tapit) register(w http.ResponseWriter, r *http.Request) {
if strings.ToUpper(r.Method) == "POST" {
// start doing work
requestBody, err:= ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "Bad request", 400)
return
}
userJson := UserJson{}
err = json.Unmarshal(requestBody, &userJson)
if err != nil {
http.Error(w, "Bad request", 400)
return
}
// checks if secret code is correct
if userJson.SecretCode != tapit.globalSettings.secretRegistrationCode {
messageOutput := NotificationJson{
Text: "Your secret code is incorrect. Please try again.",
ResultType: "failure",
}
jsonResults, err := json.Marshal(messageOutput)
if err!=nil {
http.Error(w, "Internal server error", 500)
return
}
w.Header().Set("Content-Type", "application/json")
http.Error(w, string(jsonResults), 200)
return
}
//check if user exists
currUser := User{}
tapit.db.Where(&User{Username: userJson.Username}).First(&currUser)
if currUser.Username != "" {
messageOutput := NotificationJson{
Text: "Username exists. Please choose another one.",
ResultType: "failure",
}
jsonResults, err := json.Marshal(messageOutput)
if err!=nil {
http.Error(w, "Internal server error", 500)
return
}
w.Header().Set("Content-Type", "application/json")
http.Error(w, string(jsonResults), 200)
return
}
//input validation that all are filled
if userJson.Username == "" || userJson.Name == "" || userJson.Email == "" || userJson.Password == "" {
messageOutput := NotificationJson{
Text: "Please fill up all the information",
ResultType: "failure",
}
jsonResults, err := json.Marshal(messageOutput)
if err!=nil {
http.Error(w, "Internal server error", 500)
return
}
w.Header().Set("Content-Type", "application/json")
http.Error(w, string(jsonResults), 200)
return
}
// creates user...
currUser.Username = userJson.Username
currUser.Name = userJson.Name
currUser.Email = userJson.Email
currUser.PasswordHash, _ = tapit.hashPassword(userJson.Password)
var jsonResults []byte
if (tapit.db.NewRecord(&currUser)) {
tapit.db.Create(&currUser)
userJson.Password = ""
messageOutput := NotificationJson{
Text: "Successfully registered!",
ResultType: "success",
Payload: userJson,
}
jsonResults, err = json.Marshal(messageOutput)
if err!=nil {
http.Error(w, "Internal server error", 500)
return
}
} else {
http.Error(w, "Internal server error", 500)
return
}
w.Header().Set("Content-Type", "application/json")
authCookie := tapit.generateCookie(currUser)
http.SetCookie(w, &authCookie)
w.Write(jsonResults)
return
} else {
http.Error(w, "HTTP method not implemented", 400)
return
}
}
func (tapit *Tapit) logout(w http.ResponseWriter, r *http.Request) {
if strings.ToUpper(r.Method) == "POST" {
// start doing work
var currSession Session
authCookie, err := r.Cookie("tapitsession")
if err!=nil {
http.Error(w, "Not authorised", 401)
return
}
authCookieStr := authCookie.String()[13:]
tapit.db.Where(&Session{SessionID: authCookieStr}).First(&currSession)
if currSession.SessionID != authCookieStr {
http.Error(w, "Not authorised", 401)
return
} else {
tapit.db.Delete(&currSession)
messageOutput := NotificationJson{
Text: "Successfully logged out",
ResultType: "success",
Payload: "",
}
jsonResults, err := json.Marshal(messageOutput)
if err!=nil {
http.Error(w, "Internal server error", 500)
return
}
delCookie := tapit.deleteCookie()
http.SetCookie(w, &delCookie)
w.Header().Set("Content-Type", "application/json")
w.Write(jsonResults)
}
} else {
http.Error(w, "HTTP method not implemented", 400)
return
}
}
func (tapit *Tapit) authenticationHandler(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var currSession Session
authCookie, err := r.Cookie("tapitsession")
if err!=nil {
http.Error(w, "Not authorised", 401)
return
}
authCookieStr := authCookie.String()[13:]
tapit.db.Where(&Session{SessionID: authCookieStr}).First(&currSession)
if currSession.SessionID != authCookieStr {
http.Error(w, "Not authorised", 401)
return
} else {
next.ServeHTTP(w, r)
return
}
}
}
func (tapit *Tapit) generateCookie(user User) http.Cookie {
newToken := generateToken()
newSession := Session{}
tapit.db.Where(&Session{SessionID: newToken}).First(&newSession)
for newToken == newSession.SessionID {
newToken = generateToken()
tapit.db.Where(&Session{SessionID: newToken}).First(&newSession)
}
newSession.UserID = user.ID
newSession.SessionID = newToken
tapit.db.NewRecord(&newSession)
tapit.db.Create(&newSession)
newCookie := http.Cookie {
Name: "tapitsession",
Value: newToken,
Path: "/",
MaxAge: 60*60*24*365*10,
HttpOnly: true,
}
return newCookie
}
func (tapit *Tapit) deleteCookie() http.Cookie {
newCookie := http.Cookie {
Name: "tapitsession",
Value: "",
Path: "/",
MaxAge: 0,
HttpOnly: true,
}
return newCookie
}
func generateToken() string {
var tokenResult strings.Builder
rand.Seed(time.Now().UnixNano())
var r int
tokenCharset := "abcdefghijklmnopqrstuvwxyz0123456789"
for i:=0; i<16; i++ {
r = rand.Int() % len(tokenCharset)
tokenResult.WriteRune(rune(tokenCharset[r]))
}
return tokenResult.String()
}
func (tapit *Tapit) hashPassword(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), tapit.globalSettings.bcryptCost)
return string(bytes), err
}
func checkPasswordHash(hash string, password string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}
func (tapit *Tapit) myselfHandler(w http.ResponseWriter, r *http.Request) {
if strings.ToUpper(r.Method) == "GET" {
tapit.checkUser(w, r)
} else if strings.ToUpper(r.Method) == "PUT" {
tapit.updateUser(w, r)
} else {
http.Error(w, "HTTP method not implemented", 400)
return
}
}
func (tapit *Tapit) checkUser(w http.ResponseWriter, r *http.Request) {
var currSession Session
authCookie, err := r.Cookie("tapitsession")
if err!=nil {
http.Error(w, "Not authorised", 401)
return
}
authCookieStr := authCookie.String()[13:]
tapit.db.Where(&Session{SessionID: authCookieStr}).First(&currSession)
if currSession.SessionID != authCookieStr {
http.Error(w, "Not authorised", 401)
return
} else {
currUser := User{}
searchUser := User{}
searchUser.ID = currSession.UserID
tapit.db.Where(searchUser).First(&currUser)
currentUserJson := UserJson{}
currentUserJson.Username = currUser.Username
currentUserJson.Name = currUser.Name
currentUserJson.Email = currUser.Email
jsonResults, err := json.Marshal(currentUserJson)
if err != nil {
http.Error(w, err.Error(), 500)
return
} else {
w.Header().Set("Content-Type", "application/json")
w.Write(jsonResults)
return
}
}
}
func (tapit *Tapit) updateUser(w http.ResponseWriter, r *http.Request) {
var currSession Session
authCookie, err := r.Cookie("tapitsession")
if err!=nil {
http.Error(w, "Not authorised", 401)
return
}
authCookieStr := authCookie.String()[13:]
tapit.db.Where(&Session{SessionID: authCookieStr}).First(&currSession)
if currSession.SessionID != authCookieStr {
http.Error(w, "Not authorised", 401)
return
} else {
requestBody, err:= ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "Bad request", 400)
return
}
userJson := UserJson{}
err = json.Unmarshal(requestBody, &userJson)
if err != nil {
http.Error(w, "Bad request", 400)
return
}
currUser := User{}
searchUser := User{}
searchUser.ID = currSession.UserID
tapit.db.Where(searchUser).First(&currUser)
if currUser.ID == currSession.UserID && currUser.Username == userJson.Username {
currUser.Name = userJson.Name
currUser.Email = userJson.Email
currUser.PasswordHash, _ = tapit.hashPassword(userJson.Password)
tapit.db.Save(&currUser)
userJson.Password = ""
// writing output
notifyPopup(w, r, "success", "Successfully changed profile!", userJson)
return
} else {
http.Error(w, "Not authorised", 401)
return
}
}
}

540
tapit-backend/campaign.go Normal file
View File

@@ -0,0 +1,540 @@
package main
import (
"github.com/jinzhu/gorm"
"github.com/gorilla/mux"
"sync"
"time"
"net/http"
"strings"
"encoding/json"
"io/ioutil"
"strconv"
"log"
)
type Campaign struct {
gorm.Model
Name string
FromNumber string
Size int
CurrentStatus string // enum Running, Paused, Completed, Not Started
PhonebookId uint
TextTemplateId uint
WebTemplateId uint
ProviderTag string
Jobs []Job `gorm:"foreignkey:CampaignId"`
}
type CampaignComms struct {
Campaign Campaign
Action string // enum run, stop
}
type JobComms struct {
Job Job
Action string // enum run, stop
}
type CampaignJson struct {
Id uint `json:"id"`
Name string `json:"name"`
FromNumber string `json:"fromNumber"`
Size int `json:"size"`
CurrentStatus string `json:"currentStatus"`
CreateDate time.Time `json:"createDate"`
PhonebookId uint `json:"phoneBookId"`
TextTemplateId uint `json:"textTemplateId"`
WebTemplateId uint `json:"webTemplateId"`
ProviderTag string `json:"providerTag"`
Jobs []JobJson `json:"jobs"`
}
type Job struct {
gorm.Model
CampaignId uint
CurrentStatus string // enum Failed, Queued, Sent, Delivered, Not Started
TimeSent time.Time
ProviderTag string
AccSID string
AuthToken string
BodyText string
FromNum string
ToNum string
ResultStr string
MessageSid string
}
type JobJson struct {
Id uint `json:"id"`
CurrentStatus string `json:"currentStatus"`
TimeSent time.Time `json:"timeSent"`
FromNum string `json:"fromNum"`
ToNum string `json:"toNum"`
}
type TwilioMessageJson struct {
AccountSid string `json:"account_sid"`
ApiVersion string `json:"api_version"`
Body string `json:"body"`
DateCreated string `json:"date_created"`
DateSent string `json:"date_sent"`
DateUpdated string `json:"date_updated"`
Direction string `json:"direction"`
ErrorCode string `json:"error_code"`
ErrorMessage string `json:"error_message"`
From string `json:"from"`
MessagingServiceSid string `json:"messaging_service_sid"`
NumMedia string `json:"num_media"`
NumSegments string `json:"num_segments"`
Price string `json:"price"`
PriceUnit string `json:"price_unit"`
Sid string `json:"sid"`
Status string `json:"status"`
SubResourceUri SubResourceUriJson `json:"subresource_uris"`
To string `json:"to"`
Uri string `json:"uri"`
}
type SubResourceUriJson struct {
Media string `json:"media"`
}
func (tapit *Tapit) handleCampaign(w http.ResponseWriter, r *http.Request) {
if strings.ToUpper(r.Method) == "GET" {
tapit.getCampaigns(w, r)
} else if strings.ToUpper(r.Method) == "POST" {
tapit.createCampaign(w, r)
} else {
http.Error(w, "HTTP method not implemented", 400)
return
}
}
func (tapit *Tapit) getCampaigns(w http.ResponseWriter, r *http.Request) {
var campaigns []Campaign
tapit.db.Find(&campaigns)
jsonResults, err := json.Marshal(campaignsToJson(campaigns))
if err != nil {
http.Error(w, err.Error(), 500)
return
} else {
w.Header().Set("Content-Type", "application/json")
w.Write(jsonResults)
return
}
}
func campaignsToJson(campaigns []Campaign) []CampaignJson {
var results []CampaignJson
for _, currCampaign := range campaigns {
var currJson CampaignJson
currJson.Id = currCampaign.ID
currJson.Name = currCampaign.Name
currJson.Size = currCampaign.Size
currJson.FromNumber = currCampaign.FromNumber
currJson.CurrentStatus = currCampaign.CurrentStatus
currJson.CreateDate = currCampaign.CreatedAt
currJson.PhonebookId = currCampaign.PhonebookId
currJson.TextTemplateId = currCampaign.TextTemplateId
currJson.WebTemplateId = currCampaign.WebTemplateId
currJson.ProviderTag = currCampaign.ProviderTag
results = append(results, currJson)
}
return results
}
func (tapit *Tapit) createCampaign(w http.ResponseWriter, r *http.Request) {
requestBody, err:= ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "Bad request", 400)
return
}
var newCampaignJson CampaignJson
err = json.Unmarshal(requestBody, &newCampaignJson)
if err != nil {
http.Error(w, "Bad request", 400)
return
}
if newCampaignJson.Name != "" {
var newCampaign Campaign
// populate details to be used later
var newRecords []PhoneRecord
var newTextTemplateBody string
var newAccSID string
var newAuthToken string
newRecords = tapit.getSpecificPhonebook(newCampaignJson.PhonebookId).Records
newTextTemplateBody = tapit.getSpecificTextBody(newCampaignJson.TextTemplateId)
if newCampaignJson.ProviderTag == "twilio" {
var twilioProvider TwilioProvider
tapit.db.Last(&twilioProvider)
newAccSID = twilioProvider.AccountSID
newAuthToken = twilioProvider.AuthToken
}
// update static details
newCampaign.Name = newCampaignJson.Name
newCampaign.Size = len(newRecords)
newCampaign.CurrentStatus = "Not Started"
newCampaign.FromNumber = newCampaignJson.FromNumber
newCampaign.PhonebookId = newCampaignJson.PhonebookId
newCampaign.TextTemplateId = newCampaignJson.TextTemplateId
newCampaign.WebTemplateId = newCampaignJson.WebTemplateId
newCampaign.ProviderTag = newCampaignJson.ProviderTag
// update records
for _, record := range newRecords {
var newJob Job
newJob.CurrentStatus = "Not Started"
newJob.ProviderTag = newCampaign.ProviderTag
newJob.AccSID = newAccSID
newJob.AuthToken = newAuthToken
newJob.FromNum = newCampaign.FromNumber
// interpreting records
var newBodyText string
newJob.ToNum = record.PhoneNumber
newBodyText = newTextTemplateBody
newBodyText = strings.Replace(newBodyText, "{firstName}", record.FirstName, -1)
newBodyText = strings.Replace(newBodyText, "{lastName}", record.LastName, -1)
newBodyText = strings.Replace(newBodyText, "{alias}", record.Alias, -1)
newBodyText = strings.Replace(newBodyText, "{phoneNumber}", record.PhoneNumber, -1)
newJob.BodyText = newBodyText
// saving it
newCampaign.Jobs = append(newCampaign.Jobs, newJob)
}
// update database
tapit.db.NewRecord(&newCampaign)
tapit.db.Create(&newCampaign)
if newCampaign.ID == 0 {
notifyPopup(w, r, "failure", "Failed to create campaign", nil)
return
}
newCampaignJson.Id = newCampaign.ID
newCampaignJson.CreateDate = newCampaign.CreatedAt
newCampaignJson.Size = newCampaign.Size
newCampaignJson.CurrentStatus = newCampaign.CurrentStatus
notifyPopup(w, r, "success", "Successfully added new campaign", newCampaignJson)
return
} else {
notifyPopup(w, r, "failure", "Please enter the campaign name", nil)
return
}
}
func (tapit *Tapit) handleSpecificCampaign(w http.ResponseWriter, r *http.Request) {
if strings.ToUpper(r.Method) == "PUT" {
// not implmented -- complexity in changing campaign perimeters
// tapit.updateCampaign(w, r)
http.Error(w, "HTTP method not implemented", 400)
return
} else if strings.ToUpper(r.Method) == "DELETE" {
tapit.deleteCampaign(w,r)
return
} else if strings.ToUpper(r.Method) == "GET" {
tapit.getCampaign(w,r)
return
} else {
http.Error(w, "HTTP method not implemented", 400)
return
}
}
func (tapit *Tapit) getSpecificCampaign(id uint) Campaign {
var campaign Campaign
var jobs []Job
var dbSearchCampaign Campaign
dbSearchCampaign.ID = id
tapit.db.Where(&dbSearchCampaign).First(&campaign)
var dbSearchJob Job
dbSearchJob.CampaignId = id
tapit.db.Where(&dbSearchJob).Find(&jobs)
campaign.Jobs = jobs
return campaign
}
func (tapit *Tapit) getCampaign(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
tempID, err := strconv.Atoi(vars["id"])
if err != nil {
http.Error(w, "Bad request", 400)
return
}
phonebook := tapit.getSpecificCampaign(uint(tempID))
jsonResults, err := json.Marshal(campaignToJson(phonebook))
if err != nil {
http.Error(w, err.Error(), 500)
return
} else {
w.Header().Set("Content-Type", "application/json")
w.Write(jsonResults)
return
}
}
func campaignToJson(campaign Campaign) CampaignJson {
var cJson CampaignJson
cJson.Id = campaign.ID
cJson.Name = campaign.Name
cJson.FromNumber = campaign.FromNumber
cJson.Size = campaign.Size
cJson.CurrentStatus = campaign.CurrentStatus
cJson.PhonebookId = campaign.PhonebookId
cJson.TextTemplateId = campaign.TextTemplateId
cJson.WebTemplateId = campaign.WebTemplateId
cJson.ProviderTag = campaign.ProviderTag
// iterating jobs
for _, job := range campaign.Jobs {
var currJson JobJson
currJson.CurrentStatus = job.CurrentStatus
currJson.TimeSent = job.TimeSent
currJson.FromNum = job.FromNum
currJson.ToNum = job.ToNum
cJson.Jobs = append(cJson.Jobs, currJson)
}
return cJson
}
func (tapit *Tapit) deleteCampaign(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
tempID, err := strconv.Atoi(vars["id"])
if err != nil {
http.Error(w, "Bad request", 400)
return
}
// start working
var campaign Campaign
// get phonebook
var dbSearchCampaign Campaign
dbSearchCampaign.ID = uint(tempID)
tapit.db.Where(&dbSearchCampaign).First(&campaign)
if campaign.ID == uint(tempID) {
// finally delete it
tapit.db.Delete(&campaign)
notifyPopup(w, r, "success", "Successfully deleted campaign", nil)
return
} else {
http.Error(w, "Bad request", 400)
return
}
}
func (tapit *Tapit) handleStartCampaign(w http.ResponseWriter, r *http.Request) {
if strings.ToUpper(r.Method) == "GET" {
tapit.startCampaign(w,r)
return
} else {
http.Error(w, "HTTP method not implemented", 400)
return
}
}
func (tapit *Tapit) handleStopCampaign(w http.ResponseWriter, r *http.Request) {
if strings.ToUpper(r.Method) == "GET" {
tapit.stopCampaign(w,r)
return
} else {
http.Error(w, "HTTP method not implemented", 400)
return
}
}
func (tapit *Tapit) startCampaign(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
tempID, err := strconv.Atoi(vars["id"])
if err != nil {
http.Error(w, "Bad request", 400)
return
}
// start working
var campaign Campaign
campaign = tapit.getSpecificCampaign(uint(tempID))
if campaign.ID == uint(tempID) && campaign.CurrentStatus != "Running" && campaign.CurrentStatus != "Completed" {
// finally start new thread and start working
go tapit.workerCampaign(campaign)
campaign.CurrentStatus = "Running"
tapit.db.Save(&campaign)
jsonResults := campaignToJson(campaign)
if err != nil {
http.Error(w, err.Error(), 500)
return
} else {
notifyPopup(w, r, "success", "Started campaign", jsonResults)
return
}
} else {
http.Error(w, "Bad request", 400)
return
}
}
func (tapit *Tapit) stopCampaign(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
tempID, err := strconv.Atoi(vars["id"])
if err != nil {
http.Error(w, "Bad request", 400)
return
}
// start working
var campaign Campaign
campaign = tapit.getSpecificCampaign(uint(tempID))
if campaign.ID == uint(tempID) && campaign.CurrentStatus == "Running" {
var campaignComms CampaignComms
campaignComms.Action = "stop"
campaignComms.Campaign = campaign
tapit.campaignChan <- campaignComms
// notify
notifyPopup(w, r, "success", "Paused campaign", nil)
return
} else {
http.Error(w, "Bad request", 400)
return
}
}
func (tapit *Tapit) workerCampaign(campaign Campaign) {
var campaignComms CampaignComms
var jobChan chan JobComms
var wg sync.WaitGroup
jobChan = make(chan JobComms, 1)
for i:=0; i<tapit.globalSettings.threadsPerCampaign; i++ {
wg.Add(1)
go tapit.workerJob(jobChan, &wg)
}
for _, job := range campaign.Jobs {
select {
case campaignComms = <-tapit.campaignChan:
if campaignComms.Campaign.ID == campaign.ID {
if campaignComms.Action == "stop" {
// kill all
for i:=0; i<tapit.globalSettings.threadsPerCampaign; i++ {
var stopComms JobComms
stopComms.Action = "stop"
jobChan <- stopComms
}
// wait to end
wg.Wait()
// get updated campaign
var newCampaign Campaign
var searchCampaign Campaign
searchCampaign.ID = campaign.ID
tapit.db.Where(&searchCampaign).First(&newCampaign)
// update campaign
newCampaign.CurrentStatus = "Paused"
tapit.db.Save(&newCampaign)
return
}
} else {
// not mine -- throw it back
tapit.campaignChan<- campaignComms
}
default:
if job.CurrentStatus == "Not Started" {
var workComms JobComms
workComms.Action = "run"
workComms.Job = job
jobChan <- workComms
}
}
}
for i:=0; i<tapit.globalSettings.threadsPerCampaign; i++ {
var stopComms JobComms
stopComms.Action = "stop"
jobChan <- stopComms
}
// wait to end
wg.Wait()
// get updated campaign
var newCampaign Campaign
var searchCampaign Campaign
searchCampaign.ID = campaign.ID
tapit.db.Where(&searchCampaign).First(&newCampaign)
// update campaign
newCampaign.CurrentStatus = "Completed"
tapit.db.Save(&newCampaign)
}
func (tapit *Tapit) workerJob(jobChan chan JobComms, wg *sync.WaitGroup) {
var currentJob JobComms
exitCode := false
for !exitCode {
currentJob = <-jobChan
if currentJob.Action != "stop" {
if currentJob.Job.ProviderTag == "twilio" {
var resultJson []byte
resultJson = tapit.twilioSend(currentJob.Job.AccSID, currentJob.Job.AuthToken, currentJob.Job.BodyText, currentJob.Job.FromNum, currentJob.Job.ToNum)
currentJob.Job.ResultStr = string(resultJson)
var twilioResult TwilioMessageJson
err := json.Unmarshal(resultJson, &twilioResult)
if err != nil {
log.Println(err)
currentJob.Job.CurrentStatus = "Failed"
} else if twilioResult.Status == "queued" {
currentJob.Job.MessageSid = twilioResult.Sid
currentJob.Job.CurrentStatus = "Queued"
} else if twilioResult.Status == "delivered" {
currentJob.Job.MessageSid = twilioResult.Sid
currentJob.Job.CurrentStatus = "Delivered"
} else {
currentJob.Job.CurrentStatus = "Failed"
}
// redo until done
tapit.db.Save(&currentJob.Job)
}
} else {
exitCode = true
}
}
wg.Done()
}
func (tapit *Tapit) clearRunningCampaigns() {
var campaigns []Campaign
var searchCampaign Campaign
searchCampaign.CurrentStatus = "Running"
tapit.db.Where(&searchCampaign).Find(&campaigns)
for _, campaign := range campaigns {
campaign.CurrentStatus = "Paused"
tapit.db.Save(&campaign)
}
}

143
tapit-backend/main.go Normal file
View File

@@ -0,0 +1,143 @@
package main
import (
"github.com/jinzhu/gorm"
_ "github.com/jinzhu/gorm/dialects/postgres"
"log"
"github.com/gorilla/mux"
"io/ioutil"
"net/http"
"os"
"path/filepath"
)
type Tapit struct {
db *gorm.DB
globalSettings GlobalSettings
campaignChan chan CampaignComms
}
type GlobalSettings struct {
secretRegistrationCode string
threadsPerCampaign int
bcryptCost int
maxRequestRetries int
waitBeforeRetry int
}
func generateFileHandler(path string) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
r.Header.Add("Cache-Control", "private, max-age=604800") // 7 days
//r.Header.Add("Cache-Control", "private, max-age=1") // 1 sec -- debug
http.ServeFile(w, r, path)
}
}
func iterateStatic(r *mux.Router, path string, startWebPath string) {
files, err := ioutil.ReadDir(path)
if err!=nil {
log.Fatal(err)
}
for _, f := range files {
if !f.IsDir() && f.Name()[0] != '.' {
r.HandleFunc(startWebPath + f.Name(), generateFileHandler(path+"/"+f.Name()))
log.Println(startWebPath + f.Name()+" added to path")
} else if f.IsDir() && f.Name()[0] != '.' {
iterateStatic(r, path + "/" + string(f.Name()), startWebPath + string(f.Name() + "/"))
}
}
}
func generateRoutes(r *mux.Router, indexPath string, routes []string) {
for _, route := range routes {
r.HandleFunc(route, generateFileHandler(indexPath))
log.Println(route+" added as route")
}
}
func main() {
// Setting up DB
host := "postgres-tapit"
db, err := gorm.Open("postgres", "sslmode=disable host=" + host + " port=5432 user=tapit dbname=tapit password=secret-tapit-password")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// DB Migrations
db.AutoMigrate(&Session{})
db.AutoMigrate(&User{})
db.AutoMigrate(&TextTemplate{})
db.AutoMigrate(&TwilioProvider{})
db.AutoMigrate(&Phonebook{})
db.AutoMigrate(&PhoneRecord{})
db.AutoMigrate(&Campaign{})
db.AutoMigrate(&Job{})
// Setting up Tapit app
var tapit Tapit
tapit.db = db
tapit.globalSettings.secretRegistrationCode = "Super-Secret-Code"
tapit.globalSettings.threadsPerCampaign = 2
tapit.globalSettings.bcryptCost = 12
tapit.globalSettings.maxRequestRetries = 5
tapit.globalSettings.waitBeforeRetry = 1000
// Clear running campaigns & starting background jobs
tapit.clearRunningCampaigns()
go tapit.workerTwilioChecker()
tapit.campaignChan = make(chan CampaignComms, 10)
// Setting up mux
r := mux.NewRouter()
// Get current dir
dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
if err != nil {
log.Fatal(err)
}
// Setting up static routes (frontend)
iterateStatic(r, dir + "/static/", "/")
routes := []string{
"/",
"/login",
"/register",
"/profile",
"/campaign",
"/campaign/new",
"/campaign/{id}/view",
"/phonebook",
"/phonebook/new",
"/phonebook/{id}/edit",
"/text-template",
"/text-template/new",
"/text-template/{id}/edit",
"/provider",
}
indexPath := dir + "/static/index.html"
generateRoutes(r, indexPath, routes)
// Setting up API routes
r.HandleFunc("/api/login", tapit.login)
r.HandleFunc("/api/logout", tapit.logout)
r.HandleFunc("/api/register", tapit.register)
r.HandleFunc("/api/myself", tapit.authenticationHandler(tapit.myselfHandler))
r.Handle("/api/text-template",tapit.authenticationHandler(tapit.handleTextTemplate))
r.Handle("/api/text-template/{id}",tapit.authenticationHandler(tapit.handleSpecificTextTemplate))
r.Handle("/api/provider/twilio",tapit.authenticationHandler(tapit.handleTwilioProvider))
r.Handle("/api/phonebook",tapit.authenticationHandler(tapit.handlePhonebook))
r.Handle("/api/phonebook/{id}",tapit.authenticationHandler(tapit.handleSpecificPhonebook))
r.Handle("/api/import-phonebook",tapit.authenticationHandler(tapit.importPhonebook))
r.Handle("/api/campaign",tapit.authenticationHandler(tapit.handleCampaign))
r.Handle("/api/campaign/{id}",tapit.authenticationHandler(tapit.handleSpecificCampaign))
r.Handle("/api/campaign/{id}/start",tapit.authenticationHandler(tapit.handleStartCampaign))
r.Handle("/api/campaign/{id}/pause",tapit.authenticationHandler(tapit.handleStopCampaign))
// Starting web server
http.Handle("/", r)
log.Println("Starting web server...")
http.ListenAndServe(":8000", nil)
}

View File

@@ -0,0 +1,30 @@
package main
import (
"encoding/json"
"net/http"
)
type NotificationJson struct {
ResultType string `json:"resultType"` // success/failure/info
Text string `json:"text"`
Payload interface{} `json:"payload"`
}
type Payload interface{}
func notifyPopup(w http.ResponseWriter, r *http.Request, resultType string, text string, payload Payload) {
messageOutput := NotificationJson{
ResultType: resultType,
Text: text,
Payload: payload,
}
jsonResults, err := json.Marshal(messageOutput)
if err!=nil {
http.Error(w, "Internal server error", 500)
return
}
w.Header().Set("Content-Type", "application/json")
http.Error(w, string(jsonResults), 200)
return
}

342
tapit-backend/phonebook.go Normal file
View File

@@ -0,0 +1,342 @@
package main
import (
"github.com/jinzhu/gorm"
"github.com/gorilla/mux"
"github.com/tealeg/xlsx"
"time"
"net/http"
"strings"
"encoding/json"
"io/ioutil"
"strconv"
"log"
"io"
"bytes"
)
type Phonebook struct {
gorm.Model
Name string
Size int
Records []PhoneRecord `gorm:"foreignkey:PhonebookID"`
}
type PhonebookJson struct {
Id uint `json:"id"`
Name string `json:"name"`
Size int `json:"size"`
CreateDate time.Time `json:"createDate"`
Records []PhoneRecordJson `json:"records"`
}
type PhoneRecord struct {
gorm.Model
PhonebookID uint
FirstName string
LastName string
Alias string
PhoneNumber string
}
type PhoneRecordJson struct {
Id uint `json:"id"`
FirstName string `json:"firstName"`
LastName string `json:"lastName"`
Alias string `json:"alias"`
PhoneNumber string `json:"phoneNumber"`
}
func (tapit *Tapit) handlePhonebook(w http.ResponseWriter, r *http.Request) {
if strings.ToUpper(r.Method) == "GET" {
tapit.getPhonebooks(w, r)
} else if strings.ToUpper(r.Method) == "POST" {
tapit.createPhonebook(w, r)
} else {
http.Error(w, "HTTP method not implemented", 400)
return
}
}
func (tapit *Tapit) getPhonebooks(w http.ResponseWriter, r *http.Request) {
var phonebooks []Phonebook
tapit.db.Find(&phonebooks)
jsonResults, err := json.Marshal(phonebooksToJson(phonebooks))
if err != nil {
http.Error(w, err.Error(), 500)
return
} else {
w.Header().Set("Content-Type", "application/json")
w.Write(jsonResults)
return
}
}
func phonebooksToJson(pb []Phonebook) []PhonebookJson {
var pbJson []PhonebookJson
for _, currObj := range pb {
var currPbJson PhonebookJson
currPbJson.Id = currObj.ID
currPbJson.Name = currObj.Name
currPbJson.CreateDate = currObj.CreatedAt
currPbJson.Size = currObj.Size
pbJson = append(pbJson, currPbJson)
}
return pbJson
}
func (tapit *Tapit) createPhonebook(w http.ResponseWriter, r *http.Request) {
requestBody, err:= ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "Bad request", 400)
return
}
var newPhonebookJson PhonebookJson
err = json.Unmarshal(requestBody, &newPhonebookJson)
if err != nil {
http.Error(w, "Bad request", 400)
return
}
if newPhonebookJson.Name != "" {
var newPhonebook Phonebook
// update name & size
newPhonebook.Name = newPhonebookJson.Name
newPhonebook.Size = len(newPhonebookJson.Records)
// update records
for _, record := range newPhonebookJson.Records {
var newRecord PhoneRecord
newRecord.FirstName = record.FirstName
newRecord.LastName = record.LastName
newRecord.Alias = record.Alias
newRecord.PhoneNumber = record.PhoneNumber
newPhonebook.Records = append(newPhonebook.Records, newRecord)
}
// update database
tapit.db.NewRecord(&newPhonebook)
tapit.db.Create(&newPhonebook)
if newPhonebook.ID == 0 {
notifyPopup(w, r, "failure", "Failed to create phonebook", nil)
return
}
newPhonebookJson.Id = newPhonebook.ID
newPhonebookJson.CreateDate = newPhonebook.CreatedAt
newPhonebookJson.Size = newPhonebook.Size
notifyPopup(w, r, "success", "Successfully added new phonebook", newPhonebookJson)
return
} else {
notifyPopup(w, r, "failure", "Please enter the phonebook name", nil)
return
}
}
func (tapit *Tapit) getSpecificPhonebook(id uint) Phonebook {
var phonebook Phonebook
var records []PhoneRecord
var dbPhonebookSearch Phonebook
dbPhonebookSearch.ID = id
tapit.db.Where(&dbPhonebookSearch).First(&phonebook)
var dbSearchPhoneRecord PhoneRecord
dbSearchPhoneRecord.PhonebookID = id
tapit.db.Where(&dbSearchPhoneRecord).Find(&records)
phonebook.Records = records
return phonebook
}
func (tapit *Tapit) getPhonebook(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
tempID, err := strconv.Atoi(vars["id"])
if err != nil {
http.Error(w, "Bad request", 400)
return
}
phonebook := tapit.getSpecificPhonebook(uint(tempID))
jsonResults, err := json.Marshal(phonebookToJson(phonebook))
if err != nil {
http.Error(w, err.Error(), 500)
return
} else {
w.Header().Set("Content-Type", "application/json")
w.Write(jsonResults)
return
}
}
func phonebookToJson(pb Phonebook) PhonebookJson {
var pbJson PhonebookJson
pbJson.Id = pb.ID
pbJson.Name = pb.Name
pbJson.CreateDate = pb.CreatedAt
pbJson.Size = pb.Size
for _, record := range pb.Records {
var recordJson PhoneRecordJson
recordJson.Id = record.ID
recordJson.FirstName = record.FirstName
recordJson.LastName = record.LastName
recordJson.Alias = record.Alias
recordJson.PhoneNumber = record.PhoneNumber
pbJson.Records = append(pbJson.Records, recordJson)
}
return pbJson
}
func (tapit *Tapit) handleSpecificPhonebook(w http.ResponseWriter, r *http.Request) {
if strings.ToUpper(r.Method) == "PUT" {
tapit.updatePhonebook(w, r)
} else if strings.ToUpper(r.Method) == "DELETE" {
tapit.deletePhonebook(w,r)
} else if strings.ToUpper(r.Method) == "GET" {
tapit.getPhonebook(w,r)
} else {
http.Error(w, "HTTP method not implemented", 400)
return
}
}
func (tapit *Tapit) updatePhonebook(w http.ResponseWriter, r *http.Request) {
requestBody, err:= ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "Bad request", 400)
return
}
var newPhonebookJson PhonebookJson
err = json.Unmarshal(requestBody, &newPhonebookJson)
if err != nil {
http.Error(w, "Bad request", 400)
return
}
if newPhonebookJson.Name != "" {
var newPhonebook Phonebook
// get current phonebook
var dbSearchPhonebook Phonebook
tapit.db.Where(&dbSearchPhonebook).First(&newPhonebook)
// update name & size
newPhonebook.Name = newPhonebookJson.Name
newPhonebook.Size = len(newPhonebookJson.Records)
// update records
for _, record := range newPhonebookJson.Records {
var newRecord PhoneRecord
newRecord.FirstName = record.FirstName
newRecord.LastName = record.LastName
newRecord.Alias = record.Alias
newRecord.PhoneNumber = record.PhoneNumber
newPhonebook.Records = append(newPhonebook.Records, newRecord)
}
// update database
tapit.db.Save(&newPhonebook)
if newPhonebook.ID == 0 {
notifyPopup(w, r, "failure", "Failed to create phonebook", nil)
return
}
newPhonebookJson.Id = newPhonebook.ID
newPhonebookJson.CreateDate = newPhonebook.CreatedAt
newPhonebookJson.Size = newPhonebook.Size
notifyPopup(w, r, "success", "Successfully added new phonebook", newPhonebookJson)
return
} else {
notifyPopup(w, r, "failure", "Please enter the phonebook name", nil)
return
}
}
func (tapit *Tapit) deletePhonebook(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
tempID, err := strconv.Atoi(vars["id"])
if err != nil {
http.Error(w, "Bad request", 400)
return
}
// start working
var phonebook Phonebook
// get phonebook
var dbSearchPhonebook Phonebook
dbSearchPhonebook.ID = uint(tempID)
tapit.db.Where(&dbSearchPhonebook).First(&phonebook)
if phonebook.ID == uint(tempID) {
// finally delete it
tapit.db.Delete(&phonebook)
notifyPopup(w, r, "success", "Successfully deleted phonebook", nil)
return
} else {
http.Error(w, "Bad request", 400)
return
}
}
func (tapit *Tapit) importPhonebook(w http.ResponseWriter, r *http.Request) {
var records []PhoneRecordJson
err := r.ParseForm()
if err != nil {
log.Println(err)
http.Error(w, "Bad request", 400)
return
}
// 100 M reserved
err = r.ParseMultipartForm(100000000)
if err != nil {
log.Println(err)
http.Error(w, "Bad request", 400)
return
}
importFile, _, err := r.FormFile("phonebookFile")
if err != nil {
log.Println(err)
http.Error(w, "Bad request", 400)
return
}
var buff bytes.Buffer
// use buffer to bytes
io.Copy(&buff, importFile)
fileBytes := buff.Bytes()
excelFile, err := xlsx.OpenBinary(fileBytes)
if err != nil {
log.Println(err)
http.Error(w, "Bad request", 400)
return
}
for num, row := range excelFile.Sheet["import"].Rows {
if num != 0 {
var tempRecord PhoneRecordJson
tempRecord.FirstName = row.Cells[0].Value
tempRecord.LastName = row.Cells[1].Value
tempRecord.Alias = row.Cells[2].Value
tempRecord.PhoneNumber = row.Cells[3].Value
records = append(records, tempRecord)
}
}
jsonResults, err := json.Marshal(records)
if err != nil {
http.Error(w, err.Error(), 500)
return
} else {
w.Header().Set("Content-Type", "application/json")
w.Write(jsonResults)
return
}
}

BIN
tapit-backend/tapit-backend Executable file

Binary file not shown.

View File

@@ -0,0 +1,240 @@
package main
import (
"github.com/jinzhu/gorm"
"github.com/gorilla/mux"
"time"
"net/http"
"strings"
"encoding/json"
"io/ioutil"
"strconv"
)
type TextTemplate struct {
gorm.Model
Name string
TemplateStr string
}
type TextTemplateJson struct {
Id int `json:"id"`
Name string `json:"name"`
TemplateStr string `json:"templateStr"`
CreateDate time.Time `json:"createDate"`
}
func (tapit *Tapit) handleTextTemplate(w http.ResponseWriter, r *http.Request) {
if strings.ToUpper(r.Method) == "GET" {
tapit.getTextTemplates(w, r)
} else if strings.ToUpper(r.Method) == "POST" {
tapit.createTextTemplate(w, r)
} else {
http.Error(w, "HTTP method not implemented", 400)
return
}
}
func (tapit *Tapit) getTextTemplates(w http.ResponseWriter, r *http.Request) {
textTemplates := []TextTemplate{}
tapit.db.Find(&textTemplates)
jsonResults, err := json.Marshal(textTemplatesToJson(textTemplates))
if err != nil {
http.Error(w, err.Error(), 500)
return
} else {
w.Header().Set("Content-Type", "application/json")
w.Write(jsonResults)
return
}
}
func textTemplatesToJson(textTemplates []TextTemplate) []TextTemplateJson {
textTemplateJson := make([]TextTemplateJson, 0)
for _, textTemplate := range textTemplates {
var currentTextTemplateJson TextTemplateJson
currentTextTemplateJson.Id = int(textTemplate.ID)
currentTextTemplateJson.Name = textTemplate.Name
currentTextTemplateJson.TemplateStr = textTemplate.TemplateStr
currentTextTemplateJson.CreateDate = textTemplate.CreatedAt
textTemplateJson = append(textTemplateJson, currentTextTemplateJson)
}
return textTemplateJson
}
func jsonToTextTemplate(textTemplateJson TextTemplateJson) TextTemplate {
var resultTextTemplate TextTemplate
resultTextTemplate.Name = textTemplateJson.Name
resultTextTemplate.TemplateStr = textTemplateJson.TemplateStr
return resultTextTemplate
}
func (tapit *Tapit) createTextTemplate(w http.ResponseWriter, r *http.Request) {
// start doing work
requestBody, err:= ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "Bad request", 400)
return
}
newTextTemplateJson := TextTemplateJson{}
err = json.Unmarshal(requestBody, &newTextTemplateJson)
if err != nil {
http.Error(w, "Bad request", 400)
return
}
if newTextTemplateJson.Name != "" && newTextTemplateJson.TemplateStr != "" {
newTextTemplate := jsonToTextTemplate(newTextTemplateJson)
tapit.db.NewRecord(&newTextTemplate)
tapit.db.Create(&newTextTemplate)
if newTextTemplate.ID == 0 {
notifyPopup(w, r, "failure", "Failed to create text template", nil)
return
}
newTextTemplateJson.Id = int(newTextTemplate.ID)
newTextTemplateJson.CreateDate = newTextTemplate.CreatedAt
notifyPopup(w, r, "success", "Successfully added new text template", newTextTemplate)
return
} else {
notifyPopup(w, r, "failure", "Please fill in all details", nil)
return
}
}
func (tapit *Tapit) getSpecificTextBody(id uint) string {
var textTemplate TextTemplate
var dbSearchTT TextTemplate
dbSearchTT.ID = id
tapit.db.Where(&dbSearchTT).First(&textTemplate)
return textTemplate.TemplateStr
}
func (tapit *Tapit) handleSpecificTextTemplate(w http.ResponseWriter, r *http.Request) {
if strings.ToUpper(r.Method) == "PUT" {
tapit.updateTextTemplate(w, r)
} else if strings.ToUpper(r.Method) == "DELETE" {
tapit.deleteTextTemplate(w,r)
} else if strings.ToUpper(r.Method) == "GET" {
tapit.getTextTemplate(w,r)
} else {
http.Error(w, "HTTP method not implemented", 400)
return
}
}
func (tapit *Tapit) updateTextTemplate(w http.ResponseWriter, r *http.Request) {
requestBody, err:= ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "Bad request", 400)
return
}
var newTextTemplateJson TextTemplateJson
err = json.Unmarshal(requestBody, &newTextTemplateJson)
if err != nil {
http.Error(w, "Bad request", 400)
return
}
if newTextTemplateJson.Name != "" {
var newTextTemplate TextTemplate
// get current phonebook
var dbSearchTT TextTemplate
dbSearchTT.ID = uint(newTextTemplateJson.Id)
tapit.db.Where(&dbSearchTT).First(&newTextTemplate)
if newTextTemplate.ID == uint(newTextTemplateJson.Id) {
// update name & template
newTextTemplate.Name = newTextTemplateJson.Name
newTextTemplate.TemplateStr = newTextTemplateJson.TemplateStr
// update database
tapit.db.Save(&newTextTemplate)
if newTextTemplate.ID == 0 {
notifyPopup(w, r, "failure", "Failed to update phonebook", nil)
return
}
newTextTemplateJson.Id = int(newTextTemplate.ID)
newTextTemplateJson.CreateDate = newTextTemplate.CreatedAt
notifyPopup(w, r, "success", "Successfully updated text template", newTextTemplateJson)
return
} else {
notifyPopup(w, r, "failure", "Failed to update text template", nil)
return
}
} else {
notifyPopup(w, r, "failure", "Please enter the phonebook name", nil)
return
}
}
func (tapit *Tapit) deleteTextTemplate(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
tempID, err := strconv.Atoi(vars["id"])
if err != nil {
http.Error(w, "Bad request", 400)
return
}
// start working
var textTemplate TextTemplate
// get tt
var dbSearchTT TextTemplate
dbSearchTT.ID = uint(tempID)
tapit.db.Where(dbSearchTT).First(&textTemplate)
if textTemplate.ID == uint(tempID) {
// finally delete it
tapit.db.Delete(&textTemplate)
notifyPopup(w, r, "success", "Successfully deleted phonebook", nil)
return
} else {
http.Error(w, "Bad request", 400)
return
}
}
func (tapit *Tapit) getTextTemplate(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
tempID, err := strconv.Atoi(vars["id"])
if err != nil {
http.Error(w, "Bad request", 400)
return
}
// start working
var textTemplate TextTemplate
// get tt
var dbSearchTT TextTemplate
dbSearchTT.ID = uint(tempID)
tapit.db.Where(dbSearchTT).First(&textTemplate)
if textTemplate.ID == uint(tempID) {
jsonResults, err := json.Marshal(textTemplateToJson(textTemplate))
if err != nil {
http.Error(w, err.Error(), 500)
return
} else {
w.Header().Set("Content-Type", "application/json")
w.Write(jsonResults)
return
}
} else {
http.Error(w, "Bad request", 400)
return
}
}
func textTemplateToJson(textTemplate TextTemplate) TextTemplateJson {
var result TextTemplateJson
result.Id = int(textTemplate.ID)
result.Name = textTemplate.Name
result.TemplateStr = textTemplate.TemplateStr
result.CreateDate = textTemplate.CreatedAt
return result
}

215
tapit-backend/twilio.go Normal file
View File

@@ -0,0 +1,215 @@
package main
import (
"github.com/jinzhu/gorm"
"net/http"
"net/url"
"strings"
"log"
"encoding/json"
"io/ioutil"
"time"
)
type TwilioProvider struct {
gorm.Model
AccountSID string
AuthToken string
}
type TwilioProviderJson struct {
AccountSID string `json:"accountSID"`
AuthToken string `json:"authToken"`
}
func (tapit *Tapit) handleTwilioProvider(w http.ResponseWriter, r *http.Request) {
if strings.ToUpper(r.Method) == "GET" {
tapit.getTwilioProvider(w, r)
} else if strings.ToUpper(r.Method) == "POST" {
tapit.updateTwilioProvider(w, r)
} else {
http.Error(w, "HTTP method not implemented", 400)
return
}
}
func (tapit *Tapit) getTwilioProvider(w http.ResponseWriter, r *http.Request) {
var twilioProvider TwilioProvider
tapit.db.Last(&twilioProvider)
jsonResults, err := json.Marshal(twilioProviderToJson(twilioProvider))
if err != nil {
http.Error(w, err.Error(), 500)
return
} else {
w.Header().Set("Content-Type", "application/json")
w.Write(jsonResults)
return
}
}
func (tapit *Tapit) updateTwilioProvider(w http.ResponseWriter, r *http.Request) {
requestBody, err:= ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "Bad request", 400)
return
}
var newTwilioProviderJson TwilioProviderJson
err = json.Unmarshal(requestBody, &newTwilioProviderJson)
if err != nil {
http.Error(w, "Bad request", 400)
return
}
// first check if already exist
var twilioProvider TwilioProvider
tapit.db.Last(&twilioProvider)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
// update twilioProvider
twilioProvider.AccountSID = newTwilioProviderJson.AccountSID
twilioProvider.AuthToken = newTwilioProviderJson.AuthToken
// does not exist
if twilioProvider.ID == 0 {
tapit.db.NewRecord(&twilioProvider)
tapit.db.Create(&twilioProvider)
if twilioProvider.ID == 0 {
notifyPopup(w, r, "failure", "Failed to create Twilio Provider", nil)
return
} else {
notifyPopup(w, r, "success", "Twilio provider updated", newTwilioProviderJson)
return
}
} else {
// exists
tapit.db.Save(&twilioProvider)
notifyPopup(w, r, "success", "Twilio provider updated", newTwilioProviderJson)
return
}
}
func twilioProviderToJson(tProvider TwilioProvider) TwilioProviderJson {
var results TwilioProviderJson
results.AccountSID = tProvider.AccountSID
results.AuthToken = tProvider.AuthToken
return results
}
func (tapit *Tapit) twilioSend(accSid string, accToken string, bodyText string, fromNum string, toNum string) []byte {
// if burp proxy is necessary
client := &http.Client{
Timeout: 5 * time.Second,
}
method1 := "POST"
url1 := "https://api.twilio.com/2010-04-01/Accounts/"+accSid+"/Messages.json"
// making body
params := url.Values{}
params.Add("Body", bodyText)
params.Add("From", fromNum)
params.Add("To", toNum)
body1 := strings.NewReader(params.Encode())
log.Println(params.Encode())
// making request
newRequest1, err := http.NewRequest(method1, url1, body1)
if err != nil {
log.Fatal("Error in creating request")
}
//basic auth with token
newRequest1.SetBasicAuth(accSid, accToken)
//set headers
newRequest1.Header.Add("Content-Type","application/x-www-form-urlencoded; charset=UTF-8")
// sending request
res, err := client.Do(newRequest1)
retriesLeft := tapit.globalSettings.maxRequestRetries
for err != nil && retriesLeft > 0 {
log.Println("Error in sending request")
res, err = client.Do(newRequest1)
retriesLeft -= 1
time.Sleep(time.Duration(tapit.globalSettings.waitBeforeRetry) * time.Millisecond)
}
// exit gracefully if can't
if err!= nil {
var emptyBytes []byte
return emptyBytes
}
outputStr, _ := ioutil.ReadAll(res.Body)
log.Println(string(outputStr))
return outputStr
}
func (tapit *Tapit) twilioCheck(accSid string, accToken string, messageSid string) []byte {
client := &http.Client{
Timeout: 5 * time.Second,
}
method1 := "GET"
url1 := "https://api.twilio.com/2010-04-01/Accounts/"+accSid+"/Messages/"+messageSid+".json"
body1 := strings.NewReader("")
newRequest1, err := http.NewRequest(method1, url1, body1)
// authenticate
newRequest1.SetBasicAuth(accSid, accToken)
// sending request
res, err := client.Do(newRequest1)
retriesLeft := tapit.globalSettings.maxRequestRetries
for err != nil && retriesLeft > 0 {
log.Println("Error in sending request")
res, err = client.Do(newRequest1)
retriesLeft -= 1
time.Sleep(time.Duration(tapit.globalSettings.waitBeforeRetry) * time.Millisecond)
}
// exit gracefully if can't
if err!= nil {
var emptyBytes []byte
return emptyBytes
}
outputStr, _ := ioutil.ReadAll(res.Body)
log.Println(string(outputStr))
return outputStr
}
func (tapit *Tapit) workerTwilioChecker() {
// infinite loop to keep checking for queued jobs to check delivery status
for true {
// sleep 5 second per cycle
time.Sleep(5000 * time.Millisecond)
var pendJobs []Job
tapit.db.Where("provider_tag = ? AND (current_status = ? OR current_status = ?)", "twilio", "Queued", "Sent").Find(&pendJobs)
for _, job := range pendJobs {
// sleep 100ms per job
time.Sleep(100 * time.Millisecond)
resultJson := tapit.twilioCheck(job.AccSID, job.AuthToken, job.MessageSid)
job.ResultStr = string(resultJson)
var twilioResult TwilioMessageJson
err := json.Unmarshal(resultJson, &twilioResult)
if err != nil {
log.Println(err)
job.CurrentStatus = "Failed"
} else if twilioResult.Status == "queued" {
job.MessageSid = twilioResult.Sid
job.CurrentStatus = "Queued"
} else if twilioResult.Status == "sent" {
job.MessageSid = twilioResult.Sid
job.CurrentStatus = "Sent"
} else if twilioResult.Status == "delivered" {
job.MessageSid = twilioResult.Sid
job.CurrentStatus = "Delivered"
}
tapit.db.Save(&job)
}
}
}