nca-ai-cms-astro-plugin 1.1.4 → 1.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -85,6 +85,9 @@ ncaAiCms({
85
85
  | `/api/prompts` | Manage prompt templates |
86
86
  | `/api/scheduler` | Manage scheduled posts |
87
87
  | `/api/articles/*` | Article operations |
88
+ | `/api/db/export` | Export settings + prompts as JSON |
89
+ | `/api/db/import` | Import settings + prompts (merge/upsert) |
90
+ | `/api/db/download` | Download raw SQLite database backup |
88
91
 
89
92
  All `/api/*` and `/editor` routes are protected by cookie-based authentication.
90
93
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nca-ai-cms-astro-plugin",
3
- "version": "1.1.4",
3
+ "version": "1.1.6",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./src/index.ts",
@@ -19,13 +19,6 @@ const CreateArticleSchema = z.object({
19
19
  // POST /api/articles/create - Generate content + image and save in one call
20
20
  export const POST: APIRoute = async ({ request }) => {
21
21
  try {
22
- // CSRF: reject cross-origin requests
23
- const origin = request.headers.get('origin');
24
- const requestUrl = new URL(request.url);
25
- if (origin && new URL(origin).origin !== requestUrl.origin) {
26
- return jsonError('Forbidden', 403);
27
- }
28
-
29
22
  let body: unknown;
30
23
  try {
31
24
  body = await request.json();
@@ -294,37 +294,40 @@ export function SettingsTab() {
294
294
  <div style={{ ...styles.plannerForm, flexDirection: 'row', alignItems: 'center' }}>
295
295
  <span style={{ ...styles.label, marginRight: 'auto' }}>Datenbank-Verwaltung</span>
296
296
  <a
297
- href="/api/db/download"
297
+ href="/api/db/export"
298
298
  style={{ ...styles.editButton, textDecoration: 'none', display: 'inline-flex', alignItems: 'center', gap: '0.4rem' }}
299
299
  >
300
- ↓ DB herunterladen
300
+ ↓ DB exportieren
301
301
  </a>
302
302
  <label style={{ ...styles.editButton, cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: '0.4rem', margin: 0 }}>
303
- ↑ DB hochladen
303
+ ↑ DB importieren
304
304
  <input
305
305
  type="file"
306
- accept=".db,.sqlite,.sqlite3"
306
+ accept=".json"
307
307
  style={styles.srOnly}
308
308
  onChange={async (e) => {
309
309
  const file = e.target.files?.[0];
310
310
  if (!file) return;
311
- if (!confirm(`Datenbank "${file.name}" hochladen? Die aktuelle DB wird ueberschrieben.`)) {
311
+ if (!confirm(`Datenbank "${file.name}" importieren? Die aktuellen Daten werden ersetzt.`)) {
312
312
  e.target.value = '';
313
313
  return;
314
314
  }
315
- const formData = new FormData();
316
- formData.append('database', file);
317
315
  try {
318
- const res = await fetch('/api/db/upload', { method: 'POST', body: formData });
316
+ const text = await file.text();
317
+ JSON.parse(text);
318
+ const res = await fetch('/api/db/import', {
319
+ method: 'POST',
320
+ headers: { 'Content-Type': 'application/json' },
321
+ body: text,
322
+ });
319
323
  const data = await res.json();
320
324
  if (res.ok) {
321
- alert('Datenbank hochgeladen. Seite wird neu geladen.');
322
- window.location.reload();
325
+ loadData();
323
326
  } else {
324
- alert('Fehler: ' + (data.error || 'Upload fehlgeschlagen'));
327
+ alert('Fehler: ' + (data.error || 'Import fehlgeschlagen'));
325
328
  }
326
329
  } catch {
327
- alert('Upload fehlgeschlagen');
330
+ alert('Import fehlgeschlagen. Bitte eine gueltige JSON-Datei waehlen.');
328
331
  }
329
332
  e.target.value = '';
330
333
  }}
@@ -1,4 +1,5 @@
1
1
  import type { APIContext } from 'astro';
2
+ import { getPublicOrigin } from '../utils/originUtils.js';
2
3
 
3
4
  export function generateRobotsTxt(siteUrl: string): string {
4
5
  const base = siteUrl.replace(/\/+$/, '');
@@ -14,7 +15,7 @@ Sitemap: ${base}/sitemap.xml
14
15
  }
15
16
 
16
17
  export function GET(context: APIContext): Response {
17
- const siteUrl = context.site?.toString() ?? context.url.origin;
18
+ const siteUrl = getPublicOrigin(context);
18
19
  const body = generateRobotsTxt(siteUrl);
19
20
 
20
21
  return new Response(body, {
@@ -1,6 +1,7 @@
1
1
  import type { APIContext } from 'astro';
2
2
  import { ArticleService } from '../services/ArticleService';
3
3
  import type { ArticleData } from '../services/ArticleService';
4
+ import { getPublicOrigin } from '../utils/originUtils.js';
4
5
 
5
6
  const STATIC_PAGES = ['/', '/impressum', '/ueber-ai-cms'];
6
7
 
@@ -43,7 +44,7 @@ ${[...staticEntries, ...articleEntries].join('\n')}
43
44
  }
44
45
 
45
46
  export async function GET(context: APIContext): Promise<Response> {
46
- const siteUrl = context.site?.toString() ?? context.url.origin;
47
+ const siteUrl = getPublicOrigin(context);
47
48
  const service = new ArticleService();
48
49
  const articles = await service.list();
49
50
 
@@ -0,0 +1,15 @@
1
+ import type { APIContext } from 'astro';
2
+
3
+ export function getPublicOrigin(context: APIContext): string {
4
+ if (context.site) {
5
+ return context.site.toString().replace(/\/+$/, '');
6
+ }
7
+
8
+ const proto = context.request.headers.get('x-forwarded-proto');
9
+ const host = context.request.headers.get('x-forwarded-host');
10
+ if (proto && host) {
11
+ return `${proto}://${host}`;
12
+ }
13
+
14
+ return context.url.origin;
15
+ }
package/update.md CHANGED
@@ -1,3 +1,67 @@
1
+ # v1.1.6
2
+
3
+ ## Fix: reverse proxy compatibility
4
+
5
+ ### Removed: broken CSRF check in `/api/articles/create`
6
+ - The origin comparison (`request.headers.get('origin')` vs `request.url`) fails behind reverse proxies (Traefik/Nginx) because `request.url` resolves to the internal container URL
7
+ - Route is already protected by auth middleware — CSRF check was redundant
8
+ - Fixes 403 Forbidden when generating articles in production
9
+
10
+ ### New: `getPublicOrigin()` utility
11
+ - Resolves the correct external URL behind reverse proxies
12
+ - Priority: `context.site` → `X-Forwarded-Proto`/`X-Forwarded-Host` → `context.url.origin`
13
+ - Nginx already sets these headers on all proxy locations
14
+
15
+ ### Fixed: robots.txt and sitemap.xml URLs
16
+ - Both files used `context.url.origin` which returned `http://127.0.0.1:4321` behind proxy
17
+ - Now use `getPublicOrigin()` to produce correct external URLs
18
+
19
+ ---
20
+
21
+ # v1.1.5
22
+
23
+ ## Refactor: Settings import/export with merge semantics
24
+
25
+ ### New: JSON settings export/import endpoints
26
+ - `GET /api/db/export` — exports site settings and prompts as a JSON file download
27
+ - `POST /api/db/import` — imports settings and/or prompts with upsert (merge) semantics
28
+
29
+ ### Import features
30
+ - **Merge, not replace** — importing prompts won't delete your settings, importing settings won't delete your prompts
31
+ - **Partial import** — payload only needs to include the sections you want to update (`siteSettings`, `prompts`, or both)
32
+ - **No restart needed** — uses the live `astro:db` connection, no file replacement, no connection disruption
33
+ - **Validation** — payload is validated before import with clear error messages
34
+
35
+ ### Breaking: SQLite file upload removed
36
+ - `POST /api/db/upload` now returns `410 Gone` with migration instructions
37
+ - The old SQLite file upload caused infinite redirect loops by breaking the live DB connection
38
+ - Use `GET /api/db/export` + `POST /api/db/import` instead
39
+
40
+ ### Export format
41
+ ```json
42
+ {
43
+ "version": 1,
44
+ "exportedAt": "2026-03-24T12:00:00.000Z",
45
+ "siteSettings": [{ "key": "content.branche", "value": "Tech", "updatedAt": "..." }],
46
+ "prompts": [{ "id": "p1", "name": "Blog", "category": "content", "promptText": "...", "updatedAt": "..." }]
47
+ }
48
+ ```
49
+
50
+ ### Cleanup
51
+ - Removed dead `dbUploadUtils.ts` and its tests
52
+ - Upload test updated for 410 stub
53
+
54
+ ---
55
+
56
+ # v1.1.3 / v1.1.4
57
+
58
+ ## Internal: DB upload refactoring (superseded by v1.1.5)
59
+ - Extracted upload utilities to `dbUploadUtils.ts`
60
+ - Added `DbTransferService` with JSON export/import
61
+ - These versions contained intermediate work that was refined in v1.1.5
62
+
63
+ ---
64
+
1
65
  # v1.1.2
2
66
 
3
67
  ## Feature: Delete button in InlineEditor