@digitalygo/create-diggocms-app 0.1.0
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 +152 -0
- package/bin/cli.js +392 -0
- package/package.json +44 -0
- package/templates/full/.env.local.example +13 -0
- package/templates/full/README.md +101 -0
- package/templates/full/app/[...slug]/page.tsx +115 -0
- package/templates/full/app/globals.css +187 -0
- package/templates/full/app/layout.tsx +25 -0
- package/templates/full/app/page.tsx +6 -0
- package/templates/full/components/DiggoProvider.tsx +15 -0
- package/templates/full/components/ExtendedCard.tsx +15 -0
- package/templates/full/components/ExtendedImage.tsx +16 -0
- package/templates/full/components/ExtendedMenuContainer.tsx +10 -0
- package/templates/full/components/ExtendedMenuItem.tsx +22 -0
- package/templates/full/components/ExtendedRichtext.tsx +15 -0
- package/templates/full/components/ExtendedSubtitle.tsx +10 -0
- package/templates/full/components/ExtendedTitle.tsx +10 -0
- package/templates/full/components/server-components.ts +8 -0
- package/templates/full/lib/data-fetching.ts +205 -0
- package/templates/full/lib/diggo-config.ts +48 -0
- package/templates/full/next-env.d.ts +2 -0
- package/templates/full/next.config.ts +7 -0
- package/templates/full/package.json +25 -0
- package/templates/full/tsconfig.json +23 -0
- package/templates/minimal/.env.local.example +13 -0
- package/templates/minimal/README.md +67 -0
- package/templates/minimal/app/[...slug]/page.tsx +56 -0
- package/templates/minimal/app/globals.css +11 -0
- package/templates/minimal/app/layout.tsx +22 -0
- package/templates/minimal/app/page.tsx +6 -0
- package/templates/minimal/components/DiggoProvider.tsx +15 -0
- package/templates/minimal/lib/data-fetching.ts +59 -0
- package/templates/minimal/lib/diggo-config.ts +35 -0
- package/templates/minimal/next-env.d.ts +2 -0
- package/templates/minimal/next.config.ts +7 -0
- package/templates/minimal/package.json +25 -0
- package/templates/minimal/tsconfig.json +23 -0
- package/templates/with-mock/.env.local.example +13 -0
- package/templates/with-mock/README.md +128 -0
- package/templates/with-mock/app/[...slug]/page.tsx +115 -0
- package/templates/with-mock/app/globals.css +187 -0
- package/templates/with-mock/app/layout.tsx +25 -0
- package/templates/with-mock/app/page.tsx +6 -0
- package/templates/with-mock/components/DiggoProvider.tsx +15 -0
- package/templates/with-mock/components/ExtendedCard.tsx +15 -0
- package/templates/with-mock/components/ExtendedImage.tsx +16 -0
- package/templates/with-mock/components/ExtendedMenuContainer.tsx +10 -0
- package/templates/with-mock/components/ExtendedMenuItem.tsx +22 -0
- package/templates/with-mock/components/ExtendedRichtext.tsx +15 -0
- package/templates/with-mock/components/ExtendedSubtitle.tsx +10 -0
- package/templates/with-mock/components/ExtendedTitle.tsx +10 -0
- package/templates/with-mock/components/server-components.ts +8 -0
- package/templates/with-mock/fixtures/collection.json +28 -0
- package/templates/with-mock/fixtures/menu.json +29 -0
- package/templates/with-mock/fixtures/pages/chi-siamo.json +37 -0
- package/templates/with-mock/fixtures/pages/contatti.json +33 -0
- package/templates/with-mock/fixtures/pages/home.json +62 -0
- package/templates/with-mock/fixtures/pages/index.json +17 -0
- package/templates/with-mock/lib/data-fetching.ts +205 -0
- package/templates/with-mock/lib/diggo-config.ts +48 -0
- package/templates/with-mock/next-env.d.ts +2 -0
- package/templates/with-mock/next.config.ts +7 -0
- package/templates/with-mock/package.json +27 -0
- package/templates/with-mock/scripts/mock-server.ts +231 -0
- package/templates/with-mock/tsconfig.json +23 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
PagePayload,
|
|
3
|
+
CollectionPayload,
|
|
4
|
+
MenuPayload,
|
|
5
|
+
} from '@digitalygo/diggocms-sdk-core';
|
|
6
|
+
import { sdkConfig, isMockMode, hasBaseUrl } from './diggo-config';
|
|
7
|
+
|
|
8
|
+
interface PageIndexItem {
|
|
9
|
+
id: string;
|
|
10
|
+
slug: string;
|
|
11
|
+
title: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const DEFAULT_MOCK_API_URL = 'http://localhost:3001';
|
|
15
|
+
|
|
16
|
+
function getMockApiBaseUrl(): string {
|
|
17
|
+
return process.env.MOCK_API_URL ?? DEFAULT_MOCK_API_URL;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function fetchFromMockApi<T>(endpoint: string): Promise<T | null> {
|
|
21
|
+
try {
|
|
22
|
+
const baseUrl = getMockApiBaseUrl();
|
|
23
|
+
const response = await fetch(`${baseUrl}${endpoint}`);
|
|
24
|
+
|
|
25
|
+
if (!response.ok) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return (await response.json()) as T;
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function loadPagesIndex(): Promise<PageIndexItem[]> {
|
|
36
|
+
const index = await fetchFromMockApi<PageIndexItem[]>('/pages');
|
|
37
|
+
return index ?? [];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface FetchPageResult {
|
|
41
|
+
page: PagePayload | null;
|
|
42
|
+
error: string | null;
|
|
43
|
+
usingMock: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface FetchCollectionResult {
|
|
47
|
+
collection: CollectionPayload | null;
|
|
48
|
+
error: string | null;
|
|
49
|
+
usingMock: boolean;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface FetchMenuResult {
|
|
53
|
+
menu: MenuPayload | null;
|
|
54
|
+
error: string | null;
|
|
55
|
+
usingMock: boolean;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function normalizeSlug(slug: string): string {
|
|
59
|
+
if (slug === 'home' || slug === '') {
|
|
60
|
+
return '';
|
|
61
|
+
}
|
|
62
|
+
return slug;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function loadPageFromMockApi(slug: string): Promise<PagePayload | null> {
|
|
66
|
+
const normalizedSlug = normalizeSlug(slug);
|
|
67
|
+
const index = await loadPagesIndex();
|
|
68
|
+
|
|
69
|
+
const pageExists = index.some(
|
|
70
|
+
(item) => normalizeSlug(item.slug) === normalizedSlug
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
if (!pageExists) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const endpoint =
|
|
78
|
+
normalizedSlug === '' ? '/pages/home' : `/pages/${normalizedSlug}`;
|
|
79
|
+
|
|
80
|
+
return fetchFromMockApi<PagePayload>(endpoint);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function fetchPageData(slug: string): Promise<FetchPageResult> {
|
|
84
|
+
if (isMockMode()) {
|
|
85
|
+
const page = await loadPageFromMockApi(slug);
|
|
86
|
+
return {
|
|
87
|
+
page,
|
|
88
|
+
error: page ? null : 'Failed to load mock page from API',
|
|
89
|
+
usingMock: true,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!hasBaseUrl()) {
|
|
94
|
+
return {
|
|
95
|
+
page: null,
|
|
96
|
+
error: 'BASE_URL is not configured and MOCK mode is not enabled.',
|
|
97
|
+
usingMock: false,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
const response = await fetch(`${sdkConfig.baseUrl}/pages/${slug}`);
|
|
103
|
+
|
|
104
|
+
if (!response.ok) {
|
|
105
|
+
return {
|
|
106
|
+
page: null,
|
|
107
|
+
error: `Failed to fetch page: ${response.statusText}`,
|
|
108
|
+
usingMock: false,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const page = (await response.json()) as PagePayload;
|
|
113
|
+
return { page, error: null, usingMock: false };
|
|
114
|
+
} catch {
|
|
115
|
+
return {
|
|
116
|
+
page: null,
|
|
117
|
+
error: 'Error connecting to CMS',
|
|
118
|
+
usingMock: false,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export async function fetchCollectionData(
|
|
124
|
+
type: string
|
|
125
|
+
): Promise<FetchCollectionResult> {
|
|
126
|
+
if (isMockMode()) {
|
|
127
|
+
const collection = await fetchFromMockApi<CollectionPayload>(
|
|
128
|
+
`/collections/${type}`
|
|
129
|
+
);
|
|
130
|
+
return {
|
|
131
|
+
collection,
|
|
132
|
+
error: collection ? null : 'Failed to load mock collection from API',
|
|
133
|
+
usingMock: true,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!hasBaseUrl()) {
|
|
138
|
+
return {
|
|
139
|
+
collection: null,
|
|
140
|
+
error: 'BASE_URL is not configured and MOCK mode is not enabled.',
|
|
141
|
+
usingMock: false,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
const response = await fetch(`${sdkConfig.baseUrl}/collections/${type}`);
|
|
147
|
+
|
|
148
|
+
if (!response.ok) {
|
|
149
|
+
return {
|
|
150
|
+
collection: null,
|
|
151
|
+
error: `Failed to fetch collection: ${response.statusText}`,
|
|
152
|
+
usingMock: false,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const collection = (await response.json()) as CollectionPayload;
|
|
157
|
+
return { collection, error: null, usingMock: false };
|
|
158
|
+
} catch {
|
|
159
|
+
return {
|
|
160
|
+
collection: null,
|
|
161
|
+
error: 'Error connecting to CMS',
|
|
162
|
+
usingMock: false,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export async function fetchMenuData(key: string): Promise<FetchMenuResult> {
|
|
168
|
+
if (isMockMode()) {
|
|
169
|
+
const menu = await fetchFromMockApi<MenuPayload>(`/menus/${key}`);
|
|
170
|
+
return {
|
|
171
|
+
menu,
|
|
172
|
+
error: menu ? null : 'Failed to load mock menu from API',
|
|
173
|
+
usingMock: true,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (!hasBaseUrl()) {
|
|
178
|
+
return {
|
|
179
|
+
menu: null,
|
|
180
|
+
error: 'BASE_URL is not configured and MOCK mode is not enabled.',
|
|
181
|
+
usingMock: false,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
const response = await fetch(`${sdkConfig.baseUrl}/menus/${key}`);
|
|
187
|
+
|
|
188
|
+
if (!response.ok) {
|
|
189
|
+
return {
|
|
190
|
+
menu: null,
|
|
191
|
+
error: `Failed to fetch menu: ${response.statusText}`,
|
|
192
|
+
usingMock: false,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const menu = (await response.json()) as MenuPayload;
|
|
197
|
+
return { menu, error: null, usingMock: false };
|
|
198
|
+
} catch {
|
|
199
|
+
return {
|
|
200
|
+
menu: null,
|
|
201
|
+
error: 'Error connecting to CMS',
|
|
202
|
+
usingMock: false,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { ComponentRegistry, NavigationRegistry, DiggoConfig } from '@digitalygo/diggocms-sdk-core';
|
|
2
|
+
import {
|
|
3
|
+
ExtendedTitle,
|
|
4
|
+
ExtendedSubtitle,
|
|
5
|
+
ExtendedImage,
|
|
6
|
+
ExtendedRichtext,
|
|
7
|
+
ExtendedCard,
|
|
8
|
+
ExtendedMenuItem,
|
|
9
|
+
ExtendedMenuContainer,
|
|
10
|
+
} from '@/components/server-components';
|
|
11
|
+
|
|
12
|
+
export const componentsRegistry: ComponentRegistry = {
|
|
13
|
+
subtitle: ExtendedSubtitle,
|
|
14
|
+
image: ExtendedImage,
|
|
15
|
+
richtext: ExtendedRichtext,
|
|
16
|
+
card: ExtendedCard,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export const navigationRegistry: NavigationRegistry = {
|
|
20
|
+
MenuItem: ExtendedMenuItem,
|
|
21
|
+
MenuContainer: ExtendedMenuContainer,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const isMock = process.env.MOCK === '1';
|
|
25
|
+
const baseUrl = process.env.BASE_URL;
|
|
26
|
+
const mockApiUrl = process.env.MOCK_API_URL;
|
|
27
|
+
|
|
28
|
+
function getBaseUrl(): string {
|
|
29
|
+
if (isMock) {
|
|
30
|
+
return mockApiUrl ?? 'http://localhost:3001';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return baseUrl ?? 'http://placeholder.local';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const sdkConfig: DiggoConfig = {
|
|
37
|
+
baseUrl: getBaseUrl(),
|
|
38
|
+
components: componentsRegistry,
|
|
39
|
+
navigation: navigationRegistry,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export function isMockMode(): boolean {
|
|
43
|
+
return isMock;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function hasBaseUrl(): boolean {
|
|
47
|
+
return !!baseUrl;
|
|
48
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "my-diggocms-app",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "next dev",
|
|
8
|
+
"build": "next build",
|
|
9
|
+
"start": "next start",
|
|
10
|
+
"lint": "next lint",
|
|
11
|
+
"typecheck": "tsc --noEmit",
|
|
12
|
+
"mock:api": "bun run scripts/mock-server.ts",
|
|
13
|
+
"dev:mock": "bun run mock:api & next dev"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@digitalygo/diggocms-sdk-core": "^0.1.0",
|
|
17
|
+
"next": "^16.0.0",
|
|
18
|
+
"react": "^18.3.0",
|
|
19
|
+
"react-dom": "^18.3.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/node": "^20.0.0",
|
|
23
|
+
"@types/react": "^18.3.0",
|
|
24
|
+
"@types/react-dom": "^18.3.0",
|
|
25
|
+
"typescript": "^5.5.0"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Standalone mock API server for DiggoCMS SDK.
|
|
4
|
+
* Serves fixtures as REST API endpoints for local development.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createServer, type IncomingMessage, type ServerResponse } from 'http';
|
|
8
|
+
import { readFile } from 'fs/promises';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
|
|
12
|
+
const PORT = Number(process.env.PORT) || 3001;
|
|
13
|
+
const HOST = process.env.HOST || 'localhost';
|
|
14
|
+
|
|
15
|
+
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
|
16
|
+
const fixturesPath = join(__dirname, '..', 'fixtures');
|
|
17
|
+
const pagesPath = join(fixturesPath, 'pages');
|
|
18
|
+
|
|
19
|
+
type Handler = (req: IncomingMessage, res: ServerResponse, params: Record<string, string>) => Promise<string>;
|
|
20
|
+
|
|
21
|
+
interface Route {
|
|
22
|
+
pattern: RegExp;
|
|
23
|
+
method: string;
|
|
24
|
+
handler: Handler;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function sendJson(res: ServerResponse, statusCode: number, data: unknown): string {
|
|
28
|
+
const jsonString = JSON.stringify(data);
|
|
29
|
+
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
30
|
+
res.end(JSON.stringify(data, null, 2));
|
|
31
|
+
return jsonString;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function logRequest(method: string, url: string, statusCode: number, body?: string): void {
|
|
35
|
+
const timestamp = new Date().toISOString();
|
|
36
|
+
const statusColor = statusCode >= 400 ? '\x1b[31m' : statusCode >= 300 ? '\x1b[33m' : '\x1b[32m';
|
|
37
|
+
const resetColor = '\x1b[0m';
|
|
38
|
+
const dimColor = '\x1b[2m';
|
|
39
|
+
const cyanColor = '\x1b[36m';
|
|
40
|
+
const yellowColor = '\x1b[33m';
|
|
41
|
+
|
|
42
|
+
let logLine = `${dimColor}${timestamp}${resetColor} | ${cyanColor}${method}${resetColor} ${url} | ${statusColor}${statusCode}${resetColor}`;
|
|
43
|
+
|
|
44
|
+
if (body) {
|
|
45
|
+
const preview = body.length > 100 ? body.slice(0, 100) + '...' : body;
|
|
46
|
+
logLine += ` | ${yellowColor}Body: ${preview}${resetColor}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
console.log(logLine);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function readJsonFile<T>(filepath: string): Promise<T | null> {
|
|
53
|
+
try {
|
|
54
|
+
const content = await readFile(filepath, 'utf-8');
|
|
55
|
+
return JSON.parse(content) as T;
|
|
56
|
+
} catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function matchRoute(path: string, method: string, routes: Route[]): { handler: Handler; params: Record<string, string> } | null {
|
|
62
|
+
for (const route of routes) {
|
|
63
|
+
if (route.method !== method) continue;
|
|
64
|
+
const match = route.pattern.exec(path);
|
|
65
|
+
if (match) {
|
|
66
|
+
const params: Record<string, string> = {};
|
|
67
|
+
const groups = match.groups ?? {};
|
|
68
|
+
for (const [key, value] of Object.entries(groups)) {
|
|
69
|
+
if (value !== undefined) {
|
|
70
|
+
params[key] = decodeURIComponent(value);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return { handler: route.handler, params };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const routes: Route[] = [
|
|
80
|
+
{
|
|
81
|
+
pattern: /^\/pages\/?$/,
|
|
82
|
+
method: 'GET',
|
|
83
|
+
handler: async (_req, res) => {
|
|
84
|
+
const data = await readJsonFile<unknown[]>(join(pagesPath, 'index.json'));
|
|
85
|
+
if (data === null) {
|
|
86
|
+
return sendJson(res, 404, { error: 'Pages index not found' });
|
|
87
|
+
}
|
|
88
|
+
return sendJson(res, 200, data);
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
pattern: /^\/pages\/(?<slug>.+)$/,
|
|
93
|
+
method: 'GET',
|
|
94
|
+
handler: async (_req, res, params) => {
|
|
95
|
+
const slug = params.slug ?? '';
|
|
96
|
+
|
|
97
|
+
const index = await readJsonFile<Array<{ id: string; slug: string; title: string }>>(
|
|
98
|
+
join(pagesPath, 'index.json')
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
if (index === null) {
|
|
102
|
+
return sendJson(res, 404, { error: 'Pages index not found' });
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const pageItem = index.find(
|
|
106
|
+
(item) => item.slug === slug || (slug === 'home' && item.slug === '')
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
if (!pageItem) {
|
|
110
|
+
return sendJson(res, 404, { error: `Page not found: ${slug}` });
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const filepath = pageItem.slug === '' ? 'home.json' : `${pageItem.id}.json`;
|
|
114
|
+
const data = await readJsonFile<unknown>(join(pagesPath, filepath));
|
|
115
|
+
|
|
116
|
+
if (data === null) {
|
|
117
|
+
return sendJson(res, 404, { error: `Page file not found: ${filepath}` });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return sendJson(res, 200, data);
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
pattern: /^\/collections\/(?<type>[^/]+)$/,
|
|
125
|
+
method: 'GET',
|
|
126
|
+
handler: async (_req, res) => {
|
|
127
|
+
const data = await readJsonFile<unknown>(join(fixturesPath, 'collection.json'));
|
|
128
|
+
if (data === null) {
|
|
129
|
+
return sendJson(res, 404, { error: 'Collection not found' });
|
|
130
|
+
}
|
|
131
|
+
return sendJson(res, 200, data);
|
|
132
|
+
},
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
pattern: /^\/menus\/(?<key>[^/]+)$/,
|
|
136
|
+
method: 'GET',
|
|
137
|
+
handler: async (_req, res) => {
|
|
138
|
+
const data = await readJsonFile<unknown>(join(fixturesPath, 'menu.json'));
|
|
139
|
+
if (data === null) {
|
|
140
|
+
return sendJson(res, 404, { error: 'Menu not found' });
|
|
141
|
+
}
|
|
142
|
+
return sendJson(res, 200, data);
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
pattern: /^\/$/,
|
|
147
|
+
method: 'GET',
|
|
148
|
+
handler: async (_req, res) => {
|
|
149
|
+
return sendJson(res, 200, {
|
|
150
|
+
name: 'DiggoCMS Mock API',
|
|
151
|
+
version: '1.0.0',
|
|
152
|
+
endpoints: [
|
|
153
|
+
'GET /pages',
|
|
154
|
+
'GET /pages/:slug',
|
|
155
|
+
'GET /collections/:type',
|
|
156
|
+
'GET /menus/:key',
|
|
157
|
+
],
|
|
158
|
+
});
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
];
|
|
162
|
+
|
|
163
|
+
const server = createServer(async (req, res) => {
|
|
164
|
+
const url = new URL(req.url ?? '/', `http://${req.headers.host}`);
|
|
165
|
+
const pathname = url.pathname;
|
|
166
|
+
|
|
167
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
168
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
|
|
169
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
170
|
+
|
|
171
|
+
if (req.method === 'OPTIONS') {
|
|
172
|
+
res.writeHead(204);
|
|
173
|
+
res.end();
|
|
174
|
+
logRequest(req.method ?? 'OPTIONS', pathname, 204, '');
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const routeMatch = matchRoute(pathname, req.method ?? 'GET', routes);
|
|
179
|
+
|
|
180
|
+
if (routeMatch) {
|
|
181
|
+
let responseBody = '';
|
|
182
|
+
try {
|
|
183
|
+
responseBody = await routeMatch.handler(req, res, routeMatch.params);
|
|
184
|
+
} catch (error) {
|
|
185
|
+
console.error('Error handling request:', error);
|
|
186
|
+
responseBody = sendJson(res, 500, { error: 'Internal server error' });
|
|
187
|
+
}
|
|
188
|
+
const statusCode = res.statusCode ?? 200;
|
|
189
|
+
logRequest(req.method ?? 'GET', pathname, statusCode, responseBody);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const responseBody = sendJson(res, 404, { error: 'Not found', path: pathname });
|
|
194
|
+
logRequest(req.method ?? 'GET', pathname, 404, responseBody);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
server.listen(PORT, HOST, () => {
|
|
198
|
+
console.log(`
|
|
199
|
+
┌─────────────────────────────────────────────────────┐
|
|
200
|
+
│ │
|
|
201
|
+
│ DiggoCMS Mock API Server │
|
|
202
|
+
│ │
|
|
203
|
+
│ Running at: http://${HOST}:${PORT} │
|
|
204
|
+
│ │
|
|
205
|
+
│ Endpoints: │
|
|
206
|
+
│ • GET /pages → List all pages │
|
|
207
|
+
│ • GET /pages/:slug → Get page by slug │
|
|
208
|
+
│ • GET /collections/:type → Get collection │
|
|
209
|
+
│ • GET /menus/:key → Get menu by key │
|
|
210
|
+
│ │
|
|
211
|
+
│ Environment: │
|
|
212
|
+
│ • PORT=${PORT} │
|
|
213
|
+
│ • HOST=${HOST} │
|
|
214
|
+
│ │
|
|
215
|
+
└─────────────────────────────────────────────────────┘
|
|
216
|
+
`);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
process.on('SIGINT', () => {
|
|
220
|
+
console.log('\n\nShutting down mock server...');
|
|
221
|
+
server.close(() => {
|
|
222
|
+
console.log('Mock server stopped.');
|
|
223
|
+
process.exit(0);
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
process.on('SIGTERM', () => {
|
|
228
|
+
server.close(() => {
|
|
229
|
+
process.exit(0);
|
|
230
|
+
});
|
|
231
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["dom", "dom.iterable", "ES2022"],
|
|
5
|
+
"allowJs": true,
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"module": "ESNext",
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"jsx": "preserve",
|
|
15
|
+
"incremental": true,
|
|
16
|
+
"plugins": [{ "name": "next" }],
|
|
17
|
+
"paths": {
|
|
18
|
+
"@/*": ["./*"]
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
22
|
+
"exclude": ["node_modules"]
|
|
23
|
+
}
|