Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/add properties #2609

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions jest.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { getJestProjects } from '@nx/jest';
import { getJestProjectsAsync } from '@nx/jest';

export default {
projects: getJestProjects()
};
export default async () => ({
projects: await getJestProjectsAsync()
});
83 changes: 43 additions & 40 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,23 +28,25 @@
"format-fix": "nx format --all"
},
"devDependencies": {
"@babel/core": "^7.25.2",
"@babel/preset-react": "^7.24.7",
"@nx/esbuild": "19.8.0",
"@nx/eslint": "19.8.0",
"@nx/eslint-plugin": "19.8.0",
"@nx/express": "19.8.0",
"@nx/jest": "19.8.0",
"@nx/js": "19.8.0",
"@nx/node": "19.8.0",
"@nx/react": "19.8.0",
"@nx/web": "19.8.0",
"@nx/webpack": "19.8.0",
"@nx/workspace": "19.8.0",
"@babel/core": "^7.25.8",
"@babel/preset-react": "^7.25.7",
"@commitlint/config-conventional": "^18.6.3",
"@commitlint/config-nx-scopes": "^18.6.1",
"@nx/esbuild": "20.0.0",
"@nx/eslint": "20.0.0",
"@nx/eslint-plugin": "20.0.0",
"@nx/express": "20.0.0",
"@nx/jest": "20.0.0",
"@nx/js": "20.0.0",
"@nx/node": "20.0.0",
"@nx/react": "20.0.0",
"@nx/web": "20.0.0",
"@nx/webpack": "20.0.0",
"@nx/workspace": "20.0.0",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.15",
"@svgr/webpack": "8.1.0",
"@swc/cli": "0.4.0",
"@swc/core": "1.7.26",
"@swc/core": "1.7.35",
"@swc/jest": "0.2.36",
"@testing-library/react": "16.0.1",
"@types/bcrypt": "5.0.2",
Expand All @@ -56,52 +58,52 @@
"@types/jest": "29.5.13",
"@types/json2csv": "5.0.7",
"@types/jsonwebtoken": "9.0.7",
"@types/node": "^22.5.5",
"@types/node": "^22.7.5",
"@types/nodemailer": "6.4.16",
"@types/passport": "1.0.16",
"@types/qrcode": "1.5.5",
"@types/react": "18.3.8",
"@types/react-dom": "18.3.0",
"@types/react": "18.3.11",
"@types/react-dom": "18.3.1",
"@types/react-highlight-words": "0.20.0",
"@types/supertest": "6.0.2",
"@types/tmp": "0.2.6",
"@types/uuid": "10.0.0",
"@types/webpack-env": "1.18.5",
"@typescript-eslint/eslint-plugin": "8.6.0",
"@typescript-eslint/parser": "8.6.0",
"@typescript-eslint/eslint-plugin": "8.8.1",
"@typescript-eslint/parser": "8.8.1",
"commitlint": "^18.6.1",
"esbuild": "^0.24.0",
"eslint": "8.57.1",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-import": "2.30.0",
"eslint-plugin-import": "2.31.0",
"eslint-plugin-jsonc": "2.16.0",
"eslint-plugin-jsx-a11y": "6.10.0",
"eslint-plugin-react": "7.36.1",
"eslint-plugin-react-hooks": "4.6.2",
"eslint-plugin-react": "7.37.1",
"eslint-plugin-react-hooks": "5.0.0",
"get-port": "7.1.0",
"git-rev-sync": "3.0.2",
"husky": "^9.1.6",
"jest": "29.7.0",
"jest-environment-jsdom": "29.7.0",
"mongodb-memory-server": "10.0.1",
"nx": "19.8.0",
"mongodb-memory-server": "10.1.2",
"nx": "20.0.0",
"prettier": "^3.3.3",
"react-refresh": "^0.14.2",
"supertest": "7.0.0",
"swc-loader": "0.2.6",
"ts-essentials": "10.0.2",
"ts-jest": "29.2.5",
"ts-node": "10.9.2",
"typescript": "5.6.2",
"typescript": "5.6.3",
"url-loader": "^4.1.1",
"webpack": "^5.94.0",
"webpack": "^5.95.0",
"webpack-merge": "^6.0.1"
},
"dependencies": {
"@ant-design/icons": "5.5.1",
"@apollo/client": "3.11.8",
"@apollo/server": "4.11.0",
"@commitlint/config-conventional": "^18.6.3",
"@commitlint/config-nx-scopes": "^18.6.1",
"@ideafast/idgen": "0.1.1",
"@ideafast/idgen": "0.2.1",
"@simplewebauthn/browser": "^10.0.0",
"@simplewebauthn/server": "^10.0.1",
"@nivo/calendar": "0.87.0",
Expand All @@ -116,24 +118,22 @@
"@trpc/react-query": "10.45.2",
"@trpc/server": "10.45.2",
"JSONStream": "1.3.5",
"antd": "5.21.0",
"antd": "5.21.4",
"antd-img-crop": "4.23.0",
"apollo-upload-client": "18.0.1",
"axios": "1.7.7",
"bcrypt": "5.1.1",
"commitlint": "^18.6.1",
"connect-mongo": "5.1.0",
"connect-timeout": "1.9.0",
"core-js": "^3.38.1",
"cors": "2.8.5",
"csv-parse": "5.5.6",
"dayjs": "1.11.13",
"deepmerge": "4.3.1",
"esbuild": "^0.24.0",
"export-from-json": "1.7.4",
"express": "^4.21.0",
"express-rate-limit": "7.4.0",
"express-session": "1.18.0",
"express": "^4.21.1",
"express-rate-limit": "7.4.1",
"express-session": "1.18.1",
"fs-extra": "11.2.0",
"graphql": "16.9.0",
"graphql-scalars": "1.23.0",
Expand All @@ -143,7 +143,7 @@
"graphql-upload-minimal": "1.6.1",
"graphql-ws": "5.16.0",
"hi-base32": "0.5.1",
"http-proxy-middleware": "3.0.2",
"http-proxy-middleware": "3.0.3",
"https-browserify": "1.0.0",
"json-bigint-patch": "0.0.8",
"jsonwebtoken": "9.0.2",
Expand All @@ -158,14 +158,14 @@
"passport": "0.7.0",
"path-browserify": "1.0.1",
"qrcode": "1.5.4",
"rc-picker": "4.6.14",
"rc-picker": "4.6.15",
"react": "18.3.1",
"react-csv": "2.2.2",
"react-dom": "18.3.1",
"react-dropzone": "14.2.3",
"react-dropzone": "14.2.9",
"react-helmet-async": "2.0.5",
"react-highlight-words": "0.20.0",
"react-router-dom": "6.26.2",
"react-router-dom": "6.27.0",
"react-spinners": "0.14.1",
"regenerator-runtime": "0.14.1",
"sanitize-filename": "1.6.3",
Expand All @@ -183,9 +183,12 @@
"**/@jest/create-cache-key-function": "^29",
"**/@jest/reporters": "^29",
"**/@jest/test-result": "^29",
"**/@types/express": "^4",
"**/@types/express-serve-static-core": "^4",
"**/dicer": "^0.3.1",
"**/jest-config": "^29",
"**/jest-resolve": "^29",
"**/jest-util": "^29",
"**/pretty-format": "^29"
}
}
}
2 changes: 1 addition & 1 deletion packages/itmat-apis/src/graphql/resolvers/userResolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export class UserResolvers {

async validateResetPassword(_parent, args: { token: string, encryptedEmail: string }) {
try {
return await this.userCore.validateResetPassword(args.token, args.encryptedEmail);
return await this.userCore.validateResetPassword(args.encryptedEmail, args.token);
} catch (e) {
return GraphQLErrorDecroator(e as CoreError);
}
Expand Down
8 changes: 5 additions & 3 deletions packages/itmat-apis/src/trpc/dataProcedure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ const CreateFieldInputSchema = z.object({
unit: z.optional(z.string()),
comments: z.optional(z.string()),
verifier: z.optional(z.array(z.array(ZValueVerifier))),
properties: z.optional(z.array(ZFieldProperty))
properties: z.optional(z.array(ZFieldProperty)),
metadata: z.optional(z.record(z.string(), z.unknown()))
});

const EditFieldInputSchema = CreateFieldInputSchema;
Expand Down Expand Up @@ -108,7 +109,8 @@ export class DataRouter {
unit: opts.input.unit,
comments: opts.input.comments,
verifier: opts.input.verifier,
properties: opts.input.properties
properties: opts.input.properties,
metadata: opts.input.metadata
});
}),
/**
Expand Down Expand Up @@ -288,7 +290,7 @@ export class DataRouter {
*/
getFiles: this.baseProcedure.input(z.object({
studyId: z.string(),
versionId: z.optional(z.string()),
versionId: z.optional(z.union([z.string(), z.null(), z.array(z.union([z.string(), z.null()]))])),
fieldIds: z.optional(z.array(z.string())),
readable: z.optional(z.boolean()),
useCache: z.optional(z.boolean()),
Expand Down
60 changes: 57 additions & 3 deletions packages/itmat-cores/src/coreFunc/dataCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ interface CreateFieldInput {
comments?: string;
verifier?: ValueVerifierInput[][];
properties?: IFieldProperty[];
metadata?: Record<string, unknown>;
}

type EditFieldInput = CreateFieldInput;
Expand Down Expand Up @@ -255,7 +256,7 @@ export class DataCore {
deletedTime: null,
deletedUser: null
},
metadata: {}
metadata: fieldInput.metadata ?? {}
};

await this.db.collections.field_dictionary_collection.insertOne(fieldEntry);
Expand Down Expand Up @@ -1301,16 +1302,19 @@ export class DataCore {
permissionArr.push(obj);
}
if (permissionArr.length === 0) {
return [];
continue;
}
roleArr.push({ $or: permissionArr });
}

if (roleArr.length === 0) {
return [];
}

const availableFields = (await this.getStudyFields(requester, studyId, dataVersions)).reduce((a, c) => {
a[c.fieldId] = c;
return a;
}, {});

const availableFieldIds = Object.keys(availableFields);
const refactoredFieldIds = fieldIds ?? Object.keys(availableFields);
const queryField = async (fieldId: string) => {
Expand Down Expand Up @@ -1340,6 +1344,10 @@ export class DataCore {
}
}, {
$replaceRoot: { newRoot: '$latestDocument' }
}, {
$match: {
'life.deletedTime': null
}
}, {
$project: {
_id: 0, // Exclude the _id field
Expand Down Expand Up @@ -1536,6 +1544,52 @@ export class DataCore {
}
}

public async uploadFileDataWithFileEntry(requester: IUserWithoutToken | undefined, studyId: string, fieldId: string, fileEntry: IFile, properties?: string) {
if (!requester) {
throw new CoreError(
enumCoreErrors.NOT_LOGGED_IN,
enumCoreErrors.NOT_LOGGED_IN
);
}
const roles = await this.permissionCore.getRolesOfUser(requester, requester.id, studyId);
if (roles.length === 0) {
throw new CoreError(
enumCoreErrors.NO_PERMISSION_ERROR,
enumCoreErrors.NO_PERMISSION_ERROR
);
}
const study = await this.db.collections.studies_collection.findOne({ 'id': studyId, 'life.deletedTime': null });
if (!study) {
throw new CoreError(
enumCoreErrors.CLIENT_ACTION_ON_NON_EXISTENT_ENTRY,
'Study does not exist.'
);
}
try {
const parsedProperties = properties ? JSON.parse(properties) : {};
const dataInput: IDataInput[] = [{
fieldId: fieldId,
value: fileEntry.id,
properties: parsedProperties
}];
const res = await this.uploadData(requester, studyId, dataInput);
if (!res[0].successful) {
throw new CoreError(
enumCoreErrors.CLIENT_MALFORMED_INPUT,
res[0].description ?? 'Failed to upload file.'
);
}
// invalidate the cache
await this.db.collections.cache_collection.updateMany({ 'keys.studyId': studyId, 'keys.query': 'getStudyFiles' }, { $set: { status: enumCacheStatus.OUTDATED } });
return fileEntry;
} catch (error) {
throw new CoreError(
enumCoreErrors.CLIENT_MALFORMED_INPUT,
`${(error as Error).message}`
);
}
}

/**
* Get the summary of a study.
* Admins can study managers can access this function.
Expand Down
26 changes: 12 additions & 14 deletions packages/itmat-cores/src/coreFunc/permissionCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,35 +95,33 @@ export class PermissionCore {
*/
public async checkFieldOrDataPermission(user: IUserWithoutToken, studyId: string, entry: Partial<IField> | Partial<IData>, permission: enumDataAtomicPermissions) {
const roles = await this.getRolesOfUser(user, user.id, studyId);
let tag = true;
const tags: boolean[] = [];
for (const role of roles) {
let tag = true;
const dataPermissions = role.dataPermissions;
for (const dataPermission of dataPermissions) {
for (const field of dataPermission.fields) {
if (!(new RegExp(field).test(String(entry.fieldId)))) {
tag = false;
}
if (dataPermission.fields.every(field => !(new RegExp(field).test(String(entry.fieldId))))) {
tag = false;
}
if ('value' in entry && dataPermission.dataProperties) {
if (entry.properties) {
for (const property in dataPermission.dataProperties) {
for (const prop of dataPermission.dataProperties[property]) {
if (!(new RegExp(prop).test(String(entry.properties[property])))) {
tag = false;
}
if (dataPermission.dataProperties[property].every(prop => !(new RegExp(prop).test(String(entry.properties?.[property]))))) {
tag = false;
}
}
}
}
if (!permissionString[permission].includes(dataPermission.permission)) {
tag = false;
}
if (tag) {
return true;
}
}
tags.push(tag);
}
if (tags.every(tag => tag === false)) {
return false;
}
return false;
return true;
}

/**
Expand Down Expand Up @@ -269,4 +267,4 @@ export class PermissionCore {

return makeGenericResponse(roleId, true, undefined, 'Role deleted successfully.');
}
}
}
Loading
Loading