activity: route to show activity activity log

filterable by type, sortable by time, and paginated.
This commit is contained in:
Harvey Tindall 2023-10-19 22:10:42 +01:00
parent 9d1c7bba6f
commit df1581d48e
Signed by: hrfee
GPG Key ID: BBC65952848FB1A2
5 changed files with 171 additions and 0 deletions

141
api-activities.go Normal file
View File

@ -0,0 +1,141 @@
package main
import (
"github.com/gin-gonic/gin"
"github.com/timshannon/badgerhold/v4"
)
func stringToActivityType(v string) ActivityType {
switch v {
case "creation":
return ActivityCreation
case "deletion":
return ActivityDeletion
case "disabled":
return ActivityDisabled
case "enabled":
return ActivityEnabled
case "contactLinked":
return ActivityContactLinked
case "contactUnlinked":
return ActivityContactUnlinked
case "changePassword":
return ActivityChangePassword
case "resetPassword":
return ActivityResetPassword
case "createInvite":
return ActivityCreateInvite
case "deleteInvite":
return ActivityDeleteInvite
}
return ActivityUnknown
}
func activityTypeToString(v ActivityType) string {
switch v {
case ActivityCreation:
return "creation"
case ActivityDeletion:
return "deletion"
case ActivityDisabled:
return "disabled"
case ActivityEnabled:
return "enabled"
case ActivityContactLinked:
return "contactLinked"
case ActivityContactUnlinked:
return "contactUnlinked"
case ActivityChangePassword:
return "changePassword"
case ActivityResetPassword:
return "resetPassword"
case ActivityCreateInvite:
return "createInvite"
case ActivityDeleteInvite:
return "deleteInvite"
}
return "unknown"
}
func stringToActivitySource(v string) ActivitySource {
switch v {
case "user":
return ActivityUser
case "admin":
return ActivityAdmin
case "anon":
return ActivityAnon
case "daemon":
return ActivityDaemon
}
return ActivityAnon
}
func activitySourceToString(v ActivitySource) string {
switch v {
case ActivityUser:
return "user"
case ActivityAdmin:
return "admin"
case ActivityAnon:
return "anon"
case ActivityDaemon:
return "daemon"
}
return "anon"
}
// @Summary Get the requested set of activities, Paginated, filtered and sorted.
// @Produce json
// @Param GetActivitiesDTO body GetActivitiesDTO true "search parameters"
// @Success 200 {object} GetActivitiesRespDTO
// @Router /activity [get]
// @Security Bearer
// @tags Activity
func (app *appContext) GetActivities(gc *gin.Context) {
req := GetActivitiesDTO{}
gc.BindJSON(&req)
query := &badgerhold.Query{}
activityType := stringToActivityType(req.Type)
if activityType != ActivityUnknown {
query = badgerhold.Where("Type").Eq(activityType)
}
if req.Ascending {
query = query.Reverse()
}
query = query.SortBy("Time")
if req.Limit == 0 {
req.Limit = 10
}
query = query.Skip(req.Page * req.Limit).Limit(req.Limit)
var results []Activity
err := app.storage.db.Find(&results, query)
if err != nil {
app.err.Printf("Failed to read activities from DB: %v\n", err)
}
resp := GetActivitiesRespDTO{
Activities: make([]ActivityDTO, len(results)),
}
for i, act := range results {
resp.Activities[i] = ActivityDTO{
ID: act.ID,
Type: activityTypeToString(act.Type),
UserID: act.UserID,
SourceType: activitySourceToString(act.SourceType),
Source: act.Source,
InviteCode: act.InviteCode,
Value: act.Value,
Time: act.Time.Unix(),
}
}
gc.JSON(200, resp)
}

View File

@ -638,6 +638,9 @@ func flagPassed(name string) (found bool) {
// @tag.name Profiles & Settings
// @tag.description Profile and settings related operations.
// @tag.name Activity
// @tag.description Routes related to the activity log.
// @tag.name Configuration
// @tag.description jfa-go settings.

View File

@ -430,3 +430,25 @@ type GetMyReferralRespDTO struct {
type EnableDisableReferralDTO struct {
Users []string `json:"users"`
}
type ActivityDTO struct {
ID string `json:"id"`
Type string `json:"type"`
UserID string `json:"user_id"`
SourceType string `json:"source_type"`
Source string `json:"source"`
InviteCode string `json:"invite_code"`
Value string `json:"value"`
Time int64 `json:"time"`
}
type GetActivitiesDTO struct {
Type string `json:"type"` // Type of activity to get. Leave blank for all.
Limit int `json:"limit"`
Page int `json:"page"` // zero-indexed
Ascending bool `json:"ascending"`
}
type GetActivitiesRespDTO struct {
Activities []ActivityDTO `json:"activities"`
}

View File

@ -232,6 +232,8 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
api.DELETE(p+"/profiles/referral/:profile", app.DisableReferralForProfile)
}
api.GET(p+"/activity", app.GetActivities)
if userPageEnabled {
user.GET("/details", app.MyDetails)
user.POST("/contact", app.SetMyContactMethods)

View File

@ -34,6 +34,7 @@ const (
ActivityResetPassword
ActivityCreateInvite
ActivityDeleteInvite
ActivityUnknown
)
type ActivitySource int
@ -46,6 +47,7 @@ const (
)
type Activity struct {
ID string `badgerhold:"key"`
Type ActivityType `badgerhold:"index"`
UserID string // ID of target user. For account creation, this will be the newly created account
SourceType ActivitySource
@ -562,6 +564,7 @@ func (st *Storage) GetActivityKey(k string) (Activity, bool) {
// SetActivityKey stores value v in key k.
func (st *Storage) SetActivityKey(k string, v Activity) {
v.ID = k
err := st.db.Upsert(k, v)
if err != nil {
// fmt.Printf("Failed to set custom content: %v\n", err)