@raketa-cloud/admin 0.0.2 → 0.0.4

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.
Files changed (50) hide show
  1. package/babel.config.js +6 -0
  2. package/bin/tailwind +2 -0
  3. package/package.json +22 -29
  4. package/src/Adapter.js +105 -0
  5. package/src/HonoAdapter.js +120 -0
  6. package/src/RaketaContext.js +33 -0
  7. package/src/createNestedResource.js +83 -0
  8. package/src/createResource.js +83 -0
  9. package/src/index.js +3 -0
  10. package/src/styles/application.css +17 -0
  11. package/src/tests/HonoAdapter.test.js +33 -0
  12. package/src/tests/createNestedResource.test.js +150 -0
  13. package/src/tests/createResource.test.js +115 -0
  14. package/src/views/layouts/application.ejs +22 -0
  15. package/src/views/redirects/index.ejs +1 -0
  16. package/tailwind.config.js +8 -0
  17. package/tsconfig.json +14 -14
  18. package/tsconfig.lint.json +8 -0
  19. package/biome.json +0 -6
  20. package/jest.config.js +0 -15
  21. package/src/app.tsx +0 -19
  22. package/src/crud/login.tsx +0 -99
  23. package/src/crud/logout.tsx +0 -14
  24. package/src/crud/tags.tsx +0 -235
  25. package/src/index.ts +0 -1
  26. package/src/lib/dxp/private/tags.ts +0 -106
  27. package/src/lib/dxp/protocol.ts +0 -32
  28. package/src/lib/gateway/login.ts +0 -44
  29. package/src/lib/gateway/protocol.ts +0 -8
  30. package/src/lib/gqlEngine.ts +0 -17
  31. package/src/lib/logger.tsx +0 -149
  32. package/src/middleware/authentication.ts +0 -62
  33. package/src/middleware/http_logger.ts +0 -41
  34. package/src/middleware/logger.ts +0 -9
  35. package/src/shared/Layout.tsx +0 -50
  36. package/tests/integration/authentication/middleware.test.ts +0 -100
  37. package/tests/integration/login/create.test.ts +0 -117
  38. package/tests/integration/login/index.test.ts +0 -16
  39. package/tests/integration/logout/delete.test.ts +0 -17
  40. package/tests/integration/tags/create.test.ts +0 -66
  41. package/tests/integration/tags/delete.test.ts +0 -74
  42. package/tests/integration/tags/edit.test.ts +0 -43
  43. package/tests/integration/tags/index.test.ts +0 -66
  44. package/tests/integration/tags/new.test.ts +0 -16
  45. package/tests/integration/tags/update.test.ts +0 -87
  46. package/tests/support/helpers.ts +0 -53
  47. package/tests/support/setup.js +0 -9
  48. package/turbo/generators/config.ts +0 -30
  49. package/turbo/generators/templates/component.hbs +0 -14
  50. package/turbo.json +0 -9
@@ -0,0 +1,6 @@
1
+ module.exports = {
2
+ presets: [
3
+ ["@babel/preset-env", { targets: { node: "current" } }],
4
+ "@babel/preset-typescript",
5
+ ],
6
+ };
package/bin/tailwind ADDED
@@ -0,0 +1,2 @@
1
+ # npx tailwindcss -i ./src/styles/application.css -o ./dist/application.css --watch
2
+ npx tailwindcss -i ./src/styles/application.css -o ../../apps/admin/static/application.css --watch
package/package.json CHANGED
@@ -1,31 +1,24 @@
1
1
  {
2
- "name": "@raketa-cloud/admin",
3
- "version": "0.0.2",
4
- "private": false,
5
- "main": "./dist/index.js",
6
- "types": "./dist/index.d.ts",
7
- "scripts": {
8
- "build": "tsup src/index.ts --format cjs --loader \".js=jsx\"",
9
- "dev": "tsup src/index.ts --format cjs --loader \".js=jsx\" --watch",
10
- "lint": "biome lint --apply .",
11
- "test": "jest --verbose",
12
- "format": "prettier --write ."
13
- },
14
- "devDependencies": {
15
- "@jest/globals": "^29.7.0",
16
- "@repo/typescript-config": "*",
17
- "@turbo/gen": "^1.11.3",
18
- "@types/jest": "^29.5.8",
19
- "@types/node": "^20.10.6",
20
- "jest": "^29.7.0",
21
- "ts-jest": "^29.1.1",
22
- "tsup": "6.5.0",
23
- "typescript": "^5.3.3"
24
- },
25
- "dependencies": {
26
- "@biomejs/biome": "1.5.3",
27
- "graphql-request": "^6.1.0",
28
- "hono": "^3.12.3",
29
- "prettier": "^3.2.4"
30
- }
2
+ "name": "@raketa-cloud/admin",
3
+ "version": "0.0.4",
4
+ "main": "src/index.js",
5
+ "scripts": {
6
+ "dev": "bin/tailwind",
7
+ "test": "jest"
8
+ },
9
+ "devDependencies": {
10
+ "@babel/core": "^7.24.7",
11
+ "@babel/preset-env": "^7.24.7",
12
+ "@babel/preset-typescript": "^7.24.7",
13
+ "@repo/typescript-config": "*",
14
+ "@tailwindcss/forms": "^0.5.7",
15
+ "@tailwindcss/typography": "^0.5.13",
16
+ "@types/node": "^20.11.24",
17
+ "jest": "^29.7.0",
18
+ "tailwindcss": "^3.4.4",
19
+ "typescript": "^5.3.3"
20
+ },
21
+ "dependencies": {
22
+ "ejs": "^3.1.10"
23
+ }
31
24
  }
package/src/Adapter.js ADDED
@@ -0,0 +1,105 @@
1
+ const nodePath = require("node:path");
2
+ import { existsSync } from "node:fs";
3
+ import ejs from "ejs";
4
+
5
+ function escapeHtml(string) {
6
+ var matchHtmlRegExp = /["'&<>]/;
7
+
8
+ var str = "" + string;
9
+ var match = matchHtmlRegExp.exec(str);
10
+
11
+ if (!match) {
12
+ return str;
13
+ }
14
+
15
+ var escape;
16
+ var html = "";
17
+ var index = 0;
18
+ var lastIndex = 0;
19
+
20
+ for (index = match.index; index < str.length; index++) {
21
+ switch (str.charCodeAt(index)) {
22
+ case 34: // "
23
+ escape = "&quot;";
24
+ break;
25
+ case 38: // &
26
+ escape = "&amp;";
27
+ break;
28
+ case 39: // '
29
+ escape = "&#39;";
30
+ break;
31
+ case 60: // <
32
+ escape = "&lt;";
33
+ break;
34
+ case 62: // >
35
+ escape = "&gt;";
36
+ break;
37
+ default:
38
+ continue;
39
+ }
40
+
41
+ if (lastIndex !== index) {
42
+ html += str.substring(lastIndex, index);
43
+ }
44
+
45
+ lastIndex = index + 1;
46
+ html += escape;
47
+ }
48
+
49
+ return lastIndex !== index ? html + str.substring(lastIndex, index) : html;
50
+ }
51
+
52
+ const renderError = (message, stack) => {
53
+ const errorCSS = `
54
+ * { box-sizing: border-box; margin: 0; padding: 0; }
55
+ html { font-family: sans-serif; font-size: 16px; padding: 2rem; background-color: pink; }
56
+ h1 { margin-bottom: 1rem; color: FireBrick; }
57
+ .fail-squid { font-size: 7rem; }
58
+ pre { padding: 1.5rem 2rem; border-radius: 0.5rem; background-color: black; color: white; line-height: 1.5; font-size: 1.25rem; }
59
+ `;
60
+ return `<style>${errorCSS}</style><div class="fail-squid">🦑</div><h1>${message}</h1><pre>${escapeHtml(stack)}</pre>`;
61
+ };
62
+
63
+ const Logger = {
64
+ error: (msg) => {
65
+ console.error(`\x1b[31m🦑 ${msg}\x1b[0m`);
66
+ },
67
+ };
68
+
69
+ export default class Adapter {
70
+ constructor(router) {
71
+ this.router = router;
72
+ }
73
+
74
+ createResource(_resource, _mapping) {
75
+ throw new Error("Implement in an adapter");
76
+ }
77
+
78
+ getActionPath(resource, action) {
79
+ const packagePath = `${nodePath.dirname(__filename)}/views/${resource}/${action}.ejs`;
80
+ const appPath = `${process.cwd()}/src/views/${resource}/${action}.ejs`;
81
+
82
+ return existsSync(appPath) ? appPath : packagePath;
83
+ }
84
+
85
+ async renderLayout(assigns, body) {
86
+ const layoutTemplate = `${nodePath.dirname(__filename)}/views/layouts/application.ejs`;
87
+ return await ejs.renderFile(layoutTemplate, { ...assigns, body });
88
+ }
89
+
90
+ async renderAction({ resource, action, assigns }) {
91
+ const fullTemplatePath = this.getActionPath(resource, action);
92
+
93
+ try {
94
+ const rendered = await ejs.renderFile(fullTemplatePath, assigns);
95
+ const fullPage = await this.renderLayout(assigns, rendered);
96
+
97
+ return fullPage;
98
+ } catch (e) {
99
+ const error = `Error rendering:\n ${fullTemplatePath}`;
100
+ Logger.error(`${error}\n\n${e.stack}`);
101
+
102
+ return renderError(error, e.stack);
103
+ }
104
+ }
105
+ }
@@ -0,0 +1,120 @@
1
+ import Adapter from "./Adapter";
2
+ import RaketaContext from "./RaketaContext";
3
+
4
+ export default class HonoAdapter extends Adapter {
5
+ constructor(router) {
6
+ super(router);
7
+ this.router = router;
8
+ }
9
+
10
+ async createContext(router, next, verb, resource, action) {
11
+ return new RaketaContext({
12
+ router,
13
+ res: next,
14
+ req: router.req,
15
+ method: verb,
16
+ headers: router.req.header(),
17
+ rawPath: router.req.routePath,
18
+ path: router.req.path,
19
+ url: router.req.url,
20
+ action,
21
+ params: router.req.param(),
22
+ body: await router.req.parseBody(),
23
+ query: router.req.query(),
24
+ resource,
25
+ assigns: {},
26
+ });
27
+ }
28
+
29
+ createAction([
30
+ verb,
31
+ path,
32
+ resource,
33
+ action,
34
+ handler,
35
+ before_filter,
36
+ after_filter,
37
+ ]) {
38
+ const method = this.getMethod(verb);
39
+
40
+ method(path, async (router, next) => {
41
+ const ctx = await this.createContext(
42
+ router,
43
+ next,
44
+ verb,
45
+ resource,
46
+ action
47
+ );
48
+
49
+ console.info(
50
+ `\x1b[35m[${ctx.method}] ${ctx.rawPath}\x1b[0m -> ${resource}/${action} | ${JSON.stringify(ctx.params)}`
51
+ );
52
+
53
+ const assigns = {
54
+ beforeFilterAssigns: {},
55
+ actionAssigns: {},
56
+ afterFilterAssigns: {},
57
+ };
58
+
59
+ // Process before filters
60
+ if (before_filter) {
61
+ const { redirect, assigns: beforeFilterAssigns = {} } =
62
+ before_filter(ctx) || {};
63
+
64
+ if (redirect) return router.redirect(redirect);
65
+
66
+ assigns.beforeFilterAssigns = beforeFilterAssigns;
67
+ }
68
+
69
+ // Process action
70
+ const { redirect, assigns: actionAssigns = {} } = handler(ctx) || {};
71
+ assigns.actionAssigns = actionAssigns;
72
+
73
+ if (redirect) return router.redirect(redirect);
74
+
75
+ // Process after filters
76
+ if (after_filter) {
77
+ const { redirect, assigns: afterFilterAssigns = {} } =
78
+ after_filter(ctx) || {};
79
+
80
+ if (redirect) return router.redirect(redirect);
81
+
82
+ assigns.afterFilterAssigns = afterFilterAssigns;
83
+ }
84
+
85
+ const fullAssigns = {
86
+ ...ctx.assigns,
87
+ ...assigns.beforeFilterAssigns,
88
+ ...assigns.actionAssigns,
89
+ ...assigns.afterFilterAssigns,
90
+ };
91
+
92
+ const rendered = this.renderAction({
93
+ resource,
94
+ action,
95
+ assigns: fullAssigns,
96
+ });
97
+
98
+ return router.html(rendered);
99
+ });
100
+ }
101
+
102
+ createResource(mapping) {
103
+ mapping.forEach((handlerMap) => this.createAction(handlerMap));
104
+ }
105
+
106
+ createNestedResource(mapping) {
107
+ mapping.forEach((handlerMap) => this.createAction(handlerMap));
108
+ }
109
+
110
+ getMethod(verb) {
111
+ return (
112
+ {
113
+ GET: this.router.get,
114
+ POST: this.router.post,
115
+ PUT: this.router.put,
116
+ DELETE: this.router.delete,
117
+ }[verb] || this.router.get
118
+ );
119
+ }
120
+ }
@@ -0,0 +1,33 @@
1
+ export default class RaketaContext {
2
+ constructor({
3
+ router,
4
+ res,
5
+ req,
6
+ method,
7
+ headers,
8
+ rawPath,
9
+ path,
10
+ url,
11
+ action,
12
+ params,
13
+ query,
14
+ body,
15
+ resource,
16
+ assigns,
17
+ }) {
18
+ this.router = router;
19
+ this.res = res;
20
+ this.req = req;
21
+ this.method = method;
22
+ this.headers = headers;
23
+ this.rawPath = rawPath;
24
+ this.path = path;
25
+ this.url = url;
26
+ this.action = action;
27
+ this.params = params;
28
+ this.query = query;
29
+ this.body = body;
30
+ this.resource = resource;
31
+ this.assigns = assigns;
32
+ }
33
+ }
@@ -0,0 +1,83 @@
1
+ const createNestedResource = (adapter, parent, resource, handler) => {
2
+ const {
3
+ before_filter,
4
+ after_filter,
5
+ index,
6
+ show,
7
+ create_form,
8
+ edit_form,
9
+ create,
10
+ update,
11
+ destroy,
12
+ } = handler;
13
+
14
+ const mapping = [
15
+ [
16
+ "GET",
17
+ `/${parent}/:parent_id/${resource}`,
18
+ resource,
19
+ "index",
20
+ index,
21
+ before_filter,
22
+ after_filter,
23
+ ],
24
+ [
25
+ "GET",
26
+ `/${parent}/:parent_id/${resource}/new`,
27
+ resource,
28
+ "create_form",
29
+ create_form,
30
+ before_filter,
31
+ after_filter,
32
+ ],
33
+ [
34
+ "GET",
35
+ `/${resource}/:id`,
36
+ resource,
37
+ "show",
38
+ show,
39
+ before_filter,
40
+ after_filter,
41
+ ],
42
+ [
43
+ "GET",
44
+ `/${resource}/:id/edit`,
45
+ resource,
46
+ "edit_form",
47
+ edit_form,
48
+ before_filter,
49
+ after_filter,
50
+ ],
51
+ [
52
+ "POST",
53
+ `/orders/:parent_id/${resource}`,
54
+ resource,
55
+ "create",
56
+ create,
57
+ before_filter,
58
+ after_filter,
59
+ ],
60
+ [
61
+ "PUT",
62
+ `/${resource}/:id`,
63
+ resource,
64
+ "update",
65
+ update,
66
+ before_filter,
67
+ after_filter,
68
+ ],
69
+ [
70
+ "DELETE",
71
+ `/${resource}/:id`,
72
+ resource,
73
+ "destroy",
74
+ destroy,
75
+ before_filter,
76
+ after_filter,
77
+ ],
78
+ ];
79
+
80
+ adapter.createNestedResource(mapping);
81
+ };
82
+
83
+ export default createNestedResource;
@@ -0,0 +1,83 @@
1
+ const createResource = (adapter, resource, handler) => {
2
+ const {
3
+ before_filter,
4
+ after_filter,
5
+ index,
6
+ show,
7
+ create_form,
8
+ edit_form,
9
+ create,
10
+ update,
11
+ destroy,
12
+ } = handler;
13
+
14
+ const mapping = [
15
+ [
16
+ "GET",
17
+ `/${resource}`,
18
+ resource,
19
+ "index",
20
+ index,
21
+ before_filter,
22
+ after_filter,
23
+ ],
24
+ [
25
+ "GET",
26
+ `/${resource}/new`,
27
+ resource,
28
+ "create_form",
29
+ create_form,
30
+ before_filter,
31
+ after_filter,
32
+ ],
33
+ [
34
+ "GET",
35
+ `/${resource}/:id`,
36
+ resource,
37
+ "show",
38
+ show,
39
+ before_filter,
40
+ after_filter,
41
+ ],
42
+ [
43
+ "GET",
44
+ `/${resource}/:id/edit`,
45
+ resource,
46
+ "edit_form",
47
+ edit_form,
48
+ before_filter,
49
+ after_filter,
50
+ ],
51
+ [
52
+ "POST",
53
+ `/${resource}`,
54
+ resource,
55
+ "create",
56
+ create,
57
+ before_filter,
58
+ after_filter,
59
+ ],
60
+ [
61
+ "PUT",
62
+ `/${resource}/:id`,
63
+ resource,
64
+ "update",
65
+ update,
66
+ before_filter,
67
+ after_filter,
68
+ ],
69
+ [
70
+ "DELETE",
71
+ `/${resource}/:id`,
72
+ resource,
73
+ "destroy",
74
+ destroy,
75
+ before_filter,
76
+ after_filter,
77
+ ],
78
+ ];
79
+
80
+ adapter.createResource(mapping);
81
+ };
82
+
83
+ export default createResource;
package/src/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { default as HonoAdapter } from "./HonoAdapter";
2
+ export { default as createResource } from "./createResource";
3
+ export { default as createNestedResource } from "./createNestedResource";
@@ -0,0 +1,17 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ @layer components {
6
+ .btn-primary {
7
+ @apply bg-purple-500 text-white inline-block py-1 px-3 rounded;
8
+ }
9
+
10
+ .btn-secondary {
11
+ @apply bg-neutral-300 text-black inline-block py-1 px-3 rounded;
12
+ }
13
+
14
+ .btn-text {
15
+ @apply underline underline-offset-2;
16
+ }
17
+ }
@@ -0,0 +1,33 @@
1
+ import HonoAdapter from "../HonoAdapter";
2
+
3
+ class HonoMock {
4
+ get() {}
5
+ post() {}
6
+ put() {}
7
+ delete() {}
8
+ }
9
+
10
+ const app = new HonoMock();
11
+ const adapter = new HonoAdapter(app);
12
+
13
+ describe("HonoAdapter", () => {
14
+ test("getMethod returns Hono's get for invalid verbs", () => {
15
+ expect(adapter.getMethod("not existing")).toBe(app.get);
16
+ });
17
+
18
+ test("getMethod for GET", () => {
19
+ expect(adapter.getMethod("GET")).toBe(app.get);
20
+ });
21
+
22
+ test("getMethod for POST", () => {
23
+ expect(adapter.getMethod("POST")).toBe(app.post);
24
+ });
25
+
26
+ test("getMethod for PUT", () => {
27
+ expect(adapter.getMethod("PUT")).toBe(app.put);
28
+ });
29
+
30
+ test("getMethod for DELETE", () => {
31
+ expect(adapter.getMethod("DELETE")).toBe(app.delete);
32
+ });
33
+ });
@@ -0,0 +1,150 @@
1
+ import createNestedResource from "../createNestedResource";
2
+
3
+ const OrdersHandler = {
4
+ index: () => {},
5
+ show: () => {},
6
+ create_form: () => {},
7
+ edit_form: () => {},
8
+ create: () => {},
9
+ update: () => {},
10
+ destroy: () => {},
11
+ };
12
+
13
+ const OrdersHandlerWithFilters = {
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
+ const honoAdapter = {
26
+ createNestedResource: (_mapping) => {},
27
+ };
28
+
29
+ const adapterSpy = jest.spyOn(honoAdapter, "createNestedResource");
30
+
31
+ describe("createNestedResource", () => {
32
+ test("Valid handler mapping is passed to router", () => {
33
+ createNestedResource(honoAdapter, "orders", "line_items", OrdersHandler);
34
+
35
+ expect(adapterSpy).toHaveBeenCalledWith([
36
+ [
37
+ "GET",
38
+ "/orders/:parent_id/line_items",
39
+ "line_items",
40
+ "index",
41
+ OrdersHandler.index,
42
+ ],
43
+ [
44
+ "GET",
45
+ "/orders/:parent_id/line_items/new",
46
+ "line_items",
47
+ "create_form",
48
+ OrdersHandler.create_form,
49
+ ],
50
+ ["GET", "/line_items/:id", "line_items", "show", OrdersHandler.show],
51
+ [
52
+ "GET",
53
+ "/line_items/:id/edit",
54
+ "line_items",
55
+ "edit_form",
56
+ OrdersHandler.edit_form,
57
+ ],
58
+ [
59
+ "POST",
60
+ "/orders/:parent_id/line_items",
61
+ "line_items",
62
+ "create",
63
+ OrdersHandler.create,
64
+ ],
65
+ ["PUT", "/line_items/:id", "line_items", "update", OrdersHandler.update],
66
+ [
67
+ "DELETE",
68
+ "/line_items/:id",
69
+ "line_items",
70
+ "destroy",
71
+ OrdersHandler.destroy,
72
+ ],
73
+ ]);
74
+ });
75
+
76
+ test("Valid handler mapping is passed to router with filters", () => {
77
+ createNestedResource(
78
+ honoAdapter,
79
+ "orders",
80
+ "line_items",
81
+ OrdersHandlerWithFilters
82
+ );
83
+
84
+ expect(adapterSpy).toHaveBeenCalledWith([
85
+ [
86
+ "GET",
87
+ "/orders/:parent_id/line_items",
88
+ "line_items",
89
+ "index",
90
+ OrdersHandlerWithFilters.index,
91
+ OrdersHandlerWithFilters.before_filter,
92
+ OrdersHandlerWithFilters.after_filter,
93
+ ],
94
+ [
95
+ "GET",
96
+ "/orders/:parent_id/line_items/new",
97
+ "line_items",
98
+ "create_form",
99
+ OrdersHandlerWithFilters.create_form,
100
+ OrdersHandlerWithFilters.before_filter,
101
+ OrdersHandlerWithFilters.after_filter,
102
+ ],
103
+ [
104
+ "GET",
105
+ "/line_items/:id",
106
+ "line_items",
107
+ "show",
108
+ OrdersHandlerWithFilters.show,
109
+ OrdersHandlerWithFilters.before_filter,
110
+ OrdersHandlerWithFilters.after_filter,
111
+ ],
112
+ [
113
+ "GET",
114
+ "/line_items/:id/edit",
115
+ "line_items",
116
+ "edit_form",
117
+ OrdersHandlerWithFilters.edit_form,
118
+ OrdersHandlerWithFilters.before_filter,
119
+ OrdersHandlerWithFilters.after_filter,
120
+ ],
121
+ [
122
+ "POST",
123
+ "/orders/:parent_id/line_items",
124
+ "line_items",
125
+ "create",
126
+ OrdersHandlerWithFilters.create,
127
+ OrdersHandlerWithFilters.before_filter,
128
+ OrdersHandlerWithFilters.after_filter,
129
+ ],
130
+ [
131
+ "PUT",
132
+ "/line_items/:id",
133
+ "line_items",
134
+ "update",
135
+ OrdersHandlerWithFilters.update,
136
+ OrdersHandlerWithFilters.before_filter,
137
+ OrdersHandlerWithFilters.after_filter,
138
+ ],
139
+ [
140
+ "DELETE",
141
+ "/line_items/:id",
142
+ "line_items",
143
+ "destroy",
144
+ OrdersHandlerWithFilters.destroy,
145
+ OrdersHandlerWithFilters.before_filter,
146
+ OrdersHandlerWithFilters.after_filter,
147
+ ],
148
+ ]);
149
+ });
150
+ });