changemaker.lite/api/src/modules/docs/docs-templates.service.ts
bunker-admin 8b9ab93856 Add docs CMS: blog authoring, access policies, sharing, version history, templates, metadata, search, Gitea auto-setup
7 documentation system features:
- Blog authoring: frontmatter panel, new post wizard, authors management
- Access policies: per-file/directory edit restrictions with role/user granularity
- Public sharing: share links with collaborative editing via dual JWT auth
- Version history: Gitea auto-commit on save, diff viewer, restore
- Document templates: 8 built-in templates (blog, guide, API ref, ADR, FAQ, etc.)
- Metadata dashboard: overview of all docs with warnings (no-tags, stale, etc.)
- Content search: in-file text search with line-level matches

Gitea auto-setup: one-click configuration of API token, repos, labels, OAuth app
- Backend service + startup hook (auto-configures if GITEA_ADMIN_PASSWORD set)
- Admin GUI wizard at /app/services/gitea/setup
- config.sh now prompts for Gitea admin password

Backend: 10 new files, 5 modified (3 models, 1 enum, 2 migrations, 30+ API endpoints)
Frontend: 13 new files, 3 modified (hooks, components, pages)

Bunker Admin
2026-03-27 13:28:52 -06:00

405 lines
6.8 KiB
TypeScript

export interface DocTemplate {
id: string;
name: string;
description: string;
category: 'blog' | 'guide' | 'reference' | 'planning' | 'general';
icon: string; // Material icon name
filenamePattern: string; // e.g. "{{slug}}.md" or "blog/posts/{{date}}-{{slug}}.md"
content: string;
}
const today = () => new Date().toISOString().split('T')[0];
const BUILT_IN_TEMPLATES: DocTemplate[] = [
{
id: 'blog-post',
name: 'Blog Post',
description: 'A blog post with MkDocs Material frontmatter',
category: 'blog',
icon: 'article',
filenamePattern: 'blog/posts/{{date}}-{{slug}}.md',
content: `---
date: {{date}}
authors:
- admin
categories:
- General
draft: true
---
# {{title}}
Write your intro paragraph here. This appears on the blog index page.
<!-- more -->
## Main Content
Continue writing below the fold...
`,
},
{
id: 'how-to-guide',
name: 'How-To Guide',
description: 'Step-by-step guide for a specific task',
category: 'guide',
icon: 'menu_book',
filenamePattern: 'docs/{{slug}}.md',
content: `---
tags:
- guide
---
# How to {{title}}
Brief description of what this guide covers and who it's for.
## Prerequisites
- Prerequisite 1
- Prerequisite 2
## Steps
### Step 1: Getting Started
Description of the first step.
### Step 2: Configuration
Description of the second step.
### Step 3: Verification
How to verify everything is working.
## Troubleshooting
Common issues and their solutions.
## Next Steps
What to do after completing this guide.
`,
},
{
id: 'api-reference',
name: 'API Reference',
description: 'API endpoint documentation',
category: 'reference',
icon: 'api',
filenamePattern: 'docs/api/{{slug}}.md',
content: `---
tags:
- api
- reference
---
# {{title}} API
## Overview
Brief description of this API group.
## Endpoints
### GET /api/endpoint
Description of what this endpoint does.
**Parameters:**
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| id | string | Yes | Resource ID |
**Response:**
\`\`\`json
{
"id": "abc123",
"name": "Example"
}
\`\`\`
### POST /api/endpoint
Description of what this endpoint does.
**Request Body:**
\`\`\`json
{
"name": "Example"
}
\`\`\`
## Error Codes
| Code | Description |
|------|-------------|
| 400 | Bad Request |
| 401 | Unauthorized |
| 404 | Not Found |
`,
},
{
id: 'adr',
name: 'Architecture Decision Record',
description: 'Document an architecture decision',
category: 'planning',
icon: 'architecture',
filenamePattern: 'docs/architecture/adr-{{slug}}.md',
content: `---
tags:
- architecture
- decision
---
# ADR: {{title}}
**Date:** {{date}}
**Status:** Proposed
## Context
What is the issue that we're seeing that is motivating this decision or change?
## Decision
What is the change that we're proposing and/or doing?
## Consequences
What becomes easier or more difficult to do because of this change?
### Positive
- Benefit 1
- Benefit 2
### Negative
- Drawback 1
- Drawback 2
### Neutral
- Side effect 1
`,
},
{
id: 'faq',
name: 'FAQ Page',
description: 'Frequently asked questions with collapsible answers',
category: 'general',
icon: 'quiz',
filenamePattern: 'docs/{{slug}}.md',
content: `---
tags:
- faq
---
# {{title}}
Frequently asked questions about this topic.
??? question "Question 1?"
Answer to question 1. You can include **formatting**, links, and code blocks.
??? question "Question 2?"
Answer to question 2.
\`\`\`bash
# Example command
echo "hello world"
\`\`\`
??? question "Question 3?"
Answer to question 3.
`,
},
{
id: 'release-notes',
name: 'Release Notes',
description: 'Release notes for a version',
category: 'planning',
icon: 'new_releases',
filenamePattern: 'docs/{{slug}}.md',
content: `---
tags:
- release
---
# Release Notes — {{title}}
**Release Date:** {{date}}
## Highlights
Brief summary of the most important changes.
## New Features
- **Feature Name** — Description of the new feature
## Improvements
- **Improvement** — Description of the improvement
## Bug Fixes
- **Fix** — Description of what was fixed
## Breaking Changes
!!! warning "Breaking Changes"
List any breaking changes that require user action.
## Upgrade Instructions
Steps needed to upgrade from the previous version.
`,
},
{
id: 'tutorial',
name: 'Tutorial',
description: 'In-depth tutorial with learning objectives',
category: 'guide',
icon: 'school',
filenamePattern: 'docs/{{slug}}.md',
content: `---
tags:
- tutorial
---
# {{title}}
## What You'll Learn
By the end of this tutorial, you will be able to:
- Learning objective 1
- Learning objective 2
- Learning objective 3
## Prerequisites
!!! info "Before you begin"
- Prerequisite 1
- Prerequisite 2
## Part 1: Setup
Content for part 1...
## Part 2: Implementation
Content for part 2...
## Part 3: Testing
Content for part 3...
## Summary
Recap of what was covered and next steps.
## Further Reading
- [Related Guide 1](link)
- [Related Guide 2](link)
`,
},
{
id: 'meeting-notes',
name: 'Meeting Notes',
description: 'Meeting notes template with agenda and action items',
category: 'general',
icon: 'groups',
filenamePattern: 'docs/{{slug}}.md',
content: `---
tags:
- meeting
---
# {{title}}
**Date:** {{date}}
**Attendees:**
## Agenda
1. Item 1
2. Item 2
3. Item 3
## Discussion Notes
### Topic 1
Notes...
### Topic 2
Notes...
## Action Items
- [ ] Action item 1 — @assignee — due date
- [ ] Action item 2 — @assignee — due date
## Next Meeting
Date and topics for next meeting.
`,
},
];
function getTemplates(): DocTemplate[] {
return BUILT_IN_TEMPLATES;
}
function applyTemplate(
templateId: string,
variables: { title: string; date?: string; slug?: string; author?: string },
): { content: string; suggestedPath: string } | null {
const template = BUILT_IN_TEMPLATES.find(t => t.id === templateId);
if (!template) return null;
const date = variables.date || today();
const slug = variables.slug || slugify(variables.title);
const author = variables.author || 'admin';
let content = template.content
.replace(/\{\{title\}\}/g, variables.title)
.replace(/\{\{date\}\}/g, date)
.replace(/\{\{slug\}\}/g, slug)
.replace(/\{\{author\}\}/g, author);
let suggestedPath = template.filenamePattern
.replace(/\{\{title\}\}/g, variables.title)
.replace(/\{\{date\}\}/g, date)
.replace(/\{\{slug\}\}/g, slug);
return { content, suggestedPath };
}
function slugify(text: string): string {
return text
.toLowerCase()
.replace(/[^a-z0-9\s-]/g, '')
.replace(/\s+/g, '-')
.replace(/-+/g, '-')
.replace(/^-|-$/g, '')
.substring(0, 80);
}
export const docsTemplatesService = {
getTemplates,
applyTemplate,
};