@raketa-cloud/admin 0.0.14 → 0.0.15

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 (59) hide show
  1. package/package.json +1 -1
  2. package/src/cloud/ArticleChanges.js +15 -0
  3. package/src/cloud/ArticleRevisions.js +152 -0
  4. package/src/cloud/Articles.js +133 -0
  5. package/src/cloud/documents/articlesDocuments.js +109 -469
  6. package/src/cloud/documents/common.js +10 -0
  7. package/src/handlers/article_revisions/ArticleRevisionsHandler.js +49 -0
  8. package/src/handlers/article_revisions/CodeHandler.js +90 -0
  9. package/src/handlers/article_revisions/GeneralHandler.js +95 -0
  10. package/src/handlers/article_revisions/LocksHandler.js +54 -0
  11. package/src/handlers/article_revisions/MetaHandler.js +90 -0
  12. package/src/handlers/article_revisions/PublishesHandler.js +90 -0
  13. package/src/handlers/article_revisions/RevertsHandler.js +28 -0
  14. package/src/handlers/article_revisions/TagHandler.js +89 -0
  15. package/src/handlers/article_revisions/UnlocksHandler.js +22 -0
  16. package/src/handlers/article_revisions/VisualHandler.js +84 -0
  17. package/src/handlers/article_revisions/WidgetHandler.js +47 -0
  18. package/src/handlers/articles/ArticlesHandler.js +119 -0
  19. package/src/handlers/articles/ClonesHandler.js +17 -0
  20. package/src/handlers/articles/DeletesHandler.js +68 -0
  21. package/src/handlers/articles/TranslationsHandler.js +103 -0
  22. package/src/handlers/articles/URLsHandler.js +71 -0
  23. package/src/handlers/pages/PagesHandler.js +4 -2
  24. package/src/handlers/pages/TranslationsHandler.js +7 -5
  25. package/src/index.js +22 -0
  26. package/src/routeArticles.js +65 -0
  27. package/src/routeStandard.js +2 -0
  28. package/src/setupCloud.js +12 -0
  29. package/src/tests/handlers/articles/ArticlesHandler.test/create.test.js +144 -0
  30. package/src/tests/handlers/articles/ArticlesHandler.test/create_form.test.js +41 -0
  31. package/src/tests/handlers/articles/ArticlesHandler.test/index.test.js +49 -0
  32. package/src/tests/handlers/articles/ArticlesHandler.test/show.test.js +105 -0
  33. package/src/tests/handlers/articles/ConesHandler.test/create.test.js +54 -0
  34. package/src/tests/handlers/articles/DeletesHandler.test/create.test.js +110 -0
  35. package/src/tests/handlers/articles/DeletesHandler.test/create_form.test.js +54 -0
  36. package/src/tests/handlers/articles/URLsHandler.test/create.test.js +155 -0
  37. package/src/tests/handlers/articles/URLsHandler.test/create_form.test.js +51 -0
  38. package/src/tests/handlers/pages/PagesHandler.test/create.test.js +3 -3
  39. package/src/utils.js +46 -0
  40. package/src/views/article_revisions/_nav.ejs +40 -0
  41. package/src/views/article_revisions/codes/edit_form.ejs +39 -0
  42. package/src/views/article_revisions/general/edit_form.ejs +70 -0
  43. package/src/views/article_revisions/locks/create_form.ejs +35 -0
  44. package/src/views/article_revisions/meta/edit_form.ejs +41 -0
  45. package/src/views/article_revisions/publishes/_form.ejs +57 -0
  46. package/src/views/article_revisions/publishes/create_form.ejs +10 -0
  47. package/src/views/article_revisions/tags/edit_form.ejs +64 -0
  48. package/src/views/article_revisions/visuals/edit_form.ejs +42 -0
  49. package/src/views/articles/_form.ejs +86 -0
  50. package/src/views/articles/create_form.ejs +9 -0
  51. package/src/views/articles/deletes/create_form.ejs +55 -0
  52. package/src/views/articles/edit_form.ejs +3 -0
  53. package/src/views/articles/index.ejs +161 -0
  54. package/src/views/articles/show.ejs +281 -0
  55. package/src/views/articles/translations/_form.ejs +77 -0
  56. package/src/views/articles/translations/create_form.ejs +11 -0
  57. package/src/views/articles/urls/create_form.ejs +31 -0
  58. package/src/views/page_revisions/locks/create_form.ejs +1 -1
  59. package/test/factory.js +37 -0
@@ -0,0 +1,144 @@
1
+ import ArticlesHandler from "../../../../handlers/articles/ArticlesHandler";
2
+ import Articles from "../../../../cloud/Articles";
3
+ import Tags from "../../../../cloud/Tags";
4
+ import { testConfig } from "../../../../../test/handlersHelper";
5
+ import Factory from "../../../../../test/factory";
6
+
7
+ describe("Articles Handler", () => {
8
+ function buildCtx(params) {
9
+ return {
10
+ client: {
11
+ articles: new Articles(testConfig, "token"),
12
+ tags: new Tags(testConfig, "token"),
13
+ },
14
+ resourceParams: {
15
+ article: params,
16
+ },
17
+ locale: "en",
18
+ };
19
+ }
20
+
21
+ test("create successfull", async () => {
22
+ const tag = Factory.build("tag");
23
+ const params = {
24
+ title: "Article title",
25
+ slug: "url",
26
+ visibility: "public",
27
+ publish_at: "2024-10-10T06:58",
28
+ layout: "default",
29
+ meta_title: "Meta title",
30
+ meta_description: "Meta description",
31
+ meta_image: JSON.stringify({ id: 42 }),
32
+ username: "admin",
33
+ password: "admin123!@#",
34
+ tags: [tag.id],
35
+ };
36
+
37
+ const ctx = buildCtx(params);
38
+
39
+ const mockedCreate = jest
40
+ .spyOn(ctx.client.articles, "create")
41
+ .mockImplementation(() => ({ article: {}, errors: null }));
42
+
43
+ const result = await ArticlesHandler.create(ctx);
44
+
45
+ expect(mockedCreate).toHaveBeenCalledWith(
46
+ {
47
+ title: params.title,
48
+ slug: params.slug,
49
+ visibility: params.visibility,
50
+ publishAt: "2024-10-10T03:58:00Z",
51
+ settings: {
52
+ layout: params.layout,
53
+ meta: {
54
+ title: params.meta_title,
55
+ description: params.meta_description,
56
+ image: JSON.parse(params.meta_image || "{}"),
57
+ },
58
+ credentials: {
59
+ user: params.username,
60
+ password: params.password,
61
+ },
62
+ },
63
+ widgets: [],
64
+ tags: params.tags,
65
+ categories: [],
66
+ },
67
+ "en",
68
+ "article"
69
+ );
70
+
71
+ expect(result).toEqual({
72
+ redirect: "/en/articles",
73
+ });
74
+ });
75
+
76
+ test("create failed", async () => {
77
+ const tag = Factory.build("tag");
78
+ const params = {
79
+ title: "Article title",
80
+ slug: "",
81
+ publish_at: "2024-10-10T06:58",
82
+ visibility: "public",
83
+ layout: "default",
84
+ meta_title: "Meta title",
85
+ meta_description: "Meta description",
86
+ meta_image: JSON.stringify({ id: 42 }),
87
+ username: "admin",
88
+ password: "admin123!@#",
89
+ tags: [tag.id],
90
+ };
91
+
92
+ const ctx = buildCtx(params);
93
+
94
+ const mockedTagsList = jest
95
+ .spyOn(ctx.client.tags, "all")
96
+ .mockImplementation(() => ({ list: [tag] }));
97
+
98
+ const mockedCreate = jest
99
+ .spyOn(ctx.client.articles, "create")
100
+ .mockImplementation(() => ({
101
+ article: {},
102
+ errors: [{ key: "slug", message: "cannot be blank" }],
103
+ }));
104
+
105
+ const result = await ArticlesHandler.create(ctx);
106
+
107
+ expect(mockedTagsList).toHaveBeenCalledWith("en");
108
+ expect(mockedCreate).toHaveBeenCalledWith(
109
+ {
110
+ title: params.title,
111
+ slug: "",
112
+ visibility: params.visibility,
113
+ publishAt: "2024-10-10T03:58:00Z",
114
+ settings: {
115
+ layout: params.layout,
116
+ meta: {
117
+ title: params.meta_title,
118
+ description: params.meta_description,
119
+ image: JSON.parse(params.meta_image || "{}"),
120
+ },
121
+ credentials: {
122
+ user: params.username,
123
+ password: params.password,
124
+ },
125
+ },
126
+ widgets: [],
127
+ tags: params.tags,
128
+ categories: [],
129
+ },
130
+ "en",
131
+ "article"
132
+ );
133
+
134
+ expect(result).toEqual({
135
+ assigns: {
136
+ article: { ...params, meta_image: JSON.parse(params.meta_image) },
137
+ tags: [tag],
138
+ errors: { article: { slug: "cannot be blank" } },
139
+ visibilityOptions: ctx.client.articles.visibilityOptions(),
140
+ _translations: [],
141
+ },
142
+ });
143
+ });
144
+ });
@@ -0,0 +1,41 @@
1
+ import ArticlesHandler from "../../../../handlers/articles/ArticlesHandler";
2
+ import Articles from "../../../../cloud/Articles";
3
+ import Tags from "../../../../cloud/Tags";
4
+ import { testConfig } from "../../../../../test/handlersHelper";
5
+ import Factory from "../../../../../test/factory";
6
+
7
+ describe("Articles Handler", () => {
8
+ function buildCtx() {
9
+ return {
10
+ client: {
11
+ articles: new Articles(testConfig, "token"),
12
+ tags: new Tags(testConfig, "token"),
13
+ },
14
+ query: { foo: "bar" },
15
+ locale: "en",
16
+ };
17
+ }
18
+
19
+ test("index", async () => {
20
+ const tags = [Factory.build("tag")];
21
+ const ctx = buildCtx();
22
+
23
+ const mockedTagList = jest
24
+ .spyOn(ctx.client.tags, "all")
25
+ .mockImplementation(() => ({ list: tags, pagination: null }));
26
+
27
+ const result = await ArticlesHandler.create_form(ctx);
28
+
29
+ expect(mockedTagList).toHaveBeenCalledWith("en");
30
+
31
+ expect(result).toEqual({
32
+ assigns: {
33
+ article: { ...ctx.client.articles.defaults(), meta_image: {} },
34
+ tags: tags,
35
+ errors: {},
36
+ visibilityOptions: ctx.client.articles.visibilityOptions(),
37
+ _translations: [],
38
+ },
39
+ });
40
+ });
41
+ });
@@ -0,0 +1,49 @@
1
+ import ArticlesHandler from "../../../../handlers/articles/ArticlesHandler";
2
+ import Articles from "../../../../cloud/Articles";
3
+ import Tags from "../../../../cloud/Tags";
4
+ import { testConfig } from "../../../../../test/handlersHelper";
5
+ import Factory from "../../../../../test/factory";
6
+
7
+ describe("Articles Handler", () => {
8
+ function buildCtx() {
9
+ return {
10
+ client: {
11
+ articles: new Articles(testConfig, "token"),
12
+ tags: new Tags(testConfig, "token"),
13
+ },
14
+ query: { foo: "bar" },
15
+ locale: "en",
16
+ };
17
+ }
18
+
19
+ test("index", async () => {
20
+ const article = Factory.build("article");
21
+ const pagination = Factory.build("pagination");
22
+ const tags = [Factory.build("tag")];
23
+ const ctx = buildCtx();
24
+
25
+ const mockedArticleList = jest
26
+ .spyOn(ctx.client.articles, "list")
27
+ .mockImplementation(() => ({ list: [article], pagination }));
28
+
29
+ const mockedTagList = jest
30
+ .spyOn(ctx.client.tags, "all")
31
+ .mockImplementation(() => ({ list: tags, pagination: null }));
32
+
33
+ const result = await ArticlesHandler.index(ctx);
34
+
35
+ expect(mockedArticleList).toHaveBeenCalledWith({ foo: "bar" }, "en");
36
+ expect(mockedTagList).toHaveBeenCalledWith("en");
37
+
38
+ expect(result).toEqual({
39
+ assigns: {
40
+ items: {
41
+ list: [article],
42
+ pagination: pagination,
43
+ },
44
+ tags: tags,
45
+ visibilityOptions: ctx.client.articles.visibilityOptions(),
46
+ },
47
+ });
48
+ });
49
+ });
@@ -0,0 +1,105 @@
1
+ import ArticlesHandler from "../../../../handlers/articles/ArticlesHandler";
2
+ import Articles from "../../../../cloud/Articles";
3
+ import ArticleChanges from "../../../../cloud/ArticleChanges";
4
+ import ArticleRevisions from "../../../../cloud/ArticleRevisions";
5
+ import { testConfig } from "../../../../../test/handlersHelper";
6
+ import Factory from "../../../../../test/factory";
7
+
8
+ describe("Articles Handler", () => {
9
+ function buildCtx(article) {
10
+ return {
11
+ client: {
12
+ articles: new Articles(testConfig, "token"),
13
+ articleChanges: new ArticleChanges(testConfig, "token"),
14
+ articleRevisions: new ArticleRevisions(testConfig, "token"),
15
+ },
16
+ params: { id: article.id },
17
+ locale: "en",
18
+ locales: {
19
+ en: {
20
+ name: "English",
21
+ code: "en-gb",
22
+ flag: "/static/_assets/flags/gb.svg",
23
+ },
24
+ bg: {
25
+ name: "Bulgarian",
26
+ code: "bg",
27
+ flag: "/static/_assets/flags/bg.svg",
28
+ },
29
+ de: {
30
+ name: "German",
31
+ code: "de",
32
+ flag: "/static/_assets/flags/de.svg",
33
+ },
34
+ },
35
+ };
36
+ }
37
+
38
+ test("show for existing article", async () => {
39
+ const article = Factory.build("article", {
40
+ translations: {
41
+ de: {
42
+ id: 42,
43
+ },
44
+ bg: {
45
+ id: 69,
46
+ },
47
+ },
48
+ });
49
+ const revision = Factory.build("article_revision", {
50
+ originalId: article.id,
51
+ });
52
+ const change = Factory.build("article_change");
53
+ const ctx = buildCtx(article);
54
+
55
+ const mockedArticleFind = jest
56
+ .spyOn(ctx.client.articles, "find")
57
+ .mockImplementation(() => article);
58
+
59
+ const mockedArticleRevisions = jest
60
+ .spyOn(ctx.client.articleRevisions, "all")
61
+ .mockImplementation(() => ({ list: [revision] }));
62
+
63
+ const mockedArticleChanges = jest
64
+ .spyOn(ctx.client.articleChanges, "all")
65
+ .mockImplementation(() => ({ list: [change] }));
66
+
67
+ const result = await ArticlesHandler.show(ctx);
68
+
69
+ expect(mockedArticleFind).toHaveBeenCalledWith(article.id, "en");
70
+ expect(mockedArticleRevisions).toHaveBeenCalledWith(article.id, "en");
71
+ expect(mockedArticleChanges).toHaveBeenCalledWith(article.id, "en");
72
+
73
+ expect(result).toEqual({
74
+ assigns: {
75
+ article,
76
+ revisions: [revision],
77
+ changes: [change],
78
+ _translations: [
79
+ {
80
+ locale: "bg",
81
+ path: "/bg/articles/69",
82
+ },
83
+ {
84
+ locale: "de",
85
+ path: "/de/articles/42",
86
+ },
87
+ ],
88
+ },
89
+ });
90
+ });
91
+
92
+ test("show for non existing article", async () => {
93
+ const ctx = buildCtx({ id: "42" });
94
+
95
+ const mockedArticleFind = jest
96
+ .spyOn(ctx.client.articles, "find")
97
+ .mockImplementation(() => null);
98
+
99
+ expect(async () => {
100
+ await ArticlesHandler.show(ctx);
101
+ }).rejects.toThrow("Missing article #42");
102
+
103
+ expect(mockedArticleFind).toHaveBeenCalledWith("42", "en");
104
+ });
105
+ });
@@ -0,0 +1,54 @@
1
+ import Articles from "../../../../cloud/Articles";
2
+ import ClonesHandler from "../../../../handlers/articles/ClonesHandler";
3
+ import Factory from "../../../../../test/factory";
4
+ import { testConfig } from "../../../../../test/handlersHelper";
5
+
6
+ function buildCtx(article) {
7
+ return {
8
+ client: {
9
+ articles: new Articles(testConfig, "token"),
10
+ },
11
+ params: { parent_id: article.id },
12
+ locale: "en",
13
+ };
14
+ }
15
+
16
+ describe("Clone article", () => {
17
+ test("clone successful", async () => {
18
+ const article = Factory.build("article");
19
+ const ctx = buildCtx(article);
20
+
21
+ const mockClone = jest
22
+ .spyOn(ctx.client.articles, "clone")
23
+ .mockImplementation(() => article);
24
+
25
+ const result = await ClonesHandler.create(ctx);
26
+
27
+ expect(mockClone).toHaveBeenCalledWith(article.id, "en");
28
+
29
+ expect(result).toEqual({
30
+ redirect: "/en/articles",
31
+ });
32
+ });
33
+
34
+ test("clone failed", async () => {
35
+ const article = Factory.build("article");
36
+
37
+ const ctx = buildCtx(article);
38
+
39
+ const mockClone = jest
40
+ .spyOn(ctx.client.articles, "clone")
41
+ .mockImplementation(() => ({
42
+ article: null,
43
+ errors: [{ key: "generalError", message: "some error" }],
44
+ }));
45
+
46
+ expect(async () => {
47
+ await ClonesHandler.create(ctx);
48
+ }).rejects.toThrow(
49
+ `Error cloning article: ${JSON.stringify([{ key: "generalError", message: "some error" }])}`
50
+ );
51
+
52
+ expect(mockClone).toHaveBeenCalledWith(article.id, "en");
53
+ });
54
+ });
@@ -0,0 +1,110 @@
1
+ import DeletesHandler from "../../../../handlers/articles/DeletesHandler";
2
+ import Articles from "../../../../cloud/Articles";
3
+ import Factory from "../../../../../test/factory";
4
+ import { testConfig } from "../../../../../test//handlersHelper";
5
+
6
+ function buildCtx(article, updateSettings) {
7
+ return {
8
+ client: {
9
+ articles: new Articles(testConfig, "token"),
10
+ },
11
+ params: { parent_id: article.id },
12
+ resourceParams: {
13
+ deleteAction: updateSettings,
14
+ },
15
+ locale: "en",
16
+ };
17
+ }
18
+
19
+ describe("Article delete", () => {
20
+ test("create successful", async () => {
21
+ const article = Factory.build("article");
22
+ const updateSettings = {
23
+ redirect: "301",
24
+ destination: "/test-dest",
25
+ };
26
+ const ctx = buildCtx(article, updateSettings);
27
+
28
+ const mockFind = jest
29
+ .spyOn(ctx.client.articles, "find")
30
+ .mockImplementation(() => article);
31
+
32
+ const mockDelete = jest
33
+ .spyOn(ctx.client.articles, "delete")
34
+ .mockImplementation(() => ({ article, errors: null }));
35
+
36
+ const result = await DeletesHandler.create(ctx);
37
+
38
+ expect(mockFind).toHaveBeenCalledWith(article.id, "en");
39
+ expect(mockDelete).toHaveBeenCalledWith(article.id, "en", {
40
+ url: `/${article.slug}`,
41
+ destination: updateSettings.destination,
42
+ httpResponseCode: 301,
43
+ note: "Created via article deletion process",
44
+ });
45
+
46
+ expect(result).toEqual({
47
+ redirect: "/en/articles",
48
+ });
49
+ });
50
+
51
+ test("create failed due to redirect create", async () => {
52
+ const article = Factory.build("article");
53
+ const updateSettings = {
54
+ redirect: "410",
55
+ destination: "/test-dest",
56
+ };
57
+ const ctx = buildCtx(article, updateSettings);
58
+
59
+ const mockFind = jest
60
+ .spyOn(ctx.client.articles, "find")
61
+ .mockImplementation(() => article);
62
+
63
+ const mockDelete = jest
64
+ .spyOn(ctx.client.articles, "delete")
65
+ .mockImplementation(() => ({
66
+ article: null,
67
+ errors: [
68
+ {
69
+ codes: [],
70
+ key: "generalError",
71
+ message: "url: has already been taken",
72
+ },
73
+ ],
74
+ }));
75
+
76
+ const result = await DeletesHandler.create(ctx);
77
+
78
+ expect(mockFind).toHaveBeenCalledWith(article.id, "en");
79
+ expect(mockDelete).toHaveBeenCalledWith(article.id, "en", {
80
+ url: `/${article.slug}`,
81
+ destination: "/",
82
+ httpResponseCode: 410,
83
+ note: "Created via article deletion process",
84
+ });
85
+
86
+ expect(result).toEqual({
87
+ assigns: {
88
+ deleteAction: { ...updateSettings, id: article.id },
89
+ errors: {
90
+ deleteAction: { redirect: "url: has already been taken" },
91
+ },
92
+ _translations: [],
93
+ },
94
+ });
95
+ });
96
+
97
+ test("create for non existing article", async () => {
98
+ const ctx = buildCtx({ id: "42" }, {});
99
+
100
+ const mockFind = jest
101
+ .spyOn(ctx.client.articles, "find")
102
+ .mockImplementation(() => null);
103
+
104
+ expect(async () => {
105
+ await DeletesHandler.create(ctx);
106
+ }).rejects.toThrow("Missing article #42");
107
+
108
+ expect(mockFind).toHaveBeenCalledWith("42", "en");
109
+ });
110
+ });
@@ -0,0 +1,54 @@
1
+ import DeletesHandler from "../../../../handlers/articles/DeletesHandler";
2
+ import Articles from "../../../../cloud/Articles";
3
+ import Factory from "../../../../../test/factory";
4
+ import { testConfig } from "../../../../../test//handlersHelper";
5
+
6
+ function buildCtx(article) {
7
+ return {
8
+ client: {
9
+ articles: new Articles(testConfig, "token"),
10
+ },
11
+ params: { parent_id: article.id },
12
+ locale: "en",
13
+ };
14
+ }
15
+
16
+ describe("Article delete", () => {
17
+ test("create_form for existing article", async () => {
18
+ const article = Factory.build("article");
19
+ const ctx = buildCtx(article);
20
+
21
+ const mockFind = jest
22
+ .spyOn(ctx.client.articles, "find")
23
+ .mockImplementation(() => article);
24
+
25
+ const result = await DeletesHandler.create_form(ctx);
26
+
27
+ expect(mockFind).toHaveBeenCalledWith(article.id, "en");
28
+
29
+ expect(result).toEqual({
30
+ assigns: {
31
+ deleteAction: {
32
+ id: article.id,
33
+ redirect: "",
34
+ },
35
+ errors: {},
36
+ _translations: [],
37
+ },
38
+ });
39
+ });
40
+
41
+ test("create_form for non existing article", async () => {
42
+ const ctx = buildCtx({ id: "42" });
43
+
44
+ const mockFind = jest
45
+ .spyOn(ctx.client.articles, "find")
46
+ .mockImplementation(() => null);
47
+
48
+ expect(async () => {
49
+ await DeletesHandler.create_form(ctx);
50
+ }).rejects.toThrow("Missing article #42");
51
+
52
+ expect(mockFind).toHaveBeenCalledWith("42", "en");
53
+ });
54
+ });