Define Strapi content types matching the content/ schema

Adds writing, portfolio-entry, bookshelf-entry, and curated-blog
collection types with attributes mirroring the TOML frontmatter
fields in content/README.md exactly, so the future sync provider has
a direct field mapping. draftAndPublish is disabled on all four since
our own `draft` boolean (synced to the file) is the source of publish
state, not Strapi's built-in workflow. Verified Strapi boots and
registers all four REST routes (api/writings, api/portfolio-entries,
api/bookshelf-entries, api/curated-blogs).
This commit is contained in:
2026-08-30 18:23:42 +03:00
parent f24701b1e7
commit 9d6d78b31f
20 changed files with 1398 additions and 1 deletions
View File
@@ -0,0 +1,71 @@
{
"kind": "collectionType",
"collectionName": "bookshelf_entries",
"info": {
"singularName": "bookshelf-entry",
"pluralName": "bookshelf-entries",
"displayName": "Bookshelf Entry",
"description": "Books and papers, synced to content/bookshelf/. `kind` selects which identifier fields apply."
},
"options": {
"draftAndPublish": false
},
"attributes": {
"title": {
"type": "string",
"required": true
},
"kind": {
"type": "enumeration",
"enum": ["book", "paper"],
"required": true
},
"authors": {
"type": "json",
"description": "Array of author name strings"
},
"isbn": {
"type": "string",
"description": "Books only"
},
"doi": {
"type": "string",
"description": "Papers only"
},
"arxiv_id": {
"type": "string",
"description": "Papers only, optional"
},
"url": {
"type": "string",
"description": "Papers only: landing page / source link"
},
"cover_image": {
"type": "string",
"description": "Path to the locally cached cover, e.g. /covers/slug.jpg"
},
"rating": {
"type": "integer",
"min": 1,
"max": 5
},
"status": {
"type": "enumeration",
"enum": ["reading", "read", "abandoned"]
},
"date_started": {
"type": "date"
},
"date_finished": {
"type": "date"
},
"tags": {
"type": "json",
"description": "Array of tag strings, maps to [taxonomies].tags"
},
"body": {
"type": "richtext",
"description": "Personal review/notes below the frontmatter"
}
}
}
@@ -0,0 +1,5 @@
import { factories } from "@strapi/strapi";
export default factories.createCoreController(
"api::bookshelf-entry.bookshelf-entry",
);
@@ -0,0 +1,5 @@
import { factories } from "@strapi/strapi";
export default factories.createCoreRouter(
"api::bookshelf-entry.bookshelf-entry",
);
@@ -0,0 +1,5 @@
import { factories } from "@strapi/strapi";
export default factories.createCoreService(
"api::bookshelf-entry.bookshelf-entry",
);
@@ -0,0 +1,30 @@
{
"kind": "collectionType",
"collectionName": "curated_blogs",
"info": {
"singularName": "curated-blog",
"pluralName": "curated-blogs",
"displayName": "Curated Blog",
"description": "Hand-picked external blogs, synced to content/blogs/. No live feed fetching."
},
"options": {
"draftAndPublish": false
},
"attributes": {
"title": {
"type": "string",
"required": true
},
"url": {
"type": "string",
"required": true
},
"feed_url": {
"type": "string"
},
"body": {
"type": "richtext",
"description": "One-to-two sentence blurb below the frontmatter"
}
}
}
@@ -0,0 +1,3 @@
import { factories } from "@strapi/strapi";
export default factories.createCoreController("api::curated-blog.curated-blog");
@@ -0,0 +1,3 @@
import { factories } from "@strapi/strapi";
export default factories.createCoreRouter("api::curated-blog.curated-blog");
@@ -0,0 +1,3 @@
import { factories } from "@strapi/strapi";
export default factories.createCoreService("api::curated-blog.curated-blog");
@@ -0,0 +1,50 @@
{
"kind": "collectionType",
"collectionName": "portfolio_entries",
"info": {
"singularName": "portfolio-entry",
"pluralName": "portfolio-entries",
"displayName": "Portfolio Entry",
"description": "Projects and professional work, synced to content/portfolio/."
},
"options": {
"draftAndPublish": false
},
"attributes": {
"title": {
"type": "string",
"required": true
},
"description": {
"type": "text"
},
"role": {
"type": "string"
},
"date_start": {
"type": "date"
},
"date_end": {
"type": "date"
},
"outcome": {
"type": "string"
},
"draft": {
"type": "boolean",
"default": false
},
"links": {
"type": "json",
"description": "Array of { label, url }, maps to [[links]]"
},
"tags": {
"type": "json",
"description": "Array of tag strings, maps to [taxonomies].tags"
},
"body": {
"type": "richtext",
"description": "Markdown case-study body below the frontmatter"
}
}
}
@@ -0,0 +1,5 @@
import { factories } from "@strapi/strapi";
export default factories.createCoreController(
"api::portfolio-entry.portfolio-entry",
);
@@ -0,0 +1,5 @@
import { factories } from "@strapi/strapi";
export default factories.createCoreRouter(
"api::portfolio-entry.portfolio-entry",
);
@@ -0,0 +1,5 @@
import { factories } from "@strapi/strapi";
export default factories.createCoreService(
"api::portfolio-entry.portfolio-entry",
);
@@ -0,0 +1,59 @@
{
"kind": "collectionType",
"collectionName": "writings",
"info": {
"singularName": "writing",
"pluralName": "writings",
"displayName": "Writing",
"description": "Long-form essays and technical posts, synced to content/writings/."
},
"options": {
"draftAndPublish": false
},
"attributes": {
"title": {
"type": "string",
"required": true
},
"description": {
"type": "text"
},
"author": {
"type": "string"
},
"date": {
"type": "date",
"required": true
},
"updated": {
"type": "date"
},
"draft": {
"type": "boolean",
"default": false
},
"canonical_url": {
"type": "string"
},
"tags": {
"type": "json",
"description": "Array of tag strings, maps to [taxonomies].tags"
},
"series": {
"type": "string",
"description": "Maps to [extra].series"
},
"series_index": {
"type": "integer",
"description": "Maps to [extra].series_index"
},
"llms_description": {
"type": "text",
"description": "Maps to [extra].llms_description"
},
"body": {
"type": "richtext",
"description": "Markdown body below the frontmatter"
}
}
}
@@ -0,0 +1,3 @@
import { factories } from "@strapi/strapi";
export default factories.createCoreController("api::writing.writing");
@@ -0,0 +1,3 @@
import { factories } from "@strapi/strapi";
export default factories.createCoreRouter("api::writing.writing");
@@ -0,0 +1,3 @@
import { factories } from "@strapi/strapi";
export default factories.createCoreService("api::writing.writing");
+3
View File
@@ -0,0 +1,3 @@
/*
* The app doesn't have any components yet.
*/
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -12,7 +12,8 @@
"scripts/**",
"!apps/**/dist",
"!apps/**/.strapi",
"!apps/**/public"
"!apps/**/public",
"!apps/**/types/generated"
]
},
"formatter": {