@raketa-cloud/admin 0.0.88 → 0.0.90

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 (30) hide show
  1. package/jest.config.js +22 -0
  2. package/package.json +2 -2
  3. package/src/cloud/ArticleRevisions.js +33 -5
  4. package/src/cloud/Articles.js +34 -4
  5. package/src/cloud/EventRevisions.js +33 -5
  6. package/src/cloud/Events.js +35 -5
  7. package/src/cloud/PageRevisions.js +31 -3
  8. package/src/cloud/Pages.js +34 -4
  9. package/src/cloud/documents/articlesDocuments.js +44 -3
  10. package/src/cloud/documents/eventsDocuments.js +43 -2
  11. package/src/cloud/documents/pagesDocuments.js +43 -2
  12. package/src/handlers/articles/TranslationsHandler.js +81 -15
  13. package/src/handlers/events/TranslationsHandler.js +80 -14
  14. package/src/handlers/pages/TranslationsHandler.js +84 -18
  15. package/src/helpers/AuthHelper.js +2 -2
  16. package/src/tests/handlers/TagsHandler.test/create.test.js +96 -0
  17. package/src/tests/handlers/TagsHandler.test/create_form.test.js +25 -0
  18. package/src/tests/handlers/TagsHandler.test/destroy.test.js +51 -0
  19. package/src/tests/handlers/TagsHandler.test/edit_form.test.js +60 -0
  20. package/src/tests/handlers/TagsHandler.test/index.test.js +37 -0
  21. package/src/tests/handlers/TagsHandler.test/update.test.js +105 -0
  22. package/src/tests/handlers/articles/TranslationHandler.test/create.test.js +85 -79
  23. package/src/tests/handlers/events/TranslationHandler.test/create.test.js +84 -78
  24. package/src/tests/handlers/pages/TranslationHandler.test/create.test.js +83 -72
  25. package/src/tests/helpers/AuthHelper.test.js +169 -0
  26. package/src/tests/helpers/FormBuildert.test.js +357 -0
  27. package/src/tests/helpers/Inflector.test.js +75 -0
  28. package/src/views/articles/translations/create_form.ejs +2 -0
  29. package/src/views/events/translations/create_form.ejs +2 -0
  30. package/src/views/pages/translations/create_form.ejs +3 -1
@@ -76,7 +76,28 @@ const create_form = async (ctx) => {
76
76
  const create = async (ctx) => {
77
77
  const { parent_id } = ctx.params;
78
78
  const originalLocale = ctx.locale;
79
- const blueprint = await ctx.client.articles.find(parent_id, ctx.locale);
79
+ const blueprint = await ctx.client.articles.find(parent_id, originalLocale);
80
+
81
+ if (!ctx.resourceParams?.resource?.languages) {
82
+ return {
83
+ assigns: {
84
+ resource: {
85
+ translations: blueprint.translations || {},
86
+ languages: [],
87
+ },
88
+ blueprint,
89
+ targetLocale: originalLocale,
90
+ errors: {
91
+ resource: ctx.client.articles.formatErrors([
92
+ {
93
+ key: "languages",
94
+ message: "select at least one language for translation",
95
+ },
96
+ ]),
97
+ },
98
+ },
99
+ };
100
+ }
80
101
 
81
102
  const formLanguages = Array.isArray(ctx.resourceParams.resource.languages)
82
103
  ? ctx.resourceParams.resource.languages
@@ -98,18 +119,61 @@ const create = async (ctx) => {
98
119
  tags: [],
99
120
  };
100
121
 
122
+ const resource = ctx.client.articles.deepMerge(params, {
123
+ visibility: "private",
124
+ });
125
+
101
126
  const translationIds = [];
102
127
 
103
- for (const locale of selectedLocales) {
104
- const resource = ctx.client.articles.deepMerge(params, {
105
- visibility: "private",
106
- });
128
+ const translations = await ctx.client.articles.translateBatch(
129
+ parent_id,
130
+ selectedLocales,
131
+ resource
132
+ );
107
133
 
108
- const { errors, article } = await ctx.client.articles.translate(
109
- parent_id,
110
- locale,
111
- resource
112
- );
134
+ if (!translations) {
135
+ return {
136
+ assigns: {
137
+ resource: {
138
+ ...resource,
139
+ translations: blueprint.translations || {},
140
+ languages: formLanguages,
141
+ },
142
+ blueprint,
143
+ targetLocale: originalLocale,
144
+ errors: {
145
+ resource: ctx.client.articles.formatErrors([
146
+ {
147
+ key: "generalError",
148
+ message: "Failed to process translations",
149
+ },
150
+ ]),
151
+ },
152
+ },
153
+ };
154
+ }
155
+
156
+ for (let i = 0; i < translations.length; i++) {
157
+ const { article } = translations[i];
158
+ const locale = selectedLocales[i];
159
+
160
+ if (article) {
161
+ blueprint.translations = {
162
+ ...blueprint.translations,
163
+ [locale]: {
164
+ id: article.id,
165
+ slug: article.slug,
166
+ visibility: article.visibility,
167
+ },
168
+ };
169
+ }
170
+ }
171
+
172
+ const revisionInputs = [];
173
+
174
+ for (let i = 0; i < translations.length; i++) {
175
+ const { errors, article } = translations[i];
176
+ const locale = selectedLocales[i];
113
177
 
114
178
  if (errors) {
115
179
  return {
@@ -128,13 +192,15 @@ const create = async (ctx) => {
128
192
 
129
193
  if (article) {
130
194
  translationIds.push(`${locale}:${article.id}`);
195
+ revisionInputs.push({ articleId: article.id, locale });
131
196
  }
197
+ }
132
198
 
133
- const { errors: revisionErrors } = await ctx.client.articleRevisions.create(
134
- article.id,
135
- locale
136
- );
199
+ const revisionResults =
200
+ await ctx.client.articleRevisions.createBatch(revisionInputs);
137
201
 
202
+ for (let i = 0; i < revisionResults.length; i++) {
203
+ const { errors: revisionErrors } = revisionResults[i];
138
204
  if (revisionErrors) {
139
205
  return {
140
206
  assigns: {
@@ -144,7 +210,7 @@ const create = async (ctx) => {
144
210
  languages: formLanguages,
145
211
  },
146
212
  blueprint,
147
- targetLocale: locale,
213
+ targetLocale: selectedLocales[i],
148
214
  errors: {
149
215
  resource: ctx.client.articles.formatErrors(revisionErrors),
150
216
  },
@@ -78,6 +78,27 @@ const create = async (ctx) => {
78
78
  const originalLocale = ctx.locale;
79
79
  const blueprint = await ctx.client.events.find(parent_id, ctx.locale);
80
80
 
81
+ if (!ctx.resourceParams?.resource?.languages) {
82
+ return {
83
+ assigns: {
84
+ resource: {
85
+ translations: blueprint.translations || {},
86
+ languages: [],
87
+ },
88
+ blueprint,
89
+ targetLocale: originalLocale,
90
+ errors: {
91
+ resource: ctx.client.events.formatErrors([
92
+ {
93
+ key: "languages",
94
+ message: "select at least one language for translation",
95
+ },
96
+ ]),
97
+ },
98
+ },
99
+ };
100
+ }
101
+
81
102
  const formLanguages = Array.isArray(ctx.resourceParams.resource.languages)
82
103
  ? ctx.resourceParams.resource.languages
83
104
  : [ctx.resourceParams.resource.languages].filter(Boolean);
@@ -100,18 +121,61 @@ const create = async (ctx) => {
100
121
  tags: [],
101
122
  };
102
123
 
124
+ const resource = ctx.client.events.deepMerge(params, {
125
+ visibility: "private",
126
+ });
127
+
103
128
  const translationIds = [];
104
129
 
105
- for (const locale of selectedLocales) {
106
- const resource = ctx.client.events.deepMerge(params, {
107
- visibility: "private",
108
- });
130
+ const translations = await ctx.client.events.translateBatch(
131
+ parent_id,
132
+ selectedLocales,
133
+ resource
134
+ );
109
135
 
110
- const { errors, event } = await ctx.client.events.translate(
111
- parent_id,
112
- locale,
113
- resource
114
- );
136
+ if (!translations) {
137
+ return {
138
+ assigns: {
139
+ resource: {
140
+ ...resource,
141
+ translations: blueprint.translations || {},
142
+ languages: formLanguages,
143
+ },
144
+ blueprint,
145
+ targetLocale: originalLocale,
146
+ errors: {
147
+ resource: ctx.client.events.formatErrors([
148
+ {
149
+ key: "generalError",
150
+ message: "Failed to process translations",
151
+ },
152
+ ]),
153
+ },
154
+ },
155
+ };
156
+ }
157
+
158
+ for (let i = 0; i < translations.length; i++) {
159
+ const { event } = translations[i];
160
+ const locale = selectedLocales[i];
161
+
162
+ if (event) {
163
+ blueprint.translations = {
164
+ ...blueprint.translations,
165
+ [locale]: {
166
+ id: event.id,
167
+ slug: event.slug,
168
+ visibility: event.visibility,
169
+ },
170
+ };
171
+ }
172
+ }
173
+
174
+ const revisionInputs = [];
175
+
176
+ for (let i = 0; i < translations.length; i++) {
177
+ const { errors, event } = translations[i];
178
+ const locale = selectedLocales[i];
115
179
 
116
180
  if (errors) {
117
181
  return {
@@ -130,13 +194,15 @@ const create = async (ctx) => {
130
194
 
131
195
  if (event) {
132
196
  translationIds.push(`${locale}:${event.id}`);
197
+ revisionInputs.push({ eventId: event.id, locale });
133
198
  }
199
+ }
134
200
 
135
- const { errors: revisionErrors } = await ctx.client.eventRevisions.create(
136
- event.id,
137
- locale
138
- );
201
+ const revisionResults =
202
+ await ctx.client.eventRevisions.createBatch(revisionInputs);
139
203
 
204
+ for (let i = 0; i < revisionResults.length; i++) {
205
+ const { errors: revisionErrors } = revisionResults[i];
140
206
  if (revisionErrors) {
141
207
  return {
142
208
  assigns: {
@@ -146,7 +212,7 @@ const create = async (ctx) => {
146
212
  languages: formLanguages,
147
213
  },
148
214
  blueprint,
149
- targetLocale: locale,
215
+ targetLocale: selectedLocales[i],
150
216
  errors: {
151
217
  resource: ctx.client.events.formatErrors(revisionErrors),
152
218
  },
@@ -78,6 +78,27 @@ const create = async (ctx) => {
78
78
  const originalLocale = ctx.locale;
79
79
  const blueprint = await ctx.client.pages.find(parent_id, originalLocale);
80
80
 
81
+ if (!ctx.resourceParams?.resource?.languages) {
82
+ return {
83
+ assigns: {
84
+ resource: {
85
+ translations: blueprint.translations || {},
86
+ languages: [],
87
+ },
88
+ blueprint,
89
+ targetLocale: originalLocale,
90
+ errors: {
91
+ resource: ctx.client.pages.formatErrors([
92
+ {
93
+ key: "languages",
94
+ message: "select at least one language for translation",
95
+ },
96
+ ]),
97
+ },
98
+ },
99
+ };
100
+ }
101
+
81
102
  const formLanguages = Array.isArray(ctx.resourceParams.resource.languages)
82
103
  ? ctx.resourceParams.resource.languages
83
104
  : [ctx.resourceParams.resource.languages].filter(Boolean);
@@ -95,23 +116,66 @@ const create = async (ctx) => {
95
116
  slug: blueprint.slug,
96
117
  };
97
118
 
119
+ const resource = ctx.client.pages.deepMerge(
120
+ {
121
+ settings: blueprint.settings,
122
+ widgets: blueprint.widgets,
123
+ categories: blueprint.categories.map((category) => category.id),
124
+ },
125
+ params
126
+ );
127
+
98
128
  const translationIds = [];
99
129
 
100
- for (const locale of selectedLocales) {
101
- const resource = ctx.client.pages.deepMerge(
102
- {
103
- settings: blueprint.settings,
104
- widgets: blueprint.widgets,
105
- categories: blueprint.categories.map((category) => category.id),
130
+ const translations = await ctx.client.pages.translateBatch(
131
+ parent_id,
132
+ selectedLocales,
133
+ resource
134
+ );
135
+
136
+ if (!translations) {
137
+ return {
138
+ assigns: {
139
+ resource: {
140
+ ...resource,
141
+ translations: blueprint.translations || {},
142
+ languages: formLanguages,
143
+ },
144
+ blueprint,
145
+ targetLocale: originalLocale,
146
+ errors: {
147
+ resource: ctx.client.pages.formatErrors([
148
+ {
149
+ key: "generalError",
150
+ message: "Failed to process translations",
151
+ },
152
+ ]),
153
+ },
106
154
  },
107
- params
108
- );
155
+ };
156
+ }
109
157
 
110
- const { errors, page } = await ctx.client.pages.translate(
111
- parent_id,
112
- locale,
113
- resource
114
- );
158
+ for (let i = 0; i < translations.length; i++) {
159
+ const { page } = translations[i];
160
+ const locale = selectedLocales[i];
161
+
162
+ if (page) {
163
+ blueprint.translations = {
164
+ ...blueprint.translations,
165
+ [locale]: {
166
+ id: page.id,
167
+ slug: page.slug,
168
+ visibility: page.visibility,
169
+ },
170
+ };
171
+ }
172
+ }
173
+
174
+ const revisionInputs = [];
175
+
176
+ for (let i = 0; i < translations.length; i++) {
177
+ const { errors, page } = translations[i];
178
+ const locale = selectedLocales[i];
115
179
 
116
180
  if (errors) {
117
181
  return {
@@ -130,13 +194,15 @@ const create = async (ctx) => {
130
194
 
131
195
  if (page) {
132
196
  translationIds.push(`${locale}:${page.id}`);
197
+ revisionInputs.push({ pageId: page.id, locale });
133
198
  }
199
+ }
134
200
 
135
- const { errors: revisionErrors } = await ctx.client.pageRevisions.create(
136
- page.id,
137
- locale
138
- );
201
+ const revisionResults =
202
+ await ctx.client.pageRevisions.createBatch(revisionInputs);
139
203
 
204
+ for (let i = 0; i < revisionResults.length; i++) {
205
+ const { errors: revisionErrors } = revisionResults[i];
140
206
  if (revisionErrors) {
141
207
  return {
142
208
  assigns: {
@@ -146,7 +212,7 @@ const create = async (ctx) => {
146
212
  languages: formLanguages,
147
213
  },
148
214
  blueprint,
149
- targetLocale: locale,
215
+ targetLocale: selectedLocales[i],
150
216
  errors: {
151
217
  resource: ctx.client.pages.formatErrors(revisionErrors),
152
218
  },
@@ -33,12 +33,12 @@ const AuthHelper = {
33
33
  auth_callback_url,
34
34
  }) => {
35
35
  if (
36
- [
36
+ ![
37
37
  gateway_url,
38
38
  gateway_project,
39
39
  gateway_user_pool,
40
40
  auth_callback_url,
41
- ].join() === ""
41
+ ].every(Boolean)
42
42
  )
43
43
  throw new Error("Invalid ENVs for authentication configuration");
44
44
 
@@ -0,0 +1,96 @@
1
+ import Tags from "../../../cloud/Tags";
2
+ import TagsHandler from "../../../handlers/TagsHandler";
3
+ import Factory from "../../support/factory";
4
+
5
+ describe("Tags Handler", () => {
6
+ function buildCtx(tag) {
7
+ return {
8
+ client: {
9
+ tags: new Tags(() => ({}), "token"),
10
+ },
11
+ resourceParams: {
12
+ tag: {
13
+ name: tag.name,
14
+ slug: tag.slug,
15
+ },
16
+ },
17
+ locale: "en",
18
+ };
19
+ }
20
+
21
+ test("create successful", async () => {
22
+ const tag = Factory.build("tag", {
23
+ name: "Test Tag",
24
+ slug: "test-tag",
25
+ });
26
+ const ctx = buildCtx(tag);
27
+
28
+ const mockedCreate = jest
29
+ .spyOn(ctx.client.tags, "create")
30
+ .mockImplementation(() => ({ tag, errors: null }));
31
+
32
+ const result = await TagsHandler.create(ctx);
33
+
34
+ expect(mockedCreate).toHaveBeenCalledWith(
35
+ {
36
+ name: tag.name,
37
+ slug: tag.slug,
38
+ },
39
+ "en"
40
+ );
41
+
42
+ expect(result).toEqual({
43
+ redirect: "/en/tags",
44
+ });
45
+ });
46
+
47
+ test("create failed", async () => {
48
+ const tag = Factory.build("tag", {
49
+ name: "Test Tag",
50
+ slug: "test-tag",
51
+ });
52
+ const ctx = buildCtx(tag);
53
+
54
+ const errors = [{ key: "name", message: "already taken" }];
55
+
56
+ const mockedCreate = jest
57
+ .spyOn(ctx.client.tags, "create")
58
+ .mockImplementation(() => ({
59
+ tag: null,
60
+ errors,
61
+ }));
62
+
63
+ const mockedFormatErrors = jest
64
+ .spyOn(ctx.client.tags, "formatErrors")
65
+ .mockImplementation(() => ({
66
+ name: "already taken",
67
+ }));
68
+
69
+ const result = await TagsHandler.create(ctx);
70
+
71
+ expect(mockedCreate).toHaveBeenCalledWith(
72
+ {
73
+ name: tag.name,
74
+ slug: tag.slug,
75
+ },
76
+ "en"
77
+ );
78
+
79
+ expect(mockedFormatErrors).toHaveBeenCalledWith(errors);
80
+
81
+ expect(result).toEqual({
82
+ assigns: {
83
+ tag: {
84
+ name: tag.name,
85
+ slug: tag.slug,
86
+ },
87
+ errors: {
88
+ tag: {
89
+ name: "already taken",
90
+ },
91
+ },
92
+ _translations: [],
93
+ },
94
+ });
95
+ });
96
+ });
@@ -0,0 +1,25 @@
1
+ import Tags from "../../../cloud/Tags";
2
+ import TagsHandler from "../../../handlers/TagsHandler";
3
+
4
+ describe("Tags Handler", () => {
5
+ function buildCtx() {
6
+ return {
7
+ client: {
8
+ tags: new Tags(() => ({}), "token"),
9
+ },
10
+ };
11
+ }
12
+
13
+ test("create_form", () => {
14
+ const ctx = buildCtx();
15
+ const result = TagsHandler.create_form(ctx);
16
+
17
+ expect(result).toEqual({
18
+ assigns: {
19
+ tag: {},
20
+ errors: {},
21
+ _translations: [],
22
+ },
23
+ });
24
+ });
25
+ });
@@ -0,0 +1,51 @@
1
+ import Tags from "../../../cloud/Tags";
2
+ import TagsHandler from "../../../handlers/TagsHandler";
3
+ import Factory from "../../support/factory";
4
+
5
+ describe("Tags Handler", () => {
6
+ function buildCtx() {
7
+ return {
8
+ client: {
9
+ tags: new Tags(() => ({}), "token"),
10
+ },
11
+ params: { id: "1" },
12
+ locale: "en",
13
+ };
14
+ }
15
+
16
+ test("destroy successful", async () => {
17
+ const tag = Factory.build("tag");
18
+ const ctx = buildCtx();
19
+
20
+ const mockedDelete = jest
21
+ .spyOn(ctx.client.tags, "delete")
22
+ .mockImplementation(() => ({ tag, errors: null }));
23
+
24
+ const result = await TagsHandler.destroy(ctx);
25
+
26
+ expect(mockedDelete).toHaveBeenCalledWith("1", "en");
27
+ expect(result).toEqual({
28
+ headers: {
29
+ "HX-redirect": "/en/tags",
30
+ },
31
+ json: {},
32
+ });
33
+ });
34
+
35
+ test("destroy failed", async () => {
36
+ const ctx = buildCtx();
37
+
38
+ const mockedDelete = jest
39
+ .spyOn(ctx.client.tags, "delete")
40
+ .mockImplementation(() => ({
41
+ tag: null,
42
+ errors: [{ key: "generalError", message: "Not found" }],
43
+ }));
44
+
45
+ expect(() => TagsHandler.destroy(ctx)).rejects.toMatch(
46
+ "Failed to delete tag #1. WTF?"
47
+ );
48
+
49
+ expect(mockedDelete).toHaveBeenCalledWith("1", "en");
50
+ });
51
+ });
@@ -0,0 +1,60 @@
1
+ import Tags from "../../../cloud/Tags";
2
+ import TagsHandler from "../../../handlers/TagsHandler";
3
+ import Factory from "../../support/factory";
4
+
5
+ describe("Tags Handler", () => {
6
+ function buildCtx() {
7
+ return {
8
+ client: {
9
+ tags: new Tags(() => ({}), "token"),
10
+ },
11
+ router: {
12
+ req: {
13
+ param: () => ({ id: "1" }),
14
+ },
15
+ },
16
+ locale: "en",
17
+ };
18
+ }
19
+
20
+ test("edit_form", async () => {
21
+ const tag = Factory.build("tag", { id: "1", name: "Test Tag" });
22
+ const ctx = buildCtx();
23
+
24
+ const mockedFind = jest
25
+ .spyOn(ctx.client.tags, "find")
26
+ .mockImplementation(() => tag);
27
+
28
+ const result = await TagsHandler.edit_form(ctx);
29
+
30
+ expect(mockedFind).toHaveBeenCalledWith("1", "en");
31
+ expect(result).toEqual({
32
+ assigns: {
33
+ id: "1",
34
+ tag: tag,
35
+ errors: {},
36
+ _translations: [],
37
+ },
38
+ });
39
+ });
40
+
41
+ test("edit_form when tag does not exist", async () => {
42
+ const ctx = buildCtx();
43
+
44
+ const mockedFind = jest
45
+ .spyOn(ctx.client.tags, "find")
46
+ .mockImplementation(() => null);
47
+
48
+ const result = await TagsHandler.edit_form(ctx);
49
+
50
+ expect(mockedFind).toHaveBeenCalledWith("1", "en");
51
+ expect(result).toEqual({
52
+ assigns: {
53
+ id: "1",
54
+ tag: null,
55
+ errors: {},
56
+ _translations: [],
57
+ },
58
+ });
59
+ });
60
+ });
@@ -0,0 +1,37 @@
1
+ import Tags from "../../../cloud/Tags";
2
+ import TagsHandler from "../../../handlers/TagsHandler";
3
+ import Factory from "../../support/factory";
4
+
5
+ describe("Tags Handler", () => {
6
+ function buildCtx() {
7
+ return {
8
+ client: {
9
+ tags: new Tags(() => ({}), "token"),
10
+ },
11
+ query: { foo: "bar" },
12
+ locale: "en",
13
+ };
14
+ }
15
+
16
+ test("index", async () => {
17
+ const tags = [
18
+ Factory.build("tag", { id: "1", name: "First Tag" }),
19
+ Factory.build("tag", { id: "2", name: "Second Tag" }),
20
+ ];
21
+
22
+ const ctx = buildCtx();
23
+
24
+ const mockedList = jest
25
+ .spyOn(ctx.client.tags, "list")
26
+ .mockImplementation(() => tags);
27
+
28
+ const result = await TagsHandler.index(ctx);
29
+
30
+ expect(mockedList).toHaveBeenCalledWith({ foo: "bar" }, "en");
31
+ expect(result).toEqual({
32
+ assigns: {
33
+ items: tags,
34
+ },
35
+ });
36
+ });
37
+ });