From 8738ebbb3590cd8059d151b3a9770bcb614f7068 Mon Sep 17 00:00:00 2001 From: Jordan Wright Date: Thu, 5 Jan 2017 21:48:54 -0600 Subject: [PATCH] Added campaign summary routes: /api/campaigns/summary /api/campaigns/:id/summary This is part of #505 --- controllers/api.go | 34 +++++++++++++++ controllers/route.go | 2 + models/campaign.go | 100 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+) diff --git a/controllers/api.go b/controllers/api.go index 0d57916e..f961981c 100644 --- a/controllers/api.go +++ b/controllers/api.go @@ -87,6 +87,20 @@ func API_Campaigns(w http.ResponseWriter, r *http.Request) { } } +// API_Campaigns_Summary returns the summary for the current user's campaigns +func API_Campaigns_Summary(w http.ResponseWriter, r *http.Request) { + switch { + case r.Method == "GET": + cs, err := models.GetCampaignSummaries(ctx.Get(r, "user_id").(int64)) + if err != nil { + Logger.Println(err) + JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusInternalServerError) + return + } + JSONResponse(w, cs, http.StatusOK) + } +} + // API_Campaigns_Id returns details about the requested campaign. If the campaign is not // valid, API_Campaigns_Id returns null. func API_Campaigns_Id(w http.ResponseWriter, r *http.Request) { @@ -128,6 +142,26 @@ func API_Campaigns_Id_Results(w http.ResponseWriter, r *http.Request) { } } +// API_Campaigns_Id_Summary returns just the summary for a given campaign. +func API_Campaign_Id_Summary(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + id, _ := strconv.ParseInt(vars["id"], 0, 64) + switch { + case r.Method == "GET": + cs, err := models.GetCampaignSummary(id, ctx.Get(r, "user_id").(int64)) + if err != nil { + if err == gorm.ErrRecordNotFound { + JSONResponse(w, models.Response{Success: false, Message: "Campaign not found"}, http.StatusNotFound) + } else { + JSONResponse(w, models.Response{Success: false, Message: err.Error()}, http.StatusInternalServerError) + } + Logger.Println(err) + return + } + JSONResponse(w, cs, http.StatusOK) + } +} + // API_Campaigns_Id_Complete effectively "ends" a campaign. // Future phishing emails clicked will return a simple "404" page. func API_Campaigns_Id_Complete(w http.ResponseWriter, r *http.Request) { diff --git a/controllers/route.go b/controllers/route.go index 56e9da58..aa21bcf2 100644 --- a/controllers/route.go +++ b/controllers/route.go @@ -48,8 +48,10 @@ func CreateAdminRouter() http.Handler { api.HandleFunc("/", Use(API, mid.RequireLogin)) api.HandleFunc("/reset", Use(API_Reset, mid.RequireLogin)) api.HandleFunc("/campaigns/", Use(API_Campaigns, mid.RequireAPIKey)) + api.HandleFunc("/campaigns/summary", Use(API_Campaigns_Summary, mid.RequireAPIKey)) api.HandleFunc("/campaigns/{id:[0-9]+}", Use(API_Campaigns_Id, mid.RequireAPIKey)) api.HandleFunc("/campaigns/{id:[0-9]+}/results", Use(API_Campaigns_Id_Results, mid.RequireAPIKey)) + api.HandleFunc("/campaigns/{id:[0-9]+}/summary", Use(API_Campaign_Id_Summary, mid.RequireAPIKey)) api.HandleFunc("/campaigns/{id:[0-9]+}/complete", Use(API_Campaigns_Id_Complete, mid.RequireAPIKey)) api.HandleFunc("/groups/", Use(API_Groups, mid.RequireAPIKey)) api.HandleFunc("/groups/{id:[0-9]+}", Use(API_Groups_Id, mid.RequireAPIKey)) diff --git a/models/campaign.go b/models/campaign.go index 875fc0c0..6e1b7b36 100644 --- a/models/campaign.go +++ b/models/campaign.go @@ -37,6 +37,33 @@ type CampaignResults struct { Events []Event `json:"timeline,omitempty"` } +// CampaignsSummary is a struct representing the overview of campaigns +type CampaignSummaries struct { + Total int64 `json:"total"` + Campaigns []CampaignSummary `json:"campaigns"` +} + +// CampaignSummary is a struct representing the overview of a single camaign +type CampaignSummary struct { + Id int64 `json:"id"` + CreatedDate time.Time `json:"created_date"` + LaunchDate time.Time `json:"launch_date"` + CompletedDate time.Time `json:"completed_date"` + Status string `json:"status"` + Name string `json:"name"` + Stats CampaignStats `json:"stats"` +} + +// CampaignStats is a struct representing the statistics for a single campaign +type CampaignStats struct { + Total int64 `json:"total"` + EmailsSent int64 `json:"sent"` + OpenedEmail int64 `json:"opened"` + ClickedLink int64 `json:"clicked"` + SubmittedData int64 `json:"submitted_data"` + Error int64 `json:"error"` +} + // ErrCampaignNameNotSpecified indicates there was no template given by the user var ErrCampaignNameNotSpecified = errors.New("Campaign name not specified") @@ -165,6 +192,34 @@ func (c *Campaign) getDetails() error { return nil } +// getCampaignStats returns a CampaignStats object for the campaign with the given campaign ID. +func getCampaignStats(cid int64) (CampaignStats, error) { + s := CampaignStats{} + query := db.Table("results").Where("campaign_id = ?", cid) + err := query.Count(&s.Total).Error + if err != nil { + return s, err + } + err = query.Where("status=?", EVENT_SENT).Count(&s.EmailsSent).Error + if err != nil { + return s, err + } + err = query.Where("status=?", EVENT_OPENED).Count(&s.OpenedEmail).Error + if err != nil { + return s, err + } + query.Where("status=?", EVENT_CLICKED).Count(&s.ClickedLink) + if err != nil { + return s, err + } + query.Where("status=?", EVENT_DATA_SUBMIT).Count(&s.SubmittedData) + if err != nil { + return s, err + } + err = query.Where("status=?", ERROR).Count(&s.Error).Error + return s, err +} + // Event contains the fields for an event // that occurs during the campaign type Event struct { @@ -192,6 +247,51 @@ func GetCampaigns(uid int64) ([]Campaign, error) { return cs, err } +// GetCampaignSummaries gets the summary objects for all the campaigns +// owned by the current user +func GetCampaignSummaries(uid int64) (CampaignSummaries, error) { + overview := CampaignSummaries{} + cs := []CampaignSummary{} + // Get the basic campaign information + query := db.Table("campaigns").Where("user_id = ?", uid) + query = query.Select("id, name, created_date, launch_date, completed_date, status") + err := query.Scan(&cs).Error + if err != nil { + Logger.Println(err) + return overview, err + } + for i := range cs { + s, err := getCampaignStats(cs[i].Id) + if err != nil { + Logger.Println(err) + return overview, err + } + cs[i].Stats = s + } + overview.Total = int64(len(cs)) + overview.Campaigns = cs + return overview, nil +} + +// GetCampaignSummary gets the summary object for a campaign specified by the campaign ID +func GetCampaignSummary(id int64, uid int64) (CampaignSummary, error) { + cs := CampaignSummary{} + query := db.Table("campaigns").Where("user_id = ? AND id = ?", uid, id) + query = query.Select("id, name, created_date, launch_date, completed_date, status") + err := query.Scan(&cs).Error + if err != nil { + Logger.Println(err) + return cs, err + } + s, err := getCampaignStats(cs.Id) + if err != nil { + Logger.Println(err) + return cs, err + } + cs.Stats = s + return cs, nil +} + // GetCampaign returns the campaign, if it exists, specified by the given id and user_id. func GetCampaign(id int64, uid int64) (Campaign, error) { c := Campaign{}