Skip to content

Commit 945c71b

Browse files
committed
Merge branch 'master' into staging
2 parents 6de69d8 + b1819d2 commit 945c71b

File tree

10 files changed

+284
-14
lines changed

10 files changed

+284
-14
lines changed

backend/VERSION.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{ "VERSION" : "5.14.0",
1+
{ "VERSION" : "5.14.1",
22
"unity" : {
33
"current" : "5.20.0",
44
"supported": []

backend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "3drepo.io",
3-
"version": "5.14.0",
3+
"version": "5.14.1",
44
"engines": {
55
"node": "18.x.x"
66
},

backend/src/v4/models/project.js

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,11 @@
2424
const responseCodes = require("../response_codes.js");
2525
const utils = require("../utils");
2626
const _ = require("lodash");
27-
const { changePermissions, findModelSettings, removePermissionsFromModels } = require("./modelSetting");
2827
const { publish } = require(`${v5Path}/services/eventsManager/eventsManager`);
2928
const { events } = require(`${v5Path}/services/eventsManager/eventsManager.constants`);
29+
const { changePermissions, prepareDefaultView, findModelSettings, findPermissionByUser, removePermissionsFromModels } = require("./modelSetting");
30+
const { getTeamspaceSettings } = require("./teamspaceSetting");
31+
const PermissionTemplates = require("./permissionTemplates");
3032

3133
const PROJECTS_COLLECTION_NAME = "projects";
3234

@@ -300,6 +302,49 @@
300302
return projects.map(p => p.name);
301303
};
302304

305+
Project.listModels = async function(account, project, username, filters) {
306+
const AccountPermissions = require("./accountPermissions");
307+
const ModelHelper = require("./helper/model");
308+
const [dbUser, projectObj] = await Promise.all([
309+
getTeamspaceSettings(account),
310+
Project.findOneProject(account, {name: project})
311+
]);
312+
if (!projectObj) {
313+
throw responseCodes.PROJECT_NOT_FOUND;
314+
}
315+
if (filters && filters.name) {
316+
filters.name = new RegExp(".*" + filters.name + ".*", "i");
317+
}
318+
let modelsSettings = await findModelSettings(account, { _id: { $in : projectObj.models }, ...filters});
319+
let permissions = [];
320+
const accountPerm = AccountPermissions.findByUser(dbUser, username);
321+
const projectPerm = projectObj.permissions.find(p=> p.user === username);
322+
if (accountPerm && accountPerm.permissions) {
323+
permissions = permissions.concat(ModelHelper.flattenPermissions(accountPerm.permissions));
324+
}
325+
if (projectPerm && projectPerm.permissions) {
326+
permissions = permissions.concat(ModelHelper.flattenPermissions(projectPerm.permissions));
327+
}
328+
modelsSettings = await Promise.all(modelsSettings.map(async setting => {
329+
const template = await findPermissionByUser(account, setting._id, username);
330+
let settingsPermissions = [];
331+
if(template) {
332+
const permissionTemplate = PermissionTemplates.findById(dbUser, template.permission);
333+
if (permissionTemplate && permissionTemplate.permissions) {
334+
settingsPermissions = settingsPermissions.concat(ModelHelper.flattenPermissions(permissionTemplate.permissions, true));
335+
}
336+
}
337+
setting = await prepareDefaultView(account, setting._id, setting);
338+
setting.permissions = _.uniq(permissions.concat(settingsPermissions));
339+
setting.model = setting._id;
340+
setting.account = account;
341+
setting.subModels = await ModelHelper.listSubModels(account, setting._id, C.MASTER_BRANCH_NAME);
342+
setting.headRevisions = {};
343+
return setting;
344+
}));
345+
return modelsSettings;
346+
};
347+
303348
Project.isProjectAdmin = async function(teamspace, model, user) {
304349
const projection = { "permissions": { "$elemMatch": { user: user } }};
305350
const project = await Project.findOneProject(teamspace, {models: model}, projection);

backend/src/v4/routes/apidoc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
"version": "5.14.0"
2+
"version": "5.14.1"
33
}

backend/src/v4/routes/project.js

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"use strict";
1919
(function() {
2020

21+
const _ = require("lodash");
2122
const express = require("express");
2223
const router = express.Router({mergeParams: true});
2324
const responseCodes = require("../response_codes");
@@ -205,6 +206,133 @@
205206
*/
206207
router.patch("/projects/:project", middlewares.project.canUpdate, updateProject);
207208

209+
/**
210+
* @api {get} /:teamspace/projects/:project/models List models of the project
211+
* @apiName listModels
212+
* @apiGroup Project
213+
*
214+
* @apiDescription It returns a list of models .
215+
*
216+
* @apiPermission canListProjects
217+
*
218+
* @apiParam {String} teamspace Name of the teamspace
219+
* @apiParam {String} project The name of the project to list models
220+
*
221+
* @apiParam (Query) {String} [name] Filters models by name
222+
*
223+
*
224+
* @apiExample {get} Example usage:
225+
* GET /teamSpace1/projects/Bim%20Logo/models?name=log HTTP/1.1
226+
*
227+
* @apiSuccessExample {json} Success:
228+
* [
229+
* {
230+
* "_id": "5ce7dd19-1252-4548-a9c9-4a5414f2e0c5",
231+
* "federate": true,
232+
* "desc": "",
233+
* "name": "Full Logo",
234+
* "__v": 17,
235+
* "timestamp": "2019-05-02T16:17:37.902Z",
236+
* "type": "Federation",
237+
* "subModels": [
238+
* {
239+
* "database": "teamSpace1",
240+
* "model": "b1fceab8-b0e9-4e45-850b-b9888efd6521",
241+
* "name": "block"
242+
* },
243+
* {
244+
* "database": "teamSpace1",
245+
* "model": "7cf61b4f-acdf-4295-b2d0-9b45f9f27418",
246+
* "name": "letters"
247+
* },
248+
* {
249+
* "database": "teamSpace1",
250+
* "model": "2710bd65-37d3-4e7f-b2e0-ffe743ce943f",
251+
* "name": "pipes"
252+
* }
253+
* ],
254+
* "surveyPoints": [
255+
* {
256+
* "position": [
257+
* 0,
258+
* 0,
259+
* 0
260+
* ],
261+
* "latLong": [
262+
* -34.459127,
263+
* 0
264+
* ]
265+
* }
266+
* ],
267+
* "properties": {
268+
* "unit": "mm",
269+
* "topicTypes": [
270+
* {
271+
* "label": "Clash",
272+
* "value": "clash"
273+
* },
274+
* {
275+
* "label": "Diff",
276+
* "value": "diff"
277+
* },
278+
* {
279+
* "label": "RFI",
280+
* "value": "rfi"
281+
* },
282+
* {
283+
* "label": "Risk",
284+
* "value": "risk"
285+
* },
286+
* {
287+
* "label": "H&S",
288+
* "value": "hs"
289+
* },
290+
* {
291+
* "label": "Design",
292+
* "value": "design"
293+
* },
294+
* {
295+
* "label": "Constructibility",
296+
* "value": "constructibility"
297+
* },
298+
* {
299+
* "label": "GIS",
300+
* "value": "gis"
301+
* },
302+
* {
303+
* "label": "For information",
304+
* "value": "for_information"
305+
* },
306+
* {
307+
* "label": "VR",
308+
* "value": "vr"
309+
* }
310+
* ]
311+
* },
312+
* "permissions": [
313+
* "change_model_settings",
314+
* "upload_files",
315+
* "create_issue",
316+
* "comment_issue",
317+
* "view_issue",
318+
* "view_model",
319+
* "download_model",
320+
* "edit_federation",
321+
* "delete_federation",
322+
* "delete_model",
323+
* "manage_model_permission"
324+
* ],
325+
* "status": "ok",
326+
* "id": "5ce7dd19-1252-4548-a9c9-4a5414f2e0c5",
327+
* "model": "5ce7dd19-1252-4548-a9c9-4a5414f2e0c5",
328+
* "account": "teamSpace1",
329+
* "headRevisions": {
330+
* }
331+
* }
332+
* ] *
333+
*/
334+
router.get("/projects/:project/models", middlewares.project.canList, listModels);
335+
208336
/**
209337
* @api {get} /:teamspace/projects List projects
210338
* @apiName listProjects
@@ -446,6 +574,16 @@
446574
});
447575
}
448576

577+
function listModels(req, res, next) {
578+
const filters = _.pick(req.query, "name");
579+
const username = req.session.user.username;
580+
Project.listModels(req.params.account, req.params.project, username, filters).then(models => {
581+
responseCodes.respond(utils.APIInfo(req), req, res, next, responseCodes.OK, models);
582+
}).catch(err => {
583+
responseCodes.respond(utils.APIInfo(req), req, res, next, err, err);
584+
});
585+
}
586+
449587
module.exports = router;
450588

451589
}());

backend/tests/v4/integrated/project.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,77 @@ describe('Projects', () => {
616616
});
617617
});
618618

619+
it('list all project models should succeed', (done) => {
620+
agent.get(`/${username}/projects/${projectName}/models`)
621+
.expect(200, (err, res) => {
622+
expect(res.body).to.deep.equal(goldenFullModelList);
623+
done(err);
624+
});
625+
});
626+
627+
it("list all project models from project that doesn't exist should fail", async () => {
628+
const { body } = await agent.get(`/${username}/projects/notexist/models`)
629+
.expect(404);
630+
631+
expect(body.value).to.equal(responseCodes.PROJECT_NOT_FOUND.value);
632+
});
633+
634+
it('list project models matching query name should succeed', (done) => {
635+
agent.get(`/${username}/projects/${projectName}/models?name=RandomName`)
636+
.expect(200, (err, res) => {
637+
expect(res.body).to.deep.equal(goldenRandomNameList);
638+
done(err);
639+
});
640+
});
641+
642+
it('list project models matching partial query name (start) should succeed', (done) => {
643+
agent.get(`/${username}/projects/${projectName}/models?name=TestModel`)
644+
.expect(200, (err, res) => {
645+
expect(res.body).to.deep.equal(goldenTestModelList);
646+
done(err);
647+
});
648+
});
649+
650+
it('list project models matching partial query name (middle) should succeed', (done) => {
651+
agent.get(`/${username}/projects/${projectName}/models?name=Model`)
652+
.expect(200, (err, res) => {
653+
expect(res.body).to.deep.equal(goldenTestModelList);
654+
done(err);
655+
});
656+
});
657+
658+
it('list project models matching partial query name (end) should succeed', (done) => {
659+
agent.get(`/${username}/projects/${projectName}/models?name=Name`)
660+
.expect(200, (err, res) => {
661+
expect(res.body).to.deep.equal(goldenRandomNameList);
662+
done(err);
663+
});
664+
});
665+
666+
it('list project models query with different casing should succeed', (done) => {
667+
agent.get(`/${username}/projects/${projectName}/models?name=testmodel`)
668+
.expect(200, (err, res) => {
669+
expect(res.body).to.deep.equal(goldenTestModelList);
670+
done(err);
671+
});
672+
});
673+
674+
it('list project models matching no names should succeed', (done) => {
675+
agent.get(`/${username}/projects/${projectName}/models?name=DOESNTEXIST`)
676+
.expect(200, (err, res) => {
677+
expect(res.body).to.deep.equal([]);
678+
done(err);
679+
});
680+
});
681+
682+
it("list all project models with name query from project that doesn't exist should fail", (done) => {
683+
agent.get(`/${username}/projects/notexist/models?name=TestModel`)
684+
.expect(404, (err, res) => {
685+
expect(res.body.value).to.equal(responseCodes.PROJECT_NOT_FOUND.value);
686+
done(err);
687+
});
688+
});
689+
619690
it('should able to delete project', (done) => {
620691
const project = {
621692
name: 'project_exists',

frontend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "3drepo.io-frontend",
3-
"version": "5.14.0",
3+
"version": "5.14.1",
44
"description": "The frontend for 3drepo.io",
55
"engines": {
66
"node": "18.x.x"

frontend/src/v5/validation/containerAndFederationSchemes/federationSchemes.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,17 @@
1414
* You should have received a copy of the GNU Affero General Public License
1515
* along with this program. If not, see <http://www.gnu.org/licenses/>.
1616
*/
17-
import { SettingsSchemaWithGeoPosition, SettingsSchema } from './settingsSchemes';
17+
import { name, desc } from '../shared/validators';
18+
import { SettingsSchemaWithGeoPosition } from './settingsSchemes';
19+
import * as Yup from 'yup';
20+
import { unit, code } from './validators';
21+
22+
23+
export const NewFederationSettingsSchema = Yup.object().shape({
24+
name,
25+
desc,
26+
unit,
27+
code,
28+
});
1829

19-
export const NewFederationSettingsSchema = SettingsSchema;
2030
export const FederationSettingsSchema = SettingsSchemaWithGeoPosition;

frontend/src/v5/validation/containerAndFederationSchemes/settingsSchemes.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,14 @@ import * as Yup from 'yup';
1919
import { formatMessage } from '@/v5/services/intl';
2020
import { EMPTY_VIEW } from '@/v5/store/store.helpers';
2121
import { unit, code, nullableNumberField } from './validators';
22-
import { desc, name } from '../shared/validators';
22+
import { name, nullableDesc } from '../shared/validators';
2323
import { isNumber } from 'lodash';
2424

25-
export const SettingsSchema = Yup.object().shape({
25+
export const SettingsSchemaWithGeoPosition = Yup.object().shape({
2626
name,
27-
desc,
27+
desc: nullableDesc,
2828
unit,
2929
code,
30-
});
31-
32-
export const SettingsSchemaWithGeoPosition = SettingsSchema.shape({
3330
defaultView: Yup.string()
3431
.nullable()
3532
.transform((value) => (value === EMPTY_VIEW._id ? null : value)),

frontend/src/v5/validation/shared/validators.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,17 @@ export const name = trimmedString
8888
},
8989
);
9090

91-
export const desc = nullableString.max(660,
91+
export const nullableDesc = nullableString.max(660,
9292
formatMessage({
9393
id: 'validation.model.description.error.max',
9494
defaultMessage: 'Description is limited to 660 characters',
9595
}));
96+
97+
export const desc = Yup.lazy((value) => (
98+
stripIfBlankString(value)
99+
.max(660,
100+
formatMessage({
101+
id: 'validation.model.description.error.max',
102+
defaultMessage: 'Description is limited to 660 characters',
103+
}))
104+
));

0 commit comments

Comments
 (0)