-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #76 from Ferlab-Ste-Justine/feat/clin-2985
feat: CLIN-2985 add CRUD for variants info
- Loading branch information
Showing
5 changed files
with
196 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |