This commit is contained in:
2026-02-11 17:46:22 +03:00
commit eacfacb13b
266 changed files with 51337 additions and 0 deletions

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

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

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

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

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

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