837 lines
21 KiB
Go
837 lines
21 KiB
Go
package routes
|
||
|
||
import (
|
||
"bssapp-backend/auth"
|
||
"bssapp-backend/internal/auditlog"
|
||
"bssapp-backend/internal/security"
|
||
"bssapp-backend/models"
|
||
"bssapp-backend/queries"
|
||
"bssapp-backend/repository"
|
||
"bssapp-backend/services"
|
||
"database/sql"
|
||
"encoding/json"
|
||
"fmt"
|
||
"log"
|
||
"net/http"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/gorilla/mux"
|
||
"golang.org/x/crypto/bcrypt"
|
||
)
|
||
|
||
/* ======================================================
|
||
🔐 LOGIN
|
||
====================================================== */
|
||
|
||
type LoginRequest struct {
|
||
Username string `json:"username"`
|
||
Password string `json:"password"`
|
||
}
|
||
|
||
func looksLikeBcryptHash(value string) bool {
|
||
return strings.HasPrefix(value, "$2a$") ||
|
||
strings.HasPrefix(value, "$2b$") ||
|
||
strings.HasPrefix(value, "$2y$")
|
||
}
|
||
|
||
func ensureLegacyUserReadyForSession(db *sql.DB, legacyUser *models.User) (int64, error) {
|
||
desiredID := int64(legacyUser.ID)
|
||
|
||
_, err := db.Exec(`
|
||
INSERT INTO mk_dfusr (
|
||
id,
|
||
username,
|
||
email,
|
||
full_name,
|
||
mobile,
|
||
address,
|
||
is_active,
|
||
password_hash,
|
||
force_password_change,
|
||
created_at,
|
||
updated_at
|
||
)
|
||
VALUES (
|
||
$1,$2,$3,$4,$5,$6,$7,'',true,NOW(),NOW()
|
||
)
|
||
ON CONFLICT (id)
|
||
DO UPDATE SET
|
||
username = EXCLUDED.username,
|
||
email = EXCLUDED.email,
|
||
full_name = COALESCE(NULLIF(EXCLUDED.full_name, ''), mk_dfusr.full_name),
|
||
mobile = COALESCE(NULLIF(EXCLUDED.mobile, ''), mk_dfusr.mobile),
|
||
address = COALESCE(NULLIF(EXCLUDED.address, ''), mk_dfusr.address),
|
||
is_active = EXCLUDED.is_active,
|
||
force_password_change = true,
|
||
updated_at = NOW()
|
||
`,
|
||
desiredID,
|
||
strings.TrimSpace(legacyUser.Username),
|
||
strings.TrimSpace(legacyUser.Email),
|
||
strings.TrimSpace(legacyUser.FullName),
|
||
strings.TrimSpace(legacyUser.Mobile),
|
||
strings.TrimSpace(legacyUser.Address),
|
||
legacyUser.IsActive,
|
||
)
|
||
if err == nil {
|
||
return desiredID, nil
|
||
}
|
||
|
||
mkRepo := repository.NewMkUserRepository(db)
|
||
existing, lookupErr := mkRepo.GetByUsername(legacyUser.Username)
|
||
if lookupErr != nil {
|
||
return 0, err
|
||
}
|
||
|
||
_, updErr := db.Exec(`
|
||
UPDATE mk_dfusr
|
||
SET
|
||
is_active = $1,
|
||
force_password_change = true,
|
||
updated_at = NOW()
|
||
WHERE id = $2
|
||
`, legacyUser.IsActive, existing.ID)
|
||
if updErr != nil {
|
||
return 0, updErr
|
||
}
|
||
|
||
return existing.ID, nil
|
||
}
|
||
|
||
func LoginHandler(db *sql.DB) http.HandlerFunc {
|
||
return func(w http.ResponseWriter, r *http.Request) {
|
||
|
||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||
|
||
// --------------------------------------------------
|
||
// 0️⃣ REQUEST
|
||
// --------------------------------------------------
|
||
var req LoginRequest
|
||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||
http.Error(w, "Geçersiz JSON", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
login := strings.TrimSpace(req.Username)
|
||
pass := req.Password // ⚠️ TRIM YAPMA
|
||
|
||
if login == "" || pass == "" {
|
||
http.Error(w, "Kullanıcı adı veya parola hatalı", http.StatusUnauthorized)
|
||
return
|
||
}
|
||
|
||
mkRepo := repository.NewMkUserRepository(db)
|
||
|
||
// ==================================================
|
||
// 1️⃣ mk_dfusr ÖNCELİKLİ
|
||
// ==================================================
|
||
mkUser, err := mkRepo.GetByUsername(login)
|
||
if err == nil {
|
||
log.Println("🧪 MK USER FROM DB")
|
||
log.Printf("🧪 ID=%d role_id=%d role_code='%s' depts=%v",
|
||
mkUser.ID,
|
||
mkUser.RoleID,
|
||
mkUser.RoleCode,
|
||
mkUser.DepartmentCodes,
|
||
)
|
||
|
||
}
|
||
|
||
log.Printf(
|
||
"🔎 LOGIN DEBUG | mk_user_found=%v err=%v hash_len=%d",
|
||
err == nil,
|
||
err,
|
||
func() int {
|
||
if err == nil {
|
||
return len(strings.TrimSpace(mkUser.PasswordHash))
|
||
}
|
||
return 0
|
||
}(),
|
||
)
|
||
|
||
if err == nil {
|
||
|
||
// mk_dfusr authoritative
|
||
mkHash := strings.TrimSpace(mkUser.PasswordHash)
|
||
if mkHash != "" {
|
||
if looksLikeBcryptHash(mkHash) {
|
||
cmpErr := bcrypt.CompareHashAndPassword(
|
||
[]byte(mkHash),
|
||
[]byte(pass),
|
||
)
|
||
if cmpErr == nil {
|
||
_ = mkRepo.TouchLastLogin(mkUser.ID)
|
||
writeLoginResponse(w, db, mkUser)
|
||
return
|
||
}
|
||
|
||
if !mkUser.ForcePasswordChange {
|
||
http.Error(w, "invalid credentials", http.StatusUnauthorized)
|
||
return
|
||
}
|
||
|
||
log.Printf(
|
||
"LOGIN FALLBACK legacy allowed (force_password_change=true) username=%s id=%d",
|
||
mkUser.Username,
|
||
mkUser.ID,
|
||
)
|
||
} else {
|
||
log.Printf(
|
||
"LOGIN FALLBACK legacy allowed (non-bcrypt mk hash) username=%s id=%d",
|
||
mkUser.Username,
|
||
mkUser.ID,
|
||
)
|
||
}
|
||
}
|
||
// password_hash boşsa legacy fallback
|
||
} else if err != repository.ErrMkUserNotFound {
|
||
log.Println("❌ mk_dfusr lookup error:", err)
|
||
http.Error(w, "Giriş yapılamadı", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
// ==================================================
|
||
// 2️⃣ dfusr FALLBACK (LEGACY)
|
||
// ==================================================
|
||
log.Println("🟡 LEGACY LOGIN PATH:", login)
|
||
|
||
legacyRepo := repository.NewUserRepository(db)
|
||
legacyUser, err := legacyRepo.GetLegacyUserForLogin(login)
|
||
if err != nil || !legacyUser.IsActive {
|
||
http.Error(w, "Kullanıcı adı veya parola hatalı", http.StatusUnauthorized)
|
||
return
|
||
}
|
||
|
||
log.Printf(
|
||
"🔎 LOGIN DEBUG | legacy_upass_len=%d prefix=%s",
|
||
len(strings.TrimSpace(legacyUser.Upass)),
|
||
func() string {
|
||
if len(legacyUser.Upass) >= 4 {
|
||
return legacyUser.Upass[:4]
|
||
}
|
||
return legacyUser.Upass
|
||
}(),
|
||
)
|
||
|
||
if !services.CheckPasswordWithLegacy(legacyUser, pass) {
|
||
http.Error(w, "Kullanıcı adı veya parola hatalı", http.StatusUnauthorized)
|
||
return
|
||
}
|
||
|
||
// ==================================================
|
||
// 3️⃣ LEGACY SESSION (PENDING MIGRATION)
|
||
// - mk_dfusr migration is completed in /api/password/change
|
||
// ==================================================
|
||
mkID, err := ensureLegacyUserReadyForSession(db, legacyUser)
|
||
if err != nil {
|
||
log.Printf("LEGACY LOGIN MIGRATION BIND FAILED username=%s err=%v", login, err)
|
||
http.Error(w, "Giriş yapılamadı", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
mkUser = &models.MkUser{
|
||
ID: mkID,
|
||
Username: legacyUser.Username,
|
||
Email: legacyUser.Email,
|
||
IsActive: legacyUser.IsActive,
|
||
RoleID: int64(legacyUser.RoleID),
|
||
RoleCode: legacyUser.RoleCode,
|
||
ForcePasswordChange: true,
|
||
}
|
||
|
||
auditlog.Write(auditlog.ActivityLog{
|
||
ActionType: "LEGACY_USER_LOGIN_PENDING_MIGRATION",
|
||
ActionCategory: "security",
|
||
Description: "legacy giriş başarılı, ilk şifre değişikliği gerekli",
|
||
IsSuccess: true,
|
||
})
|
||
|
||
// ==================================================
|
||
// 4️⃣ SUCCESS
|
||
// ==================================================
|
||
writeLoginResponse(w, db, mkUser)
|
||
}
|
||
}
|
||
|
||
// ======================================================
|
||
// 🔑 LOGIN RESPONSE
|
||
// ======================================================
|
||
|
||
func writeLoginResponse(w http.ResponseWriter, db *sql.DB, user *models.MkUser) {
|
||
// 🔥 ROLE GARANTİSİ
|
||
if user.RoleID == 0 {
|
||
|
||
_ = db.QueryRow(`
|
||
SELECT dfrole_id
|
||
FROM dfrole_usr
|
||
WHERE dfusr_id = $1
|
||
LIMIT 1
|
||
`, user.ID).Scan(&user.RoleID)
|
||
}
|
||
|
||
if user.RoleCode == "" && user.RoleID > 0 {
|
||
|
||
_ = db.QueryRow(`
|
||
SELECT code
|
||
FROM dfrole
|
||
WHERE id = $1
|
||
`, user.RoleID).Scan(&user.RoleCode)
|
||
}
|
||
|
||
log.Println("🧪 LOGIN RESPONSE USER DEBUG")
|
||
log.Printf("🧪 user.ID = %d", user.ID)
|
||
log.Printf("🧪 user.Username = %s", user.Username)
|
||
log.Printf("🧪 user.RoleID = %d", user.RoleID)
|
||
log.Printf("🧪 user.RoleCode = '%s'", user.RoleCode)
|
||
log.Printf("🧪 user.IsActive = %v", user.IsActive)
|
||
|
||
permRepo := repository.NewPermissionRepository(db)
|
||
perms, _ := permRepo.GetPermissionsByRoleID(user.RoleID)
|
||
|
||
// ✅ CLAIMS BUILD
|
||
claims := auth.BuildClaimsFromUser(user, 15*time.Minute)
|
||
|
||
token, err := auth.GenerateToken(
|
||
claims,
|
||
user.Username,
|
||
user.ForcePasswordChange,
|
||
)
|
||
if err != nil {
|
||
http.Error(w, "Token üretilemedi", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
refreshPlain, refreshHash, err := security.GenerateRefreshToken()
|
||
if err != nil {
|
||
http.Error(w, "Refresh token üretilemedi", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
refreshExp := time.Now().Add(14 * 24 * time.Hour)
|
||
rtRepo := repository.NewRefreshTokenRepository(db)
|
||
if err := rtRepo.IssueRefreshToken(user.ID, refreshHash, refreshExp); err != nil {
|
||
log.Printf("refresh token store failed user=%d err=%v", user.ID, err)
|
||
http.Error(w, "Session başlatılamadı", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
setRefreshCookie(w, refreshPlain, refreshExp)
|
||
|
||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||
"token": token,
|
||
"user": map[string]any{
|
||
"id": user.ID,
|
||
"username": user.Username,
|
||
"email": user.Email,
|
||
"is_active": user.IsActive,
|
||
"role_id": user.RoleID,
|
||
"role_code": user.RoleCode,
|
||
"force_password_change": user.ForcePasswordChange,
|
||
"v3_username": user.V3Username,
|
||
"v3_usergroup": user.V3UserGroup,
|
||
},
|
||
"permissions": perms,
|
||
})
|
||
}
|
||
|
||
/* ======================================================
|
||
🔎 LOOKUPS (aynen korunuyor)
|
||
====================================================== */
|
||
|
||
type LookupOption struct {
|
||
Value string `json:"value"`
|
||
Label string `json:"label"`
|
||
}
|
||
|
||
func GetRoleLookupRoute(db *sql.DB) http.HandlerFunc {
|
||
return func(w http.ResponseWriter, r *http.Request) {
|
||
rows, err := db.Query(queries.GetRoleLookup)
|
||
if err != nil {
|
||
http.Error(w, "role lookup error", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
defer rows.Close()
|
||
|
||
var list []LookupOption
|
||
for rows.Next() {
|
||
var o LookupOption
|
||
if err := rows.Scan(&o.Value, &o.Label); err == nil {
|
||
list = append(list, o)
|
||
}
|
||
}
|
||
_ = json.NewEncoder(w).Encode(list)
|
||
}
|
||
}
|
||
|
||
func GetDepartmentLookupRoute(db *sql.DB) http.HandlerFunc {
|
||
return func(w http.ResponseWriter, r *http.Request) {
|
||
rows, err := db.Query(queries.GetDepartmentLookup)
|
||
if err != nil {
|
||
http.Error(w, "department lookup error", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
defer rows.Close()
|
||
|
||
var list []LookupOption
|
||
for rows.Next() {
|
||
var o LookupOption
|
||
if err := rows.Scan(&o.Value, &o.Label); err == nil {
|
||
list = append(list, o)
|
||
}
|
||
}
|
||
_ = json.NewEncoder(w).Encode(list)
|
||
}
|
||
}
|
||
|
||
func GetPiyasaLookupRoute(db *sql.DB) http.HandlerFunc {
|
||
return func(w http.ResponseWriter, r *http.Request) {
|
||
rows, err := db.Query(queries.GetPiyasaLookup)
|
||
if err != nil {
|
||
http.Error(w, "piyasa lookup error", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
defer rows.Close()
|
||
|
||
var list []LookupOption
|
||
for rows.Next() {
|
||
var o LookupOption
|
||
if err := rows.Scan(&o.Value, &o.Label); err == nil {
|
||
list = append(list, o)
|
||
}
|
||
}
|
||
_ = json.NewEncoder(w).Encode(list)
|
||
}
|
||
}
|
||
|
||
// ======================================================
|
||
// 🧾 NEBIM USER LOOKUP
|
||
// GET /api/lookups/nebim-users
|
||
// ======================================================
|
||
func GetNebimUserLookupRoute(db *sql.DB) http.HandlerFunc {
|
||
return func(w http.ResponseWriter, r *http.Request) {
|
||
|
||
rows, err := db.Query(queries.GetNebimUserLookup)
|
||
if err != nil {
|
||
http.Error(w, "nebim lookup error", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
defer rows.Close()
|
||
|
||
var list []map[string]string
|
||
for rows.Next() {
|
||
var v, l, g string
|
||
if err := rows.Scan(&v, &l, &g); err == nil {
|
||
list = append(list, map[string]string{
|
||
"value": v,
|
||
"label": l,
|
||
"user_group_code": g,
|
||
})
|
||
}
|
||
}
|
||
|
||
_ = json.NewEncoder(w).Encode(list)
|
||
}
|
||
}
|
||
func UserCreateRoute(db *sql.DB) http.HandlerFunc {
|
||
return func(w http.ResponseWriter, r *http.Request) {
|
||
|
||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||
|
||
var payload models.UserWrite
|
||
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
||
http.Error(w, "Geçersiz payload", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
if payload.Code == "" {
|
||
http.Error(w, "Kullanıcı kodu zorunludur", http.StatusUnprocessableEntity)
|
||
return
|
||
}
|
||
|
||
tx, err := db.Begin()
|
||
if err != nil {
|
||
http.Error(w, "Transaction başlatılamadı", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
defer tx.Rollback()
|
||
|
||
var newID int64
|
||
err = tx.QueryRow(`
|
||
INSERT INTO mk_dfusr (
|
||
code,
|
||
is_active,
|
||
full_name,
|
||
email,
|
||
mobile,
|
||
address,
|
||
force_password_change,
|
||
last_updated_date
|
||
)
|
||
VALUES ($1,$2,$3,$4,$5,$6,true,NOW())
|
||
RETURNING id
|
||
`,
|
||
payload.Code,
|
||
payload.IsActive,
|
||
payload.FullName,
|
||
payload.Email,
|
||
payload.Mobile,
|
||
payload.Address,
|
||
).Scan(&newID)
|
||
|
||
if err != nil {
|
||
log.Println("USER INSERT ERROR:", err)
|
||
http.Error(w, "Kullanıcı oluşturulamadı", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
// ROLES
|
||
for _, role := range payload.Roles {
|
||
_, _ = tx.Exec(queries.InsertUserRole, newID, role)
|
||
}
|
||
|
||
// DEPARTMENTS
|
||
for _, d := range payload.Departments {
|
||
_, _ = tx.Exec(queries.InsertUserDepartment, newID, d.Code)
|
||
}
|
||
|
||
// PIYASALAR
|
||
for _, p := range payload.Piyasalar {
|
||
_, _ = tx.Exec(queries.InsertUserPiyasa, newID, p.Code)
|
||
}
|
||
|
||
// NEBIM
|
||
for _, n := range payload.NebimUsers {
|
||
_, _ = tx.Exec(queries.InsertUserNebim, newID, 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,
|
||
"id": newID,
|
||
})
|
||
}
|
||
}
|
||
|
||
// ======================================================
|
||
// 🔐 ROLES LIST
|
||
// GET /api/roles
|
||
// ======================================================
|
||
|
||
func GetRolesHandler(db *sql.DB) http.HandlerFunc {
|
||
type RoleRow struct {
|
||
ID int64 `json:"id"`
|
||
Code string `json:"code"`
|
||
Title string `json:"title"`
|
||
}
|
||
|
||
return func(w http.ResponseWriter, r *http.Request) {
|
||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||
|
||
rows, err := db.Query(queries.GetRoles)
|
||
if err != nil {
|
||
http.Error(w, "roles query error", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
defer rows.Close()
|
||
|
||
var list []RoleRow
|
||
for rows.Next() {
|
||
var x RoleRow
|
||
if err := rows.Scan(&x.ID, &x.Code, &x.Title); err == nil {
|
||
list = append(list, x)
|
||
}
|
||
}
|
||
|
||
_ = json.NewEncoder(w).Encode(list)
|
||
}
|
||
}
|
||
|
||
// ======================================================
|
||
// 🏢 DEPARTMENTS LIST
|
||
// GET /api/departments
|
||
// ======================================================
|
||
|
||
func GetDepartmentsHandler(db *sql.DB) http.HandlerFunc {
|
||
type DeptRow struct {
|
||
ID int64 `json:"id"`
|
||
Code string `json:"code"`
|
||
Title string `json:"title"`
|
||
}
|
||
|
||
return func(w http.ResponseWriter, r *http.Request) {
|
||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||
|
||
rows, err := db.Query(queries.GetDepartments)
|
||
if err != nil {
|
||
http.Error(w, "departments query error", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
defer rows.Close()
|
||
|
||
var list []DeptRow
|
||
for rows.Next() {
|
||
var x DeptRow
|
||
if err := rows.Scan(&x.ID, &x.Code, &x.Title); err == nil {
|
||
list = append(list, x)
|
||
}
|
||
}
|
||
|
||
_ = json.NewEncoder(w).Encode(list)
|
||
}
|
||
}
|
||
|
||
// ======================================================
|
||
// 🌍 PIYASALAR LIST
|
||
// GET /api/piyasalar
|
||
// ======================================================
|
||
|
||
func GetPiyasalarHandler(db *sql.DB) http.HandlerFunc {
|
||
type PiyRow struct {
|
||
Code string `json:"code"`
|
||
Title string `json:"title"`
|
||
}
|
||
|
||
return func(w http.ResponseWriter, r *http.Request) {
|
||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||
|
||
rows, err := db.Query(queries.GetPiyasalar)
|
||
if err != nil {
|
||
http.Error(w, "piyasalar query error", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
defer rows.Close()
|
||
|
||
var list []PiyRow
|
||
for rows.Next() {
|
||
var x PiyRow
|
||
if err := rows.Scan(&x.Code, &x.Title); err == nil {
|
||
list = append(list, x)
|
||
}
|
||
}
|
||
|
||
_ = json.NewEncoder(w).Encode(list)
|
||
}
|
||
}
|
||
|
||
// ======================================================
|
||
// 🔗 ROLE → DEPARTMENTS UPDATE
|
||
// POST /api/roles/{id}/departments
|
||
// body: { "codes": ["D01","D02"] }
|
||
// ======================================================
|
||
|
||
type CodesPayload struct {
|
||
Codes []string `json:"codes"`
|
||
}
|
||
|
||
func UpdateRoleDepartmentsHandler(db *sql.DB) http.HandlerFunc {
|
||
return func(w http.ResponseWriter, r *http.Request) {
|
||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||
|
||
roleIDStr := mux.Vars(r)["id"]
|
||
roleID, err := strconv.ParseInt(roleIDStr, 10, 64)
|
||
if err != nil || roleID <= 0 {
|
||
http.Error(w, "Geçersiz role id", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
var payload CodesPayload
|
||
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
||
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()
|
||
|
||
// reset
|
||
if _, err := tx.Exec(queries.DeleteRoleDepartments, roleID); err != nil {
|
||
http.Error(w, "Role departments silinemedi", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
// insert new
|
||
for _, code := range payload.Codes {
|
||
code = strings.TrimSpace(code)
|
||
if code == "" {
|
||
continue
|
||
}
|
||
if _, err := tx.Exec(queries.InsertRoleDepartment, roleID, code); err != nil {
|
||
http.Error(w, "Role department eklenemedi", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
}
|
||
|
||
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})
|
||
}
|
||
}
|
||
|
||
// ======================================================
|
||
// 🔗 ROLE → PIYASALAR UPDATE
|
||
// POST /api/roles/{id}/piyasalar
|
||
// body: { "codes": ["TR","EU"] } (piyasa_code list)
|
||
// ======================================================
|
||
|
||
func UpdateRolePiyasalarHandler(db *sql.DB) http.HandlerFunc {
|
||
return func(w http.ResponseWriter, r *http.Request) {
|
||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||
|
||
roleIDStr := mux.Vars(r)["id"]
|
||
roleID, err := strconv.ParseInt(roleIDStr, 10, 64)
|
||
if err != nil || roleID <= 0 {
|
||
http.Error(w, "Geçersiz role id", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
var payload CodesPayload
|
||
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
||
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()
|
||
|
||
// reset
|
||
if _, err := tx.Exec(queries.DeleteRolePiyasalar, roleID); err != nil {
|
||
http.Error(w, "Role piyasalar silinemedi", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
// insert new
|
||
for _, code := range payload.Codes {
|
||
code = strings.TrimSpace(code)
|
||
if code == "" {
|
||
continue
|
||
}
|
||
if _, err := tx.Exec(queries.InsertRolePiyasa, roleID, code); err != nil {
|
||
http.Error(w, "Role piyasa eklenemedi", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
}
|
||
|
||
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})
|
||
}
|
||
}
|
||
|
||
// ======================================================
|
||
// 👤 USER → ROLES UPDATE
|
||
// POST /api/users/{id}/roles
|
||
// body: { "codes": ["admin","user"] } (dfrole.code list)
|
||
// ======================================================
|
||
|
||
func UpdateUserRolesHandler(db *sql.DB) http.HandlerFunc {
|
||
return func(w http.ResponseWriter, r *http.Request) {
|
||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||
|
||
// ---------- CLAIMS ----------
|
||
claims, ok := auth.GetClaimsFromContext(r.Context())
|
||
if !ok || !claims.IsAdmin() {
|
||
http.Error(w, "forbidden", http.StatusForbidden)
|
||
return
|
||
}
|
||
|
||
// ---------- TARGET USER ----------
|
||
userIDStr := mux.Vars(r)["id"]
|
||
userID, err := strconv.ParseInt(userIDStr, 10, 64)
|
||
if err != nil || userID <= 0 {
|
||
http.Error(w, "Geçersiz user id", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
// ---------- PAYLOAD ----------
|
||
var payload CodesPayload
|
||
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
||
http.Error(w, "Geçersiz payload", http.StatusBadRequest)
|
||
return
|
||
}
|
||
|
||
// ---------- BEFORE (ROLE LIST) ----------
|
||
oldRoles, _ := repository.GetUserRolesByUserID(db, userID)
|
||
|
||
// ---------- TX ----------
|
||
tx, err := db.Begin()
|
||
if err != nil {
|
||
http.Error(w, "Transaction başlatılamadı", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
defer tx.Rollback()
|
||
|
||
// reset
|
||
if _, err := tx.Exec(queries.DeleteUserRoles, userID); err != nil {
|
||
http.Error(w, "User roles silinemedi", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
// insert new
|
||
for _, roleCode := range payload.Codes {
|
||
roleCode = strings.TrimSpace(roleCode)
|
||
if roleCode == "" {
|
||
continue
|
||
}
|
||
if _, err := tx.Exec(queries.InsertUserRole, userID, roleCode); err != nil {
|
||
http.Error(w, "User role eklenemedi", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
}
|
||
|
||
if err := tx.Commit(); err != nil {
|
||
http.Error(w, "Commit başarısız", http.StatusInternalServerError)
|
||
return
|
||
}
|
||
|
||
// ---------- AFTER (ROLE LIST) ----------
|
||
newRoles, _ := repository.GetUserRolesByUserID(db, userID)
|
||
|
||
// ---------- AUDIT ----------
|
||
auditlog.Enqueue(r.Context(), auditlog.ActivityLog{
|
||
ActionType: "role_change",
|
||
ActionCategory: "user_admin",
|
||
ActionTarget: fmt.Sprintf("/api/users/%d/roles", userID),
|
||
Description: "user roles updated",
|
||
|
||
Username: claims.Username,
|
||
RoleCode: claims.RoleCode,
|
||
DfUsrID: int64(claims.ID),
|
||
|
||
TargetDfUsrID: userID,
|
||
|
||
ChangeBefore: map[string]any{
|
||
"roles": oldRoles,
|
||
},
|
||
ChangeAfter: map[string]any{
|
||
"roles": newRoles,
|
||
},
|
||
|
||
IsSuccess: true,
|
||
})
|
||
|
||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||
"success": true,
|
||
})
|
||
}
|
||
}
|