diff --git a/.github/workflows/client.yaml b/.github/workflows/client.yaml new file mode 100644 index 00000000..dc91c978 --- /dev/null +++ b/.github/workflows/client.yaml @@ -0,0 +1,60 @@ +name: client + +on: + workflow_dispatch: + push: + paths: + - 'packages/client/**' + branches: + - main + tags: + - "v*.*.*" + pull_request: + paths: + - 'packages/client/**' + branches: + - main + +jobs: + lint: + runs-on: ubuntu-latest + name: Check for Linting Errors + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.ref }} + + - name: Setup NodeJS + uses: actions/setup-node@v3 + with: + node-version: 18 + + - name: NPM Install + run: npm install --only=dev + shell: bash + + - name: Check for Linting Issues + run: npm run prettier --workspace=packages/client + + build: + runs-on: ubuntu-latest + name: Build Code + defaults: + run: + working-directory: packages/client + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + + - name: Setup NodeJS + uses: actions/setup-node@v3 + with: + node-version: 18 + + - name: NPM Install + run: npm install + shell: bash + + - name: Build + run: npm run build diff --git a/.github/workflows/gateway.yaml b/.github/workflows/gateway.yaml new file mode 100644 index 00000000..78a21b9d --- /dev/null +++ b/.github/workflows/gateway.yaml @@ -0,0 +1,60 @@ +name: gateway + +on: + workflow_dispatch: + push: + paths: + - 'packages/gateway/**' + branches: + - main + tags: + - "v*.*.*" + pull_request: + paths: + - 'packages/gateway/**' + branches: + - main + +jobs: + lint: + runs-on: ubuntu-latest + name: Check for Linting Errors + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.ref }} + + - name: Setup NodeJS + uses: actions/setup-node@v3 + with: + node-version: 18 + + - name: NPM Install + run: npm install --only=dev + shell: bash + + - name: Check for Linting Issues + run: npm run prettier --workspace=packages/gateway + + build: + runs-on: ubuntu-latest + name: Build Code + defaults: + run: + working-directory: packages/gateway + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + + - name: Setup NodeJS + uses: actions/setup-node@v3 + with: + node-version: 18 + + - name: NPM Install + run: npm install + shell: bash + + - name: Build + run: npm run build diff --git a/.github/workflows/server.yaml b/.github/workflows/server.yaml new file mode 100644 index 00000000..e5d8b210 --- /dev/null +++ b/.github/workflows/server.yaml @@ -0,0 +1,60 @@ +name: server + +on: + workflow_dispatch: + push: + paths: + - 'packages/server/**' + branches: + - main + tags: + - "v*.*.*" + pull_request: + paths: + - 'packages/server/**' + branches: + - main + +jobs: + lint: + runs-on: ubuntu-latest + name: Check for Linting Errors + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + with: + ref: ${{ github.event.pull_request.head.ref }} + + - name: Setup NodeJS + uses: actions/setup-node@v3 + with: + node-version: 18 + + - name: NPM Install + run: npm install --only=dev + shell: bash + + - name: Check for Linting Issues + run: npm run prettier --workspace=packages/server + + build: + runs-on: ubuntu-latest + name: Build Code + defaults: + run: + working-directory: packages/server + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + + - name: Setup NodeJS + uses: actions/setup-node@v3 + with: + node-version: 18 + + - name: NPM Install + run: npm install + shell: bash + + - name: Build + run: npm run build diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 00000000..7ea56ce4 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,5 @@ +module.exports = { + printWidth: 120, + singleQuote: true, + trailingComma: "none" +} diff --git a/package.json b/package.json index 332c58b6..e5c6df20 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,9 @@ "description": "", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "build": "npm run build --workspaces --if-present", + "prettier": "npm run prettier --workspaces --if-present", + "prettier:fix": "npm run prettier:fix --workspaces --if-present" }, "workspaces": [ "packages/*" diff --git a/packages/client/.prettierrc b/packages/client/.prettierrc deleted file mode 100644 index eb55e100..00000000 --- a/packages/client/.prettierrc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "printWidth": 180, - "singleQuote": true, - "trailingComma": "none" -} diff --git a/packages/client/.prettierrc.cjs b/packages/client/.prettierrc.cjs new file mode 100644 index 00000000..2d293bab --- /dev/null +++ b/packages/client/.prettierrc.cjs @@ -0,0 +1,3 @@ +module.exports = { + ...require('../../.prettierrc.js'), +} diff --git a/packages/client/src/App.tsx b/packages/client/src/App.tsx index c0de019f..4a2d8cd5 100644 --- a/packages/client/src/App.tsx +++ b/packages/client/src/App.tsx @@ -57,9 +57,9 @@ const App: FC = () => { return { headers: { ...headers, - authorization: token ? `Bearer ${token}` : '', + authorization: token ? `Bearer ${token}` : '' } - } + }; }); const apolloClient = new ApolloClient({ @@ -81,7 +81,7 @@ const App: FC = () => { ); -} +}; const AppInternal: FC = () => { const [drawerOpen, setDrawerOpen] = useState(true); @@ -107,7 +107,7 @@ const AppInternal: FC = () => { ); - return (<>{ authenticated ? mainView : }); + return <>{authenticated ? mainView : }; }; const UnauthenticatedView: FC = () => { diff --git a/packages/client/src/components/AddDataset.component.tsx b/packages/client/src/components/AddDataset.component.tsx index 9dfc7f79..8ec4af1c 100644 --- a/packages/client/src/components/AddDataset.component.tsx +++ b/packages/client/src/components/AddDataset.component.tsx @@ -65,7 +65,14 @@ export const AddDataset: React.FC = (props: ShowProps) => { Create New Dataset - handleChange(data)} /> + handleChange(data)} + /> diff --git a/packages/client/src/components/TagsDisplay.component.tsx b/packages/client/src/components/TagsDisplay.component.tsx index 97bbe591..18a030ef 100644 --- a/packages/client/src/components/TagsDisplay.component.tsx +++ b/packages/client/src/components/TagsDisplay.component.tsx @@ -23,7 +23,11 @@ type TagPreviewInformation = { export const TagsDisplay: React.FC = () => { const [tagFields, setTagFields] = useState([]); - const [data, setData] = useState({ previewDataSchema: {}, previewUiSchema: {}, renderers: [] }); + const [data, setData] = useState({ + previewDataSchema: {}, + previewUiSchema: {}, + renderers: [] + }); const [valid, setValid] = useState([]); const [open, setOpen] = useState(false); const renderers = [...materialRenderers]; @@ -42,7 +46,11 @@ export const TagsDisplay: React.FC = () => { }; const produceJSONForm = () => { - const dataSchema: { type: string; properties: any; required: string[] } = { type: 'object', properties: {}, required: [] }; + const dataSchema: { type: string; properties: any; required: string[] } = { + type: 'object', + properties: {}, + required: [] + }; const uiSchema: { type: string; elements: any[] } = { type: 'VerticalLayout', elements: [] }; for (const tagField of tagFields) { @@ -102,7 +110,12 @@ export const TagsDisplay: React.FC = () => { {button.name} ))} - @@ -115,7 +128,12 @@ export const TagsDisplay: React.FC = () => { tagFields.map((value: TagField, index: number) => ( - } - diff --git a/packages/client/src/components/upload/CSVUpload.component.tsx b/packages/client/src/components/upload/CSVUpload.component.tsx index 0c9ef5eb..a438540c 100644 --- a/packages/client/src/components/upload/CSVUpload.component.tsx +++ b/packages/client/src/components/upload/CSVUpload.component.tsx @@ -1,13 +1,16 @@ import { Dataset, UploadSession, UploadStatus } from '../../graphql/graphql'; import { Dispatch, SetStateAction, ChangeEvent } from 'react'; import { StatusMessage } from '../../models/StatusMessage'; -import { CreateUploadSessionDocument, GetCsvUploadUrlDocument, ValidateCsvDocument } from '../../graphql/upload-session/upload-session'; +import { + CreateUploadSessionDocument, + GetCsvUploadUrlDocument, + ValidateCsvDocument +} from '../../graphql/upload-session/upload-session'; import { useApolloClient } from '@apollo/client'; import axios from 'axios'; import { Box, Button } from '@mui/material'; import UploadIcon from '@mui/icons-material/Upload'; - export interface CSVUploadProps { dataset: Dataset | null; uploadSession: UploadSession | null; @@ -16,7 +19,12 @@ export interface CSVUploadProps { setCsvValid: Dispatch>; } -export const CSVUpload: React.FC = ({ dataset, setUploadSession, setValidationMessage, setCsvValid }) => { +export const CSVUpload: React.FC = ({ + dataset, + setUploadSession, + setValidationMessage, + setCsvValid +}) => { const apolloClient = useApolloClient(); // Implemented with using the apollo client directly instead of the useMutation hook @@ -47,7 +55,6 @@ export const CSVUpload: React.FC = ({ dataset, setUploadSession, variables: { session: uploadSession._id } }); - if (!uploadUrlQuery.data?.getCSVUploadURL) { console.error('Failed to get upload url'); return; @@ -75,7 +82,7 @@ export const CSVUpload: React.FC = ({ dataset, setUploadSession, // Share any validation results const result = validation.data!.validateCSV; - if (result.status == UploadStatus.Success ) { + if (result.status == UploadStatus.Success) { setValidationMessage({ severity: 'success', message: 'CSV validated successfully' }); setCsvValid(true); } else { @@ -84,12 +91,11 @@ export const CSVUpload: React.FC = ({ dataset, setUploadSession, } }; - return ( ); diff --git a/packages/client/src/components/upload/DatasetSelect.component.tsx b/packages/client/src/components/upload/DatasetSelect.component.tsx index efa3635a..e453a86b 100644 --- a/packages/client/src/components/upload/DatasetSelect.component.tsx +++ b/packages/client/src/components/upload/DatasetSelect.component.tsx @@ -23,12 +23,14 @@ export const DatasetSelect: React.FC = ({ selectedDataset, s return ( - - {isUploading && } + {isUploading && ( + + )} ); diff --git a/packages/client/src/context/Auth.context.tsx b/packages/client/src/context/Auth.context.tsx index 73192b17..40fd23ba 100644 --- a/packages/client/src/context/Auth.context.tsx +++ b/packages/client/src/context/Auth.context.tsx @@ -88,7 +88,11 @@ export const AuthProvider: FC = ({ children }) => { } }, [token]); - return {children}; + return ( + + {children} + + ); }; const saveToken = (token: string) => { @@ -101,6 +105,6 @@ const restoreToken = (): string | null => { const clearToken = (): void => { localStorage.removeItem(AUTH_TOKEN_STR); -} +}; export const useAuth = () => useContext(AuthContext); diff --git a/packages/client/src/context/Confirmation.context.tsx b/packages/client/src/context/Confirmation.context.tsx index e0fdaf1f..057099e1 100644 --- a/packages/client/src/context/Confirmation.context.tsx +++ b/packages/client/src/context/Confirmation.context.tsx @@ -22,7 +22,6 @@ export const ConfirmationProvider: React.FC = ({ chil const [open, setOpen] = useState(false); const [confirmationRequest, setConfirmationRequest] = useState(null); - const pushConfirmationRequest = (confirmationRequest: ConfirmationRequest) => { setConfirmationRequest(confirmationRequest); setOpen(true); @@ -42,14 +41,9 @@ export const ConfirmationProvider: React.FC = ({ chil setOpen(false); }; - return ( - + {confirmationRequest && confirmationRequest.title} {confirmationRequest && confirmationRequest.message} @@ -64,6 +58,6 @@ export const ConfirmationProvider: React.FC = ({ chil {children} ); -} +}; export const useConfirmation = () => useContext(ConfirmationContext); diff --git a/packages/client/src/context/Project.context.tsx b/packages/client/src/context/Project.context.tsx index b1da75a0..3a011d78 100644 --- a/packages/client/src/context/Project.context.tsx +++ b/packages/client/src/context/Project.context.tsx @@ -30,17 +30,18 @@ export const ProjectProvider: FC = ({ children }) => { setProject(null); } } - }, [getProjectResults.data, getProjectResults.error]); return ( - getProjectResults.refetch() - }}> - {children} + }} + > + {children} ); }; diff --git a/packages/client/src/context/Study.context.tsx b/packages/client/src/context/Study.context.tsx index b2d0ff07..495d0b8d 100644 --- a/packages/client/src/context/Study.context.tsx +++ b/packages/client/src/context/Study.context.tsx @@ -46,7 +46,9 @@ export const StudyProvider: FC = (props) => { } }, [findStudiesResults]); - return {props.children}; + return ( + {props.children} + ); }; export const useStudy = () => useContext(StudyContext); diff --git a/packages/client/src/pages/LoginPage.tsx b/packages/client/src/pages/LoginPage.tsx index 33db8045..615e3727 100644 --- a/packages/client/src/pages/LoginPage.tsx +++ b/packages/client/src/pages/LoginPage.tsx @@ -2,7 +2,7 @@ import LockOutlinedIcon from '@mui/icons-material/LockOutlined'; import { Avatar, Box, Container, Link, Typography } from '@mui/material'; import { FC, useEffect } from 'react'; import { useAuth } from '../context/Auth.context'; -import {useNavigate} from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; export const LoginPage: FC = () => { // Construct the Auth URL @@ -39,10 +39,7 @@ export const LoginPage: FC = () => { Sign In or Sign Up - + by following this link diff --git a/packages/client/src/pages/contribute/Contribute.tsx b/packages/client/src/pages/contribute/Contribute.tsx index d12822e2..dfc8c99d 100644 --- a/packages/client/src/pages/contribute/Contribute.tsx +++ b/packages/client/src/pages/contribute/Contribute.tsx @@ -93,19 +93,19 @@ export const ContributePage: React.FC = () => { return ( - - Study: {initialData.name} - + Study: {initialData.name} - - {initialData.complete ? 'Study Training' : 'Study Tagging'} - Study: {initialData.name} - Description: {initialData.description} - Instructions: {initialData.instructions} + + {initialData.complete ? 'Study Training' : 'Study Tagging'} + Study: {initialData.name} + Description: {initialData.description} + Instructions: {initialData.instructions} {initialData.complete ? ( - Training Complete! Reach out to your study administrator to get access to tagging + + Training Complete! Reach out to your study administrator to get access to tagging + ) : ( - )} diff --git a/packages/client/src/pages/datasets/DatasetControls.tsx b/packages/client/src/pages/datasets/DatasetControls.tsx index d3840761..8f98bf20 100644 --- a/packages/client/src/pages/datasets/DatasetControls.tsx +++ b/packages/client/src/pages/datasets/DatasetControls.tsx @@ -98,9 +98,11 @@ export const DatasetControls: React.FC = () => { return ( <> - Dataset Controls + Dataset Controls - + handleClick('add')}> @@ -109,7 +111,9 @@ export const DatasetControls: React.FC = () => { Add New Dataset - + handleClick('upload')}> @@ -123,11 +127,17 @@ export const DatasetControls: React.FC = () => { {controls.map((item: Control) => ( } aria-controls="panel1a-content" id="panel1a-header"> - {item.name} - {item.description} + + {item.name} + + + {item.description} + - + diff --git a/packages/client/src/pages/datasets/ProjectAccess.tsx b/packages/client/src/pages/datasets/ProjectAccess.tsx index dbc222e7..5a400b30 100644 --- a/packages/client/src/pages/datasets/ProjectAccess.tsx +++ b/packages/client/src/pages/datasets/ProjectAccess.tsx @@ -22,13 +22,15 @@ const rows = [ { id: 3, name: 'Project Starbucks', - description: 'The company was founded by Steven Paul Jobs, Ronald Gerald Wayne, and Stephen G. Wozniak on April 1, 1976 and is headquartered in Cupertino, CA.', + description: + 'The company was founded by Steven Paul Jobs, Ronald Gerald Wayne, and Stephen G. Wozniak on April 1, 1976 and is headquartered in Cupertino, CA.', access: true }, { id: 4, name: 'Project Charles', - description: 'Investment, wealth and alternative managers, asset owners and insurers in over 30 countries rely on Charles River IMS to manage USD $48 Trillion in assets.', + description: + 'Investment, wealth and alternative managers, asset owners and insurers in over 30 countries rely on Charles River IMS to manage USD $48 Trillion in assets.', access: false } ]; @@ -39,8 +41,12 @@ export const ProjectAccess: React.FC = () => { Project Access } aria-controls="panel1a-content" id="panel1a-header"> - Dataset 1 name - Dataset 1 description + + Dataset 1 name + + + Dataset 1 description + @@ -52,8 +58,12 @@ export const ProjectAccess: React.FC = () => { } aria-controls="panel2a-content" id="panel2a-header"> - Dataset 2 name - Dataset2 description + + Dataset 2 name + + + Dataset2 description + diff --git a/packages/client/src/pages/projects/NewProject.tsx b/packages/client/src/pages/projects/NewProject.tsx index 824b3313..ad7e415d 100644 --- a/packages/client/src/pages/projects/NewProject.tsx +++ b/packages/client/src/pages/projects/NewProject.tsx @@ -74,7 +74,14 @@ export const NewProject: React.FC = () => { return ( <> - handleChange(data)} /> + handleChange(data)} + /> diff --git a/packages/client/src/pages/projects/ProjectControl.tsx b/packages/client/src/pages/projects/ProjectControl.tsx index bc714588..62471768 100644 --- a/packages/client/src/pages/projects/ProjectControl.tsx +++ b/packages/client/src/pages/projects/ProjectControl.tsx @@ -6,8 +6,7 @@ import DeleteIcon from '@mui/icons-material/DeleteOutlined'; import { Project } from '../../graphql/graphql'; import { useDeleteProjectMutation } from '../../graphql/project/project'; import { useConfirmation } from '../../context/Confirmation.context'; -import {useEffect} from 'react'; - +import { useEffect } from 'react'; const ProjectControl: React.FC = () => { const { projects, updateProjectList } = useProject(); @@ -55,20 +54,16 @@ const ProjectControl: React.FC = () => { maxWidth: 120, cellClassName: 'delete', getActions: (params) => { - return [} label="Delete" onClick={() => handleDelete(params.id)}/>]; + return [} label="Delete" onClick={() => handleDelete(params.id)} />]; } } ]; return ( <> - Project Control + Project Control - row._id} - /> + row._id} /> ); diff --git a/packages/client/src/pages/projects/ProjectUserPermissions.tsx b/packages/client/src/pages/projects/ProjectUserPermissions.tsx index 003a8994..368ab392 100644 --- a/packages/client/src/pages/projects/ProjectUserPermissions.tsx +++ b/packages/client/src/pages/projects/ProjectUserPermissions.tsx @@ -95,7 +95,7 @@ export const ProjectUserPermissions: React.FC = () => { return ( <> - User Permissions + User Permissions 'auto'} rows={rows} diff --git a/packages/client/src/pages/studies/DownloadTags.tsx b/packages/client/src/pages/studies/DownloadTags.tsx index 210011a7..6b32995d 100644 --- a/packages/client/src/pages/studies/DownloadTags.tsx +++ b/packages/client/src/pages/studies/DownloadTags.tsx @@ -3,7 +3,7 @@ import { Button, Container, Typography } from '@mui/material'; export const DownloadTags: React.FC = () => { return ( - Download Tags + Download Tags diff --git a/packages/client/src/pages/studies/StudyControl.tsx b/packages/client/src/pages/studies/StudyControl.tsx index d2aa258c..96e6ab08 100644 --- a/packages/client/src/pages/studies/StudyControl.tsx +++ b/packages/client/src/pages/studies/StudyControl.tsx @@ -54,20 +54,16 @@ export const StudyControl: React.FC = () => { maxWidth: 120, cellClassName: 'delete', getActions: (params) => { - return [} label="Delete" onClick={() => handleDelete(params.id)}/>]; + return [} label="Delete" onClick={() => handleDelete(params.id)} />]; } } ]; return ( <> - Study Control + Study Control - row._id} - /> + row._id} /> ); diff --git a/packages/client/src/pages/studies/UserPermissions.tsx b/packages/client/src/pages/studies/UserPermissions.tsx index 78dd260b..2d7d22a0 100644 --- a/packages/client/src/pages/studies/UserPermissions.tsx +++ b/packages/client/src/pages/studies/UserPermissions.tsx @@ -103,7 +103,7 @@ export const StudyUserPermissions: React.FC = () => { flex: 1, headerName: 'Study Admin', renderCell: (params) => , - renderEditCell: (params) => , + renderEditCell: (params) => }, { field: 'visibleSwitch', @@ -113,7 +113,7 @@ export const StudyUserPermissions: React.FC = () => { renderCell: (params) => , maxWidth: 200, flex: 1, - renderEditCell: (params) => , + renderEditCell: (params) => }, { field: 'switch', @@ -123,13 +123,13 @@ export const StudyUserPermissions: React.FC = () => { flex: 1, headerName: 'Contribute', renderCell: (params) => , - renderEditCell: (params) => , + renderEditCell: (params) => } ]; return ( <> - User Permissions + User Permissions 'auto'} rows={rows} diff --git a/packages/client/src/services/tag-field-generator.service.tsx b/packages/client/src/services/tag-field-generator.service.tsx index 58b64de4..fbac1c3e 100644 --- a/packages/client/src/services/tag-field-generator.service.tsx +++ b/packages/client/src/services/tag-field-generator.service.tsx @@ -1,4 +1,13 @@ -import { TagFieldType, AslLexField, AutoCompleteField, BooleanField, EmbeddedVideoOption, FreeTextField, NumericField, SliderField } from '../models/TagField'; +import { + TagFieldType, + AslLexField, + AutoCompleteField, + BooleanField, + EmbeddedVideoOption, + FreeTextField, + NumericField, + SliderField +} from '../models/TagField'; export const TagFieldGeneratorService = (tagFieldType: TagFieldType) => { /** diff --git a/packages/gateway/.prettierrc.js b/packages/gateway/.prettierrc.js index 0d9f53f6..2d293bab 100644 --- a/packages/gateway/.prettierrc.js +++ b/packages/gateway/.prettierrc.js @@ -1,3 +1,3 @@ module.exports = { - ...require('../.prettierrc.js'), + ...require('../../.prettierrc.js'), } diff --git a/packages/server/.prettierrc b/packages/server/.prettierrc deleted file mode 100644 index dcb72794..00000000 --- a/packages/server/.prettierrc +++ /dev/null @@ -1,4 +0,0 @@ -{ - "singleQuote": true, - "trailingComma": "all" -} \ No newline at end of file diff --git a/packages/server/.prettierrc.js b/packages/server/.prettierrc.js new file mode 100644 index 00000000..2d293bab --- /dev/null +++ b/packages/server/.prettierrc.js @@ -0,0 +1,3 @@ +module.exports = { + ...require('../../.prettierrc.js'), +} diff --git a/packages/server/package.json b/packages/server/package.json index 336ca500..e43070e2 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -8,7 +8,8 @@ "scripts": { "prebuild": "rimraf dist", "build": "nest build", - "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"", + "prettier": "prettier -l \"src/**/*.ts\"", + "prettier:fix": "prettier -wl \"src/**/*.ts\"", "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", diff --git a/packages/server/src/app.module.ts b/packages/server/src/app.module.ts index 2e28fdd1..5e022e71 100644 --- a/packages/server/src/app.module.ts +++ b/packages/server/src/app.module.ts @@ -41,6 +41,6 @@ import { AuthModule } from './auth/auth.module'; TagModule, SharedModule, AuthModule - ], + ] }) export class AppModule {} diff --git a/packages/server/src/auth/auth.module.ts b/packages/server/src/auth/auth.module.ts index 8e0bdd7a..f432923b 100644 --- a/packages/server/src/auth/auth.module.ts +++ b/packages/server/src/auth/auth.module.ts @@ -26,7 +26,7 @@ import { AuthResolver } from './auth.resolver'; }; return options; } - }), + }) ], providers: [ AuthService, diff --git a/packages/server/src/auth/auth.resolver.ts b/packages/server/src/auth/auth.resolver.ts index d989c0f9..ee78914e 100644 --- a/packages/server/src/auth/auth.resolver.ts +++ b/packages/server/src/auth/auth.resolver.ts @@ -4,8 +4,8 @@ import { UseGuards } from '@nestjs/common'; import { UserContext } from './user.decorator'; import { TokenPayload } from './user.dto'; import { AuthService } from './auth.service'; -import {OrganizationContext} from 'src/organization/organization.context'; -import {Organization} from 'src/organization/organization.model'; +import { OrganizationContext } from 'src/organization/organization.context'; +import { Organization } from 'src/organization/organization.model'; @UseGuards(JwtAuthGuard) @Resolver() @@ -13,9 +13,11 @@ export class AuthResolver { constructor(private readonly authService: AuthService) {} @Mutation(() => Boolean) - async grantOwner(@Args('targetUser', { type: () => ID }) targetUser: string, - @UserContext() requestingUser: TokenPayload, - @OrganizationContext() organization: Organization): Promise { + async grantOwner( + @Args('targetUser', { type: () => ID }) targetUser: string, + @UserContext() requestingUser: TokenPayload, + @OrganizationContext() organization: Organization + ): Promise { await this.authService.grantOwner(targetUser, requestingUser.id, organization._id); return true; } diff --git a/packages/server/src/auth/auth.service.ts b/packages/server/src/auth/auth.service.ts index a010f96e..e17c8da5 100644 --- a/packages/server/src/auth/auth.service.ts +++ b/packages/server/src/auth/auth.service.ts @@ -10,9 +10,11 @@ import { Roles } from './roles'; export class AuthService { private publicKey: string | null = null; - constructor(private readonly httpService: HttpService, - private readonly configService: ConfigService, - @Inject(CASBIN_PROVIDER) private readonly enforcer: casbin.Enforcer) {} + constructor( + private readonly httpService: HttpService, + private readonly configService: ConfigService, + @Inject(CASBIN_PROVIDER) private readonly enforcer: casbin.Enforcer + ) {} // TODO: In the future this will be replaced by a library which handles // key rotation diff --git a/packages/server/src/auth/casbin.provider.ts b/packages/server/src/auth/casbin.provider.ts index 97c4c0a3..d559b149 100644 --- a/packages/server/src/auth/casbin.provider.ts +++ b/packages/server/src/auth/casbin.provider.ts @@ -1,5 +1,5 @@ import { Provider } from '@nestjs/common'; -import {ConfigService} from '@nestjs/config'; +import { ConfigService } from '@nestjs/config'; import * as casbin from 'casbin'; import { MongooseAdapter } from 'casbin-mongoose-adapter'; import { roleHierarchy } from './roles'; @@ -25,7 +25,7 @@ export const casbinProvider: Provider = { ...roleToTagPermissions, ...roleToDatasetPermissions ]; - await Promise.all(groups.map(group => enforcer.addNamedGroupingPolicy('g', ...group))); + await Promise.all(groups.map((group) => enforcer.addNamedGroupingPolicy('g', ...group))); return enforcer; }, diff --git a/packages/server/src/config/configuration.ts b/packages/server/src/config/configuration.ts index 4f7947f6..9cf2592d 100644 --- a/packages/server/src/config/configuration.ts +++ b/packages/server/src/config/configuration.ts @@ -17,7 +17,7 @@ export default () => ({ prefix: process.env.GCP_STORAGE_DATASET_PREFIX || 'datasets' }, entry: { - signedURLExpiration: process.env.GCP_STORAGE_ENTRY_SIGNED_URL_EXPIRATION || (15 * 60 * 1000) // 15 minutes + signedURLExpiration: process.env.GCP_STORAGE_ENTRY_SIGNED_URL_EXPIRATION || 15 * 60 * 1000 // 15 minutes }, auth: { publicKeyUrl: process.env.AUTH_PUBLIC_KEY_URL || 'https://test-auth-service.sail.codes/public-key' diff --git a/packages/server/src/dataset/dataset.resolver.ts b/packages/server/src/dataset/dataset.resolver.ts index 33623bb7..35d83acd 100644 --- a/packages/server/src/dataset/dataset.resolver.ts +++ b/packages/server/src/dataset/dataset.resolver.ts @@ -17,7 +17,10 @@ import { DatasetPermissions } from '../auth/permissions/dataset'; @UseGuards(JwtAuthGuard) @Resolver(() => Dataset) export class DatasetResolver { - constructor(private readonly datasetService: DatasetService, @Inject(CASBIN_PROVIDER) private readonly enforcer: casbin.Enforcer) {} + constructor( + private readonly datasetService: DatasetService, + @Inject(CASBIN_PROVIDER) private readonly enforcer: casbin.Enforcer + ) {} @Query(() => [Dataset]) async getDatasets(@OrganizationContext() organization: Organization): Promise { @@ -27,9 +30,11 @@ export class DatasetResolver { } @Mutation(() => Dataset) - async createDataset(@Args('dataset') dataset: DatasetCreate, - @OrganizationContext() organization: Organization, - @UserContext() user: TokenPayload): Promise { + async createDataset( + @Args('dataset') dataset: DatasetCreate, + @OrganizationContext() organization: Organization, + @UserContext() user: TokenPayload + ): Promise { if (!(await this.enforcer.enforce(user.id, DatasetPermissions.CREATE, organization._id))) { throw new UnauthorizedException('User does not have permission to create a dataset in this organization'); } @@ -46,8 +51,8 @@ export class DatasetResolver { async changeDatasetName( @Args('dataset', { type: () => ID }, DatasetPipe) dataset: Dataset, @Args('newName') newName: string, - @OrganizationContext() organization: Organization): Promise { - + @OrganizationContext() organization: Organization + ): Promise { if (!(await this.enforcer.enforce(organization._id, DatasetPermissions.UPDATE, dataset._id))) { throw new UnauthorizedException('User does not have permission to update this dataset'); } @@ -65,8 +70,8 @@ export class DatasetResolver { @Mutation(() => Boolean) async changeDatasetDescription( @Args('dataset', { type: () => ID }, DatasetPipe) dataset: Dataset, - @Args('newDescription') newDescription: string): Promise { - + @Args('newDescription') newDescription: string + ): Promise { if (!(await this.enforcer.enforce(dataset._id, DatasetPermissions.UPDATE, dataset._id))) { throw new UnauthorizedException('User does not have permission to update this dataset'); } diff --git a/packages/server/src/dataset/dataset.service.ts b/packages/server/src/dataset/dataset.service.ts index 938ebccd..cf616797 100644 --- a/packages/server/src/dataset/dataset.service.ts +++ b/packages/server/src/dataset/dataset.service.ts @@ -11,9 +11,11 @@ import * as casbin from 'casbin'; export class DatasetService { private readonly datasetPrefix = this.configService.getOrThrow('dataset.prefix'); - constructor(@InjectModel(Dataset.name) private readonly datasetModel: Model, - private readonly configService: ConfigService, - @Inject(CASBIN_PROVIDER) private readonly enforcer: casbin.Enforcer) {} + constructor( + @InjectModel(Dataset.name) private readonly datasetModel: Model, + private readonly configService: ConfigService, + @Inject(CASBIN_PROVIDER) private readonly enforcer: casbin.Enforcer + ) {} async findById(id: string): Promise { return this.datasetModel.findById(id); diff --git a/packages/server/src/entry/dtos/upload-result.dto.ts b/packages/server/src/entry/dtos/upload-result.dto.ts index 9a7b70e8..2ddd7824 100644 --- a/packages/server/src/entry/dtos/upload-result.dto.ts +++ b/packages/server/src/entry/dtos/upload-result.dto.ts @@ -7,7 +7,7 @@ export enum UploadStatus { } registerEnumType(UploadStatus, { - name: 'UploadStatus', + name: 'UploadStatus' }); @ObjectType() diff --git a/packages/server/src/entry/entry.module.ts b/packages/server/src/entry/entry.module.ts index a494f190..0c59c266 100644 --- a/packages/server/src/entry/entry.module.ts +++ b/packages/server/src/entry/entry.module.ts @@ -20,7 +20,7 @@ import { AuthModule } from '../auth/auth.module'; MongooseModule.forFeature([ { name: Entry.name, schema: EntrySchema }, { name: UploadSession.name, schema: UploadSessionSchema }, - { name: EntryUpload.name, schema: EntryUploadSchema }, + { name: EntryUpload.name, schema: EntryUploadSchema } ]), DatasetModule, GcpModule, diff --git a/packages/server/src/entry/models/upload-session.model.ts b/packages/server/src/entry/models/upload-session.model.ts index 67b1953f..e07a9a41 100644 --- a/packages/server/src/entry/models/upload-session.model.ts +++ b/packages/server/src/entry/models/upload-session.model.ts @@ -2,7 +2,6 @@ import { Schema, Prop, SchemaFactory } from '@nestjs/mongoose'; import { Document } from 'mongoose'; import { Field, ID, ObjectType } from '@nestjs/graphql'; - /** * Represents a single user uploading against a dataset. */ diff --git a/packages/server/src/entry/pipes/upload-session.pipe.ts b/packages/server/src/entry/pipes/upload-session.pipe.ts index c2565c6f..2479d68e 100644 --- a/packages/server/src/entry/pipes/upload-session.pipe.ts +++ b/packages/server/src/entry/pipes/upload-session.pipe.ts @@ -1,7 +1,6 @@ import { BadRequestException, Injectable, PipeTransform } from '@nestjs/common'; import { UploadSession } from '../models/upload-session.model'; -import {UploadSessionService} from '../services/upload-session.service'; - +import { UploadSessionService } from '../services/upload-session.service'; @Injectable() export class UploadSessionPipe implements PipeTransform> { diff --git a/packages/server/src/entry/resolvers/entry.resolver.ts b/packages/server/src/entry/resolvers/entry.resolver.ts index b7599ac7..55ffdee6 100644 --- a/packages/server/src/entry/resolvers/entry.resolver.ts +++ b/packages/server/src/entry/resolvers/entry.resolver.ts @@ -3,7 +3,7 @@ import { Dataset } from '../../dataset/dataset.model'; import { Entry } from '../models/entry.model'; import { EntryService } from '../services/entry.service'; import { DatasetPipe } from '../../dataset/pipes/dataset.pipe'; -import { UseGuards, Inject, UnauthorizedException} from '@nestjs/common'; +import { UseGuards, Inject, UnauthorizedException } from '@nestjs/common'; import { JwtAuthGuard } from '../../auth/jwt.guard'; import { DatasetPermissions } from '../../auth/permissions/dataset'; import { CASBIN_PROVIDER } from '../../auth/casbin.provider'; @@ -14,10 +14,16 @@ import { UserContext } from '../../auth/user.decorator'; @UseGuards(JwtAuthGuard) @Resolver(() => Entry) export class EntryResolver { - constructor(private readonly entryService: EntryService, @Inject(CASBIN_PROVIDER) private readonly enforcer: casbin.Enforcer) {} + constructor( + private readonly entryService: EntryService, + @Inject(CASBIN_PROVIDER) private readonly enforcer: casbin.Enforcer + ) {} @Query(() => [Entry]) - async entryForDataset(@Args('dataset', { type: () => ID }, DatasetPipe) dataset: Dataset, @UserContext() user: TokenPayload): Promise { + async entryForDataset( + @Args('dataset', { type: () => ID }, DatasetPipe) dataset: Dataset, + @UserContext() user: TokenPayload + ): Promise { if (!(await this.enforcer.enforce(user.id, DatasetPermissions.READ, dataset._id))) { throw new UnauthorizedException('User cannot read entries on this dataset'); } diff --git a/packages/server/src/entry/resolvers/upload-session.resolver.ts b/packages/server/src/entry/resolvers/upload-session.resolver.ts index 957e6166..65f6b4f1 100644 --- a/packages/server/src/entry/resolvers/upload-session.resolver.ts +++ b/packages/server/src/entry/resolvers/upload-session.resolver.ts @@ -10,18 +10,23 @@ import { JwtAuthGuard } from '../../auth/jwt.guard'; import { DatasetPermissions } from '../../auth/permissions/dataset'; import { CASBIN_PROVIDER } from '../../auth/casbin.provider'; import * as casbin from 'casbin'; -import {UserContext} from 'src/auth/user.decorator'; -import {TokenPayload} from 'src/auth/user.dto'; - +import { UserContext } from 'src/auth/user.decorator'; +import { TokenPayload } from 'src/auth/user.dto'; @UseGuards(JwtAuthGuard) @Injectable() export class UploadSessionResolver { - constructor(private readonly uploadSessionService: UploadSessionService, @Inject(CASBIN_PROVIDER) private readonly enforcer: casbin.Enforcer) {} + constructor( + private readonly uploadSessionService: UploadSessionService, + @Inject(CASBIN_PROVIDER) private readonly enforcer: casbin.Enforcer + ) {} // TODO: Grab the user from the request @Mutation(() => UploadSession) - async createUploadSession(@Args('dataset', { type: () => ID }, DatasetPipe) dataset: Dataset, @UserContext() user: TokenPayload): Promise { + async createUploadSession( + @Args('dataset', { type: () => ID }, DatasetPipe) dataset: Dataset, + @UserContext() user: TokenPayload + ): Promise { if (!(await this.enforcer.enforce(user.id, DatasetPermissions.UPDATE, dataset._id))) { throw new UnauthorizedException('User cannot write entries on this dataset'); } @@ -31,8 +36,10 @@ export class UploadSessionResolver { // TODO: Add return for any cleanup @Mutation(() => UploadResult) - async completeUploadSession(@Args('session', { type: () => ID }, UploadSessionPipe) uploadSession: UploadSession, - @UserContext() user: TokenPayload): Promise { + async completeUploadSession( + @Args('session', { type: () => ID }, UploadSessionPipe) uploadSession: UploadSession, + @UserContext() user: TokenPayload + ): Promise { if (!(await this.enforcer.enforce(user.id, DatasetPermissions.UPDATE, uploadSession.dataset))) { throw new UnauthorizedException('User cannot write entries on this dataset'); } @@ -41,8 +48,10 @@ export class UploadSessionResolver { } @Query(() => String, { description: 'Get the presigned URL for where to upload the CSV against' }) - async getCSVUploadURL(@Args('session', { type: () => ID }, UploadSessionPipe) uploadSession: UploadSession, - @UserContext() user: TokenPayload): Promise { + async getCSVUploadURL( + @Args('session', { type: () => ID }, UploadSessionPipe) uploadSession: UploadSession, + @UserContext() user: TokenPayload + ): Promise { if (!(await this.enforcer.enforce(user.id, DatasetPermissions.UPDATE, uploadSession.dataset))) { throw new UnauthorizedException('User cannot write entries on this dataset'); } @@ -51,8 +60,10 @@ export class UploadSessionResolver { } @Query(() => UploadResult) - async validateCSV(@Args('session', { type: () => ID }, UploadSessionPipe) uploadSession: UploadSession, - @UserContext() user: TokenPayload): Promise { + async validateCSV( + @Args('session', { type: () => ID }, UploadSessionPipe) uploadSession: UploadSession, + @UserContext() user: TokenPayload + ): Promise { if (!(await this.enforcer.enforce(user.id, DatasetPermissions.UPDATE, uploadSession.dataset))) { throw new UnauthorizedException('User cannot write entries on this dataset'); } @@ -62,11 +73,12 @@ export class UploadSessionResolver { // TODO: Implement caching for the upload session since it's used a lot @Query(() => String) - async getEntryUploadURL(@Args('session', { type: () => ID }, UploadSessionPipe) uploadSession: UploadSession, - @Args('filename') filename: string, - @Args('contentType') contentType: string, - @UserContext() user: TokenPayload): Promise { - + async getEntryUploadURL( + @Args('session', { type: () => ID }, UploadSessionPipe) uploadSession: UploadSession, + @Args('filename') filename: string, + @Args('contentType') contentType: string, + @UserContext() user: TokenPayload + ): Promise { if (!(await this.enforcer.enforce(user.id, DatasetPermissions.UPDATE, uploadSession.dataset))) { throw new UnauthorizedException('User cannot write entries on this dataset'); } diff --git a/packages/server/src/entry/services/csv-validation.service.ts b/packages/server/src/entry/services/csv-validation.service.ts index 1f468479..06813d89 100644 --- a/packages/server/src/entry/services/csv-validation.service.ts +++ b/packages/server/src/entry/services/csv-validation.service.ts @@ -18,10 +18,7 @@ export interface CsvValidationResult { */ @Injectable() export class CsvValidationService { - private static REQUIRED_CSV_HEADERS = [ - 'entryID', - 'filename' - ]; + private static REQUIRED_CSV_HEADERS = ['entryID', 'filename']; constructor(private readonly entryService: EntryService) {} @@ -96,7 +93,11 @@ export class CsvValidationService { } /** Helper to convert the CSV lines to an array of objects */ - private toEntryUpload(row: any, _dataset: Dataset, session: UploadSession): { data?: EntryUpload, errorMessage?: string } { + private toEntryUpload( + row: any, + _dataset: Dataset, + session: UploadSession + ): { data?: EntryUpload; errorMessage?: string } { const entryID = row.entryID; const filename = row.filename; @@ -111,7 +112,7 @@ export class CsvValidationService { session: session._id, entryID, filename, - metadata, + metadata } }; } diff --git a/packages/server/src/entry/services/entry-upload.service.ts b/packages/server/src/entry/services/entry-upload.service.ts index 634dc979..f6a421fb 100644 --- a/packages/server/src/entry/services/entry-upload.service.ts +++ b/packages/server/src/entry/services/entry-upload.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { EntryUpload } from '../models/entry-upload.model'; import { Model } from 'mongoose'; -import {UploadSession} from '../models/upload-session.model'; +import { UploadSession } from '../models/upload-session.model'; @Injectable() export class EntryUploadService { diff --git a/packages/server/src/entry/services/entry.service.ts b/packages/server/src/entry/services/entry.service.ts index 17ffef9f..309d8e4c 100644 --- a/packages/server/src/entry/services/entry.service.ts +++ b/packages/server/src/entry/services/entry.service.ts @@ -14,9 +14,11 @@ export class EntryService { private readonly bucket: Bucket = this.storage.bucket(this.bucketName); private readonly expiration = this.configService.getOrThrow('entry.signedURLExpiration'); - constructor(@InjectModel(Entry.name) private readonly entryMode: Model, - @Inject(GCP_STORAGE_PROVIDER) private readonly storage: Storage, - private readonly configService: ConfigService) {} + constructor( + @InjectModel(Entry.name) private readonly entryMode: Model, + @Inject(GCP_STORAGE_PROVIDER) private readonly storage: Storage, + private readonly configService: ConfigService + ) {} async find(entryID: string): Promise { return this.entryMode.findOne({ _id: entryID }); @@ -54,10 +56,10 @@ export class EntryService { } /** - * Get how long the signed URL is valid for in milliseconds. - * - * In the future, this could be configurable per entry. - */ + * Get how long the signed URL is valid for in milliseconds. + * + * In the future, this could be configurable per entry. + */ async getSignedUrlExpiration(_entry: Entry): Promise { return this.expiration; } diff --git a/packages/server/src/entry/services/upload-session.service.ts b/packages/server/src/entry/services/upload-session.service.ts index 01dbf774..e900d7b8 100644 --- a/packages/server/src/entry/services/upload-session.service.ts +++ b/packages/server/src/entry/services/upload-session.service.ts @@ -20,13 +20,15 @@ export class UploadSessionService { private readonly entryFolder = this.configService.getOrThrow('upload.entryFolder'); private readonly bucket: Bucket = this.storage.bucket(this.uploadBucket); - constructor(@InjectModel(UploadSession.name) private readonly uploadSessionModel: Model, - @Inject(GCP_STORAGE_PROVIDER) private readonly storage: Storage, - private readonly configService: ConfigService, - private readonly csvValidation: CsvValidationService, - private readonly datasetService: DatasetService, - private readonly entryUploadService: EntryUploadService, - private readonly entryService: EntryService) {} + constructor( + @InjectModel(UploadSession.name) private readonly uploadSessionModel: Model, + @Inject(GCP_STORAGE_PROVIDER) private readonly storage: Storage, + private readonly configService: ConfigService, + private readonly csvValidation: CsvValidationService, + private readonly datasetService: DatasetService, + private readonly entryUploadService: EntryUploadService, + private readonly entryService: EntryService + ) {} async find(id: string): Promise { return this.uploadSessionModel.findById(id).exec(); @@ -38,7 +40,7 @@ export class UploadSessionService { // Make the session const uploadSession = await this.uploadSessionModel.create({ dataset: dataset._id, - created: new Date(), + created: new Date() }); // Add in the bucket prefix for the session @@ -87,11 +89,14 @@ export class UploadSessionService { // TODO: Remove media URL // Determine media type const contentType = entryFile.metadata.contentType; - const entry = await this.entryService.create({ - entryID: entryUpload.entryID, - contentType: entryFile.metadata.contentType, - meta: entryUpload.metadata - }, dataset); + const entry = await this.entryService.create( + { + entryID: entryUpload.entryID, + contentType: entryFile.metadata.contentType, + meta: entryUpload.metadata + }, + dataset + ); // Move the entry to the dataset const fileExtension = entryUpload.filename.split('.').pop(); @@ -109,7 +114,10 @@ export class UploadSessionService { // Let users know if there were missing entries // TODO: Add concept of status to messages if (missingEntries.length > 0) { - return { status: UploadStatus.WARNING, message: `The following entries where in the CSV, but not uploaded:\n ${missingEntries.join(', ')}` }; + return { + status: UploadStatus.WARNING, + message: `The following entries where in the CSV, but not uploaded:\n ${missingEntries.join(', ')}` + }; } // No issues @@ -121,13 +129,11 @@ export class UploadSessionService { const csvURL = `${this.uploadPrefix}/${uploadSession.bucketPrefix}/${this.csvFileName}`; const entryPrefix = `${this.uploadPrefix}/${uploadSession.bucketPrefix}/${this.entryFolder}`; - const [url] = await this.bucket - .file(csvURL) - .getSignedUrl({ - action: 'write', - expires: Date.now() + 2 * 60 * 1000, // 2 minutes - contentType: 'text/csv', - }); + const [url] = await this.bucket.file(csvURL).getSignedUrl({ + action: 'write', + expires: Date.now() + 2 * 60 * 1000, // 2 minutes + contentType: 'text/csv' + }); // Add the url to the upload session to signify the upload is ready await this.uploadSessionModel.updateOne({ _id: uploadSession._id }, { $set: { csvURL, entryPrefix } }); @@ -142,13 +148,11 @@ export class UploadSessionService { const entryURL = `${uploadSession.entryPrefix}/${filename}`; - const [url] = await this.bucket - .file(entryURL) - .getSignedUrl({ - action: 'write', - expires: Date.now() + 2 * 60 * 1000, // 2 minutes - contentType: filetype, - }); + const [url] = await this.bucket.file(entryURL).getSignedUrl({ + action: 'write', + expires: Date.now() + 2 * 60 * 1000, // 2 minutes + contentType: filetype + }); return url; } @@ -182,7 +186,9 @@ export class UploadSessionService { } // Otherwise store the validated results for the next step - await Promise.all(csvValidationResults.entryUploads!.map(entryUpload => this.entryUploadService.create(entryUpload))); + await Promise.all( + csvValidationResults.entryUploads!.map((entryUpload) => this.entryUploadService.create(entryUpload)) + ); // Return the validation status return { status: UploadStatus.SUCCESS }; diff --git a/packages/server/src/gcp/providers/storage.provider.ts b/packages/server/src/gcp/providers/storage.provider.ts index 4f506bb7..b0f7aa8a 100644 --- a/packages/server/src/gcp/providers/storage.provider.ts +++ b/packages/server/src/gcp/providers/storage.provider.ts @@ -19,5 +19,5 @@ export const storageProvider: Provider = { keyFilename: configService.getOrThrow('gcp.storage.keyFilename') }); }, - inject: [ConfigService], + inject: [ConfigService] }; diff --git a/packages/server/src/organization/organization.module.ts b/packages/server/src/organization/organization.module.ts index 835b8c5d..9427063a 100644 --- a/packages/server/src/organization/organization.module.ts +++ b/packages/server/src/organization/organization.module.ts @@ -6,9 +6,7 @@ import { Organization, OrganizationSchema } from './organization.model'; import { CreateOrganizationPipe } from './pipes/create.pipe'; @Module({ - imports: [ - MongooseModule.forFeature([{ name: Organization.name, schema: OrganizationSchema }]) - ], + imports: [MongooseModule.forFeature([{ name: Organization.name, schema: OrganizationSchema }])], providers: [OrganizationResolver, OrganizationService, CreateOrganizationPipe] }) export class OrganizationModule {} diff --git a/packages/server/src/organization/organization.resolver.ts b/packages/server/src/organization/organization.resolver.ts index 48119bc5..c5c11aea 100644 --- a/packages/server/src/organization/organization.resolver.ts +++ b/packages/server/src/organization/organization.resolver.ts @@ -25,7 +25,9 @@ export class OrganizationResolver { // TODO: Add authentication guard @UseGuards(JwtAuthGuard) @Mutation(() => Organization) - async createOrganization(@Args('organization', CreateOrganizationPipe) organization: OrganizationCreate): Promise { + async createOrganization( + @Args('organization', CreateOrganizationPipe) organization: OrganizationCreate + ): Promise { return this.orgService.create(organization); } } diff --git a/packages/server/src/project/project.module.ts b/packages/server/src/project/project.module.ts index 79ad2d25..c793d7ec 100644 --- a/packages/server/src/project/project.module.ts +++ b/packages/server/src/project/project.module.ts @@ -24,7 +24,7 @@ import { AuthModule } from '../auth/auth.module'; return schema; }, imports: [SharedModule], - inject: [MongooseMiddlewareService], + inject: [MongooseMiddlewareService] } ]), AuthModule diff --git a/packages/server/src/project/project.resolver.ts b/packages/server/src/project/project.resolver.ts index c2d1c9a8..628ac15a 100644 --- a/packages/server/src/project/project.resolver.ts +++ b/packages/server/src/project/project.resolver.ts @@ -16,12 +16,19 @@ import { ProjectPermissions } from '../auth/permissions/project'; @UseGuards(JwtAuthGuard) @Resolver(() => Project) export class ProjectResolver { - constructor(private readonly projectService: ProjectService, @Inject(CASBIN_PROVIDER) private readonly enforcer: casbin.Enforcer) {} + constructor( + private readonly projectService: ProjectService, + @Inject(CASBIN_PROVIDER) private readonly enforcer: casbin.Enforcer + ) {} @Mutation(() => Project) - async signLabCreateProject(@Args('project') project: ProjectCreate, @OrganizationContext() organization: Organization, @UserContext() user: TokenPayload): Promise { + async signLabCreateProject( + @Args('project') project: ProjectCreate, + @OrganizationContext() organization: Organization, + @UserContext() user: TokenPayload + ): Promise { // Make sure the user is allowed to create projects - if(!(await this.enforcer.enforce(user.id, ProjectPermissions.CREATE, organization._id))) { + if (!(await this.enforcer.enforce(user.id, ProjectPermissions.CREATE, organization._id))) { throw new UnauthorizedException('User does not have permission to create projects'); } @@ -33,16 +40,22 @@ export class ProjectResolver { } @Query(() => Boolean) - async projectExists(@Args('name') name: string, @OrganizationContext() organization: Organization, @UserContext() _user: TokenPayload): Promise { + async projectExists( + @Args('name') name: string, + @OrganizationContext() organization: Organization, + @UserContext() _user: TokenPayload + ): Promise { return this.projectService.exists(name, organization._id); } // TODO: Handle Project deletion @Mutation(() => Boolean) - async deleteProject(@Args('project', { type: () => ID }, ProjectPipe) project: Project, - @UserContext() user: TokenPayload, - @OrganizationContext() organization: Organization): Promise { - if(!(await this.enforcer.enforce(user.id, ProjectPermissions.DELETE, organization._id))) { + async deleteProject( + @Args('project', { type: () => ID }, ProjectPipe) project: Project, + @UserContext() user: TokenPayload, + @OrganizationContext() organization: Organization + ): Promise { + if (!(await this.enforcer.enforce(user.id, ProjectPermissions.DELETE, organization._id))) { throw new UnauthorizedException('User does not have permission to delete projects'); } @@ -52,8 +65,11 @@ export class ProjectResolver { // TODO: Handle the ability to get project based on user access @Query(() => [Project]) - async getProjects(@OrganizationContext() organization: Organization, @UserContext() user: TokenPayload): Promise { - if(!(await this.enforcer.enforce(user.id, ProjectPermissions.READ, organization._id))) { + async getProjects( + @OrganizationContext() organization: Organization, + @UserContext() user: TokenPayload + ): Promise { + if (!(await this.enforcer.enforce(user.id, ProjectPermissions.READ, organization._id))) { throw new UnauthorizedException('User does not have permission to read projects'); } return this.projectService.findAll(organization._id); diff --git a/packages/server/src/project/project.service.ts b/packages/server/src/project/project.service.ts index 5bfcf66f..226d429e 100644 --- a/packages/server/src/project/project.service.ts +++ b/packages/server/src/project/project.service.ts @@ -8,8 +8,10 @@ import * as casbin from 'casbin'; @Injectable() export class ProjectService { - constructor(@InjectModel(Project.name) private projectModel: Model, - @Inject(CASBIN_PROVIDER) private readonly enforcer: casbin.Enforcer) {} + constructor( + @InjectModel(Project.name) private projectModel: Model, + @Inject(CASBIN_PROVIDER) private readonly enforcer: casbin.Enforcer + ) {} async create(project: ProjectCreate, organization: string): Promise { const newProject = await this.projectModel.create({ diff --git a/packages/server/src/study/pipes/create.pipe.ts b/packages/server/src/study/pipes/create.pipe.ts index f78ab482..d4e62b14 100644 --- a/packages/server/src/study/pipes/create.pipe.ts +++ b/packages/server/src/study/pipes/create.pipe.ts @@ -1,7 +1,7 @@ import { BadRequestException, Injectable, PipeTransform } from '@nestjs/common'; -import {ProjectPipe} from 'src/project/pipes/project.pipe'; -import {StudyCreate} from '../dtos/create.dto'; -import {StudyService} from '../study.service'; +import { ProjectPipe } from 'src/project/pipes/project.pipe'; +import { StudyCreate } from '../dtos/create.dto'; +import { StudyService } from '../study.service'; @Injectable() export class StudyCreatePipe implements PipeTransform> { diff --git a/packages/server/src/study/pipes/study.pipe.ts b/packages/server/src/study/pipes/study.pipe.ts index b973e3d1..5d6bf953 100644 --- a/packages/server/src/study/pipes/study.pipe.ts +++ b/packages/server/src/study/pipes/study.pipe.ts @@ -14,4 +14,3 @@ export class StudyPipe implements PipeTransform> { return study; } } - diff --git a/packages/server/src/study/study.module.ts b/packages/server/src/study/study.module.ts index 9dbbbaa3..6167d974 100644 --- a/packages/server/src/study/study.module.ts +++ b/packages/server/src/study/study.module.ts @@ -11,23 +11,28 @@ import { SharedModule } from '../shared/shared.module'; import { AuthModule } from '../auth/auth.module'; @Module({ - imports: [MongooseModule.forFeatureAsync([ - { - name: Study.name, - useFactory: (middlewareService: MongooseMiddlewareService) => { - const schema = StudySchema; + imports: [ + MongooseModule.forFeatureAsync([ + { + name: Study.name, + useFactory: (middlewareService: MongooseMiddlewareService) => { + const schema = StudySchema; - schema.pre('deleteOne', async function () { - const study = await this.model.findOne(this.getQuery()); - await middlewareService.apply(Study.name, 'deleteOne', study); - }); + schema.pre('deleteOne', async function () { + const study = await this.model.findOne(this.getQuery()); + await middlewareService.apply(Study.name, 'deleteOne', study); + }); - return schema; - }, - imports: [SharedModule], - inject: [MongooseMiddlewareService], - } - ]), ProjectModule, SharedModule, AuthModule], + return schema; + }, + imports: [SharedModule], + inject: [MongooseMiddlewareService] + } + ]), + ProjectModule, + SharedModule, + AuthModule + ], providers: [StudyService, StudyResolver, StudyPipe, StudyCreatePipe], exports: [StudyService, StudyPipe] }) diff --git a/packages/server/src/study/study.resolver.ts b/packages/server/src/study/study.resolver.ts index 3859df73..9543be5f 100644 --- a/packages/server/src/study/study.resolver.ts +++ b/packages/server/src/study/study.resolver.ts @@ -11,16 +11,22 @@ import { JwtAuthGuard } from '../auth/jwt.guard'; import { CASBIN_PROVIDER } from '../auth/casbin.provider'; import * as casbin from 'casbin'; import { StudyPermissions } from '../auth/permissions/study'; -import {UserContext} from 'src/auth/user.decorator'; -import {TokenPayload} from 'src/auth/user.dto'; +import { UserContext } from 'src/auth/user.decorator'; +import { TokenPayload } from 'src/auth/user.dto'; @UseGuards(JwtAuthGuard) @Resolver(() => Study) export class StudyResolver { - constructor(private readonly studyService: StudyService, @Inject(CASBIN_PROVIDER) private readonly enforcer: casbin.Enforcer) {} + constructor( + private readonly studyService: StudyService, + @Inject(CASBIN_PROVIDER) private readonly enforcer: casbin.Enforcer + ) {} @Mutation(() => Study) - async createStudy(@Args('study', { type: () => StudyCreate }, StudyCreatePipe) study: StudyCreate, @UserContext() user: TokenPayload): Promise { + async createStudy( + @Args('study', { type: () => StudyCreate }, StudyCreatePipe) study: StudyCreate, + @UserContext() user: TokenPayload + ): Promise { if (!(await this.enforcer.enforce(user.id, StudyPermissions.CREATE, study.project))) { throw new UnauthorizedException('User cannot create studies on this project'); } @@ -29,7 +35,10 @@ export class StudyResolver { } @Query(() => Boolean) - async studyExists(@Args('name') name: string, @Args('project', { type: () => ID }, ProjectPipe) project: Project): Promise { + async studyExists( + @Args('name') name: string, + @Args('project', { type: () => ID }, ProjectPipe) project: Project + ): Promise { if (!(await this.enforcer.enforce(name, StudyPermissions.READ, project))) { throw new UnauthorizedException('User cannot read studies on this project'); } @@ -54,7 +63,10 @@ export class StudyResolver { } @Mutation(() => Study) - async changeStudyName(@Args('study',{ type: () => ID }, StudyPipe) study: Study, @Args('newName') newName: string): Promise { + async changeStudyName( + @Args('study', { type: () => ID }, StudyPipe) study: Study, + @Args('newName') newName: string + ): Promise { if (!(await this.enforcer.enforce(study.name, StudyPermissions.UPDATE, study._id))) { throw new UnauthorizedException('User cannot update studies on this project'); } @@ -63,7 +75,10 @@ export class StudyResolver { } @Mutation(() => Study) - async changeStudyDescription(@Args('study', { type: () => ID }, StudyPipe) study: Study, @Args('newDescription') newDescription: string): Promise { + async changeStudyDescription( + @Args('study', { type: () => ID }, StudyPipe) study: Study, + @Args('newDescription') newDescription: string + ): Promise { if (!(await this.enforcer.enforce(study.name, StudyPermissions.UPDATE, study._id))) { throw new UnauthorizedException('User cannot update studies on this project'); } diff --git a/packages/server/src/study/study.service.ts b/packages/server/src/study/study.service.ts index 634d07ee..8a5689de 100644 --- a/packages/server/src/study/study.service.ts +++ b/packages/server/src/study/study.service.ts @@ -11,8 +11,11 @@ import * as casbin from 'casbin'; @Injectable() export class StudyService { - constructor(@InjectModel(Study.name) private readonly studyModel: Model, middlewareService: MongooseMiddlewareService, - @Inject(CASBIN_PROVIDER) private readonly enforcer: casbin.Enforcer) { + constructor( + @InjectModel(Study.name) private readonly studyModel: Model, + middlewareService: MongooseMiddlewareService, + @Inject(CASBIN_PROVIDER) private readonly enforcer: casbin.Enforcer + ) { // Remove cooresponding studies when a project is deleted middlewareService.register(Project.name, 'deleteOne', async (project: Project) => { // TODO: Update Casbin policies diff --git a/packages/server/src/tag/pipes/tag.pipe.ts b/packages/server/src/tag/pipes/tag.pipe.ts index 8485a499..9b53456a 100644 --- a/packages/server/src/tag/pipes/tag.pipe.ts +++ b/packages/server/src/tag/pipes/tag.pipe.ts @@ -1,5 +1,5 @@ import { BadRequestException, Injectable, PipeTransform } from '@nestjs/common'; -import {Tag} from '../tag.model'; +import { Tag } from '../tag.model'; import { TagService } from '../tag.service'; @Injectable() diff --git a/packages/server/src/tag/tag.model.ts b/packages/server/src/tag/tag.model.ts index 1fb31181..d2876634 100644 --- a/packages/server/src/tag/tag.model.ts +++ b/packages/server/src/tag/tag.model.ts @@ -24,11 +24,14 @@ export class Tag { complete: boolean; @Prop({ required: false }) - @Field({ nullable: true, description: 'The user assigned to the tag '}) + @Field({ nullable: true, description: 'The user assigned to the tag ' }) user?: string; @Prop({ requried: false, type: mongoose.Schema.Types.Mixed }) - @Field(() => JSON, { nullable: true, description: 'The data stored in the tag, not populated until a colaborator has tagged' }) + @Field(() => JSON, { + nullable: true, + description: 'The data stored in the tag, not populated until a colaborator has tagged' + }) data?: any; @Prop() diff --git a/packages/server/src/tag/tag.module.ts b/packages/server/src/tag/tag.module.ts index 4fcd12ae..48b937a1 100644 --- a/packages/server/src/tag/tag.module.ts +++ b/packages/server/src/tag/tag.module.ts @@ -9,12 +9,7 @@ import { TagPipe } from './pipes/tag.pipe'; import { SharedModule } from '../shared/shared.module'; @Module({ - imports: [ - MongooseModule.forFeature([{ name: Tag.name, schema: TagSchema }]), - StudyModule, - EntryModule, - SharedModule - ], + imports: [MongooseModule.forFeature([{ name: Tag.name, schema: TagSchema }]), StudyModule, EntryModule, SharedModule], providers: [TagService, TagResolver, TagPipe] }) export class TagModule {} diff --git a/packages/server/src/tag/tag.resolver.ts b/packages/server/src/tag/tag.resolver.ts index d310a9c3..56d8a06c 100644 --- a/packages/server/src/tag/tag.resolver.ts +++ b/packages/server/src/tag/tag.resolver.ts @@ -14,11 +14,17 @@ import { JwtAuthGuard } from '../auth/jwt.guard'; @UseGuards(JwtAuthGuard) @Resolver(() => Tag) export class TagResolver { - constructor(private readonly tagService: TagService, private readonly entryPipe: EntryPipe, private readonly studyPipe: StudyPipe) {} + constructor( + private readonly tagService: TagService, + private readonly entryPipe: EntryPipe, + private readonly studyPipe: StudyPipe + ) {} @Mutation(() => [Tag]) - async createTags(@Args('study', { type: () => ID }, StudyPipe) study: Study, - @Args('entries', { type: () => [ID] }, EntriesPipe) entries: Entry[]) { + async createTags( + @Args('study', { type: () => ID }, StudyPipe) study: Study, + @Args('entries', { type: () => [ID] }, EntriesPipe) entries: Entry[] + ) { return this.tagService.createTags(study, entries); } @@ -29,7 +35,10 @@ export class TagResolver { } @Mutation(() => Boolean) - async completeTag(@Args('tag', { type: () => ID }, TagPipe) tag: Tag, @Args('data', { type: () => JSON }) data: any): Promise { + async completeTag( + @Args('tag', { type: () => ID }, TagPipe) tag: Tag, + @Args('data', { type: () => JSON }) data: any + ): Promise { // TODO: Add user context and verify the correct user has completed the tag await this.tagService.complete(tag, data); return true; diff --git a/packages/server/src/tag/tag.service.ts b/packages/server/src/tag/tag.service.ts index 3e8384e0..22af37de 100644 --- a/packages/server/src/tag/tag.service.ts +++ b/packages/server/src/tag/tag.service.ts @@ -7,11 +7,13 @@ import { Entry } from '../entry/models/entry.model'; import { StudyService } from '../study/study.service'; import { MongooseMiddlewareService } from '../shared/service/mongoose-callback.service'; - @Injectable() export class TagService { - constructor(@InjectModel(Tag.name) private readonly tagModel: Model, private readonly studyService: StudyService, - middlewareService: MongooseMiddlewareService) { + constructor( + @InjectModel(Tag.name) private readonly tagModel: Model, + private readonly studyService: StudyService, + middlewareService: MongooseMiddlewareService + ) { // Subscribe to study delete events middlewareService.register(Study.name, 'deleteOne', async (study: Study) => { await this.removeByStudy(study); @@ -32,7 +34,7 @@ export class TagService { async createTags(study: Study, entries: Entry[]): Promise { const tags: Tag[] = []; for (const entry of entries) { - for(let order = 0; order < study.tagsPerEntry; order++) { + for (let order = 0; order < study.tagsPerEntry; order++) { const newTag = await this.tagModel.create({ entry: entry._id, study: study._id, @@ -65,7 +67,7 @@ export class TagService { // Only search on tags that are enabled for the current study { $match: { enabled: true, study: study._id.toString() } }, // Grab tags that are unassigned (user field doesn't exist) or have been completed by the user - { $match: { $or: [ { user: { $exists: false } }, { user: { $eq: user } } ] } }, + { $match: { $or: [{ user: { $exists: false } }, { user: { $eq: user } }] } }, // Group by the entrys and expand tags { $group: { _id: { entry: '$entry' }, tag: { $push: '$$ROOT' } } }, // Now filter where user does not show up in the list of tags @@ -84,10 +86,7 @@ export class TagService { } // Otherwise mark the tag as assigned - await this.tagModel.findOneAndUpdate( - { _id: searchResult[0].tag._id }, - { $set: { user } } - ); + await this.tagModel.findOneAndUpdate({ _id: searchResult[0].tag._id }, { $set: { user } }); }); // At this point, if a tag was found, it will be assigned to the user