mirror of https://github.com/gophish/gophish
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
parent
ab8dfc8bb3
commit
66dbe2e799
|
@ -69,15 +69,13 @@ func API_Campaigns(w http.ResponseWriter, r *http.Request) {
|
||||||
c := models.Campaign{}
|
c := models.Campaign{}
|
||||||
// Put the request into a campaign
|
// Put the request into a campaign
|
||||||
err := json.NewDecoder(r.Body).Decode(&c)
|
err := json.NewDecoder(r.Body).Decode(&c)
|
||||||
if checkError(err, w, "Invalid Request", http.StatusBadRequest) {
|
if err != nil {
|
||||||
return
|
JSONResponse(w, models.Response{Success: false, Message: "Invalid JSON structure"}, http.StatusBadRequest)
|
||||||
}
|
|
||||||
if m, ok := c.Validate(); !ok {
|
|
||||||
http.Error(w, "Error: "+m, http.StatusBadRequest)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = models.PostCampaign(&c, ctx.Get(r, "user_id").(int64))
|
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
|
return
|
||||||
}
|
}
|
||||||
Worker.Queue <- &c
|
Worker.Queue <- &c
|
||||||
|
@ -91,7 +89,8 @@ func API_Campaigns_Id(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
id, _ := strconv.ParseInt(vars["id"], 0, 64)
|
id, _ := strconv.ParseInt(vars["id"], 0, 64)
|
||||||
c, err := models.GetCampaign(id, ctx.Get(r, "user_id").(int64))
|
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
|
return
|
||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
|
@ -99,7 +98,8 @@ func API_Campaigns_Id(w http.ResponseWriter, r *http.Request) {
|
||||||
JSONResponse(w, c, http.StatusOK)
|
JSONResponse(w, c, http.StatusOK)
|
||||||
case r.Method == "DELETE":
|
case r.Method == "DELETE":
|
||||||
err = models.DeleteCampaign(id)
|
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
|
return
|
||||||
}
|
}
|
||||||
JSONResponse(w, models.Response{Success: true, Message: "Campaign deleted successfully!"}, http.StatusOK)
|
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 {
|
switch {
|
||||||
case r.Method == "GET":
|
case r.Method == "GET":
|
||||||
gs, err := models.GetGroups(ctx.Get(r, "user_id").(int64))
|
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
|
return
|
||||||
}
|
}
|
||||||
JSONResponse(w, gs, http.StatusOK)
|
JSONResponse(w, gs, http.StatusOK)
|
||||||
|
@ -121,7 +122,8 @@ func API_Groups(w http.ResponseWriter, r *http.Request) {
|
||||||
g := models.Group{}
|
g := models.Group{}
|
||||||
// Put the request into a group
|
// Put the request into a group
|
||||||
err := json.NewDecoder(r.Body).Decode(&g)
|
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
|
return
|
||||||
}
|
}
|
||||||
_, err = models.GetGroupByName(g.Name, ctx.Get(r, "user_id").(int64))
|
_, 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)
|
JSONResponse(w, models.Response{Success: false, Message: "Group name already in use"}, http.StatusConflict)
|
||||||
return
|
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.ModifiedDate = time.Now()
|
||||||
g.UserId = ctx.Get(r, "user_id").(int64)
|
g.UserId = ctx.Get(r, "user_id").(int64)
|
||||||
err = models.PostGroup(&g)
|
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
|
return
|
||||||
}
|
}
|
||||||
w.Header().Set("Location", "http://localhost:3333/api/groups/"+string(g.Id))
|
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)
|
vars := mux.Vars(r)
|
||||||
id, _ := strconv.ParseInt(vars["id"], 0, 64)
|
id, _ := strconv.ParseInt(vars["id"], 0, 64)
|
||||||
g, err := models.GetGroup(id, ctx.Get(r, "user_id").(int64))
|
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
|
return
|
||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
|
@ -159,7 +158,8 @@ func API_Groups_Id(w http.ResponseWriter, r *http.Request) {
|
||||||
JSONResponse(w, g, http.StatusOK)
|
JSONResponse(w, g, http.StatusOK)
|
||||||
case r.Method == "DELETE":
|
case r.Method == "DELETE":
|
||||||
err = models.DeleteGroup(&g)
|
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
|
return
|
||||||
}
|
}
|
||||||
JSONResponse(w, models.Response{Success: true, Message: "Group deleted successfully!"}, http.StatusOK)
|
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{}
|
g = models.Group{}
|
||||||
err = json.NewDecoder(r.Body).Decode(&g)
|
err = json.NewDecoder(r.Body).Decode(&g)
|
||||||
if g.Id != id {
|
if g.Id != id {
|
||||||
http.Error(w, "Error: /:id and group_id mismatch", http.StatusBadRequest)
|
JSONResponse(w, models.Response{Success: false, Message: "Error: /:id and group_id mismatch"}, http.StatusInternalServerError)
|
||||||
return
|
|
||||||
}
|
|
||||||
// Check to make sure targets were specified
|
|
||||||
if len(g.Targets) == 0 {
|
|
||||||
http.Error(w, "Error: No targets specified", http.StatusBadRequest)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
g.ModifiedDate = time.Now()
|
g.ModifiedDate = time.Now()
|
||||||
g.UserId = ctx.Get(r, "user_id").(int64)
|
g.UserId = ctx.Get(r, "user_id").(int64)
|
||||||
err = models.PutGroup(&g)
|
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
|
return
|
||||||
}
|
}
|
||||||
JSONResponse(w, g, http.StatusOK)
|
JSONResponse(w, g, http.StatusOK)
|
||||||
|
@ -199,11 +195,11 @@ func API_Templates(w http.ResponseWriter, r *http.Request) {
|
||||||
t := models.Template{}
|
t := models.Template{}
|
||||||
// Put the request into a template
|
// Put the request into a template
|
||||||
err := json.NewDecoder(r.Body).Decode(&t)
|
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
|
return
|
||||||
}
|
}
|
||||||
_, err = models.GetTemplateByName(t.Name, ctx.Get(r, "user_id").(int64))
|
_, err = models.GetTemplateByName(t.Name, ctx.Get(r, "user_id").(int64))
|
||||||
fmt.Println(err)
|
|
||||||
if err != gorm.RecordNotFound {
|
if err != gorm.RecordNotFound {
|
||||||
JSONResponse(w, models.Response{Success: false, Message: "Template name already in use"}, http.StatusConflict)
|
JSONResponse(w, models.Response{Success: false, Message: "Template name already in use"}, http.StatusConflict)
|
||||||
return
|
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)
|
JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusBadRequest)
|
||||||
return
|
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
|
return
|
||||||
}
|
}
|
||||||
JSONResponse(w, t, http.StatusCreated)
|
JSONResponse(w, t, http.StatusCreated)
|
||||||
|
@ -230,8 +228,8 @@ func API_Templates_Id(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
id, _ := strconv.ParseInt(vars["id"], 0, 64)
|
id, _ := strconv.ParseInt(vars["id"], 0, 64)
|
||||||
t, err := models.GetTemplate(id, ctx.Get(r, "user_id").(int64))
|
t, err := models.GetTemplate(id, ctx.Get(r, "user_id").(int64))
|
||||||
if checkError(err, w, "Template not found", http.StatusNotFound) {
|
if err != nil {
|
||||||
Logger.Println(err)
|
JSONResponse(w, models.Response{Success: false, Message: "Template not found"}, http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
|
@ -239,7 +237,8 @@ func API_Templates_Id(w http.ResponseWriter, r *http.Request) {
|
||||||
JSONResponse(w, t, http.StatusOK)
|
JSONResponse(w, t, http.StatusOK)
|
||||||
case r.Method == "DELETE":
|
case r.Method == "DELETE":
|
||||||
err = models.DeleteTemplate(id, ctx.Get(r, "user_id").(int64))
|
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
|
return
|
||||||
}
|
}
|
||||||
JSONResponse(w, models.Response{Success: true, Message: "Template deleted successfully!"}, http.StatusOK)
|
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)
|
Logger.Println(err)
|
||||||
}
|
}
|
||||||
if t.Id != id {
|
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
|
return
|
||||||
}
|
}
|
||||||
err = t.Validate()
|
|
||||||
/* if checkError(err, w, http.StatusBadRequest) {
|
|
||||||
return
|
|
||||||
}*/
|
|
||||||
t.ModifiedDate = time.Now()
|
t.ModifiedDate = time.Now()
|
||||||
t.UserId = ctx.Get(r, "user_id").(int64)
|
t.UserId = ctx.Get(r, "user_id").(int64)
|
||||||
err = models.PutTemplate(&t)
|
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
|
return
|
||||||
}
|
}
|
||||||
JSONResponse(w, t, http.StatusOK)
|
JSONResponse(w, t, http.StatusOK)
|
||||||
|
@ -306,8 +302,8 @@ func API_Pages_Id(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
id, _ := strconv.ParseInt(vars["id"], 0, 64)
|
id, _ := strconv.ParseInt(vars["id"], 0, 64)
|
||||||
p, err := models.GetPage(id, ctx.Get(r, "user_id").(int64))
|
p, err := models.GetPage(id, ctx.Get(r, "user_id").(int64))
|
||||||
if checkError(err, w, "Page not found", http.StatusNotFound) {
|
if err != nil {
|
||||||
Logger.Println(err)
|
JSONResponse(w, models.Response{Success: false, Message: "Page not found"}, http.StatusNotFound)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
|
@ -315,7 +311,8 @@ func API_Pages_Id(w http.ResponseWriter, r *http.Request) {
|
||||||
JSONResponse(w, p, http.StatusOK)
|
JSONResponse(w, p, http.StatusOK)
|
||||||
case r.Method == "DELETE":
|
case r.Method == "DELETE":
|
||||||
err = models.DeletePage(id, ctx.Get(r, "user_id").(int64))
|
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
|
return
|
||||||
}
|
}
|
||||||
JSONResponse(w, models.Response{Success: true, Message: "Page Deleted Successfully"}, http.StatusOK)
|
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
|
// API_Import_Group imports a CSV of group members
|
||||||
func API_Import_Group(w http.ResponseWriter, r *http.Request) {
|
func API_Import_Group(w http.ResponseWriter, r *http.Request) {
|
||||||
ts, err := util.ParseCSV(r)
|
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
|
return
|
||||||
}
|
}
|
||||||
JSONResponse(w, ts, http.StatusOK)
|
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.
|
// is written to the given ResponseWriter.
|
||||||
func JSONResponse(w http.ResponseWriter, d interface{}, c int) {
|
func JSONResponse(w http.ResponseWriter, d interface{}, c int) {
|
||||||
dj, err := json.MarshalIndent(d, "", " ")
|
dj, err := json.MarshalIndent(d, "", " ")
|
||||||
if checkError(err, w, "Error creating JSON response", http.StatusInternalServerError) {
|
if err != nil {
|
||||||
return
|
http.Error(w, "Error creating JSON response", http.StatusInternalServerError)
|
||||||
|
Logger.Println(err)
|
||||||
}
|
}
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.WriteHeader(c)
|
w.WriteHeader(c)
|
||||||
|
|
|
@ -264,16 +264,6 @@ func getTemplate(w http.ResponseWriter, tmpl string) *template.Template {
|
||||||
return template.Must(templates, err)
|
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
|
// Flash handles the rendering flash messages
|
||||||
func Flash(w http.ResponseWriter, r *http.Request, t string, m string) {
|
func Flash(w http.ResponseWriter, r *http.Request, t string, m string) {
|
||||||
session := ctx.Get(r, "session").(*sessions.Session)
|
session := ctx.Get(r, "session").(*sessions.Session)
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -26,17 +27,32 @@ type Campaign struct {
|
||||||
SMTP SMTP `json:"smtp"`
|
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
|
// 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 {
|
switch {
|
||||||
case c.Name == "":
|
case c.Name == "":
|
||||||
return "Must specify campaign name", false
|
return ErrCampaignNameNotSpecified
|
||||||
case len(c.Groups) == 0:
|
case len(c.Groups) == 0:
|
||||||
return "No groups specified", false
|
return ErrGroupNotSpecified
|
||||||
case c.Template.Name == "":
|
case c.Template.Name == "":
|
||||||
return "No template specified", false
|
return ErrTemplateNotSpecified
|
||||||
}
|
}
|
||||||
return "", true
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateStatus changes the campaign status appropriately
|
// 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.
|
// PostCampaign inserts a campaign and all associated records into the database.
|
||||||
func PostCampaign(c *Campaign, uid int64) error {
|
func PostCampaign(c *Campaign, uid int64) error {
|
||||||
|
if err := c.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
// Fill in the details
|
// Fill in the details
|
||||||
c.UserId = uid
|
c.UserId = uid
|
||||||
c.CreatedDate = time.Now()
|
c.CreatedDate = time.Now()
|
||||||
|
@ -115,7 +134,7 @@ func PostCampaign(c *Campaign, uid int64) error {
|
||||||
c.Groups[i], err = GetGroupByName(g.Name, uid)
|
c.Groups[i], err = GetGroupByName(g.Name, uid)
|
||||||
if err == gorm.RecordNotFound {
|
if err == gorm.RecordNotFound {
|
||||||
Logger.Printf("Error - Group %s does not exist", g.Name)
|
Logger.Printf("Error - Group %s does not exist", g.Name)
|
||||||
return err
|
return ErrGroupNotFound
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
Logger.Println(err)
|
Logger.Println(err)
|
||||||
return err
|
return err
|
||||||
|
@ -125,7 +144,7 @@ func PostCampaign(c *Campaign, uid int64) error {
|
||||||
t, err := GetTemplateByName(c.Template.Name, uid)
|
t, err := GetTemplateByName(c.Template.Name, uid)
|
||||||
if err == gorm.RecordNotFound {
|
if err == gorm.RecordNotFound {
|
||||||
Logger.Printf("Error - Template %s does not exist", t.Name)
|
Logger.Printf("Error - Template %s does not exist", t.Name)
|
||||||
return err
|
return ErrTemplateNotFound
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
Logger.Println(err)
|
Logger.Println(err)
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/mail"
|
"net/mail"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jinzhu/gorm"
|
"github.com/jinzhu/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Group contains the fields needed for a user -> group mapping
|
||||||
|
// Groups contain 1..* Targets
|
||||||
type Group struct {
|
type Group struct {
|
||||||
Id int64 `json:"id"`
|
Id int64 `json:"id"`
|
||||||
UserId int64 `json:"-"`
|
UserId int64 `json:"-"`
|
||||||
|
@ -15,11 +18,14 @@ type Group struct {
|
||||||
Targets []Target `json:"targets" sql:"-"`
|
Targets []Target `json:"targets" sql:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GroupTarget is used for a many-to-many relationship between 1..* Groups and 1..* Targets
|
||||||
type GroupTarget struct {
|
type GroupTarget struct {
|
||||||
GroupId int64 `json:"-"`
|
GroupId int64 `json:"-"`
|
||||||
TargetId 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 {
|
type Target struct {
|
||||||
Id int64 `json:"-"`
|
Id int64 `json:"-"`
|
||||||
FirstName string `json:"first_name"`
|
FirstName string `json:"first_name"`
|
||||||
|
@ -27,6 +33,23 @@ type Target struct {
|
||||||
Email string `json:"email"`
|
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.
|
// GetGroups returns the groups owned by the given user.
|
||||||
func GetGroups(uid int64) ([]Group, error) {
|
func GetGroups(uid int64) ([]Group, error) {
|
||||||
gs := []Group{}
|
gs := []Group{}
|
||||||
|
@ -76,7 +99,11 @@ func GetGroupByName(n string, uid int64) (Group, error) {
|
||||||
|
|
||||||
// PostGroup creates a new group in the database.
|
// PostGroup creates a new group in the database.
|
||||||
func PostGroup(g *Group) error {
|
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
|
err = db.Save(g).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Logger.Println(err)
|
Logger.Println(err)
|
||||||
|
@ -189,6 +216,7 @@ func insertTargetIntoGroup(t Target, gid int64) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetTargets performs a many-to-many select to get all the Targets for a Group
|
||||||
func GetTargets(gid int64) ([]Target, error) {
|
func GetTargets(gid int64) ([]Target, error) {
|
||||||
ts := []Target{}
|
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
|
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
|
||||||
|
|
|
@ -20,7 +20,7 @@ type Template struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrTemplateNameNotSpecified is thrown when a template name is not specified
|
// 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
|
// ErrTemplateMissingParameter is thrown when a needed parameter is not provided
|
||||||
var ErrTemplateMissingParameter = errors.New("Need to specify at least plaintext or HTML content")
|
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.
|
// PutTemplate edits an existing template in the database.
|
||||||
// Per the PUT Method RFC, it presumes all data for a template is provided.
|
// Per the PUT Method RFC, it presumes all data for a template is provided.
|
||||||
func PutTemplate(t *Template) error {
|
func PutTemplate(t *Template) error {
|
||||||
|
if err := t.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
// Delete all attachments, and replace with new ones
|
// 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 {
|
if err != nil && err != gorm.RecordNotFound {
|
||||||
Logger.Println(err)
|
Logger.Println(err)
|
||||||
return err
|
return err
|
||||||
|
@ -129,7 +132,7 @@ func PutTemplate(t *Template) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err = db.Debug().Where("id=?", t.Id).Save(t).Error
|
err = db.Where("id=?", t.Id).Save(t).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Logger.Println(err)
|
Logger.Println(err)
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -132,7 +132,23 @@ app.controller('DashboardCtrl', function($scope, $filter, $location, CampaignSer
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
app.controller('CampaignCtrl', function($scope, $modal, CampaignService, GroupService, TemplateService, ngTableParams, $http) {
|
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({
|
$scope.mainTableParams = new ngTableParams({
|
||||||
page: 1, // show first page
|
page: 1, // show first page
|
||||||
count: 10, // count per page
|
count: 10, // count per page
|
||||||
|
@ -158,21 +174,6 @@ app.controller('CampaignCtrl', function($scope, $modal, CampaignService, GroupSe
|
||||||
$scope.templates = templates;
|
$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.newCampaign = function() {
|
||||||
$scope.campaign = {
|
$scope.campaign = {
|
||||||
name: '',
|
name: '',
|
||||||
|
@ -188,11 +189,19 @@ app.controller('CampaignCtrl', function($scope, $modal, CampaignService, GroupSe
|
||||||
scope: $scope
|
scope: $scope
|
||||||
});
|
});
|
||||||
|
|
||||||
modalInstance.result.then(function(selectedItem) {
|
modalInstance.result.then(function(message) {
|
||||||
$scope.selected = selectedItem;
|
$scope.successFlash(message)
|
||||||
|
$scope.campaign = {
|
||||||
|
name: '',
|
||||||
|
groups: [],
|
||||||
|
};
|
||||||
}, function() {
|
}, function() {
|
||||||
console.log('closed')
|
$scope.campaign = {
|
||||||
|
name: '',
|
||||||
|
groups: [],
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
$scope.mainTableParams.reload()
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.editGroupTableParams = new ngTableParams({
|
$scope.editGroupTableParams = new ngTableParams({
|
||||||
|
@ -209,35 +218,64 @@ 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) {
|
$scope.deleteCampaign = function(campaign) {
|
||||||
var deleteCampaign = new CampaignService(campaign);
|
var deleteCampaign = new CampaignService(campaign);
|
||||||
deleteCampaign.$delete({
|
deleteCampaign.$delete({
|
||||||
id: deleteCampaign.id
|
id: deleteCampaign.id
|
||||||
}, function() {
|
}, function(response) {
|
||||||
$scope.successFlash("Campaign deleted successfully")
|
if (response.success) {
|
||||||
|
$scope.successFlash(response.message)
|
||||||
|
} else {
|
||||||
|
$scope.errorFlash(response.message)
|
||||||
|
}
|
||||||
$scope.mainTableParams.reload();
|
$scope.mainTableParams.reload();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var CampaignModalCtrl = function($scope, CampaignService, $modalInstance) {
|
||||||
$scope.errorFlash = function(message) {
|
$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.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) {
|
||||||
|
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.errorFlash = function(message) {
|
||||||
|
$scope.flashes = {"main" : [], "modal" : []};
|
||||||
|
$scope.flashes.main.push({
|
||||||
"type": "danger",
|
"type": "danger",
|
||||||
"message": message,
|
"message": message,
|
||||||
"icon": "fa-exclamation-circle"
|
"icon": "fa-exclamation-circle"
|
||||||
|
@ -245,27 +283,13 @@ app.controller('CampaignCtrl', function($scope, $modal, CampaignService, GroupSe
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.successFlash = function(message) {
|
$scope.successFlash = function(message) {
|
||||||
$scope.flashes.push({
|
$scope.flashes = {"main" : [], "modal" : []};;
|
||||||
|
$scope.flashes.main.push({
|
||||||
"type": "success",
|
"type": "success",
|
||||||
"message": message,
|
"message": message,
|
||||||
"icon": "fa-check-circle"
|
"icon": "fa-check-circle"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
var CampaignModalCtrl = function($scope, $modalInstance) {
|
|
||||||
$scope.cancel = function() {
|
|
||||||
$modalInstance.dismiss('cancel');
|
|
||||||
};
|
|
||||||
$scope.ok = function(campaign) {
|
|
||||||
$modalInstance.dismiss("")
|
|
||||||
$scope.saveCampaign(campaign)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
app.controller('CampaignResultsCtrl', function($scope, $filter, CampaignService, GroupService, ngTableParams, $http, $window) {
|
|
||||||
id = $window.location.hash.split('/')[2];
|
|
||||||
$scope.flashes = []
|
|
||||||
$scope.mainTableParams = new ngTableParams({
|
$scope.mainTableParams = new ngTableParams({
|
||||||
page: 1, // show first page
|
page: 1, // show first page
|
||||||
count: 10, // count per page
|
count: 10, // count per page
|
||||||
|
@ -378,51 +402,43 @@ app.controller('CampaignResultsCtrl', function($scope, $filter, CampaignService,
|
||||||
xAxis: {
|
xAxis: {
|
||||||
type: 'datetime',
|
type: 'datetime',
|
||||||
dateTimeLabelFormats: { // don't display the dummy year
|
dateTimeLabelFormats: { // don't display the dummy year
|
||||||
day: "%e of %b",
|
day: "%e of %b",
|
||||||
hour: "%l:%M",
|
hour: "%l:%M",
|
||||||
second: '%l:%M:%S',
|
second: '%l:%M:%S',
|
||||||
minute: '%l:%M'
|
minute: '%l:%M'
|
||||||
},
|
|
||||||
max: Date.now(),
|
|
||||||
title: {
|
|
||||||
text: 'Date'
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
max: Date.now(),
|
||||||
|
title: {
|
||||||
|
text: 'Date'
|
||||||
|
}
|
||||||
},
|
},
|
||||||
series: [{
|
},
|
||||||
name: "Events",
|
series: [{
|
||||||
data: $scope.campaign.timeline
|
name: "Events",
|
||||||
}],
|
data: $scope.campaign.timeline
|
||||||
title: {
|
}],
|
||||||
text: 'Campaign Timeline'
|
title: {
|
||||||
},
|
text: 'Campaign Timeline'
|
||||||
size: {
|
},
|
||||||
height: 300
|
size: {
|
||||||
},
|
height: 300
|
||||||
credits: {
|
},
|
||||||
enabled: false
|
credits: {
|
||||||
},
|
enabled: false
|
||||||
loading: false,
|
},
|
||||||
}
|
loading: false,
|
||||||
params.total(campaign.results.length)
|
}
|
||||||
$defer.resolve(campaign.results.slice((params.page() - 1) * params.count(), params.page() * params.count()));
|
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"
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
})
|
||||||
|
|
||||||
app.controller('GroupCtrl', function($scope, $modal, GroupService, ngTableParams) {
|
app.controller('GroupCtrl', function($scope, $modal, GroupService, ngTableParams) {
|
||||||
$scope.errorFlash = function(message) {
|
$scope.errorFlash = function(message) {
|
||||||
$scope.flashes = [];
|
$scope.flashes = {"main" : [], "modal" : []};
|
||||||
$scope.flashes.push({
|
$scope.flashes.main.push({
|
||||||
"type": "danger",
|
"type": "danger",
|
||||||
"message": message,
|
"message": message,
|
||||||
"icon": "fa-exclamation-circle"
|
"icon": "fa-exclamation-circle"
|
||||||
|
@ -430,8 +446,8 @@ app.controller('GroupCtrl', function($scope, $modal, GroupService, ngTableParams
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.successFlash = function(message) {
|
$scope.successFlash = function(message) {
|
||||||
$scope.flashes = [];
|
$scope.flashes = {"main" : [], "modal" : []};;
|
||||||
$scope.flashes.push({
|
$scope.flashes.main.push({
|
||||||
"type": "success",
|
"type": "success",
|
||||||
"message": message,
|
"message": message,
|
||||||
"icon": "fa-check-circle"
|
"icon": "fa-check-circle"
|
||||||
|
@ -487,8 +503,64 @@ app.controller('GroupCtrl', function($scope, $modal, GroupService, ngTableParams
|
||||||
controller: GroupModalCtrl,
|
controller: GroupModalCtrl,
|
||||||
scope: $scope
|
scope: $scope
|
||||||
});
|
});
|
||||||
|
modalInstance.result.then(function(message) {
|
||||||
|
$scope.successFlash(message)
|
||||||
|
$scope.group = {
|
||||||
|
name: '',
|
||||||
|
targets: [],
|
||||||
|
};
|
||||||
|
}, function() {
|
||||||
|
$scope.group = {
|
||||||
|
name: '',
|
||||||
|
targets: [],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
$scope.mainTableParams.reload()
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.deleteGroup = function(group) {
|
||||||
|
var deleteGroup = new GroupService(group);
|
||||||
|
deleteGroup.$delete({
|
||||||
|
id: deleteGroup.id
|
||||||
|
}, function(response) {
|
||||||
|
if (response.success) {
|
||||||
|
$scope.successFlash(response.message)
|
||||||
|
} else {
|
||||||
|
$scope.errorFlash(response.message)
|
||||||
|
}
|
||||||
|
$scope.mainTableParams.reload();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
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',
|
||||||
|
data: {},
|
||||||
|
file: $file,
|
||||||
|
}).progress(function(evt) {
|
||||||
|
console.log('percent: ' + parseInt(100.0 * evt.loaded / evt.total));
|
||||||
|
}).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
|
||||||
|
});
|
||||||
|
});
|
||||||
|
$scope.editGroupTableParams.reload();
|
||||||
|
});
|
||||||
|
};
|
||||||
$scope.addTarget = function() {
|
$scope.addTarget = function() {
|
||||||
if ($scope.newTarget.email != "") {
|
if ($scope.newTarget.email != "") {
|
||||||
$scope.group.targets.push({
|
$scope.group.targets.push({
|
||||||
|
@ -502,60 +574,27 @@ app.controller('GroupCtrl', function($scope, $modal, GroupService, ngTableParams
|
||||||
$scope.group.targets.splice($scope.group.targets.indexOf(target), 1);
|
$scope.group.targets.splice($scope.group.targets.indexOf(target), 1);
|
||||||
$scope.editGroupTableParams.reload()
|
$scope.editGroupTableParams.reload()
|
||||||
};
|
};
|
||||||
$scope.saveGroup = function(group) {
|
$scope.cancel = function() {
|
||||||
|
$modalInstance.dismiss();
|
||||||
|
};
|
||||||
|
$scope.ok = function(group) {
|
||||||
var newGroup = new GroupService(group);
|
var newGroup = new GroupService(group);
|
||||||
if ($scope.newGroup) {
|
if ($scope.newGroup) {
|
||||||
newGroup.$save({}, function() {
|
newGroup.$save({}, function() {
|
||||||
$scope.groups.push(newGroup);
|
$scope.groups.push(newGroup);
|
||||||
$scope.mainTableParams.reload()
|
$modalInstance.close("Group created successfully!")
|
||||||
|
}, function(error){
|
||||||
|
$scope.errorFlash(error.data.message)
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
newGroup.$update({
|
newGroup.$update({
|
||||||
id: newGroup.id
|
id: newGroup.id
|
||||||
|
},function(){
|
||||||
|
$modalInstance.close("Group updated successfully!")
|
||||||
|
}, function(error){
|
||||||
|
$scope.errorFlash(error.data.message)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
$scope.group = {
|
|
||||||
name: '',
|
|
||||||
targets: [],
|
|
||||||
};
|
|
||||||
$scope.editGroupTableParams.reload()
|
|
||||||
}
|
|
||||||
$scope.deleteGroup = function(group) {
|
|
||||||
var deleteGroup = new GroupService(group);
|
|
||||||
deleteGroup.$delete({
|
|
||||||
id: deleteGroup.id
|
|
||||||
}, function() {
|
|
||||||
$scope.mainTableParams.reload();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
var GroupModalCtrl = function($scope, $modalInstance, $upload) {
|
|
||||||
$scope.onFileSelect = function($file) {
|
|
||||||
$scope.upload = $upload.upload({
|
|
||||||
url: '/api/import/group',
|
|
||||||
data: {},
|
|
||||||
file: $file,
|
|
||||||
}).progress(function(evt) {
|
|
||||||
console.log('percent: ' + parseInt(100.0 * evt.loaded / evt.total));
|
|
||||||
}).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
|
|
||||||
});
|
|
||||||
});
|
|
||||||
$scope.editGroupTableParams.reload();
|
|
||||||
//.error(...)
|
|
||||||
});
|
|
||||||
};
|
|
||||||
$scope.cancel = function() {
|
|
||||||
$modalInstance.dismiss('cancel');
|
|
||||||
};
|
|
||||||
$scope.ok = function(group) {
|
|
||||||
$modalInstance.dismiss('')
|
|
||||||
$scope.saveGroup(group)
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -623,11 +662,11 @@ app.controller('TemplateCtrl', function($scope, $modal, TemplateService, ngTable
|
||||||
text: '',
|
text: '',
|
||||||
};
|
};
|
||||||
}, function() {
|
}, function() {
|
||||||
$scope.template = {
|
$scope.template = {
|
||||||
name: '',
|
name: '',
|
||||||
html: '',
|
html: '',
|
||||||
text: '',
|
text: '',
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -648,7 +687,7 @@ app.controller('TemplateCtrl', function($scope, $modal, TemplateService, ngTable
|
||||||
|
|
||||||
var TemplateModalCtrl = function($scope, TemplateService, $upload, $modalInstance, $modal) {
|
var TemplateModalCtrl = function($scope, TemplateService, $upload, $modalInstance, $modal) {
|
||||||
$scope.editorOptions = {
|
$scope.editorOptions = {
|
||||||
fullPage: true,
|
fullPage: true,
|
||||||
allowedContent: true,
|
allowedContent: true,
|
||||||
}
|
}
|
||||||
$scope.errorFlash = function(message) {
|
$scope.errorFlash = function(message) {
|
||||||
|
@ -673,9 +712,9 @@ var TemplateModalCtrl = function($scope, TemplateService, $upload, $modalInstanc
|
||||||
var reader = new FileReader();
|
var reader = new FileReader();
|
||||||
reader.onload = function(e) {
|
reader.onload = function(e) {
|
||||||
$scope.template.attachments.push({
|
$scope.template.attachments.push({
|
||||||
name : file.name,
|
name : file.name,
|
||||||
content : reader.result.split(",")[1],
|
content : reader.result.split(",")[1],
|
||||||
type : file.type || "application/octet-stream"
|
type : file.type || "application/octet-stream"
|
||||||
})
|
})
|
||||||
$scope.$apply();
|
$scope.$apply();
|
||||||
}
|
}
|
||||||
|
@ -700,9 +739,8 @@ var TemplateModalCtrl = function($scope, TemplateService, $upload, $modalInstanc
|
||||||
// Close the dialog, returning the template
|
// Close the dialog, returning the template
|
||||||
$modalInstance.close("Template created successfully!")
|
$modalInstance.close("Template created successfully!")
|
||||||
}, function(error){
|
}, function(error){
|
||||||
// Otherwise, leave the dialog open, showing the error
|
// Otherwise, leave the dialog open, showing the error
|
||||||
console.log(error.data)
|
$scope.errorFlash(error.data.message)
|
||||||
$scope.errorFlash(error.data.message)
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
newTemplate.$update({
|
newTemplate.$update({
|
||||||
|
@ -710,7 +748,6 @@ var TemplateModalCtrl = function($scope, TemplateService, $upload, $modalInstanc
|
||||||
}, function(){
|
}, function(){
|
||||||
$modalInstance.close("Template updated successfully!")
|
$modalInstance.close("Template updated successfully!")
|
||||||
}, function(error){
|
}, function(error){
|
||||||
console.log(error.data)
|
|
||||||
$scope.errorFlash(error.data.message)
|
$scope.errorFlash(error.data.message)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -733,109 +770,109 @@ var TemplateModalCtrl = function($scope, TemplateService, $upload, $modalInstanc
|
||||||
};
|
};
|
||||||
|
|
||||||
var ImportEmailCtrl = function($scope, $http, $modalInstance) {
|
var ImportEmailCtrl = function($scope, $http, $modalInstance) {
|
||||||
$scope.email = {}
|
$scope.email = {}
|
||||||
$scope.ok = function() {
|
$scope.ok = function() {
|
||||||
// Simple POST request example (passing data) :
|
// Simple POST request example (passing data) :
|
||||||
$http.post('/api/import/email', $scope.email.raw,
|
$http.post('/api/import/email', $scope.email.raw,
|
||||||
{ headers : {"Content-Type" : "text/plain"}}
|
{ headers : {"Content-Type" : "text/plain"}}
|
||||||
).success(function(data) {console.log("Success: " + data)})
|
).success(function(data) {console.log("Success: " + data)})
|
||||||
.error(function(data) {console.log("Error: " + data)});
|
.error(function(data) {console.log("Error: " + data)});
|
||||||
$modalInstance.close($scope.email.raw)
|
$modalInstance.close($scope.email.raw)
|
||||||
};
|
};
|
||||||
$scope.cancel = function() {$modalInstance.dismiss()}
|
$scope.cancel = function() {$modalInstance.dismiss()}
|
||||||
};
|
};
|
||||||
|
|
||||||
app.controller('LandingPageCtrl', function($scope, $modal, LandingPageService, ngTableParams) {
|
app.controller('LandingPageCtrl', function($scope, $modal, LandingPageService, ngTableParams) {
|
||||||
$scope.errorFlash = function(message) {
|
$scope.errorFlash = function(message) {
|
||||||
$scope.flashes = [];
|
$scope.flashes = [];
|
||||||
$scope.flashes.push({
|
$scope.flashes.push({
|
||||||
"type": "danger",
|
"type": "danger",
|
||||||
"message": message,
|
"message": message,
|
||||||
"icon": "fa-exclamation-circle"
|
"icon": "fa-exclamation-circle"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.successFlash = function(message) {
|
$scope.successFlash = function(message) {
|
||||||
$scope.flashes = [];
|
$scope.flashes = [];
|
||||||
$scope.flashes.push({
|
$scope.flashes.push({
|
||||||
"type": "success",
|
"type": "success",
|
||||||
"message": message,
|
"message": message,
|
||||||
"icon": "fa-check-circle"
|
"icon": "fa-check-circle"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.mainTableParams = new ngTableParams({
|
$scope.mainTableParams = new ngTableParams({
|
||||||
page: 1, // show first page
|
page: 1, // show first page
|
||||||
count: 10, // count per page
|
count: 10, // count per page
|
||||||
sorting: {
|
sorting: {
|
||||||
name: 'asc' // initial sorting
|
name: 'asc' // initial sorting
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
total: 0, // length of data
|
total: 0, // length of data
|
||||||
getData: function($defer, params) {
|
getData: function($defer, params) {
|
||||||
LandingPageService.query(function(pages) {
|
LandingPageService.query(function(pages) {
|
||||||
$scope.pages = pages
|
$scope.pages = pages
|
||||||
params.total(pages.length)
|
params.total(pages.length)
|
||||||
$defer.resolve(pages.slice((params.page() - 1) * params.count(), params.page() * params.count()));
|
$defer.resolve(pages.slice((params.page() - 1) * params.count(), params.page() * params.count()));
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$scope.editPage = function(page) {
|
$scope.editPage = function(page) {
|
||||||
if (page === 'new') {
|
if (page === 'new') {
|
||||||
$scope.newPage = true;
|
$scope.newPage = true;
|
||||||
$scope.page = {
|
$scope.page = {
|
||||||
name: '',
|
name: '',
|
||||||
html: '',
|
html: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
$scope.newPage = false;
|
$scope.newPage = false;
|
||||||
$scope.page = page;
|
$scope.page = page;
|
||||||
}
|
}
|
||||||
var modalInstance = $modal.open({
|
var modalInstance = $modal.open({
|
||||||
templateUrl: '/js/app/partials/modals/LandingPageModal.html',
|
templateUrl: '/js/app/partials/modals/LandingPageModal.html',
|
||||||
controller: LandingPageModalCtrl,
|
controller: LandingPageModalCtrl,
|
||||||
scope: $scope
|
scope: $scope
|
||||||
});
|
});
|
||||||
|
|
||||||
modalInstance.result.then(function(selectedItem) {
|
modalInstance.result.then(function(selectedItem) {
|
||||||
$scope.selected = selectedItem;
|
$scope.selected = selectedItem;
|
||||||
}, function() {
|
}, function() {
|
||||||
console.log('closed')
|
console.log('closed')
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.savePage = function(page) {
|
$scope.savePage = function(page) {
|
||||||
var newPage = new LandingPageService(page);
|
var newPage = new LandingPageService(page);
|
||||||
if ($scope.newPage) {
|
if ($scope.newPage) {
|
||||||
newPage.$save({}, function() {
|
newPage.$save({}, function() {
|
||||||
$scope.pages.push(newPage);
|
$scope.pages.push(newPage);
|
||||||
$scope.mainTableParams.reload()
|
$scope.mainTableParams.reload()
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
newPage.$update({
|
newPage.$update({
|
||||||
id: newPage.id
|
id: newPage.id
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
$scope.page = {
|
$scope.page = {
|
||||||
name: '',
|
name: '',
|
||||||
html: '',
|
html: '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
$scope.deletePage = function(page) {
|
$scope.deletePage = function(page) {
|
||||||
var deletePage = new LandingPageService(page);
|
var deletePage = new LandingPageService(page);
|
||||||
deletePage.$delete({
|
deletePage.$delete({
|
||||||
id: deletePage.id
|
id: deletePage.id
|
||||||
}, function(response) {
|
}, function(response) {
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
$scope.successFlash(response.message)
|
$scope.successFlash(response.message)
|
||||||
} else {
|
} else {
|
||||||
$scope.errorFlash(response.message)
|
$scope.errorFlash(response.message)
|
||||||
}
|
}
|
||||||
$scope.mainTableParams.reload();
|
$scope.mainTableParams.reload();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var LandingPageModalCtrl = function($scope, $modalInstance) {
|
var LandingPageModalCtrl = function($scope, $modalInstance) {
|
||||||
|
@ -887,13 +924,13 @@ app.controller('SettingsCtrl', function($scope, $http, $window) {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded'
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
} // set the headers so angular passing info as form data (not request payload)
|
} // set the headers so angular passing info as form data (not request payload)
|
||||||
})
|
})
|
||||||
.success(function(response) {
|
.success(function(response) {
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
$scope.user.api_key = response.data;
|
$scope.user.api_key = response.data;
|
||||||
$window.user.api_key = response.data;
|
$window.user.api_key = response.data;
|
||||||
$scope.successFlash(response.message)
|
$scope.successFlash(response.message)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
$scope.save_settings = function() {
|
$scope.save_settings = function() {
|
||||||
$http({
|
$http({
|
||||||
|
@ -904,12 +941,12 @@ app.controller('SettingsCtrl', function($scope, $http, $window) {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded'
|
'Content-Type': 'application/x-www-form-urlencoded'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.success(function(data) {
|
.success(function(data) {
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
$scope.successFlash(data.message)
|
$scope.successFlash(data.message)
|
||||||
} else {
|
} else {
|
||||||
$scope.errorFlash(data.message)
|
$scope.errorFlash(data.message)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
Campaigns
|
Campaigns
|
||||||
</h1>
|
</h1>
|
||||||
<div class="row">
|
<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}}
|
<i class="fa {{flash.icon}}"></i> {{flash.message}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -43,7 +43,7 @@
|
||||||
<div ng-show="campaigns.length" class="row">
|
<div ng-show="campaigns.length" class="row">
|
||||||
<table ng-table="mainTableParams" class="table table-hover table-striped table-bordered">
|
<table ng-table="mainTableParams" class="table table-hover table-striped table-bordered">
|
||||||
<tbody>
|
<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="'Created Date'" class="col-sm-1">{{campaign.created_date | date:'medium'}}</td>
|
||||||
<td data-title="'Name'" class="col-sm-2">{{campaign.name}}
|
<td data-title="'Name'" class="col-sm-2">{{campaign.name}}
|
||||||
<div class="btn-group" style="float: right;">
|
<div class="btn-group" style="float: right;">
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
<div ng-show="pages.length" class="row">
|
<div ng-show="pages.length" class="row">
|
||||||
<table ng-table="mainTableParams" class="table table-hover table-striped table-bordered">
|
<table ng-table="mainTableParams" class="table table-hover table-striped table-bordered">
|
||||||
<tbody>
|
<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}}
|
<td data-title="'Name'" sortable="'name'" class="col-sm-2">{{page.name}}
|
||||||
<div class="btn-group" style="float: right;">
|
<div class="btn-group" style="float: right;">
|
||||||
<button type="button" class="btn btn-primary dropdown-toggle edit-button" data-toggle="dropdown">
|
<button type="button" class="btn btn-primary dropdown-toggle edit-button" data-toggle="dropdown">
|
||||||
|
|
|
@ -4,6 +4,11 @@
|
||||||
<h4 class="modal-title" id="campaignModalLabel">New Campaign</h4>
|
<h4 class="modal-title" id="campaignModalLabel">New Campaign</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<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">
|
<div class="form-group">
|
||||||
<label for="name">Name:</label>
|
<label for="name">Name:</label>
|
||||||
<input type="text" class="form-control" ng-model="campaign.name" id="name" placeholder="Campaign name" autofocus>
|
<input type="text" class="form-control" ng-model="campaign.name" id="name" placeholder="Campaign name" autofocus>
|
||||||
|
@ -54,7 +59,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-default" ng-click="cancel()">Cancel</button>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -6,6 +6,11 @@
|
||||||
<h4 class="modal-title" ng-show="newGroup" id="groupModalLabel">New Group</h4>
|
<h4 class="modal-title" ng-show="newGroup" id="groupModalLabel">New Group</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<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>
|
<label class="control-label" for="name">Name:</label>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<input type="text" class="form-control" ng-model="group.name" placeholder="Group name" id="name" autofocus/>
|
<input type="text" class="form-control" ng-model="group.name" placeholder="Group name" id="name" autofocus/>
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
<div ng-show="templates.length" class="row">
|
<div ng-show="templates.length" class="row">
|
||||||
<table ng-table="mainTableParams" class="table table-hover table-striped table-bordered">
|
<table ng-table="mainTableParams" class="table table-hover table-striped table-bordered">
|
||||||
<tbody>
|
<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}}
|
<td data-title="'Name'" sortable="'name'" class="col-sm-2">{{template.name}}
|
||||||
<div class="btn-group" style="float: right;">
|
<div class="btn-group" style="float: right;">
|
||||||
<button type="button" class="btn btn-primary dropdown-toggle edit-button" data-toggle="dropdown">
|
<button type="button" class="btn btn-primary dropdown-toggle edit-button" data-toggle="dropdown">
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
Users & Groups
|
Users & Groups
|
||||||
</h1>
|
</h1>
|
||||||
<div class="row">
|
<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}}
|
<i class="fa {{flash.icon}}"></i> {{flash.message}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -43,7 +43,7 @@
|
||||||
<div ng-show="groups.length" class="row">
|
<div ng-show="groups.length" class="row">
|
||||||
<table ng-table="mainTableParams" class="table table-hover table-striped table-bordered">
|
<table ng-table="mainTableParams" class="table table-hover table-striped table-bordered">
|
||||||
<tbody>
|
<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="'Name'" sortable="'name'" class="col-sm-1">{{group.name}}</td>
|
||||||
<td data-title="'Members'" class="col-sm-2">
|
<td data-title="'Members'" class="col-sm-2">
|
||||||
<span ng-repeat="target in group.targets | cut:5 track by $index ">
|
<span ng-repeat="target in group.targets | cut:5 track by $index ">
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
|
|
||||||
var Logger = log.New(os.Stdout, " ", log.Ldate|log.Ltime|log.Lshortfile)
|
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 {
|
type Worker struct {
|
||||||
Queue chan *models.Campaign
|
Queue chan *models.Campaign
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue