diff --git a/svc/main.go b/svc/main.go
index a324c12..901c921 100644
--- a/svc/main.go
+++ b/svc/main.go
@@ -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",
diff --git a/svc/routes/user_detail.go b/svc/routes/user_detail.go
index 7c65b5e..d2ca072 100644
--- a/svc/routes/user_detail.go
+++ b/svc/routes/user_detail.go
@@ -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
// ======================================================
diff --git a/ui/src/pages/UserDetail.vue b/ui/src/pages/UserDetail.vue
index fd41520..087a1ae 100644
--- a/ui/src/pages/UserDetail.vue
+++ b/ui/src/pages/UserDetail.vue
@@ -46,6 +46,15 @@
:loading="saving"
@click="onSave"
/>
+
{
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({
diff --git a/ui/src/stores/UserDetailStore.js b/ui/src/stores/UserDetailStore.js
index e2e760c..abe3169 100644
--- a/ui/src/stores/UserDetailStore.js
+++ b/ui/src/stores/UserDetailStore.js
@@ -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)
===================================================== */