First commit
This commit is contained in:
47
.gitignore
vendored
Normal file
47
.gitignore
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# compiled output
|
||||
/tapit-frontend/dist
|
||||
/tapit-frontend/tmp
|
||||
/tapit-frontend/out-tsc
|
||||
|
||||
# postgres data
|
||||
/postgres-data
|
||||
|
||||
# dependencies
|
||||
/tapit-frontend/node_modules
|
||||
|
||||
# profiling files
|
||||
chrome-profiler-events.json
|
||||
speed-measure-plugin.json
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# IDE - VSCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.history/*
|
||||
|
||||
# misc
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage
|
||||
/libpeerconnection.log
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# System Files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
14
build.sh
Executable file
14
build.sh
Executable file
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
|
||||
cd ./tapit-backend
|
||||
go build
|
||||
cd ..
|
||||
|
||||
cd ./tapit-frontend
|
||||
ng build --optimization
|
||||
cd ..
|
||||
|
||||
cp -r ./tapit-frontend/dist/tapit-frontend/* ./tapit-build/static/
|
||||
cp ./tapit-backend/tapit-backend ./tapit-build/tapit
|
||||
|
||||
./tapit-build/tapit
|
||||
26
docker-compose.yml
Normal file
26
docker-compose.yml
Normal file
@@ -0,0 +1,26 @@
|
||||
version: '3'
|
||||
services:
|
||||
#######################################
|
||||
# TapIt Application
|
||||
#######################################
|
||||
app:
|
||||
build:
|
||||
context: ./tapit-build/
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "127.0.0.1:8000:8000"
|
||||
restart: always
|
||||
|
||||
#######################################
|
||||
# Postgres server
|
||||
#######################################
|
||||
postgres-tapit:
|
||||
image: postgres
|
||||
restart: always
|
||||
volumes:
|
||||
- ./postgres-data:/var/lib/postgresql/data
|
||||
environment:
|
||||
- POSTGRES_USER=tapit
|
||||
- POSTGRES_PASSWORD=secret-tapit-password
|
||||
- POSTGRES_DB=tapit
|
||||
|
||||
BIN
phonebook-template.xlsx
Normal file
BIN
phonebook-template.xlsx
Normal file
Binary file not shown.
4
psql-start.sh
Normal file
4
psql-start.sh
Normal file
@@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
sudo docker rm postgres --force
|
||||
sudo docker run -d -e POSTGRES_USER="tapit" -e POSTGRES_PASSWORD="secret-tapit-password" -e POSTGRES_DB="tapit" --name postgres postgres
|
||||
389
tapit-backend/auth.go
Normal file
389
tapit-backend/auth.go
Normal 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
540
tapit-backend/campaign.go
Normal 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(¤tJob.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
143
tapit-backend/main.go
Normal 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)
|
||||
}
|
||||
30
tapit-backend/notification.go
Normal file
30
tapit-backend/notification.go
Normal 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
342
tapit-backend/phonebook.go
Normal 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
BIN
tapit-backend/tapit-backend
Executable file
Binary file not shown.
240
tapit-backend/text-template.go
Normal file
240
tapit-backend/text-template.go
Normal 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
215
tapit-backend/twilio.go
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
18
tapit-build/Dockerfile
Normal file
18
tapit-build/Dockerfile
Normal file
@@ -0,0 +1,18 @@
|
||||
FROM ubuntu
|
||||
|
||||
ENV PROVISION_CONTEXT "production"
|
||||
|
||||
# Deploy scripts/configurations
|
||||
COPY static/ /static
|
||||
COPY tapit /tapit
|
||||
COPY api-poc /api-poc
|
||||
COPY entrypoint.sh /entrypoint.sh
|
||||
|
||||
run apt-get update
|
||||
run apt-get install -y ca-certificates
|
||||
|
||||
# Harder to bypass
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
|
||||
# Can be overwritten -- with run args
|
||||
CMD ["/entrypoint.sh"]
|
||||
3
tapit-build/entrypoint.sh
Executable file
3
tapit-build/entrypoint.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
#! /bin/bash
|
||||
sleep 5
|
||||
/tapit
|
||||
3719
tapit-build/static/assets/bootstrap-4.3.1-dist/css/bootstrap-grid.css
vendored
Normal file
3719
tapit-build/static/assets/bootstrap-4.3.1-dist/css/bootstrap-grid.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
7
tapit-build/static/assets/bootstrap-4.3.1-dist/css/bootstrap-grid.min.css
vendored
Normal file
7
tapit-build/static/assets/bootstrap-4.3.1-dist/css/bootstrap-grid.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
331
tapit-build/static/assets/bootstrap-4.3.1-dist/css/bootstrap-reboot.css
vendored
Normal file
331
tapit-build/static/assets/bootstrap-4.3.1-dist/css/bootstrap-reboot.css
vendored
Normal file
@@ -0,0 +1,331 @@
|
||||
/*!
|
||||
* Bootstrap Reboot v4.3.1 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2019 The Bootstrap Authors
|
||||
* Copyright 2011-2019 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
|
||||
*/
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
font-family: sans-serif;
|
||||
line-height: 1.15;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
color: #212529;
|
||||
text-align: left;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
[tabindex="-1"]:focus {
|
||||
outline: 0 !important;
|
||||
}
|
||||
|
||||
hr {
|
||||
box-sizing: content-box;
|
||||
height: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
abbr[title],
|
||||
abbr[data-original-title] {
|
||||
text-decoration: underline;
|
||||
-webkit-text-decoration: underline dotted;
|
||||
text-decoration: underline dotted;
|
||||
cursor: help;
|
||||
border-bottom: 0;
|
||||
-webkit-text-decoration-skip-ink: none;
|
||||
text-decoration-skip-ink: none;
|
||||
}
|
||||
|
||||
address {
|
||||
margin-bottom: 1rem;
|
||||
font-style: normal;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
dl {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
ol ol,
|
||||
ul ul,
|
||||
ol ul,
|
||||
ul ol {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-bottom: .5rem;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
sub,
|
||||
sup {
|
||||
position: relative;
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -.5em;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #007bff;
|
||||
text-decoration: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #0056b3;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:not([href]):not([tabindex]) {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:not([href]):not([tabindex]):hover, a:not([href]):not([tabindex]):focus {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:not([href]):not([tabindex]):focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
pre,
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
img {
|
||||
vertical-align: middle;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
svg {
|
||||
overflow: hidden;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
caption {
|
||||
padding-top: 0.75rem;
|
||||
padding-bottom: 0.75rem;
|
||||
color: #6c757d;
|
||||
text-align: left;
|
||||
caption-side: bottom;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: inherit;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
button:focus {
|
||||
outline: 1px dotted;
|
||||
outline: 5px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
select,
|
||||
optgroup,
|
||||
textarea {
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
button,
|
||||
input {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
select {
|
||||
word-wrap: normal;
|
||||
}
|
||||
|
||||
button,
|
||||
[type="button"],
|
||||
[type="reset"],
|
||||
[type="submit"] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
button:not(:disabled),
|
||||
[type="button"]:not(:disabled),
|
||||
[type="reset"]:not(:disabled),
|
||||
[type="submit"]:not(:disabled) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button::-moz-focus-inner,
|
||||
[type="button"]::-moz-focus-inner,
|
||||
[type="reset"]::-moz-focus-inner,
|
||||
[type="submit"]::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
input[type="radio"],
|
||||
input[type="checkbox"] {
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
input[type="date"],
|
||||
input[type="time"],
|
||||
input[type="datetime-local"],
|
||||
input[type="month"] {
|
||||
-webkit-appearance: listbox;
|
||||
}
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
display: block;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
padding: 0;
|
||||
margin-bottom: .5rem;
|
||||
font-size: 1.5rem;
|
||||
line-height: inherit;
|
||||
color: inherit;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
progress {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
[type="number"]::-webkit-inner-spin-button,
|
||||
[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
[type="search"] {
|
||||
outline-offset: -2px;
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
font: inherit;
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
output {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
/*# sourceMappingURL=bootstrap-reboot.css.map */
|
||||
File diff suppressed because one or more lines are too long
8
tapit-build/static/assets/bootstrap-4.3.1-dist/css/bootstrap-reboot.min.css
vendored
Normal file
8
tapit-build/static/assets/bootstrap-4.3.1-dist/css/bootstrap-reboot.min.css
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
/*!
|
||||
* Bootstrap Reboot v4.3.1 (https://getbootstrap.com/)
|
||||
* Copyright 2011-2019 The Bootstrap Authors
|
||||
* Copyright 2011-2019 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
|
||||
*/*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=date],input[type=datetime-local],input[type=month],input[type=time]{-webkit-appearance:listbox}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}
|
||||
/*# sourceMappingURL=bootstrap-reboot.min.css.map */
|
||||
File diff suppressed because one or more lines are too long
10038
tapit-build/static/assets/bootstrap-4.3.1-dist/css/bootstrap.css
vendored
Normal file
10038
tapit-build/static/assets/bootstrap-4.3.1-dist/css/bootstrap.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
7
tapit-build/static/assets/bootstrap-4.3.1-dist/css/bootstrap.min.css
vendored
Normal file
7
tapit-build/static/assets/bootstrap-4.3.1-dist/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
7013
tapit-build/static/assets/bootstrap-4.3.1-dist/js/bootstrap.bundle.js
vendored
Normal file
7013
tapit-build/static/assets/bootstrap-4.3.1-dist/js/bootstrap.bundle.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
7
tapit-build/static/assets/bootstrap-4.3.1-dist/js/bootstrap.bundle.min.js
vendored
Normal file
7
tapit-build/static/assets/bootstrap-4.3.1-dist/js/bootstrap.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
4435
tapit-build/static/assets/bootstrap-4.3.1-dist/js/bootstrap.js
vendored
Normal file
4435
tapit-build/static/assets/bootstrap-4.3.1-dist/js/bootstrap.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
7
tapit-build/static/assets/bootstrap-4.3.1-dist/js/bootstrap.min.js
vendored
Normal file
7
tapit-build/static/assets/bootstrap-4.3.1-dist/js/bootstrap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
2
tapit-build/static/assets/jquery-3.4.1.min.js
vendored
Normal file
2
tapit-build/static/assets/jquery-3.4.1.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
tapit-build/static/assets/phonebook-template.xlsx
Normal file
BIN
tapit-build/static/assets/phonebook-template.xlsx
Normal file
Binary file not shown.
5
tapit-build/static/assets/popper.min.js
vendored
Normal file
5
tapit-build/static/assets/popper.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
tapit-build/static/es2015-polyfills.js
Normal file
2
tapit-build/static/es2015-polyfills.js
Normal file
File diff suppressed because one or more lines are too long
1
tapit-build/static/es2015-polyfills.js.map
Normal file
1
tapit-build/static/es2015-polyfills.js.map
Normal file
File diff suppressed because one or more lines are too long
BIN
tapit-build/static/favicon.ico
Normal file
BIN
tapit-build/static/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
21
tapit-build/static/index.html
Normal file
21
tapit-build/static/index.html
Normal file
@@ -0,0 +1,21 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Start Bootstrap - Possible to change to static links-->
|
||||
<script src="/assets/jquery-3.4.1.min.js"></script>
|
||||
<script src="/assets/popper.min.js"></script>
|
||||
<link rel="stylesheet" href="/assets/bootstrap-4.3.1-dist/css/bootstrap.min.css">
|
||||
<script src="/assets/bootstrap-4.3.1-dist/js/bootstrap.min.js"></script>
|
||||
<!-- End Bootstrap -->
|
||||
|
||||
<meta charset="utf-8">
|
||||
<title>Tap It - Text Phishing Framework</title>
|
||||
<base href="/">
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
<script type="text/javascript" src="runtime.js"></script><script type="text/javascript" src="es2015-polyfills.js" nomodule></script><script type="text/javascript" src="polyfills.js"></script><script type="text/javascript" src="styles.js"></script><script type="text/javascript" src="vendor.js"></script><script type="text/javascript" src="main.js"></script></body>
|
||||
</html>
|
||||
2
tapit-build/static/main.js
Normal file
2
tapit-build/static/main.js
Normal file
File diff suppressed because one or more lines are too long
1
tapit-build/static/main.js.map
Normal file
1
tapit-build/static/main.js.map
Normal file
File diff suppressed because one or more lines are too long
2
tapit-build/static/polyfills.js
Normal file
2
tapit-build/static/polyfills.js
Normal file
File diff suppressed because one or more lines are too long
1
tapit-build/static/polyfills.js.map
Normal file
1
tapit-build/static/polyfills.js.map
Normal file
File diff suppressed because one or more lines are too long
2
tapit-build/static/runtime.js
Normal file
2
tapit-build/static/runtime.js
Normal file
@@ -0,0 +1,2 @@
|
||||
!function(e){function r(r){for(var n,f,i=r[0],l=r[1],a=r[2],c=0,s=[];c<i.length;c++)f=i[c],o[f]&&s.push(o[f][0]),o[f]=0;for(n in l)Object.prototype.hasOwnProperty.call(l,n)&&(e[n]=l[n]);for(p&&p(r);s.length;)s.shift()();return u.push.apply(u,a||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++){var l=t[i];0!==o[l]&&(n=!1)}n&&(u.splice(r--,1),e=f(f.s=t[0]))}return e}var n={},o={0:0},u=[];function f(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,f),t.l=!0,t.exports}f.m=e,f.c=n,f.d=function(e,r,t){f.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},f.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.t=function(e,r){if(1&r&&(e=f(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(f.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)f.d(t,n,(function(r){return e[r]}).bind(null,n));return t},f.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return f.d(r,"a",r),r},f.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},f.p="";var i=window.webpackJsonp=window.webpackJsonp||[],l=i.push.bind(i);i.push=r,i=i.slice();for(var a=0;a<i.length;a++)r(i[a]);var p=l;t()}([]);
|
||||
//# sourceMappingURL=runtime.js.map
|
||||
1
tapit-build/static/runtime.js.map
Normal file
1
tapit-build/static/runtime.js.map
Normal file
File diff suppressed because one or more lines are too long
2
tapit-build/static/styles.js
Normal file
2
tapit-build/static/styles.js
Normal file
File diff suppressed because one or more lines are too long
1
tapit-build/static/styles.js.map
Normal file
1
tapit-build/static/styles.js.map
Normal file
File diff suppressed because one or more lines are too long
2
tapit-build/static/vendor.js
Normal file
2
tapit-build/static/vendor.js
Normal file
File diff suppressed because one or more lines are too long
1
tapit-build/static/vendor.js.map
Normal file
1
tapit-build/static/vendor.js.map
Normal file
File diff suppressed because one or more lines are too long
BIN
tapit-build/tapit
Executable file
BIN
tapit-build/tapit
Executable file
Binary file not shown.
13
tapit-frontend/.editorconfig
Normal file
13
tapit-frontend/.editorconfig
Normal file
@@ -0,0 +1,13 @@
|
||||
# Editor configuration, see https://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
trim_trailing_whitespace = false
|
||||
27
tapit-frontend/README.md
Normal file
27
tapit-frontend/README.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# TapitFrontend
|
||||
|
||||
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 7.3.1.
|
||||
|
||||
## Development server
|
||||
|
||||
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
|
||||
|
||||
## Code scaffolding
|
||||
|
||||
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
|
||||
|
||||
## Build
|
||||
|
||||
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
||||
|
||||
## Running end-to-end tests
|
||||
|
||||
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
|
||||
|
||||
## Further help
|
||||
|
||||
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
|
||||
136
tapit-frontend/angular.json
Normal file
136
tapit-frontend/angular.json
Normal file
@@ -0,0 +1,136 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"tapit-frontend": {
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"projectType": "application",
|
||||
"prefix": "app",
|
||||
"schematics": {},
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "dist/tapit-frontend",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "src/tsconfig.app.json",
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.css"
|
||||
],
|
||||
"scripts": [],
|
||||
"es5BrowserSupport": true
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"extractCss": true,
|
||||
"namedChunks": false,
|
||||
"aot": true,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true,
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "2mb",
|
||||
"maximumError": "5mb"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"browserTarget": "tapit-frontend:build"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "tapit-frontend:build:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "tapit-frontend:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"main": "src/test.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "src/tsconfig.spec.json",
|
||||
"karmaConfig": "src/karma.conf.js",
|
||||
"styles": [
|
||||
"src/styles.css"
|
||||
],
|
||||
"scripts": [],
|
||||
"assets": [
|
||||
"src/favicon.ico",
|
||||
"src/assets"
|
||||
]
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": [
|
||||
"src/tsconfig.app.json",
|
||||
"src/tsconfig.spec.json"
|
||||
],
|
||||
"exclude": [
|
||||
"**/node_modules/**"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tapit-frontend-e2e": {
|
||||
"root": "e2e/",
|
||||
"projectType": "application",
|
||||
"prefix": "",
|
||||
"architect": {
|
||||
"e2e": {
|
||||
"builder": "@angular-devkit/build-angular:protractor",
|
||||
"options": {
|
||||
"protractorConfig": "e2e/protractor.conf.js",
|
||||
"devServerTarget": "tapit-frontend:serve"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"devServerTarget": "tapit-frontend:serve:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": "e2e/tsconfig.e2e.json",
|
||||
"exclude": [
|
||||
"**/node_modules/**"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultProject": "tapit-frontend"
|
||||
}
|
||||
28
tapit-frontend/e2e/protractor.conf.js
Normal file
28
tapit-frontend/e2e/protractor.conf.js
Normal file
@@ -0,0 +1,28 @@
|
||||
// Protractor configuration file, see link for more information
|
||||
// https://github.com/angular/protractor/blob/master/lib/config.ts
|
||||
|
||||
const { SpecReporter } = require('jasmine-spec-reporter');
|
||||
|
||||
exports.config = {
|
||||
allScriptsTimeout: 11000,
|
||||
specs: [
|
||||
'./src/**/*.e2e-spec.ts'
|
||||
],
|
||||
capabilities: {
|
||||
'browserName': 'chrome'
|
||||
},
|
||||
directConnect: true,
|
||||
baseUrl: 'http://localhost:4200/',
|
||||
framework: 'jasmine',
|
||||
jasmineNodeOpts: {
|
||||
showColors: true,
|
||||
defaultTimeoutInterval: 30000,
|
||||
print: function() {}
|
||||
},
|
||||
onPrepare() {
|
||||
require('ts-node').register({
|
||||
project: require('path').join(__dirname, './tsconfig.e2e.json')
|
||||
});
|
||||
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
|
||||
}
|
||||
};
|
||||
23
tapit-frontend/e2e/src/app.e2e-spec.ts
Normal file
23
tapit-frontend/e2e/src/app.e2e-spec.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { AppPage } from './app.po';
|
||||
import { browser, logging } from 'protractor';
|
||||
|
||||
describe('workspace-project App', () => {
|
||||
let page: AppPage;
|
||||
|
||||
beforeEach(() => {
|
||||
page = new AppPage();
|
||||
});
|
||||
|
||||
it('should display welcome message', () => {
|
||||
page.navigateTo();
|
||||
expect(page.getTitleText()).toEqual('Welcome to tapit-frontend!');
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
// Assert that there are no errors emitted from the browser
|
||||
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
|
||||
expect(logs).not.toContain(jasmine.objectContaining({
|
||||
level: logging.Level.SEVERE,
|
||||
}));
|
||||
});
|
||||
});
|
||||
11
tapit-frontend/e2e/src/app.po.ts
Normal file
11
tapit-frontend/e2e/src/app.po.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { browser, by, element } from 'protractor';
|
||||
|
||||
export class AppPage {
|
||||
navigateTo() {
|
||||
return browser.get(browser.baseUrl) as Promise<any>;
|
||||
}
|
||||
|
||||
getTitleText() {
|
||||
return element(by.css('app-root h1')).getText() as Promise<string>;
|
||||
}
|
||||
}
|
||||
13
tapit-frontend/e2e/tsconfig.e2e.json
Normal file
13
tapit-frontend/e2e/tsconfig.e2e.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../out-tsc/app",
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"types": [
|
||||
"jasmine",
|
||||
"jasminewd2",
|
||||
"node"
|
||||
]
|
||||
}
|
||||
}
|
||||
10514
tapit-frontend/package-lock.json
generated
Normal file
10514
tapit-frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
48
tapit-frontend/package.json
Normal file
48
tapit-frontend/package.json
Normal file
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "tapit-frontend",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
"test": "ng test",
|
||||
"lint": "ng lint",
|
||||
"e2e": "ng e2e"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "~7.2.0",
|
||||
"@angular/common": "~7.2.0",
|
||||
"@angular/compiler": "~7.2.0",
|
||||
"@angular/core": "~7.2.0",
|
||||
"@angular/forms": "~7.2.0",
|
||||
"@angular/platform-browser": "~7.2.0",
|
||||
"@angular/platform-browser-dynamic": "~7.2.0",
|
||||
"@angular/router": "~7.2.0",
|
||||
"core-js": "^2.5.4",
|
||||
"rxjs": "~6.3.3",
|
||||
"tslib": "^1.9.0",
|
||||
"zone.js": "~0.8.26"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "~0.13.0",
|
||||
"@angular/cli": "~7.3.1",
|
||||
"@angular/compiler-cli": "~7.2.0",
|
||||
"@angular/language-service": "~7.2.0",
|
||||
"@types/node": "~8.9.4",
|
||||
"@types/jasmine": "~2.8.8",
|
||||
"@types/jasminewd2": "~2.0.3",
|
||||
"codelyzer": "~4.5.0",
|
||||
"jasmine-core": "~2.99.1",
|
||||
"jasmine-spec-reporter": "~4.2.1",
|
||||
"karma": "~3.1.1",
|
||||
"karma-chrome-launcher": "~2.2.0",
|
||||
"karma-coverage-istanbul-reporter": "~2.0.1",
|
||||
"karma-jasmine": "~1.1.2",
|
||||
"karma-jasmine-html-reporter": "^0.2.2",
|
||||
"protractor": "~5.4.0",
|
||||
"ts-node": "~7.0.0",
|
||||
"tslint": "~5.11.0",
|
||||
"typescript": "~3.2.2"
|
||||
}
|
||||
}
|
||||
37
tapit-frontend/src/app/app-routing.module.ts
Normal file
37
tapit-frontend/src/app/app-routing.module.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
import { MainComponent } from './main/main.component';
|
||||
import { LoginComponent } from './login/login.component';
|
||||
import { RegisterComponent } from './register/register.component';
|
||||
import { CampaignComponent } from './campaign/campaign.component';
|
||||
import { CampaignNewComponent } from './campaign-new/campaign-new.component';
|
||||
import { CampaignViewComponent } from './campaign-view/campaign-view.component';
|
||||
import { PhonebookComponent } from './phonebook/phonebook.component';
|
||||
import { PhonebookNewComponent } from './phonebook-new/phonebook-new.component';
|
||||
import { TextTemplateComponent } from './text-template/text-template.component';
|
||||
import { TextTemplateNewComponent } from './text-template-new/text-template-new.component';
|
||||
import { ProviderComponent } from './provider/provider.component';
|
||||
import { ProfileComponent } from './profile/profile.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: '', component: MainComponent },
|
||||
{ path: 'login', component: LoginComponent },
|
||||
{ path: 'register', component: RegisterComponent },
|
||||
{ path: 'profile', component: ProfileComponent },
|
||||
{ path: 'campaign', component: CampaignComponent },
|
||||
{ path: 'campaign/new', component: CampaignNewComponent },
|
||||
{ path: 'campaign/:id/view', component: CampaignViewComponent },
|
||||
{ path: 'phonebook', component: PhonebookComponent },
|
||||
{ path: 'phonebook/new', component: PhonebookNewComponent },
|
||||
{ path: 'phonebook/:id/edit', component: PhonebookNewComponent },
|
||||
{ path: 'text-template', component: TextTemplateComponent },
|
||||
{ path: 'text-template/new', component: TextTemplateNewComponent },
|
||||
{ path: 'text-template/:id/edit', component: TextTemplateNewComponent },
|
||||
{ path: 'provider', component: ProviderComponent },
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forRoot(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class AppRoutingModule { }
|
||||
0
tapit-frontend/src/app/app.component.css
Normal file
0
tapit-frontend/src/app/app.component.css
Normal file
42
tapit-frontend/src/app/app.component.html
Normal file
42
tapit-frontend/src/app/app.component.html
Normal file
@@ -0,0 +1,42 @@
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<a class="navbar-brand" routerLink="/">Tap It!</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarSupportedContent">
|
||||
<ul class="navbar-nav">
|
||||
<li *ngFor="let navlink of navlinks" data-toggle="collapse" data-target="#navbarNav" class="nav-item">
|
||||
<a class="nav-link" *ngIf="navlink.loginOnly === authService.loggedin" [ngClass]="{'active': router.url === navlink.link}" routerLink="/{{ navlink.link }}">
|
||||
{{ navlink.name }}
|
||||
<span *ngIf="this.router.url === navlink.link" class="sr-only">(current)</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item dropdown" *ngIf="authService.loggedin === true">
|
||||
<a class="nav-link dropdown-toggle" routerLink="{{ router.url }}" id="navbarDropdownMenuLink" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
Settings
|
||||
</a>
|
||||
<div class="dropdown-menu" aria-labelledby="navbarDropdownMenuLink">
|
||||
<a class="dropdown-item" routerLink="/profile">Profile</a>
|
||||
<a class="dropdown-item" routerLink="/provider">Providers</a>
|
||||
<a class="dropdown-item" routerLink="/globalsettings">Global Settings</a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="navbar-nav ml-auto">
|
||||
<li *ngIf="authService.loggedin" data-toggle="collapse" data-target="#navbarNav" class="nav-item">
|
||||
<a class="nav-link" routerLink="/" (click)="authService.logout()">Log Out</a>
|
||||
</li>
|
||||
<li *ngIf="!authService.loggedin" data-toggle="collapse" data-target="#navbarNav" class="nav-item">
|
||||
<a class="nav-link" routerLink="/login">Login</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="container-fluid pt-2">
|
||||
<router-outlet></router-outlet>
|
||||
</main>
|
||||
|
||||
|
||||
<div class="fixed-bottom"><app-notification></app-notification></div>
|
||||
35
tapit-frontend/src/app/app.component.spec.ts
Normal file
35
tapit-frontend/src/app/app.component.spec.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { TestBed, async } from '@angular/core/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
RouterTestingModule
|
||||
],
|
||||
declarations: [
|
||||
AppComponent
|
||||
],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
it('should create the app', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should have as title 'tapit-frontend'`, () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.debugElement.componentInstance;
|
||||
expect(app.title).toEqual('tapit-frontend');
|
||||
});
|
||||
|
||||
it('should render title in a h1 tag', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.debugElement.nativeElement;
|
||||
expect(compiled.querySelector('h1').textContent).toContain('Welcome to tapit-frontend!');
|
||||
});
|
||||
});
|
||||
37
tapit-frontend/src/app/app.component.ts
Normal file
37
tapit-frontend/src/app/app.component.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { RouterModule, Routes, Router } from '@angular/router';
|
||||
import { AuthService } from './auth.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.css']
|
||||
})
|
||||
export class AppComponent {
|
||||
title = 'tapit-frontend';
|
||||
navlinks = [
|
||||
{
|
||||
link: '/campaign',
|
||||
name: 'Campaigns',
|
||||
loginOnly: true,
|
||||
},
|
||||
{
|
||||
link: '/phonebook',
|
||||
name: 'Phonebook',
|
||||
loginOnly: true,
|
||||
},
|
||||
{
|
||||
link: '/text-template',
|
||||
name: 'Text Templates',
|
||||
loginOnly: true,
|
||||
},
|
||||
{
|
||||
link: '/web-template',
|
||||
name: 'Web Templates',
|
||||
loginOnly: true,
|
||||
},
|
||||
];
|
||||
constructor( private router: Router, private authService: AuthService) {
|
||||
authService.getUser();
|
||||
}
|
||||
}
|
||||
48
tapit-frontend/src/app/app.module.ts
Normal file
48
tapit-frontend/src/app/app.module.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
|
||||
import { AppRoutingModule } from './app-routing.module';
|
||||
import { AppComponent } from './app.component';
|
||||
import { MainComponent } from './main/main.component';
|
||||
import { CampaignComponent } from './campaign/campaign.component';
|
||||
import { CampaignNewComponent } from './campaign-new/campaign-new.component';
|
||||
import { NotificationComponent } from './notification/notification.component';
|
||||
import { PhonebookComponent } from './phonebook/phonebook.component';
|
||||
import { PhonebookNewComponent } from './phonebook-new/phonebook-new.component';
|
||||
import { TextTemplateComponent } from './text-template/text-template.component';
|
||||
import { TextTemplateNewComponent } from './text-template-new/text-template-new.component';
|
||||
import { LoginComponent } from './login/login.component';
|
||||
import { RegisterComponent } from './register/register.component';
|
||||
import { ProviderComponent } from './provider/provider.component';
|
||||
import { ProfileComponent } from './profile/profile.component';
|
||||
import { CampaignViewComponent } from './campaign-view/campaign-view.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
AppComponent,
|
||||
MainComponent,
|
||||
CampaignComponent,
|
||||
CampaignNewComponent,
|
||||
NotificationComponent,
|
||||
PhonebookComponent,
|
||||
PhonebookNewComponent,
|
||||
TextTemplateComponent,
|
||||
TextTemplateNewComponent,
|
||||
LoginComponent,
|
||||
RegisterComponent,
|
||||
ProviderComponent,
|
||||
ProfileComponent,
|
||||
CampaignViewComponent
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
AppRoutingModule,
|
||||
FormsModule,
|
||||
HttpClientModule,
|
||||
],
|
||||
providers: [],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule { }
|
||||
12
tapit-frontend/src/app/auth.service.spec.ts
Normal file
12
tapit-frontend/src/app/auth.service.spec.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AuthService } from './auth.service';
|
||||
|
||||
describe('AuthService', () => {
|
||||
beforeEach(() => TestBed.configureTestingModule({}));
|
||||
|
||||
it('should be created', () => {
|
||||
const service: AuthService = TestBed.get(AuthService);
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
131
tapit-frontend/src/app/auth.service.ts
Normal file
131
tapit-frontend/src/app/auth.service.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
import { NotificationService } from './notification.service';
|
||||
import { Observable, of } from 'rxjs';
|
||||
|
||||
export class User {
|
||||
username: string;
|
||||
password: string;
|
||||
name: string;
|
||||
email: string;
|
||||
secretCode: string;
|
||||
}
|
||||
|
||||
export class UserNotification {
|
||||
resultType: string;
|
||||
text: string;
|
||||
payload: User;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AuthService {
|
||||
currUser = new User();
|
||||
loggedin = false;
|
||||
loginUrl = 'api/login';
|
||||
logoutUrl = 'api/logout';
|
||||
registerUrl = 'api/register';
|
||||
myselfUrl = 'api/myself';
|
||||
|
||||
httpOptions = {
|
||||
headers: new HttpHeaders({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
};
|
||||
|
||||
login(username: string, password: string) {
|
||||
this.currUser.username = username;
|
||||
this.currUser.password = password;
|
||||
this.http.post<UserNotification>(this.loginUrl, this.currUser, this.httpOptions).subscribe(usermessage => {
|
||||
if (usermessage.payload !== null) {
|
||||
this.loggedin = true;
|
||||
|
||||
// update user
|
||||
this.currUser.username = usermessage.payload.username;
|
||||
this.currUser.email = usermessage.payload.email;
|
||||
this.currUser.name = usermessage.payload.name;
|
||||
|
||||
this.notificationService.addNotification(usermessage.resultType, usermessage.text);
|
||||
this.router.navigate(['/campaign']);
|
||||
} else {
|
||||
this.notificationService.addNotification(usermessage.resultType, usermessage.text);
|
||||
}
|
||||
},
|
||||
err => {
|
||||
this.notificationService.addNotification('failure', 'Error in logging in');
|
||||
});
|
||||
this.currUser.password = '';
|
||||
}
|
||||
|
||||
register(username: string, password: string, email: string, name: string, secretCode: string) {
|
||||
this.currUser.username = username;
|
||||
this.currUser.password = password;
|
||||
this.currUser.email = email;
|
||||
this.currUser.name = name;
|
||||
this.currUser.secretCode = secretCode;
|
||||
|
||||
this.http.post<UserNotification>(this.registerUrl, this.currUser, this.httpOptions).subscribe(usermessage => {
|
||||
if (usermessage.payload !== null) {
|
||||
this.loggedin = true;
|
||||
this.notificationService.addNotification(usermessage.resultType, usermessage.text);
|
||||
this.router.navigate(['/campaign']);
|
||||
|
||||
// update user
|
||||
this.currUser.username = usermessage.payload.username;
|
||||
this.currUser.email = usermessage.payload.email;
|
||||
this.currUser.name = usermessage.payload.name;
|
||||
} else {
|
||||
this.notificationService.addNotification(usermessage.resultType, usermessage.text);
|
||||
}
|
||||
});
|
||||
|
||||
this.currUser.secretCode = '';
|
||||
}
|
||||
|
||||
logout() {
|
||||
this.http.post<UserNotification>(this.logoutUrl, '', this.httpOptions).subscribe(usermessage => {
|
||||
this.notificationService.addNotification(usermessage.resultType, usermessage.text);
|
||||
this.loggedin = false;
|
||||
this.currUser = new User();
|
||||
this.router.navigate(['/']);
|
||||
});
|
||||
}
|
||||
|
||||
getUser(): User {
|
||||
this.http.get<User>(this.myselfUrl, this.httpOptions).subscribe(thisUser => {
|
||||
this.currUser = thisUser;
|
||||
if (this.currUser.username !== '') {
|
||||
this.loggedin = true;
|
||||
} else {
|
||||
this.router.navigate(['/']);
|
||||
}
|
||||
// separate one to redirect main to campaign dashboard
|
||||
if (this.router.url === '/' || this.router.url === '') {
|
||||
this.router.navigate(['/campaign']);
|
||||
}
|
||||
},
|
||||
err => {
|
||||
this.router.navigate(['/']);
|
||||
});
|
||||
return this.currUser;
|
||||
}
|
||||
|
||||
getUserObs(): Observable<User> {
|
||||
return this.http.get<User>(this.myselfUrl, this.httpOptions);
|
||||
}
|
||||
|
||||
updateUser(user: User) {
|
||||
this.currUser = user;
|
||||
this.http.put<UserNotification>(this.myselfUrl, this.currUser, this.httpOptions).subscribe(usermessage => {
|
||||
this.notificationService.addNotification(usermessage.resultType, usermessage.text);
|
||||
},
|
||||
err => {
|
||||
this.notificationService.addNotification('failure', 'Error in updating profile');
|
||||
});
|
||||
this.currUser.password = '';
|
||||
}
|
||||
|
||||
constructor(private http: HttpClient, private router: Router, private notificationService: NotificationService) { }
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
<div class="row p-2">
|
||||
<div class="col-12">
|
||||
<div class="row mt-3 mb-3">
|
||||
<div class="col-12 d-flex">
|
||||
<label for="campaignName" class="pr-2 mt-auto mb-auto">Campaign Name</label>
|
||||
<input type="text" class="flex-grow-1" id="campaignName" [(ngModel)]="newCampaign.name" placeholder="Campaign Name">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3 mb-3">
|
||||
<div class="col-12 d-flex">
|
||||
<label for="newFromNum" class="pr-2 mt-auto mb-auto">From Number</label>
|
||||
<input type="text" class="flex-grow-1" id="newFromNum" [(ngModel)]="newCampaign.fromNumber" placeholder="From Number">
|
||||
</div>
|
||||
</div>
|
||||
<!-- Add phonebook & template via list -->
|
||||
<div class="form-group">
|
||||
<label for="provider-select">Provider</label>
|
||||
<select class="form-control" [(ngModel)]="newCampaign.providerTag" id="provider-select">
|
||||
<option></option>
|
||||
<option *ngFor="let providerEnum of providerService.providerEnums" [ngValue]="providerEnum.tag">{{providerEnum.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="phonebook-select">Phonebook</label>
|
||||
<select class="form-control" [(ngModel)]="newCampaign.phonebookId" id="phonebook-select">
|
||||
<option></option>
|
||||
<option *ngFor="let phonebook of phonebookService.phonebooks" [ngValue]="phonebook.id">{{phonebook.name}}: Size {{phonebook.size}}</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="text-template-select">Text Template</label>
|
||||
<select class="form-control" [(ngModel)]="newCampaign.textTemplateId" id="text-template-select">
|
||||
<option></option>
|
||||
<option *ngFor="let textTemplate of textTemplateService.textTemplates" [ngValue]="textTemplate.id">{{textTemplate.name}}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="row mt-4">
|
||||
<div class="col-12 d-flex">
|
||||
<button type="button" (click)="submitNewCampaignRun()" class="btn btn-primary mr-2">Start</button>
|
||||
<button type="button" (click)="submitNewCampaign()" class="btn btn-secondary ml-2">Save</button>
|
||||
<button type="button" *ngIf="router.url !== '/campaign/new'" (click)="askDelete()" class="btn btn-danger ml-auto" data-toggle="modal" data-target="#completeModal">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CampaignNewComponent } from './campaign-new.component';
|
||||
|
||||
describe('CampaignNewComponent', () => {
|
||||
let component: CampaignNewComponent;
|
||||
let fixture: ComponentFixture<CampaignNewComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ CampaignNewComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CampaignNewComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { CampaignService, Campaign } from '../campaign.service';
|
||||
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
|
||||
import { ProviderService } from '../provider.service';
|
||||
import { PhonebookService } from '../phonebook.service';
|
||||
import { TextTemplateService } from '../text-template.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-campaign-new',
|
||||
templateUrl: './campaign-new.component.html',
|
||||
styleUrls: ['./campaign-new.component.css']
|
||||
})
|
||||
export class CampaignNewComponent implements OnInit {
|
||||
|
||||
constructor(
|
||||
private campaignService: CampaignService,
|
||||
private router: Router,
|
||||
private providerService: ProviderService,
|
||||
private phonebookService: PhonebookService,
|
||||
private textTemplateService: TextTemplateService) { }
|
||||
|
||||
newCampaign: Campaign = new Campaign();
|
||||
|
||||
submitNewCampaign() {
|
||||
this.campaignService.addCampaign(this.newCampaign);
|
||||
}
|
||||
|
||||
submitNewCampaignRun() {
|
||||
this.campaignService.addCampaignRun(this.newCampaign);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
.campaign-details:read-only {
|
||||
background-color: white;
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
<div class="row">
|
||||
<div class="col-12 mb-3 d-flux">
|
||||
<button type="button" *ngIf="currCampaign.currentStatus === 'Running'" (click)="pauseCampaign()" class="btn btn-warning mr-2">Pause Campaign</button>
|
||||
<button type="button" *ngIf="currCampaign.currentStatus !== 'Running'" (click)="startCampaign()" class="btn btn-primary mr-2">Start Campaign</button>
|
||||
<button type="button" class="btn btn-danger ml-auto" data-toggle="modal" data-target="#completeModal">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Campaign Name</span>
|
||||
</div>
|
||||
<input type="text" class="form-control campaign-details" value="{{ currCampaign.name }}" readonly>
|
||||
</div>
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Campaign Size</span>
|
||||
</div>
|
||||
<input type="text" class="form-control campaign-details" value="{{ currCampaign.size }}" readonly>
|
||||
</div>
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Campaign Status</span>
|
||||
</div>
|
||||
<input type="text" class="form-control campaign-details" value="{{ currCampaign.currentStatus }}" readonly>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-2">
|
||||
<div class="col-12">
|
||||
<table class="table table-hover">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th scope="col">From</th>
|
||||
<th scope="col">To</th>
|
||||
<th scope="col">Currrent Status</th>
|
||||
<th scope="col">Time Sent</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<ng-container *ngFor="let job of currCampaign.jobs">
|
||||
<tr>
|
||||
<td>{{ job.fromNum }}</td>
|
||||
<td>{{ job.toNum }}</td>
|
||||
<td>{{ job.currentStatus }}</td>
|
||||
<td>{{ job.timeSent | date:'dd-MMM-yyyy'}}</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal fade" id="completeModal" tabindex="-1" role="dialog" aria-labelledby="completeModal" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="exampleModalLabel">{{ currCampaign.name }}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Are you sure you want to delete the campaign?</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-danger" (click)="deleteCampaign()" data-dismiss="modal">Delete Campaign</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CampaignViewComponent } from './campaign-view.component';
|
||||
|
||||
describe('CampaignViewComponent', () => {
|
||||
let component: CampaignViewComponent;
|
||||
let fixture: ComponentFixture<CampaignViewComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ CampaignViewComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CampaignViewComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,69 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { CampaignService, Campaign, Job, CampaignNotification } from '../campaign.service';
|
||||
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
|
||||
import { NotificationService } from '../notification.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-campaign-view',
|
||||
templateUrl: './campaign-view.component.html',
|
||||
styleUrls: ['./campaign-view.component.css']
|
||||
})
|
||||
export class CampaignViewComponent implements OnInit {
|
||||
|
||||
currCampaign: Campaign = new Campaign();
|
||||
|
||||
id = 0;
|
||||
|
||||
constructor(
|
||||
private campaignService: CampaignService,
|
||||
private router: Router,
|
||||
private route: ActivatedRoute,
|
||||
private notificationService: NotificationService
|
||||
) { }
|
||||
|
||||
startCampaign() {
|
||||
this.campaignService.startCampaign(this.currCampaign).subscribe(campaignNotification => {
|
||||
this.notificationService.addNotification(campaignNotification.resultType, campaignNotification.text);
|
||||
this.campaignService.getCampaignObs(this.id).subscribe(campaign => {
|
||||
this.currCampaign = campaign;
|
||||
});
|
||||
},
|
||||
err => {
|
||||
this.notificationService.addNotification('failure', 'Error in starting campaign');
|
||||
});
|
||||
}
|
||||
|
||||
pauseCampaign() {
|
||||
this.campaignService.pauseCampaign(this.currCampaign).subscribe(campaignNotification => {
|
||||
this.notificationService.addNotification(campaignNotification.resultType, campaignNotification.text);
|
||||
},
|
||||
err => {
|
||||
this.notificationService.addNotification('failure', 'Error in pausing campaign');
|
||||
});
|
||||
}
|
||||
|
||||
deleteCampaign() {
|
||||
this.campaignService.deleteCampaign(this.currCampaign);
|
||||
}
|
||||
|
||||
updateThisCampaign() {
|
||||
this.campaignService.getCampaignObs(this.id).subscribe(campaign => {
|
||||
this.currCampaign = campaign;
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
const idParam = 'id';
|
||||
this.route.params.subscribe( params => {
|
||||
this.id = parseInt(params[idParam], 10);
|
||||
});
|
||||
this.updateThisCampaign();
|
||||
const intervalId = setInterval(() => {
|
||||
this.updateThisCampaign();
|
||||
if (!this.router.url.includes('/campaign')) {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
}
|
||||
12
tapit-frontend/src/app/campaign.service.spec.ts
Normal file
12
tapit-frontend/src/app/campaign.service.spec.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CampaignService } from './campaign.service';
|
||||
|
||||
describe('CampaignService', () => {
|
||||
beforeEach(() => TestBed.configureTestingModule({}));
|
||||
|
||||
it('should be created', () => {
|
||||
const service: CampaignService = TestBed.get(CampaignService);
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
115
tapit-frontend/src/app/campaign.service.ts
Normal file
115
tapit-frontend/src/app/campaign.service.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { Router } from '@angular/router';
|
||||
import { HttpClient, HttpHeaders } from '@angular/common/http';
|
||||
import { NotificationService } from './notification.service';
|
||||
|
||||
export class Campaign {
|
||||
id: number;
|
||||
name: string;
|
||||
fromNumber: string;
|
||||
size: number;
|
||||
currentStatus: string;
|
||||
createDate: Date;
|
||||
phonebookId: number;
|
||||
textTemplateId: number;
|
||||
webTemplateId: number;
|
||||
providerTag: string;
|
||||
jobs: Job[];
|
||||
}
|
||||
|
||||
export class Job {
|
||||
id: number;
|
||||
currentStatus: string;
|
||||
timeSent: Date;
|
||||
fromNum: string;
|
||||
toNum: string;
|
||||
}
|
||||
|
||||
export class CampaignNotification {
|
||||
resultType: string;
|
||||
text: string;
|
||||
payload: Campaign;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
|
||||
export class CampaignService {
|
||||
|
||||
campaigns: Campaign[] = [];
|
||||
|
||||
campaignUrl = '/api/campaign';
|
||||
|
||||
httpOptions = {
|
||||
headers: new HttpHeaders({
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
};
|
||||
|
||||
getCampaigns() {
|
||||
this.http.get<Campaign[]>(this.campaignUrl).subscribe(campaigns => {
|
||||
if (campaigns === null) {
|
||||
this.campaigns = [];
|
||||
} else {
|
||||
this.campaigns = campaigns;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getCampaignObs(id: number): Observable<Campaign> {
|
||||
return this.http.get<Campaign>(this.campaignUrl + '/' + id.toString());
|
||||
}
|
||||
|
||||
addCampaign(newCampaign: Campaign) {
|
||||
this.http.post<CampaignNotification>(this.campaignUrl, newCampaign, this.httpOptions).subscribe(campaignNotification => {
|
||||
this.notificationService.addNotification(campaignNotification.resultType, campaignNotification.text);
|
||||
this.campaigns.push(campaignNotification.payload);
|
||||
if (campaignNotification.payload !== null) {
|
||||
this.router.navigate(['/campaign']);
|
||||
}
|
||||
},
|
||||
err => {
|
||||
this.notificationService.addNotification('failure', 'Error in creating template');
|
||||
});
|
||||
}
|
||||
|
||||
addCampaignRun(newCampaign: Campaign) {
|
||||
this.http.post<CampaignNotification>(this.campaignUrl, newCampaign, this.httpOptions).subscribe(campaignNotification => {
|
||||
this.notificationService.addNotification(campaignNotification.resultType, campaignNotification.text);
|
||||
this.campaigns.push(campaignNotification.payload);
|
||||
if (campaignNotification.payload !== null) {
|
||||
this.startCampaign(campaignNotification.payload).subscribe();
|
||||
this.router.navigate(['/campaign']);
|
||||
}
|
||||
},
|
||||
err => {
|
||||
this.notificationService.addNotification('failure', 'Error in creating template');
|
||||
});
|
||||
}
|
||||
|
||||
deleteCampaign(campaign: Campaign) {
|
||||
this.http.delete<CampaignNotification>(this.campaignUrl + '/' + campaign.id.toString(), this.httpOptions)
|
||||
.subscribe(campaignNotification => {
|
||||
this.notificationService.addNotification(campaignNotification.resultType, campaignNotification.text);
|
||||
this.router.navigate(['/campaign']);
|
||||
},
|
||||
err => {
|
||||
this.notificationService.addNotification('failure', 'Error in deleting campaign');
|
||||
});
|
||||
}
|
||||
|
||||
startCampaign(campaign: Campaign) {
|
||||
return this.http.get<CampaignNotification>(this.campaignUrl + '/' + campaign.id.toString() + '/' + 'start');
|
||||
}
|
||||
|
||||
pauseCampaign(campaign: Campaign) {
|
||||
return this.http.get<CampaignNotification>(this.campaignUrl + '/' + campaign.id.toString() + '/' + 'pause');
|
||||
}
|
||||
|
||||
constructor(private http: HttpClient, private router: Router, private notificationService: NotificationService) {
|
||||
this.campaigns = [];
|
||||
this.getCampaigns();
|
||||
}
|
||||
}
|
||||
31
tapit-frontend/src/app/campaign/campaign.component.html
Normal file
31
tapit-frontend/src/app/campaign/campaign.component.html
Normal file
@@ -0,0 +1,31 @@
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<button class="btn btn-primary" routerLink="/campaign/new">New Campaign</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-2">
|
||||
<div class="col-12">
|
||||
<table class="table table-hover">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Status</th>
|
||||
<th scope="col">Target Size</th>
|
||||
<th scope="col">Create Date</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<ng-container *ngFor="let campaign of campaignService.campaigns">
|
||||
<tr routerLink="/campaign/{{ campaign.id }}/view">
|
||||
<td>{{ campaign.name }}</td>
|
||||
<td>{{ campaign.currentStatus }}</td>
|
||||
<td>{{ campaign.size }}</td>
|
||||
<td>{{ campaign.createDate | date:'dd-MMM-yyyy'}}</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<p *ngIf="campaignService.campaigns.length === 0">No campaigns created yet. Create compaigns by clicking <a routerLink="/campaign/new">here</a></p>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
25
tapit-frontend/src/app/campaign/campaign.component.spec.ts
Normal file
25
tapit-frontend/src/app/campaign/campaign.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CampaignComponent } from './campaign.component';
|
||||
|
||||
describe('CampaignComponent', () => {
|
||||
let component: CampaignComponent;
|
||||
let fixture: ComponentFixture<CampaignComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ CampaignComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CampaignComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
24
tapit-frontend/src/app/campaign/campaign.component.ts
Normal file
24
tapit-frontend/src/app/campaign/campaign.component.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
|
||||
import { CampaignService } from '../campaign.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-campaign',
|
||||
templateUrl: './campaign.component.html',
|
||||
styleUrls: ['./campaign.component.css']
|
||||
})
|
||||
export class CampaignComponent implements OnInit {
|
||||
|
||||
constructor(private campaignService: CampaignService, private router: Router) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.campaignService.getCampaigns();
|
||||
const intervalId = setInterval(() => {
|
||||
this.campaignService.getCampaigns();
|
||||
if (!this.router.url.includes('/campaign')) {
|
||||
clearInterval(intervalId);
|
||||
}
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
}
|
||||
0
tapit-frontend/src/app/login/login.component.css
Normal file
0
tapit-frontend/src/app/login/login.component.css
Normal file
27
tapit-frontend/src/app/login/login.component.html
Normal file
27
tapit-frontend/src/app/login/login.component.html
Normal file
@@ -0,0 +1,27 @@
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<h4>Login</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<label for="login-username" class="pr-2 mt-auto mb-auto">Username</label>
|
||||
<input type="text" class="flex-grow-1" id="login-username" [(ngModel)]="username" >
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<label for="login-password" class="pr-2 mt-auto mb-auto">Password</label>
|
||||
<input type="password" class="flex-grow-1" id="login-password" [(ngModel)]="password" >
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<button type="button" (click)="login()" class="btn btn-primary mr-3">Login</button>
|
||||
<button type="button" (click)="routeRegister()" class="btn btn-primary">Register</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
25
tapit-frontend/src/app/login/login.component.spec.ts
Normal file
25
tapit-frontend/src/app/login/login.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { LoginComponent } from './login.component';
|
||||
|
||||
describe('LoginComponent', () => {
|
||||
let component: LoginComponent;
|
||||
let fixture: ComponentFixture<LoginComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ LoginComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(LoginComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
27
tapit-frontend/src/app/login/login.component.ts
Normal file
27
tapit-frontend/src/app/login/login.component.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { AuthService } from '../auth.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-login',
|
||||
templateUrl: './login.component.html',
|
||||
styleUrls: ['./login.component.css']
|
||||
})
|
||||
export class LoginComponent implements OnInit {
|
||||
|
||||
username: string;
|
||||
password: string;
|
||||
|
||||
login() {
|
||||
this.authService.login(this.username, this.password);
|
||||
}
|
||||
|
||||
routeRegister() {
|
||||
this.router.navigate(['/register']);
|
||||
}
|
||||
constructor(private authService: AuthService, private router: Router) { }
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
}
|
||||
0
tapit-frontend/src/app/main/main.component.css
Normal file
0
tapit-frontend/src/app/main/main.component.css
Normal file
3
tapit-frontend/src/app/main/main.component.html
Normal file
3
tapit-frontend/src/app/main/main.component.html
Normal file
@@ -0,0 +1,3 @@
|
||||
<p>
|
||||
main works!
|
||||
</p>
|
||||
25
tapit-frontend/src/app/main/main.component.spec.ts
Normal file
25
tapit-frontend/src/app/main/main.component.spec.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { MainComponent } from './main.component';
|
||||
|
||||
describe('MainComponent', () => {
|
||||
let component: MainComponent;
|
||||
let fixture: ComponentFixture<MainComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ MainComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(MainComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
15
tapit-frontend/src/app/main/main.component.ts
Normal file
15
tapit-frontend/src/app/main/main.component.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-main',
|
||||
templateUrl: './main.component.html',
|
||||
styleUrls: ['./main.component.css']
|
||||
})
|
||||
export class MainComponent implements OnInit {
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
}
|
||||
12
tapit-frontend/src/app/notification.service.spec.ts
Normal file
12
tapit-frontend/src/app/notification.service.spec.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { NotificationService } from './notification.service';
|
||||
|
||||
describe('NotificationService', () => {
|
||||
beforeEach(() => TestBed.configureTestingModule({}));
|
||||
|
||||
it('should be created', () => {
|
||||
const service: NotificationService = TestBed.get(NotificationService);
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
38
tapit-frontend/src/app/notification.service.ts
Normal file
38
tapit-frontend/src/app/notification.service.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
export class Notification {
|
||||
id: number;
|
||||
resultType: string; // enum success or failure or info
|
||||
text: string;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class NotificationService {
|
||||
notifications: Notification[] = [];
|
||||
currentCount = 0;
|
||||
|
||||
addNotification(resultType, text) {
|
||||
const newNotification = new Notification();
|
||||
newNotification.id = this.currentCount;
|
||||
this.currentCount++;
|
||||
newNotification.resultType = resultType;
|
||||
newNotification.text = text;
|
||||
|
||||
this.notifications.push(newNotification);
|
||||
setTimeout(() => this.closeNotification(newNotification), 3000);
|
||||
}
|
||||
|
||||
closeNotification(notify: Notification) {
|
||||
for (let i = 0; i < this.notifications.length; i++) {
|
||||
if (this.notifications[i].id === notify.id) {
|
||||
this.notifications.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<div class="alert notification col-11 mx-auto" *ngFor="let notification of notificationService.notifications" [ngClass]="{'alert-success': notification.resultType === 'success', 'alert-danger': notification.resultType ==='failure'}" (click)=notificationService.closeNotification(notification)>
|
||||
{{ notification.text }}
|
||||
</div>
|
||||
@@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { NotificationComponent } from './notification.component';
|
||||
|
||||
describe('NotificationComponent', () => {
|
||||
let component: NotificationComponent;
|
||||
let fixture: ComponentFixture<NotificationComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ NotificationComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(NotificationComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { NotificationService } from '../notification.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-notification',
|
||||
templateUrl: './notification.component.html',
|
||||
styleUrls: ['./notification.component.css']
|
||||
})
|
||||
export class NotificationComponent implements OnInit {
|
||||
|
||||
constructor(private notificationService: NotificationService) { }
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
.no-space-break {
|
||||
white-space:nowrap;
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
<div class="row p-2">
|
||||
<div class="col-12">
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<label for="campaignName" class="pr-2 mt-auto mb-auto">Phonebook Name</label>
|
||||
<input type="text" class="flex-grow-1" id="campaignName" [(ngModel)]="newPhonebook.name" placeholder="Phonebook Name">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<label class="no-space-break mt-auto mb-auto pr-2" for="import-records">Import Records</label>
|
||||
<div class="custom-file" id="import-records">
|
||||
<input type="file" (change)="importPhoneRecords($event.target.files)" class="custom-file-input" id="customFile">
|
||||
<label class="custom-file-label" for="customFile">Choose file</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12 d-flex">
|
||||
<p><small><em><a href="/assets/phonebook-template.xlsx">Download file template here.</a></em></small></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-1">
|
||||
<div class="col-12 d-flex">
|
||||
<table class="table table-hover">
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th scope="col">First Name</th>
|
||||
<th scope="col">Last Name</th>
|
||||
<th scope="col">Alias</th>
|
||||
<th scope="col">Phone Number</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<ng-container *ngFor="let phoneRecord of newPhoneRecords">
|
||||
<tr>
|
||||
<td>{{ phoneRecord.firstName }}</td>
|
||||
<td>{{ phoneRecord.lastName }}</td>
|
||||
<td>{{ phoneRecord.alias }}</td>
|
||||
<td>{{ phoneRecord.phoneNumber }}</td>
|
||||
</tr>
|
||||
</ng-container>
|
||||
<tr (keyup.enter)="insertAdditionalRecord()">
|
||||
<td><input type="text" [(ngModel)]="additionalRecord.firstName" class="form-control" placeholder="firstName"></td>
|
||||
<td><input type="text" [(ngModel)]="additionalRecord.lastName" class="form-control" placeholder="lastName"></td>
|
||||
<td><input type="text" [(ngModel)]="additionalRecord.alias" class="form-control" placeholder="alias"></td>
|
||||
<td><input type="text" [(ngModel)]="additionalRecord.phoneNumber" class="form-control" placeholder="phoneNumber"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<p><small><em>Press enter to insert additional record</em></small></p>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-3">
|
||||
<div class="col-12 d-flex">
|
||||
<button type="button" (click)="submitNewPhonebook()" class="btn btn-primary mr-2">Save Phonebook</button>
|
||||
<button type="button" *ngIf="router.url !== '/phonebook/new'" class="btn btn-danger ml-auto" data-toggle="modal" data-target="#completeModal">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="completeModal" tabindex="-1" role="dialog" aria-labelledby="completeModal" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="exampleModalLabel">{{ newPhonebook.name }}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Are you sure you want to delete the phonebook?</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
|
||||
<button type="button" class="btn btn-danger" (click)="deletePhonebook()" data-dismiss="modal">Delete Phonebook</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user