@remit/backend 0.0.14 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remit/backend",
3
- "version": "0.0.14",
3
+ "version": "0.0.15",
4
4
  "description": "Remit Mail Inspector API backend",
5
5
  "license": "MIT",
6
6
  "author": "",
@@ -0,0 +1,81 @@
1
+ import assert from "node:assert/strict";
2
+ import { afterEach, describe, it } from "node:test";
3
+ import type { QuarantineItem } from "@remit/data-ports";
4
+ import type { APIGatewayProxyEvent } from "aws-lambda";
5
+ import type { Context } from "openapi-backend";
6
+ import { deriveAccountConfigId } from "../auth.js";
7
+ import {
8
+ _resetForTest,
9
+ type RemitClient,
10
+ setClient,
11
+ } from "../service/dynamodb.js";
12
+ import { MeOperations } from "./me.js";
13
+
14
+ const listQuarantine = MeOperations.MeOperations_listQuarantine as unknown as (
15
+ context: Context,
16
+ event: APIGatewayProxyEvent,
17
+ ) => Promise<{ entries: QuarantineItem[] }>;
18
+
19
+ const SUB = "cognito-sub-1";
20
+
21
+ const eventFor = (sub: string): APIGatewayProxyEvent =>
22
+ ({
23
+ requestContext: { authorizer: { claims: { sub } } },
24
+ }) as unknown as APIGatewayProxyEvent;
25
+
26
+ const entry = (over: Partial<QuarantineItem> = {}): QuarantineItem =>
27
+ ({
28
+ quarantineId: "q-1",
29
+ accountConfigId: deriveAccountConfigId(SUB),
30
+ accountId: "acct-1",
31
+ mailboxId: "mbx-1",
32
+ uidValidity: 1_712_000_000,
33
+ uid: 40217,
34
+ mailboxPath: "INBOX",
35
+ quarantinedAt: 1_000,
36
+ attempts: 3,
37
+ failureStage: "BodyParse",
38
+ failureCode: "UnterminatedMultipartBoundary",
39
+ failureMessage: "multipart boundary was never closed",
40
+ workerVersion: "worker 1.0.0",
41
+ structure: [{ depth: 0, contentType: "multipart/mixed" }],
42
+ createdAt: 1_000,
43
+ updatedAt: 1_000,
44
+ ...over,
45
+ }) as QuarantineItem;
46
+
47
+ const clientListing = (items: QuarantineItem[], seen: string[]): RemitClient =>
48
+ ({
49
+ quarantine: {
50
+ listByAccountConfigId: async (
51
+ accountConfigId: string,
52
+ ): Promise<QuarantineItem[]> => {
53
+ seen.push(accountConfigId);
54
+ return items;
55
+ },
56
+ },
57
+ }) as unknown as RemitClient;
58
+
59
+ afterEach(() => {
60
+ _resetForTest();
61
+ });
62
+
63
+ describe("MeOperations_listQuarantine", () => {
64
+ it("lists only what belongs to the caller", async () => {
65
+ const seen: string[] = [];
66
+ setClient(clientListing([entry()], seen));
67
+
68
+ const response = await listQuarantine({} as Context, eventFor(SUB));
69
+
70
+ assert.deepEqual(seen, [deriveAccountConfigId(SUB)]);
71
+ assert.deepEqual(response, { entries: [entry()] });
72
+ });
73
+
74
+ it("returns an empty list rather than failing when nothing is quarantined", async () => {
75
+ setClient(clientListing([], []));
76
+
77
+ const response = await listQuarantine({} as Context, eventFor(SUB));
78
+
79
+ assert.deepEqual(response, { entries: [] });
80
+ });
81
+ });
@@ -4,6 +4,7 @@ import type {
4
4
  CreateExportResponse,
5
5
  DeleteAccountConfigResponse,
6
6
  DeleteMeInput,
7
+ QuarantineListResponse,
7
8
  VipSuggestionsResponse,
8
9
  } from "@remit/api-openapi-types";
9
10
  import { BadRequestError, NotFoundError } from "@remit/data-ports/errors";
@@ -35,6 +36,18 @@ export const MeOperations: Record<
35
36
 
36
37
  return { suggestions: items.map(toVipSuggestionEntry) };
37
38
  },
39
+ MeOperations_listQuarantine: async (
40
+ _context: Context,
41
+ ...args: unknown[]
42
+ ): Promise<QuarantineListResponse> => {
43
+ const event = args[0] as APIGatewayProxyEvent;
44
+ const accountConfigId = getAccountConfigIdFromEvent(event);
45
+
46
+ const { quarantine } = await getClient();
47
+ const entries = await quarantine.listByAccountConfigId(accountConfigId);
48
+
49
+ return { entries };
50
+ },
38
51
  MeOperations_deleteMe: async (
39
52
  _context: Context,
40
53
  ...args: unknown[]
@@ -21,6 +21,7 @@ import {
21
21
  messageDataSchema,
22
22
  OrganizeJobRequestRepo,
23
23
  OutboxMessageRepo,
24
+ QuarantineRepo,
24
25
  } from "@remit/drizzle-service";
25
26
  import { drizzle, type NodePgDatabase } from "drizzle-orm/node-postgres";
26
27
  import { env } from "expect-env";
@@ -58,6 +59,7 @@ export const buildPostgresClient = (): RemitClient => {
58
59
  threadMessage: new DrizzleThreadMessageRepository(pgConnectionUrl),
59
60
  envelope: new DrizzleEnvelopeRepository(messageDataDb),
60
61
  accountExportRequest: new AccountExportRequestRepo(genericDb),
62
+ quarantine: new QuarantineRepo(genericDb),
61
63
  organizeJobRequest: new OrganizeJobRequestRepo(genericDb),
62
64
  placementMove: new MessagePlacementMoveRepo(genericDb),
63
65
  flagPush: new MessageFlagPushRepo(genericDb),
@@ -22,6 +22,7 @@ import {
22
22
  messageDataSchema,
23
23
  OrganizeJobRequestRepo,
24
24
  OutboxMessageRepo,
25
+ QuarantineRepo,
25
26
  } from "@remit/drizzle-service";
26
27
  import type { NodePgDatabase } from "drizzle-orm/node-postgres";
27
28
  import { env } from "expect-env";
@@ -66,6 +67,7 @@ export const buildSqliteClient = async (): Promise<RemitClient> => {
66
67
  threadMessage: new DrizzleThreadMessageRepository(genericDb),
67
68
  envelope: new DrizzleEnvelopeRepository(messageDataDb),
68
69
  accountExportRequest: new AccountExportRequestRepo(genericDb),
70
+ quarantine: new QuarantineRepo(genericDb),
69
71
  organizeJobRequest: new OrganizeJobRequestRepo(genericDb),
70
72
  placementMove: new MessagePlacementMoveRepo(genericDb),
71
73
  flagPush: new MessageFlagPushRepo(genericDb),
@@ -18,6 +18,7 @@ import type {
18
18
  IMessageRepository,
19
19
  IOrganizeJobRequestRepository,
20
20
  IOutboxMessageRepository,
21
+ IQuarantineRepository,
21
22
  IThreadMessageRepository,
22
23
  IUnitOfWork,
23
24
  } from "@remit/data-ports";
@@ -71,6 +72,10 @@ export interface RemitClient {
71
72
  envelope: IEnvelopeRepository;
72
73
  accountExportRequest: IAccountExportRequestRepository;
73
74
 
75
+ // Messages the sync path could not read (issue #72). Read-only from the API
76
+ // process: the sync worker writes the rows, settings lists them.
77
+ quarantine: IQuarantineRepository;
78
+
74
79
  // Smart Organize back-apply job (RFC 034, #1278). Present on both backends,
75
80
  // modeled on accountExportRequest — a Pending row the account-fanout worker
76
81
  // picks up and drives to Complete/Failed. Never a Filter.
@@ -151,6 +156,7 @@ export interface RemitClientRepositories {
151
156
  threadMessage: IThreadMessageRepository;
152
157
  envelope: IEnvelopeRepository;
153
158
  accountExportRequest: IAccountExportRequestRepository;
159
+ quarantine: IQuarantineRepository;
154
160
  organizeJobRequest: IOrganizeJobRequestRepository;
155
161
  placementMove: IMessagePlacementMoveRepository;
156
162
  flagPush: IMessageFlagPushRepository;
@@ -310,6 +316,7 @@ export const createRemitClient = (deps: RemitClientDeps): RemitClient => {
310
316
  threadMessage: repositories.threadMessage,
311
317
  envelope: repositories.envelope,
312
318
  accountExportRequest: repositories.accountExportRequest,
319
+ quarantine: repositories.quarantine,
313
320
  organizeJobRequest: repositories.organizeJobRequest,
314
321
  filter: repositories.filter,
315
322
  filterAnchor: repositories.filterAnchor,
@@ -20,6 +20,7 @@ const REQUIRED_KEYS: ReadonlyArray<keyof RemitClient> = [
20
20
  "threadMessage",
21
21
  "envelope",
22
22
  "accountExportRequest",
23
+ "quarantine",
23
24
  "storage",
24
25
  "search",
25
26
  "secrets",
package/src/types.ts CHANGED
@@ -10,6 +10,7 @@ export type OperationIds =
10
10
  | "MeOperations_listVipSuggestions"
11
11
  | "MeOperations_createExport"
12
12
  | "MeOperations_getExport"
13
+ | "MeOperations_listQuarantine"
13
14
  | "ConfigOperations_getConfig"
14
15
  | "AccountOperations_createAccount"
15
16
  | "AccountOperations_testConnection"