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

320
svc/routes/user_detail.go Normal file
View 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)
}
}