Skip to content

Commit f3480b6

Browse files
server: add analytics and reports
1 parent a6d434a commit f3480b6

5 files changed

Lines changed: 169 additions & 6 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package analytics
2+
3+
import "github.com/go-chi/chi"
4+
5+
func Router() chi.Router {
6+
r := chi.NewRouter()
7+
8+
r.Get("/users", details)
9+
10+
return r
11+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package analytics
2+
3+
import (
4+
"net/http"
5+
"time"
6+
7+
"github.com/factly/kavach-server/model"
8+
"github.com/factly/x/errorx"
9+
"github.com/factly/x/loggerx"
10+
"github.com/factly/x/renderx"
11+
)
12+
13+
type Data struct {
14+
Name string `json:"name"`
15+
Count int64 `json:"count"`
16+
}
17+
18+
type response struct {
19+
TotalUsers int64 `json:"total_users"`
20+
Analytics []Data `json:"analytics"`
21+
}
22+
23+
type rawdata struct {
24+
Name time.Time
25+
Count int64
26+
}
27+
28+
func details(w http.ResponseWriter, r *http.Request) {
29+
result := &response{}
30+
from := r.URL.Query().Get("from")
31+
to := r.URL.Query().Get("to")
32+
33+
// Get total users
34+
err := model.DB.Model(&model.User{}).Count(&result.TotalUsers).Error
35+
if err != nil {
36+
loggerx.Error(err)
37+
errorx.Render(w, errorx.Parser(errorx.DBError()))
38+
return
39+
}
40+
41+
if from == "" || to == "" {
42+
renderx.JSON(w, http.StatusOK, result)
43+
return
44+
}
45+
46+
fromTime, err := time.Parse("2006-01-02", from)
47+
if err != nil {
48+
loggerx.Error(err)
49+
errorx.Render(w, errorx.Parser(errorx.InvalidID()))
50+
return
51+
}
52+
toTime, err := time.Parse("2006-01-02", to)
53+
if err != nil {
54+
loggerx.Error(err)
55+
errorx.Render(w, errorx.Parser(errorx.InvalidID()))
56+
return
57+
}
58+
toTime = toTime.Add(23*time.Hour + 59*time.Minute + 59*time.Second)
59+
60+
if toTime.Sub(fromTime).Hours() > 24*30*12 {
61+
errorx.Render(w, errorx.Parser(errorx.GetMessage("range cannot be more than 12 months", http.StatusUnprocessableEntity)))
62+
return
63+
}
64+
65+
var format, level string
66+
67+
if toTime.Sub(fromTime).Hours() <= 24*30 {
68+
format = "02/01/2006"
69+
level = "day"
70+
} else {
71+
format = "Jan 2006"
72+
level = "month"
73+
}
74+
75+
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")
76+
77+
rawAnalytics := make([]rawdata, 0)
78+
err = tx.Scan(&rawAnalytics).Error
79+
if err != nil {
80+
loggerx.Error(err)
81+
errorx.Render(w, errorx.Parser(errorx.DBError()))
82+
return
83+
}
84+
85+
for _, raw := range rawAnalytics {
86+
result.Analytics = append(result.Analytics, Data{
87+
Name: raw.Name.Format(format),
88+
Count: raw.Count,
89+
})
90+
}
91+
92+
renderx.JSON(w, http.StatusOK, result)
93+
}

server/action/admin/route.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"errors"
55
"net/http"
66

7+
"github.com/factly/kavach-server/action/admin/analytics"
78
"github.com/factly/kavach-server/action/admin/application"
89
"github.com/factly/kavach-server/action/admin/organisation"
910
"github.com/factly/kavach-server/action/admin/user"
@@ -19,6 +20,7 @@ func AdminRouter() chi.Router {
1920
r.With(CheckMasterKey).Route("/", func(r chi.Router) {
2021
r.Mount("/users", user.Router())
2122
r.Mount("/organisations", organisation.Router())
23+
r.Mount("/analytics", analytics.Router())
2224
r.Post("/applications/user", application.AddUser)
2325
r.Get("/applications/{application_id}", application.ListOrgs)
2426
})

server/action/admin/user/create.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package user
33
import (
44
"bytes"
55
"encoding/json"
6+
"errors"
67
"fmt"
78
"net/http"
89

@@ -73,6 +74,18 @@ func create(w http.ResponseWriter, r *http.Request) {
7374
return
7475
}
7576

77+
if response.StatusCode != http.StatusCreated {
78+
if response.StatusCode == http.StatusConflict {
79+
msg := "user email already exists"
80+
loggerx.Error(errors.New(msg))
81+
errorx.Render(w, errorx.Parser(errorx.GetMessage(msg, http.StatusConflict)))
82+
return
83+
}
84+
loggerx.Error(errors.New("kratos returned status " + fmt.Sprint(response.StatusCode)))
85+
errorx.Render(w, errorx.Parser(errorx.InternalServerError()))
86+
return
87+
}
88+
7689
responseBody := make(map[string]interface{})
7790

7891
err = json.NewDecoder(response.Body).Decode(&responseBody)

server/action/admin/user/list.go

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package user
22

33
import (
44
"net/http"
5+
"time"
56

67
"github.com/factly/kavach-server/model"
78
"github.com/factly/x/errorx"
@@ -20,6 +21,9 @@ func list(w http.ResponseWriter, r *http.Request) {
2021
userIDs := r.URL.Query()["id"]
2122
searchQuery := r.URL.Query().Get("q")
2223
sort := r.URL.Query().Get("sort")
24+
from := r.URL.Query().Get("from")
25+
to := r.URL.Query().Get("to")
26+
isReport := r.URL.Query().Get("is_report")
2327
res := &response{}
2428

2529
if sort != "asc" {
@@ -28,12 +32,52 @@ func list(w http.ResponseWriter, r *http.Request) {
2832

2933
if len(userIDs) == 0 {
3034
qs := "%" + searchQuery + "%"
31-
offset, limit := paginationx.Parse(r.URL.Query())
32-
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
33-
if err != nil {
34-
loggerx.Error(err)
35-
errorx.Render(w, errorx.Parser(errorx.DBError()))
36-
return
35+
tx := model.DB.Model(&model.User{}).Preload("Organisations").Where("display_name ILIKE ? OR email ILIKE ?", qs, qs).Order("created_at " + sort)
36+
37+
if isReport == "true" {
38+
var fromTime, toTime time.Time
39+
var err error
40+
if from == "" || to == "" {
41+
toTime = time.Now()
42+
fromTime = toTime.AddDate(0, 0, -30)
43+
} else {
44+
fromTime, err = time.Parse("2006-01-02", from)
45+
if err != nil {
46+
loggerx.Error(err)
47+
errorx.Render(w, errorx.Parser(errorx.InvalidID()))
48+
return
49+
}
50+
toTime, err = time.Parse("2006-01-02", to)
51+
if err != nil {
52+
loggerx.Error(err)
53+
errorx.Render(w, errorx.Parser(errorx.InvalidID()))
54+
return
55+
}
56+
toTime = toTime.Add(23*time.Hour + 59*time.Minute + 59*time.Second)
57+
}
58+
59+
if toTime.Sub(fromTime).Hours() > 24*30*3 {
60+
errorx.Render(w, errorx.Parser(errorx.GetMessage("range cannot be more than 3 months", http.StatusUnprocessableEntity)))
61+
return
62+
}
63+
64+
tx.Where("created_at BETWEEN ? AND ?", fromTime, toTime)
65+
66+
err = tx.Find(&res.Nodes).Error
67+
if err != nil {
68+
loggerx.Error(err)
69+
errorx.Render(w, errorx.Parser(errorx.DBError()))
70+
return
71+
}
72+
res.Total = int64(len(res.Nodes))
73+
} else {
74+
offset, limit := paginationx.Parse(r.URL.Query())
75+
err := tx.Count(&res.Total).Offset(offset).Limit(limit).Find(&res.Nodes).Error
76+
if err != nil {
77+
loggerx.Error(err)
78+
errorx.Render(w, errorx.Parser(errorx.DBError()))
79+
return
80+
}
3781
}
3882
} else {
3983
err := model.DB.Model(&model.User{}).Preload("Organisations").Where(userIDs).Find(&res.Nodes).Error

0 commit comments

Comments
 (0)