• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

geosolutions-it / MapStore2 / 15829819958

23 Jun 2025 04:29PM UTC coverage: 76.979% (+0.03%) from 76.95%
15829819958

Pull #11183

github

web-flow
Merge 124f321fe into 7cde38ac9
Pull Request #11183: #11165: Option to deny app context for normal users

31124 of 48441 branches covered (64.25%)

14 of 16 new or added lines in 5 files covered. (87.5%)

1401 existing lines in 128 files now uncovered.

38752 of 50341 relevant lines covered (76.98%)

36.22 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.56
/web/client/api/GeoStoreDAO.js
1
/**
2
 * Copyright 2015-2016, GeoSolutions Sas.
3
 * All rights reserved.
4
 *
5
 * This source code is licensed under the BSD-style license found in the
6
 * LICENSE file in the root directory of this source tree.
7
*/
8
import { castArray, findIndex, get, has, isArray, merge, omit, pick } from 'lodash';
9

10
import uuidv1 from 'uuid/v1';
11
import xml2js from 'xml2js';
12
const xmlBuilder = new xml2js.Builder();
1✔
13

14
import axios from '../libs/ajax';
15
import ConfigUtils from '../utils/ConfigUtils';
16
import { registerErrorParser } from '../utils/LocaleUtils';
17
import { encodeUTF8 } from '../utils/EncodeUtils';
18

19

20
const generateMetadata = (name = "", description = "", advertised = true) =>
1✔
21
    "<description><![CDATA[" + description + "]]></description>"
21✔
22
    + "<metadata></metadata>"
23
    + "<name><![CDATA[" + (name) + "]]></name>"
24
    + "<advertised>" + advertised + "</advertised>";
25
const createAttributeList = (metadata = {}) => {
1✔
26
    const attributes = metadata.attributes || omit(metadata, ["name", "description", "id", "advertised"]);
21✔
27

28
    const xmlAttrs = Object.keys(attributes).map((key) => {
21✔
29
        return "<attribute><name>" + key + "</name><value><![CDATA[" + attributes[key] + "]]></value><type>STRING</type></attribute>";
9✔
30
    });
31
    let attributesSection = "";
21✔
32
    if (xmlAttrs.length > 0) {
21✔
33
        attributesSection = "<Attributes>" + xmlAttrs.join("") + "</Attributes>";
6✔
34
    }
35
    return attributesSection;
21✔
36
};
37
let parseOptions = (opts) => opts;
97✔
38

39
let parseAdminGroups = (groupsObj) => {
1✔
40
    if (!groupsObj || !groupsObj.UserGroupList || !groupsObj.UserGroupList.UserGroup) return [];
2!
41

42
    const pickFromObj = (obj) => pick(obj, ["id", "groupName", "description"]);
3✔
43
    if (isArray(groupsObj.UserGroupList.UserGroup)) {
2✔
44
        return groupsObj.UserGroupList.UserGroup.filter(obj => !!obj.id).map(pickFromObj);
2✔
45
    }
46
    return [pickFromObj(groupsObj.UserGroupList.UserGroup)];
1✔
47
};
48

49
let parseUserGroups = (groupsObj) => {
1✔
UNCOV
50
    if (!groupsObj || !groupsObj.User || !groupsObj.User.groups || !groupsObj.User.groups.group || !isArray(groupsObj.User.groups.group)) {
×
51
        if (has(groupsObj.User.groups.group, "id", "groupName")) {
×
52
            return [groupsObj.User.groups.group];
×
53
        }
UNCOV
54
        return [];
×
55
    }
UNCOV
56
    return groupsObj.User.groups.group.filter(obj => !!obj.id).map((obj) => pick(obj, ["id", "groupName", "description"]));
×
57
};
58

59
const boolToString = (b) => b ? "true" : "false";
12✔
60

61
const errorParser = {
1✔
62
    /**
63
     * Returns localized message for geostore map errors
64
     * @param  {object} e error object
65
     * @return {object} {title, message}
66
     */
67
    mapsError: e => {
68
        if (e.status === 403 || e.status === 404 || e.status === 409 || e.status === 500) {
4✔
69
            return {
2✔
70
                title: 'map.mapError.errorTitle',
71
                message: 'map.mapError.error' + e.status
72
            };
73
        }
74
        return {
2✔
75
            title: 'map.mapError.errorTitle',
76
            message: 'map.mapError.errorDefault'
77
        };
78
    }
79
};
80

81
registerErrorParser('geostore', {...errorParser});
1✔
82

83
/**
84
 * API for local config
85
 */
86
const Api = {
1✔
87
    createAttributeList,
88
    generateMetadata,
89
    authProviderName: "geostore",
90
    /**
91
     * add the geostore base url, default is /mapstore/rest/geostore/
92
     * @param {object} options axios options
93
     * @return {object} options with baseURL
94
     */
95
    addBaseUrl: function(options) {
96
        return Object.assign({}, options, {baseURL: options && options.baseURL || ConfigUtils.getDefaults().geoStoreUrl});
138✔
97
    },
98
    getData: function(id, options) {
99
        const url = "data/" + id;
18✔
100
        return axios.get(url, this.addBaseUrl(parseOptions(options))).then(function(response) {
18✔
101
            return response.data;
16✔
102
        });
103
    },
104
    getResource: function(resourceId, options) {
UNCOV
105
        return axios.get(
×
106
            "resources/resource/" + resourceId,
UNCOV
107
            this.addBaseUrl(parseOptions(options))).then(function(response) {return response.data; });
×
108
    },
109
    getResourceIdByName: function(category, name, options) {
110
        return axios.get(
11✔
111
            "misc/category/name/" + category + "/resource/name/" + name,
112
            this.addBaseUrl(parseOptions(options))).then(response => get(response, 'data.Resource.id'));
7✔
113
    },
114
    getResourceDataByName: function(category, name, options) {
115
        return axios.get(
2✔
116
            "misc/category/name/" + category + "/resource/name/" + name + "/data",
117
            this.addBaseUrl(parseOptions(options))).then(response => get(response, 'data'));
2✔
118
    },
119
    getShortResource: function(resourceId, options) {
120
        return axios.get(
9✔
121
            "extjs/resource/" + resourceId,
122
            this.addBaseUrl(parseOptions(options))).then(function(response) { return response.data; });
9✔
123
    },
124
    getResourcesByCategory: function(category, query, options) {
125
        const q = query || "*";
5✔
126
        const url = "extjs/search/category/" + category + "/*" + q + "*/thumbnail,details,featured"; // comma-separated list of wanted attributes
5✔
127
        return axios.get(url, this.addBaseUrl(parseOptions(options))).then(function(response) {return response.data; });
5✔
128
    },
129
    createCategory: function(category) {
130
        return axios.post(
1✔
131
            "categories",
132
            `<Category><name>${category}</name></Category>`,
133
            this.addBaseUrl({
134
                headers: {
135
                    'Content-Type': "application/xml"
136
                }
137
            })
138
        ).then(response => response.data);
1✔
139
    },
140
    getUserDetailsBasic: function(username, password, options) {
UNCOV
141
        const url = "users/user/details";
×
142
        return axios.get(url, this.addBaseUrl(merge({
×
143
            auth: {
144
                username: username,
145
                password: password
146
            },
147
            params: {
148
                includeattributes: true
149
            }
150
        }, options))).then(function(response) {
UNCOV
151
            return response.data;
×
152
        });
153
    },
154
    /**
155
     * Gets the user details using the given access token.
156
     * Can be used to finalize access with openID after redirect, using the token passed by the service to retrieve the
157
     * remaining information.
158
     * @param {object} params contains access_token to pass in the bearer header
159
     * @returns {object} user details
160
     */
161
    getUserDetails: function({access_token: accessToken}) {
162
        const url = "users/user/details";
1✔
163
        return axios.get(url, {
1✔
164
            baseURL: "rest/geostore",
165
            headers: {
166
                Authorization: `Bearer ${accessToken}`
167
            },
168
            params: {
169
                includeattributes: true
170
            }
171
        }).then(function(response) {
172
            return response.data;
1✔
173
        });
174
    },
175
    /**
176
     * Gets the tokens for a given identifier, created during openID login.
177
     * @param {string} provider the provider name (e.g. keycloak)
178
     * @param {string} identifier the identifier of the token
179
     * @returns {object}
180
     */
181
    getTokens: function(provider, identifier) {
182
        return axios.get(
1✔
183
            `openid/${provider}/tokens`,
184
            this.addBaseUrl({params: {identifier}})
185
        ).then(response => response.data?.sessionToken);
1✔
186
    },
187
    login: function(username, password, options) {
188
        const url = "session/login";
3✔
189
        let authData;
190
        return axios.post(url, null, this.addBaseUrl(merge((username && password) ? {
3✔
191
            auth: {
192
                username: encodeUTF8(username),
193
                password: password // password is already encoded by axios
194
            }
195
        } : {}, options))).then((response) => {
196
            authData = response.data?.sessionToken ?? response.data;
3✔
197
            return axios.get("users/user/details", this.addBaseUrl(merge({
3✔
198
                headers: {
199
                    'Authorization': 'Bearer ' + authData?.access_token
200
                },
201
                params: {
202
                    includeattributes: true
203
                }
204
            }, options)));
205
        }).then((response) => {
206
            return { ...response.data, ...authData};
3✔
207
        });
208
    },
209
    logout: function() {
UNCOV
210
        const url = "session/logout";
×
211
        return axios.delete(url, this.addBaseUrl({}));
×
212
    },
213
    changePassword: function(user, newPassword, options) {
UNCOV
214
        return axios.put(
×
215
            "users/user/" + user.id, {
216
                User: {
217
                    newPassword
218
                }
219
            },
220
            this.addBaseUrl(merge({
221
                headers: {
222
                    'Content-Type': "application/json"
223
                }
224
            }, options)));
225
    },
226
    updateResourceAttribute: function(resourceId, name, value, type, options) {
227
        return axios.put(
1✔
228
            "resources/resource/" + resourceId + "/attributes/", {
229
                "restAttribute": {
230
                    name,
231
                    value
232
                }
233
            },
234
            this.addBaseUrl(merge({
235
                headers: {
236
                    'Content-Type': "application/json"
237
                }
238
            }, options)));
239
    },
240
    getResourceAttribute: function(resourceId, name, options = {}) {
×
UNCOV
241
        return axios.get(
×
242
            "resources/resource/" + resourceId + "/attributes/" + name,
243
            this.addBaseUrl(merge({
244
                headers: {
245
                    'Content-Type': "application/xml"
246
                }
247
            }, options)));
248
    },
249
    getResourceAttributes: function(resourceId, options = {}) {
13✔
250
        return axios.get(
13✔
251
            "resources/resource/" + resourceId + "/attributes",
252
            this.addBaseUrl({
253
                headers: {
254
                    'Accept': "application/json"
255
                },
256
                ...options
257
            })).then(({ data } = {}) => data)
12!
258
            .then(data => castArray(get(data, "AttributeList.Attribute") || []))
12✔
259
            .then(attributes => attributes || []);
12!
260
    },
261
    /**
262
     * same of getPermissions but clean data properly and returns only the array of rules.
263
     */
264
    getResourcePermissions: function(resourceId, options, withSelector = true) {
6✔
265
        return Api.getPermissions(resourceId, options)
6✔
266
            .then(rl => castArray( withSelector ? get(rl, 'SecurityRuleList.SecurityRule') : rl))
6!
267
            .then(rules => (rules && rules[0] && rules[0] !== "") ? rules : []);
6✔
268
    },
269
    putResourceMetadata: function(resourceId, newName, newDescription, advertised, options) {
UNCOV
270
        return axios.put(
×
271
            "resources/resource/" + resourceId,
272
            "<Resource>" + generateMetadata(newName, newDescription, advertised) + "</Resource>",
273
            this.addBaseUrl(merge({
274
                headers: {
275
                    'Content-Type': "application/xml"
276
                }
277
            }, options)));
278
    },
279
    putResourceMetadataAndAttributes: function(resourceId, metadata, options) {
280
        return axios.put(
9✔
281
            "resources/resource/" + resourceId,
282
            "<Resource>" + generateMetadata(metadata.name, metadata.description, metadata.advertised) + createAttributeList(metadata) + "</Resource>",
283
            this.addBaseUrl(merge({
284
                headers: {
285
                    'Content-Type': "application/xml"
286
                }
287
            }, options)));
288
    },
289
    putResource: function(resourceId, content, options) {
290
        return axios.put(
5✔
291
            "data/" + resourceId,
292
            content,
293
            this.addBaseUrl(merge({
294
                headers: {
295
                    'Content-Type': typeof content === 'string' ? "text/plain; charset=utf-8" : 'application/json; charset=utf-8'
5✔
296
                }
297
            }, options)));
298
    },
299
    writeSecurityRules: function(SecurityRuleList = {}) {
×
300
        return "<SecurityRuleList>" +
8✔
301
        (castArray(SecurityRuleList.SecurityRule) || []).map( rule => {
8!
302
            if (rule.canRead || rule.canWrite) {
6!
303
                if (rule.user) {
6✔
304
                    return "<SecurityRule>"
4✔
305
                        + "<canRead>" + boolToString(rule.canRead || rule.canWrite) + "</canRead>"
4!
306
                        + "<canWrite>" + boolToString(rule.canWrite) + "</canWrite>"
307
                        + "<user><id>" + (rule.user.id || "") + "</id><name>" + (rule.user.name || "") + "</name></user>"
10!
308
                        + "</SecurityRule>";
309
                } else if (rule.group) {
2!
310
                    return "<SecurityRule>"
2✔
311
                        + "<canRead>" + boolToString(rule.canRead || rule.canWrite) + "</canRead>"
2!
312
                        + "<canWrite>" + boolToString(rule.canWrite) + "</canWrite>"
313
                        + "<group><id>" + (rule.group.id || "") + "</id><groupName>" + (rule.group.groupName || "") + "</groupName></group>"
4!
314
                        + "</SecurityRule>";
315
                }
316
                // NOTE: if rule has no group or user, it is skipped
317
                // NOTE: if rule is "no read and no write", it is skipped
318
            }
UNCOV
319
            return "";
×
320
        }).join('') + "</SecurityRuleList>";
321
    },
322
    updateResourcePermissions: function(resourceId, securityRules) {
323
        const payload = Api.writeSecurityRules(securityRules.SecurityRuleList);
7✔
324
        return axios.post(
7✔
325
            "resources/resource/" + resourceId + "/permissions",
326
            payload,
327
            this.addBaseUrl({
328
                headers: {
329
                    'Content-Type': "application/xml"
330
                }
331
            }));
332
    },
333
    createResource: function(metadata, data, category, options) {
334
        const name = metadata.name;
10✔
335
        const description = metadata.description || "";
10✔
336
        // filter attributes from the metadata object excluding the default ones
337
        const attributesSection = createAttributeList(metadata);
10✔
338
        return axios.post(
10✔
339
            "resources/",
340
            "<Resource>" + generateMetadata(name, description, metadata.advertised) + "<category><name>" + (category || "") + "</name></category>" +
11✔
341
                attributesSection +
342
                "<store><data><![CDATA[" + (
343
                data
20✔
344
                        && (
345
                            (typeof data === 'object')
9✔
346
                                ? JSON.stringify(data)
347
                                : data)
348
                        || "") + "]]></data></store></Resource>",
349
            this.addBaseUrl(merge({
350
                headers: {
351
                    'Content-Type': "application/xml"
352
                }
353
            }, options)));
354
    },
355
    deleteResource: function(resourceId, options) {
356
        return axios.delete(
3✔
357
            "resources/resource/" + resourceId,
358
            this.addBaseUrl(merge({
359
            }, options)));
360
    },
361
    getUserGroups: function(options) {
UNCOV
362
        const url = "usergroups/";
×
363
        return axios.get(url, this.addBaseUrl(parseOptions(options))).then(function(response) {
×
364
            return response.data;
×
365
        });
366
    },
367
    getPermissions: function(mapId, options) {
368
        const url = "resources/resource/" + mapId + "/permissions";
6✔
369
        return axios.get(url, this.addBaseUrl(parseOptions(options))).then(function(response) {return response.data; });
6✔
370
    },
371
    getAvailableGroups: function(user) {
372
        if (user && user.role === "ADMIN") {
3✔
373
            return axios.get(
2✔
374
                "usergroups/?all=true&users=false",
375
                this.addBaseUrl({
376
                    headers: {
377
                        'Accept': "application/json"
378
                    }
379
                })).then(function(response) {
380
                return parseAdminGroups(response.data);
2✔
381
            });
382
        }
383
        return axios.get(
1✔
384
            "users/user/details",
385
            this.addBaseUrl({
386
                headers: {
387
                    'Accept': "application/json"
388
                }
389
            })).then(function(response) {
UNCOV
390
            return parseUserGroups(response.data);
×
391
        });
392
    },
393
    getUsers: function(textSearch, options = {}) {
×
394
        const url = "extjs/search/users" + (textSearch ? "/" + textSearch : "");
2!
395
        return axios.get(url, this.addBaseUrl(parseOptions(options))).then(function(response) {return response.data; });
2✔
396
    },
397
    getUser: function(id, options = {params: {includeattributes: true}}) {
×
398
        const url = "users/user/" + id;
3✔
399
        return axios.get(url, this.addBaseUrl(parseOptions(options))).then(function(response) {return response.data; });
3✔
400
    },
401
    updateUser: function(id, user, options) {
402
        const url = "users/user/" + id;
2✔
403
        const postUser = Object.assign({}, user);
2✔
404
        if (postUser.newPassword === "") {
2!
UNCOV
405
            delete postUser.newPassword;
×
406
        }
407
        return axios.put(url, {User: postUser}, this.addBaseUrl(parseOptions(options))).then(function(response) {return response.data; });
2✔
408
    },
409
    createUser: function(user, options) {
410
        const url = "users/";
2✔
411

412
        return axios.post(url, {User: Api.utils.initUser(user)}, this.addBaseUrl(parseOptions(options))).then(function(response) {return response.data; });
2✔
413
    },
414
    deleteUser: function(id, options = {}) {
1✔
415
        const url = "users/user/" + id;
1✔
416
        return axios.delete(url, this.addBaseUrl(parseOptions(options))).then(function(response) {return response.data; });
1✔
417
    },
418
    getGroups: function(textSearch, options = {}) {
×
UNCOV
419
        const url = "extjs/search/groups" + (textSearch ? "/" + textSearch : "");
×
420
        return axios.get(url, this.addBaseUrl(parseOptions(options))).then(function(response) {return response.data; });
×
421
    },
422
    getGroup: function(id, options = {}) {
×
423
        const url = "usergroups/group/" + id;
2✔
424
        return axios.get(url, this.addBaseUrl(parseOptions(options))).then(function(response) {
2✔
425
            const groupLoaded = response.data.UserGroup;
1✔
426
            const users = groupLoaded?.restUsers?.User;
1✔
427
            const attributes = groupLoaded?.attributes;
1✔
428
            return {
1✔
429
                ...groupLoaded,
430
                users: users ? castArray(users) : undefined,
1!
431
                attributes: attributes ? castArray(attributes) : undefined
1!
432
            };
433
        });
434
    },
435
    createGroup: function(group, options) {
436
        const url = "usergroups/";
2✔
437
        let groupId;
438
        return axios.post(url, {UserGroup: {...group}}, this.addBaseUrl(parseOptions(options)))
2✔
439
            .then(function(response) {
440
                groupId = response.data;
2✔
441
                return Api.updateGroupMembers({...group, id: groupId}, options);
2✔
442
            }).then(() => groupId);
2✔
443
    },
444
    updateGroup: function(group, options) {
445
        const id = group?.id;
3✔
446
        const url = `usergroups/group/${id}`;
3✔
447
        return axios.put(url, {UserGroup: {...group}}, this.addBaseUrl(parseOptions(options)))
3✔
448
            .then(function() {
449
                return Api.updateGroupMembers(group, options);
3✔
450
            })
451
            .then(() => id);
3✔
452
    },
453
    updateGroupMembers: function(group, options) {
454
        // No GeoStore API to update group name and description. only update new users
455
        if (group.newUsers) {
5✔
456
            let restUsers = group.users || group.restUsers && group.restUsers.User || [];
3!
457
            restUsers = Array.isArray(restUsers) ? restUsers : [restUsers];
3!
458
            // old users not present in the new users list
459
            let toRemove = restUsers.filter( (user) => findIndex(group.newUsers, u => u.id === user.id) < 0);
3✔
460
            // new users not present in the old users list
461
            let toAdd = group.newUsers.filter( (user) => findIndex(restUsers, u => u.id === user.id) < 0);
3✔
462

463
            // create callbacks
464
            let removeCallbacks = toRemove.map( (user) => () => this.removeUserFromGroup(user.id, group.id, options) );
3✔
465
            let addCallbacks = toAdd.map( (user) => () => this.addUserToGroup(user.id, group.id), options );
3✔
466
            let requests = [...removeCallbacks.map( call => call.call(this)), ...addCallbacks.map(call => call())];
3✔
467
            return axios.all(requests).then(() => {
3✔
468
                return {
3✔
469
                    ...group,
470
                    newUsers: null,
471
                    restUsers: { User: group.newUsers},
472
                    users: group.newUsers
473
                };
474
            });
475
        }
476
        return new Promise( (resolve) => {
2✔
477
            resolve({
2✔
478
                ...group
479
            });
480
        });
481
    },
482
    deleteGroup: function(id, options = {}) {
1✔
483
        const url = "usergroups/group/" + id;
1✔
484
        return axios.delete(url, this.addBaseUrl(parseOptions(options))).then(function(response) {return response.data; });
1✔
485
    },
486
    addUserToGroup(userId, groupId, options = {}) {
3✔
487
        const url = "/usergroups/group/" + userId + "/" + groupId + "/";
3✔
488
        return axios.post(url, null, this.addBaseUrl(parseOptions(options)));
3✔
489
    },
490
    removeUserFromGroup(userId, groupId, options = {}) {
1✔
491
        const url = "/usergroups/group/" + userId + "/" + groupId + "/";
1✔
492
        return axios.delete(url, this.addBaseUrl(parseOptions(options)));
1✔
493
    },
494
    verifySession: function(options) {
UNCOV
495
        const url = "users/user/details";
×
496
        return axios.get(url, this.addBaseUrl(merge({
×
497
            params: {
498
                includeattributes: true
499
            }
500
        }, options))).then(function(response) {
UNCOV
501
            return response.data;
×
502
        });
503
    },
504
    refreshToken: function(accessToken, refreshToken, options) {
505
        const url = "session/refreshToken";
1✔
506
        return axios.post(url, {
1✔
507
            sessionToken: {
508
                access_token: accessToken,
509
                refresh_token: refreshToken
510
            }
511
        }, this.addBaseUrl(parseOptions(options))).then(function(response) {
512
            return response.data?.sessionToken ?? response.data;
1!
513
        });
514
    },
515
    /**
516
     * send a request to /extjs/search/list
517
     * @param  {object} filters
518
     * @param  {object} options additional axios options
519
     * @return {object}
520
     * @example
521
     *
522
     *  const filters = {
523
     *      AND: {
524
     *          ATTRIBUTE: [
525
     *              {
526
     *                  name: ['featured'],
527
     *                  operator: ['EQUAL_TO'],
528
     *                  type: ['STRING'],
529
     *                  value: [true]
530
     *              }
531
     *          ]
532
     *      }
533
     *  }
534
     *
535
     *  searchListByAttributes(filters)
536
     *      .then(results => results)
537
     *      .catch(error => error);
538
     *
539
     */
540
    searchListByAttributes: (filter, options, url = "/extjs/search/list") => {
11✔
541
        const xmlFilter = xmlBuilder.buildObject(filter);
12✔
542
        return axios.post(
12✔
543
            url,
544
            xmlFilter,
545
            Api.addBaseUrl({
546
                ...parseOptions(options),
547
                headers: {
548
                    "Content-Type": "application/xml",
549
                    "Accept": "application/json"
550
                }
551
            })
552
        )
553
            .then(response => response.data);
12✔
554
    },
555
    utils: {
556
        /**
557
         * initialize User with newPassword and UUID
558
         * @param  {object} user The user object
559
         * @return {object}      The user object adapted for creation (newPassword, UUID)
560
         */
561
        initUser: (user) => {
562
            const postUser = Object.assign({}, user);
4✔
563
            if (postUser.newPassword) {
4✔
564
                postUser.password = postUser.newPassword;
2✔
565
            }
566
            // uuid is time-based
567
            const uuidAttr = {
4✔
568
                name: "UUID", value: uuidv1()
569
            };
570
            postUser.attribute = postUser.attribute && postUser.attribute.length > 0 ? [...postUser.attribute, uuidAttr] : [uuidAttr];
4✔
571
            return postUser;
4✔
572
        }
573
    },
574
    errorParser,
575
    /**
576
     * get the available tags
577
     * @param {string} textSearch search text query
578
     * @param {object} options additional axios options
579
     */
580
    getTags: (textSearch, options = {}) => {
1✔
581
        const url = '/resources/tag';
1✔
582
        return axios.get(url, Api.addBaseUrl(parseOptions({
1✔
583
            ...options,
584
            params: {
585
                ...options?.params,
586
                ...(textSearch && { nameLike: textSearch })
2✔
587
            }
588
        }))).then((response) => response.data);
1✔
589
    },
590
    /**
591
     * update/create a tag
592
     * @param {object} tag a tag object { id, name, description, color } (it will create a new tag if id is undefined)
593
     * @param {object} options additional axios options
594
     */
595
    updateTag: (tag = {}, options = {}) => {
2!
596
        const url = `/resources/tag${tag.id ? `/${tag.id}` : ''}`;
2✔
597
        return axios[tag.id ? 'put' : 'post'](
2✔
598
            url,
599
            [
600
                '<Tag>',
601
                `<name><![CDATA[${tag.name}]]></name>`,
602
                `<description><![CDATA[${tag.description}]]></description>`,
603
                `<color>${tag.color}</color>`,
604
                '</Tag>'
605
            ].join(''),
606
            Api.addBaseUrl(
607
                parseOptions({
608
                    ...options,
609
                    headers: {
610
                        'Content-Type': "application/xml"
611
                    }
612
                })
613
            )).then((response) => response.data);
2✔
614
    },
615
    /**
616
     * get the available tags
617
     * @param {string} tagId tag identifier
618
     * @param {object} options additional axios options
619
     */
620
    deleteTag: (tagId, options = {}) => {
1✔
621
        const url = `/resources/tag/${tagId}`;
1✔
622
        return axios.delete(url, Api.addBaseUrl(parseOptions(options))).then((response) => response.data);
1✔
623
    },
624
    /**
625
     * link a tag to a resource
626
     * @param {string} tagId tag identifier
627
     * @param {string} resourceId resource identifier
628
     * @param {object} options additional axios options
629
     */
630
    linkTagToResource: (tagId, resourceId, options) => {
631
        const url = `/resources/tag/${tagId}/resource/${resourceId}`;
1✔
632
        return axios.post(url, undefined, Api.addBaseUrl(parseOptions(options))).then((response) => response.data);
1✔
633
    },
634
    /**
635
     * unlink a tag from a resource
636
     * @param {string} tagId tag identifier
637
     * @param {string} resourceId resource identifier
638
     * @param {object} options additional axios options
639
     */
640
    unlinkTagFromResource: (tagId, resourceId, options) => {
641
        const url = `/resources/tag/${tagId}/resource/${resourceId}`;
1✔
642
        return axios.delete(url, Api.addBaseUrl(parseOptions(options))).then((response) => response.data);
1✔
643
    },
644
    /**
645
     * add a resource to user favorites
646
     * @param  {string} userId user identifier
647
     * @param  {string} resourceId resource identifier
648
     * @param  {object} options additional axios options
649
     */
650
    addFavoriteResource: (userId, resourceId, options) => {
651
        const url = `/users/user/${userId}/favorite/${resourceId}`;
3✔
652
        return axios.post(url, undefined, Api.addBaseUrl(parseOptions(options))).then((response) => response.data);
3✔
653
    },
654
    /**
655
     * remove a resource from user favorites
656
     * @param  {string} userId user identifier
657
     * @param  {string} resourceId resource identifier
658
     * @param  {object} options additional axios options
659
     */
660
    removeFavoriteResource: (userId, resourceId, options) => {
661
        const url = `/users/user/${userId}/favorite/${resourceId}`;
2✔
662
        return axios.delete(url, Api.addBaseUrl(parseOptions(options))).then((response) => response.data);
2✔
663
    }
664
};
665

666
export default Api;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc