@saltcorn/server 0.7.4-beta.1 → 0.7.4-beta.2

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/routes/tables.js CHANGED
@@ -56,6 +56,7 @@ const {
56
56
  } = require("@saltcorn/data/models/discovery");
57
57
  const { getState } = require("@saltcorn/data/db/state");
58
58
  const { cardHeaderTabs } = require("@saltcorn/markup/layout_utils");
59
+ const { tablesList } = require("./common_lists");
59
60
 
60
61
  /**
61
62
  * @type {object}
@@ -129,7 +130,7 @@ const tableForm = async (table, req) => {
129
130
  {
130
131
  label: req.__("Minimum role to read"),
131
132
  sublabel: req.__(
132
- "User must have this role or higher to read rows from the table"
133
+ "User must have this role or higher to read rows from the table, unless they are the owner"
133
134
  ),
134
135
  name: "min_role_read",
135
136
  input_type: "select",
@@ -143,7 +144,7 @@ const tableForm = async (table, req) => {
143
144
  name: "min_role_write",
144
145
  input_type: "select",
145
146
  sublabel: req.__(
146
- "User must have this role or higher to edit or create new rows in the table"
147
+ "User must have this role or higher to edit or create new rows in the table, unless they are the owner"
147
148
  ),
148
149
  options: roleOptions,
149
150
  },
@@ -888,7 +889,6 @@ router.post(
888
889
  if (rest.ownership_field_id === "_formula") {
889
890
  rest.ownership_field_id = null;
890
891
  const fmlValidRes = expressionValidator(rest.ownership_formula);
891
- console.log({ fmlValidRes });
892
892
  if (typeof fmlValidRes === "string") {
893
893
  req.flash(
894
894
  "error",
@@ -1011,42 +1011,7 @@ router.get(
1011
1011
  const rows = await Table.find_with_external({}, { orderBy: "name" });
1012
1012
  const roles = await User.get_roles();
1013
1013
  const getRole = (rid) => roles.find((r) => r.id === rid).role;
1014
- const mainCard =
1015
- rows.length > 0
1016
- ? mkTable(
1017
- [
1018
- {
1019
- label: req.__("Name"),
1020
- key: (r) => link(`/table/${r.id || r.name}`, text(r.name)),
1021
- },
1022
- {
1023
- label: "",
1024
- key: (r) => tableBadges(r, req),
1025
- },
1026
- {
1027
- label: req.__("Access Read/Write"),
1028
- key: (t) =>
1029
- t.external
1030
- ? `${getRole(t.min_role_read)} (read only)`
1031
- : `${getRole(t.min_role_read)}/${getRole(
1032
- t.min_role_write
1033
- )}`,
1034
- },
1035
- {
1036
- label: req.__("Delete"),
1037
- key: (r) =>
1038
- r.name === "users" || r.external
1039
- ? ""
1040
- : post_delete_btn(`/table/delete/${r.id}`, req, r.name),
1041
- },
1042
- ],
1043
- rows,
1044
- { hover: true }
1045
- )
1046
- : div(
1047
- h4(req.__("No tables defined")),
1048
- p(req.__("Tables hold collections of similar data"))
1049
- );
1014
+ const mainCard = await tablesList(rows, req);
1050
1015
  const createCard = div(
1051
1016
  h5(req.__("Create table")),
1052
1017
  a(
@@ -0,0 +1,173 @@
1
+ const {
2
+ a,
3
+ div,
4
+ text,
5
+ button,
6
+ i,
7
+ form,
8
+ select,
9
+ option,
10
+ label,
11
+ } = require("@saltcorn/markup/tags");
12
+
13
+ const Tag = require("@saltcorn/data/models/tag");
14
+ const TagEntry = require("@saltcorn/data/models/tag_entry");
15
+ const Router = require("express-promise-router");
16
+
17
+ const { isAdmin, error_catcher, csrfField } = require("./utils");
18
+
19
+ const Table = require("@saltcorn/data/models/table");
20
+ const View = require("@saltcorn/data/models/view");
21
+ const Page = require("@saltcorn/data/models/page");
22
+ const Trigger = require("@saltcorn/data/models/trigger");
23
+
24
+ const router = new Router();
25
+ module.exports = router;
26
+
27
+ const buildFields = (entryType, formOptions, req) => {
28
+ return Object.entries(formOptions).map(([type, list]) => {
29
+ return div(
30
+ { class: "form-group row" },
31
+ div({ class: "col-sm-2" }, label("type")),
32
+ div(
33
+ { class: "col-sm-10" },
34
+ select(
35
+ { name: "ids", class: "form-control form-select", multiple: true },
36
+ list.map((entry) => {
37
+ return option({ value: entry.id, label: entry.name });
38
+ })
39
+ )
40
+ ),
41
+ div(
42
+ { class: "col-sm-12" },
43
+ button({ type: "submit", class: "btn btn-primary" }, req.__("Save"))
44
+ )
45
+ );
46
+ });
47
+ };
48
+
49
+ const buildForm = (entryType, tag_id, formOptions, req) => {
50
+ return form(
51
+ { action: `/tag-entries/add/${entryType}/${tag_id}`, method: "post" },
52
+ csrfField(req),
53
+ buildFields(entryType, formOptions, req)
54
+ );
55
+ };
56
+
57
+ const formOptions = async (type, tag_id) => {
58
+ const tag = await Tag.findOne({ id: tag_id });
59
+ switch (type) {
60
+ case "tables": {
61
+ const ids = await tag.getTableIds();
62
+ return {
63
+ tables: (await Table.find()).filter(
64
+ (value) => ids.indexOf(value.id) === -1
65
+ ),
66
+ };
67
+ }
68
+ case "views": {
69
+ const ids = await tag.getViewIds();
70
+ return {
71
+ views: (await View.find()).filter(
72
+ (value) => ids.indexOf(value.id) === -1
73
+ ),
74
+ };
75
+ }
76
+ case "pages": {
77
+ const ids = await tag.getPageIds();
78
+ return {
79
+ pages: (await Page.find()).filter(
80
+ (value) => ids.indexOf(value.id) === -1
81
+ ),
82
+ };
83
+ }
84
+ case "trigger": {
85
+ const ids = await tag.getTriggerIds();
86
+ return {
87
+ trigger: (await Trigger.find()).filter(
88
+ (value) => ids.indexOf(value.id) === -1
89
+ ),
90
+ };
91
+ }
92
+ }
93
+ };
94
+
95
+ router.get(
96
+ "/add/:entry_type/:tag_id",
97
+ isAdmin,
98
+ error_catcher(async (req, res) => {
99
+ const { entry_type, tag_id } = req.params;
100
+ res.sendWrap(req.__("Add %s to tag"), {
101
+ above: [
102
+ {
103
+ type: "breadcrumbs",
104
+ crumbs: [{ text: `Tag entry` }],
105
+ },
106
+ {
107
+ type: "card",
108
+ title: `Add entries to tag`,
109
+ contents: buildForm(
110
+ entry_type,
111
+ tag_id,
112
+ await formOptions(entry_type, tag_id),
113
+ req
114
+ ),
115
+ },
116
+ ],
117
+ });
118
+ })
119
+ );
120
+
121
+ const idField = (entryType) => {
122
+ switch (entryType) {
123
+ case "tables": {
124
+ return "table_id";
125
+ }
126
+ case "views": {
127
+ return "view_id";
128
+ }
129
+ case "pages": {
130
+ return "page_id";
131
+ }
132
+ case "trigger": {
133
+ return "trigger_id";
134
+ }
135
+ }
136
+ return null;
137
+ };
138
+
139
+ router.post(
140
+ "/add/:entry_type/:tag_id",
141
+ isAdmin,
142
+ error_catcher(async (req, res) => {
143
+ const { entry_type, tag_id } = req.params;
144
+ const { ids } = req.body;
145
+ if (!ids) {
146
+ req.flash("error", req.__("Please select at least on item"));
147
+ return res.redirect(`/tag-entries/add/${entry_type}/${tag_id}`);
148
+ }
149
+ const fieldName = idField(entry_type);
150
+ const tag = await Tag.findOne({ id: tag_id });
151
+ for (const id of ids) {
152
+ await tag.addEntry({ [fieldName]: id });
153
+ }
154
+ res.redirect(`/tag/${tag_id}?show_list=${entry_type}`);
155
+ })
156
+ );
157
+
158
+ router.post(
159
+ "/remove/:entry_type/:entry_id/:tag_id",
160
+ isAdmin,
161
+ error_catcher(async (req, res) => {
162
+ const { tag_id, entry_type, entry_id } = req.params;
163
+ const fieldName = idField(entry_type);
164
+ const entry = await TagEntry.findOne({ tag_id, [fieldName]: entry_id });
165
+ entry[fieldName] = undefined;
166
+ if (entry.isEmpty()) {
167
+ await entry.delete();
168
+ } else {
169
+ await TagEntry.update(entry.id, { [fieldName]: null });
170
+ }
171
+ res.redirect(`/tag/${tag_id}?show_list=${entry_type}`);
172
+ })
173
+ );
package/routes/tags.js ADDED
@@ -0,0 +1,266 @@
1
+ const { a, text } = require("@saltcorn/markup/tags");
2
+
3
+ const Tag = require("@saltcorn/data/models/tag");
4
+ const Router = require("express-promise-router");
5
+ const Form = require("@saltcorn/data/models/form");
6
+ const User = require("@saltcorn/data/models/user");
7
+
8
+ const { isAdmin, error_catcher, csrfField } = require("./utils");
9
+ const { send_admin_page } = require("../markup/admin");
10
+
11
+ const {
12
+ mkTable,
13
+ post_delete_btn,
14
+ link,
15
+ renderForm,
16
+ } = require("@saltcorn/markup");
17
+
18
+ const {
19
+ tablesList,
20
+ setTableRefs,
21
+ viewsList,
22
+ getPageList,
23
+ getTriggerList,
24
+ } = require("./common_lists");
25
+
26
+ const router = new Router();
27
+ module.exports = router;
28
+
29
+ router.get(
30
+ "/",
31
+ isAdmin,
32
+ error_catcher(async (req, res) => {
33
+ const rows = await Tag.find();
34
+ send_admin_page({
35
+ res,
36
+ req,
37
+ active_sub: "Tags",
38
+ contents: [
39
+ mkTable(
40
+ [
41
+ {
42
+ label: req.__("Tagname"),
43
+ key: (r) =>
44
+ link(`/tag/${r.id || r.name}?show_list=tables`, text(r.name)),
45
+ },
46
+ {
47
+ label: req.__("Delete"),
48
+ key: (r) => post_delete_btn(`/tag/delete/${r.id}`, req, r.name),
49
+ },
50
+ ],
51
+ rows,
52
+ {}
53
+ ),
54
+ a(
55
+ {
56
+ href: `/tag/new`,
57
+ class: "btn btn-primary",
58
+ },
59
+ req.__("Create tag")
60
+ ),
61
+ ],
62
+ });
63
+ })
64
+ );
65
+
66
+ router.get(
67
+ "/new",
68
+ isAdmin,
69
+ error_catcher(async (req, res) => {
70
+ res.sendWrap(req.__(`New tag`), {
71
+ above: [
72
+ {
73
+ type: "card",
74
+ title: req.__(`New tag`),
75
+ contents: renderForm(
76
+ new Form({
77
+ action: "/tag",
78
+ submitLabel: req.__("Create"),
79
+ fields: [
80
+ {
81
+ label: req.__("Tag name"),
82
+ name: "name",
83
+ input_type: "text",
84
+ required: true,
85
+ },
86
+ ],
87
+ }),
88
+ req.csrfToken()
89
+ ),
90
+ },
91
+ ],
92
+ });
93
+ })
94
+ );
95
+
96
+ const headerWithCollapser = (title, cardId, showList) =>
97
+ a(
98
+ {
99
+ class: `card-header-left-collapse ${!showList ? "collapsed" : ""} ps-3`,
100
+ "data-bs-toggle": "collapse",
101
+ href: `#${cardId}`,
102
+ "aria-expanded": "false",
103
+ "aria-controls": cardId,
104
+ role: "button",
105
+ },
106
+ title
107
+ );
108
+
109
+ const isShowList = (showList, listType) => showList === listType;
110
+
111
+ router.get(
112
+ "/:idorname",
113
+ isAdmin,
114
+ error_catcher(async (req, res) => {
115
+ const { idorname } = req.params;
116
+ const { show_list } = req.query;
117
+ const id = parseInt(idorname);
118
+ const tag = await Tag.findOne(id ? { id } : { name: idorname });
119
+ if (!tag) {
120
+ req.flash("error", req.__("Tag not found"));
121
+ return res.redirect(`/tag`);
122
+ }
123
+ const tables = await tag.getTables();
124
+ const views = await tag.getViews();
125
+ await setTableRefs(views);
126
+ const pages = await tag.getPages();
127
+ const trigger = await tag.getTrigger();
128
+ const roles = await User.get_roles();
129
+
130
+ const tablesDomId = "tablesListId";
131
+ const viewsDomId = "viewsListId";
132
+ const pagesDomId = "pagesDomId";
133
+ const triggerDomId = "triggerDomId";
134
+ res.sendWrap(req.__("%s Tag", tag.name), {
135
+ above: [
136
+ {
137
+ type: "breadcrumbs",
138
+ crumbs: [{ text: `Tag: ${tag.name}` }],
139
+ },
140
+ {
141
+ type: "card",
142
+ title: headerWithCollapser(
143
+ req.__("Tables"),
144
+ tablesDomId,
145
+ isShowList(show_list, "tables")
146
+ ),
147
+ contents: [
148
+ await tablesList(tables, req, {
149
+ tagId: tag.id,
150
+ domId: tablesDomId,
151
+ showList: isShowList(show_list, "tables"),
152
+ }),
153
+ a(
154
+ {
155
+ href: `/tag-entries/add/tables/${tag.id}`,
156
+ class: "btn btn-primary",
157
+ },
158
+ req.__("Add tables")
159
+ ),
160
+ ],
161
+ },
162
+ {
163
+ type: "card",
164
+ title: headerWithCollapser(
165
+ req.__("Views"),
166
+ viewsDomId,
167
+ isShowList(show_list, "views")
168
+ ),
169
+ contents: [
170
+ await viewsList(views, req, {
171
+ tagId: tag.id,
172
+ domId: viewsDomId,
173
+ showList: isShowList(show_list, "views"),
174
+ }),
175
+ a(
176
+ {
177
+ href: `/tag-entries/add/views/${tag.id}`,
178
+ class: "btn btn-primary",
179
+ },
180
+ req.__("Add views")
181
+ ),
182
+ ],
183
+ },
184
+ {
185
+ type: "card",
186
+ title: headerWithCollapser(
187
+ req.__("Pages"),
188
+ pagesDomId,
189
+ isShowList(show_list, "pages")
190
+ ),
191
+ contents: [
192
+ getPageList(pages, roles, req, {
193
+ tagId: tag.id,
194
+ domId: pagesDomId,
195
+ showList: isShowList(show_list, "pages"),
196
+ }),
197
+ a(
198
+ {
199
+ href: `/tag-entries/add/pages/${tag.id}`,
200
+ class: "btn btn-primary",
201
+ },
202
+ req.__("Add tages")
203
+ ),
204
+ ],
205
+ },
206
+ {
207
+ type: "card",
208
+ bodyId: "collapseTriggerCard",
209
+ title: headerWithCollapser(
210
+ req.__("Trigger"),
211
+ triggerDomId,
212
+ isShowList(show_list, "trigger")
213
+ ),
214
+ contents: [
215
+ getTriggerList(trigger, req, {
216
+ tagId: tag.id,
217
+ domId: triggerDomId,
218
+ showList: isShowList(show_list, "trigger"),
219
+ }),
220
+ a(
221
+ {
222
+ href: `/tag-entries/add/trigger/${tag.id}`,
223
+ class: "btn btn-primary",
224
+ },
225
+ req.__("Add trigger")
226
+ ),
227
+ ],
228
+ },
229
+ ],
230
+ });
231
+ })
232
+ );
233
+
234
+ // create
235
+ router.post(
236
+ "/",
237
+ isAdmin,
238
+ error_catcher(async (req, res) => {
239
+ const { name } = req.body;
240
+ const tag = await Tag.create({ name });
241
+ req.flash("success", req.__(`Tag %s created`, name));
242
+ res.redirect(`/tag/${tag.id}?show_list=tables`);
243
+ })
244
+ );
245
+
246
+ // delete
247
+ router.post(
248
+ "/delete/:id",
249
+ isAdmin,
250
+ error_catcher(async (req, res) => {
251
+ const { id } = req.params;
252
+ const tag = await Tag.findOne({ id });
253
+ if (!tag) {
254
+ req.flash("error", req.__("Tag not found"));
255
+ return res.redirect("/tag");
256
+ }
257
+ try {
258
+ await tag.delete();
259
+ req.flash("success", req.__("Tag %s deleted", tag.name));
260
+ res.redirect(`/tag`);
261
+ } catch (error) {
262
+ req.flash("error", error.message);
263
+ res.redirect(`/tag`);
264
+ }
265
+ })
266
+ );