Files
OpenArchiver/packages/backend/src/api/controllers/archived-email.controller.ts
Wei S. e5e119528f V0.5.0 release (#335)
* adding exports to backend package, page icons update

* Integrity report PDF generation

* Fixed inline attachment images not displaying in the email preview by modifying `EmailPreview.svelte`.
The email HTML references embedded images via `cid:` URIs (e.g., `src="cid:ii_19c6d5f8d5eee7bd6d91"`), but the component never resolved those `cid:` references to actual image data, even though `postal-mime` already parses inline attachments with their `contentId` and binary `content`.
The `emailHtml` derived value now calls `resolveContentIdReferences()` before rendering, so inline/embedded images display correctly in the iframe preview.

* feat: strip non-inline attachments from EML before storage

Add nodemailer dependency and emlUtils helper to remove non-inline
attachments from .eml buffers during ingestion. This avoids
double-storing attachment data since attachments are already stored
separately.

* upload error handing for file based ingestion

* Use Postgres for sync session management

* Google workspace / MS 365 duplicate check, avoid extra API call when previous ingestion fails

* OpenAPI specs for API docs

* code formatting

* ran duplicate check for IMAP import, optimize message listing

* Version update
2026-03-20 13:14:41 +01:00

100 lines
3.3 KiB
TypeScript

import { Request, Response } from 'express';
import { ArchivedEmailService } from '../../services/ArchivedEmailService';
import { UserService } from '../../services/UserService';
import { checkDeletionEnabled } from '../../helpers/deletionGuard';
export class ArchivedEmailController {
private userService = new UserService();
public getArchivedEmails = async (req: Request, res: Response): Promise<Response> => {
try {
const { ingestionSourceId } = req.params;
const page = parseInt(req.query.page as string, 10) || 1;
const limit = parseInt(req.query.limit as string, 10) || 10;
const userId = req.user?.sub;
if (!userId) {
return res.status(401).json({ message: req.t('errors.unauthorized') });
}
const result = await ArchivedEmailService.getArchivedEmails(
ingestionSourceId,
page,
limit,
userId
);
return res.status(200).json(result);
} catch (error) {
console.error('Get archived emails error:', error);
return res.status(500).json({ message: req.t('errors.internalServerError') });
}
};
public getArchivedEmailById = async (req: Request, res: Response): Promise<Response> => {
try {
const { id } = req.params;
const userId = req.user?.sub;
if (!userId) {
return res.status(401).json({ message: req.t('errors.unauthorized') });
}
const actor = await this.userService.findById(userId);
if (!actor) {
return res.status(401).json({ message: req.t('errors.unauthorized') });
}
const email = await ArchivedEmailService.getArchivedEmailById(
id,
userId,
actor,
req.ip || 'unknown'
);
if (!email) {
return res.status(404).json({ message: req.t('archivedEmail.notFound') });
}
return res.status(200).json(email);
} catch (error) {
console.error(`Get archived email by id ${req.params.id} error:`, error);
return res.status(500).json({ message: req.t('errors.internalServerError') });
}
};
public deleteArchivedEmail = async (req: Request, res: Response): Promise<Response> => {
// Guard: return 400 if deletion is disabled in system settings before touching anything else
try {
checkDeletionEnabled();
} catch (error) {
return res.status(400).json({
message: error instanceof Error ? error.message : req.t('errors.deletionDisabled'),
});
}
const { id } = req.params;
const userId = req.user?.sub;
if (!userId) {
return res.status(401).json({ message: req.t('errors.unauthorized') });
}
const actor = await this.userService.findById(userId);
if (!actor) {
return res.status(401).json({ message: req.t('errors.unauthorized') });
}
try {
await ArchivedEmailService.deleteArchivedEmail(id, actor, req.ip || 'unknown');
return res.status(204).send();
} catch (error) {
console.error(`Delete archived email ${req.params.id} error:`, error);
if (error instanceof Error) {
if (error.message === 'Archived email not found') {
return res.status(404).json({ message: req.t('archivedEmail.notFound') });
}
// Retention policy / legal hold blocks are user-facing 400 errors
if (error.message.startsWith('Deletion blocked by retention policy')) {
return res.status(400).json({ message: error.message });
}
return res.status(500).json({ message: error.message });
}
return res.status(500).json({ message: req.t('errors.internalServerError') });
}
};
}