@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/auth/routes.js +64 -86
- package/locales/en.json +20 -2
- package/markup/admin.js +15 -1
- package/package.json +7 -7
- package/public/saltcorn.css +24 -1
- package/public/saltcorn.js +9 -7
- package/routes/actions.js +2 -39
- package/routes/common_lists.js +419 -0
- package/routes/fields.js +23 -6
- package/routes/index.js +4 -0
- package/routes/pageedit.js +12 -105
- package/routes/tables.js +4 -39
- package/routes/tag_entries.js +173 -0
- package/routes/tags.js +266 -0
- package/routes/viewedit.js +19 -136
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
|
+
);
|