ilk
This commit is contained in:
35
svc/internal/auditlog/events.go
Normal file
35
svc/internal/auditlog/events.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package auditlog
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
func ForcePasswordChangeStarted(
|
||||
ctx context.Context,
|
||||
targetUserID int64,
|
||||
reason string, // admin_reset | login_enforced
|
||||
) {
|
||||
Write(ActivityLog{
|
||||
UserID: IntUserIDToUUID(int(targetUserID)),
|
||||
ActionType: "force_password_change_started",
|
||||
ActionCategory: "auth",
|
||||
Description: "kullanıcı için zorunlu parola değişimi başlatıldı",
|
||||
IsSuccess: true,
|
||||
ErrorMessage: reason,
|
||||
})
|
||||
}
|
||||
|
||||
func ForcePasswordChangeCompleted(
|
||||
ctx context.Context,
|
||||
userID int64,
|
||||
source string, // reset_link | self_change | admin_reset
|
||||
) {
|
||||
Write(ActivityLog{
|
||||
UserID: IntUserIDToUUID(int(userID)),
|
||||
ActionType: "force_password_change_completed",
|
||||
ActionCategory: "auth",
|
||||
Description: "kullanıcı parolasını başarıyla güncelledi",
|
||||
IsSuccess: true,
|
||||
ErrorMessage: source,
|
||||
})
|
||||
}
|
||||
73
svc/internal/auditlog/helpers.go
Normal file
73
svc/internal/auditlog/helpers.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package auditlog
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
//
|
||||
// =======================================================
|
||||
// 🕵️ AUDIT LOG — HELPER FUNCTIONS
|
||||
// =======================================================
|
||||
// Bu dosya:
|
||||
// - UUID bekleyen kolonlar için int → uuid dönüşümü
|
||||
// - NULL-safe insert yardımcıları
|
||||
// içerir
|
||||
//
|
||||
|
||||
// -------------------------------------------------------
|
||||
// 🔹 IntUserIDToUUID
|
||||
// -------------------------------------------------------
|
||||
// int user_id → deterministic UUID
|
||||
// PostgreSQL uuid kolonu ile %100 uyumlu
|
||||
//
|
||||
// Aynı userID → her zaman aynı UUID
|
||||
func IntUserIDToUUID(userID int) string {
|
||||
if userID <= 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
sum := md5.Sum([]byte(fmt.Sprintf("bssapp-user-%d", userID)))
|
||||
|
||||
return fmt.Sprintf(
|
||||
"%x-%x-%x-%x-%x",
|
||||
sum[0:4],
|
||||
sum[4:6],
|
||||
sum[6:8],
|
||||
sum[8:10],
|
||||
sum[10:16],
|
||||
)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------
|
||||
// 🔹 nullIfZeroTime
|
||||
// -------------------------------------------------------
|
||||
// Zero time → NULL (SQL uyumlu)
|
||||
func nullIfZeroTime(t time.Time) interface{} {
|
||||
if t.IsZero() {
|
||||
return nil
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// -------------------------------------------------------
|
||||
// 🔹 nullIfZeroInt
|
||||
// -------------------------------------------------------
|
||||
// 0 → NULL (SQL uyumlu)
|
||||
func nullIfZeroInt(v int) interface{} {
|
||||
if v == 0 {
|
||||
return nil
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func Int64UserIDToUUID(id int64) string {
|
||||
return IntUserIDToUUID(int(id))
|
||||
}
|
||||
func nullIfEmpty(s string) any {
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
return s
|
||||
}
|
||||
30
svc/internal/auditlog/init.go
Normal file
30
svc/internal/auditlog/init.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package auditlog
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"log"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
logQueue chan ActivityLog
|
||||
dbConn *sql.DB
|
||||
once sync.Once
|
||||
)
|
||||
|
||||
// Init → main.go içinden çağrılacak (tek sefer)
|
||||
func Init(db *sql.DB, bufferSize int) {
|
||||
log.Println("🟢 auditlog Init called, buffer:", bufferSize)
|
||||
|
||||
dbConn = db
|
||||
logQueue = make(chan ActivityLog, bufferSize)
|
||||
|
||||
go logWorker()
|
||||
}
|
||||
|
||||
// Optional: app kapanırken flush/stop istersen
|
||||
func Close() {
|
||||
if logQueue != nil {
|
||||
close(logQueue)
|
||||
}
|
||||
}
|
||||
37
svc/internal/auditlog/model.go
Normal file
37
svc/internal/auditlog/model.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package auditlog
|
||||
|
||||
import "time"
|
||||
|
||||
type ActivityLog struct {
|
||||
// identity
|
||||
UserID string // UUID (auth)
|
||||
DfUsrID int64 // DF user id (mk_dfusr.id)
|
||||
|
||||
Username string
|
||||
RoleCode string
|
||||
|
||||
// action
|
||||
ActionType string
|
||||
ActionCategory string
|
||||
ActionTarget string
|
||||
Description string
|
||||
|
||||
// tech
|
||||
IpAddress string
|
||||
UserAgent string
|
||||
SessionID string
|
||||
|
||||
// timing
|
||||
RequestStartedAt time.Time
|
||||
RequestFinishedAt time.Time
|
||||
DurationMs int
|
||||
HttpStatus int
|
||||
|
||||
// result
|
||||
IsSuccess bool
|
||||
ErrorMessage string
|
||||
TargetDfUsrID int64
|
||||
TargetUsername string
|
||||
ChangeBefore any // map[string]any
|
||||
ChangeAfter any
|
||||
}
|
||||
141
svc/internal/auditlog/worker.go
Normal file
141
svc/internal/auditlog/worker.go
Normal file
@@ -0,0 +1,141 @@
|
||||
package auditlog
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
)
|
||||
|
||||
func toJSONB(v any) any {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
// JSON marshal hata olursa log’u bozmayalım
|
||||
log.Println("⚠️ auditlog json marshal error:", err)
|
||||
return nil
|
||||
}
|
||||
return b // pq jsonb için []byte kabul eder
|
||||
}
|
||||
|
||||
func logWorker() {
|
||||
log.Println("🟢 auditlog worker STARTED")
|
||||
|
||||
for entry := range logQueue {
|
||||
|
||||
// ---------- DFUSR_ID ----------
|
||||
var dfusrID any
|
||||
if entry.DfUsrID > 0 {
|
||||
dfusrID = entry.DfUsrID
|
||||
} else {
|
||||
dfusrID = nil
|
||||
}
|
||||
|
||||
// ---------- USERNAME ----------
|
||||
var username any
|
||||
if entry.Username != "" {
|
||||
username = entry.Username
|
||||
} else {
|
||||
username = nil
|
||||
}
|
||||
|
||||
// ---------- ROLE CODE (SNAPSHOT) ----------
|
||||
roleCode := entry.RoleCode
|
||||
if roleCode == "" {
|
||||
roleCode = "public"
|
||||
}
|
||||
|
||||
// ---------- TARGET ----------
|
||||
var targetDfUsrID any
|
||||
if entry.TargetDfUsrID > 0 {
|
||||
targetDfUsrID = entry.TargetDfUsrID
|
||||
} else {
|
||||
targetDfUsrID = nil
|
||||
}
|
||||
targetUsername := nullIfEmpty(entry.TargetUsername)
|
||||
|
||||
log.Printf(
|
||||
"🧾 auditlog INSERT | actor_dfusr=%v actor_user=%v role=%s %s %s target=%v",
|
||||
dfusrID,
|
||||
username,
|
||||
roleCode,
|
||||
entry.ActionCategory,
|
||||
entry.ActionTarget,
|
||||
targetDfUsrID,
|
||||
)
|
||||
|
||||
_, err := dbConn.Exec(`
|
||||
INSERT INTO mk_user_activity_log (
|
||||
log_id,
|
||||
dfusr_id,
|
||||
username,
|
||||
role_code,
|
||||
|
||||
action_type,
|
||||
action_category,
|
||||
action_target,
|
||||
description,
|
||||
|
||||
ip_address,
|
||||
user_agent,
|
||||
session_id,
|
||||
|
||||
request_started_at,
|
||||
request_finished_at,
|
||||
duration_ms,
|
||||
http_status,
|
||||
|
||||
is_success,
|
||||
error_message,
|
||||
|
||||
-- ✅ NEW
|
||||
target_dfusr_id,
|
||||
target_username,
|
||||
change_before,
|
||||
change_after,
|
||||
|
||||
created_at
|
||||
) VALUES (
|
||||
gen_random_uuid(),
|
||||
$1,$2,$3,
|
||||
$4,$5,$6,$7,
|
||||
$8,$9,$10,
|
||||
$11,$12,$13,$14,
|
||||
$15,$16,
|
||||
$17,$18,$19,$20,
|
||||
now()
|
||||
)
|
||||
`,
|
||||
dfusrID,
|
||||
username,
|
||||
roleCode,
|
||||
|
||||
entry.ActionType,
|
||||
entry.ActionCategory,
|
||||
entry.ActionTarget,
|
||||
entry.Description,
|
||||
|
||||
entry.IpAddress,
|
||||
entry.UserAgent,
|
||||
entry.SessionID,
|
||||
|
||||
nullIfZeroTime(entry.RequestStartedAt),
|
||||
nullIfZeroTime(entry.RequestFinishedAt),
|
||||
nullIfZeroInt(entry.DurationMs),
|
||||
nullIfZeroInt(entry.HttpStatus),
|
||||
|
||||
entry.IsSuccess,
|
||||
entry.ErrorMessage,
|
||||
|
||||
// ✅ NEW
|
||||
targetDfUsrID,
|
||||
targetUsername,
|
||||
toJSONB(entry.ChangeBefore),
|
||||
toJSONB(entry.ChangeAfter),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
log.Println("❌ auditlog insert error:", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
25
svc/internal/auditlog/writer.go
Normal file
25
svc/internal/auditlog/writer.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package auditlog
|
||||
|
||||
import "context"
|
||||
|
||||
func Write(log ActivityLog) {
|
||||
if logQueue == nil {
|
||||
return // sistem henüz init edilmediyse sessizce çık
|
||||
}
|
||||
|
||||
select {
|
||||
case logQueue <- log:
|
||||
// kuyruğa alındı
|
||||
default:
|
||||
// kuyruk dolu → drop edilir, ana akış bozulmaz
|
||||
}
|
||||
}
|
||||
func Enqueue(ctx context.Context, al ActivityLog) {
|
||||
|
||||
select {
|
||||
case logQueue <- al:
|
||||
// ok
|
||||
default:
|
||||
// queue dolu → drop
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user