Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -325,6 +325,11 @@ func InitRoutes(pgDB *sql.DB, mssql *sql.DB, ml *mailer.GraphMailer) *mux.Router
|
||||
"user", "update",
|
||||
wrapV3(routes.UserDetailRoute(pgDB)),
|
||||
)
|
||||
bindV3(r, pgDB,
|
||||
"/api/users/{id}", "DELETE",
|
||||
"user", "delete",
|
||||
wrapV3(routes.UserDetailRoute(pgDB)),
|
||||
)
|
||||
|
||||
bindV3(r, pgDB,
|
||||
"/api/users/{id}/admin-reset-password", "POST",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package routes
|
||||
|
||||
import (
|
||||
"bssapp-backend/auth"
|
||||
"bssapp-backend/internal/auditlog"
|
||||
"bssapp-backend/internal/mailer"
|
||||
"bssapp-backend/internal/security"
|
||||
@@ -51,6 +52,8 @@ func UserDetailRoute(db *sql.DB) http.Handler {
|
||||
handleUserGet(db, w, userID)
|
||||
case http.MethodPut:
|
||||
handleUserUpdate(db, w, r, userID)
|
||||
case http.MethodDelete:
|
||||
handleUserDelete(db, w, r, userID)
|
||||
case http.MethodOptions:
|
||||
w.WriteHeader(http.StatusOK)
|
||||
default:
|
||||
@@ -323,6 +326,102 @@ func handleUserUpdate(db *sql.DB, w http.ResponseWriter, r *http.Request, userID
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"success": true})
|
||||
}
|
||||
|
||||
// ======================================================
|
||||
// 🗑️ DELETE USER (HARD DELETE)
|
||||
// ======================================================
|
||||
func handleUserDelete(db *sql.DB, w http.ResponseWriter, r *http.Request, userID int64) {
|
||||
claims, _ := auth.GetClaimsFromContext(r.Context())
|
||||
if claims != nil && int64(claims.ID) == userID {
|
||||
http.Error(w, "Kendi kullanicinizi silemezsiniz", http.StatusConflict)
|
||||
return
|
||||
}
|
||||
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
http.Error(w, "Transaction baslatilamadi", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
var username string
|
||||
_ = tx.QueryRow(`
|
||||
SELECT username
|
||||
FROM mk_dfusr
|
||||
WHERE id = $1
|
||||
`, userID).Scan(&username)
|
||||
|
||||
if strings.TrimSpace(username) == "" {
|
||||
_ = tx.QueryRow(`
|
||||
SELECT code
|
||||
FROM dfusr
|
||||
WHERE id = $1
|
||||
`, userID).Scan(&username)
|
||||
}
|
||||
|
||||
if strings.TrimSpace(username) == "" {
|
||||
http.Error(w, "Kullanici bulunamadi", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
cleanupQueries := []string{
|
||||
`DELETE FROM mk_refresh_tokens WHERE mk_user_id = $1`,
|
||||
`DELETE FROM mk_dfusr_password_reset WHERE mk_dfusr_id = $1`,
|
||||
`DELETE FROM dfusr_password_reset WHERE dfusr_id = $1`,
|
||||
`DELETE FROM mk_sys_user_permissions WHERE user_id = $1`,
|
||||
`DELETE FROM dfrole_usr WHERE dfusr_id = $1`,
|
||||
`DELETE FROM dfusr_dprt WHERE dfusr_id = $1`,
|
||||
`DELETE FROM dfusr_piyasa WHERE dfusr_id = $1`,
|
||||
`DELETE FROM dfusr_nebim_user WHERE dfusr_id = $1`,
|
||||
}
|
||||
|
||||
for _, q := range cleanupQueries {
|
||||
if _, err := tx.Exec(q, userID); err != nil {
|
||||
log.Printf("❌ [UserDetail] cleanup failed user_id=%d err=%v query=%s", userID, err, q)
|
||||
http.Error(w, "Kullanici baglantilari silinemedi", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := tx.Exec(`DELETE FROM mk_dfusr WHERE id = $1`, userID); err != nil {
|
||||
log.Printf("❌ [UserDetail] delete mk_dfusr failed user_id=%d err=%v", userID, err)
|
||||
http.Error(w, "Kullanici silinemedi", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err := tx.Exec(`DELETE FROM dfusr WHERE id = $1`, userID); err != nil {
|
||||
log.Printf("❌ [UserDetail] delete dfusr failed user_id=%d err=%v", userID, err)
|
||||
http.Error(w, "Kullanici silinemedi", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
log.Printf("❌ [UserDetail] delete commit failed user_id=%d err=%v", userID, err)
|
||||
http.Error(w, "Commit basarisiz", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if claims != nil {
|
||||
auditlog.Enqueue(r.Context(), auditlog.ActivityLog{
|
||||
ActionType: "user_delete",
|
||||
ActionCategory: "user_admin",
|
||||
ActionTarget: fmt.Sprintf("/api/users/%d", userID),
|
||||
Description: "user deleted from mk_dfusr and dfusr",
|
||||
Username: claims.Username,
|
||||
RoleCode: claims.RoleCode,
|
||||
DfUsrID: int64(claims.ID),
|
||||
TargetDfUsrID: userID,
|
||||
TargetUsername: username,
|
||||
IsSuccess: true,
|
||||
})
|
||||
}
|
||||
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{
|
||||
"success": true,
|
||||
"deleted": userID,
|
||||
"username": username,
|
||||
})
|
||||
}
|
||||
|
||||
// ======================================================
|
||||
// 🔐 ADMIN — PASSWORD RESET MAIL
|
||||
// ======================================================
|
||||
|
||||
@@ -46,6 +46,15 @@
|
||||
:loading="saving"
|
||||
@click="onSave"
|
||||
/>
|
||||
<q-btn
|
||||
v-if="canDeleteThisUser"
|
||||
label="SIL"
|
||||
color="negative"
|
||||
icon="delete"
|
||||
class="q-ml-sm"
|
||||
:loading="deleting"
|
||||
@click="confirmDeleteUser"
|
||||
/>
|
||||
<q-btn
|
||||
v-if="canReadUser"
|
||||
label="LİSTEYE DÖN"
|
||||
@@ -259,10 +268,11 @@ import { storeToRefs } from 'pinia'
|
||||
import { useUserDetailStore } from 'src/stores/UserDetailStore'
|
||||
import { usePermission } from 'src/composables/usePermission'
|
||||
|
||||
const { canRead, canWrite, canUpdate } = usePermission()
|
||||
const { canRead, canWrite, canUpdate, canDelete } = usePermission()
|
||||
const canReadUser = canRead('user')
|
||||
const canWriteUser = canWrite('user')
|
||||
const canUpdateUser = canUpdate('user')
|
||||
const canDeleteUser = canDelete('user')
|
||||
|
||||
const $q = useQuasar()
|
||||
const route = useRoute()
|
||||
@@ -274,6 +284,7 @@ const {
|
||||
form,
|
||||
loading,
|
||||
saving,
|
||||
deleting,
|
||||
roleOptions,
|
||||
departmentOptions,
|
||||
piyasaOptions,
|
||||
@@ -296,6 +307,11 @@ const canAccessPage = computed(() => {
|
||||
const canSaveUser = computed(() => isNew.value ? canWriteUser.value : canUpdateUser.value)
|
||||
|
||||
const userId = computed(() => (isEdit.value || isView.value) ? Number(route.params.id) : null)
|
||||
const canDeleteThisUser = computed(() =>
|
||||
(isEdit.value || isView.value) &&
|
||||
!!userId.value &&
|
||||
canDeleteUser.value
|
||||
)
|
||||
|
||||
const hasPassword = computed(() => store.hasPassword)
|
||||
|
||||
@@ -386,6 +402,36 @@ function goList () {
|
||||
router.push({ name: 'user-list' })
|
||||
}
|
||||
|
||||
function confirmDeleteUser () {
|
||||
if (!canDeleteThisUser.value) {
|
||||
$q.notify({ type: 'negative', message: 'Silme yetkiniz yok' })
|
||||
return
|
||||
}
|
||||
|
||||
const code = (form.value.code || '').trim()
|
||||
const label = code || `ID ${userId.value}`
|
||||
|
||||
$q.dialog({
|
||||
title: 'Kullanici silinsin mi?',
|
||||
message: `${label} kaydi tablodan silinecek. Bu islem geri alinamaz.`,
|
||||
cancel: true,
|
||||
persistent: true
|
||||
}).onOk(async () => {
|
||||
await deleteUser()
|
||||
})
|
||||
}
|
||||
|
||||
async function deleteUser () {
|
||||
try {
|
||||
await store.deleteUser(userId.value)
|
||||
$q.notify({ type: 'positive', message: 'Kullanici silindi' })
|
||||
await router.replace({ name: 'user-list' })
|
||||
window.location.reload()
|
||||
} catch {
|
||||
$q.notify({ type: 'negative', message: store.error || 'Kullanici silinemedi' })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function confirmSendPasswordMail () {
|
||||
$q.dialog({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// src/stores/userDetailStore.js
|
||||
import { defineStore } from 'pinia'
|
||||
import api, { get, post, put } from 'src/services/api'
|
||||
import api, { get, post, put, del } from 'src/services/api'
|
||||
|
||||
export const useUserDetailStore = defineStore('userDetail', {
|
||||
state: () => ({
|
||||
@@ -11,6 +11,7 @@ export const useUserDetailStore = defineStore('userDetail', {
|
||||
/* ================= FLAGS ================= */
|
||||
loading: false,
|
||||
saving: false,
|
||||
deleting: false,
|
||||
error: null,
|
||||
|
||||
/* ================= FORM ================= */
|
||||
@@ -222,6 +223,23 @@ export const useUserDetailStore = defineStore('userDetail', {
|
||||
}
|
||||
},
|
||||
|
||||
/* =====================================================
|
||||
🗑️ DELETE USER
|
||||
===================================================== */
|
||||
async deleteUser (id) {
|
||||
this.deleting = true
|
||||
this.error = null
|
||||
|
||||
try {
|
||||
await del(`/users/${id}`)
|
||||
} catch (e) {
|
||||
this.error = 'Kullanici silinemedi'
|
||||
throw e
|
||||
} finally {
|
||||
this.deleting = false
|
||||
}
|
||||
},
|
||||
|
||||
/* =====================================================
|
||||
📚 LOOKUPS (NEW + EDIT ORTAK)
|
||||
===================================================== */
|
||||
|
||||
Reference in New Issue
Block a user