refactor: use UUID for envelope id to prevent accidental deletion

This commit is contained in:
rustmailer
2026-03-18 01:04:41 +08:00
parent 8a42fcdb4a
commit d690f57290
37 changed files with 393 additions and 393 deletions
+3 -2
View File
@@ -27,7 +27,7 @@ export interface PaginatedResponse<S> {
export interface EmailEnvelope {
id: number;
id: string;
message_id: string;
account_id: number;
account_email?: string;
@@ -42,7 +42,8 @@ export interface EmailEnvelope {
date: number;
internal_date: number;
size: number;
thread_id: number,
thread_id: string,
attachment_count: number;
tags: string[];
content_hash: string;
}
+9 -22
View File
@@ -21,20 +21,7 @@ import { EmailEnvelope, PaginatedResponse } from "@/api";
import axiosInstance from "@/api/axiosInstance";
import { saveAs } from 'file-saver';
// export const list_messages = async (accountId: number, mailbox_id: number, page: number, page_size: number) => {
// const params = new URLSearchParams({
// mailbox_id: String(mailbox_id),
// page: String(page),
// page_size: String(page_size),
// });
// const response = await axiosInstance.get<PaginatedResponse<EmailEnvelope>>(
// `api/v1/list-messages/${accountId}?${params.toString()}`
// );
// return response.data;
// };
export const get_thread_messages = async (accountId: number, thread_id: number, page: number, page_size: number) => {
export const get_thread_messages = async (accountId: number, thread_id: string, page: number, page_size: number) => {
const params = new URLSearchParams({
thread_id: String(thread_id),
page: String(page),
@@ -47,13 +34,13 @@ export const get_thread_messages = async (accountId: number, thread_id: number,
return response.data;
}
export const download_attachment = async (accountId: number, id: number, attachmentFileName: string) => {
export const download_attachment = async (accountId: number, id: string, attachmentFileName: string) => {
const response = await axiosInstance.get(`api/v1/download-attachment/${accountId}/${id}?name=${attachmentFileName}`, { responseType: 'blob' });
const blob = new Blob([response.data]);
saveAs(blob, attachmentFileName);
};
export const download_nested_attachment = async (accountId: number, id: number, attachmentFileName: string, nestedAttachmentFileName: string) => {
export const download_nested_attachment = async (accountId: number, id: string, attachmentFileName: string, nestedAttachmentFileName: string) => {
const response = await axiosInstance.get(`api/v1/download-nested-attachment/${accountId}/${id}?name=${attachmentFileName}&nested_name=${nestedAttachmentFileName}`, { responseType: 'blob' });
const blob = new Blob([response.data]);
saveAs(blob, nestedAttachmentFileName);
@@ -92,30 +79,30 @@ export const getContent = (messageContent: MessageContentResponse): string | nul
return null;
};
export const load_message = async (accountId: number, id: number) => {
export const load_message = async (accountId: number, id: string) => {
const response = await axiosInstance.get<MessageContentResponse>(`api/v1/message-content/${accountId}/${id}`);
return response.data;
};
export const load_nested_message = async (accountId: number, id: number, attachmentFileName: string) => {
export const load_nested_message = async (accountId: number, id: string, attachmentFileName: string) => {
const response = await axiosInstance.get<NestedMessageContentResponse>(`api/v1/nested-message-content/${accountId}/${id}?name=${attachmentFileName}`);
return response.data;
};
export const delete_messages = async (payload: Record<string, number[]>) => {
export const delete_messages = async (payload: Record<number, string[]>) => {
const response = await axiosInstance.post("api/v1/delete-messages", payload);
return response.data;
};
export const download_message = async (accountId: number, id: number) => {
export const download_message = async (accountId: number, id: string) => {
const response = await axiosInstance.get(`api/v1/download-message/${accountId}/${id}`, { responseType: 'blob' });
const blob = new Blob([response.data]);
saveAs(blob, `${id}.eml`);
};
export const restore_message = async (accountId: number, messageIds: number[]) => {
export const restore_message = async (accountId: number, envelopeIds: string[]) => {
const response = await axiosInstance.post(`api/v1/restore-messages/${accountId}`, {
message_ids: messageIds,
envelope_ids: envelopeIds,
});
return response.data;
};
+4 -4
View File
@@ -28,10 +28,10 @@ interface SearchContextType {
setOpen: (str: SearchDialogType | null) => void
currentEnvelope: EmailEnvelope | undefined
setCurrentEnvelope: React.Dispatch<React.SetStateAction<EmailEnvelope | undefined>>
toDelete: Map<number, Set<number>>
setToDelete: React.Dispatch<React.SetStateAction<Map<number, Set<number>>>>
selected: Map<number, Set<number>>
setSelected: React.Dispatch<React.SetStateAction<Map<number, Set<number>>>>
toDelete: Map<number, Set<string>>
setToDelete: React.Dispatch<React.SetStateAction<Map<number, Set<string>>>>
selected: Map<number, Set<string>>
setSelected: React.Dispatch<React.SetStateAction<Map<number, Set<string>>>>
deleteMailboxId: string | undefined
setDeleteMailboxId: React.Dispatch<React.SetStateAction<string | undefined>>
selectedAccountId: number | undefined
+1 -1
View File
@@ -37,7 +37,7 @@ export function EnvelopeDeleteDialog({ open, onOpenChange }: Props) {
const { t } = useTranslation()
const deleteMutation = useMutation({
mutationFn: ({ payload }: { payload: Record<string, number[]> }) =>
mutationFn: ({ payload }: { payload: Record<number, string[]> }) =>
delete_messages(payload),
retry: false,
onSuccess: () => {
+2 -2
View File
@@ -39,8 +39,8 @@ export default function Search() {
const { t } = useTranslation()
const [selectedEnvelope, setSelectedEnvelope] = React.useState<EmailEnvelope | undefined>(undefined);
const [open, setOpen] = useDialogState<SearchDialogType>(null)
const [toDelete, setToDelete] = React.useState<Map<number, Set<number>>>(new Map());
const [selected, setSelected] = React.useState<Map<number, Set<number>>>(new Map());
const [toDelete, setToDelete] = React.useState<Map<number, Set<string>>>(new Map());
const [selected, setSelected] = React.useState<Map<number, Set<string>>>(new Map());
const [selectedTags, setSelectedTags] = React.useState<string[]>([]);
const [sorting, setSorting] = React.useState<SortingState>([{ id: "date", desc: true }]);
const [deleteMailboxId, setDeleteMailboxId] = React.useState<string | undefined>(undefined);
+2 -2
View File
@@ -273,7 +273,7 @@ export function MailListTable({
}
}
const toggleSelected = (accountId: number, mailId: number) => {
const toggleSelected = (accountId: number, mailId: string) => {
setSelected(prev => {
const next = new Map(prev)
const set = new Set(next.get(accountId) || [])
@@ -292,7 +292,7 @@ export function MailListTable({
const totalSelected = Array.from(selected.values()).reduce((sum, set) => sum + set.size, 0)
const hasSelected = (accountId: number, mailId: number) => selected.get(accountId)?.has(mailId) ?? false
const hasSelected = (accountId: number, mailId: string) => selected.get(accountId)?.has(mailId) ?? false
if (isLoading) {
return (
+3 -3
View File
@@ -66,7 +66,7 @@ export function MailList({
}
}
const toggleToDelete = (accountId: number, mailId: number) => {
const toggleToDelete = (accountId: number, mailId: string) => {
setToDelete(prev => {
const next = new Map(prev);
const set = new Set(next.get(accountId) || []);
@@ -84,7 +84,7 @@ export function MailList({
});
};
const toggleSelected = (accountId: number, mailId: number) => {
const toggleSelected = (accountId: number, mailId: string) => {
setSelected(prev => {
const next = new Map(prev);
const set = new Set(next.get(accountId) || []);
@@ -105,7 +105,7 @@ export function MailList({
const totalSelected = Array.from(selected.values())
.reduce((sum, set) => sum + set.size, 0);
const hasSelected = (accountId: number, mailId: number) => {
const hasSelected = (accountId: number, mailId: string) => {
return selected.get(accountId)?.has(mailId) ?? false;
}
@@ -44,7 +44,7 @@ import { NestedEmailDialog } from './nested-email-dialog';
interface MailMessageViewProps {
envelope: {
id: number;
id: string;
account_id: number,
from?: string;
to?: string[];
@@ -172,7 +172,7 @@ export function MailMessageView({
setNestedEmlFile(filename);
};
const toggleToDelete = (accountId: number, mailId: number) => {
const toggleToDelete = (accountId: number, mailId: string) => {
setToDelete(prev => {
const next = new Map(prev);
const set = new Set(next.get(accountId) || []);
@@ -40,7 +40,7 @@ export function DataTableRowActions({ row }: DataTableRowActionsProps) {
const { setOpen, setCurrentEnvelope, setSelected, setToDelete } = useSearchContext()
const { t } = useTranslation()
const toggleToDelete = (accountId: number, mailId: number) => {
const toggleToDelete = (accountId: number, mailId: string) => {
setToDelete(prev => {
const next = new Map(prev)
const set = new Set(next.get(accountId) || [])
+2 -2
View File
@@ -42,7 +42,7 @@ interface MailThreadDialogProps {
export function MailThreadDialog({ open, onOpenChange }: MailThreadDialogProps) {
const { currentEnvelope } = useSearchContext();
const [expandedIds, setExpandedIds] = useState<Set<number>>(new Set());
const [expandedIds, setExpandedIds] = useState<Set<string>>(new Set());
const { t } = useTranslation();
const threadId = currentEnvelope?.thread_id;
@@ -73,7 +73,7 @@ export function MailThreadDialog({ open, onOpenChange }: MailThreadDialogProps)
const allMessages = data?.pages.flatMap((page) => page.items) ?? [];
const totalCount = data?.pages[0]?.total_items ?? 0;
const toggleExpand = (id: number) => {
const toggleExpand = (id: string) => {
setExpandedIds((prev) => {
const next = new Set(prev);
next.has(id) ? next.delete(id) : next.add(id);
+1 -1
View File
@@ -22,7 +22,7 @@ import { useMutation, useQueryClient } from '@tanstack/react-query';
import { toast } from './use-toast';
export interface UpdateTagsParams {
updates: Record<number, number[]>;
updates: Record<number, string[]>;
tags: string[];
}
+3 -3
View File
@@ -63,10 +63,10 @@ export const validateFlag = (input: string): string | null => {
export function mapToRecordOfArrays(
map: Map<number, Set<number>>
): Record<string, number[]> {
map: Map<number, Set<string>>
): Record<number, string[]> {
return Object.fromEntries(
Array.from(map.entries()).map(([key, value]) => [String(key), Array.from(value)])
Array.from(map.entries()).map(([key, value]) => [key, Array.from(value)])
);
}