diff --git a/apps/cms/package.json b/apps/cms/package.json index 3462b9f..41bdf9b 100644 --- a/apps/cms/package.json +++ b/apps/cms/package.json @@ -23,6 +23,7 @@ "react": "^18.0.0", "react-dom": "^18.0.0", "react-router-dom": "^6.30.3", + "smol-toml": "^1.8.0", "styled-components": "^6.0.0" }, "devDependencies": { diff --git a/apps/cms/src/api/bookshelf-entry/content-types/bookshelf-entry/schema.json b/apps/cms/src/api/bookshelf-entry/content-types/bookshelf-entry/schema.json index 5ff135a..8f394b2 100644 --- a/apps/cms/src/api/bookshelf-entry/content-types/bookshelf-entry/schema.json +++ b/apps/cms/src/api/bookshelf-entry/content-types/bookshelf-entry/schema.json @@ -15,6 +15,12 @@ "type": "string", "required": true }, + "slug": { + "type": "uid", + "targetField": "title", + "required": true, + "description": "Matches the file name under content/bookshelf/" + }, "kind": { "type": "enumeration", "enum": ["book", "paper"], diff --git a/apps/cms/src/api/curated-blog/content-types/curated-blog/schema.json b/apps/cms/src/api/curated-blog/content-types/curated-blog/schema.json index 50e6359..e47f24c 100644 --- a/apps/cms/src/api/curated-blog/content-types/curated-blog/schema.json +++ b/apps/cms/src/api/curated-blog/content-types/curated-blog/schema.json @@ -15,6 +15,12 @@ "type": "string", "required": true }, + "slug": { + "type": "uid", + "targetField": "title", + "required": true, + "description": "Matches the file name under content/blogs/" + }, "url": { "type": "string", "required": true diff --git a/apps/cms/src/api/portfolio-entry/content-types/portfolio-entry/schema.json b/apps/cms/src/api/portfolio-entry/content-types/portfolio-entry/schema.json index 8518d91..acf5a2a 100644 --- a/apps/cms/src/api/portfolio-entry/content-types/portfolio-entry/schema.json +++ b/apps/cms/src/api/portfolio-entry/content-types/portfolio-entry/schema.json @@ -15,6 +15,12 @@ "type": "string", "required": true }, + "slug": { + "type": "uid", + "targetField": "title", + "required": true, + "description": "Matches the file name under content/portfolio/" + }, "description": { "type": "text" }, diff --git a/apps/cms/src/api/writing/content-types/writing/schema.json b/apps/cms/src/api/writing/content-types/writing/schema.json index aa3b19f..0fa92ce 100644 --- a/apps/cms/src/api/writing/content-types/writing/schema.json +++ b/apps/cms/src/api/writing/content-types/writing/schema.json @@ -15,6 +15,12 @@ "type": "string", "required": true }, + "slug": { + "type": "uid", + "targetField": "title", + "required": true, + "description": "Matches the file/directory name under content/writings/" + }, "description": { "type": "text" }, diff --git a/apps/cms/src/index.ts b/apps/cms/src/index.ts index 58be268..6770761 100644 --- a/apps/cms/src/index.ts +++ b/apps/cms/src/index.ts @@ -1,20 +1,20 @@ -// import type { Core } from '@strapi/strapi'; +import type { Core } from "@strapi/strapi"; +import { + importContentFromDisk, + registerContentSync, +} from "./utils/content-sync"; export default { - /** - * An asynchronous register function that runs before - * your application is initialized. - * - * This gives you an opportunity to extend code. - */ - register(/* { strapi }: { strapi: Core.Strapi } */) {}, + register() {}, /** - * An asynchronous bootstrap function that runs before - * your application gets started. - * - * This gives you an opportunity to set up your data model, - * run jobs, or perform some special logic. + * content/ is the source of truth (see content/README.md). On boot we + * import every file into Strapi's DB (a disposable cache), then register + * a Document Service middleware that writes any create/update/delete made + * through Strapi straight back to the same files. */ - bootstrap(/* { strapi }: { strapi: Core.Strapi } */) {}, + async bootstrap({ strapi }: { strapi: Core.Strapi }) { + registerContentSync(strapi as never); + await importContentFromDisk(strapi as never); + }, }; diff --git a/apps/cms/src/utils/content-file.ts b/apps/cms/src/utils/content-file.ts new file mode 100644 index 0000000..3c889f8 --- /dev/null +++ b/apps/cms/src/utils/content-file.ts @@ -0,0 +1,127 @@ +import { + mkdirSync, + readdirSync, + readFileSync, + rmSync, + writeFileSync, +} from "node:fs"; +import { dirname, join, relative } from "node:path"; +import { + parse as parseToml, + stringify as stringifyToml, + TomlDate, +} from "smol-toml"; + +const FRONTMATTER_RE = /^\+\+\+\r?\n([\s\S]*?)\r?\n\+\+\+\r?\n?([\s\S]*)$/; + +export interface ParsedEntryFile { + slug: string; + frontmatter: Record; + body: string; +} + +/** Strips undefined/null/empty-string/empty-array values so smol-toml doesn't choke on them. */ +export function compact>( + obj: T, +): Record { + const out: Record = {}; + for (const [key, value] of Object.entries(obj)) { + if (value === undefined || value === null || value === "") continue; + if (Array.isArray(value) && value.length === 0) continue; + if ( + typeof value === "object" && + !Array.isArray(value) && + !(value instanceof Date) + ) { + const nested = compact(value as Record); + if (Object.keys(nested).length === 0) continue; + out[key] = nested; + continue; + } + out[key] = value; + } + return out; +} + +/** Converts a Strapi "date" field (an ISO `YYYY-MM-DD` string) to a bare TOML local-date. */ +export function toTomlDate( + value: string | null | undefined, +): TomlDate | undefined { + return value ? new TomlDate(value) : undefined; +} + +function walk(dir: string): string[] { + const out: string[] = []; + for (const entry of readdirSync(dir, { withFileTypes: true })) { + const full = join(dir, entry.name); + if (entry.isDirectory()) out.push(...walk(full)); + else if (entry.isFile() && full.endsWith(".md")) out.push(full); + } + return out; +} + +function slugFromPath(dir: string, file: string): string { + const rel = relative(dir, file).split("\\").join("/"); + return rel.endsWith("/index.md") + ? rel.slice(0, -"/index.md".length) + : rel.slice(0, -".md".length); +} + +/** Reads every `+++`-frontmatter Markdown file under `dir` (flat or `/index.md`). */ +export function readAllEntries(dir: string): ParsedEntryFile[] { + let files: string[]; + try { + files = walk(dir); + } catch { + return []; + } + return files.flatMap((file) => { + const raw = readFileSync(file, "utf8"); + const match = raw.match(FRONTMATTER_RE); + if (!match) return []; + const [, fm, body] = match; + return [ + { + slug: slugFromPath(dir, file), + frontmatter: parseToml(fm), + body: body.trim(), + }, + ]; + }); +} + +function entryPath( + dir: string, + slug: string, + layout: "nested" | "flat", +): string { + return layout === "nested" + ? join(dir, slug, "index.md") + : join(dir, `${slug}.md`); +} + +/** Writes (or overwrites) the frontmatter+body file for `slug` under `dir`. */ +export function writeEntry( + dir: string, + slug: string, + layout: "nested" | "flat", + frontmatter: Record, + body: string, +): void { + const file = entryPath(dir, slug, layout); + mkdirSync(dirname(file), { recursive: true }); + const toml = stringifyToml(compact(frontmatter)).trimEnd(); + writeFileSync(file, `+++\n${toml}\n+++\n\n${(body ?? "").trim()}\n`); +} + +/** Deletes the file (and, for nested layout, its now-empty directory) for `slug`. */ +export function deleteEntry( + dir: string, + slug: string, + layout: "nested" | "flat", +): void { + const file = entryPath(dir, slug, layout); + rmSync(file, { force: true }); + if (layout === "nested") + rmSync(join(dir, slug), { recursive: true, force: true }); +} diff --git a/apps/cms/src/utils/content-sync.ts b/apps/cms/src/utils/content-sync.ts new file mode 100644 index 0000000..a7c1147 --- /dev/null +++ b/apps/cms/src/utils/content-sync.ts @@ -0,0 +1,320 @@ +import { join } from "node:path"; +import { + deleteEntry, + readAllEntries, + toTomlDate, + writeEntry, +} from "./content-file"; + +type Layout = "nested" | "flat"; + +interface SyncConfig { + uid: string; + dir: string; + layout: Layout; + /** Strapi entry -> { frontmatter, body } written to disk. */ + toFile: (entry: Record) => { + frontmatter: Record; + body: string; + }; + /** Parsed file -> Strapi entry fields, for importing content/ into Strapi on boot. */ + toEntry: ( + slug: string, + frontmatter: Record, + body: string, + ) => Record; +} + +const isoDate = (value: unknown): string | undefined => + value instanceof Date + ? value.toISOString().slice(0, 10) + : typeof value === "string" + ? value + : undefined; + +function contentRoot(): string { + // apps/cms -> repo root/content (also the mount point in the dev container) + return join(process.cwd(), "..", "..", "content"); +} + +function buildConfigs(): Record { + const root = contentRoot(); + + const writing: SyncConfig = { + uid: "api::writing.writing", + dir: join(root, "writings"), + layout: "nested", + toFile: (e) => ({ + frontmatter: { + title: e.title, + description: e.description, + author: e.author, + date: toTomlDate(e.date as string), + updated: toTomlDate(e.updated as string), + draft: e.draft ?? false, + canonical_url: e.canonical_url, + taxonomies: { tags: e.tags ?? [] }, + extra: { + series: e.series, + series_index: e.series_index, + llms_description: e.llms_description, + }, + }, + body: (e.body as string) ?? "", + }), + toEntry: (slug, fm, body) => { + const taxonomies = fm.taxonomies as { tags?: string[] } | undefined; + const extra = fm.extra as + | { series?: string; series_index?: number; llms_description?: string } + | undefined; + return { + slug, + title: fm.title, + description: fm.description, + author: fm.author, + date: isoDate(fm.date), + updated: isoDate(fm.updated), + draft: fm.draft ?? false, + canonical_url: fm.canonical_url, + tags: taxonomies?.tags ?? [], + series: extra?.series, + series_index: extra?.series_index, + llms_description: extra?.llms_description, + body, + }; + }, + }; + + const portfolioEntry: SyncConfig = { + uid: "api::portfolio-entry.portfolio-entry", + dir: join(root, "portfolio"), + layout: "flat", + toFile: (e) => ({ + frontmatter: { + title: e.title, + description: e.description, + role: e.role, + date_start: toTomlDate(e.date_start as string), + date_end: toTomlDate(e.date_end as string), + outcome: e.outcome, + draft: e.draft ?? false, + links: e.links ?? [], + taxonomies: { tags: e.tags ?? [] }, + }, + body: (e.body as string) ?? "", + }), + toEntry: (slug, fm, body) => { + const taxonomies = fm.taxonomies as { tags?: string[] } | undefined; + return { + slug, + title: fm.title, + description: fm.description, + role: fm.role, + date_start: isoDate(fm.date_start), + date_end: isoDate(fm.date_end), + outcome: fm.outcome, + draft: fm.draft ?? false, + links: fm.links ?? [], + tags: taxonomies?.tags ?? [], + body, + }; + }, + }; + + const bookshelfEntry: SyncConfig = { + uid: "api::bookshelf-entry.bookshelf-entry", + dir: join(root, "bookshelf"), + layout: "flat", + toFile: (e) => ({ + frontmatter: { + title: e.title, + kind: e.kind, + authors: e.authors ?? [], + isbn: e.isbn, + doi: e.doi, + arxiv_id: e.arxiv_id, + url: e.url, + cover_image: e.cover_image, + rating: e.rating, + status: e.status, + date_started: toTomlDate(e.date_started as string), + date_finished: toTomlDate(e.date_finished as string), + taxonomies: { tags: e.tags ?? [] }, + }, + body: (e.body as string) ?? "", + }), + toEntry: (slug, fm, body) => { + const taxonomies = fm.taxonomies as { tags?: string[] } | undefined; + return { + slug, + title: fm.title, + kind: fm.kind, + authors: fm.authors ?? [], + isbn: fm.isbn, + doi: fm.doi, + arxiv_id: fm.arxiv_id, + url: fm.url, + cover_image: fm.cover_image, + rating: fm.rating, + status: fm.status, + date_started: isoDate(fm.date_started), + date_finished: isoDate(fm.date_finished), + tags: taxonomies?.tags ?? [], + body, + }; + }, + }; + + const curatedBlog: SyncConfig = { + uid: "api::curated-blog.curated-blog", + dir: join(root, "blogs"), + layout: "flat", + toFile: (e) => ({ + frontmatter: { + title: e.title, + url: e.url, + feed_url: e.feed_url, + }, + body: (e.body as string) ?? "", + }), + toEntry: (slug, fm, body) => ({ + slug, + title: fm.title, + url: fm.url, + feed_url: fm.feed_url, + body, + }), + }; + + return { + [writing.uid]: writing, + [portfolioEntry.uid]: portfolioEntry, + [bookshelfEntry.uid]: bookshelfEntry, + [curatedBlog.uid]: curatedBlog, + }; +} + +export const SYNC_CONFIGS = buildConfigs(); + +/** Guards the outbound (DB -> file) middleware while the inbound (file -> DB) import runs. */ +let importing = false; + +function writeEntryForConfig( + config: SyncConfig, + entry: Record, +): void { + const slug = entry.slug as string | undefined; + if (!slug) return; + const { frontmatter, body } = config.toFile(entry); + writeEntry(config.dir, slug, config.layout, frontmatter, body); +} + +/** + * On boot, upserts every file under content// into Strapi so the admin + * UI reflects on-disk edits made outside Strapi. Strapi's DB stays a + * disposable cache: this can always rebuild it from content/ alone. + */ +export async function importContentFromDisk(strapi: { + documents: (uid: string) => { + findFirst: (params: { + filters: Record; + }) => Promise | null>; + create: (params: { data: Record }) => Promise; + update: (params: { + documentId: string; + data: Record; + }) => Promise; + }; + log: { info: (msg: string) => void; error: (msg: string) => void }; +}): Promise { + importing = true; + try { + for (const config of Object.values(SYNC_CONFIGS)) { + const files = readAllEntries(config.dir); + let created = 0; + let updated = 0; + for (const { slug, frontmatter, body } of files) { + const data = config.toEntry(slug, frontmatter, body); + try { + const existing = await strapi + .documents(config.uid) + .findFirst({ filters: { slug } }); + if (existing) { + await strapi + .documents(config.uid) + .update({ documentId: existing.documentId as string, data }); + updated += 1; + } else { + await strapi.documents(config.uid).create({ data }); + created += 1; + } + } catch (err) { + strapi.log.error( + `content-sync: failed to import ${config.dir}/${slug}: ${(err as Error).message}`, + ); + } + } + strapi.log.info( + `content-sync: imported ${config.uid} (${created} created, ${updated} updated, ${files.length} total)`, + ); + } + } finally { + importing = false; + } +} + +/** Registers the outbound (DB -> file) sync as a Document Service middleware. */ +export function registerContentSync(strapi: { + documents: { + use: ( + middleware: ( + ctx: { uid: string; action: string; params: Record }, + next: () => Promise, + ) => Promise, + ) => void; + ( + uid: string, + ): { + findOne: (params: { + documentId: string; + }) => Promise | null>; + }; + }; + log: { info: (msg: string) => void; error: (msg: string) => void }; +}): void { + strapi.documents.use(async (ctx, next) => { + const config = SYNC_CONFIGS[ctx.uid]; + if (!config || importing) return next(); + + let preDeleteSlug: string | undefined; + if (ctx.action === "delete") { + const documentId = (ctx.params as { documentId?: string }).documentId; + if (documentId) { + const existing = await strapi + .documents(ctx.uid) + .findOne({ documentId }); + preDeleteSlug = existing?.slug as string | undefined; + } + } + + const result = await next(); + + try { + if ( + (ctx.action === "create" || ctx.action === "update") && + result && + typeof result === "object" + ) { + writeEntryForConfig(config, result as Record); + } else if (ctx.action === "delete" && preDeleteSlug) { + deleteEntry(config.dir, preDeleteSlug, config.layout); + } + } catch (err) { + strapi.log.error( + `content-sync: failed to write ${ctx.uid} to disk: ${(err as Error).message}`, + ); + } + + return result; + }); +} diff --git a/apps/cms/types/generated/contentTypes.d.ts b/apps/cms/types/generated/contentTypes.d.ts index 9398e23..9721bf7 100644 --- a/apps/cms/types/generated/contentTypes.d.ts +++ b/apps/cms/types/generated/contentTypes.d.ts @@ -1,22 +1,22 @@ -import type { Schema, Struct } from "@strapi/strapi"; +import type { Schema, Struct } from '@strapi/strapi'; export interface AdminApiToken extends Struct.CollectionTypeSchema { - collectionName: "strapi_api_tokens"; + collectionName: 'strapi_api_tokens'; info: { - description: ""; - displayName: "Api Token"; - name: "Api Token"; - pluralName: "api-tokens"; - singularName: "api-token"; + description: ''; + displayName: 'Api Token'; + name: 'Api Token'; + pluralName: 'api-tokens'; + singularName: 'api-token'; }; options: { draftAndPublish: false; }; pluginOptions: { - "content-manager": { + 'content-manager': { visible: false; }; - "content-type-builder": { + 'content-type-builder': { visible: false; }; }; @@ -27,30 +27,30 @@ export interface AdminApiToken extends Struct.CollectionTypeSchema { minLength: 1; }>; adminPermissions: Schema.Attribute.Relation< - "oneToMany", - "admin::permission" + 'oneToMany', + 'admin::permission' >; - adminUserOwner: Schema.Attribute.Relation<"manyToOne", "admin::user">; + adminUserOwner: Schema.Attribute.Relation<'manyToOne', 'admin::user'>; createdAt: Schema.Attribute.DateTime; - createdBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; description: Schema.Attribute.String & Schema.Attribute.SetMinMaxLength<{ minLength: 1; }> & - Schema.Attribute.DefaultTo<"">; + Schema.Attribute.DefaultTo<''>; encryptedKey: Schema.Attribute.Text & Schema.Attribute.SetMinMaxLength<{ minLength: 1; }>; expiresAt: Schema.Attribute.DateTime; - kind: Schema.Attribute.Enumeration<["content-api", "admin"]> & + kind: Schema.Attribute.Enumeration<['content-api', 'admin']> & Schema.Attribute.Required & - Schema.Attribute.DefaultTo<"content-api">; + Schema.Attribute.DefaultTo<'content-api'>; lastUsedAt: Schema.Attribute.DateTime; lifespan: Schema.Attribute.BigInteger; locale: Schema.Attribute.String & Schema.Attribute.Private; - localizations: Schema.Attribute.Relation<"oneToMany", "admin::api-token"> & + localizations: Schema.Attribute.Relation<'oneToMany', 'admin::api-token'> & Schema.Attribute.Private; name: Schema.Attribute.String & Schema.Attribute.Required & @@ -59,35 +59,35 @@ export interface AdminApiToken extends Struct.CollectionTypeSchema { minLength: 1; }>; permissions: Schema.Attribute.Relation< - "oneToMany", - "admin::api-token-permission" + 'oneToMany', + 'admin::api-token-permission' >; publishedAt: Schema.Attribute.DateTime; - type: Schema.Attribute.Enumeration<["read-only", "full-access", "custom"]> & - Schema.Attribute.DefaultTo<"read-only">; + type: Schema.Attribute.Enumeration<['read-only', 'full-access', 'custom']> & + Schema.Attribute.DefaultTo<'read-only'>; updatedAt: Schema.Attribute.DateTime; - updatedBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; }; } export interface AdminApiTokenPermission extends Struct.CollectionTypeSchema { - collectionName: "strapi_api_token_permissions"; + collectionName: 'strapi_api_token_permissions'; info: { - description: ""; - displayName: "API Token Permission"; - name: "API Token Permission"; - pluralName: "api-token-permissions"; - singularName: "api-token-permission"; + description: ''; + displayName: 'API Token Permission'; + name: 'API Token Permission'; + pluralName: 'api-token-permissions'; + singularName: 'api-token-permission'; }; options: { draftAndPublish: false; }; pluginOptions: { - "content-manager": { + 'content-manager': { visible: false; }; - "content-type-builder": { + 'content-type-builder': { visible: false; }; }; @@ -98,39 +98,39 @@ export interface AdminApiTokenPermission extends Struct.CollectionTypeSchema { minLength: 1; }>; createdAt: Schema.Attribute.DateTime; - createdBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; locale: Schema.Attribute.String & Schema.Attribute.Private; localizations: Schema.Attribute.Relation< - "oneToMany", - "admin::api-token-permission" + 'oneToMany', + 'admin::api-token-permission' > & Schema.Attribute.Private; publishedAt: Schema.Attribute.DateTime; - token: Schema.Attribute.Relation<"manyToOne", "admin::api-token">; + token: Schema.Attribute.Relation<'manyToOne', 'admin::api-token'>; updatedAt: Schema.Attribute.DateTime; - updatedBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; }; } export interface AdminPermission extends Struct.CollectionTypeSchema { - collectionName: "admin_permissions"; + collectionName: 'admin_permissions'; info: { - description: ""; - displayName: "Permission"; - name: "Permission"; - pluralName: "permissions"; - singularName: "permission"; + description: ''; + displayName: 'Permission'; + name: 'Permission'; + pluralName: 'permissions'; + singularName: 'permission'; }; options: { draftAndPublish: false; }; pluginOptions: { - "content-manager": { + 'content-manager': { visible: false; }; - "content-type-builder": { + 'content-type-builder': { visible: false; }; }; @@ -141,44 +141,44 @@ export interface AdminPermission extends Struct.CollectionTypeSchema { minLength: 1; }>; actionParameters: Schema.Attribute.JSON & Schema.Attribute.DefaultTo<{}>; - apiToken: Schema.Attribute.Relation<"manyToOne", "admin::api-token">; + apiToken: Schema.Attribute.Relation<'manyToOne', 'admin::api-token'>; conditions: Schema.Attribute.JSON & Schema.Attribute.DefaultTo<[]>; createdAt: Schema.Attribute.DateTime; - createdBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; locale: Schema.Attribute.String & Schema.Attribute.Private; - localizations: Schema.Attribute.Relation<"oneToMany", "admin::permission"> & + localizations: Schema.Attribute.Relation<'oneToMany', 'admin::permission'> & Schema.Attribute.Private; properties: Schema.Attribute.JSON & Schema.Attribute.DefaultTo<{}>; publishedAt: Schema.Attribute.DateTime; - role: Schema.Attribute.Relation<"manyToOne", "admin::role">; + role: Schema.Attribute.Relation<'manyToOne', 'admin::role'>; subject: Schema.Attribute.String & Schema.Attribute.SetMinMaxLength<{ minLength: 1; }>; updatedAt: Schema.Attribute.DateTime; - updatedBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; }; } export interface AdminRole extends Struct.CollectionTypeSchema { - collectionName: "admin_roles"; + collectionName: 'admin_roles'; info: { - description: ""; - displayName: "Role"; - name: "Role"; - pluralName: "roles"; - singularName: "role"; + description: ''; + displayName: 'Role'; + name: 'Role'; + pluralName: 'roles'; + singularName: 'role'; }; options: { draftAndPublish: false; }; pluginOptions: { - "content-manager": { + 'content-manager': { visible: false; }; - "content-type-builder": { + 'content-type-builder': { visible: false; }; }; @@ -190,11 +190,11 @@ export interface AdminRole extends Struct.CollectionTypeSchema { minLength: 1; }>; createdAt: Schema.Attribute.DateTime; - createdBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; description: Schema.Attribute.String; locale: Schema.Attribute.String & Schema.Attribute.Private; - localizations: Schema.Attribute.Relation<"oneToMany", "admin::role"> & + localizations: Schema.Attribute.Relation<'oneToMany', 'admin::role'> & Schema.Attribute.Private; name: Schema.Attribute.String & Schema.Attribute.Required & @@ -202,32 +202,32 @@ export interface AdminRole extends Struct.CollectionTypeSchema { Schema.Attribute.SetMinMaxLength<{ minLength: 1; }>; - permissions: Schema.Attribute.Relation<"oneToMany", "admin::permission">; + permissions: Schema.Attribute.Relation<'oneToMany', 'admin::permission'>; publishedAt: Schema.Attribute.DateTime; updatedAt: Schema.Attribute.DateTime; - updatedBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; - users: Schema.Attribute.Relation<"manyToMany", "admin::user">; + users: Schema.Attribute.Relation<'manyToMany', 'admin::user'>; }; } export interface AdminSession extends Struct.CollectionTypeSchema { - collectionName: "strapi_sessions"; + collectionName: 'strapi_sessions'; info: { - description: "Session Manager storage"; - displayName: "Session"; - name: "Session"; - pluralName: "sessions"; - singularName: "session"; + description: 'Session Manager storage'; + displayName: 'Session'; + name: 'Session'; + pluralName: 'sessions'; + singularName: 'session'; }; options: { draftAndPublish: false; }; pluginOptions: { - "content-manager": { + 'content-manager': { visible: false; }; - "content-type-builder": { + 'content-type-builder': { visible: false; }; i18n: { @@ -238,7 +238,7 @@ export interface AdminSession extends Struct.CollectionTypeSchema { absoluteExpiresAt: Schema.Attribute.DateTime & Schema.Attribute.Private; childId: Schema.Attribute.String & Schema.Attribute.Private; createdAt: Schema.Attribute.DateTime; - createdBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; deviceId: Schema.Attribute.String & Schema.Attribute.Required & @@ -247,7 +247,7 @@ export interface AdminSession extends Struct.CollectionTypeSchema { Schema.Attribute.Required & Schema.Attribute.Private; locale: Schema.Attribute.String & Schema.Attribute.Private; - localizations: Schema.Attribute.Relation<"oneToMany", "admin::session"> & + localizations: Schema.Attribute.Relation<'oneToMany', 'admin::session'> & Schema.Attribute.Private; metadata: Schema.Attribute.JSON & Schema.Attribute.Private; origin: Schema.Attribute.String & @@ -261,7 +261,7 @@ export interface AdminSession extends Struct.CollectionTypeSchema { status: Schema.Attribute.String & Schema.Attribute.Private; type: Schema.Attribute.String & Schema.Attribute.Private; updatedAt: Schema.Attribute.DateTime; - updatedBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; userId: Schema.Attribute.String & Schema.Attribute.Required & @@ -270,22 +270,22 @@ export interface AdminSession extends Struct.CollectionTypeSchema { } export interface AdminTransferToken extends Struct.CollectionTypeSchema { - collectionName: "strapi_transfer_tokens"; + collectionName: 'strapi_transfer_tokens'; info: { - description: ""; - displayName: "Transfer Token"; - name: "Transfer Token"; - pluralName: "transfer-tokens"; - singularName: "transfer-token"; + description: ''; + displayName: 'Transfer Token'; + name: 'Transfer Token'; + pluralName: 'transfer-tokens'; + singularName: 'transfer-token'; }; options: { draftAndPublish: false; }; pluginOptions: { - "content-manager": { + 'content-manager': { visible: false; }; - "content-type-builder": { + 'content-type-builder': { visible: false; }; }; @@ -296,20 +296,20 @@ export interface AdminTransferToken extends Struct.CollectionTypeSchema { minLength: 1; }>; createdAt: Schema.Attribute.DateTime; - createdBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; description: Schema.Attribute.String & Schema.Attribute.SetMinMaxLength<{ minLength: 1; }> & - Schema.Attribute.DefaultTo<"">; + Schema.Attribute.DefaultTo<''>; expiresAt: Schema.Attribute.DateTime; lastUsedAt: Schema.Attribute.DateTime; lifespan: Schema.Attribute.BigInteger; locale: Schema.Attribute.String & Schema.Attribute.Private; localizations: Schema.Attribute.Relation< - "oneToMany", - "admin::transfer-token" + 'oneToMany', + 'admin::transfer-token' > & Schema.Attribute.Private; name: Schema.Attribute.String & @@ -319,34 +319,34 @@ export interface AdminTransferToken extends Struct.CollectionTypeSchema { minLength: 1; }>; permissions: Schema.Attribute.Relation< - "oneToMany", - "admin::transfer-token-permission" + 'oneToMany', + 'admin::transfer-token-permission' >; publishedAt: Schema.Attribute.DateTime; updatedAt: Schema.Attribute.DateTime; - updatedBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; }; } export interface AdminTransferTokenPermission extends Struct.CollectionTypeSchema { - collectionName: "strapi_transfer_token_permissions"; + collectionName: 'strapi_transfer_token_permissions'; info: { - description: ""; - displayName: "Transfer Token Permission"; - name: "Transfer Token Permission"; - pluralName: "transfer-token-permissions"; - singularName: "transfer-token-permission"; + description: ''; + displayName: 'Transfer Token Permission'; + name: 'Transfer Token Permission'; + pluralName: 'transfer-token-permissions'; + singularName: 'transfer-token-permission'; }; options: { draftAndPublish: false; }; pluginOptions: { - "content-manager": { + 'content-manager': { visible: false; }; - "content-type-builder": { + 'content-type-builder': { visible: false; }; }; @@ -357,50 +357,50 @@ export interface AdminTransferTokenPermission minLength: 1; }>; createdAt: Schema.Attribute.DateTime; - createdBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; locale: Schema.Attribute.String & Schema.Attribute.Private; localizations: Schema.Attribute.Relation< - "oneToMany", - "admin::transfer-token-permission" + 'oneToMany', + 'admin::transfer-token-permission' > & Schema.Attribute.Private; publishedAt: Schema.Attribute.DateTime; - token: Schema.Attribute.Relation<"manyToOne", "admin::transfer-token">; + token: Schema.Attribute.Relation<'manyToOne', 'admin::transfer-token'>; updatedAt: Schema.Attribute.DateTime; - updatedBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; }; } export interface AdminUser extends Struct.CollectionTypeSchema { - collectionName: "admin_users"; + collectionName: 'admin_users'; info: { - description: ""; - displayName: "User"; - name: "User"; - pluralName: "users"; - singularName: "user"; + description: ''; + displayName: 'User'; + name: 'User'; + pluralName: 'users'; + singularName: 'user'; }; options: { draftAndPublish: false; }; pluginOptions: { - "content-manager": { + 'content-manager': { visible: false; }; - "content-type-builder": { + 'content-type-builder': { visible: false; }; }; attributes: { - apiTokens: Schema.Attribute.Relation<"oneToMany", "admin::api-token"> & + apiTokens: Schema.Attribute.Relation<'oneToMany', 'admin::api-token'> & Schema.Attribute.Private; blocked: Schema.Attribute.Boolean & Schema.Attribute.Private & Schema.Attribute.DefaultTo; createdAt: Schema.Attribute.DateTime; - createdBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; email: Schema.Attribute.Email & Schema.Attribute.Required & @@ -421,7 +421,7 @@ export interface AdminUser extends Struct.CollectionTypeSchema { minLength: 1; }>; locale: Schema.Attribute.String & Schema.Attribute.Private; - localizations: Schema.Attribute.Relation<"oneToMany", "admin::user"> & + localizations: Schema.Attribute.Relation<'oneToMany', 'admin::user'> & Schema.Attribute.Private; password: Schema.Attribute.Password & Schema.Attribute.Private & @@ -434,10 +434,10 @@ export interface AdminUser extends Struct.CollectionTypeSchema { resetPasswordToken: Schema.Attribute.String & Schema.Attribute.Private; resetPasswordTokenExpiresAt: Schema.Attribute.DateTime & Schema.Attribute.Private; - roles: Schema.Attribute.Relation<"manyToMany", "admin::role"> & + roles: Schema.Attribute.Relation<'manyToMany', 'admin::role'> & Schema.Attribute.Private; updatedAt: Schema.Attribute.DateTime; - updatedBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; username: Schema.Attribute.String; }; @@ -445,12 +445,12 @@ export interface AdminUser extends Struct.CollectionTypeSchema { export interface ApiBookshelfEntryBookshelfEntry extends Struct.CollectionTypeSchema { - collectionName: "bookshelf_entries"; + collectionName: 'bookshelf_entries'; info: { - description: "Books and papers, synced to content/bookshelf/. `kind` selects which identifier fields apply."; - displayName: "Bookshelf Entry"; - pluralName: "bookshelf-entries"; - singularName: "bookshelf-entry"; + description: 'Books and papers, synced to content/bookshelf/. `kind` selects which identifier fields apply.'; + displayName: 'Bookshelf Entry'; + pluralName: 'bookshelf-entries'; + singularName: 'bookshelf-entry'; }; options: { draftAndPublish: false; @@ -461,18 +461,18 @@ export interface ApiBookshelfEntryBookshelfEntry body: Schema.Attribute.RichText; cover_image: Schema.Attribute.String; createdAt: Schema.Attribute.DateTime; - createdBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; date_finished: Schema.Attribute.Date; date_started: Schema.Attribute.Date; doi: Schema.Attribute.String; isbn: Schema.Attribute.String; - kind: Schema.Attribute.Enumeration<["book", "paper"]> & + kind: Schema.Attribute.Enumeration<['book', 'paper']> & Schema.Attribute.Required; locale: Schema.Attribute.String & Schema.Attribute.Private; localizations: Schema.Attribute.Relation< - "oneToMany", - "api::bookshelf-entry.bookshelf-entry" + 'oneToMany', + 'api::bookshelf-entry.bookshelf-entry' > & Schema.Attribute.Private; publishedAt: Schema.Attribute.DateTime; @@ -484,23 +484,24 @@ export interface ApiBookshelfEntryBookshelfEntry }, number >; - status: Schema.Attribute.Enumeration<["reading", "read", "abandoned"]>; + slug: Schema.Attribute.UID<'title'> & Schema.Attribute.Required; + status: Schema.Attribute.Enumeration<['reading', 'read', 'abandoned']>; tags: Schema.Attribute.JSON; title: Schema.Attribute.String & Schema.Attribute.Required; updatedAt: Schema.Attribute.DateTime; - updatedBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; url: Schema.Attribute.String; }; } export interface ApiCuratedBlogCuratedBlog extends Struct.CollectionTypeSchema { - collectionName: "curated_blogs"; + collectionName: 'curated_blogs'; info: { - description: "Hand-picked external blogs, synced to content/blogs/. No live feed fetching."; - displayName: "Curated Blog"; - pluralName: "curated-blogs"; - singularName: "curated-blog"; + description: 'Hand-picked external blogs, synced to content/blogs/. No live feed fetching.'; + displayName: 'Curated Blog'; + pluralName: 'curated-blogs'; + singularName: 'curated-blog'; }; options: { draftAndPublish: false; @@ -508,19 +509,20 @@ export interface ApiCuratedBlogCuratedBlog extends Struct.CollectionTypeSchema { attributes: { body: Schema.Attribute.RichText; createdAt: Schema.Attribute.DateTime; - createdBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; feed_url: Schema.Attribute.String; locale: Schema.Attribute.String & Schema.Attribute.Private; localizations: Schema.Attribute.Relation< - "oneToMany", - "api::curated-blog.curated-blog" + 'oneToMany', + 'api::curated-blog.curated-blog' > & Schema.Attribute.Private; publishedAt: Schema.Attribute.DateTime; + slug: Schema.Attribute.UID<'title'> & Schema.Attribute.Required; title: Schema.Attribute.String & Schema.Attribute.Required; updatedAt: Schema.Attribute.DateTime; - updatedBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; url: Schema.Attribute.String & Schema.Attribute.Required; }; @@ -528,12 +530,12 @@ export interface ApiCuratedBlogCuratedBlog extends Struct.CollectionTypeSchema { export interface ApiPortfolioEntryPortfolioEntry extends Struct.CollectionTypeSchema { - collectionName: "portfolio_entries"; + collectionName: 'portfolio_entries'; info: { - description: "Projects and professional work, synced to content/portfolio/."; - displayName: "Portfolio Entry"; - pluralName: "portfolio-entries"; - singularName: "portfolio-entry"; + description: 'Projects and professional work, synced to content/portfolio/.'; + displayName: 'Portfolio Entry'; + pluralName: 'portfolio-entries'; + singularName: 'portfolio-entry'; }; options: { draftAndPublish: false; @@ -541,7 +543,7 @@ export interface ApiPortfolioEntryPortfolioEntry attributes: { body: Schema.Attribute.RichText; createdAt: Schema.Attribute.DateTime; - createdBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; date_end: Schema.Attribute.Date; date_start: Schema.Attribute.Date; @@ -550,28 +552,29 @@ export interface ApiPortfolioEntryPortfolioEntry links: Schema.Attribute.JSON; locale: Schema.Attribute.String & Schema.Attribute.Private; localizations: Schema.Attribute.Relation< - "oneToMany", - "api::portfolio-entry.portfolio-entry" + 'oneToMany', + 'api::portfolio-entry.portfolio-entry' > & Schema.Attribute.Private; outcome: Schema.Attribute.String; publishedAt: Schema.Attribute.DateTime; role: Schema.Attribute.String; + slug: Schema.Attribute.UID<'title'> & Schema.Attribute.Required; tags: Schema.Attribute.JSON; title: Schema.Attribute.String & Schema.Attribute.Required; updatedAt: Schema.Attribute.DateTime; - updatedBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; }; } export interface ApiWritingWriting extends Struct.CollectionTypeSchema { - collectionName: "writings"; + collectionName: 'writings'; info: { - description: "Long-form essays and technical posts, synced to content/writings/."; - displayName: "Writing"; - pluralName: "writings"; - singularName: "writing"; + description: 'Long-form essays and technical posts, synced to content/writings/.'; + displayName: 'Writing'; + pluralName: 'writings'; + singularName: 'writing'; }; options: { draftAndPublish: false; @@ -581,7 +584,7 @@ export interface ApiWritingWriting extends Struct.CollectionTypeSchema { body: Schema.Attribute.RichText; canonical_url: Schema.Attribute.String; createdAt: Schema.Attribute.DateTime; - createdBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; date: Schema.Attribute.Date & Schema.Attribute.Required; description: Schema.Attribute.Text; @@ -589,53 +592,54 @@ export interface ApiWritingWriting extends Struct.CollectionTypeSchema { llms_description: Schema.Attribute.Text; locale: Schema.Attribute.String & Schema.Attribute.Private; localizations: Schema.Attribute.Relation< - "oneToMany", - "api::writing.writing" + 'oneToMany', + 'api::writing.writing' > & Schema.Attribute.Private; publishedAt: Schema.Attribute.DateTime; series: Schema.Attribute.String; series_index: Schema.Attribute.Integer; + slug: Schema.Attribute.UID<'title'> & Schema.Attribute.Required; tags: Schema.Attribute.JSON; title: Schema.Attribute.String & Schema.Attribute.Required; updated: Schema.Attribute.Date; updatedAt: Schema.Attribute.DateTime; - updatedBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; }; } export interface PluginContentReleasesRelease extends Struct.CollectionTypeSchema { - collectionName: "strapi_releases"; + collectionName: 'strapi_releases'; info: { - displayName: "Release"; - pluralName: "releases"; - singularName: "release"; + displayName: 'Release'; + pluralName: 'releases'; + singularName: 'release'; }; options: { draftAndPublish: false; }; pluginOptions: { - "content-manager": { + 'content-manager': { visible: false; }; - "content-type-builder": { + 'content-type-builder': { visible: false; }; }; attributes: { actions: Schema.Attribute.Relation< - "oneToMany", - "plugin::content-releases.release-action" + 'oneToMany', + 'plugin::content-releases.release-action' >; createdAt: Schema.Attribute.DateTime; - createdBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; locale: Schema.Attribute.String & Schema.Attribute.Private; localizations: Schema.Attribute.Relation< - "oneToMany", - "plugin::content-releases.release" + 'oneToMany', + 'plugin::content-releases.release' > & Schema.Attribute.Private; name: Schema.Attribute.String & Schema.Attribute.Required; @@ -643,90 +647,90 @@ export interface PluginContentReleasesRelease releasedAt: Schema.Attribute.DateTime; scheduledAt: Schema.Attribute.DateTime; status: Schema.Attribute.Enumeration< - ["ready", "blocked", "failed", "done", "empty"] + ['ready', 'blocked', 'failed', 'done', 'empty'] > & Schema.Attribute.Required; timezone: Schema.Attribute.String; updatedAt: Schema.Attribute.DateTime; - updatedBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; }; } export interface PluginContentReleasesReleaseAction extends Struct.CollectionTypeSchema { - collectionName: "strapi_release_actions"; + collectionName: 'strapi_release_actions'; info: { - displayName: "Release Action"; - pluralName: "release-actions"; - singularName: "release-action"; + displayName: 'Release Action'; + pluralName: 'release-actions'; + singularName: 'release-action'; }; options: { draftAndPublish: false; }; pluginOptions: { - "content-manager": { + 'content-manager': { visible: false; }; - "content-type-builder": { + 'content-type-builder': { visible: false; }; }; attributes: { contentType: Schema.Attribute.String & Schema.Attribute.Required; createdAt: Schema.Attribute.DateTime; - createdBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; entryDocumentId: Schema.Attribute.String; isEntryValid: Schema.Attribute.Boolean; locale: Schema.Attribute.String & Schema.Attribute.Private; localizations: Schema.Attribute.Relation< - "oneToMany", - "plugin::content-releases.release-action" + 'oneToMany', + 'plugin::content-releases.release-action' > & Schema.Attribute.Private; publishedAt: Schema.Attribute.DateTime; release: Schema.Attribute.Relation< - "manyToOne", - "plugin::content-releases.release" + 'manyToOne', + 'plugin::content-releases.release' >; - type: Schema.Attribute.Enumeration<["publish", "unpublish"]> & + type: Schema.Attribute.Enumeration<['publish', 'unpublish']> & Schema.Attribute.Required; updatedAt: Schema.Attribute.DateTime; - updatedBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; }; } export interface PluginI18NLocale extends Struct.CollectionTypeSchema { - collectionName: "i18n_locale"; + collectionName: 'i18n_locale'; info: { - collectionName: "locales"; - description: ""; - displayName: "Locale"; - pluralName: "locales"; - singularName: "locale"; + collectionName: 'locales'; + description: ''; + displayName: 'Locale'; + pluralName: 'locales'; + singularName: 'locale'; }; options: { draftAndPublish: false; }; pluginOptions: { - "content-manager": { + 'content-manager': { visible: false; }; - "content-type-builder": { + 'content-type-builder': { visible: false; }; }; attributes: { code: Schema.Attribute.String & Schema.Attribute.Unique; createdAt: Schema.Attribute.DateTime; - createdBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; locale: Schema.Attribute.String & Schema.Attribute.Private; localizations: Schema.Attribute.Relation< - "oneToMany", - "plugin::i18n.locale" + 'oneToMany', + 'plugin::i18n.locale' > & Schema.Attribute.Private; name: Schema.Attribute.String & @@ -739,43 +743,43 @@ export interface PluginI18NLocale extends Struct.CollectionTypeSchema { >; publishedAt: Schema.Attribute.DateTime; updatedAt: Schema.Attribute.DateTime; - updatedBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; }; } export interface PluginReviewWorkflowsWorkflow extends Struct.CollectionTypeSchema { - collectionName: "strapi_workflows"; + collectionName: 'strapi_workflows'; info: { - description: ""; - displayName: "Workflow"; - name: "Workflow"; - pluralName: "workflows"; - singularName: "workflow"; + description: ''; + displayName: 'Workflow'; + name: 'Workflow'; + pluralName: 'workflows'; + singularName: 'workflow'; }; options: { draftAndPublish: false; }; pluginOptions: { - "content-manager": { + 'content-manager': { visible: false; }; - "content-type-builder": { + 'content-type-builder': { visible: false; }; }; attributes: { contentTypes: Schema.Attribute.JSON & Schema.Attribute.Required & - Schema.Attribute.DefaultTo<"[]">; + Schema.Attribute.DefaultTo<'[]'>; createdAt: Schema.Attribute.DateTime; - createdBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; locale: Schema.Attribute.String & Schema.Attribute.Private; localizations: Schema.Attribute.Relation< - "oneToMany", - "plugin::review-workflows.workflow" + 'oneToMany', + 'plugin::review-workflows.workflow' > & Schema.Attribute.Private; name: Schema.Attribute.String & @@ -783,81 +787,81 @@ export interface PluginReviewWorkflowsWorkflow Schema.Attribute.Unique; publishedAt: Schema.Attribute.DateTime; stageRequiredToPublish: Schema.Attribute.Relation< - "oneToOne", - "plugin::review-workflows.workflow-stage" + 'oneToOne', + 'plugin::review-workflows.workflow-stage' >; stages: Schema.Attribute.Relation< - "oneToMany", - "plugin::review-workflows.workflow-stage" + 'oneToMany', + 'plugin::review-workflows.workflow-stage' >; updatedAt: Schema.Attribute.DateTime; - updatedBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; }; } export interface PluginReviewWorkflowsWorkflowStage extends Struct.CollectionTypeSchema { - collectionName: "strapi_workflows_stages"; + collectionName: 'strapi_workflows_stages'; info: { - description: ""; - displayName: "Stages"; - name: "Workflow Stage"; - pluralName: "workflow-stages"; - singularName: "workflow-stage"; + description: ''; + displayName: 'Stages'; + name: 'Workflow Stage'; + pluralName: 'workflow-stages'; + singularName: 'workflow-stage'; }; options: { draftAndPublish: false; - version: "1.1.0"; + version: '1.1.0'; }; pluginOptions: { - "content-manager": { + 'content-manager': { visible: false; }; - "content-type-builder": { + 'content-type-builder': { visible: false; }; }; attributes: { - color: Schema.Attribute.String & Schema.Attribute.DefaultTo<"#4945FF">; + color: Schema.Attribute.String & Schema.Attribute.DefaultTo<'#4945FF'>; createdAt: Schema.Attribute.DateTime; - createdBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; locale: Schema.Attribute.String & Schema.Attribute.Private; localizations: Schema.Attribute.Relation< - "oneToMany", - "plugin::review-workflows.workflow-stage" + 'oneToMany', + 'plugin::review-workflows.workflow-stage' > & Schema.Attribute.Private; name: Schema.Attribute.String; - permissions: Schema.Attribute.Relation<"manyToMany", "admin::permission">; + permissions: Schema.Attribute.Relation<'manyToMany', 'admin::permission'>; publishedAt: Schema.Attribute.DateTime; updatedAt: Schema.Attribute.DateTime; - updatedBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; workflow: Schema.Attribute.Relation< - "manyToOne", - "plugin::review-workflows.workflow" + 'manyToOne', + 'plugin::review-workflows.workflow' >; }; } export interface PluginUploadFile extends Struct.CollectionTypeSchema { - collectionName: "files"; + collectionName: 'files'; info: { - description: ""; - displayName: "File"; - pluralName: "files"; - singularName: "file"; + description: ''; + displayName: 'File'; + pluralName: 'files'; + singularName: 'file'; }; options: { draftAndPublish: false; }; pluginOptions: { - "content-manager": { + 'content-manager': { visible: false; }; - "content-type-builder": { + 'content-type-builder': { visible: false; }; }; @@ -865,11 +869,11 @@ export interface PluginUploadFile extends Struct.CollectionTypeSchema { alternativeText: Schema.Attribute.Text; caption: Schema.Attribute.Text; createdAt: Schema.Attribute.DateTime; - createdBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; ext: Schema.Attribute.String; focalPoint: Schema.Attribute.JSON; - folder: Schema.Attribute.Relation<"manyToOne", "plugin::upload.folder"> & + folder: Schema.Attribute.Relation<'manyToOne', 'plugin::upload.folder'> & Schema.Attribute.Private; folderPath: Schema.Attribute.String & Schema.Attribute.Required & @@ -882,8 +886,8 @@ export interface PluginUploadFile extends Struct.CollectionTypeSchema { height: Schema.Attribute.Integer; locale: Schema.Attribute.String & Schema.Attribute.Private; localizations: Schema.Attribute.Relation< - "oneToMany", - "plugin::upload.file" + 'oneToMany', + 'plugin::upload.file' > & Schema.Attribute.Private; mime: Schema.Attribute.String & Schema.Attribute.Required; @@ -892,10 +896,10 @@ export interface PluginUploadFile extends Struct.CollectionTypeSchema { provider: Schema.Attribute.String & Schema.Attribute.Required; provider_metadata: Schema.Attribute.JSON; publishedAt: Schema.Attribute.DateTime; - related: Schema.Attribute.Relation<"morphToMany">; + related: Schema.Attribute.Relation<'morphToMany'>; size: Schema.Attribute.Decimal & Schema.Attribute.Required; updatedAt: Schema.Attribute.DateTime; - updatedBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; url: Schema.Attribute.Text & Schema.Attribute.Required; width: Schema.Attribute.Integer; @@ -903,33 +907,33 @@ export interface PluginUploadFile extends Struct.CollectionTypeSchema { } export interface PluginUploadFolder extends Struct.CollectionTypeSchema { - collectionName: "upload_folders"; + collectionName: 'upload_folders'; info: { - displayName: "Folder"; - pluralName: "folders"; - singularName: "folder"; + displayName: 'Folder'; + pluralName: 'folders'; + singularName: 'folder'; }; options: { draftAndPublish: false; }; pluginOptions: { - "content-manager": { + 'content-manager': { visible: false; }; - "content-type-builder": { + 'content-type-builder': { visible: false; }; }; attributes: { - children: Schema.Attribute.Relation<"oneToMany", "plugin::upload.folder">; + children: Schema.Attribute.Relation<'oneToMany', 'plugin::upload.folder'>; createdAt: Schema.Attribute.DateTime; - createdBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; - files: Schema.Attribute.Relation<"oneToMany", "plugin::upload.file">; + files: Schema.Attribute.Relation<'oneToMany', 'plugin::upload.file'>; locale: Schema.Attribute.String & Schema.Attribute.Private; localizations: Schema.Attribute.Relation< - "oneToMany", - "plugin::upload.folder" + 'oneToMany', + 'plugin::upload.folder' > & Schema.Attribute.Private; name: Schema.Attribute.String & @@ -937,7 +941,7 @@ export interface PluginUploadFolder extends Struct.CollectionTypeSchema { Schema.Attribute.SetMinMaxLength<{ minLength: 1; }>; - parent: Schema.Attribute.Relation<"manyToOne", "plugin::upload.folder">; + parent: Schema.Attribute.Relation<'manyToOne', 'plugin::upload.folder'>; path: Schema.Attribute.String & Schema.Attribute.Required & Schema.Attribute.SetMinMaxLength<{ @@ -948,84 +952,84 @@ export interface PluginUploadFolder extends Struct.CollectionTypeSchema { Schema.Attribute.Unique; publishedAt: Schema.Attribute.DateTime; updatedAt: Schema.Attribute.DateTime; - updatedBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; }; } export interface PluginUsersPermissionsPermission extends Struct.CollectionTypeSchema { - collectionName: "up_permissions"; + collectionName: 'up_permissions'; info: { - description: ""; - displayName: "Permission"; - name: "permission"; - pluralName: "permissions"; - singularName: "permission"; + description: ''; + displayName: 'Permission'; + name: 'permission'; + pluralName: 'permissions'; + singularName: 'permission'; }; options: { draftAndPublish: false; }; pluginOptions: { - "content-manager": { + 'content-manager': { visible: false; }; - "content-type-builder": { + 'content-type-builder': { visible: false; }; }; attributes: { action: Schema.Attribute.String & Schema.Attribute.Required; createdAt: Schema.Attribute.DateTime; - createdBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; locale: Schema.Attribute.String & Schema.Attribute.Private; localizations: Schema.Attribute.Relation< - "oneToMany", - "plugin::users-permissions.permission" + 'oneToMany', + 'plugin::users-permissions.permission' > & Schema.Attribute.Private; publishedAt: Schema.Attribute.DateTime; role: Schema.Attribute.Relation< - "manyToOne", - "plugin::users-permissions.role" + 'manyToOne', + 'plugin::users-permissions.role' >; updatedAt: Schema.Attribute.DateTime; - updatedBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; }; } export interface PluginUsersPermissionsRole extends Struct.CollectionTypeSchema { - collectionName: "up_roles"; + collectionName: 'up_roles'; info: { - description: ""; - displayName: "Role"; - name: "role"; - pluralName: "roles"; - singularName: "role"; + description: ''; + displayName: 'Role'; + name: 'role'; + pluralName: 'roles'; + singularName: 'role'; }; options: { draftAndPublish: false; }; pluginOptions: { - "content-manager": { + 'content-manager': { visible: false; }; - "content-type-builder": { + 'content-type-builder': { visible: false; }; }; attributes: { createdAt: Schema.Attribute.DateTime; - createdBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; description: Schema.Attribute.String; locale: Schema.Attribute.String & Schema.Attribute.Private; localizations: Schema.Attribute.Relation< - "oneToMany", - "plugin::users-permissions.role" + 'oneToMany', + 'plugin::users-permissions.role' > & Schema.Attribute.Private; name: Schema.Attribute.String & @@ -1034,30 +1038,30 @@ export interface PluginUsersPermissionsRole minLength: 3; }>; permissions: Schema.Attribute.Relation< - "oneToMany", - "plugin::users-permissions.permission" + 'oneToMany', + 'plugin::users-permissions.permission' >; publishedAt: Schema.Attribute.DateTime; type: Schema.Attribute.String & Schema.Attribute.Unique; updatedAt: Schema.Attribute.DateTime; - updatedBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; users: Schema.Attribute.Relation< - "oneToMany", - "plugin::users-permissions.user" + 'oneToMany', + 'plugin::users-permissions.user' >; }; } export interface PluginUsersPermissionsUser extends Struct.CollectionTypeSchema { - collectionName: "up_users"; + collectionName: 'up_users'; info: { - description: ""; - displayName: "User"; - name: "user"; - pluralName: "users"; - singularName: "user"; + description: ''; + displayName: 'User'; + name: 'user'; + pluralName: 'users'; + singularName: 'user'; }; options: { draftAndPublish: false; @@ -1068,7 +1072,7 @@ export interface PluginUsersPermissionsUser confirmationToken: Schema.Attribute.String & Schema.Attribute.Private; confirmed: Schema.Attribute.Boolean & Schema.Attribute.DefaultTo; createdAt: Schema.Attribute.DateTime; - createdBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + createdBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; email: Schema.Attribute.Email & Schema.Attribute.Required & @@ -1077,8 +1081,8 @@ export interface PluginUsersPermissionsUser }>; locale: Schema.Attribute.String & Schema.Attribute.Private; localizations: Schema.Attribute.Relation< - "oneToMany", - "plugin::users-permissions.user" + 'oneToMany', + 'plugin::users-permissions.user' > & Schema.Attribute.Private; password: Schema.Attribute.Password & @@ -1090,11 +1094,11 @@ export interface PluginUsersPermissionsUser publishedAt: Schema.Attribute.DateTime; resetPasswordToken: Schema.Attribute.String & Schema.Attribute.Private; role: Schema.Attribute.Relation< - "manyToOne", - "plugin::users-permissions.role" + 'manyToOne', + 'plugin::users-permissions.role' >; updatedAt: Schema.Attribute.DateTime; - updatedBy: Schema.Attribute.Relation<"oneToOne", "admin::user"> & + updatedBy: Schema.Attribute.Relation<'oneToOne', 'admin::user'> & Schema.Attribute.Private; username: Schema.Attribute.String & Schema.Attribute.Required & @@ -1105,31 +1109,31 @@ export interface PluginUsersPermissionsUser }; } -declare module "@strapi/strapi" { +declare module '@strapi/strapi' { export namespace Public { export interface ContentTypeSchemas { - "admin::api-token": AdminApiToken; - "admin::api-token-permission": AdminApiTokenPermission; - "admin::permission": AdminPermission; - "admin::role": AdminRole; - "admin::session": AdminSession; - "admin::transfer-token": AdminTransferToken; - "admin::transfer-token-permission": AdminTransferTokenPermission; - "admin::user": AdminUser; - "api::bookshelf-entry.bookshelf-entry": ApiBookshelfEntryBookshelfEntry; - "api::curated-blog.curated-blog": ApiCuratedBlogCuratedBlog; - "api::portfolio-entry.portfolio-entry": ApiPortfolioEntryPortfolioEntry; - "api::writing.writing": ApiWritingWriting; - "plugin::content-releases.release": PluginContentReleasesRelease; - "plugin::content-releases.release-action": PluginContentReleasesReleaseAction; - "plugin::i18n.locale": PluginI18NLocale; - "plugin::review-workflows.workflow": PluginReviewWorkflowsWorkflow; - "plugin::review-workflows.workflow-stage": PluginReviewWorkflowsWorkflowStage; - "plugin::upload.file": PluginUploadFile; - "plugin::upload.folder": PluginUploadFolder; - "plugin::users-permissions.permission": PluginUsersPermissionsPermission; - "plugin::users-permissions.role": PluginUsersPermissionsRole; - "plugin::users-permissions.user": PluginUsersPermissionsUser; + 'admin::api-token': AdminApiToken; + 'admin::api-token-permission': AdminApiTokenPermission; + 'admin::permission': AdminPermission; + 'admin::role': AdminRole; + 'admin::session': AdminSession; + 'admin::transfer-token': AdminTransferToken; + 'admin::transfer-token-permission': AdminTransferTokenPermission; + 'admin::user': AdminUser; + 'api::bookshelf-entry.bookshelf-entry': ApiBookshelfEntryBookshelfEntry; + 'api::curated-blog.curated-blog': ApiCuratedBlogCuratedBlog; + 'api::portfolio-entry.portfolio-entry': ApiPortfolioEntryPortfolioEntry; + 'api::writing.writing': ApiWritingWriting; + 'plugin::content-releases.release': PluginContentReleasesRelease; + 'plugin::content-releases.release-action': PluginContentReleasesReleaseAction; + 'plugin::i18n.locale': PluginI18NLocale; + 'plugin::review-workflows.workflow': PluginReviewWorkflowsWorkflow; + 'plugin::review-workflows.workflow-stage': PluginReviewWorkflowsWorkflowStage; + 'plugin::upload.file': PluginUploadFile; + 'plugin::upload.folder': PluginUploadFolder; + 'plugin::users-permissions.permission': PluginUsersPermissionsPermission; + 'plugin::users-permissions.role': PluginUsersPermissionsRole; + 'plugin::users-permissions.user': PluginUsersPermissionsUser; } } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c7a266a..569dbcc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -47,6 +47,9 @@ importers: react-router-dom: specifier: ^6.30.3 version: 6.30.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + smol-toml: + specifier: ^1.8.0 + version: 1.8.0 styled-components: specifier: ^6.0.0 version: 6.5.3(css-to-react-native@3.2.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)