Skip to content

Commit f3f350e

Browse files
authored
Merge pull request #132 from TaskarCenterAtUW/dev
Dev to Stage
2 parents e268552 + 27798a8 commit f3f350e

12 files changed

+325
-8
lines changed
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
'use strict';
2+
3+
var dbm;
4+
var type;
5+
var seed;
6+
var fs = require('fs');
7+
var path = require('path');
8+
var Promise;
9+
10+
/**
11+
* We receive the dbmigrate dependency from dbmigrate initially.
12+
* This enables us to not have to rely on NODE_PATH.
13+
*/
14+
exports.setup = function(options, seedLink) {
15+
dbm = options.dbmigrate;
16+
type = dbm.dataType;
17+
seed = seedLink;
18+
Promise = options.Promise;
19+
};
20+
21+
exports.up = function(db) {
22+
var filePath = path.join(__dirname, 'sqls', '20250818105519-dataset-viewer-pg-changes-up.sql');
23+
return new Promise( function( resolve, reject ) {
24+
fs.readFile(filePath, {encoding: 'utf-8'}, function(err,data){
25+
if (err) return reject(err);
26+
console.log('received data: ' + data);
27+
28+
resolve(data);
29+
});
30+
})
31+
.then(function(data) {
32+
return db.runSql(data);
33+
});
34+
};
35+
36+
exports.down = function(db) {
37+
var filePath = path.join(__dirname, 'sqls', '20250818105519-dataset-viewer-pg-changes-down.sql');
38+
return new Promise( function( resolve, reject ) {
39+
fs.readFile(filePath, {encoding: 'utf-8'}, function(err,data){
40+
if (err) return reject(err);
41+
console.log('received data: ' + data);
42+
43+
resolve(data);
44+
});
45+
})
46+
.then(function(data) {
47+
return db.runSql(data);
48+
});
49+
};
50+
51+
exports._meta = {
52+
"version": 1
53+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
DROP COLUMN IF EXISTS data_viewer_config;
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ALTER TABLE public.project_group
2+
ADD COLUMN IF NOT EXISTS data_viewer_config JSON DEFAULT '{}'::JSON;

src/assets/user-management-spec.json

Lines changed: 97 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -773,6 +773,15 @@
773773
}
774774
}
775775
},
776+
{
777+
"name": "data_viewer_allowed",
778+
"in": "query",
779+
"description": "<strong>Data viewer allowed:</strong> Filters project groups based on whether data viewer access is allowed. Uses a boolean value.",
780+
"required": false,
781+
"schema": {
782+
"type": "boolean"
783+
}
784+
},
776785
{
777786
"name": "page_no",
778787
"in": "query",
@@ -1058,6 +1067,51 @@
10581067
}
10591068
}
10601069
},
1070+
"/api/v1/project-group/{projectGroupId}/dataset-viewer": {
1071+
"post": {
1072+
"tags": [
1073+
"ProjectGroup"
1074+
],
1075+
"summary": "Updates the project group data viewer preferences",
1076+
"description": "Updates the project group data viewer preferences.",
1077+
"operationId": "updateDataViewer",
1078+
"parameters": [
1079+
{
1080+
"name": "projectGroupId",
1081+
"in": "path",
1082+
"description": "The ID of the project group to update.",
1083+
"required": true,
1084+
"schema": {
1085+
"type": "string"
1086+
}
1087+
}
1088+
],
1089+
"requestBody": {
1090+
"content": {
1091+
"application/json": {
1092+
"schema": {
1093+
"$ref": "#/components/schemas/data_viewer"
1094+
}
1095+
}
1096+
},
1097+
"required": true
1098+
},
1099+
"responses": {
1100+
"200": {
1101+
"description": "Project group data viewer preferences updated successfully."
1102+
},
1103+
"401": {
1104+
"description": "Unauthenticated request"
1105+
},
1106+
"403": {
1107+
"description": "Unauthorized request"
1108+
},
1109+
"500": {
1110+
"description": "An server error occurred."
1111+
}
1112+
}
1113+
}
1114+
},
10611115
"/api/v1/reset-credentials": {
10621116
"post": {
10631117
"tags": [
@@ -1188,6 +1242,10 @@
11881242
"type": "string",
11891243
"description": "Role associated with project group."
11901244
}
1245+
},
1246+
"data_viewer_config": {
1247+
"$ref": "#/components/schemas/data_viewer",
1248+
"description": "Configuration settings for the dataset viewer."
11911249
}
11921250
},
11931251
"description": "User associated project groups and roles."
@@ -1441,6 +1499,40 @@
14411499
},
14421500
"description": "Describes an Project Group."
14431501
},
1502+
"data_viewer": {
1503+
"type": "object",
1504+
"properties": {
1505+
"dataset_viewer_allowed": {
1506+
"type": "boolean",
1507+
"default": false,
1508+
"description": "Flag to indicate to allow all datasets within the project group to be viewed on the dataset viewer."
1509+
},
1510+
"feedback_turnaround_time": {
1511+
"required": [
1512+
"number",
1513+
"units"
1514+
],
1515+
"type": "object",
1516+
"description": "Feedback turnaround time for the project group.",
1517+
"properties": {
1518+
"number": {
1519+
"type": "integer",
1520+
"description": "Number of days for feedback turnaround time."
1521+
},
1522+
"units": {
1523+
"type": "string",
1524+
"enum": [
1525+
"days",
1526+
"months",
1527+
"years"
1528+
],
1529+
"description": "Unit of time for feedback turnaround time."
1530+
}
1531+
}
1532+
}
1533+
},
1534+
"description": "Describes an Project group."
1535+
},
14441536
"ProjectGroupList": {
14451537
"type": "object",
14461538
"properties": {
@@ -1475,9 +1567,12 @@
14751567
"$ref": "#/components/schemas/POC",
14761568
"description": "POC details"
14771569
}
1570+
},
1571+
"data_viewer": {
1572+
"$ref": "#/components/schemas/data_viewer",
1573+
"description": "Describes an Project group."
14781574
}
1479-
},
1480-
"description": "Describes an Project group."
1575+
}
14811576
},
14821577
"POC": {
14831578
"type": "object",

src/controller/project-group-controller.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { ProjectGroupUserQueryParams } from "../model/params/project-group-user-
1111
import { Utility } from "../utility/utility";
1212
import queryValidationMiddleware from "../middleware/query-params-validation-middleware";
1313
import { listRequestValidation } from "../middleware/list-request-validation-middleware";
14+
import { DatasetViewerDto, FeedbackTurnaroundTime } from "../model/dto/dataset-viewer-dto";
1415

1516
class ProjectGroupController implements IController {
1617
public path = '';
@@ -26,6 +27,28 @@ class ProjectGroupController implements IController {
2627
this.router.get(`${this.path}/api/v1/project-group`, listRequestValidation, authorizationMiddleware([]), queryValidationMiddleware(ProjectGroupQueryParams), this.getProjectGroup);
2728
this.router.get(`${this.path}/api/v1/project-group/:projectGroupId/users`, authorizationMiddleware([Role.TDEI_ADMIN, Role.POC], true), this.getProjectGroupUsers);
2829
this.router.put(`${this.path}/api/v1/project-group/:projectGroupId/active/:status`, authorizationMiddleware([Role.TDEI_ADMIN]), this.deleteProjectGroup);
30+
this.router.post(`${this.path}/api/v1/project-group/:projectGroupId/dataset-viewer`, authorizationMiddleware([Role.TDEI_ADMIN, Role.POC], true), this.putDatasetViewer);
31+
}
32+
33+
/**
34+
* Gets the dataset viewer configuration for a project group
35+
* @param req - The request object
36+
* @param res - The response object
37+
* @param next - The next middleware function
38+
*/
39+
async putDatasetViewer(req: Request, res: express.Response, next: NextFunction) {
40+
try {
41+
let projectGroupId = req.params.projectGroupId;
42+
let datasetViewerConfig = DatasetViewerDto.from(req.body);
43+
datasetViewerConfig.feedback_turnaround_time = FeedbackTurnaroundTime.from(datasetViewerConfig.feedback_turnaround_time);
44+
45+
await datasetViewerConfig.validateRequestInput();
46+
const result = await projectGroupService.updateDatasetViewerConfig(projectGroupId, datasetViewerConfig);
47+
Ok(res, result);
48+
} catch (error) {
49+
let errorMessage = "Error updating the dataset viewer config.";
50+
Utility.handleError(res, next, error, errorMessage);
51+
}
2952
}
3053

3154
public deleteProjectGroup = async (request: Request, response: express.Response, next: NextFunction) => {
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { IsBoolean, IsEnum, IsNotEmpty, IsNumber, validate, ValidateNested, ValidationError } from "class-validator";
2+
import { BaseDto } from "./base-dto";
3+
import { Prop } from "nodets-ms-core/lib/models";
4+
import { QueryConfig } from "pg";
5+
import { InputException } from "../../exceptions/http/http-exceptions";
6+
import { plainToInstance, Type } from "class-transformer";
7+
8+
export enum TimeUnit {
9+
DAYS = "days",
10+
MONTHS = "months",
11+
YEARS = "years"
12+
}
13+
14+
export class FeedbackTurnaroundTime extends BaseDto {
15+
@Prop()
16+
@IsNotEmpty()
17+
@IsNumber()
18+
number!: number;
19+
20+
@Prop()
21+
@IsNotEmpty()
22+
@IsEnum(TimeUnit)
23+
units!: TimeUnit;
24+
}
25+
26+
export class DatasetViewerDto extends BaseDto {
27+
@Prop()
28+
@IsNotEmpty()
29+
@IsBoolean()
30+
dataset_viewer_allowed!: boolean;
31+
32+
@Prop()
33+
@IsNotEmpty()
34+
@ValidateNested()
35+
@Type(() => FeedbackTurnaroundTime)
36+
feedback_turnaround_time!: FeedbackTurnaroundTime;
37+
38+
constructor(init?: Partial<DatasetViewerDto>) {
39+
super();
40+
Object.assign(this, init);
41+
}
42+
43+
async validateRequestInput() {
44+
// Ensure nested object exists
45+
// if (!this.feedback_turnaround_time) {
46+
// this.feedback_turnaround_time = new FeedbackTurnaroundTime();
47+
// }
48+
// const dto = plainToInstance(this.constructor as new () => DatasetViewerDto, this);
49+
let errors = await validate(this);
50+
if (errors.length > 0) {
51+
console.log('Input validation failed');
52+
let message = errors
53+
.map((error: ValidationError) => {
54+
if (error.constraints) {
55+
return Object.values(error.constraints);
56+
}
57+
if (error.children && error.children.length > 0) {
58+
// Nested validation messages
59+
return error.children.map(c => Object.values(c.constraints || {})).join(', ');
60+
}
61+
return '';
62+
})
63+
.join(', ');
64+
65+
throw new InputException(`Required fields are missing or invalid: ${message}`);
66+
}
67+
return true;
68+
}
69+
70+
/**
71+
* Builds the update dataset viewer QueryConfig object
72+
* @returns QueryConfig object
73+
*/
74+
getUpdateDatasetViewerQuery(tdei_project_group_id: string): QueryConfig {
75+
const queryObject = {
76+
text: `UPDATE project_group SET data_viewer_config = $1 WHERE project_group_id = $2`,
77+
values: [JSON.stringify(this), tdei_project_group_id]
78+
}
79+
return queryObject;
80+
}
81+
}

src/model/dto/project-group-dto.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { BaseDto } from "./base-dto";
44
import { Prop } from "nodets-ms-core/lib/models";
55
import { QueryConfig } from "pg";
66
import { FeatureCollection } from "geojson";
7+
import { DatasetViewerDto } from "./dataset-viewer-dto";
78

89
export class ProjectGroupDto extends BaseDto {
910
@Prop()
@@ -32,6 +33,9 @@ export class ProjectGroupDto extends BaseDto {
3233
@IsValidPolygon()
3334
@Prop()
3435
polygon!: FeatureCollection;
36+
@IsOptional()
37+
@Prop()
38+
data_viewer_config!: DatasetViewerDto;
3539

3640
constructor(init?: Partial<ProjectGroupDto>) {
3741
super();

src/model/dto/project-group-role-dto.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
import { DatasetViewerDto } from "./dataset-viewer-dto";
2+
13
export class ProjectGroupRoleDto {
24
tdei_project_group_id!: string;
35
project_group_name!: string;
46
roles!: string[];
5-
7+
data_viewer_config!: DatasetViewerDto;
68
constructor(init?: Partial<ProjectGroupRoleDto>) {
79
Object.assign(this, init);
810
}

0 commit comments

Comments
 (0)