ilk
This commit is contained in:
320
svc/routes/user_detail.go
Normal file
320
svc/routes/user_detail.go
Normal file
@@ -0,0 +1,320 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user