Settings → MAILBOXES: удалить ящик вместе с письмами
continuous-integration/drone/pr Build is passing

DELETE /api/admin/mailboxes/{id} (только администратор, не свой ящик):
файлы вложений, затем строка users — папки, письма, исходники, контакты,
участие в рассылках и доступы уходят каскадом. Кнопка с подтверждением.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-07 18:48:09 +01:00
co-authored by Claude Opus 5.5
parent 53c80c57d7
commit e1bc0792ea
6 changed files with 54 additions and 0 deletions
@@ -158,6 +158,19 @@ public class MailboxAdminController {
}))).flatMap(this::view);
}
/** Deleted for good with all its mail (Settings → MAILBOXES → Delete); not your own mailbox. */
@DeleteMapping("/{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public Mono<Void> delete(Mono<Authentication> auth, @PathVariable Long id) {
return admin(auth).flatMap(me -> mailbox(id).flatMap(user -> {
if (user.getEmail().equalsIgnoreCase(me)) {
return Mono.error(bad("You can not delete your own mailbox"));
}
return mail.deleteMailbox(user)
.doOnSuccess(v -> log.warn("Mailbox {} deleted with all its mail by administrator {}", user.getEmail(), me));
}));
}
/** Trap: what the mailbox sends is stored in its own INBOX, nothing leaves the server (staging@). */
@PutMapping("/{id}/trap")
public Mono<MailboxView> trap(Mono<Authentication> auth, @PathVariable Long id, @RequestBody Mono<TrapRequest> body) {
@@ -12,6 +12,10 @@ public interface AttachmentRepository extends R2dbcRepository<Attachment, Long>
Mono<Long> countByMessageId(Long messageId);
/** All attachments of a mailbox — their files are removed with the mailbox. */
@Query("SELECT a.* FROM attachments a JOIN messages m ON m.id = a.message_id JOIN folders f ON f.id = m.folder_id WHERE f.user_id = :#{#userId}")
Flux<Attachment> findByMailbox(Long userId);
@Modifying
@Query("UPDATE attachments SET message_id = :#{#to} WHERE message_id = :#{#from}")
Mono<Integer> moveAll(Long from, Long to);
@@ -401,6 +401,16 @@ public class MailService {
.then(messageRepository.delete(message));
}
/**
* Mailbox deleted for good: attachment files first, then the user row — folders, messages, sources, contacts,
* list memberships and access rows go with it (ON DELETE CASCADE).
*/
public Mono<Void> deleteMailbox(User user) {
return attachmentRepository.findByMailbox(user.getId())
.doOnNext(attachment -> deleteFile(attachment.getFilePath()))
.then(userRepository.delete(user));
}
private static void deleteFile(String path) {
try {
Files.deleteIfExists(Paths.get(path));