@raketa-cloud/admin 0.0.65 → 0.0.67

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@raketa-cloud/admin",
3
- "version": "0.0.65",
3
+ "version": "0.0.67",
4
4
  "main": "src/index.js",
5
5
  "scripts": {
6
6
  "dev": "npx tailwindcss -i ./src/styles/application.css -o ../../apps/admin/static/application.css --watch",
@@ -36,13 +36,17 @@ export default class HonoAdapter extends Adapter {
36
36
  this.globalConfig = config;
37
37
 
38
38
  // TODO: Pass in a path to redirect to after login?
39
- setupAuthentication(router, {
40
- remote: {
41
- domain: "localhost",
42
- host: "http://localhost:3000",
43
- cookie_key: "cms-starter-slim",
39
+ setupAuthentication(
40
+ router,
41
+ {
42
+ remote: {
43
+ domain: "localhost",
44
+ host: "http://localhost:3000",
45
+ cookie_key: "cms-starter-slim",
46
+ },
44
47
  },
45
- });
48
+ config
49
+ );
46
50
 
47
51
  router.get("/", (c) => {
48
52
  return c.redirect("/en");
@@ -121,6 +125,7 @@ export default class HonoAdapter extends Adapter {
121
125
  resourceConfig: resourceConfig,
122
126
  adminJwt: ctx.authentication.token,
123
127
  remoteConfig: { ...this.globalConfig.remoteConfig },
128
+ globalConfig: this.globalConfig,
124
129
  };
125
130
 
126
131
  ctx.logger.info(
@@ -0,0 +1,22 @@
1
+ export default function buildResourcePath({
2
+ prefix_map,
3
+ resource,
4
+ resourceType,
5
+ locale,
6
+ defaultLocale,
7
+ }) {
8
+ const prefix = prefix_map[resourceType];
9
+
10
+ if (!prefix) {
11
+ throw new Error(`Unknown resource type: ${resourceType}`);
12
+ }
13
+
14
+ return [
15
+ locale === defaultLocale ? "" : `/${locale}`,
16
+ prefix,
17
+ "/",
18
+ resource.slug,
19
+ ]
20
+ .join("")
21
+ .replace("//", "/");
22
+ }
@@ -96,11 +96,16 @@ export default class RaketaClient {
96
96
  }
97
97
 
98
98
  query(query, variables = {}) {
99
- return this.gqlRequest(this.getConfig().instanceURL, query, variables, {
100
- "x-raketa-project": this.getConfig().project,
101
- "x-raketa-service": this.getConfig().service,
102
- "x-raketa-identity": this.token,
103
- });
99
+ return this.gqlRequest(
100
+ `${this.getConfig().cloud.gateway_url}/dispatch`,
101
+ query,
102
+ variables,
103
+ {
104
+ "x-raketa-project": this.getConfig().cloud.gateway_project,
105
+ "x-raketa-service": this.getConfig().cloud.dxp_service_private,
106
+ "x-raketa-identity": this.token,
107
+ }
108
+ );
104
109
  }
105
110
 
106
111
  formatErrors(errors, generalErrorTo = null) {
@@ -0,0 +1,36 @@
1
+ const edit_form = async (ctx) => {
2
+ const data = await ctx.client.settings.find(ctx.locale);
3
+
4
+ return {
5
+ assigns: {
6
+ errors: {},
7
+ resource: data,
8
+ },
9
+ };
10
+ };
11
+
12
+ const update = async (ctx) => {
13
+ const params = ctx.resourceParams.resource;
14
+ const current = await ctx.client.settings.find(ctx.locale);
15
+ const updated = ctx.client.settings.deepMerge(current, params);
16
+
17
+ const { errors } = await ctx.client.settings.update(ctx.locale, updated);
18
+
19
+ if (errors) {
20
+ return {
21
+ assigns: {
22
+ resource: { ...params },
23
+ errors: { resource: ctx.client.settings.formatErrors(errors) },
24
+ },
25
+ };
26
+ }
27
+
28
+ return {
29
+ redirect: `/${ctx.locale}/${ctx.resourceConfig.resource}/edit`,
30
+ };
31
+ };
32
+
33
+ export default {
34
+ edit_form,
35
+ update,
36
+ };
@@ -26,18 +26,23 @@ const AuthHelper = {
26
26
  return false;
27
27
  },
28
28
 
29
- getGoogleURL: () => {
30
- const {
31
- GATEWAY_SOCIAL_AUTH_URL: url,
32
- GATEWAY_PROJECT: project,
33
- GATEWAY_PRIMARY_USER_POOL: user_pool,
34
- ADMIN_LOGIN_CALLBACK_URL: callback_url,
35
- } = process.env;
36
-
37
- if ([url, project, user_pool, callback_url].join() === "")
29
+ getGoogleURL: ({
30
+ gateway_url,
31
+ gateway_project,
32
+ gateway_user_pool,
33
+ auth_callback_url,
34
+ }) => {
35
+ if (
36
+ [
37
+ gateway_url,
38
+ gateway_project,
39
+ gateway_user_pool,
40
+ auth_callback_url,
41
+ ].join() === ""
42
+ )
38
43
  throw new Error("Invalid ENVs for authentication configuration");
39
44
 
40
- return `${url}/${project}/${user_pool}/google?return_to=${callback_url}`;
45
+ return `${gateway_url}/users/auth/${gateway_project}/${gateway_user_pool}/google?return_to=${auth_callback_url}`;
41
46
  },
42
47
  };
43
48
 
package/src/index.js CHANGED
@@ -11,6 +11,7 @@ export { default as routeSettings } from "./routeSettings";
11
11
  export { default as setupCloud } from "./setupCloud";
12
12
  export { default as routeStandard } from "./routeStandard";
13
13
  export { default as routeCategoriesModule } from "./routeCategoriesModule";
14
+ export { default as buildResourcePath } from "./buildResourcePath";
14
15
 
15
16
  export { default as RaketaClient } from "./cloud/RaketaClient";
16
17
  export { default as Redirects } from "./cloud/Redirects";
@@ -49,8 +50,7 @@ export { default as PageTranslationsHandler } from "./handlers/pages/Translation
49
50
  export { default as PageURLsHandler } from "./handlers/pages/URLsHandler";
50
51
  export { default as PagesHandler } from "./handlers/pages/PagesHandler";
51
52
 
52
- export { default as SettingsLayoutHandler } from "./handlers/settings/LayoutHandler";
53
- export { default as SettingsMetaHandler } from "./handlers/settings/MetaHandler";
53
+ export { default as SettingsHandler } from "./handlers/_shared/SettingsHandler";
54
54
 
55
55
  export { default as ArticleRevisionsHandler } from "./handlers/article_revisions/ArticleRevisionsHandler";
56
56
  export { default as ArticleRevisionsCodeHandler } from "./handlers/article_revisions/CodeHandler";
@@ -1,10 +1,14 @@
1
1
  import routeMember from "./routeMember";
2
- import LayoutHandler from "./handlers/settings/LayoutHandler";
3
- import MetaHandler from "./handlers/settings/MetaHandler";
2
+ import SettingsHandler from "./handlers/_shared/SettingsHandler";
4
3
 
5
4
  function routeSettings(adapter) {
6
- routeMember(adapter, "settings/meta", MetaHandler);
7
- routeMember(adapter, "settings/layout", LayoutHandler);
5
+ routeMember(adapter, "settings/meta", SettingsHandler, {
6
+ resource: "settings/meta",
7
+ });
8
+
9
+ routeMember(adapter, "settings/layout", SettingsHandler, {
10
+ resource: "settings/layout",
11
+ });
8
12
  }
9
13
 
10
14
  export default routeSettings;
@@ -9,7 +9,7 @@ function getAuthBearer(c) {
9
9
  return authHeader.replace("Bearer ", "");
10
10
  }
11
11
 
12
- const setupAuthentication = (app, opts) => {
12
+ const setupAuthentication = (app, opts, config) => {
13
13
  app.use(async (c, next) => {
14
14
  const token = getCookie(c, AuthHelper.cookie_key) || getAuthBearer(c);
15
15
 
@@ -28,7 +28,13 @@ const setupAuthentication = (app, opts) => {
28
28
  });
29
29
 
30
30
  app.get("/login", async (c) => {
31
- const googleURL = AuthHelper.getGoogleURL();
31
+ const googleURL = AuthHelper.getGoogleURL({
32
+ gateway_url: config.cloud.gateway_url,
33
+ gateway_project: config.cloud.gateway_project,
34
+ gateway_user_pool: config.cloud.gateway_user_pool,
35
+ auth_callback_url: config.cloud.auth_callback_url,
36
+ });
37
+
32
38
  const templatePath = `${currentPath}/views/_auth/login.ejs`;
33
39
 
34
40
  const rendered = await ejs.renderFile(templatePath, {
@@ -0,0 +1,36 @@
1
+ import SettingsHandler from "../../../../handlers/_shared/SettingsHandler";
2
+ import Settings from "../../../../cloud/Settings";
3
+
4
+ import Factory from "../../../support/factory";
5
+
6
+ function buildCtx() {
7
+ return {
8
+ client: {
9
+ settings: new Settings(() => ({}), "token"),
10
+ },
11
+ locale: "en",
12
+ };
13
+ }
14
+
15
+ describe("Settings edit", () => {
16
+ test("edit_form", async () => {
17
+ const settings = Factory.build("settings");
18
+
19
+ const ctx = buildCtx();
20
+
21
+ const mockFind = jest
22
+ .spyOn(ctx.client.settings, "find")
23
+ .mockImplementation(() => settings.value);
24
+
25
+ const result = await SettingsHandler.edit_form(ctx);
26
+
27
+ expect(mockFind).toHaveBeenCalledWith("en");
28
+
29
+ expect(result).toEqual({
30
+ assigns: {
31
+ resource: settings.value,
32
+ errors: {},
33
+ },
34
+ });
35
+ });
36
+ });
@@ -0,0 +1,114 @@
1
+ import SettingsHandler from "../../../../handlers/_shared/SettingsHandler";
2
+ import Settings from "../../../../cloud/Settings";
3
+
4
+ import Factory from "../../../support/factory";
5
+
6
+ function buildCtx(params) {
7
+ return {
8
+ client: {
9
+ settings: new Settings(() => ({}), "token"),
10
+ },
11
+ locale: "en",
12
+ resourceParams: {
13
+ resource: params,
14
+ },
15
+ locale: "en",
16
+ resourceConfig: {
17
+ resource: "settings/meta",
18
+ },
19
+ };
20
+ }
21
+
22
+ describe("Settings update", () => {
23
+ test("update succesfully", async () => {
24
+ const settings = Factory.build("settings", {
25
+ value: {
26
+ meta: {
27
+ title: "Not A title!",
28
+ description: "No, no!",
29
+ },
30
+ },
31
+ });
32
+
33
+ const ctx = buildCtx({
34
+ meta: {
35
+ title: "A title",
36
+ description: "A description",
37
+ image: JSON.stringify({ url: "https://image-url.com" }),
38
+ },
39
+ });
40
+
41
+ const mockFind = jest
42
+ .spyOn(ctx.client.settings, "find")
43
+ .mockImplementation(() => settings.value);
44
+
45
+ const mockUpdate = jest
46
+ .spyOn(ctx.client.settings, "update")
47
+ .mockImplementation(() => ({ setting: {}, errors: null }));
48
+
49
+ const result = await SettingsHandler.update(ctx);
50
+
51
+ expect(mockFind).toHaveBeenCalledWith("en");
52
+ expect(mockUpdate).toHaveBeenCalledWith("en", {
53
+ meta: {
54
+ title: "A title",
55
+ description: "A description",
56
+ image: { url: "https://image-url.com" },
57
+ },
58
+ });
59
+
60
+ expect(result).toEqual({
61
+ redirect: "/en/settings/meta/edit",
62
+ });
63
+ });
64
+
65
+ test("update failed", async () => {
66
+ const settings = Factory.build("settings", {
67
+ value: {
68
+ meta: {
69
+ title: "Not A title!",
70
+ description: "No, no!",
71
+ },
72
+ },
73
+ });
74
+
75
+ const updateParams = {
76
+ meta: {
77
+ title: "A title",
78
+ description: "A description",
79
+ image: JSON.stringify({ url: "https://image-url.com" }),
80
+ },
81
+ };
82
+
83
+ const ctx = buildCtx(updateParams);
84
+
85
+ const mockFind = jest
86
+ .spyOn(ctx.client.settings, "find")
87
+ .mockImplementation(() => settings.value);
88
+
89
+ const mockUpdate = jest
90
+ .spyOn(ctx.client.settings, "update")
91
+ .mockImplementation(() => ({
92
+ setting: null,
93
+ errors: [{ key: "generalError", message: "some error" }],
94
+ }));
95
+
96
+ const result = await SettingsHandler.update(ctx);
97
+
98
+ expect(mockFind).toHaveBeenCalledWith("en");
99
+ expect(mockUpdate).toHaveBeenCalledWith("en", {
100
+ meta: {
101
+ title: "A title",
102
+ description: "A description",
103
+ image: { url: "https://image-url.com" },
104
+ },
105
+ });
106
+
107
+ expect(result).toEqual({
108
+ assigns: {
109
+ resource: updateParams,
110
+ errors: { resource: { generalError: "some error" } },
111
+ },
112
+ });
113
+ });
114
+ });
@@ -18,15 +18,15 @@
18
18
  </head>
19
19
 
20
20
  <script>
21
- var RAKETA_IMAGES_API_KEY = "<%= process.env.IMAGES_API_KEY %>";
21
+ var RAKETA_IMAGES_API_KEY = "<%= _context.globalConfig.cloud.images_api_key %>";
22
22
  </script>
23
23
 
24
24
  <script src="https://storage.googleapis.com/cdn-raketa-cloud/js/raketa-image-picker-webcomponent.umd.js"></script>
25
25
 
26
26
  <script>
27
- var FILE_PICKER_GATEWAY_DISPATCH_URL = "<%= process.env.GATEWAY_DISPATCH_URL %>";
28
- var FILE_PICKER_GATEWAY_PROJECT = "<%= process.env.GATEWAY_PROJECT %>";
29
- var FILE_PICKER_DXP_PRIVATE_SERVICE = "<%= process.env.DXP_PRIVATE_SERVICE %>";
27
+ var FILE_PICKER_GATEWAY_DISPATCH_URL = "<%= _context.globalConfig.cloud.gatway_url %>/dispatch";
28
+ var FILE_PICKER_GATEWAY_PROJECT = "<%= _context.globalConfig.cloud.gateway_project %>";
29
+ var FILE_PICKER_DXP_PRIVATE_SERVICE = "<%= _context.globalConfig.cloud.dxp_service_private %>";
30
30
  var FILE_PICKER_JWT_TOKEN = "<%= _context.adminJwt %>";
31
31
  </script>
32
32
 
@@ -41,6 +41,10 @@
41
41
  }
42
42
  </style>
43
43
 
44
+ <script src="https://storage.googleapis.com/cdn-raketa-cloud/js/raketa-menu-builder-webcomponent.umd.js"></script>
45
+
46
+ <script src="https://storage.googleapis.com/cdn-raketa-cloud/js/raketa-list-input-webcomponent.umd.js"></script>
47
+
44
48
  <body class="relative max-h-screen overflow-hidden">
45
49
  <%- include('header.ejs') %>
46
50
 
@@ -214,4 +218,4 @@
214
218
  </script>
215
219
  </body>
216
220
 
217
- </html>
221
+ </html>
@@ -5,11 +5,47 @@
5
5
 
6
6
  <%- render_partial('../_nav.ejs', { active: 'layout' }) %>
7
7
 
8
+ <% const WIDGETS = {
9
+ HTML: {
10
+ defaults: {
11
+ code: "<div>HTML</div>"
12
+ },
13
+ admin: {
14
+ code: { type: "textarea" }
15
+ }
16
+ },
17
+ Image: {
18
+ primaryField: "image",
19
+ defaults: {
20
+ image: "https://placeholder.raketa.cloud/images/1920x1080"
21
+ },
22
+ admin: {
23
+ image: { type: "text" }
24
+ }
25
+ },
26
+ Title: {
27
+ primaryField: "title",
28
+ defaults: {
29
+ title: "Title"
30
+ },
31
+ admin: {
32
+ title: { type: "text" }
33
+ }
34
+ }
35
+ }; %>
36
+
8
37
  <div>
9
- <% form_for({ layout_settings }, errors, (f) => { %>
38
+ <% form_for({ resource }, errors, (f) => { %>
10
39
  <form action="<%= `/${_context.locale}/settings/layout` %>" method="post" class="form">
11
- <%- f.field('layout_settings.header', {as: "textarea"}) %>
12
- <%- f.field('layout_settings.footer', {as: "textarea"}) %>
40
+ <div class="mb-4">
41
+ <label>Header</label>
42
+ <raketa-menu-builder-input id="header-menu" name="resource[menus][header]" value="<%= JSON.stringify(resource.menus?.header || []) %>" widgets="<%= JSON.stringify(WIDGETS) %>"></raketa-menu-builder-input>
43
+ </div>
44
+
45
+ <div class="mb-4">
46
+ <label>Footer</label>
47
+ <raketa-menu-builder-input id="header-menu" name="resource[menus][footer]" value="<%= JSON.stringify(resource.menus?.footer || []) %>" widgets="<%= JSON.stringify(WIDGETS) %>"></raketa-menu-builder-input>
48
+ </div>
13
49
 
14
50
  <div class="form-actions">
15
51
  <div class="btn-toolbar">
@@ -6,12 +6,12 @@
6
6
  <%- render_partial('../_nav.ejs', { active: 'meta' }) %>
7
7
 
8
8
  <div class="grid gap-4 md:grid-cols-3">
9
- <% form_for({ meta_settings }, errors, (f) => { %>
9
+ <% form_for({ resource }, errors, (f) => { %>
10
10
  <form action="<%= `/${_context.locale}/settings/meta` %>" method="post" class="form">
11
- <%- f.field('meta_settings.title', { label: "Meta title", hint: "Used for both meta and OpenGraph" }) %>
12
- <%- f.field('meta_settings.description', { as: "textarea", label: "Meta description", hint: "Used for both meta and OpenGraph" }) %>
11
+ <%- f.field('resource.meta.title', { label: "Meta title", hint: "Used for both meta and OpenGraph" }) %>
12
+ <%- f.field('resource.meta.description', { as: "textarea", label: "Meta description", hint: "Used for both meta and OpenGraph" }) %>
13
13
 
14
- <raketa-image-picker name="meta_settings[image]" value="<%= JSON.stringify(meta_settings.image) %>" label="OpenGraph image"></raketa-image-picker>
14
+ <raketa-image-picker name="resource[image]" value="<%= JSON.stringify(resource.meta?.image || {}) %>" label="OpenGraph image"></raketa-image-picker>
15
15
 
16
16
  <div class="form-actions">
17
17
  <div class="btn-toolbar">
@@ -21,4 +21,4 @@
21
21
  </form>
22
22
  <% }) %>
23
23
  </div>
24
- </div>
24
+ </div>
@@ -1,51 +0,0 @@
1
- const edit_form = async (ctx) => {
2
- const data = await ctx.client.settings.find(ctx.locale);
3
-
4
- const layout_settings = {
5
- header: JSON.stringify(data.menus?.header),
6
- footer: JSON.stringify(data.menus?.footer),
7
- };
8
-
9
- return {
10
- assigns: {
11
- errors: {},
12
- layout_settings,
13
- },
14
- };
15
- };
16
-
17
- const update = async (ctx) => {
18
- const params = ctx.resourceParams.layout_settings;
19
-
20
- const currentSettings = await ctx.client.settings.find(ctx.locale);
21
-
22
- if (!currentSettings.menus) {
23
- currentSettings.menus = {};
24
- }
25
-
26
- currentSettings.menus.header = JSON.parse(params.header);
27
- currentSettings.menus.footer = JSON.parse(params.footer);
28
-
29
- const { errors } = await ctx.client.settings.update(
30
- ctx.locale,
31
- currentSettings
32
- );
33
-
34
- if (errors) {
35
- return {
36
- assigns: {
37
- setting: { ...params },
38
- errors: { setting: ctx.client.settings.formatErrors(errors) },
39
- },
40
- };
41
- }
42
-
43
- return {
44
- redirect: `/${ctx.locale}/settings/layout/edit`,
45
- };
46
- };
47
-
48
- export default {
49
- edit_form,
50
- update,
51
- };
@@ -1,52 +0,0 @@
1
- const edit_form = async (ctx) => {
2
- const data = await ctx.client.settings.find(ctx.locale);
3
- const meta_settings = {
4
- ...data.meta,
5
- image: data.meta?.image || {},
6
- };
7
- const errors = {};
8
-
9
- return {
10
- assigns: {
11
- errors,
12
- meta_settings,
13
- },
14
- };
15
- };
16
-
17
- const update = async (ctx) => {
18
- const params = ctx.resourceParams.meta_settings;
19
-
20
- const currentSettings = await ctx.client.settings.find(ctx.locale);
21
-
22
- if (!currentSettings.meta) {
23
- currentSettings.meta = {};
24
- }
25
-
26
- currentSettings.meta.title = params.title;
27
- currentSettings.meta.description = params.description;
28
- currentSettings.meta.image = JSON.parse(params.image);
29
-
30
- const { errors } = await ctx.client.settings.update(
31
- ctx.locale,
32
- currentSettings
33
- );
34
-
35
- if (errors) {
36
- return {
37
- assigns: {
38
- setting: { ...params },
39
- errors: { setting: ctx.client.settings.formatErrors(errors) },
40
- },
41
- };
42
- }
43
-
44
- return {
45
- redirect: `/${ctx.locale}/settings/meta/edit`,
46
- };
47
- };
48
-
49
- export default {
50
- edit_form,
51
- update,
52
- };