Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 2 additions & 9 deletions apps/api/src/file-storage/org-fs-notify.test.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,16 @@
import { describe, expect, test } from "bun:test";
import {
notifyOrgFsChange,
orgFsChangeSubject,
orgFsChangeSubjects,
} from "./org-fs-notify";
import { notifyOrgFsChange, orgFsChangeSubjects } from "./org-fs-notify";

describe("org-fs NATS compatibility subjects", () => {
test("returns the canonical subject first and its legacy alias second", () => {
expect(orgFsChangeSubject("org-1", "home")).toBe(
"studio.org-fs.changes.org-1.home",
);
expect(orgFsChangeSubjects("org-1", "home")).toEqual([
"studio.org-fs.changes.org-1.home",
"mesh.org-fs.changes.org-1.home",
]);
});

test("rejects unsafe NATS subject tokens", () => {
expect(orgFsChangeSubject("org.with.dot", "home")).toBeNull();
expect(orgFsChangeSubjects("org.with.dot", "home")).toEqual([]);
expect(orgFsChangeSubjects("org-1", "volume.*")).toEqual([]);
});

Expand Down
19 changes: 5 additions & 14 deletions apps/api/src/file-storage/org-fs-notify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,12 @@ const SUBJECT_PREFIXES = [
] as const;

/**
* NATS subject for a volume's change notifications, or null if either token
* contains a character that isn't safe in a subject (`.`, `*`, `>`, ws). Volume
* is already validated upstream; orgId is server-controlled — the guard is
* defensive, and a null subject just means the long-poll falls back to its
* timeout safety net.
* Canonical subject plus the legacy alias used during rolling upgrades, or []
* if either token contains a character that isn't safe in a subject (`.`,
* `*`, `>`, ws). Volume is already validated upstream; orgId is
* server-controlled — the guard is defensive, and empty subjects just mean
* the long-poll falls back to its timeout safety net.
*/
export function orgFsChangeSubject(
orgId: string,
volume: string,
): string | null {
if (/[.*>\s]/.test(orgId) || /[.*>\s]/.test(volume)) return null;
return `${SUBJECT_PREFIXES[0]}.${orgId}.${volume}`;
}

/** Canonical subject plus the legacy alias used during rolling upgrades. */
export function orgFsChangeSubjects(orgId: string, volume: string): string[] {
if (/[.*>\s]/.test(orgId) || /[.*>\s]/.test(volume)) return [];
return SUBJECT_PREFIXES.map((prefix) => `${prefix}.${orgId}.${volume}`);
Expand Down
Loading