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

geosolutions-it / MapStore2 / 14385903545

10 Apr 2025 04:48PM UTC coverage: 76.952% (-0.05%) from 77.006%
14385903545

Pull #10973

github

web-flow
Merge 5939466b5 into 370d48ef2
Pull Request #10973: Fix #10820 Refactor Admin Ui section like HomePage using ResourceGrid Plugin.

30756 of 47921 branches covered (64.18%)

23 of 38 new or added lines in 6 files covered. (60.53%)

8 existing lines in 5 files now uncovered.

38255 of 49713 relevant lines covered (76.95%)

36.07 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 assign from 'object-assign';
11
import uuidv1 from 'uuid/v1';
12
import xml2js from 'xml2js';
13
const xmlBuilder = new xml2js.Builder();
1✔
14

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

20

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

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

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

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

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

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

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

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

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

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

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

667
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