@raketa-cloud/admin 1.2.0 → 1.4.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/package.json +1 -1
- package/src/HonoAdapter.js +304 -169
- package/src/filters.js +17 -0
- package/src/handlers/article_revisions/ArticleRevisionsHandler.js +8 -0
- package/src/handlers/articles/ArticlesHandler.js +12 -1
- package/src/handlers/event_revisions/EventRevisionsHandler.js +8 -0
- package/src/handlers/events/EventsHandler.js +13 -2
- package/src/handlers/page_revisions/PageRevisionsHandler.js +8 -0
- package/src/handlers/pages/PagesHandler.js +12 -1
- package/src/index.js +1 -0
- package/src/routeCollection.js +14 -83
- package/src/routeMember.js +10 -33
- package/src/routeNestedCollection.js +19 -47
- package/src/routeNestedMember.js +14 -40
- package/src/tests/Adapter.test.js +4 -4
- package/src/tests/HonoAdapter.filter.test.js +40 -0
- package/src/tests/HonoAdapter.test.js +110 -9
- package/src/tests/RaketaContext.test.js +65 -0
- package/src/tests/cipher.test.js +73 -0
- package/src/tests/filters.test.js +97 -0
- package/src/tests/handlers/article_revisions/ArticleRevisionsHandler.test/create.test.js +43 -35
- package/src/tests/handlers/articles/ArticlesHandler.test/show.test.js +12 -1
- package/src/tests/handlers/event_revisions/EventRevisionsHandler.test/create.test.js +41 -33
- package/src/tests/handlers/events/EventsHandler.test/show.test.js +12 -1
- package/src/tests/handlers/page_revisions/PageRevisionsHandler.test/create.test.js +41 -27
- package/src/tests/handlers/pages/PagesHandler.test/show.test.js +12 -1
- package/src/tests/routeCollection.test.js +0 -92
- package/src/tests/routeMember.test.js +0 -6
- package/src/tests/routeNestedCollection.test.js +0 -14
- package/src/tests/routeNestedMember.test.js +0 -6
- package/src/tests/utils.test.js +70 -0
- package/src/views/articles/_shared/_history_toolbar.ejs +2 -2
- package/src/views/articles/_shared/_toolbar.ejs +38 -26
- package/src/views/articles/_shared/history_table.ejs +6 -0
- package/src/views/events/_shared/_history_toolbar.ejs +2 -2
- package/src/views/events/_shared/_toolbar.ejs +38 -26
- package/src/views/events/_shared/history_table.ejs +6 -0
- package/src/views/pages/_shared/_history_toolbar.ejs +2 -2
- package/src/views/pages/_shared/_toolbar.ejs +38 -26
- package/src/views/pages/_shared/history_table.ejs +6 -0
- package/src/Adapter.js +0 -265
package/package.json
CHANGED
package/src/HonoAdapter.js
CHANGED
|
@@ -1,54 +1,241 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import ejs from "ejs";
|
|
2
4
|
import Logger from "./Logger";
|
|
5
|
+
import currentPath from "./currentPath.cjs";
|
|
6
|
+
import FormBuilder from "./helpers/FormBuilder";
|
|
7
|
+
import HTMLBuilder from "./helpers/HTMLBuilder";
|
|
8
|
+
import Inflector from "./helpers/Inflector";
|
|
3
9
|
import RaketaContext from "./RaketaContext";
|
|
4
10
|
import setupAuthentication from "./setupAuthentication";
|
|
11
|
+
import form_for from "./helpers/form_for";
|
|
12
|
+
import history from "./helpers/historyUtils";
|
|
13
|
+
import pagination from "./helpers/pagination_helper";
|
|
14
|
+
import sort_button from "./helpers/sort_button";
|
|
15
|
+
import utils from "./helpers/utils";
|
|
16
|
+
|
|
17
|
+
function escapeHtml(string) {
|
|
18
|
+
const escape = {
|
|
19
|
+
'"': '"',
|
|
20
|
+
'&': '&',
|
|
21
|
+
"'": ''',
|
|
22
|
+
'<': '<',
|
|
23
|
+
'>': '>'
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
return String(string).replace(/["&'<>]/g, char => escape[char]);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const renderError = (message, stack) => {
|
|
30
|
+
const errorCSS = `
|
|
31
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
32
|
+
html { font-family: sans-serif; font-size: 16px; padding: 2rem; background-color: pink; }
|
|
33
|
+
h1 { margin-bottom: 1rem; color: FireBrick; }
|
|
34
|
+
.fail-squid { font-size: 7rem; }
|
|
35
|
+
pre { padding: 1.5rem 2rem; border-radius: 0.5rem; background-color: black; color: white; line-height: 1.5; font-size: 1.25rem; }
|
|
36
|
+
`;
|
|
37
|
+
return `<style>${errorCSS}</style><div class="fail-squid">🦑</div><h1>${message}</h1><pre>${escapeHtml(stack)}</pre>`;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Convert to array if needed
|
|
41
|
+
const toArray = value => Array.isArray(value) ? value : [value].filter(Boolean);
|
|
42
|
+
|
|
43
|
+
// Handle nested form values
|
|
44
|
+
function putIn(result, key, value) {
|
|
45
|
+
const segments = key.split(/[\[\]]+/).filter(Boolean);
|
|
46
|
+
let current = result;
|
|
47
|
+
|
|
48
|
+
segments.forEach((segment, idx) => {
|
|
49
|
+
const isLastSegment = idx === segments.length - 1;
|
|
50
|
+
|
|
51
|
+
if (isLastSegment) {
|
|
52
|
+
// Set value at final segment
|
|
53
|
+
current[segment] = key.endsWith("[]") || Array.isArray(value)
|
|
54
|
+
? toArray(value)
|
|
55
|
+
: value;
|
|
56
|
+
} else {
|
|
57
|
+
// Create path to next segment
|
|
58
|
+
current[segment] ??= {};
|
|
59
|
+
current = current[segment];
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Normalize paths for consistent handling
|
|
65
|
+
const sanitizePath = filePath => path.normalize(path.join("/", ...filePath.split("/")));
|
|
66
|
+
|
|
67
|
+
// Find template path with fallbacks
|
|
68
|
+
function getPartialPath(partialPath, views, viewsFallback) {
|
|
69
|
+
const paths = [
|
|
70
|
+
sanitizePath(`${process.cwd()}/src/views/${views}/${partialPath}`),
|
|
71
|
+
sanitizePath(`${currentPath}/views/${views}/${partialPath}`),
|
|
72
|
+
viewsFallback && sanitizePath(`${currentPath}/views/${viewsFallback}/${partialPath}`)
|
|
73
|
+
].filter(Boolean);
|
|
74
|
+
|
|
75
|
+
// Return first existing path
|
|
76
|
+
const existingPath = paths.find(path => existsSync(path));
|
|
77
|
+
|
|
78
|
+
if (existingPath) return existingPath;
|
|
79
|
+
|
|
80
|
+
throw new Error(`Template not found: "${partialPath}"\nSearched in:\n${paths.join('\n')}`);
|
|
81
|
+
}
|
|
5
82
|
|
|
83
|
+
// Parse request body based on content type
|
|
6
84
|
async function parseBody(req) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return req.text();
|
|
15
|
-
default:
|
|
16
|
-
// That is an ok default for now
|
|
17
|
-
return req.parseBody();
|
|
18
|
-
}
|
|
85
|
+
const contentType = req.header("Content-type") || "";
|
|
86
|
+
|
|
87
|
+
if (contentType.includes("json")) return req.json();
|
|
88
|
+
if (contentType === "text/plain") return req.text();
|
|
89
|
+
|
|
90
|
+
// Default for form data and other content types
|
|
91
|
+
return req.parseBody();
|
|
19
92
|
}
|
|
20
93
|
|
|
94
|
+
// Convert body to request parameters
|
|
21
95
|
function bodyToParams(req, parsedBody, adapter) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
default:
|
|
27
|
-
return parsedBody;
|
|
96
|
+
const contentType = req.header("Content-type") || "";
|
|
97
|
+
|
|
98
|
+
if (contentType.includes("form-data") || contentType.includes("urlencoded")) {
|
|
99
|
+
return adapter.parseFormBody(parsedBody);
|
|
28
100
|
}
|
|
101
|
+
|
|
102
|
+
return parsedBody;
|
|
29
103
|
}
|
|
30
104
|
|
|
31
|
-
export default class HonoAdapter
|
|
105
|
+
export default class HonoAdapter {
|
|
32
106
|
constructor(router, config) {
|
|
33
|
-
super(router);
|
|
34
107
|
this.globalCtx = (ctx) => ctx;
|
|
35
108
|
this.router = router;
|
|
36
109
|
this.globalConfig = config;
|
|
110
|
+
this.globalBeforeFilters = [];
|
|
111
|
+
this.globalAfterFilters = [];
|
|
37
112
|
|
|
38
113
|
// TODO: Pass in a path to redirect to after login?
|
|
39
|
-
|
|
40
|
-
router,
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
});
|
|
114
|
+
if (config) {
|
|
115
|
+
setupAuthentication(router, config);
|
|
116
|
+
|
|
117
|
+
router.get("/", (c) => {
|
|
118
|
+
return c.redirect("/en");
|
|
119
|
+
});
|
|
120
|
+
}
|
|
47
121
|
}
|
|
48
122
|
|
|
49
123
|
setGlobalContext(globalCtx) {
|
|
50
124
|
this.globalCtx = globalCtx;
|
|
51
125
|
}
|
|
126
|
+
|
|
127
|
+
setBeforeFilter(pattern, filter) {
|
|
128
|
+
this.globalBeforeFilters.push({ pattern, filter });
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
setAfterFilter(pattern, filter) {
|
|
132
|
+
this.globalAfterFilters.push({ pattern, filter });
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
getActionPath(views, viewsFallback, action) {
|
|
136
|
+
const packagePath = viewsFallback
|
|
137
|
+
? `${currentPath}/views/${viewsFallback}/${action}.ejs`
|
|
138
|
+
: `${currentPath}/views/${views}/${action}.ejs`;
|
|
139
|
+
|
|
140
|
+
const appPath = `${process.cwd()}/src/views/${views}/${action}.ejs`;
|
|
141
|
+
|
|
142
|
+
return existsSync(appPath) ? appPath : packagePath;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
getHelpers(viewHelpersConfig) {
|
|
146
|
+
const t = new HTMLBuilder();
|
|
147
|
+
const f = new FormBuilder();
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
t,
|
|
151
|
+
f,
|
|
152
|
+
form_for: form_for(viewHelpersConfig),
|
|
153
|
+
history,
|
|
154
|
+
utils,
|
|
155
|
+
sort_button,
|
|
156
|
+
pagination,
|
|
157
|
+
Inflector,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async renderLayout(assigns, body) {
|
|
162
|
+
const layoutTemplate = `${currentPath}/views/layouts/application.ejs`;
|
|
163
|
+
|
|
164
|
+
return await ejs.renderFile(layoutTemplate, {
|
|
165
|
+
...assigns,
|
|
166
|
+
body,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
renderPartial(assigns, views, viewsFallback) {
|
|
171
|
+
return function render(relativePath, localAssigns) {
|
|
172
|
+
const partialPath = getPartialPath(relativePath, views, viewsFallback);
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
// Synchronously read the partial file as ejs does not support async/await
|
|
176
|
+
const partialTemplate = readFileSync(partialPath, "utf8");
|
|
177
|
+
const rendered = ejs.render(
|
|
178
|
+
partialTemplate,
|
|
179
|
+
{
|
|
180
|
+
...assigns,
|
|
181
|
+
...localAssigns,
|
|
182
|
+
},
|
|
183
|
+
{ filename: partialPath }
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
return rendered;
|
|
187
|
+
} catch (e) {
|
|
188
|
+
const error = `Error rendering:\n ${partialPath}`;
|
|
189
|
+
Logger.error(`${error}\n\n${e.stack}`);
|
|
190
|
+
|
|
191
|
+
return renderError(error, e.stack);
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async renderAction(
|
|
197
|
+
{ views, viewsFallback, action, assigns },
|
|
198
|
+
viewHelpersConfig = {}
|
|
199
|
+
) {
|
|
200
|
+
// Pick the parent here in the views so we can reuse stuff...
|
|
201
|
+
const fullTemplatePath = this.getActionPath(views, viewsFallback, action);
|
|
202
|
+
const renderAssigns = { ...this.getHelpers(viewHelpersConfig), ...assigns };
|
|
203
|
+
const renderPartialHelper = this.renderPartial(
|
|
204
|
+
renderAssigns,
|
|
205
|
+
views,
|
|
206
|
+
viewsFallback
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
// Synchronously read the full template file
|
|
211
|
+
const template = readFileSync(fullTemplatePath, "utf8");
|
|
212
|
+
|
|
213
|
+
// Render the full template with synchronous partial rendering
|
|
214
|
+
const rendered = ejs.render(
|
|
215
|
+
template,
|
|
216
|
+
{
|
|
217
|
+
...renderAssigns,
|
|
218
|
+
render_partial: renderPartialHelper,
|
|
219
|
+
},
|
|
220
|
+
{ filename: fullTemplatePath }
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
const fullPage = this.renderLayout(renderAssigns, rendered);
|
|
224
|
+
|
|
225
|
+
return fullPage;
|
|
226
|
+
} catch (e) {
|
|
227
|
+
const error = `Error rendering:\n ${fullTemplatePath}`;
|
|
228
|
+
Logger.error(`${error}\n\n${e.stack}`);
|
|
229
|
+
|
|
230
|
+
return renderError(error, e.stack);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
parseFormBody(body) {
|
|
235
|
+
const result = {};
|
|
236
|
+
Object.entries(body).forEach(([key, value]) => putIn(result, key, value));
|
|
237
|
+
return result;
|
|
238
|
+
}
|
|
52
239
|
|
|
53
240
|
async createContext(router, next, verb, resource, action) {
|
|
54
241
|
const body = await parseBody(router.req);
|
|
@@ -74,196 +261,144 @@ export default class HonoAdapter extends Adapter {
|
|
|
74
261
|
});
|
|
75
262
|
}
|
|
76
263
|
|
|
77
|
-
async createAction(
|
|
78
|
-
[verb, path, resource, views, action, handler, before_filter, after_filter],
|
|
79
|
-
resourceConfig
|
|
80
|
-
) {
|
|
264
|
+
async createAction([verb, path, resource, views, action, handler], resourceConfig) {
|
|
81
265
|
const method = this.getMethod(verb);
|
|
82
|
-
|
|
83
|
-
|
|
266
|
+
const { i18n } = this.globalConfig;
|
|
267
|
+
|
|
268
|
+
Object.keys(i18n.locales).forEach((currentLocale) => {
|
|
84
269
|
const completePath = `/${currentLocale}${path}`;
|
|
85
|
-
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
});
|
|
270
|
+
|
|
271
|
+
// Clean up existing routes with same path/verb
|
|
272
|
+
this.router.router.routes = this.router.router.routes.filter(route =>
|
|
273
|
+
route[0] !== verb || route[1] !== completePath
|
|
274
|
+
);
|
|
91
275
|
|
|
92
276
|
method(completePath, async (routerCtx, next) => {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
ctx.logger = Logger;
|
|
277
|
+
// Create context with request data
|
|
278
|
+
const ctx = await this.createContext(routerCtx, next, verb, resource, action);
|
|
279
|
+
|
|
280
|
+
// Set context properties
|
|
281
|
+
Object.assign(ctx, {
|
|
282
|
+
resourceConfig,
|
|
283
|
+
locales: i18n.locales,
|
|
284
|
+
defaultLocale: i18n.defaultLocale,
|
|
285
|
+
maxOpenRevisions: this.globalConfig?.maxOpenRevisions || 3,
|
|
286
|
+
locale: currentLocale,
|
|
287
|
+
remoteConfig: { ...this.globalConfig.remoteConfig },
|
|
288
|
+
logger: Logger
|
|
289
|
+
});
|
|
107
290
|
|
|
291
|
+
// Build context object
|
|
108
292
|
const _context = {
|
|
109
|
-
timezones:
|
|
293
|
+
timezones: i18n.timezones,
|
|
110
294
|
locales: ctx.locales,
|
|
111
295
|
locale: ctx.locale,
|
|
112
|
-
defaultLocale: ctx.defaultLocale,
|
|
296
|
+
defaultLocale: ctx.defaultLocale,
|
|
297
|
+
maxOpenRevisions: ctx.maxOpenRevisions,
|
|
113
298
|
path: ctx.req.path,
|
|
114
299
|
query: ctx.query || {},
|
|
115
|
-
layout: {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
resourceConfig: resourceConfig,
|
|
119
|
-
adminJwt: ctx.authentication.token,
|
|
300
|
+
layout: { ...this.globalConfig.layout },
|
|
301
|
+
resourceConfig,
|
|
302
|
+
adminJwt: ctx.authentication?.token,
|
|
120
303
|
remoteConfig: { ...this.globalConfig.remoteConfig },
|
|
121
|
-
globalConfig: this.globalConfig
|
|
304
|
+
globalConfig: this.globalConfig
|
|
122
305
|
};
|
|
123
306
|
|
|
124
|
-
ctx.logger.info(
|
|
125
|
-
|
|
126
|
-
);
|
|
127
|
-
|
|
307
|
+
ctx.logger.info(`${ctx.method} "${ctx.rawPath}" -> ${resource}/${action} | ${JSON.stringify(ctx.params)}`);
|
|
308
|
+
|
|
128
309
|
const assigns = {
|
|
129
310
|
beforeFilterAssigns: {},
|
|
130
311
|
actionAssigns: {},
|
|
131
|
-
afterFilterAssigns: {}
|
|
312
|
+
afterFilterAssigns: {}
|
|
132
313
|
};
|
|
133
314
|
|
|
134
315
|
// Initialize global context
|
|
135
316
|
ctx.initGlobalContext();
|
|
136
317
|
|
|
137
|
-
// Process before filters
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if (json) {
|
|
148
|
-
return routerCtx.json(json);
|
|
318
|
+
// Process global before filters
|
|
319
|
+
for (const { pattern, filter } of this.globalBeforeFilters) {
|
|
320
|
+
if (pattern.test(ctx.path)) {
|
|
321
|
+
const result = filter(ctx) || {};
|
|
322
|
+
|
|
323
|
+
if (result.redirect) return routerCtx.redirect(result.redirect);
|
|
324
|
+
if (result.json) return routerCtx.json(result.json);
|
|
325
|
+
|
|
326
|
+
Object.assign(assigns.beforeFilterAssigns, result.assigns || {});
|
|
149
327
|
}
|
|
150
|
-
|
|
151
|
-
assigns.beforeFilterAssigns = beforeFilterAssigns;
|
|
152
328
|
}
|
|
153
329
|
|
|
154
|
-
// Process action
|
|
155
|
-
const {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
json,
|
|
172
|
-
assigns: afterFilterAssigns = {},
|
|
173
|
-
} = after_filter(ctx) || {};
|
|
174
|
-
|
|
175
|
-
if (redirect) return routerCtx.redirect(redirect);
|
|
176
|
-
|
|
177
|
-
if (json) {
|
|
178
|
-
return routerCtx.json(json);
|
|
330
|
+
// Process action handler
|
|
331
|
+
const result = await handler(ctx, resourceConfig) || {};
|
|
332
|
+
assigns.actionAssigns = result.assigns || {};
|
|
333
|
+
|
|
334
|
+
if (result.headers) this.applyHeaders(result.headers, routerCtx);
|
|
335
|
+
if (result.redirect) return routerCtx.redirect(result.redirect);
|
|
336
|
+
if (result.json) return routerCtx.json(result.json);
|
|
337
|
+
|
|
338
|
+
// Process global after filters
|
|
339
|
+
for (const { pattern, filter } of this.globalAfterFilters) {
|
|
340
|
+
if (pattern.test(ctx.path)) {
|
|
341
|
+
const afterResult = filter(ctx) || {};
|
|
342
|
+
|
|
343
|
+
if (afterResult.redirect) return routerCtx.redirect(afterResult.redirect);
|
|
344
|
+
if (afterResult.json) return routerCtx.json(afterResult.json);
|
|
345
|
+
|
|
346
|
+
Object.assign(assigns.afterFilterAssigns, afterResult.assigns || {});
|
|
179
347
|
}
|
|
180
|
-
|
|
181
|
-
assigns.afterFilterAssigns = afterFilterAssigns;
|
|
182
348
|
}
|
|
183
349
|
|
|
350
|
+
// Merge all assigns
|
|
184
351
|
const fullAssigns = {
|
|
185
352
|
...ctx.assigns,
|
|
186
353
|
...assigns.beforeFilterAssigns,
|
|
187
354
|
...assigns.actionAssigns,
|
|
188
355
|
...assigns.afterFilterAssigns,
|
|
189
|
-
_context
|
|
356
|
+
_context
|
|
190
357
|
};
|
|
191
358
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
viewsFallback: resourceConfig.viewsFallback,
|
|
200
|
-
action,
|
|
201
|
-
assigns: fullAssigns,
|
|
202
|
-
},
|
|
203
|
-
this.globalConfig?.helpers || {}
|
|
204
|
-
);
|
|
359
|
+
// Render the view
|
|
360
|
+
const rendered = this.renderAction({
|
|
361
|
+
views,
|
|
362
|
+
viewsFallback: resourceConfig.viewsFallback,
|
|
363
|
+
action,
|
|
364
|
+
assigns: fullAssigns
|
|
365
|
+
}, this.globalConfig?.helpers || {});
|
|
205
366
|
|
|
206
367
|
return routerCtx.html(rendered);
|
|
207
368
|
});
|
|
208
369
|
});
|
|
209
370
|
}
|
|
210
371
|
|
|
372
|
+
// Single method to handle all route types
|
|
211
373
|
routeCollection(mapping, resourceConfig) {
|
|
212
|
-
mapping.forEach((handlerMap)
|
|
213
|
-
this.createAction(handlerMap, resourceConfig)
|
|
214
|
-
);
|
|
374
|
+
mapping.forEach(handlerMap => this.createAction(handlerMap, resourceConfig));
|
|
215
375
|
}
|
|
216
376
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
routeMember(mapping, resourceConfig) {
|
|
224
|
-
mapping.forEach((handlerMap) =>
|
|
225
|
-
this.createAction(handlerMap, resourceConfig)
|
|
226
|
-
);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
routeNestedMember(mapping, resourceConfig) {
|
|
230
|
-
mapping.forEach((handlerMap) =>
|
|
231
|
-
this.createAction(handlerMap, resourceConfig)
|
|
232
|
-
);
|
|
233
|
-
}
|
|
377
|
+
// Use the same implementation for all route types
|
|
378
|
+
routeNestedCollection = this.routeCollection;
|
|
379
|
+
routeMember = this.routeCollection;
|
|
380
|
+
routeNestedMember = this.routeCollection;
|
|
234
381
|
|
|
235
382
|
getMethod(verb) {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
);
|
|
383
|
+
const methods = {
|
|
384
|
+
GET: this.router.get,
|
|
385
|
+
POST: this.router.post,
|
|
386
|
+
PUT: this.router.put,
|
|
387
|
+
DELETE: this.router.delete,
|
|
388
|
+
};
|
|
389
|
+
return methods[verb] || this.router.get;
|
|
244
390
|
}
|
|
245
391
|
|
|
246
392
|
applyHeaders(headers, router) {
|
|
247
|
-
if (!headers)
|
|
248
|
-
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
for (const [key, value] of Object.entries(headers)) {
|
|
252
|
-
router.header(key, value);
|
|
253
|
-
}
|
|
393
|
+
if (!headers) return;
|
|
394
|
+
Object.entries(headers).forEach(([key, value]) => router.header(key, value));
|
|
254
395
|
}
|
|
255
396
|
|
|
256
397
|
formatQuery(queries) {
|
|
257
398
|
const result = {};
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
} else {
|
|
263
|
-
result[key] = value;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
return { ...result };
|
|
399
|
+
Object.entries(queries).forEach(([key, value]) => {
|
|
400
|
+
result[key] = value.length === 1 ? value[0] : value;
|
|
401
|
+
});
|
|
402
|
+
return result;
|
|
268
403
|
}
|
|
269
|
-
}
|
|
404
|
+
}
|
package/src/filters.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sets a global filter to be applied to routes matching the pattern
|
|
3
|
+
* @param {HonoAdapter} adapter - The adapter instance
|
|
4
|
+
* @param {RegExp} pattern - Route pattern to match
|
|
5
|
+
* @param {Function} filter - The filter function
|
|
6
|
+
*/
|
|
7
|
+
export const setBeforeFilter = (adapter, pattern, filter) =>
|
|
8
|
+
adapter.setBeforeFilter(pattern, filter);
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Sets a global after filter to be applied to routes matching the pattern
|
|
12
|
+
* @param {HonoAdapter} adapter - The adapter instance
|
|
13
|
+
* @param {RegExp} pattern - Route pattern to match
|
|
14
|
+
* @param {Function} filter - The filter function
|
|
15
|
+
*/
|
|
16
|
+
export const setAfterFilter = (adapter, pattern, filter) =>
|
|
17
|
+
adapter.setAfterFilter(pattern, filter);
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
const create = async (ctx) => {
|
|
2
2
|
const { parent_id } = ctx.params;
|
|
3
3
|
|
|
4
|
+
const article = await ctx.client.articles.find(parent_id, ctx.locale);
|
|
5
|
+
|
|
6
|
+
if (article.revisionsCount >= ctx.maxOpenRevisions) {
|
|
7
|
+
return {
|
|
8
|
+
redirect: `/${ctx.locale}/${ctx.resourceConfig.articleTypePlural}/${parent_id}?revisions_count_error=1`,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
4
12
|
const { errors, articleErrors } = await ctx.client.articleRevisions.create(
|
|
5
13
|
parent_id,
|
|
6
14
|
ctx.locale
|
|
@@ -26,7 +26,18 @@ const show = async (ctx) => {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
const revisions = await ctx.client.articleRevisions.all(id, ctx.locale);
|
|
29
|
-
const changes = await ctx.client.articleChanges.list(
|
|
29
|
+
const changes = await ctx.client.articleChanges.list(
|
|
30
|
+
{
|
|
31
|
+
"f:change_type": [
|
|
32
|
+
"revision_publish",
|
|
33
|
+
"revision_lock",
|
|
34
|
+
"revision_unlock",
|
|
35
|
+
"update",
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
id,
|
|
39
|
+
ctx.locale
|
|
40
|
+
);
|
|
30
41
|
|
|
31
42
|
return {
|
|
32
43
|
assigns: {
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
const create = async (ctx) => {
|
|
2
2
|
const { parent_id } = ctx.params;
|
|
3
3
|
|
|
4
|
+
const event = await ctx.client.events.find(parent_id, ctx.locale);
|
|
5
|
+
|
|
6
|
+
if (event.revisionsCount >= ctx.maxOpenRevisions) {
|
|
7
|
+
return {
|
|
8
|
+
redirect: `/${ctx.locale}/${ctx.resourceConfig.eventTypePlural}/${parent_id}?revisions_count_error=1`,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
4
12
|
const { errors, eventErrors } = await ctx.client.eventRevisions.create(
|
|
5
13
|
parent_id,
|
|
6
14
|
ctx.locale
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { toDateInput, toDateTimeInput } from "../../utils";
|
|
2
2
|
|
|
3
3
|
const index = async (ctx) => {
|
|
4
4
|
const items = await ctx.client.events.list(
|
|
@@ -26,7 +26,18 @@ const show = async (ctx) => {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
const revisions = await ctx.client.eventRevisions.all(id, ctx.locale);
|
|
29
|
-
const changes = await ctx.client.eventChanges.list(
|
|
29
|
+
const changes = await ctx.client.eventChanges.list(
|
|
30
|
+
{
|
|
31
|
+
"f:change_type": [
|
|
32
|
+
"revision_publish",
|
|
33
|
+
"revision_lock",
|
|
34
|
+
"revision_unlock",
|
|
35
|
+
"update",
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
id,
|
|
39
|
+
ctx.locale
|
|
40
|
+
);
|
|
30
41
|
|
|
31
42
|
return {
|
|
32
43
|
assigns: {
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
const create = async (ctx) => {
|
|
2
2
|
const { parent_id } = ctx.params;
|
|
3
3
|
|
|
4
|
+
const page = await ctx.client.pages.find(parent_id, ctx.locale);
|
|
5
|
+
|
|
6
|
+
if (page.revisionsCount >= ctx.maxOpenRevisions) {
|
|
7
|
+
return {
|
|
8
|
+
redirect: `/${ctx.locale}/${ctx.resourceConfig.pageTypePlural}/${parent_id}?revisions_count_error=1`,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
|
|
4
12
|
const { errors, pageErrors } = await ctx.client.pageRevisions.create(
|
|
5
13
|
parent_id,
|
|
6
14
|
ctx.locale
|