Files
bssapp/svc/routes/user_detail.go
2026-02-11 17:46:22 +03:00

321 lines
7.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package routes
import (
"bssapp-backend/internal/auditlog"
"bssapp-backend/internal/mailer"
"bssapp-backend/internal/security"
"bssapp-backend/models"
"bssapp-backend/queries"
"bytes"
"database/sql"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
"github.com/gorilla/mux"
)
// ======================================================
// 👤 USER DETAIL ROUTE (GET + PUT)
// URL: /api/users/{id}
// ======================================================
type LoginUser struct {
ID int64 `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
RoleID int `json:"role_id"`
RoleCode string `json:"role_code"`
ForcePasswordChange bool `json:"force_password_change"`
}
func UserDetailRoute(db *sql.DB) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
idStr := mux.Vars(r)["id"]
userID, err := strconv.ParseInt(idStr, 10, 64)
if err != nil || userID <= 0 {
http.Error(w, "Geçersiz kullanıcı id", http.StatusBadRequest)
return
}
switch r.Method {
case http.MethodGet:
handleUserGet(db, w, userID)
case http.MethodPut:
handleUserUpdate(db, w, r, userID)
case http.MethodOptions:
w.WriteHeader(http.StatusOK)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
})
}
// ======================================================
// 📥 GET USER
// ======================================================
// ======================================================
// 📥 GET USER (FINAL - mk_dfusr Uyumlu)
// ======================================================
func handleUserGet(db *sql.DB, w http.ResponseWriter, userID int64) {
var u models.UserDetail
// --------------------------------------------------
// 🟢 HEADER
// --------------------------------------------------
err := db.QueryRow(
queries.GetUserHeader,
userID,
).Scan(
&u.ID,
&u.Code, // username
&u.IsActive,
&u.FullName,
&u.Email,
&u.Mobile,
&u.Address,
&u.HasPassword,
)
if err != nil {
// detaylı log (çok önemli)
fmt.Println("❌ [UserDetail] HEADER SCAN ERROR:", err)
if err == sql.ErrNoRows {
http.Error(w, "Kullanıcı bulunamadı", http.StatusNotFound)
return
}
http.Error(w, "User header error", http.StatusInternalServerError)
return
}
// --------------------------------------------------
// 🟢 ROLES
// --------------------------------------------------
roleRows, err := db.Query(queries.GetUserRoles, userID)
if err != nil {
fmt.Println("⚠️ [UserDetail] ROLE QUERY:", err)
} else {
defer roleRows.Close()
for roleRows.Next() {
var code string
if err := roleRows.Scan(&code); err == nil {
u.Roles = append(u.Roles, code)
}
}
}
// --------------------------------------------------
// 🟢 DEPARTMENTS
// --------------------------------------------------
deptRows, err := db.Query(queries.GetUserDepartments, userID)
if err != nil {
fmt.Println("⚠️ [UserDetail] DEPT QUERY:", err)
} else {
defer deptRows.Close()
for deptRows.Next() {
var d models.DeptOption
if err := deptRows.Scan(&d.Code, &d.Title); err == nil {
u.Departments = append(u.Departments, d)
}
}
}
// --------------------------------------------------
// 🟢 PIYASALAR
// --------------------------------------------------
piyRows, err := db.Query(queries.GetUserPiyasalar, userID)
if err != nil {
fmt.Println("⚠️ [UserDetail] PIYASA QUERY:", err)
} else {
defer piyRows.Close()
for piyRows.Next() {
var p models.DeptOption
if err := piyRows.Scan(&p.Code, &p.Title); err == nil {
u.Piyasalar = append(u.Piyasalar, p)
}
}
}
// --------------------------------------------------
// 🟢 NEBIM USERS
// --------------------------------------------------
nebimRows, err := db.Query(queries.GetUserNebim, userID)
if err != nil {
fmt.Println("⚠️ [UserDetail] NEBIM QUERY:", err)
} else {
defer nebimRows.Close()
for nebimRows.Next() {
var n models.NebimOption
if err := nebimRows.Scan(
&n.Username,
&n.UserGroupCode,
); err == nil {
u.NebimUsers = append(u.NebimUsers, n)
}
}
}
// --------------------------------------------------
// 🟢 RESPONSE
// --------------------------------------------------
w.WriteHeader(http.StatusOK)
if err := json.NewEncoder(w).Encode(u); err != nil {
fmt.Println("❌ [UserDetail] JSON ENCODE:", err)
}
}
// ======================================================
// ✍️ UPDATE USER (PUT)
// ======================================================
func handleUserUpdate(db *sql.DB, w http.ResponseWriter, r *http.Request, userID int64) {
raw, _ := io.ReadAll(r.Body)
fmt.Println("🟥 RAW BODY:", string(raw))
// body tekrar okunabilsin diye reset
r.Body = io.NopCloser(bytes.NewBuffer(raw))
var payload models.UserWrite
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
fmt.Printf("🟦 DECODED PAYLOAD: %+v\n", payload)
fmt.Println("❌ JSON DECODE ERROR:", err)
http.Error(w, "Geçersiz payload", http.StatusBadRequest)
return
}
tx, err := db.Begin()
if err != nil {
http.Error(w, "Transaction başlatılamadı", http.StatusInternalServerError)
return
}
defer tx.Rollback()
_, err = tx.Exec(
queries.UpdateUserHeader,
userID,
payload.Code,
payload.IsActive,
payload.FullName,
payload.Email,
payload.Mobile,
payload.Address,
)
if err != nil {
http.Error(w, "Header güncellenemedi", http.StatusInternalServerError)
return
}
tx.Exec(`DELETE FROM dfrole_usr WHERE dfusr_id = $1`, userID)
for _, code := range payload.Roles {
tx.Exec(queries.InsertUserRole, userID, code)
}
tx.Exec(`DELETE FROM dfusr_dprt WHERE dfusr_id = $1`, userID)
for _, d := range payload.Departments {
tx.Exec(queries.InsertUserDepartment, userID, d.Code)
}
tx.Exec(`DELETE FROM dfusr_piyasa WHERE dfusr_id = $1`, userID)
for _, p := range payload.Piyasalar {
tx.Exec(queries.InsertUserPiyasa, userID, p.Code)
}
tx.Exec(`DELETE FROM dfusr_nebim_user WHERE dfusr_id = $1`, userID)
for _, n := range payload.NebimUsers {
tx.Exec(queries.InsertUserNebim, userID, n.Username)
}
if err := tx.Commit(); err != nil {
http.Error(w, "Commit başarısız", http.StatusInternalServerError)
return
}
_ = json.NewEncoder(w).Encode(map[string]any{"success": true})
}
// ======================================================
// 🔐 ADMIN — PASSWORD RESET MAIL
// ======================================================
func SendPasswordResetMailHandler(
db *sql.DB,
mailer *mailer.GraphMailer,
) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
userID, _ := strconv.ParseInt(mux.Vars(r)["id"], 10, 64)
var email string
err := db.QueryRow(`
SELECT email
FROM mk_dfusr
WHERE id = $1 AND is_active = true
`, userID).Scan(&email)
if err != nil || email == "" {
w.WriteHeader(http.StatusOK)
return
}
// 🔑 TOKEN (PLAIN + HASH)
plain, hash, err := security.GenerateResetToken()
if err != nil {
w.WriteHeader(http.StatusOK)
return
}
expires := time.Now().Add(30 * time.Minute)
// 💾 DB → SADECE HASH
_, _ = db.Exec(`
INSERT INTO dfusr_password_reset (dfusr_id, token, expires_at)
VALUES ($1,$2,$3)
`, userID, hash, expires)
// 🔗 URL → PLAIN
resetURL := fmt.Sprintf(
"%s/password-reset/%s",
os.Getenv("FRONTEND_URL"),
plain,
)
_ = mailer.SendPasswordResetMail(email, resetURL)
// 🕵️ AUDIT
auditlog.ForcePasswordChangeStarted(
r.Context(),
userID,
"admin_reset",
)
w.WriteHeader(http.StatusOK)
}
}