@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,115 @@
1
+ import createResource from "../createResource";
2
+
3
+ const UsersHandler = {
4
+ index: () => {},
5
+ show: () => {},
6
+ create_form: () => {},
7
+ edit_form: () => {},
8
+ create: () => {},
9
+ update: () => {},
10
+ destroy: () => {},
11
+ };
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
+ const honoAdapter = {
26
+ createResource: (_mapping) => {},
27
+ };
28
+
29
+ const adapterSpy = jest.spyOn(honoAdapter, "createResource");
30
+
31
+ describe("createResource", () => {
32
+ test("Valid handler mapping is passed to router", () => {
33
+ createResource(honoAdapter, "users", UsersHandler);
34
+
35
+ expect(adapterSpy).toHaveBeenCalledWith([
36
+ ["GET", "/users", "users", "index", UsersHandler.index],
37
+ ["GET", "/users/new", "users", "create_form", UsersHandler.create_form],
38
+ ["GET", "/users/:id", "users", "show", UsersHandler.show],
39
+ ["GET", "/users/:id/edit", "users", "edit_form", UsersHandler.edit_form],
40
+ ["POST", "/users", "users", "create", UsersHandler.create],
41
+ ["PUT", "/users/:id", "users", "update", UsersHandler.update],
42
+ ["DELETE", "/users/:id", "users", "destroy", UsersHandler.destroy],
43
+ ]);
44
+ });
45
+
46
+ test("Valid handler mapping is passed to router with filters", () => {
47
+ createResource(honoAdapter, "users", UsersHandlerWithFilters);
48
+
49
+ expect(adapterSpy).toHaveBeenCalledWith([
50
+ [
51
+ "GET",
52
+ "/users",
53
+ "users",
54
+ "index",
55
+ UsersHandlerWithFilters.index,
56
+ UsersHandlerWithFilters.before_filter,
57
+ UsersHandlerWithFilters.after_filter,
58
+ ],
59
+ [
60
+ "GET",
61
+ "/users/new",
62
+ "users",
63
+ "create_form",
64
+ UsersHandlerWithFilters.create_form,
65
+ UsersHandlerWithFilters.before_filter,
66
+ UsersHandlerWithFilters.after_filter,
67
+ ],
68
+ [
69
+ "GET",
70
+ "/users/:id",
71
+ "users",
72
+ "show",
73
+ UsersHandlerWithFilters.show,
74
+ UsersHandlerWithFilters.before_filter,
75
+ UsersHandlerWithFilters.after_filter,
76
+ ],
77
+ [
78
+ "GET",
79
+ "/users/:id/edit",
80
+ "users",
81
+ "edit_form",
82
+ UsersHandlerWithFilters.edit_form,
83
+ UsersHandlerWithFilters.before_filter,
84
+ UsersHandlerWithFilters.after_filter,
85
+ ],
86
+ [
87
+ "POST",
88
+ "/users",
89
+ "users",
90
+ "create",
91
+ UsersHandlerWithFilters.create,
92
+ UsersHandlerWithFilters.before_filter,
93
+ UsersHandlerWithFilters.after_filter,
94
+ ],
95
+ [
96
+ "PUT",
97
+ "/users/:id",
98
+ "users",
99
+ "update",
100
+ UsersHandlerWithFilters.update,
101
+ UsersHandlerWithFilters.before_filter,
102
+ UsersHandlerWithFilters.after_filter,
103
+ ],
104
+ [
105
+ "DELETE",
106
+ "/users/:id",
107
+ "users",
108
+ "destroy",
109
+ UsersHandlerWithFilters.destroy,
110
+ UsersHandlerWithFilters.before_filter,
111
+ UsersHandlerWithFilters.after_filter,
112
+ ],
113
+ ]);
114
+ });
115
+ });
@@ -0,0 +1,22 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Administration</title>
7
+
8
+ <link rel="stylesheet" href="/static/application.css" />
9
+ <script src="https://unpkg.com/htmx.org@1.9.12"></script>
10
+ </head>
11
+ <body>
12
+ <header class="text-red-500">
13
+ <div class="container mx-auto">Header</div>
14
+ </header>
15
+
16
+ <div class="container mx-auto"><%- body %></div>
17
+
18
+ <header>
19
+ <div class="container mx-auto">Footer</div>
20
+ </header>
21
+ </body>
22
+ </html>
@@ -0,0 +1 @@
1
+ <h1>Hello from package</h1>
@@ -0,0 +1,8 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ module.exports = {
3
+ content: ["./src/**/*.{html,js,ejs}", "../../apps/**/*.{html,js,ejs}"],
4
+ theme: {
5
+ extend: {},
6
+ },
7
+ plugins: [require("@tailwindcss/typography"), require("@tailwindcss/forms")],
8
+ };
package/tsconfig.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
- "extends": "@repo/typescript-config/react-library.json",
3
- "compilerOptions": {
4
- "jsx": "react-jsx",
5
- "jsxImportSource": "hono/jsx",
6
- "outDir": "dist",
7
- "types": ["node", "@types/jest"],
8
- "baseUrl": ".",
9
- "paths": {
10
- "@App/*": ["src/*"]
11
- }
12
- },
13
- "include": ["src"],
14
- "exclude": ["node_modules", "dist"]
15
- }
2
+ "extends": "@repo/typescript-config/react-library.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "jsx": "react-jsx",
6
+ "jsxImportSource": "hono/jsx"
7
+ },
8
+ "include": [
9
+ "src"
10
+ ],
11
+ "exclude": [
12
+ "node_modules",
13
+ "dist"
14
+ ]
15
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "@repo/typescript-config/react-library.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist"
5
+ },
6
+ "include": ["src", "turbo"],
7
+ "exclude": ["node_modules", "dist"]
8
+ }
package/biome.json DELETED
@@ -1,6 +0,0 @@
1
- {
2
- "extends": ["../../biome.json"],
3
- "files": {
4
- "ignore": ["src/lib/logger.tsx"]
5
- }
6
- }
package/jest.config.js DELETED
@@ -1,15 +0,0 @@
1
- const { pathsToModuleNameMapper } = require("ts-jest");
2
- const { compilerOptions } = require("./tsconfig.json");
3
-
4
- module.exports = {
5
- clearMocks: true,
6
- preset: "ts-jest",
7
- testEnvironment: "node",
8
- globalSetup: "./tests/support/setup.js",
9
- transform: {
10
- "^.+\\.ts?$": "ts-jest",
11
- },
12
- roots: ["."],
13
- modulePaths: [compilerOptions.baseUrl],
14
- moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths),
15
- };
package/src/app.tsx DELETED
@@ -1,19 +0,0 @@
1
- import { Hono } from "hono";
2
- import logger from "src/middleware/logger";
3
- import httpLogger from "src/middleware/http_logger";
4
- import auth from "src/middleware/authentication";
5
-
6
- import tagsApp from "src/crud/tags";
7
- import loginApp from "src/crud/login";
8
- import logoutApp from "src/crud/logout";
9
-
10
- const app = new Hono();
11
- app.use("*", logger);
12
- app.use("*", httpLogger);
13
- app.use("*", auth);
14
-
15
- app.route("/", tagsApp);
16
- app.route("/", loginApp);
17
- app.route("/", logoutApp);
18
-
19
- export default app;
@@ -1,99 +0,0 @@
1
- import { Hono } from "hono";
2
- import type { FC } from "hono/jsx";
3
- import Layout from "src/shared/Layout";
4
- import login from "src/lib/gateway/login";
5
- import { setCookie, deleteCookie } from "hono/cookie";
6
- import { decode } from "hono/jwt";
7
- import { CookieOptions } from "hono/utils/cookie";
8
-
9
- const app = new Hono();
10
-
11
- // Login part
12
- const LoginForm: FC = ({ login = {}, errors = {} }) => {
13
- return (
14
- <div class="container mx-auto py-16">
15
- <h1 class="mb-8 text-2xl font-bold">Login</h1>
16
-
17
- {errors.general && (
18
- <p>
19
- <span>{errors.general}</span>
20
- </p>
21
- )}
22
-
23
- <form method="POST" action="/login">
24
- <div class="flex gap-2">
25
- <label>Email</label>
26
- <input type="email" value={login.email} name="email" class="border" />
27
- {errors.email && <span>{errors.email}</span>}
28
- </div>
29
-
30
- <div class="flex gap-2">
31
- <label>Password</label>
32
- <input type="password" value="" name="password" class="border" />
33
- </div>
34
-
35
- <div class="flex gap-4">
36
- <button type="submit">Login</button>
37
- </div>
38
- </form>
39
- </div>
40
- );
41
- };
42
-
43
- app.get("/login", async (c) => {
44
- const errors = {};
45
-
46
- if (c.req.query().reason === "inactive") {
47
- (errors as any).general =
48
- "Your user is not activated. Please contact your administrator for more information";
49
- }
50
-
51
- return c.html(
52
- <Layout context={c}>
53
- <LoginForm errors={errors} />
54
- </Layout>,
55
- );
56
- });
57
-
58
- app.post("/login", async (c) => {
59
- const body = await c.req.parseBody();
60
-
61
- const email = body.email as string;
62
- const password = body.password as string;
63
-
64
- const resp = await login.create(email, password, c);
65
-
66
- if ((resp as any).login.errors) {
67
- const errors = {};
68
-
69
- (resp as any).login.errors.forEach((error: any) => {
70
- const key = error.key;
71
-
72
- (errors as any)[key] = [(errors as any)[key] || "", error.message]
73
- .filter(Boolean)
74
- .join("; ");
75
- });
76
-
77
- deleteCookie(c, "authToken");
78
- return c.html(
79
- <Layout context={c}>
80
- <LoginForm login={{ email }} errors={errors} />
81
- </Layout>,
82
- );
83
- }
84
-
85
- const token = (resp as any).login.accessToken;
86
- const expiration =
87
- (decode(token) as any).payload.exp ||
88
- (new Date().getTime() / 1000).toFixed(0);
89
-
90
- const opts = {
91
- expires: new Date(expiration * 1000),
92
- } as CookieOptions;
93
-
94
- setCookie(c, "authToken", token, opts);
95
-
96
- return c.redirect("/tags", 301);
97
- });
98
-
99
- export default app;
@@ -1,14 +0,0 @@
1
- import { Hono } from "hono";
2
- import { deleteCookie } from "hono/cookie";
3
-
4
- const app = new Hono();
5
-
6
- app.delete("/logout", async (c) => {
7
- deleteCookie(c, "authToken");
8
-
9
- c.header("HX-Redirect", "/login");
10
- c.status(201);
11
- return c.json({ message: "Bye" });
12
- });
13
-
14
- export default app;
package/src/crud/tags.tsx DELETED
@@ -1,235 +0,0 @@
1
- import type { FC } from "hono/jsx";
2
- import { Hono } from "hono";
3
- import tags from "src/lib/dxp/private/tags";
4
- import Layout from "src/shared/Layout";
5
-
6
- const TagsList: FC = ({ data }) => {
7
- // TODO: Pagination!
8
- const list = data?.tags?.list || [];
9
-
10
- const results = list.map((tag: any) => (
11
- <tr class="bg-transparent hover:bg-yellow-50 transition">
12
- <td class="p-4 border-b">
13
- <a
14
- href={`/tags/${tag.id}/edit`}
15
- class="underline font-semibold hover:no-underline"
16
- >
17
- {tag.name}
18
- </a>
19
- </td>
20
- <td class="p-4 border-b">{tag.slug}</td>
21
- </tr>
22
- ));
23
-
24
- return (
25
- <>
26
- {results.length > 0 && results}
27
- {results.length === 0 && (
28
- <tr>
29
- <td colspan={2} class="p-4">
30
- Not found.
31
- </td>
32
- </tr>
33
- )}
34
- </>
35
- );
36
- };
37
-
38
- const Tags: FC = ({ data }) => {
39
- return (
40
- <div class="container mx-auto py-16">
41
- <h1 class="mb-8 text-2xl font-bold">Tags</h1>
42
-
43
- <a href="/tags/new">New</a>
44
-
45
- <table class="w-full">
46
- <thead>
47
- <tr>
48
- <th class="p-4 border-b text-left">Name</th>
49
- <th class="p-4 border-b text-left">URL</th>
50
- </tr>
51
- </thead>
52
- <TagsList data={data} />
53
- </table>
54
- </div>
55
- );
56
- };
57
-
58
- const Form: FC = ({ action, tag, errors = {} }) => {
59
- return (
60
- <form method="POST" action={action}>
61
- <div class="flex gap-2">
62
- <label>Name</label>
63
- <input type="text" value={tag.name} name="name" class="border" />
64
- {errors.name && <span>{errors.name}</span>}
65
- </div>
66
-
67
- <div class="flex gap-2">
68
- <label>URL</label>
69
- <input type="text" value={tag.slug} name="slug" class="border" />
70
- {errors.slug && <span>{errors.slug}</span>}
71
- </div>
72
-
73
- <div class="flex gap-4">
74
- <button type="submit">Save</button>
75
- <a href="/tags">Back</a>
76
- {tag.id && (
77
- <button
78
- type="button"
79
- hx-delete={`/tags/${tag.id}`}
80
- hx-confirm="Are you sure you wish to delete this tag?"
81
- >
82
- DELETE
83
- </button>
84
- )}
85
- </div>
86
- </form>
87
- );
88
- };
89
-
90
- const EditForm: FC = ({ tag, errors = {} }) => {
91
- return (
92
- <div class="container mx-auto py-16">
93
- <h1 class="mb-8 text-2xl font-bold">Tags</h1>
94
-
95
- <Form action={`/tags/${tag.id}`} tag={tag} errors={errors} />
96
- </div>
97
- );
98
- };
99
-
100
- const NewForm: FC = ({ tag = {}, errors = {} }) => {
101
- return (
102
- <div class="container mx-auto py-16">
103
- <h1 class="mb-8 text-2xl font-bold">Tags</h1>
104
-
105
- <Form action="/tags" tag={tag} errors={errors} />
106
- </div>
107
- );
108
- };
109
-
110
- const app = new Hono();
111
-
112
- app.get("/tags", async (c) => {
113
- const data = await tags.list({ input: { locale: "en" } }, c);
114
-
115
- return c.html(
116
- <Layout context={c}>
117
- <Tags data={data} />
118
- </Layout>,
119
- );
120
- });
121
-
122
- app.get("/tags/new", (c) => c.html(<NewForm />));
123
-
124
- app.get("/tags/:id/edit", async (c) => {
125
- const { tag }: any = await tags.find(parseInt(c.req.param().id, 10), "en", c);
126
-
127
- return c.html(
128
- <Layout context={c}>
129
- <EditForm tag={tag} />
130
- </Layout>,
131
- );
132
- });
133
-
134
- app.delete("/tags/:id", async (c) => {
135
- const { tag }: any = await tags.find(parseInt(c.req.param().id, 10), "en", c);
136
- const resp = await tags.delete(tag.id, "en", c);
137
- const deleteTag = (resp as any).deleteTag;
138
-
139
- if (deleteTag.errors) {
140
- throw `ERROR: ${deleteTag.errors}`;
141
- }
142
-
143
- c.header("HX-Redirect", "/tags");
144
- c.status(201);
145
-
146
- return c.json(deleteTag.tag);
147
- });
148
-
149
- app.post("/tags/:id", async (c) => {
150
- const body = await c.req.parseBody();
151
-
152
- const { tag }: any = await tags.find(parseInt(c.req.param().id, 10), "en", c);
153
-
154
- const resp = await tags.update(
155
- {
156
- input: {
157
- locale: "en",
158
- id: tag.id,
159
- tag: {
160
- name: body.name as string,
161
- slug: body.slug as string,
162
- },
163
- },
164
- },
165
- c,
166
- );
167
-
168
- if ((resp as any).updateTag.errors) {
169
- const errors = {};
170
-
171
- (resp as any).updateTag.errors.forEach((error: any) => {
172
- const key = error.key;
173
-
174
- (errors as any)[key] = [(errors as any)[key] || "", error.message]
175
- .filter(Boolean)
176
- .join("; ");
177
- });
178
-
179
- return c.html(
180
- <Layout context={c}>
181
- <EditForm
182
- tag={{
183
- id: tag.id,
184
- name: body.name as string,
185
- slug: body.slug as string,
186
- }}
187
- errors={errors}
188
- />
189
- </Layout>,
190
- );
191
- }
192
-
193
- return c.redirect("/tags", 301);
194
- });
195
-
196
- app.post("/tags", async (c) => {
197
- const body = await c.req.parseBody();
198
-
199
- const tag = {
200
- name: body.name as string,
201
- slug: body.slug as string,
202
- };
203
-
204
- const resp = await tags.create(
205
- {
206
- input: {
207
- locale: "en",
208
- tag,
209
- },
210
- },
211
- c,
212
- );
213
-
214
- if ((resp as any).createTag.errors) {
215
- const errors = {};
216
-
217
- (resp as any).createTag.errors.forEach((error: any) => {
218
- const key = error.key;
219
-
220
- (errors as any)[key] = [(errors as any)[key] || "", error.message]
221
- .filter(Boolean)
222
- .join("; ");
223
- });
224
-
225
- return c.html(
226
- <Layout context={c}>
227
- <NewForm tag={tag} errors={errors} />
228
- </Layout>,
229
- );
230
- }
231
-
232
- return c.redirect("/tags", 301);
233
- });
234
-
235
- export default app;
package/src/index.ts DELETED
@@ -1 +0,0 @@
1
- export { default as adminApp } from "./app";
@@ -1,106 +0,0 @@
1
- import { Context } from "hono";
2
- import { dxpPrivateRequest } from "../protocol";
3
- import { gql } from "../../gqlEngine";
4
-
5
- const TagsDocument = gql`
6
- query Tags($input: TagsInput!) {
7
- tags(input: $input) {
8
- list {
9
- id
10
- name
11
- slug
12
- }
13
- pagination {
14
- currentPage
15
- pagesCount
16
- entriesCount
17
- pageSize
18
- }
19
- }
20
- }
21
- `;
22
-
23
- const CreateTagDocument = gql`
24
- mutation CreateTag($input: CreateTagInput!) {
25
- createTag(input: $input) {
26
- tag {
27
- id
28
- slug
29
- name
30
- }
31
- errors {
32
- key
33
- message
34
- codes {
35
- key
36
- placeholders
37
- }
38
- }
39
- }
40
- }
41
- `;
42
-
43
- const UpdateTagDocument = gql`
44
- mutation UpdateTag($input: UpdateTagInput!) {
45
- updateTag(input: $input) {
46
- tag {
47
- id
48
- slug
49
- name
50
- }
51
- errors {
52
- key
53
- message
54
- codes {
55
- key
56
- placeholders
57
- }
58
- }
59
- }
60
- }
61
- `;
62
-
63
- const DeleteTagDocument = gql`
64
- mutation DeleteTag($input: IdInput!) {
65
- deleteTag(input: $input) {
66
- tag {
67
- id
68
- slug
69
- name
70
- }
71
- errors {
72
- key
73
- message
74
- codes {
75
- key
76
- placeholders
77
- }
78
- }
79
- }
80
- }
81
- `;
82
-
83
- const TagByIdDocument = gql`
84
- query TagByID($input: IdInput!) {
85
- tag(input: $input) {
86
- id
87
- slug
88
- name
89
- }
90
- }
91
- `;
92
-
93
- const tags = {
94
- list: async (variables: any, honoCtx: Context) =>
95
- dxpPrivateRequest(TagsDocument, variables, honoCtx),
96
- find: async (id: number, locale: string, honoCtx: Context) =>
97
- dxpPrivateRequest(TagByIdDocument, { input: { id, locale } }, honoCtx),
98
- create: async (variables: any, honoCtx: Context) =>
99
- dxpPrivateRequest(CreateTagDocument, variables, honoCtx),
100
- update: async (variables: any, honoCtx: Context) =>
101
- dxpPrivateRequest(UpdateTagDocument, variables, honoCtx),
102
- delete: async (id: number, locale: string, honoCtx: Context) =>
103
- dxpPrivateRequest(DeleteTagDocument, { input: { id, locale } }, honoCtx),
104
- };
105
-
106
- export default tags;