Skip to content

Commit 2a8c638

Browse files
author
Working On It
committed
feat: record user location and statistic
1 parent b0dd52e commit 2a8c638

File tree

19 files changed

+311
-188
lines changed

19 files changed

+311
-188
lines changed

controllers/activity.go

Lines changed: 5 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
package controllers
1616

1717
import (
18+
"strings"
19+
1820
"github.com/casibase/casibase/object"
1921
"github.com/casibase/casibase/util"
2022
)
@@ -29,31 +31,10 @@ import (
2931
func (c *ApiController) GetActivities() {
3032
days := util.ParseInt(c.Input().Get("days"))
3133
user := c.Input().Get("selectedUser")
32-
field := c.Input().Get("field")
33-
34-
activities, err := object.GetActivities(days, user, field)
35-
if err != nil {
36-
c.ResponseError(err.Error())
37-
return
38-
}
39-
40-
c.ResponseOk(activities)
41-
}
42-
43-
// GetRangeActivities
44-
// @Title GetRangeActivities
45-
// @Tag Activity API
46-
// @Description get range activities
47-
// @Param count query string true "count of range activities"
48-
// @Success 200 {array} object.Activity The Response object
49-
// @router /get-range-activities [get]
50-
func (c *ApiController) GetRangeActivities() {
51-
rangeType := c.Input().Get("rangeType")
52-
count := util.ParseInt(c.Input().Get("count"))
53-
user := c.Input().Get("user")
54-
field := c.Input().Get("field")
34+
fieldParam := c.Input().Get("field")
35+
fields := strings.Split(fieldParam, ",")
5536

56-
activities, err := object.GetRangeActivities(rangeType, count, user, field)
37+
activities, err := object.GetActivities(days, user, fields)
5738
if err != nil {
5839
c.ResponseError(err.Error())
5940
return

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,10 @@ require (
8888
github.com/hhrutter/tiff v1.0.2 // indirect
8989
github.com/holiman/uint256 v1.3.2 // indirect
9090
github.com/mattn/go-runewidth v0.0.16 // indirect
91+
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
9192
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
9293
github.com/rivo/uniseg v0.4.7 // indirect
94+
github.com/schollz/progressbar/v3 v3.18.0 // indirect
9395
github.com/supranational/blst v0.3.14 // indirect
9496
github.com/tidwall/sjson v1.2.5 // indirect
9597
github.com/xlab/treeprint v1.2.0 // indirect

go.sum

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -662,6 +662,8 @@ github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3N
662662
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
663663
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
664664
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
665+
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
666+
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
665667
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
666668
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
667669
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
@@ -854,6 +856,8 @@ github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWR
854856
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
855857
github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww=
856858
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
859+
github.com/schollz/progressbar/v3 v3.18.0 h1:uXdoHABRFmNIjUfte/Ex7WtuyVslrw2wVPQmCN62HpA=
860+
github.com/schollz/progressbar/v3 v3.18.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec=
857861
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
858862
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
859863
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=

object/activity.go

Lines changed: 37 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
package object
1616

1717
import (
18-
"encoding/json"
1918
"errors"
2019
"fmt"
2120
"time"
@@ -41,24 +40,25 @@ func getTargetfieldValue(record *Record, fieldName string) (string, error) {
4140
case "user_agent":
4241
return record.UserAgent, nil
4342
case "response":
44-
var data map[string]interface{}
45-
err := json.Unmarshal([]byte(record.Response), &data)
46-
if err != nil {
47-
return "", err
48-
}
49-
status, ok := data["status"].(string)
50-
if ok {
51-
return status, nil
52-
} else {
53-
return "", err
43+
status := "error"
44+
if record.Response == "{\"status\":\"ok\",\"msg\":\"\"}" {
45+
status = "ok"
5446
}
47+
return status, nil
48+
case "unit":
49+
return record.Unit, nil
50+
case "section":
51+
return record.Section, nil
52+
case "city":
53+
return record.City, nil
54+
case "region":
55+
return record.Region, nil
5556
}
5657
return "", errors.New("no matched field")
5758
}
5859

59-
func GetActivities(days int, user string, fieldName string) ([]*Activity, error) {
60-
records := []*Record{}
61-
err := adapter.engine.Desc("created_time").Find(&records, &Record{Owner: "casbin"})
60+
func GetActivities(days int, user string, fieldNames []string) (map[string][]*Activity, error) {
61+
records, err := getAllRecords()
6262
if err != nil {
6363
return nil, err
6464
}
@@ -67,14 +67,17 @@ func GetActivities(days int, user string, fieldName string) ([]*Activity, error)
6767
// Adjusted to include today in the count by subtracting days-1
6868
startDateTime := now.AddDate(0, 0, -(days - 1)).Truncate(24 * time.Hour)
6969

70-
// Adjusted the size to days, as we're now including today
71-
activities := make([]*Activity, days)
72-
73-
for i := 0; i < days; i++ {
74-
activities[i] = &Activity{
75-
Date: startDateTime.AddDate(0, 0, i).Format("2006-01-02"),
76-
FieldCount: make(map[string]int),
70+
resp := make(map[string][]*Activity)
71+
for j := 0; j < len(fieldNames); j++ {
72+
// Adjusted the size to days, as we're now including today
73+
activities := make([]*Activity, days)
74+
for i := 0; i < days; i++ {
75+
activities[i] = &Activity{
76+
Date: startDateTime.AddDate(0, 0, i).Format("2006-01-02"),
77+
FieldCount: make(map[string]int),
78+
}
7779
}
80+
resp[fieldNames[j]] = activities
7881
}
7982

8083
for _, record := range records {
@@ -92,98 +95,26 @@ func GetActivities(days int, user string, fieldName string) ([]*Activity, error)
9295
if dayIndex < 0 || dayIndex >= days {
9396
continue
9497
}
95-
value, err := getTargetfieldValue(record, fieldName)
96-
if err != nil {
97-
return nil, fmt.Errorf("failed to parse record: name %s, field %s, error: %v", record.Name, fieldName, err)
98-
}
99-
activities[dayIndex].FieldCount[value] += 1
100-
}
101-
102-
for i := 1; i < days; i++ {
103-
for action, count := range activities[i-1].FieldCount {
104-
activities[i].FieldCount[action] += count
105-
}
106-
}
107-
108-
return activities, nil
109-
}
110-
111-
func GetRangeActivities(rangeType string, count int, user string, fieldName string) ([]*Activity, error) {
112-
records, err := GetRecords("casbin")
113-
if err != nil {
114-
return nil, err
115-
}
116-
117-
now := time.Now()
118-
119-
var startDateTime time.Time
120-
121-
switch rangeType {
122-
case "Hour":
123-
startDateTime = now.Truncate(time.Hour).Add(-time.Hour * time.Duration(count-1))
124-
case "Day":
125-
startDateTime = now.Truncate(24*time.Hour).AddDate(0, 0, -(count - 1))
126-
case "Week":
127-
offset := int(time.Monday - now.Weekday())
128-
if offset > 0 {
129-
offset -= 7
130-
}
131-
startOfWeek := now.AddDate(0, 0, offset)
132-
startDateTime = startOfWeek.Truncate(24*time.Hour).AddDate(0, 0, -7*(count-1))
133-
case "Month":
134-
startDateTime = now.Truncate(24*time.Hour).AddDate(0, -count+1, 0)
135-
default:
136-
return nil, fmt.Errorf("invalid range type: %s", rangeType)
137-
}
138-
139-
activities := make([]*Activity, count)
140-
for i := range activities {
141-
activities[i] = &Activity{FieldCount: make(map[string]int)}
142-
}
143-
144-
for _, record := range records {
145-
if !(user == "All" || record.User == user) {
146-
continue
147-
}
148-
recordTime, _ := time.Parse(time.RFC3339, record.CreatedTime)
149-
bucketIndex := -1
150-
151-
switch rangeType {
152-
case "Hour":
153-
bucketIndex = int(recordTime.Sub(startDateTime).Hours())
154-
case "Day":
155-
bucketIndex = int(recordTime.Sub(startDateTime).Hours() / 24)
156-
case "Week":
157-
bucketIndex = int(recordTime.Sub(startDateTime).Hours() / (24 * 7))
158-
case "Month":
159-
monthDiff := (recordTime.Year()-startDateTime.Year())*12 + int(recordTime.Month()-startDateTime.Month())
160-
bucketIndex = monthDiff
161-
}
162-
163-
if bucketIndex >= 0 && bucketIndex < count {
98+
for _, fieldName := range fieldNames {
16499
value, err := getTargetfieldValue(record, fieldName)
165100
if err != nil {
166-
return nil, err
101+
return nil, fmt.Errorf("failed to parse record: name %s, field %s, error: %v", record.Name, fieldName, err)
102+
}
103+
104+
if value != "" {
105+
activity := resp[fieldName]
106+
activity[dayIndex].FieldCount[value] += 1
167107
}
168-
activities[bucketIndex].FieldCount[value] += 1
169108
}
170109
}
171110

172-
// Assign dates and refine price for each usage after calculations are complete
173-
for i, activity := range activities {
174-
var dateLabel string
175-
switch rangeType {
176-
case "Hour":
177-
dateLabel = startDateTime.Add(time.Hour * time.Duration(i)).Format("2006-01-02 15")
178-
case "Day":
179-
dateLabel = startDateTime.AddDate(0, 0, i).Format("2006-01-02")
180-
case "Week":
181-
dateLabel = startDateTime.AddDate(0, 0, 7*i).Format("2006-01-02")
182-
case "Month":
183-
dateLabel = startDateTime.AddDate(0, i, 0).Format("2006-01")
111+
for i := 1; i < days; i++ {
112+
for _, activities := range resp {
113+
for action, count := range activities[i-1].FieldCount {
114+
activities[i].FieldCount[action] += count
115+
}
184116
}
185-
activity.Date = dateLabel
186117
}
187118

188-
return activities, nil
119+
return resp, nil
189120
}

object/record.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ type Record struct {
4545
RequestUri string `xorm:"varchar(1000)" json:"requestUri"`
4646
Action string `xorm:"varchar(1000)" json:"action"`
4747
Language string `xorm:"varchar(100)" json:"language"`
48+
Region string `xorm:"varchar(100)" json:"region"`
49+
City string `xorm:"varchar(100)" json:"city"`
50+
Unit string `xorm:"varchar(100)" json:"unit"`
51+
Section string `xorm:"varchar(100)" json:"section"`
4852

4953
Object string `xorm:"mediumtext" json:"object"`
5054
Response string `xorm:"mediumtext" json:"response"`
@@ -85,6 +89,16 @@ func GetRecords(owner string) ([]*Record, error) {
8589
return records, nil
8690
}
8791

92+
func getAllRecords() ([]*Record, error) {
93+
records := []*Record{}
94+
err := adapter.engine.Desc("id").Find(&records, &Record{})
95+
if err != nil {
96+
return records, err
97+
}
98+
99+
return records, nil
100+
}
101+
88102
func GetPaginationRecords(owner string, offset, limit int, field, value, sortField, sortOrder string) ([]*Record, error) {
89103
records := []*Record{}
90104
session := GetSession(owner, offset, limit, field, value, sortField, sortOrder)
@@ -148,6 +162,14 @@ func UpdateRecord(id string, record *Record) (bool, error) {
148162
return affected != 0, nil
149163
}
150164

165+
func UpdateRecordInternal(id int, record Record) error {
166+
_, err := adapter.engine.ID(id).Update(record)
167+
if err != nil {
168+
return err
169+
}
170+
return nil
171+
}
172+
151173
func UpdateRecordFields(id string, fields map[string]interface{}) (bool, error) {
152174
owner, name := util.GetOwnerAndNameFromId(id)
153175
if p, err := getRecord(owner, name); err != nil {
@@ -194,6 +216,17 @@ func NewRecord(ctx *context.Context) (*Record, error) {
194216
}
195217
languageCode := conf.GetLanguage(language)
196218

219+
// get location info from client ip
220+
locationInfo, err := util.GetInfoFromIP(ip)
221+
if err != nil {
222+
return nil, err
223+
}
224+
region := locationInfo.Country
225+
city := locationInfo.City
226+
if err != nil {
227+
return nil, err
228+
}
229+
197230
record := Record{
198231
Name: util.GenerateId(),
199232
CreatedTime: util.GetCurrentTime(),
@@ -203,6 +236,8 @@ func NewRecord(ctx *context.Context) (*Record, error) {
203236
RequestUri: requestUri,
204237
Action: action,
205238
Language: languageCode,
239+
Region: region,
240+
City: city,
206241
Object: object,
207242
Response: fmt.Sprintf("{\"status\":\"%s\",\"msg\":\"%s\"}", resp.Status, resp.Msg),
208243
IsTriggered: false,

object/record_test.go

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
// Copyright 2025 The Casibase Authors. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
//go:build !skipCi
16+
// +build !skipCi
17+
18+
package object
19+
20+
import (
21+
"strings"
22+
"testing"
23+
24+
"github.com/casibase/casibase/util"
25+
"github.com/schollz/progressbar/v3"
26+
)
27+
28+
func TestUpdateRecordsLocation(t *testing.T) {
29+
InitConfig()
30+
records, err := getAllRecords()
31+
if err != nil {
32+
panic(err)
33+
}
34+
35+
bar := progressbar.Default(int64(len(records)))
36+
37+
var errorRecords []string
38+
39+
for _, r := range records {
40+
bar.Add(1)
41+
if r.Unit == "" || r.Section == "" {
42+
clientIp := r.ClientIp
43+
if strings.Contains(clientIp, " ->") {
44+
parts := strings.Split(clientIp, " ->")
45+
clientIp = strings.TrimSpace(parts[len(parts)-1])
46+
}
47+
locationInfo, err := util.GetInfoFromIP(clientIp)
48+
if err != nil {
49+
errorRecords = append(errorRecords, r.Name+": invalid client ip.")
50+
continue
51+
}
52+
r.Region = locationInfo.Country
53+
r.City = locationInfo.City
54+
err = UpdateRecordInternal(r.Id, *r)
55+
if err != nil {
56+
errorRecords = append(errorRecords, r.Name+": update error.")
57+
}
58+
}
59+
}
60+
61+
t.Log("Log location information update completed.\n")
62+
if len(errorRecords) > 0 {
63+
t.Log("The following logs encountered errors during the update.\n")
64+
for _, msg := range errorRecords {
65+
t.Log(" - " + msg)
66+
}
67+
}
68+
}

0 commit comments

Comments
 (0)