package handlers import ( "context" "strings" "time" "github.com/gofiber/fiber/v2" "wucher/internal/shared/pkg/userctx" "wucher/internal/shared/pkg/uuidv7" responsedto "wucher/internal/transport/http/dto/response" ) func userRef(id []byte, name string) *responsedto.UserRef { trimmed := strings.TrimSpace(name) if len(id) == 0 && trimmed == "" { return nil } if trimmed == "" && len(id) > 0 { trimmed = userctx.GetDisplayName(context.TODO(), id) } return &responsedto.UserRef{ UUID: idString(id), Name: trimmed, } } // updatedAuditOrCreated returns the "last modified" timestamp (RFC3339) and actor for an // audited row, falling back to the creation timestamp/actor when the row was never // updated after creation (updated_by unset — e.g. seed/legacy rows). This guarantees an // updated_at/updated_by is always presentable instead of silently omitted. func updatedAuditOrCreated(updatedAt, createdAt time.Time, updatedBy []byte, updatedByName string, createdBy []byte, createdByName string) (string, *responsedto.UserRef) { if len(updatedBy) == 0 { updatedBy, updatedByName = createdBy, createdByName } if updatedAt.IsZero() { updatedAt = createdAt } at := "" if !updatedAt.IsZero() { at = updatedAt.UTC().Format(time.RFC3339) } return at, userRef(updatedBy, updatedByName) } func actorUserID(c *fiber.Ctx) []byte { raw := c.Locals("user_id") userID, ok := raw.([]byte) if !ok || len(userID) == 0 { return nil } // Return a copy to avoid accidental mutation of local context values. return append([]byte(nil), userID...) } // uuidStringOrEmpty resolves a user ID to their last name using the global userctx getter // if set; otherwise falls back to the UUID string representation. func uuidStringOrEmpty(id []byte) string { if len(id) == 0 { return "" } if name := userctx.GetDisplayName(context.TODO(), id); name != "" { return name } // Fallback to UUID string when no getter is available or user not found. val, err := uuidv7.BytesToString(id) if err != nil { return "" } return val } // idString converts a resource ID byte slice to a UUID string. // Unlike uuidStringOrEmpty, this does not attempt user name resolution. // Use this for primary keys and resource IDs, not for audit/user reference fields. func idString(id []byte) string { if len(id) == 0 { return "" } val, err := uuidv7.BytesToString(id) if err != nil { return "" } return val } func timeStringOrEmpty(t *time.Time) string { if t == nil || t.IsZero() { return "" } return t.UTC().Format(time.RFC3339) }