Skip to content

Commit

Permalink
Feat/clin 2095
Browse files Browse the repository at this point in the history
  • Loading branch information
ethienneroy authored Aug 7, 2023
1 parent e73c8d8 commit e71c8b1
Show file tree
Hide file tree
Showing 10 changed files with 625 additions and 120 deletions.
550 changes: 449 additions & 101 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,20 @@
"test": "jest --silent"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.312.0",
"@types/pg-format": "^1.0.2",
"@types/validator": "^13.7.1",
"@aws-sdk/client-s3": "^3.312.0",
"cors": "^2.8.5",
"dotenv": "^10.0.0",
"express": "^4.17.1",
"express-xss-sanitizer": "^1.1.6",
"http-errors": "^1.7.2",
"http-status-codes": "^2.1.4",
"joi": "^17.9.2",
"keycloak-connect": "^15.0.2",
"node-pg-migrate": "^6.0.0",
"pg": "^8.7.1",
"sequelize": "^6.14.1",
"sequelize": "^6.28.2",
"uuidv4": "^6.2.13"
},
"devDependencies": {
Expand Down
7 changes: 7 additions & 0 deletions src/app.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import cors from 'cors';
import express, { Express } from 'express';
import { sanitize, xss } from 'express-xss-sanitizer';
import { Keycloak } from 'keycloak-connect';

import { adminRoleName } from './config/env';
Expand All @@ -13,7 +14,13 @@ import { globalErrorHandler, globalErrorLogger } from './utils/errors';
export default (keycloak: Keycloak): Express => {
const app = express();

app.use((req, res, next) => {
req.body = sanitize(req.body);
next();
});

app.use(cors());
app.use(xss());
app.use(express.json({ limit: '50mb' }));

app.use(
Expand Down
3 changes: 2 additions & 1 deletion src/db/dal/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ try {

const sanitizeInputPayload = (payload: IUserInput) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { id, keycloak_id, completed_registration, creation_date, ...rest } = payload;
const { id, keycloak_id, completed_registration, creation_date, email, era_commons_id, nih_ned_id, ...rest } =
payload;
return rest;
};

Expand Down
24 changes: 22 additions & 2 deletions src/db/models/SavedFilter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { DataTypes, Model } from 'sequelize';

import { NAME_REGEX, UUID_VERSION } from '../../utils/constants';
import { handleUniqueName } from '../../utils/savedFilters';
import sequelizeConnection from '../config';

Expand All @@ -17,7 +18,6 @@ interface ISavedFilterAttributes {

// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface ISavedFilterInput extends ISavedFilterAttributes {}

// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface ISavedFilterOutput extends ISavedFilterAttributes {}

Expand All @@ -39,18 +39,32 @@ SavedFilterModel.init(
type: DataTypes.STRING,
allowNull: false,
primaryKey: true,
validate: {
isUUID: UUID_VERSION,
},
},
keycloak_id: {
type: DataTypes.STRING,
allowNull: false,
validate: {
isUUID: UUID_VERSION,
},
},
title: {
type: DataTypes.TEXT,
validate: {
is: NAME_REGEX,
},
},
title: DataTypes.TEXT,
tag: DataTypes.TEXT,
type: DataTypes.ENUM('query', 'filter'),
favorite: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
validate: {
isBoolean: true,
},
},
queries: {
type: DataTypes.ARRAY(DataTypes.JSONB),
Expand All @@ -60,10 +74,16 @@ SavedFilterModel.init(
creation_date: {
type: DataTypes.DATE,
defaultValue: new Date(),
validate: {
isDate: true,
},
},
updated_date: {
type: DataTypes.DATE,
defaultValue: new Date(),
validate: {
isDate: true,
},
},
},
{ sequelize: sequelizeConnection, modelName: 'saved_filters', timestamps: false },
Expand Down
121 changes: 109 additions & 12 deletions src/db/models/User.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { DataTypes, Model } from 'sequelize';
import validator from 'validator';

import { MAX_LENGTH_PER_ROLE, NAME_REGEX, UUID_VERSION } from '../../utils/constants';
import sequelizeConnection from '../config';

interface IUserAttributes {
Expand Down Expand Up @@ -57,54 +59,149 @@ UserModel.init(
allowNull: false,
autoIncrement: true,
primaryKey: true,
unique: true,
validate: {
isInt: true,
},
},
keycloak_id: {
type: DataTypes.STRING,
allowNull: false,
validate: {
isUUID: UUID_VERSION,
},
},
deleted: {
type: DataTypes.BOOLEAN,
defaultValue: false,
validate: {
isBoolean: true,
},
},
first_name: {
type: DataTypes.CITEXT,
validate: {
len: [1, 35],
is: NAME_REGEX,
},
},
last_name: {
type: DataTypes.CITEXT,
validate: {
len: [1, 35],
is: NAME_REGEX,
},
},
era_commons_id: {
type: DataTypes.STRING,
validate: {
isAlpha: true,
},
},
nih_ned_id: {
type: DataTypes.STRING,
validate: {
isAlpha: true,
},
},
commercial_use_reason: {
type: DataTypes.STRING,
validate: {
is: NAME_REGEX,
},
},
email: {
type: DataTypes.STRING,
validate: {
isEmail: true,
},
},
external_individual_fullname: {
type: DataTypes.TEXT,
validate: {
isAlpha: true,
},
},
external_individual_email: {
type: DataTypes.TEXT,
validate: {
isEmail: true,
},
},
roles: {
type: DataTypes.ARRAY(DataTypes.CITEXT),
validate: {
validate: function (roles) {
const allAlphanumerical = (roles ?? []).every(
(role) => validator.isAlphanumeric(role) && role.length <= MAX_LENGTH_PER_ROLE,
);
if (!allAlphanumerical) {
throw new Error('%s contains invalid values.');
}
},
},
},
affiliation: {
type: DataTypes.CITEXT,
validate: {
isAlphanumeric: true,
},
},
public_email: {
type: DataTypes.TEXT,
validate: {
isEmail: true,
},
},
linkedin: {
type: DataTypes.TEXT,
validate: {
isUrl: true,
is: /^https?:\/\/(www\.)?linkedin\.com\/in\//i,
},
},
first_name: DataTypes.CITEXT,
last_name: DataTypes.CITEXT,
era_commons_id: DataTypes.STRING,
nih_ned_id: DataTypes.STRING,
commercial_use_reason: DataTypes.STRING,
email: DataTypes.STRING,
external_individual_fullname: DataTypes.TEXT,
external_individual_email: DataTypes.TEXT,
roles: DataTypes.ARRAY(DataTypes.CITEXT),
affiliation: DataTypes.CITEXT,
public_email: DataTypes.TEXT,
linkedin: DataTypes.TEXT,
portal_usages: DataTypes.ARRAY(DataTypes.CITEXT),
research_domains: DataTypes.ARRAY(DataTypes.CITEXT),
research_area_description: DataTypes.TEXT,
profile_image_key: DataTypes.TEXT,
creation_date: {
type: DataTypes.DATE,
defaultValue: new Date(),
validate: {
isDate: true,
},
},
updated_date: {
type: DataTypes.DATE,
defaultValue: new Date(),
validate: {
isDate: true,
},
},
consent_date: DataTypes.DATE,
accepted_terms: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
validate: {
isBoolean: true,
},
},
understand_disclaimer: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
validate: {
isBoolean: true,
},
},
completed_registration: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
validate: {
isBoolean: true,
},
},
config: {
type: DataTypes.JSONB,
Expand Down
19 changes: 19 additions & 0 deletions src/db/models/UserSets.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { DataTypes, Model } from 'sequelize';

import { UUID_VERSION } from '../../utils/constants';
import sequelizeConnection from '../config';

interface IUserSetAttributes {
Expand Down Expand Up @@ -34,19 +35,31 @@ UserSetModel.init(
allowNull: false,
autoIncrement: true,
primaryKey: true,
validate: {
isUUID: UUID_VERSION,
},
},
keycloak_id: {
type: DataTypes.STRING,
allowNull: false,
validate: {
isUUID: UUID_VERSION,
},
},
alias: {
type: DataTypes.STRING,
allowNull: false,
validate: {
isAlpha: true,
},
},
sharedpublicly: {
type: DataTypes.BOOLEAN,
allowNull: false,
defaultValue: false,
validate: {
isBoolean: true,
},
},
content: {
type: DataTypes.JSONB,
Expand All @@ -56,10 +69,16 @@ UserSetModel.init(
creation_date: {
type: DataTypes.DATE,
defaultValue: new Date(),
validate: {
isDate: true,
},
},
updated_date: {
type: DataTypes.DATE,
defaultValue: new Date(),
validate: {
isDate: true,
},
},
},
{ sequelize: sequelizeConnection, modelName: 'user_sets', timestamps: false },
Expand Down
4 changes: 2 additions & 2 deletions src/routes/savedFilters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
update,
updateAsDefault,
} from '../db/dal/savedFilter';
import { uniqueNameErrorHandler, getFilterIDs, removeQueryFromFilters, updateQuery } from '../utils/savedFilters';
import { getFilterIDs, removeQueryFromFilters, uniqueNameErrorHandler, updateQuery } from '../utils/savedFilters';

// Handles requests made to /saved-filters
const savedFiltersRouter = Router();
Expand Down Expand Up @@ -50,7 +50,7 @@ savedFiltersRouter.get('/tag/:tagid', async (req, res, next) => {
try {
const keycloak_id = req['kauth']?.grant?.access_token?.content?.sub;
const type = req.query.type || 'filter';
const result = await getAll({ keycloak_id, tagid: req.params.tagid, type } as any)
const result = await getAll({ keycloak_id, tagid: req.params.tagid, type } as any);
res.status(StatusCodes.OK).send(result);
} catch (e) {
next(e);
Expand Down
3 changes: 3 additions & 0 deletions src/utils/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const UUID_VERSION = 4;
export const NAME_REGEX = /^[a-zA-ZÀ-ÿ0-9- ]+$/;
export const MAX_LENGTH_PER_ROLE = 35;
8 changes: 8 additions & 0 deletions src/utils/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ export const globalErrorHandler = (err: unknown, _req: Request, res: Response, _
res.status(StatusCodes.UNPROCESSABLE_ENTITY).json({
error: 'A resource with the same id already exists.',
});
} else if (err instanceof ValidationError) {
const error = {
name: 'Invalid data',
errors: err.errors.map((error) => error.message.replace('%s', error.path)),
};
res.status(StatusCodes.UNPROCESSABLE_ENTITY).json({
error,
});
} else if (err instanceof HttpError) {
res.status(err.status).json({
error: err.message,
Expand Down

0 comments on commit e71c8b1

Please sign in to comment.