Skip to content

Commit d18b08e

Browse files
committed
sec(sanity): enforce private.* ID prefix for booking, sponsorLead, and sponsorPool documents
1 parent 7e7d17a commit d18b08e

15 files changed

Lines changed: 236 additions & 59 deletions

File tree

‎.github/workflows/deploy.yml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ jobs:
4646
run: |
4747
node apps/sanity/scripts/sync-guest-socials.mjs
4848
node apps/sanity/scripts/sync-sponsor-socials.mjs
49+
node apps/sanity/scripts/migrate-private-ids.mjs
4950
env:
5051
SANITY_AUTH_TOKEN: ${{ secrets.SANITY_AUTH_TOKEN }}
5152
SANITY_API_WRITE_TOKEN: ${{ secrets.SANITY_API_WRITE_TOKEN }}
@@ -271,6 +272,7 @@ jobs:
271272
run: |
272273
node apps/sanity/scripts/sync-guest-socials.mjs
273274
node apps/sanity/scripts/sync-sponsor-socials.mjs
275+
node apps/sanity/scripts/migrate-private-ids.mjs
274276
env:
275277
SANITY_AUTH_TOKEN: ${{ secrets.SANITY_AUTH_TOKEN }}
276278
SANITY_API_WRITE_TOKEN: ${{ secrets.SANITY_API_WRITE_TOKEN }}

‎apps/ops/src/App.tsx‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -536,7 +536,7 @@ export function App() {
536536
.slice(0, 12)}_${Math.random().toString(36).slice(2, 6)}`;
537537
const tier =
538538
RATE_CARD_TIERS[newRateCard] || RATE_CARD_TIERS["full-stack-launch"];
539-
const docId = `sponsorLead-${slugToken}`;
539+
const docId = `private.sponsorLead-${slugToken}`;
540540

541541
const newDoc: SponsorLeadDoc & { _type: "sponsorLead" } = {
542542
_id: docId,
@@ -714,7 +714,7 @@ export function App() {
714714
extra?: Record<string, any>,
715715
) => {
716716
const token =
717-
lead.portalToken || lead._id.replace(/^sponsorLead-/, "");
717+
lead.portalToken || lead._id.replace(/^(private\.)?sponsorLead-/, "");
718718
setBusyActionId(`${lead._id}:${action}`);
719719
try {
720720
const res = await fetch(`${siteOrigin}/api/ops/passionfroot`, {
@@ -833,7 +833,7 @@ export function App() {
833833
.toLowerCase()
834834
.replace(/[^a-z0-9]/g, "")
835835
.slice(0, 12)}`;
836-
const docId = `sponsorLead-${slugToken}`;
836+
const docId = `private.sponsorLead-${slugToken}`;
837837
const newDoc: SponsorLeadDoc & { _type: "sponsorLead" } = {
838838
_id: docId,
839839
_type: "sponsorLead",
@@ -1391,7 +1391,7 @@ export function App() {
13911391
.map((lead) => {
13921392
const token =
13931393
lead.portalToken ||
1394-
lead._id.replace(/^sponsorLead-/, "");
1394+
lead._id.replace(/^(private\.)?sponsorLead-/, "");
13951395
const portalUrl = `${siteOrigin}/portal/${token}`;
13961396
const isPf =
13971397
lead.source === "passionfroot" ||
@@ -2128,7 +2128,7 @@ export function App() {
21282128
{filteredLeads.map((lead) => {
21292129
const token =
21302130
lead.portalToken ||
2131-
lead._id.replace(/^sponsorLead-/, "");
2131+
lead._id.replace(/^(private\.)?sponsorLead-/, "");
21322132
const portalUrl = `${siteOrigin}/portal/${token}`;
21332133
const vaultUrl = `${portalUrl}#vault`;
21342134
const gmailUrl = lead.threadId
@@ -3030,7 +3030,7 @@ export function App() {
30303030
b.status === "recorded" ||
30313031
b.status === "published";
30323032
const token =
3033-
b.portalToken || b._id.replace(/^booking-/, "");
3033+
b.portalToken || b._id.replace(/^(private\.)?booking-/, "");
30343034
const portalUrl = `${siteOrigin}/portal/${token}`;
30353035
const vaultUrl = `${portalUrl}#vault`;
30363036
const assetCount = b.sharedAssets?.length || 0;
@@ -3761,7 +3761,7 @@ export function App() {
37613761
{filteredLeads.map((lead) => {
37623762
const token =
37633763
lead.portalToken ||
3764-
lead._id.replace(/^sponsorLead-/, "");
3764+
lead._id.replace(/^(private\.)?sponsorLead-/, "");
37653765
const portalUrl = `${siteOrigin}/portal/${token}`;
37663766
const vaultUrl = `${portalUrl}#vault`;
37673767
const isPf =
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { createClient } from "@sanity/client";
2+
3+
const projectId = process.env.SANITY_STUDIO_PROJECT_ID || "hfh83o0w";
4+
const dataset = process.env.SANITY_STUDIO_DATASET || "production";
5+
const token =
6+
process.env.SANITY_API_WRITE_TOKEN || process.env.SANITY_AUTH_TOKEN;
7+
8+
if (!token) {
9+
console.warn(
10+
"[migrate-private-ids] No SANITY_API_WRITE_TOKEN or SANITY_AUTH_TOKEN provided; skipping migration.",
11+
);
12+
process.exit(0);
13+
}
14+
15+
const client = createClient({
16+
projectId,
17+
dataset,
18+
apiVersion: "2025-02-19",
19+
token,
20+
useCdn: false,
21+
perspective: "raw",
22+
});
23+
24+
async function run() {
25+
const publicDocs = await client.fetch(
26+
`*[_type in ["booking", "sponsorLead", "sponsorPool"] && !(_id in path("private.**")) && !(_id in path("drafts.**"))]`,
27+
);
28+
29+
console.log(
30+
`[migrate-private-ids] Found ${publicDocs.length} unprefixed CRM/booking documents in ${dataset}.`,
31+
);
32+
33+
let migrated = 0;
34+
let deleted = 0;
35+
36+
for (const doc of publicDocs) {
37+
const oldId = String(doc._id || "");
38+
if (!oldId || oldId.startsWith("private.")) continue;
39+
const newId = `private.${oldId}`;
40+
const { _rev, ...rest } = doc;
41+
42+
await client.createOrReplace({
43+
...rest,
44+
_id: newId,
45+
});
46+
migrated++;
47+
48+
try {
49+
await client.delete(`drafts.${oldId}`);
50+
} catch {
51+
// Ignore if no draft exists
52+
}
53+
54+
try {
55+
await client.delete(oldId);
56+
deleted++;
57+
} catch (err) {
58+
console.warn(
59+
`[migrate-private-ids] Could not delete ${oldId}:`,
60+
err?.message || err,
61+
);
62+
}
63+
}
64+
65+
console.log(
66+
`[migrate-private-ids] Completed on ${dataset}: migrated=${migrated}, deleted=${deleted}.`,
67+
);
68+
}
69+
70+
run().catch((err) => {
71+
console.error("[migrate-private-ids] Fatal error:", err);
72+
process.exit(1);
73+
});

‎apps/sanity/structure.ts‎

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,13 @@ export const editorialStructure: StructureResolver = (S) =>
101101
.title("All Podcast & Sponsor Bookings")
102102
.icon(FaPodcast)
103103
.child(
104-
S.documentTypeList("booking")
104+
S.documentList()
105105
.title("All Bookings")
106+
.schemaType("booking")
107+
.apiVersion("2025-02-19")
108+
.filter(
109+
'_type == "booking" && (_id in path("private.**") || _id in path("*"))',
110+
)
106111
.defaultOrdering([
107112
{ field: "slotStartAt", direction: "desc" },
108113
]),
@@ -113,7 +118,10 @@ export const editorialStructure: StructureResolver = (S) =>
113118
S.documentList()
114119
.title("Pending Booking Holds")
115120
.schemaType("booking")
116-
.filter('_type == "booking" && status == "pending"')
121+
.apiVersion("2025-02-19")
122+
.filter(
123+
'_type == "booking" && status == "pending" && (_id in path("private.**") || _id in path("*"))',
124+
)
117125
.defaultOrdering([
118126
{ field: "_createdAt", direction: "desc" },
119127
]),
@@ -124,8 +132,9 @@ export const editorialStructure: StructureResolver = (S) =>
124132
S.documentList()
125133
.title("Confirmed Bookings")
126134
.schemaType("booking")
135+
.apiVersion("2025-02-19")
127136
.filter(
128-
'_type == "booking" && status in ["approved", "scheduled"]',
137+
'_type == "booking" && status in ["approved", "scheduled"] && (_id in path("private.**") || _id in path("*"))',
129138
)
130139
.defaultOrdering([
131140
{ field: "slotStartAt", direction: "asc" },
@@ -136,8 +145,13 @@ export const editorialStructure: StructureResolver = (S) =>
136145
.title("All Sponsor Leads & Deal Rooms")
137146
.icon(PiCurrencyDollarSimpleFill)
138147
.child(
139-
S.documentTypeList("sponsorLead")
148+
S.documentList()
140149
.title("Sponsor Leads & Deal Rooms")
150+
.schemaType("sponsorLead")
151+
.apiVersion("2025-02-19")
152+
.filter(
153+
'_type == "sponsorLead" && (_id in path("private.**") || _id in path("*"))',
154+
)
141155
.defaultOrdering([
142156
{ field: "_updatedAt", direction: "desc" },
143157
]),
@@ -148,8 +162,9 @@ export const editorialStructure: StructureResolver = (S) =>
148162
S.documentList()
149163
.title("Active Sponsor Deals")
150164
.schemaType("sponsorLead")
165+
.apiVersion("2025-02-19")
151166
.filter(
152-
'_type == "sponsorLead" && status in ["new", "contacted", "replied", "negotiating", "deposit_sent"]',
167+
'_type == "sponsorLead" && status in ["new", "contacted", "replied", "negotiating", "deposit_sent"] && (_id in path("private.**") || _id in path("*"))',
153168
),
154169
),
155170
S.listItem()
@@ -158,14 +173,21 @@ export const editorialStructure: StructureResolver = (S) =>
158173
S.documentList()
159174
.title("Paid / In Production")
160175
.schemaType("sponsorLead")
176+
.apiVersion("2025-02-19")
161177
.filter(
162-
'_type == "sponsorLead" && status in ["deposit_paid", "booked", "balance_sent", "paid"]',
178+
'_type == "sponsorLead" && status in ["deposit_paid", "booked", "balance_sent", "paid"] && (_id in path("private.**") || _id in path("*"))',
163179
),
164180
),
165181
S.listItem()
166182
.title("Outbound Prospects (Sponsor Pool)")
167183
.child(
168-
S.documentTypeList("sponsorPool").title("Outbound Prospects"),
184+
S.documentList()
185+
.title("Outbound Prospects")
186+
.schemaType("sponsorPool")
187+
.apiVersion("2025-02-19")
188+
.filter(
189+
'_type == "sponsorPool" && (_id in path("private.**") || _id in path("*"))',
190+
),
169191
),
170192
S.divider(),
171193
S.listItem()
@@ -411,8 +433,13 @@ export const editorialStructure: StructureResolver = (S) =>
411433
.title("All Discovered Trends (Ranked)")
412434
.icon(FaFire)
413435
.child(
414-
S.documentTypeList("trendSignal")
436+
S.documentList()
415437
.title("All Discovered Trends")
438+
.schemaType("trendSignal")
439+
.apiVersion("2025-02-19")
440+
.filter(
441+
'_type == "trendSignal" && (_id in path("trend.**") || _id in path("*"))',
442+
)
416443
.defaultOrdering([
417444
{ field: "score", direction: "desc" },
418445
{ field: "discoveredAt", direction: "desc" },

‎apps/site/src/lib/sanity/write-client.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,7 @@ export function getSanityServerClient(): {
232232
apiVersion,
233233
useCdn: false,
234234
token,
235-
perspective: "published",
235+
perspective: "raw",
236236
});
237237

238238
return {

0 commit comments

Comments
 (0)