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",
|
"user", "update",
|
||||||
wrapV3(routes.UserDetailRoute(pgDB)),
|
wrapV3(routes.UserDetailRoute(pgDB)),
|
||||||
)
|
)
|
||||||
|
bindV3(r, pgDB,
|
||||||
|
"/api/users/{id}", "DELETE",
|
||||||
|
"user", "delete",
|
||||||
|
wrapV3(routes.UserDetailRoute(pgDB)),
|
||||||
|
)
|
||||||
|
|
||||||
bindV3(r, pgDB,
|
bindV3(r, pgDB,
|
||||||
"/api/users/{id}/admin-reset-password", "POST",
|
"/api/users/{id}/admin-reset-password", "POST",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package routes
|
package routes
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bssapp-backend/auth"
|
||||||
"bssapp-backend/internal/auditlog"
|
"bssapp-backend/internal/auditlog"
|
||||||
"bssapp-backend/internal/mailer"
|
"bssapp-backend/internal/mailer"
|
||||||
"bssapp-backend/internal/security"
|
"bssapp-backend/internal/security"
|
||||||
@@ -51,6 +52,8 @@ func UserDetailRoute(db *sql.DB) http.Handler {
|
|||||||
handleUserGet(db, w, userID)
|
handleUserGet(db, w, userID)
|
||||||
case http.MethodPut:
|
case http.MethodPut:
|
||||||
handleUserUpdate(db, w, r, userID)
|
handleUserUpdate(db, w, r, userID)
|
||||||
|
case http.MethodDelete:
|
||||||
|
handleUserDelete(db, w, r, userID)
|
||||||
case http.MethodOptions:
|
case http.MethodOptions:
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
default:
|
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})
|
_ = 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
|
// 🔐 ADMIN — PASSWORD RESET MAIL
|
||||||
// ======================================================
|
// ======================================================
|
||||||
|
|||||||
@@ -46,6 +46,15 @@
|
|||||||
:loading="saving"
|
:loading="saving"
|
||||||
@click="onSave"
|
@click="onSave"
|
||||||
/>
|
/>
|
||||||
|
<q-btn
|
||||||
|
v-if="canDeleteThisUser"
|
||||||
|
label="SIL"
|
||||||
|
color="negative"
|
||||||
|
icon="delete"
|
||||||
|
class="q-ml-sm"
|
||||||
|
:loading="deleting"
|
||||||
|
@click="confirmDeleteUser"
|
||||||
|
/>
|
||||||
<q-btn
|
<q-btn
|
||||||
v-if="canReadUser"
|
v-if="canReadUser"
|
||||||
label="LİSTEYE DÖN"
|
label="LİSTEYE DÖN"
|
||||||
@@ -259,10 +268,11 @@ import { storeToRefs } from 'pinia'
|
|||||||
import { useUserDetailStore } from 'src/stores/UserDetailStore'
|
import { useUserDetailStore } from 'src/stores/UserDetailStore'
|
||||||
import { usePermission } from 'src/composables/usePermission'
|
import { usePermission } from 'src/composables/usePermission'
|
||||||
|
|
||||||
const { canRead, canWrite, canUpdate } = usePermission()
|
const { canRead, canWrite, canUpdate, canDelete } = usePermission()
|
||||||
const canReadUser = canRead('user')
|
const canReadUser = canRead('user')
|
||||||
const canWriteUser = canWrite('user')
|
const canWriteUser = canWrite('user')
|
||||||
const canUpdateUser = canUpdate('user')
|
const canUpdateUser = canUpdate('user')
|
||||||
|
const canDeleteUser = canDelete('user')
|
||||||
|
|
||||||
const $q = useQuasar()
|
const $q = useQuasar()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@@ -274,6 +284,7 @@ const {
|
|||||||
form,
|
form,
|
||||||
loading,
|
loading,
|
||||||
saving,
|
saving,
|
||||||
|
deleting,
|
||||||
roleOptions,
|
roleOptions,
|
||||||
departmentOptions,
|
departmentOptions,
|
||||||
piyasaOptions,
|
piyasaOptions,
|
||||||
@@ -296,6 +307,11 @@ const canAccessPage = computed(() => {
|
|||||||
const canSaveUser = computed(() => isNew.value ? canWriteUser.value : canUpdateUser.value)
|
const canSaveUser = computed(() => isNew.value ? canWriteUser.value : canUpdateUser.value)
|
||||||
|
|
||||||
const userId = computed(() => (isEdit.value || isView.value) ? Number(route.params.id) : null)
|
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)
|
const hasPassword = computed(() => store.hasPassword)
|
||||||
|
|
||||||
@@ -386,6 +402,36 @@ function goList () {
|
|||||||
router.push({ name: 'user-list' })
|
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 () {
|
function confirmSendPasswordMail () {
|
||||||
$q.dialog({
|
$q.dialog({
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// src/stores/userDetailStore.js
|
// src/stores/userDetailStore.js
|
||||||
import { defineStore } from 'pinia'
|
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', {
|
export const useUserDetailStore = defineStore('userDetail', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
@@ -11,6 +11,7 @@ export const useUserDetailStore = defineStore('userDetail', {
|
|||||||
/* ================= FLAGS ================= */
|
/* ================= FLAGS ================= */
|
||||||
loading: false,
|
loading: false,
|
||||||
saving: false,
|
saving: false,
|
||||||
|
deleting: false,
|
||||||
error: null,
|
error: null,
|
||||||
|
|
||||||
/* ================= FORM ================= */
|
/* ================= 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)
|
📚 LOOKUPS (NEW + EDIT ORTAK)
|
||||||
===================================================== */
|
===================================================== */
|
||||||
|
|||||||
Reference in New Issue
Block a user