RustyCMS: file-based headless CMS — API, Admin UI (content, types, assets), Docker/Caddy, image transform; only demo type and demo content in version control

Made-with: Cursor
This commit is contained in:
Peter Meier
2026-03-12 14:21:49 +01:00
parent aad93d145f
commit 7795a238e1
278 changed files with 15551 additions and 4072 deletions

View File

@@ -1,48 +0,0 @@
{
name: "blog_post",
description: "Simple blog post with title, body, tags and publish status",
tags: ["content", "blog"],
category: "content",
fields: {
title: {
type: "string",
required: true,
minLength: 1,
maxLength: 200,
description: "Title of the blog post",
},
body: {
type: "markdown",
required: true,
minLength: 1,
description: "Main content (Markdown)",
},
excerpt: {
type: "string",
maxLength: 500,
description: "Short summary for previews",
},
author: {
type: "string",
description: "Author name"
},
tags: {
type: "array",
items: {
type: "string"
},
description: "Categorisation tags"
},
published: {
type: "boolean",
default: false,
description: "Whether the post is publicly visible"
},
created_at: {
type: "datetime",
auto: true,
readonly: true,
description: "Creation timestamp (auto-generated)"
},
}
}

View File

@@ -1,61 +0,0 @@
{
// Corresponds to CF_Campaign / Contentful_Campaign.ts
name: "campaign",
description: "Campaign with URL pattern and optional content",
tags: ["marketing", "config"],
category: "config",
fields: {
campaignName: {
type: "string",
required: true,
description: "Campaign name",
},
urlPattern: {
type: "string",
required: true,
description: "URL pattern (e.g. regex) for campaign usage",
},
selector: {
type: "string",
required: true,
description: "CSS selector where content is inserted",
},
insertHtml: {
type: "string",
required: true,
enum: [
"afterbegin",
"beforeend",
"afterend",
"beforebegin",
"replace"
],
default: "beforeend",
description: "Position relative to selector",
},
timeUntil: {
type: "datetime",
description: "Time limit (until when the campaign runs)",
},
javascript: {
type: "string",
description: "Optional JavaScript",
},
medias: {
type: "array",
items: {
type: "reference",
collection: "img",
},
description: "Media (images)",
},
html: {
type: "html",
description: "HTML content",
},
css: {
type: "string",
description: "Optional CSS",
},
},
}

View File

@@ -1,27 +0,0 @@
{
// Corresponds to CF_Campaigns / Contentful_Campaigns.ts
name: "campaigns",
description: "Global campaign list and enable flag",
tags: ["marketing", "config"],
category: "config",
fields: {
id: {
type: "string",
required: true,
description: "Unique ID (e.g. campaigns-global)",
},
campaigns: {
type: "array",
items: {
type: "reference",
collection: "campaign",
},
description: "Campaign list",
},
enable: {
type: "boolean",
default: true,
description: "Campaigns enabled",
},
},
}

View File

@@ -1,33 +0,0 @@
{
// Reusable partial (CF_ComponentLayout) no own collection.
name: "component_layout",
description: "Reusable grid layout (mobile/tablet/desktop columns)",
tags: ["layout", "partial"],
category: "layout",
reusable: true,
fields: {
mobile: {
type: "string",
required: true,
enum: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"],
default: "12",
description: "Width on mobile (112)",
},
tablet: {
type: "string",
enum: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"],
description: "Width on tablet (optional)",
},
desktop: {
type: "string",
enum: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12"],
description: "Width on desktop (optional)",
},
spaceBottom: {
type: "number",
enum: [0, 0.5, 1, 1.5, 2],
default: 0,
description: "Space below (rem)",
},
},
}

View File

@@ -1,141 +0,0 @@
{
// Equivalent to: CF_Content interface
// Base type for pages/posts with a 3-row content layout
name: "content_layout",
description: "Base layout with 3 content rows (inherited by page, post, etc.)",
tags: ["layout", "partial"],
category: "layout",
fields: {
row1JustifyContent: {
type: "string",
enum: [
"start",
"end",
"center",
"between",
"around",
"evenly"
],
default: "start",
description: "Justify content for row 1",
},
row1AlignItems: {
type: "string",
enum: [
"start",
"end",
"center",
"baseline",
"stretch"
],
default: "stretch",
description: "Align items for row 1",
},
row1Content: {
type: "array",
items: {
type: "reference",
collections: [
"markdown",
"html",
"headline",
"image",
"quote",
"youtube_video",
"image_gallery",
"iframe",
"searchable_text",
"fullwidth_banner",
"list"
],
},
description: "Content components for row 1 (Markdown, HTML, Headline, Image, …)",
},
row2JustifyContent: {
type: "string",
enum: [
"start",
"end",
"center",
"between",
"around",
"evenly"
],
default: "start",
},
row2AlignItems: {
type: "string",
enum: [
"start",
"end",
"center",
"baseline",
"stretch"
],
default: "stretch",
},
row2Content: {
type: "array",
items: {
type: "reference",
collections: [
"markdown",
"html",
"headline",
"image",
"quote",
"youtube_video",
"image_gallery",
"iframe",
"searchable_text",
"fullwidth_banner",
"list"
],
},
description: "Content components for row 2",
},
row3JustifyContent: {
type: "string",
enum: [
"start",
"end",
"center",
"between",
"around",
"evenly"
],
default: "start",
},
row3AlignItems: {
type: "string",
enum: [
"start",
"end",
"center",
"baseline",
"stretch"
],
default: "stretch",
},
row3Content: {
type: "array",
items: {
type: "reference",
collections: [
"markdown",
"html",
"headline",
"image",
"quote",
"youtube_video",
"image_gallery",
"iframe",
"searchable_text",
"fullwidth_banner",
"list"
],
},
description: "Content components for row 3",
},
}
}

17
types/demo.json5 Normal file
View File

@@ -0,0 +1,17 @@
{
name: "demo",
description: "Demo content type (kept in version control as example).",
tags: ["content"],
category: "content",
fields: {
title: {
type: "string",
required: true,
description: "Title",
},
body: {
type: "string",
description: "Optional body text",
},
},
}

View File

@@ -1,17 +0,0 @@
{
// Corresponds to CF_Footer extends CF_Content / Contentful_Footer.ts
name: "footer",
description: "Footer with content layout",
tags: ["layout", "config"],
category: "config",
extends: [
"content_layout"
],
fields: {
id: {
type: "string",
required: true,
description: "Unique footer ID (e.g. for navigation)",
},
},
}

View File

@@ -1,39 +0,0 @@
{
// Equivalent to: CF_FullwidthBanner interface
name: "fullwidth_banner",
description: "Full-width banner with dark/light variant",
tags: ["component", "layout"],
category: "components",
fields: {
name: {
type: "string",
required: true,
description: "Internal name"
},
variant: {
type: "string",
required: true,
enum: ["dark", "light"],
default: "light",
description: "Color variant"
},
headline: {
type: "string",
required: true
},
subheadline: {
type: "string"
},
text: {
type: "richtext",
description: "Banner body text"
},
image: {
type: "array",
items: {
type: "string"
},
description: "Image URLs"
},
}
}

View File

@@ -1,37 +0,0 @@
{
// Corresponds to CF_ComponentHeadline / Contentful_Headline.ts
name: "headline",
description: "Headline component with optional layout",
tags: ["component", "layout"],
category: "components",
fields: {
internal: {
type: "string",
required: true,
description: "Internal key",
},
text: {
type: "string",
required: true,
description: "Headline text",
},
tag: {
type: "string",
required: true,
enum: ["h1", "h2", "h3", "h4", "h5", "h6"],
default: "h2",
description: "HTML tag for headline",
},
layout: {
type: "object",
useFields: "component_layout",
description: "Column width (grid) like CF_ComponentLayout",
},
align: {
type: "string",
enum: ["left", "center", "right"],
default: "left",
description: "Text alignment",
},
},
}

View File

@@ -1,24 +0,0 @@
{
// Corresponds to CF_HTML / Contentful_Html.ts
name: "html",
description: "Raw HTML content block with optional layout",
tags: ["content", "component"],
category: "components",
fields: {
id: {
type: "string",
required: true,
description: "Unique component ID",
},
html: {
type: "html",
required: true,
description: "HTML content (safely embedded)",
},
layout: {
type: "object",
useFields: "component_layout",
description: "Column width (grid) like CF_ComponentLayout",
},
},
}

View File

@@ -1,34 +0,0 @@
{
// Corresponds to CF_ComponentIframe / Contentful_Iframe.ts
name: "iframe",
description: "Embedded iframe with URL and optional title",
tags: ["media", "component"],
category: "components",
fields: {
name: {
type: "string",
required: true,
description: "Internal component name",
},
content: {
type: "string",
required: true,
description: "Description/content for iframe",
},
iframe: {
type: "string",
required: true,
description: "Iframe URL or embed code",
},
overlayImage: {
type: "reference",
collection: "img",
description: "Optional overlay image (reference to img)",
},
layout: {
type: "object",
useFields: "component_layout",
description: "Column width (grid) like CF_ComponentLayout",
},
},
}

View File

@@ -1,37 +0,0 @@
{
// Corresponds to CF_ComponentImage / Contentful_Image.ts
name: "image",
description: "Image component with reference to img asset and layout",
tags: ["component", "media"],
category: "components",
fields: {
name: {
type: "string",
required: true,
description: "Internal component name",
},
image: {
type: "reference",
collection: "img",
required: true,
description: "Image (reference to img)",
},
caption: {
type: "string",
description: "Image caption",
},
layout: {
type: "object",
useFields: "component_layout",
description: "Column width (grid) like CF_ComponentLayout",
},
maxWidth: {
type: "integer",
description: "Max width in px",
},
aspectRatio: {
type: "number",
description: "Aspect ratio (e.g. 16/9)",
},
},
}

View File

@@ -1,32 +0,0 @@
{
// Corresponds to CF_ImageGallery / Contentful_ImageGallery.ts
name: "image_gallery",
description: "Gallery of image references",
tags: ["media", "component"],
category: "components",
fields: {
name: {
type: "string",
required: true,
description: "Internal gallery name",
},
images: {
type: "array",
required: true,
items: {
type: "reference",
collection: "img",
},
description: "Images (references to img)",
},
layout: {
type: "object",
useFields: "component_layout",
description: "Column width (grid) like CF_ComponentLayout",
},
description: {
type: "string",
description: "Optional gallery description",
},
},
}

View File

@@ -1,56 +0,0 @@
{
// Corresponds to CF_ComponentImg / Contentful_Img.ts (Asset/Image)
name: "img",
description: "Image asset with URL, filename and optional dimensions",
tags: ["asset", "media"],
category: "components",
fields: {
title: {
type: "string",
required: true,
description: "Image title",
},
description: {
type: "string",
description: "Alt text / description",
},
file: {
type: "object",
required: true,
description: "File: url (required), fileName, contentType, details (size, image.width/height)",
fields: {
url: {
type: "string",
required: true,
description: "Image file URL",
},
fileName: {
type: "string",
description: "Filename",
},
contentType: {
type: "string",
description: "e.g. image/jpeg",
},
details: {
type: "object",
description: "Size and dimensions",
fields: {
size: {
type: "integer",
description: "File size in bytes",
},
image: {
type: "object",
description: "Image dimensions",
fields: {
width: { type: "integer", description: "Width in px" },
height: { type: "integer", description: "Height in px" },
},
},
},
},
},
},
},
}

View File

@@ -1,75 +0,0 @@
{
// Corresponds to CF_Link / Contentful_Link.ts
name: "link",
description: "Internal or external link with label and optional icon",
tags: ["navigation", "component"],
category: "components",
fields: {
name: {
type: "string",
required: true,
description: "Internal link name",
},
internal: {
type: "string",
required: true,
description: "Internal key",
},
linkName: {
type: "string",
required: true,
description: "Display name (e.g. in navigation)",
},
url: {
type: "string",
required: true,
description: "Target URL",
},
icon: {
type: "string",
description: "Icon identifier",
},
color: {
type: "string",
description: "Color (e.g. for buttons)",
},
newTab: {
type: "boolean",
default: false,
description: "Open in new tab",
},
external: {
type: "boolean",
default: false,
description: "External link",
},
description: {
type: "string",
description: "Description",
},
alt: {
type: "string",
description: "Alt text (e.g. for icon)",
},
showText: {
type: "boolean",
default: true,
description: "Show text",
},
author: {
type: "string",
required: true,
description: "Author (e.g. for sources)",
},
date: {
type: "string",
required: true,
description: "Date (e.g. publication)",
},
source: {
type: "string",
required: true,
description: "Source",
},
},
}

View File

@@ -1,23 +0,0 @@
{
// Corresponds to CF_Link_List / Contentful_Link_List.ts (componentLinkList)
name: "link_list",
description: "List of links with optional headline",
tags: ["navigation", "component"],
category: "components",
fields: {
headline: {
type: "string",
required: true,
description: "Link list headline",
},
links: {
type: "array",
required: true,
items: {
type: "reference",
collection: "link",
},
description: "Links (references to link)",
},
},
}

View File

@@ -1,20 +0,0 @@
{
// Corresponds to CF_ComponentList / Contentful_List.ts
name: "list",
description: "Simple list of string items",
tags: ["content", "component"],
category: "components",
fields: {
internal: {
type: "string",
required: true,
description: "Internal key",
},
item: {
type: "array",
required: true,
items: { type: "string" },
description: "List entries",
},
},
}

View File

@@ -1,29 +0,0 @@
{
// Corresponds to CF_Markdown / Contentful_Markdown.ts
name: "markdown",
description: "Markdown content block with optional layout",
tags: ["content", "component"],
category: "components",
fields: {
name: {
type: "string",
required: true,
description: "Internal component name",
},
content: {
type: "markdown",
description: "Markdown/body text content",
},
layout: {
type: "object",
useFields: "component_layout",
description: "Column width (grid) like CF_ComponentLayout",
},
alignment: {
type: "string",
enum: ["left", "center", "right"],
default: "left",
description: "Text alignment",
},
},
}

View File

@@ -1,28 +0,0 @@
{
// Corresponds to CF_Navigation / Contentful_Navigation.ts
name: "navigation",
description: "Navigation block with links (header/footer)",
tags: ["navigation", "layout"],
category: "config",
fields: {
name: {
type: "string",
required: true,
description: "Display name of navigation",
},
internal: {
type: "string",
required: true,
description: "Internal key (e.g. navigation-header, navigation-footer)",
},
links: {
type: "array",
required: true,
items: {
type: "reference",
collections: ["link", "page", "post"],
},
description: "Navigation entries (references to link, page or post)",
},
},
}

View File

@@ -1,45 +0,0 @@
{
// Equivalent to: CF_Page extends CF_Content, CF_SEO
name: "page",
description: "Page with layout, SEO and content rows",
tags: ["content", "page"],
category: "content",
extends: [
"content_layout",
"seo"
],
fields: {
slug: {
type: "string",
required: true,
unique: true,
description: "URL slug"
},
name: {
type: "string",
required: true,
description: "Internal page name"
},
linkName: {
type: "string",
required: true,
description: "Display name in navigation"
},
icon: {
type: "string",
description: "Icon identifier (optional)"
},
headline: {
type: "string",
required: true
},
subheadline: {
type: "string"
},
topFullwidthBanner: {
type: "reference",
collection: "fullwidth_banner",
description: "Hero banner at the top"
},
}
}

View File

@@ -1,47 +0,0 @@
{
// Corresponds to CF_PageConfig / Contentful_PageConfig.ts
name: "page_config",
description: "Global page config (logo, footer, etc.)",
tags: ["config", "layout"],
category: "config",
fields: {
logo: {
type: "reference",
collection: "img",
required: true,
description: "Logo image (reference to img)",
},
footerText1: {
type: "string",
required: true,
description: "Footer text (e.g. copyright)",
},
seoTitle: {
type: "string",
required: true,
description: "Default SEO title for website",
},
seoDescription: {
type: "string",
required: true,
description: "Default meta description",
},
blogTagPageHeadline: {
type: "string",
description: "Headline for tag page (blog)",
},
blogPostsPageHeadline: {
type: "string",
description: "Headline for blog overview page",
},
blogPostsPageSubHeadline: {
type: "string",
description: "Subheadline for blog overview page",
},
website: {
type: "string",
required: true,
description: "Website-URL",
},
},
}

View File

@@ -1,75 +0,0 @@
{
// Equivalent to: CF_Post extends CF_Content, CF_SEO
name: "post",
description: "Blog post with layout, SEO and optional tags",
tags: ["content", "blog"],
category: "content",
extends: [
"content_layout",
"seo"
],
fields: {
slug: {
type: "string",
required: true,
unique: true
},
linkName: {
type: "string",
required: true
},
icon: {
type: "string"
},
headline: {
type: "string",
required: true
},
subheadline: {
type: "string"
},
excerpt: {
type: "string",
maxLength: 500,
description: "Short summary for previews"
},
created: {
type: "datetime",
auto: true,
readonly: true
},
postImage: {
type: "reference",
collection: "img",
description: "Featured image"
},
postTag: {
type: "array",
items: {
type: "reference",
collection: "tag"
},
description: "Associated tags"
},
important: {
type: "boolean",
required: true,
default: false,
description: "Mark post as important"
},
date: {
type: "datetime",
description: "Optional display date"
},
content: {
type: "markdown",
required: true,
description: "Post body (Markdown)"
},
showCommentSection: {
type: "boolean",
default: true,
description: "Show comment section (default: true)"
},
}
}

View File

@@ -1,62 +0,0 @@
{
// Corresponds to CF_Post_Overview / Contentful_Post_Overview.ts (extends CF_Content, CF_SEO)
name: "post_overview",
description: "Post listing page with layout and SEO",
tags: ["content", "blog"],
category: "content",
extends: [
"content_layout",
"seo"
],
fields: {
id: {
type: "string",
required: true,
description: "Unique post overview ID",
},
headline: {
type: "string",
required: true,
description: "Headline",
},
text: {
type: "richtext",
description: "Intro text",
},
layout: {
type: "object",
useFields: "component_layout",
description: "Column width (grid)",
},
allPosts: {
type: "boolean",
default: false,
description: "Show all posts (otherwise filter)",
},
filterByTag: {
type: "array",
items: {
type: "reference",
collection: "tag",
},
description: "Only posts with these tags",
},
posts: {
type: "array",
items: {
type: "reference",
collection: "post",
},
description: "Fixed list of posts (when not allPosts)",
},
numberItems: {
type: "integer",
description: "Max. number of displayed entries",
},
design: {
type: "string",
enum: ["cards", "list"],
description: "Display as cards or list",
},
},
}

View File

@@ -1,73 +0,0 @@
{
// Showcases: strict mode, constraints, unique, nullable, readonly, pattern
name: "product",
description: "Product with SKU, price and optional fields (strict schema)",
tags: ["content", "ecommerce"],
category: "content",
strict: true,
fields: {
title: {
type: "string",
required: true,
minLength: 1,
maxLength: 200,
description: "Product name",
},
sku: {
type: "string",
required: true,
unique: true,
pattern: "^[A-Z]{2,4}-\\d{3,6}$",
description: "Stock Keeping Unit (e.g. EL-1234, BOOK-00042)",
},
price: {
type: "number",
required: true,
min: 0,
description: "Price in EUR",
},
description: {
type: "string",
maxLength: 5000,
description: "Detailed product description",
},
category: {
type: "string",
required: true,
enum: [
"electronics",
"clothing",
"books",
"food",
"other"
],
description: "Product category",
},
in_stock: {
type: "boolean",
default: true,
description: "Whether the product is in stock"
},
rating: {
type: "number",
min: 0,
max: 5,
nullable: true,
description: "Average customer rating (null if unrated)"
},
images: {
type: "array",
items: {
type: "string"
},
minItems: 1,
maxItems: 10,
description: "Product image URLs (110)",
},
created_at: {
type: "datetime",
auto: true,
readonly: true
},
}
}

View File

@@ -1,31 +0,0 @@
{
// Corresponds to CF_Quote / Contentful_Quote.ts
name: "quote",
description: "Quote block with author and optional variant",
tags: ["content", "component"],
category: "components",
fields: {
quote: {
type: "string",
required: true,
description: "Quote text",
},
author: {
type: "string",
required: true,
description: "Author or source of quote",
},
variant: {
type: "string",
required: true,
enum: ["left", "right"],
default: "left",
description: "Quote alignment",
},
layout: {
type: "object",
useFields: "component_layout",
description: "Column width (grid) like CF_ComponentLayout",
},
},
}

View File

@@ -1,44 +0,0 @@
{
// Corresponds to CF_ComponentSearchableText / Contentful_SearchableText.ts
name: "searchable_text",
description: "Searchable text with optional tag whitelist",
tags: ["content", "component", "search"],
category: "components",
fields: {
id: {
type: "string",
required: true,
description: "Unique component ID",
},
tagWhitelist: {
type: "array",
items: {
type: "reference",
collection: "tag",
},
description: "Optional: only search content with these tags",
},
textFragments: {
type: "array",
required: true,
items: {
type: "reference",
collection: "text_fragment",
},
description: "Searchable text fragments (references to text_fragment)",
},
title: {
type: "string",
description: "Search component title",
},
description: {
type: "string",
description: "Description",
},
layout: {
type: "object",
useFields: "component_layout",
description: "Column width (grid) like CF_ComponentLayout",
},
},
}

View File

@@ -1,30 +0,0 @@
{
// Equivalent to: CF_SEO interface
name: "seo",
description: "SEO meta (title, robots, description; inherited by page/post)",
tags: ["seo", "partial"],
category: "layout",
fields: {
seoTitle: {
type: "string",
required: true,
description: "SEO page title"
},
seoMetaRobots: {
type: "string",
enum: [
"index, follow",
"noindex, follow",
"index, nofollow",
"noindex, nofollow"
],
default: "index, follow",
description: "Robots meta directive",
},
seoDescription: {
type: "string",
maxLength: 160,
description: "Meta description for search engines"
},
}
}

View File

@@ -1,14 +0,0 @@
{
// Equivalent to: CF_Tag interface
name: "tag",
description: "Tag for categorising content",
tags: ["taxonomy", "config"],
category: "config",
fields: {
name: {
type: "string",
required: true,
unique: true
},
}
}

View File

@@ -1,32 +0,0 @@
{
// Corresponds to CF_TextFragment / Contentful_TextFragment.ts
name: "text_fragment",
description: "Reusable text fragment with optional tag references",
tags: ["content", "component"],
category: "components",
fields: {
id: {
type: "string",
required: true,
description: "Unique fragment ID",
},
tags: {
type: "array",
items: {
type: "reference",
collection: "tag",
},
description: "Optional tags for categorisation",
},
title: {
type: "string",
required: true,
description: "Text fragment title",
},
text: {
type: "richtext",
required: true,
description: "Searchable text content",
},
},
}

View File

@@ -1,19 +0,0 @@
{
// Corresponds to CF_TopBanner / Contentful_TopBanner.ts
name: "top_banner",
description: "Top-of-page banner or notice text",
tags: ["component", "layout"],
category: "components",
fields: {
id: {
type: "string",
required: true,
description: "Unique ID",
},
text: {
type: "string",
required: true,
description: "Banner text (e.g. notice at top of page)",
},
},
}

View File

@@ -1,35 +0,0 @@
{
// Corresponds to CF_YoutubeVideo / Contentful_YoutubeVideo.ts
name: "youtube_video",
description: "Embedded YouTube video with optional params",
tags: ["media", "component"],
category: "components",
fields: {
id: {
type: "string",
description: "Unique ID (optional, otherwise _slug)",
},
youtubeId: {
type: "string",
required: true,
description: "YouTube video ID (e.g. from URL ?v=VIDEO_ID)",
},
params: {
type: "string",
description: "Optional URL params (e.g. start=30, autoplay=1)",
},
title: {
type: "string",
description: "Video title",
},
description: {
type: "string",
description: "Short description",
},
layout: {
type: "object",
useFields: "component_layout",
description: "Column width (grid) like CF_ComponentLayout",
},
},
}