Skip to content

Commit

Permalink
Merge pull request #76 from Ferlab-Ste-Justine/feat/clin-2985
Browse files Browse the repository at this point in the history
feat: CLIN-2985 add CRUD for variants info
  • Loading branch information
meek0 authored Jul 16, 2024
2 parents 7ed7a56 + 2bda4d0 commit e8a86b3
Show file tree
Hide file tree
Showing 5 changed files with 196 additions and 0 deletions.
14 changes: 14 additions & 0 deletions migrations/1721162187667_add-variants.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-- Up Migration
CREATE TABLE variants (
id SERIAL NOT NULL,
unique_id TEXT NOT NULL,
author_id TEXT NOT NULL,
organization_id TEXT NOT NULL,
timestamp TIMESTAMP NOT NULL,
properties JSONB NOT NULL DEFAULT '{}'::JSONB
);
ALTER TABLE variants ADD PRIMARY KEY (id);
CREATE INDEX variants_unique_id_idx ON variants (unique_id);

-- Down Migration
DROP TABLE variants;
2 changes: 2 additions & 0 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import savedFiltersRouter from './routes/savedFilters';
import statisticsRouter from './routes/statistics';
import usersRouter from './routes/user';
import userSetsRouter from './routes/userSets';
import variantRouter from './routes/variant';
import { globalErrorHandler, globalErrorLogger } from './utils/errors';

export default (keycloak: Keycloak): Express => {
Expand Down Expand Up @@ -39,6 +40,7 @@ export default (keycloak: Keycloak): Express => {
app.use('/admin', keycloak.protect('realm:' + adminRoleName), adminRouter);
app.use('/statistics', keycloak.protect('realm:' + adminRoleName), statisticsRouter);
app.use('/newsletter', keycloak.protect(), newsletterRouter);
app.use('/variants', keycloak.protect(), variantRouter);

app.use(globalErrorLogger, globalErrorHandler);

Expand Down
25 changes: 25 additions & 0 deletions src/db/dal/variant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Op } from 'sequelize';
import VariantModel from '../models/Variant';

export const addNewEntry = async function (uniqueId: string, organizationId: string, authorId: string, properties: any) {
return await VariantModel.create({
unique_id: uniqueId,
organization_id: organizationId,
author_id: authorId,
properties
});
}

export const getEntriesByUniqueIdsAndOrganizations = async function (uniqueIds: string[], organizationIds: string[]) {
return await VariantModel.findAll({
order: [['timestamp', 'DESC']],
where: {
unique_id: {
[Op.in]: uniqueIds
},
organization_id: {
[Op.in]: organizationIds
}
}
});
}
68 changes: 68 additions & 0 deletions src/db/models/Variant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { CreationOptional, DataTypes, Model, Optional } from 'sequelize';
import sequelizeConnection from '../config';

interface IVariantAttributes {
id: number;
unique_id: string;
author_id: string;
organization_id: string;
timestamp: Date;
properties: Object;
}

type VariantCreationAttributes = Optional<IVariantAttributes, 'id'>;

class VariantModel extends Model<IVariantAttributes, VariantCreationAttributes> {
declare id: CreationOptional<number>;
declare unique_id: string;
declare author_id: string;
declare organization_id: string;
declare timestamp: Date;
declare properties: any;
}

VariantModel.init(
{
id: {
type: DataTypes.BIGINT,
allowNull: false,
autoIncrement: true,
primaryKey: true,
unique: true,
validate: {
isInt: true,
},
},
unique_id: {
type: DataTypes.STRING,
allowNull: false,
},
author_id: {
type: DataTypes.STRING,
allowNull: false,
},
organization_id: {
type: DataTypes.STRING,
allowNull: false,
},
timestamp: {
type: DataTypes.DATE,
defaultValue: new Date(),
validate: {
isDate: true,
}
},
properties: {
type: DataTypes.JSONB,
allowNull: false,
defaultValue: {},
}
},
{
sequelize: sequelizeConnection,
tableName: 'variants',
timestamps: false
}
);

export default VariantModel;
87 changes: 87 additions & 0 deletions src/routes/variant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { Request, Router } from 'express';
import { StatusCodes } from 'http-status-codes';

import Realm from '../config/realm';
import { keycloakRealm } from '../config/env';
import { addNewEntry, getEntriesByUniqueIdsAndOrganizations } from '../db/dal/variant';

const CLIN_GENETICIAN_ROLE = 'clin_genetician';

const EMPTY_PROPERTIES = { };

interface UserInfo {
authorId: string,
userRoles: string[],
userOrganizations: string[]
}

function getUserInfo(request: Request): UserInfo {
const authorId = request['kauth']?.grant?.access_token?.content?.fhir_practitioner_id;
const userRoles = request['kauth']?.grant?.access_token?.content?.realm_access?.roles;
const userOrganizations = request['kauth']?.grant?.access_token?.content?.fhir_organization_id;
return { authorId, userRoles, userOrganizations };
}

function validateCreate(userInfo: UserInfo, organization_id: string) {
return userInfo.userRoles.indexOf(CLIN_GENETICIAN_ROLE) > -1 && userInfo.userOrganizations.indexOf(organization_id) > -1;
}

function validateGet(userInfo: UserInfo) {
return userInfo.userRoles.indexOf(CLIN_GENETICIAN_ROLE) > -1;
}

const variantRouter = Router();

variantRouter.post('/:unique_id/:organization_id', async (req, res, next) => {
try {
if (keycloakRealm !== Realm.CLIN) {
return res.sendStatus(StatusCodes.NOT_IMPLEMENTED);
}

const userInfo = getUserInfo(req);
const canCreate = validateCreate(userInfo, req?.params?.organization_id);

if (canCreate) {
const dbResponse = await addNewEntry(req?.params?.unique_id, req?.params?.organization_id, userInfo.authorId, req?.body || EMPTY_PROPERTIES);
return res.status(StatusCodes.CREATED).send(dbResponse);
} else {
return res.sendStatus(StatusCodes.FORBIDDEN);
}
} catch (e) {
next(e);
}
});

variantRouter.get('/', async (req, res, next) => {
try {
if (keycloakRealm !== Realm.CLIN) {
return res.sendStatus(StatusCodes.NOT_IMPLEMENTED);
}

const userInfo = getUserInfo(req);
const canGet = validateGet(userInfo);

let dbResponse = [];
let uniqueIds = [];

if (Array.isArray(req.query?.unique_id)) {
uniqueIds.push(...req.query.unique_id);
} else if (typeof req.query?.unique_id === 'string') {
uniqueIds.push(req.query?.unique_id);
}

if (canGet && uniqueIds.length > 0) {
dbResponse = await getEntriesByUniqueIdsAndOrganizations(uniqueIds, userInfo.userOrganizations);
return res.status(StatusCodes.OK).send(dbResponse);
} else if (!canGet) {
return res.sendStatus(StatusCodes.FORBIDDEN);
} else {
return res.sendStatus(StatusCodes.BAD_REQUEST);
}

} catch (e) {
next(e);
}
});

export default variantRouter;

0 comments on commit e8a86b3

Please sign in to comment.