init push
This commit is contained in:
219
internal/app/api/audit_middleware.go
Normal file
219
internal/app/api/audit_middleware.go
Normal file
@@ -0,0 +1,219 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
|
||||
"wucher/internal/service"
|
||||
sharedconst "wucher/internal/shared/const"
|
||||
)
|
||||
|
||||
func AuditMiddleware(auditSvc *service.AuditLogService) fiber.Handler {
|
||||
return func(c *fiber.Ctx) error {
|
||||
if auditSvc == nil {
|
||||
return c.Next()
|
||||
}
|
||||
start := time.Now()
|
||||
err := c.Next()
|
||||
status := c.Response().StatusCode()
|
||||
if status == 0 {
|
||||
status = fiber.StatusOK
|
||||
}
|
||||
|
||||
requestID := c.GetRespHeader(fiber.HeaderXRequestID)
|
||||
if requestID == "" {
|
||||
requestID = localString(c.Locals("requestid"))
|
||||
}
|
||||
|
||||
meta := map[string]any{
|
||||
"latency_ms": time.Since(start).Milliseconds(),
|
||||
}
|
||||
if err != nil {
|
||||
meta["error"] = err.Error()
|
||||
}
|
||||
|
||||
module, action := resolveAuditModuleAction(c)
|
||||
if !shouldPersistAuditByMethod(c.Method()) {
|
||||
return err
|
||||
}
|
||||
|
||||
auditSvc.Log(c.UserContext(), service.AuditLogInput{
|
||||
RequestID: requestID,
|
||||
ActorUserID: localUserID(c.Locals("user_id")),
|
||||
Layer: "api",
|
||||
Module: module,
|
||||
Action: action,
|
||||
Method: c.Method(),
|
||||
Path: c.Path(),
|
||||
StatusCode: status,
|
||||
Success: status >= 200 && status < 400 && err == nil,
|
||||
IP: c.IP(),
|
||||
UserAgent: c.Get(fiber.HeaderUserAgent),
|
||||
Message: "HTTP " + strconv.Itoa(status),
|
||||
Metadata: meta,
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func shouldPersistAuditByMethod(method string) bool {
|
||||
switch strings.ToUpper(strings.TrimSpace(method)) {
|
||||
case fiber.MethodPost, fiber.MethodPut, fiber.MethodPatch, fiber.MethodDelete:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func resolveAuditModuleAction(c *fiber.Ctx) (string, string) {
|
||||
method := c.Method()
|
||||
path := c.Path()
|
||||
normalized := strings.ToLower(strings.TrimSpace(path))
|
||||
parts := strings.Split(strings.Trim(normalized, "/"), "/")
|
||||
if len(parts) < 3 || parts[0] != "api" || parts[1] != "v1" {
|
||||
return sharedconst.AuditModuleUnknown, actionFromMethod(method)
|
||||
}
|
||||
moduleSegment := strings.TrimSpace(parts[2])
|
||||
module := moduleFromPathSegment(moduleSegment)
|
||||
if moduleSegment == "bases" {
|
||||
module = resolveBaseModuleVariant(c, module)
|
||||
}
|
||||
suffix := ""
|
||||
if len(parts) > 3 {
|
||||
suffix = strings.TrimSpace(parts[len(parts)-1])
|
||||
}
|
||||
switch suffix {
|
||||
case "get-all", "dt":
|
||||
return module, sharedconst.AuditActionList
|
||||
case "get":
|
||||
return module, sharedconst.AuditActionGet
|
||||
case "create":
|
||||
return module, sharedconst.AuditActionCreate
|
||||
case "update":
|
||||
return module, sharedconst.AuditActionUpdate
|
||||
case "delete":
|
||||
return module, sharedconst.AuditActionDelete
|
||||
case "export":
|
||||
return module, sharedconst.AuditActionExport
|
||||
}
|
||||
return module, actionFromMethod(method)
|
||||
}
|
||||
|
||||
func resolveBaseModuleVariant(c *fiber.Ctx, fallback string) string {
|
||||
raw := strings.TrimSpace(c.Query("category_type"))
|
||||
if raw == "" {
|
||||
raw = strings.TrimSpace(c.Query("base_category"))
|
||||
}
|
||||
if raw == "" {
|
||||
raw = strings.TrimSpace(c.Query("category"))
|
||||
}
|
||||
switch strings.ToLower(raw) {
|
||||
case "regular":
|
||||
return sharedconst.AuditModuleBaseRegular
|
||||
case "hems":
|
||||
return sharedconst.AuditModuleBaseHEMS
|
||||
default:
|
||||
return fallback
|
||||
}
|
||||
}
|
||||
|
||||
func actionFromMethod(method string) string {
|
||||
switch strings.ToUpper(strings.TrimSpace(method)) {
|
||||
case fiber.MethodGet:
|
||||
return sharedconst.AuditActionGet
|
||||
case fiber.MethodPost:
|
||||
return sharedconst.AuditActionCreate
|
||||
case fiber.MethodPut, fiber.MethodPatch:
|
||||
return sharedconst.AuditActionUpdate
|
||||
case fiber.MethodDelete:
|
||||
return sharedconst.AuditActionDelete
|
||||
default:
|
||||
return sharedconst.AuditActionCustom
|
||||
}
|
||||
}
|
||||
|
||||
func moduleFromPathSegment(segment string) string {
|
||||
switch strings.ToLower(strings.TrimSpace(segment)) {
|
||||
case "air-rescuer-checklist":
|
||||
return sharedconst.AuditModuleAirRescuerChecklist
|
||||
case "audit-logs":
|
||||
return sharedconst.AuditModuleAuditLog
|
||||
case "auth":
|
||||
return sharedconst.AuditModuleAuth
|
||||
case "bases":
|
||||
return sharedconst.AuditModuleBase
|
||||
case "branding", "public":
|
||||
return sharedconst.AuditModuleBranding
|
||||
case "contacts":
|
||||
return sharedconst.AuditModuleContact
|
||||
case "duty-roster":
|
||||
return sharedconst.AuditModuleDutyRoster
|
||||
case "dul":
|
||||
return sharedconst.AuditModuleDUL
|
||||
case "facilities":
|
||||
return sharedconst.AuditModuleFacility
|
||||
case "federal-state":
|
||||
return sharedconst.AuditModuleFederalState
|
||||
case "file-manager":
|
||||
return sharedconst.AuditModuleFileManager
|
||||
case "flight-data":
|
||||
return sharedconst.AuditModuleFlightData
|
||||
case "flights":
|
||||
return sharedconst.AuditModuleFlight
|
||||
case "forces-present":
|
||||
return sharedconst.AuditModuleForcesPresent
|
||||
case "health-insurance-companies":
|
||||
return sharedconst.AuditModuleHealthInsuranceCompany
|
||||
case "helicopter-files":
|
||||
return sharedconst.AuditModuleHelicopterFile
|
||||
case "helicopters":
|
||||
return sharedconst.AuditModuleHelicopter
|
||||
case "hems-operation", "hems-operation-category", "hems-operational-data":
|
||||
return sharedconst.AuditModuleUnknown
|
||||
case "hospital":
|
||||
return sharedconst.AuditModuleHospital
|
||||
case "icao":
|
||||
return sharedconst.AuditModuleICAO
|
||||
case "insurance-patient-data":
|
||||
return sharedconst.AuditModuleInsurancePatientData
|
||||
case "land":
|
||||
return sharedconst.AuditModuleLand
|
||||
case "master-settings":
|
||||
return sharedconst.AuditModuleMasterSetting
|
||||
case "medicine":
|
||||
return sharedconst.AuditModuleMedicine
|
||||
case "mission":
|
||||
return sharedconst.AuditModuleMission
|
||||
case "opc":
|
||||
return sharedconst.AuditModuleOPC
|
||||
case "patient-data":
|
||||
return sharedconst.AuditModulePatientData
|
||||
case "reserve-acs":
|
||||
return sharedconst.AuditModuleReserveAC
|
||||
case "roles":
|
||||
return sharedconst.AuditModuleRole
|
||||
case "users":
|
||||
return sharedconst.AuditModuleUser
|
||||
case "vocation":
|
||||
return sharedconst.AuditModuleVocation
|
||||
default:
|
||||
return sharedconst.AuditModuleUnknown
|
||||
}
|
||||
}
|
||||
|
||||
func localString(v any) string {
|
||||
s, _ := v.(string)
|
||||
return s
|
||||
}
|
||||
|
||||
func localUserID(v any) []byte {
|
||||
id, _ := v.([]byte)
|
||||
if len(id) != 16 {
|
||||
return nil
|
||||
}
|
||||
return id
|
||||
}
|
||||
Reference in New Issue
Block a user