From 40f20d50bbf366d3208f9ec1995cc342e09cdba6 Mon Sep 17 00:00:00 2001 From: hanif salafi Date: Sat, 19 Apr 2025 23:08:09 +0700 Subject: [PATCH] feat: update activity logs svc --- app/database/entity/activity_logs.entity.go | 1 + app/middleware/audit_trails.middleware.go | 16 ++++++++++-- .../controller/activity_logs.controller.go | 14 +++++++++++ .../repository/activity_logs.repository.go | 25 +++++++++++++++++++ .../request/activity_logs.request.go | 10 +++++--- app/module/users/response/users.response.go | 4 +++ config/config/keycloak.config.go | 24 ++++++++++++++++++ docker-compose.yml | 2 +- docs/swagger/docs.go | 3 +++ docs/swagger/swagger.json | 3 +++ docs/swagger/swagger.yaml | 2 ++ 11 files changed, 97 insertions(+), 7 deletions(-) diff --git a/app/database/entity/activity_logs.entity.go b/app/database/entity/activity_logs.entity.go index 3728d96..219cca0 100644 --- a/app/database/entity/activity_logs.entity.go +++ b/app/database/entity/activity_logs.entity.go @@ -6,6 +6,7 @@ type ActivityLogs struct { ID uint `json:"id" gorm:"primaryKey;type:int4;autoIncrement"` ActivityTypeId int `json:"activity_type_id" gorm:"type:int4"` Url string `json:"url" gorm:"type:varchar"` + VisitorIp *string `json:"visitor_ip" gorm:"type:varchar"` ArticleId *uint `json:"article_id" gorm:"type:int4"` UserId *uint `json:"user_id" gorm:"type:int4"` CreatedAt time.Time `json:"created_at" gorm:"default:now()"` diff --git a/app/middleware/audit_trails.middleware.go b/app/middleware/audit_trails.middleware.go index 9f108a7..c30fac2 100644 --- a/app/middleware/audit_trails.middleware.go +++ b/app/middleware/audit_trails.middleware.go @@ -7,6 +7,7 @@ import ( utilSvc "go-humas-be/utils/service" "gorm.io/gorm" "log" + "strings" "time" ) @@ -26,7 +27,7 @@ func AuditTrailsMiddleware(db *gorm.DB) fiber.Handler { audit := entity.AuditTrails{ Method: c.Method(), Path: c.OriginalURL(), - IP: c.IP(), + IP: getIP(c), Status: c.Response().StatusCode(), UserID: userId, RequestHeaders: string(headersJSON), @@ -50,7 +51,18 @@ func StartAuditTrailCleanup(db *gorm.DB, retention int) { cutoff := time.Now().AddDate(0, 0, retention) 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 +} diff --git a/app/module/activity_logs/controller/activity_logs.controller.go b/app/module/activity_logs/controller/activity_logs.controller.go index 8f67046..1637572 100644 --- a/app/module/activity_logs/controller/activity_logs.controller.go +++ b/app/module/activity_logs/controller/activity_logs.controller.go @@ -9,6 +9,7 @@ import ( utilRes "go-humas-be/utils/response" utilVal "go-humas-be/utils/validator" "strconv" + "strings" ) type activityLogsController struct { @@ -126,6 +127,8 @@ func (_i *activityLogsController) Save(c *fiber.Ctx) error { } else { authToken = &getTokenFromHeader } + visitorIp := GetVisitorIP(c) + req.VisitorIp = &visitorIp dataResult, err := _i.activityLogsService.Save(*req, authToken) if err != nil { return err @@ -201,3 +204,14 @@ func (_i *activityLogsController) Delete(c *fiber.Ctx) error { 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 +} diff --git a/app/module/activity_logs/repository/activity_logs.repository.go b/app/module/activity_logs/repository/activity_logs.repository.go index 0c13f33..93788cd 100644 --- a/app/module/activity_logs/repository/activity_logs.repository.go +++ b/app/module/activity_logs/repository/activity_logs.repository.go @@ -8,6 +8,7 @@ import ( "go-humas-be/app/module/activity_logs/request" "go-humas-be/utils/paginator" "strings" + "time" ) type activityLogsRepository struct { @@ -22,6 +23,8 @@ type ActivityLogsRepository interface { Create(activityLogs *entity.ActivityLogs) (activityLogsReturn *entity.ActivityLogs, err error) Update(id uint, activityLogs *entity.ActivityLogs) (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 { @@ -95,3 +98,25 @@ func (_i *activityLogsRepository) Update(id uint, activityLogs *entity.ActivityL func (_i *activityLogsRepository) Delete(id uint) 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 +} diff --git a/app/module/activity_logs/request/activity_logs.request.go b/app/module/activity_logs/request/activity_logs.request.go index 1546a74..60c9613 100644 --- a/app/module/activity_logs/request/activity_logs.request.go +++ b/app/module/activity_logs/request/activity_logs.request.go @@ -20,10 +20,11 @@ type ActivityLogsQueryRequest struct { } type ActivityLogsCreateRequest struct { - ActivityTypeId int `json:"activityTypeId" validate:"required"` - Url string `json:"url" validate:"required"` - ArticleId *uint `json:"articleId"` - UserId *uint `json:"userId"` + ActivityTypeId int `json:"activityTypeId" validate:"required"` + Url string `json:"url" validate:"required"` + ArticleId *uint `json:"articleId"` + UserId *uint `json:"userId"` + VisitorIp *string `json:"visitorIp"` } func (req ActivityLogsCreateRequest) ToEntity() *entity.ActivityLogs { @@ -32,6 +33,7 @@ func (req ActivityLogsCreateRequest) ToEntity() *entity.ActivityLogs { Url: req.Url, ArticleId: req.ArticleId, UserId: req.UserId, + VisitorIp: req.VisitorIp, CreatedAt: time.Now(), } } diff --git a/app/module/users/response/users.response.go b/app/module/users/response/users.response.go index ea52bfe..2b4c309 100644 --- a/app/module/users/response/users.response.go +++ b/app/module/users/response/users.response.go @@ -30,3 +30,7 @@ type UsersResponse struct { type ParetoLoginResponse struct { AccessToken string `json:"accessToken"` } + +type VisitorStatistic struct { + TotalVisitor string `json:"accessToken"` +} diff --git a/config/config/keycloak.config.go b/config/config/keycloak.config.go index 85fb4d5..c80b23b 100644 --- a/config/config/keycloak.config.go +++ b/config/config/keycloak.config.go @@ -161,3 +161,27 @@ func (_keycloak *KeycloakConfig) SetPasswordWithoutToken(keycloakId string, pass 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 +} diff --git a/docker-compose.yml b/docker-compose.yml index fa005ef..3fc9790 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,6 +7,6 @@ services: context: . dockerfile: Dockerfile volumes: - - .:/app + - ./data/web-humas-be/logs:/app ports: - "8800:8800" \ No newline at end of file diff --git a/docs/swagger/docs.go b/docs/swagger/docs.go index 307f811..81410e7 100644 --- a/docs/swagger/docs.go +++ b/docs/swagger/docs.go @@ -9974,6 +9974,9 @@ const docTemplate = `{ }, "userId": { "type": "integer" + }, + "visitorIp": { + "type": "string" } } }, diff --git a/docs/swagger/swagger.json b/docs/swagger/swagger.json index 056d713..0a9be6c 100644 --- a/docs/swagger/swagger.json +++ b/docs/swagger/swagger.json @@ -9963,6 +9963,9 @@ }, "userId": { "type": "integer" + }, + "visitorIp": { + "type": "string" } } }, diff --git a/docs/swagger/swagger.yaml b/docs/swagger/swagger.yaml index cb68dca..2b7495a 100644 --- a/docs/swagger/swagger.yaml +++ b/docs/swagger/swagger.yaml @@ -28,6 +28,8 @@ definitions: type: string userId: type: integer + visitorIp: + type: string required: - activityTypeId - url