Skip to content

Commit 03e809b

Browse files
committed
feat(sanity): configure Media Library mlttp0BZbCpV folders, metadata aspect & migration script
1 parent 3baea16 commit 03e809b

8 files changed

Lines changed: 2144 additions & 4 deletions

File tree

‎.gitignore‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ dist/
1515
# sanity
1616
.sanity/
1717
.build/
18+
export/
19+
.media-migration-state-*.json
1820

1921
# env files
2022
.env

‎apps/sanity/sanity.cli.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ export default defineCliConfig({
55
projectId: process.env.SANITY_STUDIO_PROJECT_ID || "hfh83o0w",
66
dataset: process.env.SANITY_STUDIO_DATASET || "production",
77
},
8+
mediaLibrary: {
9+
aspectsPath: "./schemas/aspects",
10+
},
811
// Set via SANITY_STUDIO_HOSTNAME in CI: e.g. "codingcat.dev" (prod), "codingcat-dev" (dev)
912
studioHost: process.env.SANITY_STUDIO_HOSTNAME,
1013
deployment: {

‎apps/sanity/sanity.config.ts‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@ import { singletonPlugin } from "./plugins/settings";
2525
import { assistWithPresets } from "./plugins/assist";
2626
import { editorialStructure } from "./structure";
2727
import author from "./schemas/documents/author";
28+
import course from "./schemas/documents/course";
2829
import guest from "./schemas/documents/guest";
30+
import lesson from "./schemas/documents/lesson";
2931
import page from "./schemas/documents/page";
3032
import podcast from "./schemas/documents/podcast";
3133
import podcastType from "./schemas/documents/podcastType";
@@ -44,6 +46,36 @@ import trendSignal from "./schemas/documents/trendSignal";
4446
// ── Shared constants ─────────────────────────────────────────────────
4547
const projectId = process.env.SANITY_STUDIO_PROJECT_ID || "hfh83o0w";
4648
const apiVersion = process.env.SANITY_STUDIO_API_VERSION || "2025-09-30";
49+
export const MEDIA_LIBRARY_ID =
50+
process.env.SANITY_STUDIO_MEDIA_LIBRARY_ID || "mlttp0BZbCpV";
51+
52+
export function buildMediaLibraryFilters(dataset: "production" | "dev") {
53+
return [
54+
{
55+
name:
56+
dataset === "production"
57+
? "Production Public Assets"
58+
: "Dev Sandbox Assets",
59+
query: `aspects.metadata.environment == "${dataset}" && aspects.metadata.visibility != "private-vault"`,
60+
},
61+
{
62+
name: "Editorial (Courses, Podcasts, Blog)",
63+
query: `aspects.metadata.environment == "${dataset}" && aspects.metadata.category == "editorial"`,
64+
},
65+
{
66+
name: "Guest Speaker Headshots",
67+
query: `aspects.metadata.environment == "${dataset}" && aspects.metadata.category == "guest"`,
68+
},
69+
{
70+
name: "Sponsor Logos & Creatives",
71+
query: `aspects.metadata.environment == "${dataset}" && aspects.metadata.category == "sponsor"`,
72+
},
73+
{
74+
name: "Studio Portal Vault (Private)",
75+
query: `aspects.metadata.environment == "${dataset}" && (aspects.metadata.category == "portal-vault" || aspects.metadata.visibility == "private-vault")`,
76+
},
77+
];
78+
}
4779

4880
// Set SANITY_STUDIO_DISABLE_PRESENTATION=true if you get network errors to api.sanity.io (e.g. firewall/VPN)
4981
const presentationEnabled =
@@ -84,7 +116,9 @@ const schemaTypes = [
84116
sponsorConfig,
85117
// Documents
86118
author,
119+
course,
87120
guest,
121+
lesson,
88122
page,
89123
podcast,
90124
podcastType,
@@ -187,12 +221,23 @@ function buildPlugins(previewUrl: string): PluginOptions[] {
187221
// ── Workspace definitions ────────────────────────────────────────────
188222
const targetDataset = process.env.SANITY_STUDIO_DATASET;
189223

224+
const sharedMediaLibraryConfig = {
225+
mediaLibrary: {
226+
enabled: true,
227+
libraryId: MEDIA_LIBRARY_ID,
228+
},
229+
auth: {
230+
loginMethod: "token" as const,
231+
},
232+
};
233+
190234
const devWorkspace = {
191235
name: "dev",
192236
title: "CodingCat.dev (Dev)",
193237
projectId,
194238
dataset: "dev",
195239
basePath: targetDataset === "dev" ? "/" : "/dev",
240+
...sharedMediaLibraryConfig,
196241
schema: { types: schemaTypes },
197242
plugins: buildPlugins(
198243
isLocal ? localPreviewOrigin : "https://dev.codingcat.dev",
@@ -205,6 +250,7 @@ const prodWorkspace = {
205250
projectId,
206251
dataset: "production",
207252
basePath: targetDataset === "production" ? "/" : "/production",
253+
...sharedMediaLibraryConfig,
208254
schema: { types: schemaTypes },
209255
plugins: buildPlugins(
210256
isLocal ? localPreviewOrigin : "https://codingcat.dev",
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { defineAssetAspect, defineField } from "sanity";
2+
3+
/**
4+
* Sanity Media Library (`mlttp0BZbCpV`) `metadata` aspect definition.
5+
*
6+
* Captures both:
7+
* 1. Environment & folder governance (`environment`, `visibility`, `category`, `partnerSlug`, `docType`)
8+
* so `production` and `dev` assets (and `public` vs `private-vault` uploads) are cleanly separated
9+
* via both physical folders (`sanity.directory`) and GROQ filters (`options.mediaLibrary.filters`).
10+
* 2. Legacy `sanity-plugin-media` fields (`title`, `description`, `creditLine`, `altText`, `originalFilename`, `tags`)
11+
* migrated by `scripts/migrate-media.ts --include-aspects`.
12+
*/
13+
export default defineAssetAspect({
14+
name: "metadata",
15+
title: "Metadata",
16+
type: "object",
17+
fields: [
18+
defineField({
19+
name: "environment",
20+
title: "Environment (Dataset)",
21+
type: "string",
22+
options: {
23+
list: [
24+
{ title: "Production", value: "production" },
25+
{ title: "Dev Sandbox", value: "dev" },
26+
],
27+
},
28+
}),
29+
defineField({
30+
name: "visibility",
31+
title: "Visibility Tier",
32+
type: "string",
33+
options: {
34+
list: [
35+
{ title: "Public Editorial / Brand Asset", value: "public" },
36+
{ title: "Private Studio Portal Vault", value: "private-vault" },
37+
],
38+
},
39+
}),
40+
defineField({
41+
name: "category",
42+
title: "Asset Category",
43+
type: "string",
44+
options: {
45+
list: [
46+
{ title: "Editorial (Courses, Podcasts, Blog)", value: "editorial" },
47+
{ title: "Guest Speaker Headshot", value: "guest" },
48+
{ title: "Sponsor Logo / Creative", value: "sponsor" },
49+
{ title: "Studio Portal Vault", value: "portal-vault" },
50+
],
51+
},
52+
}),
53+
defineField({
54+
name: "partnerSlug",
55+
title: "Guest / Partner Slug",
56+
type: "string",
57+
}),
58+
defineField({
59+
name: "docType",
60+
title: "Primary Referencing Document Type",
61+
type: "string",
62+
}),
63+
defineField({
64+
name: "title",
65+
title: "Title",
66+
type: "string",
67+
}),
68+
defineField({
69+
name: "description",
70+
title: "Description",
71+
type: "text",
72+
}),
73+
defineField({
74+
name: "creditLine",
75+
title: "Credit line",
76+
type: "string",
77+
}),
78+
defineField({
79+
name: "altText",
80+
title: "Alt text",
81+
type: "string",
82+
}),
83+
defineField({
84+
name: "originalFilename",
85+
title: "Original filename",
86+
type: "string",
87+
}),
88+
defineField({
89+
name: "tags",
90+
title: "Tags",
91+
type: "array",
92+
of: [{ type: "string" }],
93+
}),
94+
],
95+
});

0 commit comments

Comments
 (0)