feat: update activity logs svc

This commit is contained in:
hanif salafi 2025-04-19 23:08:09 +07:00
parent 2579548a63
commit 95d79ed1df
11 changed files with 97 additions and 7 deletions

View File

@ -6,6 +6,7 @@ type ActivityLogs struct {
ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"`
ActivityTypeId int `json:"activity_type_id" gorm:"type:int4"` ActivityTypeId int `json:"activity_type_id" gorm:"type:int4"`
Url string `json:"url" gorm:"type:varchar"` Url string `json:"url" gorm:"type:varchar"`
VisitorIp *string `json:"visitor_ip" gorm:"type:varchar"`
ArticleId *uint `json:"article_id" gorm:"type:int4"` ArticleId *uint `json:"article_id" gorm:"type:int4"`
UserId *uint `json:"user_id" gorm:"type:int4"` UserId *uint `json:"user_id" gorm:"type:int4"`
CreatedAt time.Time `json:"created_at" gorm:"default:now()"` CreatedAt time.Time `json:"created_at" gorm:"default:now()"`

View File

@ -7,6 +7,7 @@ import (
utilSvc "go-humas-be/utils/service" utilSvc "go-humas-be/utils/service"
"gorm.io/gorm" "gorm.io/gorm"
"log" "log"
"strings"
"time" "time"
) )
@ -26,7 +27,7 @@ func AuditTrailsMiddleware(db *gorm.DB) fiber.Handler {
audit := entity.AuditTrails{ audit := entity.AuditTrails{
Method: c.Method(), Method: c.Method(),
Path: c.OriginalURL(), Path: c.OriginalURL(),
IP: c.IP(), IP: getIP(c),
Status: c.Response().StatusCode(), Status: c.Response().StatusCode(),
UserID: userId, UserID: userId,
RequestHeaders: string(headersJSON), RequestHeaders: string(headersJSON),
@ -50,7 +51,18 @@ func StartAuditTrailCleanup(db *gorm.DB, retention int) {
cutoff := time.Now().AddDate(0, 0, retention) cutoff := time.Now().AddDate(0, 0, retention)
db.Where("created_at < ?", cutoff).Delete(&entity.AuditTrails{}) db.Where("created_at < ?", cutoff).Delete(&entity.AuditTrails{})
log.Printf("Audit Trail Cleanup at: %s", cutoff) log.Printf(" at: %s", cutoff)
} }
}() }()
} }
func getIP(c *fiber.Ctx) string {
ip := c.Get("X-Forwarded-For")
if ip == "" {
ip = c.IP()
}
if strings.Contains(ip, ":") {
ip = strings.Split(ip, ":")[0]
}
return ip
}

View File

@ -9,6 +9,7 @@ import (
utilRes "go-humas-be/utils/response" utilRes "go-humas-be/utils/response"
utilVal "go-humas-be/utils/validator" utilVal "go-humas-be/utils/validator"
"strconv" "strconv"
"strings"
) )
type activityLogsController struct { type activityLogsController struct {
@ -126,6 +127,8 @@ func (_i *activityLogsController) Save(c *fiber.Ctx) error {
} else { } else {
authToken = &getTokenFromHeader authToken = &getTokenFromHeader
} }
visitorIp := GetVisitorIP(c)
req.VisitorIp = &visitorIp
dataResult, err := _i.activityLogsService.Save(*req, authToken) dataResult, err := _i.activityLogsService.Save(*req, authToken)
if err != nil { if err != nil {
return err return err
@ -201,3 +204,14 @@ func (_i *activityLogsController) Delete(c *fiber.Ctx) error {
Messages: utilRes.Messages{"ActivityLogs successfully deleted"}, Messages: utilRes.Messages{"ActivityLogs successfully deleted"},
}) })
} }
func GetVisitorIP(c *fiber.Ctx) string {
ip := c.Get("X-Forwarded-For")
if ip == "" {
ip = c.IP()
}
if strings.Contains(ip, ":") {
ip = strings.Split(ip, ":")[0]
}
return ip
}

View File

@ -8,6 +8,7 @@ import (
"go-humas-be/app/module/activity_logs/request" "go-humas-be/app/module/activity_logs/request"
"go-humas-be/utils/paginator" "go-humas-be/utils/paginator"
"strings" "strings"
"time"
) )
type activityLogsRepository struct { type activityLogsRepository struct {
@ -22,6 +23,8 @@ type ActivityLogsRepository interface {
Create(activityLogs *entity.ActivityLogs) (activityLogsReturn *entity.ActivityLogs, err error) Create(activityLogs *entity.ActivityLogs) (activityLogsReturn *entity.ActivityLogs, err error)
Update(id uint, activityLogs *entity.ActivityLogs) (err error) Update(id uint, activityLogs *entity.ActivityLogs) (err error)
Delete(id uint) (err error) Delete(id uint) (err error)
CountUniqueVisitorAllTime() (count int64, err error)
CountUniqueVisitorToday() (count int64, err error)
} }
func NewActivityLogsRepository(db *database.Database, logger zerolog.Logger) ActivityLogsRepository { func NewActivityLogsRepository(db *database.Database, logger zerolog.Logger) ActivityLogsRepository {
@ -95,3 +98,25 @@ func (_i *activityLogsRepository) Update(id uint, activityLogs *entity.ActivityL
func (_i *activityLogsRepository) Delete(id uint) error { func (_i *activityLogsRepository) Delete(id uint) error {
return _i.DB.DB.Delete(&entity.ActivityLogs{}, id).Error return _i.DB.DB.Delete(&entity.ActivityLogs{}, id).Error
} }
func (_i *activityLogsRepository) CountUniqueVisitorAllTime() (count int64, err error) {
err = _i.DB.DB.
Model(&entity.ActivityLogs{}).
Distinct("visitor_ip").
Count(&count).Error
return
}
func (_i *activityLogsRepository) CountUniqueVisitorToday() (count int64, err error) {
tenMinutesAgo := time.Now().Add(-10 * time.Minute)
err = _i.DB.DB.
Model(&entity.AuditTrails{}).
Where("created_at >= ?", tenMinutesAgo).
Select("ip").
Group("ip").
Count(&count).Error
return
}

View File

@ -20,10 +20,11 @@ type ActivityLogsQueryRequest struct {
} }
type ActivityLogsCreateRequest struct { type ActivityLogsCreateRequest struct {
ActivityTypeId int `json:"activityTypeId" validate:"required"` ActivityTypeId int `json:"activityTypeId" validate:"required"`
Url string `json:"url" validate:"required"` Url string `json:"url" validate:"required"`
ArticleId *uint `json:"articleId"` ArticleId *uint `json:"articleId"`
UserId *uint `json:"userId"` UserId *uint `json:"userId"`
VisitorIp *string `json:"visitorIp"`
} }
func (req ActivityLogsCreateRequest) ToEntity() *entity.ActivityLogs { func (req ActivityLogsCreateRequest) ToEntity() *entity.ActivityLogs {
@ -32,6 +33,7 @@ func (req ActivityLogsCreateRequest) ToEntity() *entity.ActivityLogs {
Url: req.Url, Url: req.Url,
ArticleId: req.ArticleId, ArticleId: req.ArticleId,
UserId: req.UserId, UserId: req.UserId,
VisitorIp: req.VisitorIp,
CreatedAt: time.Now(), CreatedAt: time.Now(),
} }
} }

View File

@ -30,3 +30,7 @@ type UsersResponse struct {
type ParetoLoginResponse struct { type ParetoLoginResponse struct {
AccessToken string `json:"accessToken"` AccessToken string `json:"accessToken"`
} }
type VisitorStatistic struct {
TotalVisitor string `json:"accessToken"`
}

View File

@ -161,3 +161,27 @@ func (_keycloak *KeycloakConfig) SetPasswordWithoutToken(keycloakId string, pass
return nil return nil
} }
func (_keycloak *KeycloakConfig) GetUserSessions() ([]*gocloak.UserSessionRepresentation, error) {
ctx := context.Background()
client := gocloak.NewClient(_keycloak.Cfg.Keycloak.Endpoint)
token, err := client.Login(
ctx,
_keycloak.Cfg.Keycloak.ClientId,
_keycloak.Cfg.Keycloak.ClientSecret,
_keycloak.Cfg.Keycloak.Realm,
_keycloak.Cfg.Keycloak.AdminUsername,
_keycloak.Cfg.Keycloak.AdminPassword,
)
if err != nil {
panic("Something wrong with the credentials or url")
}
sessionData, err := client.GetClientUserSessions(ctx, token.AccessToken, _keycloak.Cfg.Keycloak.Realm, _keycloak.Cfg.Keycloak.ClientId)
if err != nil {
panic("Oh no!, failed to set password :(")
}
return sessionData, nil
}

View File

@ -7,6 +7,6 @@ services:
context: . context: .
dockerfile: Dockerfile dockerfile: Dockerfile
volumes: volumes:
- .:/app - ./data/web-humas-be/logs:/app
ports: ports:
- "8800:8800" - "8800:8800"

View File

@ -9974,6 +9974,9 @@ const docTemplate = `{
}, },
"userId": { "userId": {
"type": "integer" "type": "integer"
},
"visitorIp": {
"type": "string"
} }
} }
}, },

View File

@ -9963,6 +9963,9 @@
}, },
"userId": { "userId": {
"type": "integer" "type": "integer"
},
"visitorIp": {
"type": "string"
} }
} }
}, },

View File

@ -28,6 +28,8 @@ definitions:
type: string type: string
userId: userId:
type: integer type: integer
visitorIp:
type: string
required: required:
- activityTypeId - activityTypeId
- url - url