Browse Source

Cleaned up error messages - *all* errors in JSON format

Cleaned up flashes - fixes #13
Added specified errors - more to come soon
Added Campaign validation
Added Group validation
Cleaned up the way angular errors are handled. Will double check, but for the most part fixes #11
Results are now shown on the webui with most recent shown first
Added comments, additional cleanup, etc.
pull/24/head
unknown 6 years ago
parent
commit
66dbe2e799
13 changed files with 410 additions and 323 deletions
  1. +42
    -43
      controllers/api.go
  2. +0
    -10
      controllers/route.go
  3. +26
    -7
      models/campaign.go
  4. +29
    -1
      models/group.go
  5. +6
    -3
      models/template.go
  6. +289
    -252
      static/js/app/controllers.js
  7. +2
    -2
      static/js/app/partials/campaigns.html
  8. +1
    -1
      static/js/app/partials/landing_pages.html
  9. +6
    -1
      static/js/app/partials/modals/campaignModal.html
  10. +5
    -0
      static/js/app/partials/modals/userModal.html
  11. +1
    -1
      static/js/app/partials/templates.html
  12. +2
    -2
      static/js/app/partials/users.html
  13. +1
    -0
      worker/worker.go

+ 42
- 43
controllers/api.go View File

@ -69,15 +69,13 @@ func API_Campaigns(w http.ResponseWriter, r *http.Request) {
c := models.Campaign{}
// Put the request into a campaign
err := json.NewDecoder(r.Body).Decode(&c)
if checkError(err, w, "Invalid Request", http.StatusBadRequest) {
return
}
if m, ok := c.Validate(); !ok {
http.Error(w, "Error: "+m, http.StatusBadRequest)
if err != nil {
JSONResponse(w, models.Response{Success: false, Message: "Invalid JSON structure"}, http.StatusBadRequest)
return
}
err = models.PostCampaign(&c, ctx.Get(r, "user_id").(int64))
if checkError(err, w, "Cannot insert campaign into database", http.StatusInternalServerError) {
if err != nil {
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
return
}
Worker.Queue <- &c
@ -91,7 +89,8 @@ func API_Campaigns_Id(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, _ := strconv.ParseInt(vars["id"], 0, 64)
c, err := models.GetCampaign(id, ctx.Get(r, "user_id").(int64))
if checkError(err, w, "Campaign not found", http.StatusNotFound) {
if err != nil {
JSONResponse(w, models.Response{Success: false, Message: "Campaign not found"}, http.StatusNotFound)
return
}
switch {
@ -99,7 +98,8 @@ func API_Campaigns_Id(w http.ResponseWriter, r *http.Request) {
JSONResponse(w, c, http.StatusOK)
case r.Method == "DELETE":
err = models.DeleteCampaign(id)
if checkError(err, w, "Error deleting campaign", http.StatusInternalServerError) {
if err != nil {
JSONResponse(w, models.Response{Success: false, Message: "Error deleting campaign"}, http.StatusInternalServerError)
return
}
JSONResponse(w, models.Response{Success: true, Message: "Campaign deleted successfully!"}, http.StatusOK)
@ -112,7 +112,8 @@ func API_Groups(w http.ResponseWriter, r *http.Request) {
switch {
case r.Method == "GET":
gs, err := models.GetGroups(ctx.Get(r, "user_id").(int64))
if checkError(err, w, "Groups not found", http.StatusNotFound) {
if err != nil {
JSONResponse(w, models.Response{Success: false, Message: "No groups found"}, http.StatusNotFound)
return
}
JSONResponse(w, gs, http.StatusOK)
@ -121,7 +122,8 @@ func API_Groups(w http.ResponseWriter, r *http.Request) {
g := models.Group{}
// Put the request into a group
err := json.NewDecoder(r.Body).Decode(&g)
if checkError(err, w, "Invalid Request", http.StatusBadRequest) {
if err != nil {
JSONResponse(w, models.Response{Success: false, Message: "Invalid JSON structure"}, http.StatusBadRequest)
return
}
_, err = models.GetGroupByName(g.Name, ctx.Get(r, "user_id").(int64))
@ -129,15 +131,11 @@ func API_Groups(w http.ResponseWriter, r *http.Request) {
JSONResponse(w, models.Response{Success: false, Message: "Group name already in use"}, http.StatusConflict)
return
}
// Check to make sure targets were specified
if len(g.Targets) == 0 {
http.Error(w, "Error: No targets specified", http.StatusBadRequest)
return
}
g.ModifiedDate = time.Now()
g.UserId = ctx.Get(r, "user_id").(int64)
err = models.PostGroup(&g)
if checkError(err, w, "Error inserting group", http.StatusInternalServerError) {
if err != nil {
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
return
}
w.Header().Set("Location", "http://localhost:3333/api/groups/"+string(g.Id))
@ -151,7 +149,8 @@ func API_Groups_Id(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, _ := strconv.ParseInt(vars["id"], 0, 64)
g, err := models.GetGroup(id, ctx.Get(r, "user_id").(int64))
if checkError(err, w, "Group not found", http.StatusNotFound) {
if err != nil {
JSONResponse(w, models.Response{Success: false, Message: "Group not found"}, http.StatusNotFound)
return
}
switch {
@ -159,7 +158,8 @@ func API_Groups_Id(w http.ResponseWriter, r *http.Request) {
JSONResponse(w, g, http.StatusOK)
case r.Method == "DELETE":
err = models.DeleteGroup(&g)
if checkError(err, w, "Error deleting group", http.StatusInternalServerError) {
if err != nil {
JSONResponse(w, models.Response{Success: false, Message: "Error deleting group"}, http.StatusInternalServerError)
return
}
JSONResponse(w, models.Response{Success: true, Message: "Group deleted successfully!"}, http.StatusOK)
@ -168,18 +168,14 @@ func API_Groups_Id(w http.ResponseWriter, r *http.Request) {
g = models.Group{}
err = json.NewDecoder(r.Body).Decode(&g)
if g.Id != id {
http.Error(w, "Error: /:id and group_id mismatch", http.StatusBadRequest)
return
}
// Check to make sure targets were specified
if len(g.Targets) == 0 {
http.Error(w, "Error: No targets specified", http.StatusBadRequest)
JSONResponse(w, models.Response{Success: false, Message: "Error: /:id and group_id mismatch"}, http.StatusInternalServerError)
return
}
g.ModifiedDate = time.Now()
g.UserId = ctx.Get(r, "user_id").(int64)
err = models.PutGroup(&g)
if checkError(err, w, "Error updating group", http.StatusInternalServerError) {
if err != nil {
JSONResponse(w, models.Response{Success: false, Message: "Error updating group"}, http.StatusInternalServerError)
return
}
JSONResponse(w, g, http.StatusOK)
@ -199,11 +195,11 @@ func API_Templates(w http.ResponseWriter, r *http.Request) {
t := models.Template{}
// Put the request into a template
err := json.NewDecoder(r.Body).Decode(&t)
if checkError(err, w, "Invalid Request", http.StatusBadRequest) {
if err != nil {
JSONResponse(w, models.Response{Success: false, Message: "Invalid JSON structure"}, http.StatusBadRequest)
return
}
_, err = models.GetTemplateByName(t.Name, ctx.Get(r, "user_id").(int64))
fmt.Println(err)
if err != gorm.RecordNotFound {
JSONResponse(w, models.Response{Success: false, Message: "Template name already in use"}, http.StatusConflict)
return
@ -219,7 +215,9 @@ func API_Templates(w http.ResponseWriter, r *http.Request) {
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
return
}
if checkError(err, w, "Error inserting template", http.StatusInternalServerError) {
if err != nil {
JSONResponse(w, models.Response{Success: false, Message: "Error inserting template into database"}, http.StatusInternalServerError)
Logger.Println(err)
return
}
JSONResponse(w, t, http.StatusCreated)
@ -230,8 +228,8 @@ func API_Templates_Id(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, _ := strconv.ParseInt(vars["id"], 0, 64)
t, err := models.GetTemplate(id, ctx.Get(r, "user_id").(int64))
if checkError(err, w, "Template not found", http.StatusNotFound) {
Logger.Println(err)
if err != nil {
JSONResponse(w, models.Response{Success: false, Message: "Template not found"}, http.StatusNotFound)
return
}
switch {
@ -239,7 +237,8 @@ func API_Templates_Id(w http.ResponseWriter, r *http.Request) {
JSONResponse(w, t, http.StatusOK)
case r.Method == "DELETE":
err = models.DeleteTemplate(id, ctx.Get(r, "user_id").(int64))
if checkError(err, w, "Error deleting template", http.StatusInternalServerError) {
if err != nil {
JSONResponse(w, models.Response{Success: false, Message: "Error deleting template"}, http.StatusInternalServerError)
return
}
JSONResponse(w, models.Response{Success: true, Message: "Template deleted successfully!"}, http.StatusOK)
@ -250,17 +249,14 @@ func API_Templates_Id(w http.ResponseWriter, r *http.Request) {
Logger.Println(err)
}
if t.Id != id {
http.Error(w, "Error: /:id and template_id mismatch", http.StatusBadRequest)
JSONResponse(w, models.Response{Success: false, Message: "Error: /:id and template_id mismatch"}, http.StatusBadRequest)
return
}
err = t.Validate()
/* if checkError(err, w, http.StatusBadRequest) {
return
}*/
t.ModifiedDate = time.Now()
t.UserId = ctx.Get(r, "user_id").(int64)
err = models.PutTemplate(&t)
if checkError(err, w, "Error updating group", http.StatusInternalServerError) {
if err != nil {
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
return
}
JSONResponse(w, t, http.StatusOK)
@ -306,8 +302,8 @@ func API_Pages_Id(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
id, _ := strconv.ParseInt(vars["id"], 0, 64)
p, err := models.GetPage(id, ctx.Get(r, "user_id").(int64))
if checkError(err, w, "Page not found", http.StatusNotFound) {
Logger.Println(err)
if err != nil {
JSONResponse(w, models.Response{Success: false, Message: "Page not found"}, http.StatusNotFound)
return
}
switch {
@ -315,7 +311,8 @@ func API_Pages_Id(w http.ResponseWriter, r *http.Request) {
JSONResponse(w, p, http.StatusOK)
case r.Method == "DELETE":
err = models.DeletePage(id, ctx.Get(r, "user_id").(int64))
if checkError(err, w, "Error deleting page", http.StatusInternalServerError) {
if err != nil {
JSONResponse(w, models.Response{Success: false, Message: "Error deleting page"}, http.StatusInternalServerError)
return
}
JSONResponse(w, models.Response{Success: true, Message: "Page Deleted Successfully"}, http.StatusOK)
@ -348,7 +345,8 @@ func API_Pages_Id(w http.ResponseWriter, r *http.Request) {
// API_Import_Group imports a CSV of group members
func API_Import_Group(w http.ResponseWriter, r *http.Request) {
ts, err := util.ParseCSV(r)
if checkError(err, w, "Error deleting template", http.StatusInternalServerError) {
if err != nil {
JSONResponse(w, models.Response{Success: false, Message: "Error parsing CSV"}, http.StatusInternalServerError)
return
}
JSONResponse(w, ts, http.StatusOK)
@ -371,8 +369,9 @@ func API_Import_Email(w http.ResponseWriter, r *http.Request) {
// is written to the given ResponseWriter.
func JSONResponse(w http.ResponseWriter, d interface{}, c int) {
dj, err := json.MarshalIndent(d, "", " ")
if checkError(err, w, "Error creating JSON response", http.StatusInternalServerError) {
return
if err != nil {
http.Error(w, "Error creating JSON response", http.StatusInternalServerError)
Logger.Println(err)
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(c)

+ 0
- 10
controllers/route.go View File

@ -264,16 +264,6 @@ func getTemplate(w http.ResponseWriter, tmpl string) *template.Template {
return template.Must(templates, err)
}
func checkError(e error, w http.ResponseWriter, m string, c int) bool {
if e != nil {
Logger.Println(e)
w.WriteHeader(c)
JSONResponse(w, models.Response{Success: false, Message: m}, http.StatusBadRequest)
return true
}
return false
}
// Flash handles the rendering flash messages
func Flash(w http.ResponseWriter, r *http.Request, t string, m string) {
session := ctx.Get(r, "session").(*sessions.Session)

+ 26
- 7
models/campaign.go View File

@ -1,6 +1,7 @@
package models
import (
"errors"
"fmt"
"time"
@ -26,17 +27,32 @@ type Campaign struct {
SMTP SMTP `json:"smtp"`
}
// ErrCampaignNameNotSpecified indicates there was no template given by the user
var ErrCampaignNameNotSpecified = errors.New("Campaign name not specified")
// ErrGroupNotSpecified indicates there was no template given by the user
var ErrGroupNotSpecified = errors.New("No groups specified")
// ErrTemplateNotSpecified indicates there was no template given by the user
var ErrTemplateNotSpecified = errors.New("No email template specified")
// ErrTemplateNotFound indicates the template specified does not exist in the database
var ErrTemplateNotFound = errors.New("Template not found")
// ErrGroupnNotFound indicates a group specified by the user does not exist in the database
var ErrGroupNotFound = errors.New("Group not found")
// Validate checks to make sure there are no invalid fields in a submitted campaign
func (c *Campaign) Validate() (string, bool) {
func (c *Campaign) Validate() error {
switch {
case c.Name == "":
return "Must specify campaign name", false
return ErrCampaignNameNotSpecified
case len(c.Groups) == 0:
return "No groups specified", false
return ErrGroupNotSpecified
case c.Template.Name == "":
return "No template specified", false
return ErrTemplateNotSpecified
}
return "", true
return nil
}
// UpdateStatus changes the campaign status appropriately
@ -105,6 +121,9 @@ func GetCampaign(id int64, uid int64) (Campaign, error) {
// PostCampaign inserts a campaign and all associated records into the database.
func PostCampaign(c *Campaign, uid int64) error {
if err := c.Validate(); err != nil {
return err
}
// Fill in the details
c.UserId = uid
c.CreatedDate = time.Now()
@ -115,7 +134,7 @@ func PostCampaign(c *Campaign, uid int64) error {
c.Groups[i], err = GetGroupByName(g.Name, uid)
if err == gorm.RecordNotFound {
Logger.Printf("Error - Group %s does not exist", g.Name)
return err
return ErrGroupNotFound
} else if err != nil {
Logger.Println(err)
return err
@ -125,7 +144,7 @@ func PostCampaign(c *Campaign, uid int64) error {
t, err := GetTemplateByName(c.Template.Name, uid)
if err == gorm.RecordNotFound {
Logger.Printf("Error - Template %s does not exist", t.Name)
return err
return ErrTemplateNotFound
} else if err != nil {
Logger.Println(err)
return err

+ 29
- 1
models/group.go View File

@ -1,12 +1,15 @@
package models
import (
"errors"
"net/mail"
"time"
"github.com/jinzhu/gorm"
)
// Group contains the fields needed for a user -> group mapping
// Groups contain 1..* Targets
type Group struct {
Id int64 `json:"id"`
UserId int64 `json:"-"`
@ -15,11 +18,14 @@ type Group struct {
Targets []Target `json:"targets" sql:"-"`
}
// GroupTarget is used for a many-to-many relationship between 1..* Groups and 1..* Targets
type GroupTarget struct {
GroupId int64 `json:"-"`
TargetId int64 `json:"-"`
}
// Target contains the fields needed for individual targets specified by the user
// Groups contain 1..* Targets, but 1 Target may belong to 1..* Groups
type Target struct {
Id int64 `json:"-"`
FirstName string `json:"first_name"`
@ -27,6 +33,23 @@ type Target struct {
Email string `json:"email"`
}
// ErrGroupNameNotSpecified is thrown when a group name is not specified
var ErrGroupNameNotSpecified = errors.New("Group name not specified")
// ErrNoTargetsSpecified is thrown when no targets are specified by the user
var ErrNoTargetsSpecified = errors.New("No targets specified")
// Validate performs validation on a group given by the user
func (g *Group) Validate() error {
switch {
case g.Name == "":
return ErrGroupNameNotSpecified
case len(g.Targets) == 0:
return ErrNoTargetsSpecified
}
return nil
}
// GetGroups returns the groups owned by the given user.
func GetGroups(uid int64) ([]Group, error) {
gs := []Group{}
@ -76,7 +99,11 @@ func GetGroupByName(n string, uid int64) (Group, error) {
// PostGroup creates a new group in the database.
func PostGroup(g *Group) error {
// Insert into the DB
Logger.Printf("%v", g.Targets)
if err := g.Validate(); err != nil {
return err
}
// Insert the group into the DB
err = db.Save(g).Error
if err != nil {
Logger.Println(err)
@ -189,6 +216,7 @@ func insertTargetIntoGroup(t Target, gid int64) error {
return nil
}
// GetTargets performs a many-to-many select to get all the Targets for a Group
func GetTargets(gid int64) ([]Target, error) {
ts := []Target{}
err := db.Table("targets").Select("targets.id, targets.email, targets.first_name, targets.last_name").Joins("left join group_targets gt ON targets.id = gt.target_id").Where("gt.group_id=?", gid).Scan(&ts).Error

+ 6
- 3
models/template.go View File

@ -20,7 +20,7 @@ type Template struct {
}
// ErrTemplateNameNotSpecified is thrown when a template name is not specified
var ErrTemplateNameNotSpecified = errors.New("Template Name not specified")
var ErrTemplateNameNotSpecified = errors.New("Template name not specified")
// ErrTemplateMissingParameter is thrown when a needed parameter is not provided
var ErrTemplateMissingParameter = errors.New("Need to specify at least plaintext or HTML content")
@ -112,8 +112,11 @@ func PostTemplate(t *Template) error {
// PutTemplate edits an existing template in the database.
// Per the PUT Method RFC, it presumes all data for a template is provided.
func PutTemplate(t *Template) error {
if err := t.Validate(); err != nil {
return err
}
// Delete all attachments, and replace with new ones
err := db.Debug().Where("template_id=?", t.Id).Delete(&Attachment{}).Error
err = db.Where("template_id=?", t.Id).Delete(&Attachment{}).Error
if err != nil && err != gorm.RecordNotFound {
Logger.Println(err)
return err
@ -129,7 +132,7 @@ func PutTemplate(t *Template) error {
return err
}
}
err = db.Debug().Where("id=?", t.Id).Save(t).Error
err = db.Where("id=?", t.Id).Save(t).Error
if err != nil {
Logger.Println(err)
return err

+ 289
- 252
static/js/app/controllers.js View File

@ -132,7 +132,23 @@ app.controller('DashboardCtrl', function($scope, $filter, $location, CampaignSer
});
})
app.controller('CampaignCtrl', function($scope, $modal, CampaignService, GroupService, TemplateService, ngTableParams, $http) {
$scope.flashes = []
$scope.errorFlash = function(message) {
$scope.flashes = {"main" : [], "modal" : []};
$scope.flashes.main.push({
"type": "danger",
"message": message,
"icon": "fa-exclamation-circle"
})
}
$scope.successFlash = function(message) {
$scope.flashes = {"main" : [], "modal" : []};;
$scope.flashes.main.push({
"type": "success",
"message": message,
"icon": "fa-check-circle"
})
}
$scope.mainTableParams = new ngTableParams({
page: 1, // show first page
count: 10, // count per page
@ -158,21 +174,6 @@ app.controller('CampaignCtrl', function($scope, $modal, CampaignService, GroupSe
$scope.templates = templates;
})
$scope.addGroup = function(group) {
if (group.name != "") {
$scope.campaign.groups.push({
name: group.name
});
group.name = ""
$scope.editGroupTableParams.reload()
}
};
$scope.removeGroup = function(group) {
$scope.campaign.groups.splice($scope.campaign.groups.indexOf(group), 1);
$scope.editGroupTableParams.reload()
};
$scope.newCampaign = function() {
$scope.campaign = {
name: '',
@ -188,11 +189,19 @@ app.controller('CampaignCtrl', function($scope, $modal, CampaignService, GroupSe
scope: $scope
});
modalInstance.result.then(function(selectedItem) {
$scope.selected = selectedItem;
modalInstance.result.then(function(message) {
$scope.successFlash(message)
$scope.campaign = {
name: '',
groups: [],
};
}, function() {
console.log('closed')
$scope.campaign = {
name: '',
groups: [],
};
});
$scope.mainTableParams.reload()
};
$scope.editGroupTableParams = new ngTableParams({
@ -209,63 +218,78 @@ app.controller('CampaignCtrl', function($scope, $modal, CampaignService, GroupSe
}
});
$scope.saveCampaign = function(campaign) {
$scope.flashes = []
$scope.validated = true
var newCampaign = new CampaignService(campaign);
newCampaign.$save({}, function() {
$scope.successFlash("Campaign added successfully")
$scope.campaigns.push(newCampaign);
$scope.mainTableParams.reload()
}, function(response) {
$scope.errorFlash(response.data)
});
$scope.campaign = {
groups: [],
};
$scope.editGroupTableParams.reload()
}
$scope.deleteCampaign = function(campaign) {
var deleteCampaign = new CampaignService(campaign);
deleteCampaign.$delete({
id: deleteCampaign.id
}, function() {
$scope.successFlash("Campaign deleted successfully")
}, function(response) {
if (response.success) {
$scope.successFlash(response.message)
} else {
$scope.errorFlash(response.message)
}
$scope.mainTableParams.reload();
});
}
});
var CampaignModalCtrl = function($scope, CampaignService, $modalInstance) {
$scope.errorFlash = function(message) {
$scope.flashes.push({
$scope.flashes = {"main" : [], "modal" : []};
$scope.flashes.modal.push({
"type": "danger",
"message": message,
"icon": "fa-exclamation-circle"
})
}
$scope.addGroup = function(group) {
if (group.name != "") {
$scope.campaign.groups.push({
name: group.name
});
group.name = ""
$scope.editGroupTableParams.reload()
}
};
$scope.successFlash = function(message) {
$scope.flashes.push({
"type": "success",
"message": message,
"icon": "fa-check-circle"
})
}
});
var CampaignModalCtrl = function($scope, $modalInstance) {
$scope.removeGroup = function(group) {
$scope.campaign.groups.splice($scope.campaign.groups.indexOf(group), 1);
$scope.editGroupTableParams.reload()
};
$scope.cancel = function() {
$modalInstance.dismiss('cancel');
};
$scope.ok = function(campaign) {
$modalInstance.dismiss("")
$scope.saveCampaign(campaign)
var newCampaign = new CampaignService(campaign);
newCampaign.$save({}, function() {
$modalInstance.close("Campaign added successfully")
$scope.campaigns.push(newCampaign);
$scope.mainTableParams.reload()
}, function(response) {
$scope.errorFlash(response.data.message)
});
}
};
app.controller('CampaignResultsCtrl', function($scope, $filter, CampaignService, GroupService, ngTableParams, $http, $window) {
id = $window.location.hash.split('/')[2];
$scope.flashes = []
$scope.errorFlash = function(message) {
$scope.flashes = {"main" : [], "modal" : []};
$scope.flashes.main.push({
"type": "danger",
"message": message,
"icon": "fa-exclamation-circle"
})
}
$scope.successFlash = function(message) {
$scope.flashes = {"main" : [], "modal" : []};;
$scope.flashes.main.push({
"type": "success",
"message": message,
"icon": "fa-check-circle"
})
}
$scope.mainTableParams = new ngTableParams({
page: 1, // show first page
count: 10, // count per page
@ -378,51 +402,43 @@ app.controller('CampaignResultsCtrl', function($scope, $filter, CampaignService,
xAxis: {
type: 'datetime',
dateTimeLabelFormats: { // don't display the dummy year
day: "%e of %b",
hour: "%l:%M",
second: '%l:%M:%S',
minute: '%l:%M'
},
max: Date.now(),
title: {
text: 'Date'
}
day: "%e of %b",
hour: "%l:%M",
second: '%l:%M:%S',
minute: '%l:%M'
},
max: Date.now(),
title: {
text: 'Date'
}
},
series: [{
name: "Events",
data: $scope.campaign.timeline
}],
title: {
text: 'Campaign Timeline'
},
size: {
height: 300
},
credits: {
enabled: false
},
loading: false,
}
params.total(campaign.results.length)
$defer.resolve(campaign.results.slice((params.page() - 1) * params.count(), params.page() * params.count()));
})
}
});
$scope.errorFlash = function(message) {
$scope.flashes.push({
"type": "danger",
"message": message,
"icon": "fa-exclamation-circle"
},
series: [{
name: "Events",
data: $scope.campaign.timeline
}],
title: {
text: 'Campaign Timeline'
},
size: {
height: 300
},
credits: {
enabled: false
},
loading: false,
}
params.total(campaign.results.length)
$defer.resolve(campaign.results.slice((params.page() - 1) * params.count(), params.page() * params.count()));
})
}
});
})
app.controller('GroupCtrl', function($scope, $modal, GroupService, ngTableParams) {
$scope.errorFlash = function(message) {
$scope.flashes = [];
$scope.flashes.push({
$scope.flashes = {"main" : [], "modal" : []};
$scope.flashes.main.push({
"type": "danger",
"message": message,
"icon": "fa-exclamation-circle"
@ -430,8 +446,8 @@ app.controller('GroupCtrl', function($scope, $modal, GroupService, ngTableParams
}
$scope.successFlash = function(message) {
$scope.flashes = [];
$scope.flashes.push({
$scope.flashes = {"main" : [], "modal" : []};;
$scope.flashes.main.push({
"type": "success",
"message": message,
"icon": "fa-check-circle"
@ -487,50 +503,46 @@ app.controller('GroupCtrl', function($scope, $modal, GroupService, ngTableParams
controller: GroupModalCtrl,
scope: $scope
});
};
modalInstance.result.then(function(message) {
$scope.successFlash(message)
$scope.group = {
name: '',
targets: [],
};
}, function() {
$scope.group = {
name: '',
targets: [],
};
});
$scope.mainTableParams.reload()
$scope.addTarget = function() {
if ($scope.newTarget.email != "") {
$scope.group.targets.push({
email: $scope.newTarget.email
});
$scope.newTarget.email = ""
$scope.editGroupTableParams.reload()
}
};
$scope.removeTarget = function(target) {
$scope.group.targets.splice($scope.group.targets.indexOf(target), 1);
$scope.editGroupTableParams.reload()
};
$scope.saveGroup = function(group) {
var newGroup = new GroupService(group);
if ($scope.newGroup) {
newGroup.$save({}, function() {
$scope.groups.push(newGroup);
$scope.mainTableParams.reload()
});
} else {
newGroup.$update({
id: newGroup.id
})
}
$scope.group = {
name: '',
targets: [],
};
$scope.editGroupTableParams.reload()
}
$scope.deleteGroup = function(group) {
var deleteGroup = new GroupService(group);
deleteGroup.$delete({
id: deleteGroup.id
}, function() {
}, function(response) {
if (response.success) {
$scope.successFlash(response.message)
} else {
$scope.errorFlash(response.message)
}
$scope.mainTableParams.reload();
});
}
})
var GroupModalCtrl = function($scope, $modalInstance, $upload) {
var GroupModalCtrl = function($scope, GroupService, $modalInstance, $upload) {
$scope.errorFlash = function(message) {
$scope.flashes = {"main" : [], "modal" : []};
$scope.flashes.modal.push({
"type": "danger",
"message": message,
"icon": "fa-exclamation-circle"
})
}
$scope.onFileSelect = function($file) {
$scope.upload = $upload.upload({
url: '/api/import/group',
@ -541,21 +553,48 @@ var GroupModalCtrl = function($scope, $modalInstance, $upload) {
}).success(function(data, status, headers, config) {
angular.forEach(data, function(record, key) {
$scope.group.targets.push({
first_name : record.first_name,
last_name : record.last_name,
email: record.email
first_name : record.first_name,
last_name : record.last_name,
email: record.email
});
});
$scope.editGroupTableParams.reload();
//.error(...)
});
};
$scope.addTarget = function() {
if ($scope.newTarget.email != "") {
$scope.group.targets.push({
email: $scope.newTarget.email
});
$scope.newTarget.email = ""
$scope.editGroupTableParams.reload()
}
};
$scope.removeTarget = function(target) {
$scope.group.targets.splice($scope.group.targets.indexOf(target), 1);
$scope.editGroupTableParams.reload()
};
$scope.cancel = function() {
$modalInstance.dismiss('cancel');
$modalInstance.dismiss();
};
$scope.ok = function(group) {
$modalInstance.dismiss('')
$scope.saveGroup(group)
var newGroup = new GroupService(group);
if ($scope.newGroup) {
newGroup.$save({}, function() {
$scope.groups.push(newGroup);
$modalInstance.close("Group created successfully!")
}, function(error){
$scope.errorFlash(error.data.message)
});
} else {
newGroup.$update({
id: newGroup.id
},function(){
$modalInstance.close("Group updated successfully!")
}, function(error){
$scope.errorFlash(error.data.message)
})
}
};
}
@ -623,11 +662,11 @@ app.controller('TemplateCtrl', function($scope, $modal, TemplateService, ngTable
text: '',
};
}, function() {
$scope.template = {
$scope.template = {
name: '',
html: '',
text: '',
};
};
});
};
@ -648,7 +687,7 @@ app.controller('TemplateCtrl', function($scope, $modal, TemplateService, ngTable
var TemplateModalCtrl = function($scope, TemplateService, $upload, $modalInstance, $modal) {
$scope.editorOptions = {
fullPage: true,
fullPage: true,
allowedContent: true,
}
$scope.errorFlash = function(message) {
@ -673,9 +712,9 @@ var TemplateModalCtrl = function($scope, TemplateService, $upload, $modalInstanc
var reader = new FileReader();
reader.onload = function(e) {
$scope.template.attachments.push({
name : file.name,
content : reader.result.split(",")[1],
type : file.type || "application/octet-stream"
name : file.name,
content : reader.result.split(",")[1],
type : file.type || "application/octet-stream"
})
$scope.$apply();
}
@ -700,9 +739,8 @@ var TemplateModalCtrl = function($scope, TemplateService, $upload, $modalInstanc
// Close the dialog, returning the template
$modalInstance.close("Template created successfully!")
}, function(error){
// Otherwise, leave the dialog open, showing the error
console.log(error.data)
$scope.errorFlash(error.data.message)
// Otherwise, leave the dialog open, showing the error
$scope.errorFlash(error.data.message)
});
} else {
newTemplate.$update({
@ -710,7 +748,6 @@ var TemplateModalCtrl = function($scope, TemplateService, $upload, $modalInstanc
}, function(){
$modalInstance.close("Template updated successfully!")
}, function(error){
console.log(error.data)
$scope.errorFlash(error.data.message)
})
}
@ -733,109 +770,109 @@ var TemplateModalCtrl = function($scope, TemplateService, $upload, $modalInstanc
};
var ImportEmailCtrl = function($scope, $http, $modalInstance) {
$scope.email = {}
$scope.ok = function() {
// Simple POST request example (passing data) :
$http.post('/api/import/email', $scope.email.raw,
{ headers : {"Content-Type" : "text/plain"}}
$scope.email = {}
$scope.ok = function() {
// Simple POST request example (passing data) :
$http.post('/api/import/email', $scope.email.raw,
{ headers : {"Content-Type" : "text/plain"}}
).success(function(data) {console.log("Success: " + data)})
.error(function(data) {console.log("Error: " + data)});
$modalInstance.close($scope.email.raw)
};
$scope.cancel = function() {$modalInstance.dismiss()}
};
$scope.cancel = function() {$modalInstance.dismiss()}
};
app.controller('LandingPageCtrl', function($scope, $modal, LandingPageService, ngTableParams) {
$scope.errorFlash = function(message) {
$scope.flashes = [];
$scope.flashes.push({
"type": "danger",
"message": message,
"icon": "fa-exclamation-circle"
})
}
$scope.successFlash = function(message) {
$scope.flashes = [];
$scope.flashes.push({
"type": "success",
"message": message,
"icon": "fa-check-circle"
})
}
$scope.mainTableParams = new ngTableParams({
page: 1, // show first page
count: 10, // count per page
sorting: {
name: 'asc' // initial sorting
}
}, {
total: 0, // length of data
getData: function($defer, params) {
LandingPageService.query(function(pages) {
$scope.pages = pages
params.total(pages.length)
$defer.resolve(pages.slice((params.page() - 1) * params.count(), params.page() * params.count()));
})
}
});
$scope.editPage = function(page) {
if (page === 'new') {
$scope.newPage = true;
$scope.page = {
name: '',
html: '',
};
} else {
$scope.newPage = false;
$scope.page = page;
}
var modalInstance = $modal.open({
templateUrl: '/js/app/partials/modals/LandingPageModal.html',
controller: LandingPageModalCtrl,
scope: $scope
});
modalInstance.result.then(function(selectedItem) {
$scope.selected = selectedItem;
}, function() {
console.log('closed')
});
};
$scope.savePage = function(page) {
var newPage = new LandingPageService(page);
if ($scope.newPage) {
newPage.$save({}, function() {
$scope.pages.push(newPage);
$scope.mainTableParams.reload()
});
} else {
newPage.$update({
id: newPage.id
})
}
$scope.page = {
name: '',
html: '',
};
}
$scope.deletePage = function(page) {
var deletePage = new LandingPageService(page);
deletePage.$delete({
id: deletePage.id
}, function(response) {
if (response.success) {
$scope.successFlash(response.message)
} else {
$scope.errorFlash(response.message)
}
$scope.mainTableParams.reload();
});
}
$scope.errorFlash = function(message) {
$scope.flashes = [];
$scope.flashes.push({
"type": "danger",
"message": message,
"icon": "fa-exclamation-circle"
})
}
$scope.successFlash = function(message) {
$scope.flashes = [];
$scope.flashes.push({
"type": "success",
"message": message,
"icon": "fa-check-circle"
})
}
$scope.mainTableParams = new ngTableParams({
page: 1, // show first page
count: 10, // count per page
sorting: {
name: 'asc' // initial sorting
}
}, {
total: 0, // length of data
getData: function($defer, params) {
LandingPageService.query(function(pages) {
$scope.pages = pages
params.total(pages.length)
$defer.resolve(pages.slice((params.page() - 1) * params.count(), params.page() * params.count()));
})
}
});
$scope.editPage = function(page) {
if (page === 'new') {
$scope.newPage = true;
$scope.page = {
name: '',
html: '',
};
} else {
$scope.newPage = false;
$scope.page = page;
}
var modalInstance = $modal.open({
templateUrl: '/js/app/partials/modals/LandingPageModal.html',
controller: LandingPageModalCtrl,
scope: $scope
});
modalInstance.result.then(function(selectedItem) {
$scope.selected = selectedItem;
}, function() {
console.log('closed')
});
};
$scope.savePage = function(page) {
var newPage = new LandingPageService(page);
if ($scope.newPage) {
newPage.$save({}, function() {
$scope.pages.push(newPage);
$scope.mainTableParams.reload()
});
} else {
newPage.$update({
id: newPage.id
})
}
$scope.page = {
name: '',
html: '',
};
}
$scope.deletePage = function(page) {
var deletePage = new LandingPageService(page);
deletePage.$delete({
id: deletePage.id
}, function(response) {
if (response.success) {
$scope.successFlash(response.message)
} else {
$scope.errorFlash(response.message)
}
$scope.mainTableParams.reload();
});
}
});
var LandingPageModalCtrl = function($scope, $modalInstance) {
@ -887,13 +924,13 @@ app.controller('SettingsCtrl', function($scope, $http, $window) {
'Content-Type': 'application/x-www-form-urlencoded'
} // set the headers so angular passing info as form data (not request payload)
})
.success(function(response) {
if (response.success) {
$scope.user.api_key = response.data;
$window.user.api_key = response.data;
$scope.successFlash(response.message)
}
})
.success(function(response) {
if (response.success) {
$scope.user.api_key = response.data;
$window.user.api_key = response.data;
$scope.successFlash(response.message)
}
})
}
$scope.save_settings = function() {
$http({
@ -904,12 +941,12 @@ app.controller('SettingsCtrl', function($scope, $http, $window) {
'Content-Type': 'application/x-www-form-urlencoded'
}
})
.success(function(data) {
if (data.success) {
$scope.successFlash(data.message)
} else {
$scope.errorFlash(data.message)
}
})
.success(function(data) {
if (data.success) {
$scope.successFlash(data.message)
} else {
$scope.errorFlash(data.message)
}
})
}
})

+ 2
- 2
static/js/app/partials/campaigns.html View File

@ -25,7 +25,7 @@
Campaigns
</h1>
<div class="row">
<div ng-repeat="flash in flashes" style="text-align:center" class="alert alert-{{flash.type}}">
<div ng-repeat="flash in flashes.main" style="text-align:center" class="alert alert-{{flash.type}}">
<i class="fa {{flash.icon}}"></i> {{flash.message}}
</div>
</div>
@ -43,7 +43,7 @@
<div ng-show="campaigns.length" class="row">
<table ng-table="mainTableParams" class="table table-hover table-striped table-bordered">
<tbody>
<tr ng-repeat="campaign in $data" class="editable-row">
<tr ng-repeat="campaign in $data | orderBy: '-modified_date'" class="editable-row">
<td data-title="'Created Date'" class="col-sm-1">{{campaign.created_date | date:'medium'}}</td>
<td data-title="'Name'" class="col-sm-2">{{campaign.name}}
<div class="btn-group" style="float: right;">

+ 1
- 1
static/js/app/partials/landing_pages.html View File

@ -43,7 +43,7 @@
<div ng-show="pages.length" class="row">
<table ng-table="mainTableParams" class="table table-hover table-striped table-bordered">
<tbody>
<tr ng-repeat="page in $data" class="editable-row">
<tr ng-repeat="page in $data | orderBy: '-modified_date'" class="editable-row">
<td data-title="'Name'" sortable="'name'" class="col-sm-2">{{page.name}}
<div class="btn-group" style="float: right;">
<button type="button" class="btn btn-primary dropdown-toggle edit-button" data-toggle="dropdown">

+ 6
- 1
static/js/app/partials/modals/campaignModal.html View File

@ -4,6 +4,11 @@
<h4 class="modal-title" id="campaignModalLabel">New Campaign</h4>
</div>
<div class="modal-body">
<div class="row">
<div ng-repeat="flash in flashes.modal" style="text-align:center" class="alert alert-{{flash.type}}">
<i class="fa {{flash.icon}}"></i> {{flash.message}}
</div>
</div>
<div class="form-group">
<label for="name">Name:</label>
<input type="text" class="form-control" ng-model="campaign.name" id="name" placeholder="Campaign name" autofocus>
@ -54,7 +59,7 @@
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" ng-click="cancel()">Cancel</button>
<button type="button" class="btn btn-primary" data-dismiss="modal" ng-click="saveCampaign(campaign)" type="submit">Launch Campaign</button>
<button type="button" class="btn btn-primary" data-dismiss="modal" ng-click="ok(campaign)" type="submit">Launch Campaign</button>
</div>
</div>
</div>

+ 5
- 0
static/js/app/partials/modals/userModal.html View File

@ -6,6 +6,11 @@
<h4 class="modal-title" ng-show="newGroup" id="groupModalLabel">New Group</h4>
</div>
<div class="modal-body">
<div class="row">
<div ng-repeat="flash in flashes.modal" style="text-align:center" class="alert alert-{{flash.type}}">
<i class="fa {{flash.icon}}"></i> {{flash.message}}
</div>
</div>
<label class="control-label" for="name">Name:</label>
<div class="form-group">
<input type="text" class="form-control" ng-model="group.name" placeholder="Group name" id="name" autofocus/>

+ 1
- 1
static/js/app/partials/templates.html View File

@ -43,7 +43,7 @@
<div ng-show="templates.length" class="row">
<table ng-table="mainTableParams" class="table table-hover table-striped table-bordered">
<tbody>
<tr ng-repeat="template in $data" class="editable-row">
<tr ng-repeat="template in $data | orderBy: '-modified_date'" class="editable-row">
<td data-title="'Name'" sortable="'name'" class="col-sm-2">{{template.name}}
<div class="btn-group" style="float: right;">
<button type="button" class="btn btn-primary dropdown-toggle edit-button" data-toggle="dropdown">

+ 2
- 2
static/js/app/partials/users.html View File

@ -25,7 +25,7 @@
Users &amp; Groups
</h1>
<div class="row">
<div ng-repeat="flash in flashes" style="text-align:center" class="alert alert-{{flash.type}}">
<div ng-repeat="flash in flashes.main" style="text-align:center" class="alert alert-{{flash.type}}">
<i class="fa {{flash.icon}}"></i> {{flash.message}}
</div>
</div>
@ -43,7 +43,7 @@
<div ng-show="groups.length" class="row">
<table ng-table="mainTableParams" class="table table-hover table-striped table-bordered">
<tbody>
<tr ng-repeat="group in $data" class="editable-row">
<tr ng-repeat="group in $data | orderBy: '-modified_date'" class="editable-row">
<td data-title="'Name'" sortable="'name'" class="col-sm-1">{{group.name}}</td>
<td data-title="'Members'" class="col-sm-2">
<span ng-repeat="target in group.targets | cut:5 track by $index ">

+ 1
- 0
worker/worker.go View File

@ -14,6 +14,7 @@ import (
var Logger = log.New(os.Stdout, " ", log.Ldate|log.Ltime|log.Lshortfile)
// Worker is the background worker that handles watching for new campaigns and sending emails appropriately.
type Worker struct {
Queue chan *models.Campaign
}

Loading…
Cancel
Save