diff --git a/server/action/admin/analytics/router.go b/server/action/admin/analytics/router.go new file mode 100644 index 00000000..a1c5895f --- /dev/null +++ b/server/action/admin/analytics/router.go @@ -0,0 +1,11 @@ +package analytics + +import "github.com/go-chi/chi" + +func Router() chi.Router { + r := chi.NewRouter() + + r.Get("/users", details) + + return r +} diff --git a/server/action/admin/analytics/user.go b/server/action/admin/analytics/user.go new file mode 100644 index 00000000..7b110451 --- /dev/null +++ b/server/action/admin/analytics/user.go @@ -0,0 +1,93 @@ +package analytics + +import ( + "net/http" + "time" + + "github.com/factly/kavach-server/model" + "github.com/factly/x/errorx" + "github.com/factly/x/loggerx" + "github.com/factly/x/renderx" +) + +type Data struct { + Name string `json:"name"` + Count int64 `json:"count"` +} + +type response struct { + TotalUsers int64 `json:"total_users"` + Analytics []Data `json:"analytics"` +} + +type rawdata struct { + Name time.Time + Count int64 +} + +func details(w http.ResponseWriter, r *http.Request) { + result := &response{} + from := r.URL.Query().Get("from") + to := r.URL.Query().Get("to") + + // Get total users + err := model.DB.Model(&model.User{}).Count(&result.TotalUsers).Error + if err != nil { + loggerx.Error(err) + errorx.Render(w, errorx.Parser(errorx.DBError())) + return + } + + if from == "" || to == "" { + renderx.JSON(w, http.StatusOK, result) + return + } + + fromTime, err := time.Parse("2006-01-02", from) + if err != nil { + loggerx.Error(err) + errorx.Render(w, errorx.Parser(errorx.InvalidID())) + return + } + toTime, err := time.Parse("2006-01-02", to) + if err != nil { + loggerx.Error(err) + errorx.Render(w, errorx.Parser(errorx.InvalidID())) + return + } + toTime = toTime.Add(23*time.Hour + 59*time.Minute + 59*time.Second) + + if toTime.Sub(fromTime).Hours() > 24*30*12 { + errorx.Render(w, errorx.Parser(errorx.GetMessage("range cannot be more than 12 months", http.StatusUnprocessableEntity))) + return + } + + var format, level string + + if toTime.Sub(fromTime).Hours() <= 24*30 { + format = "02/01/2006" + level = "day" + } else { + format = "Jan 2006" + level = "month" + } + + tx := model.DB.Model(&model.User{}).Select("count(id) as count, date_trunc('"+level+"', created_at) as name").Where("created_at BETWEEN ? AND ?", fromTime, toTime).Group("name").Order("name") + + rawAnalytics := make([]rawdata, 0) + err = tx.Scan(&rawAnalytics).Error + if err != nil { + loggerx.Error(err) + errorx.Render(w, errorx.Parser(errorx.DBError())) + return + } + + for _, raw := range rawAnalytics { + result.Analytics = append(result.Analytics, Data{ + Name: raw.Name.Format(format), + Count: raw.Count, + }) + } + + renderx.JSON(w, http.StatusOK, result) +} diff --git a/server/action/admin/route.go b/server/action/admin/route.go index 370e9d28..98e6800e 100644 --- a/server/action/admin/route.go +++ b/server/action/admin/route.go @@ -4,6 +4,7 @@ import ( "errors" "net/http" + "github.com/factly/kavach-server/action/admin/analytics" "github.com/factly/kavach-server/action/admin/application" "github.com/factly/kavach-server/action/admin/organisation" "github.com/factly/kavach-server/action/admin/user" @@ -19,6 +20,7 @@ func AdminRouter() chi.Router { r.With(CheckMasterKey).Route("/", func(r chi.Router) { r.Mount("/users", user.Router()) r.Mount("/organisations", organisation.Router()) + r.Mount("/analytics", analytics.Router()) r.Post("/applications/user", application.AddUser) r.Get("/applications/{application_id}", application.ListOrgs) }) diff --git a/server/action/admin/user/create.go b/server/action/admin/user/create.go index 2c6baa41..9d98d2f5 100644 --- a/server/action/admin/user/create.go +++ b/server/action/admin/user/create.go @@ -3,6 +3,7 @@ package user import ( "bytes" "encoding/json" + "errors" "fmt" "net/http" @@ -73,6 +74,18 @@ func create(w http.ResponseWriter, r *http.Request) { return } + if response.StatusCode != http.StatusCreated { + if response.StatusCode == http.StatusConflict { + msg := "user email already exists" + loggerx.Error(errors.New(msg)) + errorx.Render(w, errorx.Parser(errorx.GetMessage(msg, http.StatusConflict))) + return + } + loggerx.Error(errors.New("kratos returned status " + fmt.Sprint(response.StatusCode))) + errorx.Render(w, errorx.Parser(errorx.InternalServerError())) + return + } + responseBody := make(map[string]interface{}) err = json.NewDecoder(response.Body).Decode(&responseBody) diff --git a/server/action/admin/user/list.go b/server/action/admin/user/list.go index 273bb59b..2c468209 100644 --- a/server/action/admin/user/list.go +++ b/server/action/admin/user/list.go @@ -2,6 +2,7 @@ package user import ( "net/http" + "time" "github.com/factly/kavach-server/model" "github.com/factly/x/errorx" @@ -20,6 +21,9 @@ func list(w http.ResponseWriter, r *http.Request) { userIDs := r.URL.Query()["id"] searchQuery := r.URL.Query().Get("q") sort := r.URL.Query().Get("sort") + from := r.URL.Query().Get("from") + to := r.URL.Query().Get("to") + isReport := r.URL.Query().Get("is_report") res := &response{} if sort != "asc" { @@ -28,12 +32,52 @@ func list(w http.ResponseWriter, r *http.Request) { if len(userIDs) == 0 { qs := "%" + searchQuery + "%" - offset, limit := paginationx.Parse(r.URL.Query()) - err := model.DB.Model(&model.User{}).Preload("Organisations").Where("display_name ILIKE ? OR email ILIKE ?", qs, qs).Order("created_at " + sort).Count(&res.Total).Offset(offset).Limit(limit).Find(&res.Nodes).Error - if err != nil { - loggerx.Error(err) - errorx.Render(w, errorx.Parser(errorx.DBError())) - return + tx := model.DB.Model(&model.User{}).Preload("Organisations").Where("display_name ILIKE ? OR email ILIKE ?", qs, qs).Order("created_at " + sort) + + if isReport == "true" { + var fromTime, toTime time.Time + var err error + if from == "" || to == "" { + toTime = time.Now() + fromTime = toTime.AddDate(0, 0, -30) + } else { + fromTime, err = time.Parse("2006-01-02", from) + if err != nil { + loggerx.Error(err) + errorx.Render(w, errorx.Parser(errorx.InvalidID())) + return + } + toTime, err = time.Parse("2006-01-02", to) + if err != nil { + loggerx.Error(err) + errorx.Render(w, errorx.Parser(errorx.InvalidID())) + return + } + toTime = toTime.Add(23*time.Hour + 59*time.Minute + 59*time.Second) + } + + if toTime.Sub(fromTime).Hours() > 24*30*3 { + errorx.Render(w, errorx.Parser(errorx.GetMessage("range cannot be more than 3 months", http.StatusUnprocessableEntity))) + return + } + + tx.Where("created_at BETWEEN ? AND ?", fromTime, toTime) + + err = tx.Find(&res.Nodes).Error + if err != nil { + loggerx.Error(err) + errorx.Render(w, errorx.Parser(errorx.DBError())) + return + } + res.Total = int64(len(res.Nodes)) + } else { + offset, limit := paginationx.Parse(r.URL.Query()) + err := tx.Count(&res.Total).Offset(offset).Limit(limit).Find(&res.Nodes).Error + if err != nil { + loggerx.Error(err) + errorx.Render(w, errorx.Parser(errorx.DBError())) + return + } } } else { err := model.DB.Model(&model.User{}).Preload("Organisations").Where(userIDs).Find(&res.Nodes).Error