fix(uat-batch-2): external-EOI five-bug bundle (a/b/c/d) + presign filename override
Tackles the linked B4 #5 findings on the external-EOI flow. Item (e) [Edit metadata affordance per row] is deferred to a later wave so it can share infra with the broader signing-flow rework. - (a) lying toast: uploadExternallySignedEoi now returns { stageChanged, newStage }. Client toasts conditionally so a Reservation+ deal that uploads paper-signing evidence no longer claims the stage advanced. - (b) View downloads instead of previewing: SignedPdfActions takes an onView callback; InterestEoiTab lifts a single FilePreviewDialog and passes the callback down. Click-View opens the in-app preview rather than the presigned URL (which the storage backend served as attachment). - (c) UUID filename on download: getDownloadUrl now passes the canonical filename through presignDownloadUrl; S3 backend adds a response-content-disposition override (filename + UTF-8 filename*) to the presign. Filesystem backend already passed it through. - (d) Discarded dateEoiSigned: external-eoi service splits document- metadata writes (always — dateEoiSigned, eoiStatus='signed') from stage advance (gated on past-EOI). Also fires evaluateRule('eoi_signed') so berth-rules stay in sync when an EOI is filed manually. - Default title for external-EOI dialog now derives "External EOI — <Client> — <berth range> — <date>" via the existing formatBerthRange helper; rep can override. tsc clean. 1419/1419 vitest pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -293,7 +293,27 @@ export class S3Backend implements StorageBackend {
|
||||
|
||||
async presignDownload(key: string, opts: PresignOpts): Promise<{ url: string; expiresAt: Date }> {
|
||||
const expiry = opts.expirySeconds ?? 900;
|
||||
const url = await this.client.presignedGetObject(this.bucket, key, expiry);
|
||||
// Pass response-header overrides to minio-js's reqParams so the
|
||||
// browser sees the original filename / content-type instead of the
|
||||
// storage-key UUID. Without this every signed download lands with
|
||||
// a bare UUID and no extension. Filenames are escaped per RFC 5987
|
||||
// so a name like "Étude.pdf" survives the round-trip.
|
||||
const reqParams: Record<string, string> = {};
|
||||
if (opts.filename) {
|
||||
const ascii = opts.filename.replace(/[^\x20-\x7e]/g, '_');
|
||||
const encoded = encodeURIComponent(opts.filename);
|
||||
reqParams['response-content-disposition'] =
|
||||
`attachment; filename="${ascii}"; filename*=UTF-8''${encoded}`;
|
||||
}
|
||||
if (opts.contentType) {
|
||||
reqParams['response-content-type'] = opts.contentType;
|
||||
}
|
||||
const url = await this.client.presignedGetObject(
|
||||
this.bucket,
|
||||
key,
|
||||
expiry,
|
||||
Object.keys(reqParams).length > 0 ? reqParams : undefined,
|
||||
);
|
||||
return { url, expiresAt: new Date(Date.now() + expiry * 1000) };
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user