@raketa-cloud/admin 1.3.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.
@@ -0,0 +1,97 @@
1
+ import { setBeforeFilter, setAfterFilter } from "../filters";
2
+ import HonoAdapter from "../HonoAdapter";
3
+
4
+ class MockHonoAdapter {
5
+ constructor() {
6
+ this.globalBeforeFilters = [];
7
+ this.globalAfterFilters = [];
8
+ }
9
+
10
+ setBeforeFilter(pattern, filter) {
11
+ this.globalBeforeFilters.push({ pattern, filter });
12
+ }
13
+
14
+ setAfterFilter(pattern, filter) {
15
+ this.globalAfterFilters.push({ pattern, filter });
16
+ }
17
+ }
18
+
19
+ describe("filters", () => {
20
+ let adapter;
21
+
22
+ beforeEach(() => {
23
+ adapter = new MockHonoAdapter();
24
+ });
25
+
26
+ describe("setBeforeFilter", () => {
27
+ test("adds before filter to adapter", () => {
28
+ const pattern = /\/settings\/content/;
29
+ const filter = () => ({ assigns: { foo: "bar" } });
30
+
31
+ setBeforeFilter(adapter, pattern, filter);
32
+
33
+ expect(adapter.globalBeforeFilters).toHaveLength(1);
34
+ expect(adapter.globalBeforeFilters[0]).toEqual({
35
+ pattern,
36
+ filter
37
+ });
38
+ });
39
+
40
+ test("can add multiple before filters", () => {
41
+ const pattern1 = /\/settings\/content/;
42
+ const filter1 = () => ({ assigns: { foo: "bar" } });
43
+
44
+ const pattern2 = /\/pages/;
45
+ const filter2 = () => ({ assigns: { baz: "qux" } });
46
+
47
+ setBeforeFilter(adapter, pattern1, filter1);
48
+ setBeforeFilter(adapter, pattern2, filter2);
49
+
50
+ expect(adapter.globalBeforeFilters).toHaveLength(2);
51
+ expect(adapter.globalBeforeFilters[0]).toEqual({
52
+ pattern: pattern1,
53
+ filter: filter1
54
+ });
55
+ expect(adapter.globalBeforeFilters[1]).toEqual({
56
+ pattern: pattern2,
57
+ filter: filter2
58
+ });
59
+ });
60
+ });
61
+
62
+ describe("setAfterFilter", () => {
63
+ test("adds after filter to adapter", () => {
64
+ const pattern = /\/settings\/content/;
65
+ const filter = () => ({ assigns: { foo: "bar" } });
66
+
67
+ setAfterFilter(adapter, pattern, filter);
68
+
69
+ expect(adapter.globalAfterFilters).toHaveLength(1);
70
+ expect(adapter.globalAfterFilters[0]).toEqual({
71
+ pattern,
72
+ filter
73
+ });
74
+ });
75
+
76
+ test("can add multiple after filters", () => {
77
+ const pattern1 = /\/settings\/content/;
78
+ const filter1 = () => ({ assigns: { foo: "bar" } });
79
+
80
+ const pattern2 = /\/pages/;
81
+ const filter2 = () => ({ assigns: { baz: "qux" } });
82
+
83
+ setAfterFilter(adapter, pattern1, filter1);
84
+ setAfterFilter(adapter, pattern2, filter2);
85
+
86
+ expect(adapter.globalAfterFilters).toHaveLength(2);
87
+ expect(adapter.globalAfterFilters[0]).toEqual({
88
+ pattern: pattern1,
89
+ filter: filter1
90
+ });
91
+ expect(adapter.globalAfterFilters[1]).toEqual({
92
+ pattern: pattern2,
93
+ filter: filter2
94
+ });
95
+ });
96
+ });
97
+ });
@@ -10,18 +10,6 @@ const UsersHandler = {
10
10
  destroy: () => {},
11
11
  };
12
12
 
13
- const UsersHandlerWithFilters = {
14
- before_filter: () => {},
15
- after_filter: () => {},
16
- index: () => {},
17
- show: () => {},
18
- create_form: () => {},
19
- edit_form: () => {},
20
- create: () => {},
21
- update: () => {},
22
- destroy: () => {},
23
- };
24
-
25
13
  const honoAdapter = {
26
14
  routeCollection: (_mapping) => {},
27
15
  };
@@ -80,84 +68,4 @@ describe("routeCollection", () => {
80
68
  {}
81
69
  );
82
70
  });
83
-
84
- test("Valid handler mapping is passed to router with filters", () => {
85
- routeCollection(honoAdapter, "users", UsersHandlerWithFilters);
86
-
87
- expect(adapterSpy).toHaveBeenCalledWith(
88
- [
89
- [
90
- "GET",
91
- "/users",
92
- "users",
93
- "users",
94
- "index",
95
- UsersHandlerWithFilters.index,
96
- UsersHandlerWithFilters.before_filter,
97
- UsersHandlerWithFilters.after_filter,
98
- ],
99
- [
100
- "GET",
101
- "/users/new",
102
- "users",
103
- "users",
104
- "create_form",
105
- UsersHandlerWithFilters.create_form,
106
- UsersHandlerWithFilters.before_filter,
107
- UsersHandlerWithFilters.after_filter,
108
- ],
109
- [
110
- "GET",
111
- "/users/:id",
112
- "users",
113
- "users",
114
- "show",
115
- UsersHandlerWithFilters.show,
116
- UsersHandlerWithFilters.before_filter,
117
- UsersHandlerWithFilters.after_filter,
118
- ],
119
- [
120
- "GET",
121
- "/users/:id/edit",
122
- "users",
123
- "users",
124
- "edit_form",
125
- UsersHandlerWithFilters.edit_form,
126
- UsersHandlerWithFilters.before_filter,
127
- UsersHandlerWithFilters.after_filter,
128
- ],
129
- [
130
- "POST",
131
- "/users",
132
- "users",
133
- "users",
134
- "create_form",
135
- UsersHandlerWithFilters.create,
136
- UsersHandlerWithFilters.before_filter,
137
- UsersHandlerWithFilters.after_filter,
138
- ],
139
- [
140
- "POST",
141
- "/users/:id",
142
- "users",
143
- "users",
144
- "edit_form",
145
- UsersHandlerWithFilters.update,
146
- UsersHandlerWithFilters.before_filter,
147
- UsersHandlerWithFilters.after_filter,
148
- ],
149
- [
150
- "DELETE",
151
- "/users/:id",
152
- "users",
153
- "users",
154
- "destroy",
155
- UsersHandlerWithFilters.destroy,
156
- UsersHandlerWithFilters.before_filter,
157
- UsersHandlerWithFilters.after_filter,
158
- ],
159
- ],
160
- {}
161
- );
162
- });
163
71
  });
@@ -67,8 +67,6 @@ describe("routeMember", () => {
67
67
  "profile",
68
68
  "show",
69
69
  ProfileHandWithFiltersWithFilters.show,
70
- ProfileHandWithFiltersWithFilters.before_filter,
71
- ProfileHandWithFiltersWithFilters.after_filter,
72
70
  ],
73
71
  [
74
72
  "GET",
@@ -77,8 +75,6 @@ describe("routeMember", () => {
77
75
  "profile",
78
76
  "edit_form",
79
77
  ProfileHandWithFiltersWithFilters.edit_form,
80
- ProfileHandWithFiltersWithFilters.before_filter,
81
- ProfileHandWithFiltersWithFilters.after_filter,
82
78
  ],
83
79
  [
84
80
  "POST",
@@ -87,8 +83,6 @@ describe("routeMember", () => {
87
83
  "profile",
88
84
  "edit_form",
89
85
  ProfileHandWithFiltersWithFilters.update,
90
- ProfileHandWithFiltersWithFilters.before_filter,
91
- ProfileHandWithFiltersWithFilters.after_filter,
92
86
  ],
93
87
  ],
94
88
  {}
@@ -112,8 +112,6 @@ describe("routeNestedCollection", () => {
112
112
  "orders/line_items",
113
113
  "index",
114
114
  OrdersHandlerWithFilters.index,
115
- OrdersHandlerWithFilters.before_filter,
116
- OrdersHandlerWithFilters.after_filter,
117
115
  ],
118
116
  [
119
117
  "GET",
@@ -122,8 +120,6 @@ describe("routeNestedCollection", () => {
122
120
  "orders/line_items",
123
121
  "create_form",
124
122
  OrdersHandlerWithFilters.create_form,
125
- OrdersHandlerWithFilters.before_filter,
126
- OrdersHandlerWithFilters.after_filter,
127
123
  ],
128
124
  [
129
125
  "GET",
@@ -132,8 +128,6 @@ describe("routeNestedCollection", () => {
132
128
  "orders/line_items",
133
129
  "show",
134
130
  OrdersHandlerWithFilters.show,
135
- OrdersHandlerWithFilters.before_filter,
136
- OrdersHandlerWithFilters.after_filter,
137
131
  ],
138
132
  [
139
133
  "GET",
@@ -142,8 +136,6 @@ describe("routeNestedCollection", () => {
142
136
  "orders/line_items",
143
137
  "edit_form",
144
138
  OrdersHandlerWithFilters.edit_form,
145
- OrdersHandlerWithFilters.before_filter,
146
- OrdersHandlerWithFilters.after_filter,
147
139
  ],
148
140
  [
149
141
  "POST",
@@ -152,8 +144,6 @@ describe("routeNestedCollection", () => {
152
144
  "orders/line_items",
153
145
  "create_form",
154
146
  OrdersHandlerWithFilters.create,
155
- OrdersHandlerWithFilters.before_filter,
156
- OrdersHandlerWithFilters.after_filter,
157
147
  ],
158
148
  [
159
149
  "PUT",
@@ -162,8 +152,6 @@ describe("routeNestedCollection", () => {
162
152
  "orders/line_items",
163
153
  "update",
164
154
  OrdersHandlerWithFilters.update,
165
- OrdersHandlerWithFilters.before_filter,
166
- OrdersHandlerWithFilters.after_filter,
167
155
  ],
168
156
  [
169
157
  "DELETE",
@@ -172,8 +160,6 @@ describe("routeNestedCollection", () => {
172
160
  "orders/line_items",
173
161
  "destroy",
174
162
  OrdersHandlerWithFilters.destroy,
175
- OrdersHandlerWithFilters.before_filter,
176
- OrdersHandlerWithFilters.after_filter,
177
163
  ],
178
164
  ],
179
165
  {}
@@ -72,8 +72,6 @@ describe("routeNestedMember", () => {
72
72
  "artist/bio",
73
73
  "show",
74
74
  ArtistHandWithFiltersWithFilters.show,
75
- ArtistHandWithFiltersWithFilters.before_filter,
76
- ArtistHandWithFiltersWithFilters.after_filter,
77
75
  ],
78
76
  [
79
77
  "GET",
@@ -82,8 +80,6 @@ describe("routeNestedMember", () => {
82
80
  "artist/bio",
83
81
  "edit_form",
84
82
  ArtistHandWithFiltersWithFilters.edit_form,
85
- ArtistHandWithFiltersWithFilters.before_filter,
86
- ArtistHandWithFiltersWithFilters.after_filter,
87
83
  ],
88
84
  [
89
85
  "POST",
@@ -92,8 +88,6 @@ describe("routeNestedMember", () => {
92
88
  "artist/bio",
93
89
  "edit_form",
94
90
  ArtistHandWithFiltersWithFilters.update,
95
- ArtistHandWithFiltersWithFilters.before_filter,
96
- ArtistHandWithFiltersWithFilters.after_filter,
97
91
  ],
98
92
  ],
99
93
  {}
@@ -0,0 +1,70 @@
1
+ import {
2
+ toUTCISO8601String,
3
+ dateToUTCISO8601String,
4
+ toDateInput,
5
+ toDateTimeInput
6
+ } from "../utils";
7
+
8
+ describe("utils", () => {
9
+ describe("toUTCISO8601String", () => {
10
+ test("converts datetime-local input to UTC ISO8601 string", () => {
11
+ const inputDateTime = "2024-05-15T14:30";
12
+ const expected = "2024-05-15T14:30:00Z";
13
+
14
+ expect(toUTCISO8601String(inputDateTime)).toBe(expected);
15
+ });
16
+ });
17
+
18
+ describe("dateToUTCISO8601String", () => {
19
+ test("converts date input to UTC ISO8601 string with midnight time", () => {
20
+ const inputDate = "2024-05-15";
21
+ const expected = "2024-05-15T00:00:00Z";
22
+
23
+ expect(dateToUTCISO8601String(inputDate)).toBe(expected);
24
+ });
25
+ });
26
+
27
+ describe("toDateInput", () => {
28
+ test("extracts date part from ISO8601 string", () => {
29
+ const isoString = "2024-05-15T14:30:00Z";
30
+ const expected = "2024-05-15";
31
+
32
+ expect(toDateInput(isoString)).toBe(expected);
33
+ });
34
+
35
+ test("returns current date when no initial date is provided", () => {
36
+ // Mock current date
37
+ const mockDate = new Date(2024, 4, 15); // May 15, 2024
38
+ jest.spyOn(global, 'Date').mockImplementation(() => mockDate);
39
+
40
+ const expected = "2024-05-15";
41
+
42
+ expect(toDateInput()).toBe(expected);
43
+
44
+ // Restore Date
45
+ jest.restoreAllMocks();
46
+ });
47
+ });
48
+
49
+ describe("toDateTimeInput", () => {
50
+ test("removes Z suffix from ISO8601 string", () => {
51
+ const isoString = "2024-05-15T14:30:00Z";
52
+ const expected = "2024-05-15T14:30:00";
53
+
54
+ expect(toDateTimeInput(isoString)).toBe(expected);
55
+ });
56
+
57
+ test("returns current datetime when no initial date is provided", () => {
58
+ // Mock current date
59
+ const mockDate = new Date(2024, 4, 15, 14, 30); // May 15, 2024, 14:30
60
+ jest.spyOn(global, 'Date').mockImplementation(() => mockDate);
61
+
62
+ const expected = "2024-05-15T14:30";
63
+
64
+ expect(toDateTimeInput()).toBe(expected);
65
+
66
+ // Restore Date
67
+ jest.restoreAllMocks();
68
+ });
69
+ });
70
+ });
package/src/Adapter.js DELETED
@@ -1,265 +0,0 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import path from "node:path";
3
- import ejs from "ejs";
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";
9
- import form_for from "./helpers/form_for";
10
- import history from "./helpers/historyUtils";
11
- import pagination from "./helpers/pagination_helper";
12
- import sort_button from "./helpers/sort_button";
13
- import utils from "./helpers/utils";
14
-
15
- function escapeHtml(string) {
16
- var matchHtmlRegExp = /["'&<>]/;
17
-
18
- var str = "" + string;
19
- var match = matchHtmlRegExp.exec(str);
20
-
21
- if (!match) {
22
- return str;
23
- }
24
-
25
- var escape;
26
- var html = "";
27
- var index = 0;
28
- var lastIndex = 0;
29
-
30
- for (index = match.index; index < str.length; index++) {
31
- switch (str.charCodeAt(index)) {
32
- case 34: // "
33
- escape = "&quot;";
34
- break;
35
- case 38: // &
36
- escape = "&amp;";
37
- break;
38
- case 39: // '
39
- escape = "&#39;";
40
- break;
41
- case 60: // <
42
- escape = "&lt;";
43
- break;
44
- case 62: // >
45
- escape = "&gt;";
46
- break;
47
- default:
48
- continue;
49
- }
50
-
51
- if (lastIndex !== index) {
52
- html += str.substring(lastIndex, index);
53
- }
54
-
55
- lastIndex = index + 1;
56
- html += escape;
57
- }
58
-
59
- return lastIndex !== index ? html + str.substring(lastIndex, index) : html;
60
- }
61
-
62
- const renderError = (message, stack) => {
63
- const errorCSS = `
64
- * { box-sizing: border-box; margin: 0; padding: 0; }
65
- html { font-family: sans-serif; font-size: 16px; padding: 2rem; background-color: pink; }
66
- h1 { margin-bottom: 1rem; color: FireBrick; }
67
- .fail-squid { font-size: 7rem; }
68
- pre { padding: 1.5rem 2rem; border-radius: 0.5rem; background-color: black; color: white; line-height: 1.5; font-size: 1.25rem; }
69
- `;
70
- return `<style>${errorCSS}</style><div class="fail-squid">🦑</div><h1>${message}</h1><pre>${escapeHtml(stack)}</pre>`;
71
- };
72
-
73
- function toArray(value) {
74
- if (Array.isArray(value)) {
75
- return value;
76
- }
77
-
78
- return [value].filter(Boolean);
79
- }
80
-
81
- function putIn(result, key, value) {
82
- const segments = key.split(/[\[\]]+/).filter(Boolean);
83
- let current = result;
84
-
85
- segments.forEach((segment, idx) => {
86
- if (idx === segments.length - 1) {
87
- if (key.endsWith("[]") || Array.isArray(value)) {
88
- current[segment] = toArray(value);
89
- } else {
90
- current[segment] = value;
91
- }
92
- } else {
93
- if (!current[segment]) {
94
- current[segment] = {};
95
- }
96
-
97
- current = current[segment];
98
- }
99
- });
100
- }
101
-
102
- function sanitizePath(filePath) {
103
- return path.normalize(path.join(...["/", ...filePath.split("/")]));
104
- }
105
-
106
- function getPartialPath(partialPath, views, viewsFallback) {
107
- const packagePath = sanitizePath(
108
- `${currentPath}/views/${views}/${partialPath}`
109
- );
110
-
111
- const fallbackPackagePath = sanitizePath(
112
- `${currentPath}/views/${viewsFallback}/${partialPath}`
113
- );
114
-
115
- const appPath = sanitizePath(
116
- `${process.cwd()}/src/views/${views}/${partialPath}`
117
- );
118
-
119
- if (existsSync(appPath)) {
120
- return appPath;
121
- }
122
-
123
- if (existsSync(packagePath)) {
124
- return packagePath;
125
- }
126
-
127
- if (existsSync(fallbackPackagePath)) {
128
- return fallbackPackagePath;
129
- }
130
-
131
- throw new Error(
132
- `Looked for "${partialPath}" in the following places: \n${appPath}\n${packagePath}\n${fallbackPackagePath}\n`
133
- );
134
- }
135
-
136
- export default class Adapter {
137
- constructor(router) {
138
- this.router = router;
139
- }
140
-
141
- routeCollection(_resource, _mapping, _resourceConfig) {
142
- throw new Error("Implement in an adapter");
143
- }
144
-
145
- routeNestedCollection(_resource, _mapping, _resourceConfig) {
146
- throw new Error("Implement in an adapter");
147
- }
148
-
149
- routeMember(_resource, _mapping, _resourceConfig) {
150
- throw new Error("Implement in an adapter");
151
- }
152
-
153
- routeNestedMember(_resource, _mapping, _resourceConfig) {
154
- throw new Error("Implement in an adapter");
155
- }
156
-
157
- getActionPath(views, viewsFallback, action) {
158
- const packagePath = viewsFallback
159
- ? `${currentPath}/views/${viewsFallback}/${action}.ejs`
160
- : `${currentPath}/views/${views}/${action}.ejs`;
161
-
162
- const appPath = `${process.cwd()}/src/views/${views}/${action}.ejs`;
163
-
164
- return existsSync(appPath) ? appPath : packagePath;
165
- }
166
-
167
- getHelpers(viewHelpersConfig) {
168
- const t = new HTMLBuilder();
169
- const f = new FormBuilder();
170
-
171
- return {
172
- t,
173
- f,
174
- form_for: form_for(viewHelpersConfig),
175
- history,
176
- utils,
177
- sort_button,
178
- pagination,
179
- Inflector,
180
- };
181
- }
182
-
183
- async renderLayout(assigns, body) {
184
- const layoutTemplate = `${currentPath}/views/layouts/application.ejs`;
185
-
186
- return await ejs.renderFile(layoutTemplate, {
187
- ...assigns,
188
- body,
189
- });
190
- }
191
-
192
- renderPartial(assigns, views, viewsFallback) {
193
- return function render(relativePath, localAssigns) {
194
- const partialPath = getPartialPath(relativePath, views, viewsFallback);
195
-
196
- try {
197
- // Synchronously read the partial file as ejs does not support async/await
198
- const partialTemplate = readFileSync(partialPath, "utf8");
199
- const rendered = ejs.render(
200
- partialTemplate,
201
- {
202
- ...assigns,
203
- ...localAssigns,
204
- },
205
- { filename: partialPath }
206
- );
207
-
208
- return rendered;
209
- } catch (e) {
210
- const error = `Error rendering:\n ${partialPath}`;
211
- Logger.error(`${error}\n\n${e.stack}`);
212
-
213
- return renderError(error, e.stack);
214
- }
215
- };
216
- }
217
-
218
- async renderAction(
219
- { views, viewsFallback, action, assigns },
220
- viewHelpersConfig = {}
221
- ) {
222
- // Pick the parent here in the views so we can reuse stuff...
223
- const fullTemplatePath = this.getActionPath(views, viewsFallback, action);
224
- const renderAssigns = { ...this.getHelpers(viewHelpersConfig), ...assigns };
225
- const renderPartialHelper = this.renderPartial(
226
- renderAssigns,
227
- views,
228
- viewsFallback
229
- );
230
-
231
- try {
232
- // Synchronously read the full template file
233
- const template = readFileSync(fullTemplatePath, "utf8");
234
-
235
- // Render the full template with synchronous partial rendering
236
- const rendered = ejs.render(
237
- template,
238
- {
239
- ...renderAssigns,
240
- render_partial: renderPartialHelper,
241
- },
242
- { filename: fullTemplatePath }
243
- );
244
-
245
- const fullPage = this.renderLayout(renderAssigns, rendered);
246
-
247
- return fullPage;
248
- } catch (e) {
249
- const error = `Error rendering:\n ${fullTemplatePath}`;
250
- Logger.error(`${error}\n\n${e.stack}`);
251
-
252
- return renderError(error, e.stack);
253
- }
254
- }
255
-
256
- parseFormBody(body) {
257
- const result = {};
258
-
259
- for (const [key, value] of Object.entries(body)) {
260
- putIn(result, key, value);
261
- }
262
-
263
- return { ...result };
264
- }
265
- }