diff --git a/cmd/contacts.go b/cmd/contacts.go index 3f9e452b..6f556bed 100644 --- a/cmd/contacts.go +++ b/cmd/contacts.go @@ -286,3 +286,25 @@ func handleBlockContact(r *fastglue.Request) error { } return r.SendEnvelope(contact) } + + +// handleDeleteContact soft deletes a contact. +func handleDeleteContact(r *fastglue.Request) error { + var ( + app = r.Context.(*App) + contactID, _ = strconv.Atoi(r.RequestCtx.UserValue("id").(string)) + auser = r.RequestCtx.UserValue("user").(amodels.User) + ) + + if contactID <= 0 { + return r.SendErrorEnvelope(fasthttp.StatusBadRequest, app.i18n.Ts("globals.messages.invalid", "name", "`id`"), nil, envelope.InputError) + } + + app.lo.Info("deleting contact", "contact_id", contactID, "actor_id", auser.ID) + + if err := app.user.SoftDeleteContact(contactID); err != nil { + return sendErrorEnvelope(r, err) + } + + return r.SendEnvelope(true) +} diff --git a/cmd/handlers.go b/cmd/handlers.go index c75b3c1e..e903f0a8 100644 --- a/cmd/handlers.go +++ b/cmd/handlers.go @@ -122,6 +122,7 @@ func initHandlers(g *fastglue.Fastglue, hub *ws.Hub) { g.GET("/api/v1/contacts/{id}", perm(handleGetContact, "contacts:read")) g.PUT("/api/v1/contacts/{id}", perm(handleUpdateContact, "contacts:write")) g.PUT("/api/v1/contacts/{id}/block", perm(handleBlockContact, "contacts:block")) + g.DELETE("/api/v1/contacts/{id}", perm(handleDeleteContact, "contacts:delete")) // Contact notes. g.GET("/api/v1/contacts/{id}/notes", perm(handleGetContactNotes, "contact_notes:read")) diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js index 8c3b6b25..849d057d 100644 --- a/frontend/src/api/index.js +++ b/frontend/src/api/index.js @@ -192,6 +192,7 @@ const updateRole = (id, data) => const deleteRole = (id) => http.delete(`/api/v1/roles/${id}`) const getContacts = (params) => http.get('/api/v1/contacts', { params }) const getContact = (id) => http.get(`/api/v1/contacts/${id}`) +const deleteContact = (id) => http.delete(`/api/v1/contacts/${id}`) const updateContact = (id, data) => http.put(`/api/v1/contacts/${id}`, data, { headers: { @@ -548,6 +549,7 @@ export default { removeAssignee, getContacts, getContact, + deleteContact, updateContact, blockContact, getCustomAttributes, diff --git a/frontend/src/constants/permissions.js b/frontend/src/constants/permissions.js index 8ba5ec35..ee31df7d 100644 --- a/frontend/src/constants/permissions.js +++ b/frontend/src/constants/permissions.js @@ -35,6 +35,7 @@ export const permissions = { CONTACTS_READ: 'contacts:read', CONTACTS_WRITE: 'contacts:write', CONTACTS_BLOCK: 'contacts:block', + CONTACTS_DELETE: 'contacts:delete', CONTACT_NOTES_READ: 'contact_notes:read', CONTACT_NOTES_WRITE: 'contact_notes:write', CONTACT_NOTES_DELETE: 'contact_notes:delete', diff --git a/frontend/src/features/admin/roles/RoleForm.vue b/frontend/src/features/admin/roles/RoleForm.vue index 16b0d1e7..da2770dd 100644 --- a/frontend/src/features/admin/roles/RoleForm.vue +++ b/frontend/src/features/admin/roles/RoleForm.vue @@ -178,6 +178,7 @@ const permissions = ref([ { name: perms.CONTACTS_READ, label: t('admin.role.contacts.read') }, { name: perms.CONTACTS_WRITE, label: t('admin.role.contacts.write') }, { name: perms.CONTACTS_BLOCK, label: t('admin.role.contacts.block') }, + { name: perms.CONTACTS_DELETE, label: t('admin.role.contacts.delete') }, { name: perms.CONTACT_NOTES_READ, label: t('admin.role.contactNotes.read') }, { name: perms.CONTACT_NOTES_WRITE, label: t('admin.role.contactNotes.write') }, { name: perms.CONTACT_NOTES_DELETE, label: t('admin.role.contactNotes.delete') } diff --git a/frontend/src/views/contact/ContactDetailView.vue b/frontend/src/views/contact/ContactDetailView.vue index 7747b3da..4af4446f 100644 --- a/frontend/src/views/contact/ContactDetailView.vue +++ b/frontend/src/views/contact/ContactDetailView.vue @@ -31,7 +31,7 @@ {{ contact.created_at ? format(new Date(contact.created_at), 'PPP') : 'N/A' }} -
+
+
@@ -80,13 +89,34 @@ + + + + + + {{ t('globals.messages.delete', { name: t('globals.terms.contact') }) }} + + + Are you sure you want to delete this contact? This action cannot be undone. + + +
+ + +
+
+