init push

This commit is contained in:
2026-07-16 22:16:45 +07:00
commit 8b068bdb10
1021 changed files with 332816 additions and 0 deletions

View 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
}