diff --git a/.eslintrc.json b/.eslintrc.json index 730f609f..86e75217 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -2,7 +2,8 @@ "extends": [ "eslint:recommended", "next/core-web-vitals", - "plugin:@typescript-eslint/recommended" + "plugin:@typescript-eslint/recommended", + "plugin:prettier/recommended" ], "settings": { "next": { @@ -10,19 +11,19 @@ } }, "parser": "@typescript-eslint/parser", - "plugins": ["jest", "@typescript-eslint"], + "plugins": ["jest", "@typescript-eslint", "prettier"], "env": { "jest": true }, "ignorePatterns": ["**/docs/*"], "rules": { - "no-unused-vars": "off", "@typescript-eslint/no-unused-vars": "error", "@typescript-eslint/no-namespace": "off", "@typescript-eslint/no-empty-interface": "off", "@typescript-eslint/no-explicit-any": [ "error", { "fixToUnknown": true } - ] + ], + "prettier/prettier": ["error", {"tabWidth": 4}] } } diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..891c6177 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,27 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Additional context** +Add any other context about the problem here. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index a1a74868..14a72eb0 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -17,8 +17,7 @@ updates: # Set the target branch to `development` instead of default `main` target-branch: "development" assignees: - - "BramDevlaminck" - - "Jonathan-Vanbrabant" + - "SELab-2/osoc-team2" commit-message: prefix: "npm-root" include: "scope" @@ -39,8 +38,7 @@ updates: # Set the target branch to `development` instead of default `main` target-branch: "development" assignees: - - "BramDevlaminck" - - "Jonathan-Vanbrabant" + - "SELab-2/osoc-team2" commit-message: prefix: "npm-frontend" include: "scope" @@ -61,8 +59,7 @@ updates: # Set the target branch to `development` instead of default `main` target-branch: "development" assignees: - - "BramDevlaminck" - - "Jonathan-Vanbrabant" + - "SELab-2/osoc-team2" commit-message: prefix: "npm-backend" include: "scope" @@ -83,8 +80,7 @@ updates: # Set the target branch to `development` instead of default `main` target-branch: "development" assignees: - - "BramDevlaminck" - - "Jonathan-Vanbrabant" + - "SELab-2/osoc-team2" commit-message: prefix: "docker-backend" include: "scope" @@ -105,8 +101,7 @@ updates: # Set the target branch to `development` instead of default `main` target-branch: "development" assignees: - - "BramDevlaminck" - - "Jonathan-Vanbrabant" + - "SELab-2/osoc-team2" commit-message: prefix: "docker-database" include: "scope" @@ -127,8 +122,7 @@ updates: # Set the target branch to `development` instead of default `main` target-branch: "development" assignees: - - "BramDevlaminck" - - "Jonathan-Vanbrabant" + - "SELab-2/osoc-team2" commit-message: prefix: "docker-frontend" include: "scope" @@ -150,12 +144,9 @@ updates: # Set the target branch to `development` instead of default `main` target-branch: "development" assignees: - - "BramDevlaminck" - - "Jonathan-Vanbrabant" - - "HuanYu-Tan" + - "SELab-2/osoc-team2" commit-message: prefix: "github-actions" include: "scope" labels: - "CI/CD" - diff --git a/.github/workflows/backendCI.yml b/.github/workflows/backendCI.yml index 2c72785d..5b6f2005 100644 --- a/.github/workflows/backendCI.yml +++ b/.github/workflows/backendCI.yml @@ -3,9 +3,8 @@ name: BackendCI on: - # Run on every open pull request + # Run on every open pull request, default types are [opened, edited, reopened] pull_request: - types: [opened, edited, reopened, synchronize] # Should run all tests and upload the codecoverage on main push: @@ -13,11 +12,8 @@ on: jobs: ci: - runs-on: self-hosted + runs-on: ubuntu-latest - strategy: - matrix: - node-version: [16.x] services: postgres: # this is for the integration testing of the orm functions image: postgres:14.2 @@ -40,17 +36,17 @@ jobs: touch backend/prisma/.env echo DATABASE_URL="postgresql://osoc2:password@db:5432/osoc2?connect_timeout=30&pool_timeout=30" >> backend/prisma/.env cat backend/prisma/.env - - name: Use Node.js ${{ matrix.node-version }} + - name: Use Node.js 17.x uses: actions/setup-node@v3 with: - node-version: ${{ matrix.node-version }} + node-version: 17.x - run: npm install working-directory: backend # push the db scheme to the db container - name: Push schema to docker file - run: npx dotenv -e prisma/.env.test -- npx prisma migrate dev --name postgres-init + run: npx dotenv -e prisma/.env.test -- npx prisma db push working-directory: backend - name: Coverage Report @@ -58,3 +54,4 @@ jobs: with: working-directory: backend skip-step: install + annotations: failed-tests diff --git a/.github/workflows/eslint.yml b/.github/workflows/eslint.yml index b865ccd3..f40ef6b6 100644 --- a/.github/workflows/eslint.yml +++ b/.github/workflows/eslint.yml @@ -4,9 +4,8 @@ name: ESLint on: - # Run on every open pull request + # Run on every open pull request, default types are [opened, edited, reopened] pull_request: - types: [opened, edited, reopened, synchronize] # Should run on every push to main push: @@ -14,17 +13,12 @@ on: jobs: ci: - runs-on: self-hosted - - strategy: - matrix: - node-version: [16.x] - + runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - name: Use Node.js ${{ matrix.node-version }} + - name: Use Node.js 17.x uses: actions/setup-node@v3 with: - node-version: ${{ matrix.node-version }} + node-version: 17.x - run: npm install - - run: eslint . --ext .js,.jsx,.ts,.tsx + - run: npm run eslint diff --git a/.github/workflows/frontendCI.yml b/.github/workflows/frontendCI.yml index 559a5340..6d800126 100644 --- a/.github/workflows/frontendCI.yml +++ b/.github/workflows/frontendCI.yml @@ -4,9 +4,8 @@ name: FrontendCI on: - # Run on every open pull request + # Run on every open pull request, default types are [opened, edited, reopened] pull_request: - types: [opened, edited, reopened, synchronize] # Should run all tests and upload the codecoverage on main push: @@ -14,19 +13,13 @@ on: jobs: ci: - - runs-on: self-hosted - - strategy: - matrix: - node-version: [16.x] - + runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - name: Use Node.js ${{ matrix.node-version }} + - name: Use Node.js 17.x uses: actions/setup-node@v3 with: - node-version: ${{ matrix.node-version }} + node-version: 17.x - run: npm install working-directory: frontend diff --git a/.gitignore b/.gitignore index fd2f9967..84987497 100644 --- a/.gitignore +++ b/.gitignore @@ -65,3 +65,12 @@ backend/.idea .env frontend/.env.development frontend/.env.production +backend/prisma/.env +backend/.env.development +backend/.env.production +# just in case a file get's copy pasted without the . in the beginning (discord removes this) +frontend/env.development +frontend/env.production +backend/prisma/env +backend/env.development +backend/env.production \ No newline at end of file diff --git a/.lintstagedrc.js b/.lintstagedrc.js deleted file mode 100644 index 35e23046..00000000 --- a/.lintstagedrc.js +++ /dev/null @@ -1,10 +0,0 @@ -const path = require('path') - -const buildEslintCommand = (filenames) => - `next lint --fix --file ${filenames - .map((f) => path.relative(process.cwd(), f)) - .join(' --file ')}` - -module.exports = { - '*.{js,jsx,ts,tsx}': [buildEslintCommand], -} diff --git a/backend/.gitignore b/backend/.gitignore deleted file mode 100644 index 11ddd8db..00000000 --- a/backend/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -node_modules -# Keep environment variables out of version control -.env diff --git a/backend/Dockerfile b/backend/Dockerfile index 1c3feed9..7daa49f7 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,4 +1,4 @@ -FROM node:17.8.0 +FROM node:18.0.0 # Create app directory WORKDIR /app @@ -22,4 +22,4 @@ RUN npx prisma generate EXPOSE 4096 -CMD ["nodemon", "--legacy-watch", "index.ts"] \ No newline at end of file +CMD ["npm", "run", "prod"] \ No newline at end of file diff --git a/backend/config.json b/backend/config.json index ade33c82..d1a6c12a 100644 --- a/backend/config.json +++ b/backend/config.json @@ -20,19 +20,62 @@ }, "nonExistent": { "http": 404, - "reason": "The endpoint requested ($url) does not exist." + "reason": "The endpoint requested (~url) does not exist." }, "invalidVerb": { "http": 405, - "reason": "This HTTP verb ($verb) is not supported for this endpoint ($url)" + "reason": "This HTTP verb (~verb) is not supported for this endpoint (~url)" }, "nonJSONRequest": { "http": 406, - "reason": "All endpoints only support JSON ($mime requested)." + "reason": "All endpoints only support JSON (~mime requested)." }, "serverError": { "http": 500, "reason": "Something went wrong while trying to execute your request." + }, + "noDataError": { + "http": 412, + "reason": "The data you requested does not exist (yet)." + }, + "lockedRequest": { + "http": 423, + "reason": "Your account was deactivated. Please contact the webmaster." + }, + "pendingAccount": { + "http": 424, + "reason": "Your account hasn't been activated yet. Please try again later." + }, + "github": { + "argumentMissing": { + "http": 409, + "reason": "Something went wrong while authenticating with GitHub." + }, + "illegalState": { + "http": 409, + "reason": "GitHub authentication endpoints are meant for GitHub authentication only. Thank you very much." + }, + "other": { + "reason": "GitHub data requests failed. Please try again later." + } + }, + "reset": { + "sendEmail": { + "http": 503, + "reason": "Failed to send the email. If you're certain that your email address is correct, then please contact the admin." + }, + "invalidEmail": { + "http": 400, + "reason": "No such email is present. Please check your email." + }, + "generateFailed": { + "http": 500, + "reason": "Failed to generate a reset key. Please try again later." + }, + "resetFailed": { + "http": 400, + "reason": "Failed to update your password. Perhaps you used an invalid key?" + } } }, @@ -40,6 +83,13 @@ "homes": [ "", "/api-osoc" ], - "preferred": "/api-osoc" + "preferred": "/api-osoc", + "authScheme": "auth/osoc2", + "defaultUserId": 3 + }, + + "email": { + "from": "'OSOC2 Account Recovery' ", + "header": "OSOC2 Recovery Code" } } diff --git a/backend/docs/assets/main.js b/backend/docs/assets/main.js index 54869f42..b13205a3 100644 --- a/backend/docs/assets/main.js +++ b/backend/docs/assets/main.js @@ -1,5 +1,5 @@ -(()=>{var Ce=Object.create;var J=Object.defineProperty;var Pe=Object.getOwnPropertyDescriptor;var Oe=Object.getOwnPropertyNames;var Re=Object.getPrototypeOf,_e=Object.prototype.hasOwnProperty;var Me=t=>J(t,"__esModule",{value:!0});var Fe=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var De=(t,e,r,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of Oe(e))!_e.call(t,i)&&(r||i!=="default")&&J(t,i,{get:()=>e[i],enumerable:!(n=Pe(e,i))||n.enumerable});return t},Ae=(t,e)=>De(Me(J(t!=null?Ce(Re(t)):{},"default",!e&&t&&t.__esModule?{get:()=>t.default,enumerable:!0}:{value:t,enumerable:!0})),t);var de=Fe((ce,he)=>{(function(){var t=function(e){var r=new t.Builder;return r.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),r.searchPipeline.add(t.stemmer),e.call(r,r),r.build()};t.version="2.3.9";t.utils={},t.utils.warn=function(e){return function(r){e.console&&console.warn&&console.warn(r)}}(this),t.utils.asString=function(e){return e==null?"":e.toString()},t.utils.clone=function(e){if(e==null)return e;for(var r=Object.create(null),n=Object.keys(e),i=0;i0){var h=t.utils.clone(r)||{};h.position=[a,l],h.index=s.length,s.push(new t.Token(n.slice(a,o),h))}a=o+1}}return s},t.tokenizer.separator=/[\s\-]+/;t.Pipeline=function(){this._stack=[]},t.Pipeline.registeredFunctions=Object.create(null),t.Pipeline.registerFunction=function(e,r){r in this.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+r),e.label=r,t.Pipeline.registeredFunctions[e.label]=e},t.Pipeline.warnIfFunctionNotRegistered=function(e){var r=e.label&&e.label in this.registeredFunctions;r||t.utils.warn(`Function is not registered with pipeline. This may cause problems when serialising the index. -`,e)},t.Pipeline.load=function(e){var r=new t.Pipeline;return e.forEach(function(n){var i=t.Pipeline.registeredFunctions[n];if(i)r.add(i);else throw new Error("Cannot load unregistered function: "+n)}),r},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(r){t.Pipeline.warnIfFunctionNotRegistered(r),this._stack.push(r)},this)},t.Pipeline.prototype.after=function(e,r){t.Pipeline.warnIfFunctionNotRegistered(r);var n=this._stack.indexOf(e);if(n==-1)throw new Error("Cannot find existingFn");n=n+1,this._stack.splice(n,0,r)},t.Pipeline.prototype.before=function(e,r){t.Pipeline.warnIfFunctionNotRegistered(r);var n=this._stack.indexOf(e);if(n==-1)throw new Error("Cannot find existingFn");this._stack.splice(n,0,r)},t.Pipeline.prototype.remove=function(e){var r=this._stack.indexOf(e);r!=-1&&this._stack.splice(r,1)},t.Pipeline.prototype.run=function(e){for(var r=this._stack.length,n=0;n1&&(oe&&(n=s),o!=e);)i=n-r,s=r+Math.floor(i/2),o=this.elements[s*2];if(o==e||o>e)return s*2;if(ou?h+=2:a==u&&(r+=n[l+1]*i[h+1],l+=2,h+=2);return r},t.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},t.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),r=1,n=0;r0){var o=s.str.charAt(0),a;o in s.node.edges?a=s.node.edges[o]:(a=new t.TokenSet,s.node.edges[o]=a),s.str.length==1&&(a.final=!0),i.push({node:a,editsRemaining:s.editsRemaining,str:s.str.slice(1)})}if(s.editsRemaining!=0){if("*"in s.node.edges)var u=s.node.edges["*"];else{var u=new t.TokenSet;s.node.edges["*"]=u}if(s.str.length==0&&(u.final=!0),i.push({node:u,editsRemaining:s.editsRemaining-1,str:s.str}),s.str.length>1&&i.push({node:s.node,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)}),s.str.length==1&&(s.node.final=!0),s.str.length>=1){if("*"in s.node.edges)var l=s.node.edges["*"];else{var l=new t.TokenSet;s.node.edges["*"]=l}s.str.length==1&&(l.final=!0),i.push({node:l,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)})}if(s.str.length>1){var h=s.str.charAt(0),p=s.str.charAt(1),v;p in s.node.edges?v=s.node.edges[p]:(v=new t.TokenSet,s.node.edges[p]=v),s.str.length==1&&(v.final=!0),i.push({node:v,editsRemaining:s.editsRemaining-1,str:h+s.str.slice(2)})}}}return n},t.TokenSet.fromString=function(e){for(var r=new t.TokenSet,n=r,i=0,s=e.length;i=e;r--){var n=this.uncheckedNodes[r],i=n.child.toString();i in this.minimizedNodes?n.parent.edges[n.char]=this.minimizedNodes[i]:(n.child._str=i,this.minimizedNodes[i]=n.child),this.uncheckedNodes.pop()}};t.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},t.Index.prototype.search=function(e){return this.query(function(r){var n=new t.QueryParser(e,r);n.parse()})},t.Index.prototype.query=function(e){for(var r=new t.Query(this.fields),n=Object.create(null),i=Object.create(null),s=Object.create(null),o=Object.create(null),a=Object.create(null),u=0;u1?this._b=1:this._b=e},t.Builder.prototype.k1=function(e){this._k1=e},t.Builder.prototype.add=function(e,r){var n=e[this._ref],i=Object.keys(this._fields);this._documents[n]=r||{},this.documentCount+=1;for(var s=0;s=this.length)return t.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},t.QueryLexer.prototype.width=function(){return this.pos-this.start},t.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},t.QueryLexer.prototype.backup=function(){this.pos-=1},t.QueryLexer.prototype.acceptDigitRun=function(){var e,r;do e=this.next(),r=e.charCodeAt(0);while(r>47&&r<58);e!=t.QueryLexer.EOS&&this.backup()},t.QueryLexer.prototype.more=function(){return this.pos1&&(e.backup(),e.emit(t.QueryLexer.TERM)),e.ignore(),e.more())return t.QueryLexer.lexText},t.QueryLexer.lexEditDistance=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.EDIT_DISTANCE),t.QueryLexer.lexText},t.QueryLexer.lexBoost=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.BOOST),t.QueryLexer.lexText},t.QueryLexer.lexEOS=function(e){e.width()>0&&e.emit(t.QueryLexer.TERM)},t.QueryLexer.termSeparator=t.tokenizer.separator,t.QueryLexer.lexText=function(e){for(;;){var r=e.next();if(r==t.QueryLexer.EOS)return t.QueryLexer.lexEOS;if(r.charCodeAt(0)==92){e.escapeCharacter();continue}if(r==":")return t.QueryLexer.lexField;if(r=="~")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexEditDistance;if(r=="^")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexBoost;if(r=="+"&&e.width()===1||r=="-"&&e.width()===1)return e.emit(t.QueryLexer.PRESENCE),t.QueryLexer.lexText;if(r.match(t.QueryLexer.termSeparator))return t.QueryLexer.lexTerm}},t.QueryParser=function(e,r){this.lexer=new t.QueryLexer(e),this.query=r,this.currentClause={},this.lexemeIdx=0},t.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var e=t.QueryParser.parseClause;e;)e=e(this);return this.query},t.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},t.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},t.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},t.QueryParser.parseClause=function(e){var r=e.peekLexeme();if(r!=null)switch(r.type){case t.QueryLexer.PRESENCE:return t.QueryParser.parsePresence;case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var n="expected either a field or a term, found "+r.type;throw r.str.length>=1&&(n+=" with value '"+r.str+"'"),new t.QueryParseError(n,r.start,r.end)}},t.QueryParser.parsePresence=function(e){var r=e.consumeLexeme();if(r!=null){switch(r.str){case"-":e.currentClause.presence=t.Query.presence.PROHIBITED;break;case"+":e.currentClause.presence=t.Query.presence.REQUIRED;break;default:var n="unrecognised presence operator'"+r.str+"'";throw new t.QueryParseError(n,r.start,r.end)}var i=e.peekLexeme();if(i==null){var n="expecting term or field, found nothing";throw new t.QueryParseError(n,r.start,r.end)}switch(i.type){case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var n="expecting term or field, found '"+i.type+"'";throw new t.QueryParseError(n,i.start,i.end)}}},t.QueryParser.parseField=function(e){var r=e.consumeLexeme();if(r!=null){if(e.query.allFields.indexOf(r.str)==-1){var n=e.query.allFields.map(function(o){return"'"+o+"'"}).join(", "),i="unrecognised field '"+r.str+"', possible fields: "+n;throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.fields=[r.str];var s=e.peekLexeme();if(s==null){var i="expecting term, found nothing";throw new t.QueryParseError(i,r.start,r.end)}switch(s.type){case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var i="expecting term, found '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseTerm=function(e){var r=e.consumeLexeme();if(r!=null){e.currentClause.term=r.str.toLowerCase(),r.str.indexOf("*")!=-1&&(e.currentClause.usePipeline=!1);var n=e.peekLexeme();if(n==null){e.nextClause();return}switch(n.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+n.type+"'";throw new t.QueryParseError(i,n.start,n.end)}}},t.QueryParser.parseEditDistance=function(e){var r=e.consumeLexeme();if(r!=null){var n=parseInt(r.str,10);if(isNaN(n)){var i="edit distance must be numeric";throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.editDistance=n;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseBoost=function(e){var r=e.consumeLexeme();if(r!=null){var n=parseInt(r.str,10);if(isNaN(n)){var i="boost must be numeric";throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.boost=n;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},function(e,r){typeof define=="function"&&define.amd?define(r):typeof ce=="object"?he.exports=r():e.lunr=r()}(this,function(){return t})})()});var le=[];function N(t,e){le.push({selector:e,constructor:t})}var X=class{constructor(){this.createComponents(document.body)}createComponents(e){le.forEach(r=>{e.querySelectorAll(r.selector).forEach(n=>{n.dataset.hasInstance||(new r.constructor({el:n}),n.dataset.hasInstance=String(!0))})})}};var Q=class{constructor(e){this.el=e.el}};var Z=class{constructor(){this.listeners={}}addEventListener(e,r){e in this.listeners||(this.listeners[e]=[]),this.listeners[e].push(r)}removeEventListener(e,r){if(!(e in this.listeners))return;let n=this.listeners[e];for(let i=0,s=n.length;i{let r=Date.now();return(...n)=>{r+e-Date.now()<0&&(t(...n),r=Date.now())}};var ee=class extends Z{constructor(){super();this.scrollTop=0;this.lastY=0;this.width=0;this.height=0;this.showToolbar=!0;this.toolbar=document.querySelector(".tsd-page-toolbar"),this.secondaryNav=document.querySelector(".tsd-navigation.secondary"),window.addEventListener("scroll",K(()=>this.onScroll(),10)),window.addEventListener("resize",K(()=>this.onResize(),10)),this.onResize(),this.onScroll()}triggerResize(){let e=new CustomEvent("resize",{detail:{width:this.width,height:this.height}});this.dispatchEvent(e)}onResize(){this.width=window.innerWidth||0,this.height=window.innerHeight||0;let e=new CustomEvent("resize",{detail:{width:this.width,height:this.height}});this.dispatchEvent(e)}onScroll(){this.scrollTop=window.scrollY||0;let e=new CustomEvent("scroll",{detail:{scrollTop:this.scrollTop}});this.dispatchEvent(e),this.hideShowToolbar()}hideShowToolbar(){var r;let e=this.showToolbar;this.showToolbar=this.lastY>=this.scrollTop||this.scrollTop<=0,e!==this.showToolbar&&(this.toolbar.classList.toggle("tsd-page-toolbar--hide"),(r=this.secondaryNav)==null||r.classList.toggle("tsd-navigation--toolbar-hide")),this.lastY=this.scrollTop}},I=ee;I.instance=new ee;var te=class extends Q{constructor(e){super(e);this.anchors=[];this.index=-1;I.instance.addEventListener("resize",()=>this.onResize()),I.instance.addEventListener("scroll",r=>this.onScroll(r)),this.createAnchors()}createAnchors(){let e=window.location.href;e.indexOf("#")!=-1&&(e=e.substr(0,e.indexOf("#"))),this.el.querySelectorAll("a").forEach(r=>{let n=r.href;if(n.indexOf("#")==-1||n.substr(0,e.length)!=e)return;let i=n.substr(n.indexOf("#")+1),s=document.querySelector("a.tsd-anchor[name="+i+"]"),o=r.parentNode;!s||!o||this.anchors.push({link:o,anchor:s,position:0})}),this.onResize()}onResize(){let e;for(let n=0,i=this.anchors.length;nn.position-i.position);let r=new CustomEvent("scroll",{detail:{scrollTop:I.instance.scrollTop}});this.onScroll(r)}onScroll(e){let r=e.detail.scrollTop+5,n=this.anchors,i=n.length-1,s=this.index;for(;s>-1&&n[s].position>r;)s-=1;for(;s-1&&this.anchors[this.index].link.classList.remove("focus"),this.index=s,this.index>-1&&this.anchors[this.index].link.classList.add("focus"))}};var ue=(t,e=100)=>{let r;return(...n)=>{clearTimeout(r),r=setTimeout(()=>t(n),e)}};var me=Ae(de());function ve(){let t=document.getElementById("tsd-search");if(!t)return;let e=document.getElementById("search-script");t.classList.add("loading"),e&&(e.addEventListener("error",()=>{t.classList.remove("loading"),t.classList.add("failure")}),e.addEventListener("load",()=>{t.classList.remove("loading"),t.classList.add("ready")}),window.searchData&&t.classList.remove("loading"));let r=document.querySelector("#tsd-search input"),n=document.querySelector("#tsd-search .results");if(!r||!n)throw new Error("The input field or the result list wrapper was not found");let i=!1;n.addEventListener("mousedown",()=>i=!0),n.addEventListener("mouseup",()=>{i=!1,t.classList.remove("has-focus")}),r.addEventListener("focus",()=>t.classList.add("has-focus")),r.addEventListener("blur",()=>{i||(i=!1,t.classList.remove("has-focus"))});let s={base:t.dataset.base+"/"};Ve(t,n,r,s)}function Ve(t,e,r,n){r.addEventListener("input",ue(()=>{ze(t,e,r,n)},200));let i=!1;r.addEventListener("keydown",s=>{i=!0,s.key=="Enter"?Ne(e,r):s.key=="Escape"?r.blur():s.key=="ArrowUp"?fe(e,-1):s.key==="ArrowDown"?fe(e,1):i=!1}),r.addEventListener("keypress",s=>{i&&s.preventDefault()}),document.body.addEventListener("keydown",s=>{s.altKey||s.ctrlKey||s.metaKey||!r.matches(":focus")&&s.key==="/"&&(r.focus(),s.preventDefault())})}function He(t,e){t.index||window.searchData&&(e.classList.remove("loading"),e.classList.add("ready"),t.data=window.searchData,t.index=me.Index.load(window.searchData.index))}function ze(t,e,r,n){if(He(n,t),!n.index||!n.data)return;e.textContent="";let i=r.value.trim(),s=n.index.search(`*${i}*`);for(let o=0,a=Math.min(10,s.length);o${pe(u.parent,i)}.${l}`);let h=document.createElement("li");h.classList.value=u.classes;let p=document.createElement("a");p.href=n.base+u.url,p.classList.add("tsd-kind-icon"),p.innerHTML=l,h.append(p),e.appendChild(h)}}function fe(t,e){let r=t.querySelector(".current");if(!r)r=t.querySelector(e==1?"li:first-child":"li:last-child"),r&&r.classList.add("current");else{let n=r;if(e===1)do n=n.nextElementSibling;while(n instanceof HTMLElement&&n.offsetParent==null);else do n=n.previousElementSibling;while(n instanceof HTMLElement&&n.offsetParent==null);n&&(r.classList.remove("current"),n.classList.add("current"))}}function Ne(t,e){let r=t.querySelector(".current");if(r||(r=t.querySelector("li:first-child")),r){let n=r.querySelector("a");n&&(window.location.href=n.href),e.blur()}}function pe(t,e){if(e==="")return t;let r=t.toLocaleLowerCase(),n=e.toLocaleLowerCase(),i=[],s=0,o=r.indexOf(n);for(;o!=-1;)i.push(re(t.substring(s,o)),`${re(t.substring(o,o+n.length))}`),s=o+n.length,o=r.indexOf(n,s);return i.push(re(t.substring(s))),i.join("")}var je={"&":"&","<":"<",">":">","'":"'",'"':"""};function re(t){return t.replace(/[&<>"'"]/g,e=>je[e])}var ge=class{constructor(e,r){this.signature=e,this.description=r}addClass(e){return this.signature.classList.add(e),this.description.classList.add(e),this}removeClass(e){return this.signature.classList.remove(e),this.description.classList.remove(e),this}},ne=class extends Q{constructor(e){super(e);this.groups=[];this.index=-1;this.createGroups(),this.container&&(this.el.classList.add("active"),Array.from(this.el.children).forEach(r=>{r.addEventListener("touchstart",n=>this.onClick(n)),r.addEventListener("click",n=>this.onClick(n))}),this.container.classList.add("active"),this.setIndex(0))}setIndex(e){if(e<0&&(e=0),e>this.groups.length-1&&(e=this.groups.length-1),this.index==e)return;let r=this.groups[e];if(this.index>-1){let n=this.groups[this.index];n.removeClass("current").addClass("fade-out"),r.addClass("current"),r.addClass("fade-in"),I.instance.triggerResize(),setTimeout(()=>{n.removeClass("fade-out"),r.removeClass("fade-in")},300)}else r.addClass("current"),I.instance.triggerResize();this.index=e}createGroups(){let e=this.el.children;if(e.length<2)return;this.container=this.el.nextElementSibling;let r=this.container.children;this.groups=[];for(let n=0;n{r.signature===e.currentTarget&&this.setIndex(n)})}};var C="mousedown",xe="mousemove",_="mouseup",G={x:0,y:0},ye=!1,ie=!1,Be=!1,A=!1,Le=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);document.documentElement.classList.add(Le?"is-mobile":"not-mobile");Le&&"ontouchstart"in document.documentElement&&(Be=!0,C="touchstart",xe="touchmove",_="touchend");document.addEventListener(C,t=>{ie=!0,A=!1;let e=C=="touchstart"?t.targetTouches[0]:t;G.y=e.pageY||0,G.x=e.pageX||0});document.addEventListener(xe,t=>{if(!!ie&&!A){let e=C=="touchstart"?t.targetTouches[0]:t,r=G.x-(e.pageX||0),n=G.y-(e.pageY||0);A=Math.sqrt(r*r+n*n)>10}});document.addEventListener(_,()=>{ie=!1});document.addEventListener("click",t=>{ye&&(t.preventDefault(),t.stopImmediatePropagation(),ye=!1)});var se=class extends Q{constructor(e){super(e);this.className=this.el.dataset.toggle||"",this.el.addEventListener(_,r=>this.onPointerUp(r)),this.el.addEventListener("click",r=>r.preventDefault()),document.addEventListener(C,r=>this.onDocumentPointerDown(r)),document.addEventListener(_,r=>this.onDocumentPointerUp(r))}setActive(e){if(this.active==e)return;this.active=e,document.documentElement.classList.toggle("has-"+this.className,e),this.el.classList.toggle("active",e);let r=(this.active?"to-has-":"from-has-")+this.className;document.documentElement.classList.add(r),setTimeout(()=>document.documentElement.classList.remove(r),500)}onPointerUp(e){A||(this.setActive(!0),e.preventDefault())}onDocumentPointerDown(e){if(this.active){if(e.target.closest(".col-menu, .tsd-filter-group"))return;this.setActive(!1)}}onDocumentPointerUp(e){if(!A&&this.active&&e.target.closest(".col-menu")){let r=e.target.closest("a");if(r){let n=window.location.href;n.indexOf("#")!=-1&&(n=n.substr(0,n.indexOf("#"))),r.href.substr(0,n.length)==n&&setTimeout(()=>this.setActive(!1),250)}}}};var ae=class{constructor(e,r){this.key=e,this.value=r,this.defaultValue=r,this.initialize(),window.localStorage[this.key]&&this.setValue(this.fromLocalStorage(window.localStorage[this.key]))}initialize(){}setValue(e){if(this.value==e)return;let r=this.value;this.value=e,window.localStorage[this.key]=this.toLocalStorage(e),this.handleValueChange(r,e)}},oe=class extends ae{initialize(){let e=document.querySelector("#tsd-filter-"+this.key);!e||(this.checkbox=e,this.checkbox.addEventListener("change",()=>{this.setValue(this.checkbox.checked)}))}handleValueChange(e,r){!this.checkbox||(this.checkbox.checked=this.value,document.documentElement.classList.toggle("toggle-"+this.key,this.value!=this.defaultValue))}fromLocalStorage(e){return e=="true"}toLocalStorage(e){return e?"true":"false"}},Ee=class extends ae{initialize(){document.documentElement.classList.add("toggle-"+this.key+this.value);let e=document.querySelector("#tsd-filter-"+this.key);if(!e)return;this.select=e;let r=()=>{this.select.classList.add("active")},n=()=>{this.select.classList.remove("active")};this.select.addEventListener(C,r),this.select.addEventListener("mouseover",r),this.select.addEventListener("mouseleave",n),this.select.querySelectorAll("li").forEach(i=>{i.addEventListener(_,s=>{e.classList.remove("active"),this.setValue(s.target.dataset.value||"")})}),document.addEventListener(C,i=>{this.select.contains(i.target)||this.select.classList.remove("active")})}handleValueChange(e,r){this.select.querySelectorAll("li.selected").forEach(s=>{s.classList.remove("selected")});let n=this.select.querySelector('li[data-value="'+r+'"]'),i=this.select.querySelector(".tsd-select-label");n&&i&&(n.classList.add("selected"),i.textContent=n.textContent),document.documentElement.classList.remove("toggle-"+e),document.documentElement.classList.add("toggle-"+r)}fromLocalStorage(e){return e}toLocalStorage(e){return e}},Y=class extends Q{constructor(e){super(e);this.optionVisibility=new Ee("visibility","private"),this.optionInherited=new oe("inherited",!0),this.optionExternals=new oe("externals",!0)}static isSupported(){try{return typeof window.localStorage!="undefined"}catch{return!1}}};function we(t){let e=localStorage.getItem("tsd-theme")||"os";t.value=e,be(e),t.addEventListener("change",()=>{localStorage.setItem("tsd-theme",t.value),be(t.value)})}function be(t){switch(t){case"os":document.body.classList.remove("light","dark");break;case"light":document.body.classList.remove("dark"),document.body.classList.add("light");break;case"dark":document.body.classList.remove("light"),document.body.classList.add("dark");break}}ve();N(te,".menu-highlight");N(ne,".tsd-signatures");N(se,"a[data-toggle]");Y.isSupported()?N(Y,"#tsd-filter"):document.documentElement.classList.add("no-filter");var Te=document.getElementById("theme");Te&&we(Te);var qe=new X;Object.defineProperty(window,"app",{value:qe});})(); +(()=>{var Ce=Object.create;var ue=Object.defineProperty;var Pe=Object.getOwnPropertyDescriptor;var Oe=Object.getOwnPropertyNames;var Re=Object.getPrototypeOf,_e=Object.prototype.hasOwnProperty;var Me=(t,e)=>()=>(e||t((e={exports:{}}).exports,e),e.exports);var De=(t,e,r,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of Oe(e))!_e.call(t,i)&&i!==r&&ue(t,i,{get:()=>e[i],enumerable:!(n=Pe(e,i))||n.enumerable});return t};var Fe=(t,e,r)=>(r=t!=null?Ce(Re(t)):{},De(e||!t||!t.__esModule?ue(r,"default",{value:t,enumerable:!0}):r,t));var pe=Me((de,fe)=>{(function(){var t=function(e){var r=new t.Builder;return r.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),r.searchPipeline.add(t.stemmer),e.call(r,r),r.build()};t.version="2.3.9";t.utils={},t.utils.warn=function(e){return function(r){e.console&&console.warn&&console.warn(r)}}(this),t.utils.asString=function(e){return e==null?"":e.toString()},t.utils.clone=function(e){if(e==null)return e;for(var r=Object.create(null),n=Object.keys(e),i=0;i0){var h=t.utils.clone(r)||{};h.position=[a,l],h.index=s.length,s.push(new t.Token(n.slice(a,o),h))}a=o+1}}return s},t.tokenizer.separator=/[\s\-]+/;t.Pipeline=function(){this._stack=[]},t.Pipeline.registeredFunctions=Object.create(null),t.Pipeline.registerFunction=function(e,r){r in this.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+r),e.label=r,t.Pipeline.registeredFunctions[e.label]=e},t.Pipeline.warnIfFunctionNotRegistered=function(e){var r=e.label&&e.label in this.registeredFunctions;r||t.utils.warn(`Function is not registered with pipeline. This may cause problems when serialising the index. +`,e)},t.Pipeline.load=function(e){var r=new t.Pipeline;return e.forEach(function(n){var i=t.Pipeline.registeredFunctions[n];if(i)r.add(i);else throw new Error("Cannot load unregistered function: "+n)}),r},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(r){t.Pipeline.warnIfFunctionNotRegistered(r),this._stack.push(r)},this)},t.Pipeline.prototype.after=function(e,r){t.Pipeline.warnIfFunctionNotRegistered(r);var n=this._stack.indexOf(e);if(n==-1)throw new Error("Cannot find existingFn");n=n+1,this._stack.splice(n,0,r)},t.Pipeline.prototype.before=function(e,r){t.Pipeline.warnIfFunctionNotRegistered(r);var n=this._stack.indexOf(e);if(n==-1)throw new Error("Cannot find existingFn");this._stack.splice(n,0,r)},t.Pipeline.prototype.remove=function(e){var r=this._stack.indexOf(e);r!=-1&&this._stack.splice(r,1)},t.Pipeline.prototype.run=function(e){for(var r=this._stack.length,n=0;n1&&(oe&&(n=s),o!=e);)i=n-r,s=r+Math.floor(i/2),o=this.elements[s*2];if(o==e||o>e)return s*2;if(ou?h+=2:a==u&&(r+=n[l+1]*i[h+1],l+=2,h+=2);return r},t.Vector.prototype.similarity=function(e){return this.dot(e)/this.magnitude()||0},t.Vector.prototype.toArray=function(){for(var e=new Array(this.elements.length/2),r=1,n=0;r0){var o=s.str.charAt(0),a;o in s.node.edges?a=s.node.edges[o]:(a=new t.TokenSet,s.node.edges[o]=a),s.str.length==1&&(a.final=!0),i.push({node:a,editsRemaining:s.editsRemaining,str:s.str.slice(1)})}if(s.editsRemaining!=0){if("*"in s.node.edges)var u=s.node.edges["*"];else{var u=new t.TokenSet;s.node.edges["*"]=u}if(s.str.length==0&&(u.final=!0),i.push({node:u,editsRemaining:s.editsRemaining-1,str:s.str}),s.str.length>1&&i.push({node:s.node,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)}),s.str.length==1&&(s.node.final=!0),s.str.length>=1){if("*"in s.node.edges)var l=s.node.edges["*"];else{var l=new t.TokenSet;s.node.edges["*"]=l}s.str.length==1&&(l.final=!0),i.push({node:l,editsRemaining:s.editsRemaining-1,str:s.str.slice(1)})}if(s.str.length>1){var h=s.str.charAt(0),p=s.str.charAt(1),v;p in s.node.edges?v=s.node.edges[p]:(v=new t.TokenSet,s.node.edges[p]=v),s.str.length==1&&(v.final=!0),i.push({node:v,editsRemaining:s.editsRemaining-1,str:h+s.str.slice(2)})}}}return n},t.TokenSet.fromString=function(e){for(var r=new t.TokenSet,n=r,i=0,s=e.length;i=e;r--){var n=this.uncheckedNodes[r],i=n.child.toString();i in this.minimizedNodes?n.parent.edges[n.char]=this.minimizedNodes[i]:(n.child._str=i,this.minimizedNodes[i]=n.child),this.uncheckedNodes.pop()}};t.Index=function(e){this.invertedIndex=e.invertedIndex,this.fieldVectors=e.fieldVectors,this.tokenSet=e.tokenSet,this.fields=e.fields,this.pipeline=e.pipeline},t.Index.prototype.search=function(e){return this.query(function(r){var n=new t.QueryParser(e,r);n.parse()})},t.Index.prototype.query=function(e){for(var r=new t.Query(this.fields),n=Object.create(null),i=Object.create(null),s=Object.create(null),o=Object.create(null),a=Object.create(null),u=0;u1?this._b=1:this._b=e},t.Builder.prototype.k1=function(e){this._k1=e},t.Builder.prototype.add=function(e,r){var n=e[this._ref],i=Object.keys(this._fields);this._documents[n]=r||{},this.documentCount+=1;for(var s=0;s=this.length)return t.QueryLexer.EOS;var e=this.str.charAt(this.pos);return this.pos+=1,e},t.QueryLexer.prototype.width=function(){return this.pos-this.start},t.QueryLexer.prototype.ignore=function(){this.start==this.pos&&(this.pos+=1),this.start=this.pos},t.QueryLexer.prototype.backup=function(){this.pos-=1},t.QueryLexer.prototype.acceptDigitRun=function(){var e,r;do e=this.next(),r=e.charCodeAt(0);while(r>47&&r<58);e!=t.QueryLexer.EOS&&this.backup()},t.QueryLexer.prototype.more=function(){return this.pos1&&(e.backup(),e.emit(t.QueryLexer.TERM)),e.ignore(),e.more())return t.QueryLexer.lexText},t.QueryLexer.lexEditDistance=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.EDIT_DISTANCE),t.QueryLexer.lexText},t.QueryLexer.lexBoost=function(e){return e.ignore(),e.acceptDigitRun(),e.emit(t.QueryLexer.BOOST),t.QueryLexer.lexText},t.QueryLexer.lexEOS=function(e){e.width()>0&&e.emit(t.QueryLexer.TERM)},t.QueryLexer.termSeparator=t.tokenizer.separator,t.QueryLexer.lexText=function(e){for(;;){var r=e.next();if(r==t.QueryLexer.EOS)return t.QueryLexer.lexEOS;if(r.charCodeAt(0)==92){e.escapeCharacter();continue}if(r==":")return t.QueryLexer.lexField;if(r=="~")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexEditDistance;if(r=="^")return e.backup(),e.width()>0&&e.emit(t.QueryLexer.TERM),t.QueryLexer.lexBoost;if(r=="+"&&e.width()===1||r=="-"&&e.width()===1)return e.emit(t.QueryLexer.PRESENCE),t.QueryLexer.lexText;if(r.match(t.QueryLexer.termSeparator))return t.QueryLexer.lexTerm}},t.QueryParser=function(e,r){this.lexer=new t.QueryLexer(e),this.query=r,this.currentClause={},this.lexemeIdx=0},t.QueryParser.prototype.parse=function(){this.lexer.run(),this.lexemes=this.lexer.lexemes;for(var e=t.QueryParser.parseClause;e;)e=e(this);return this.query},t.QueryParser.prototype.peekLexeme=function(){return this.lexemes[this.lexemeIdx]},t.QueryParser.prototype.consumeLexeme=function(){var e=this.peekLexeme();return this.lexemeIdx+=1,e},t.QueryParser.prototype.nextClause=function(){var e=this.currentClause;this.query.clause(e),this.currentClause={}},t.QueryParser.parseClause=function(e){var r=e.peekLexeme();if(r!=null)switch(r.type){case t.QueryLexer.PRESENCE:return t.QueryParser.parsePresence;case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var n="expected either a field or a term, found "+r.type;throw r.str.length>=1&&(n+=" with value '"+r.str+"'"),new t.QueryParseError(n,r.start,r.end)}},t.QueryParser.parsePresence=function(e){var r=e.consumeLexeme();if(r!=null){switch(r.str){case"-":e.currentClause.presence=t.Query.presence.PROHIBITED;break;case"+":e.currentClause.presence=t.Query.presence.REQUIRED;break;default:var n="unrecognised presence operator'"+r.str+"'";throw new t.QueryParseError(n,r.start,r.end)}var i=e.peekLexeme();if(i==null){var n="expecting term or field, found nothing";throw new t.QueryParseError(n,r.start,r.end)}switch(i.type){case t.QueryLexer.FIELD:return t.QueryParser.parseField;case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var n="expecting term or field, found '"+i.type+"'";throw new t.QueryParseError(n,i.start,i.end)}}},t.QueryParser.parseField=function(e){var r=e.consumeLexeme();if(r!=null){if(e.query.allFields.indexOf(r.str)==-1){var n=e.query.allFields.map(function(o){return"'"+o+"'"}).join(", "),i="unrecognised field '"+r.str+"', possible fields: "+n;throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.fields=[r.str];var s=e.peekLexeme();if(s==null){var i="expecting term, found nothing";throw new t.QueryParseError(i,r.start,r.end)}switch(s.type){case t.QueryLexer.TERM:return t.QueryParser.parseTerm;default:var i="expecting term, found '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseTerm=function(e){var r=e.consumeLexeme();if(r!=null){e.currentClause.term=r.str.toLowerCase(),r.str.indexOf("*")!=-1&&(e.currentClause.usePipeline=!1);var n=e.peekLexeme();if(n==null){e.nextClause();return}switch(n.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+n.type+"'";throw new t.QueryParseError(i,n.start,n.end)}}},t.QueryParser.parseEditDistance=function(e){var r=e.consumeLexeme();if(r!=null){var n=parseInt(r.str,10);if(isNaN(n)){var i="edit distance must be numeric";throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.editDistance=n;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},t.QueryParser.parseBoost=function(e){var r=e.consumeLexeme();if(r!=null){var n=parseInt(r.str,10);if(isNaN(n)){var i="boost must be numeric";throw new t.QueryParseError(i,r.start,r.end)}e.currentClause.boost=n;var s=e.peekLexeme();if(s==null){e.nextClause();return}switch(s.type){case t.QueryLexer.TERM:return e.nextClause(),t.QueryParser.parseTerm;case t.QueryLexer.FIELD:return e.nextClause(),t.QueryParser.parseField;case t.QueryLexer.EDIT_DISTANCE:return t.QueryParser.parseEditDistance;case t.QueryLexer.BOOST:return t.QueryParser.parseBoost;case t.QueryLexer.PRESENCE:return e.nextClause(),t.QueryParser.parsePresence;default:var i="Unexpected lexeme type '"+s.type+"'";throw new t.QueryParseError(i,s.start,s.end)}}},function(e,r){typeof define=="function"&&define.amd?define(r):typeof de=="object"?fe.exports=r():e.lunr=r()}(this,function(){return t})})()});var ce=[];function N(t,e){ce.push({selector:e,constructor:t})}var Y=class{constructor(){this.createComponents(document.body)}createComponents(e){ce.forEach(r=>{e.querySelectorAll(r.selector).forEach(n=>{n.dataset.hasInstance||(new r.constructor({el:n}),n.dataset.hasInstance=String(!0))})})}};var k=class{constructor(e){this.el=e.el}};var J=class{constructor(){this.listeners={}}addEventListener(e,r){e in this.listeners||(this.listeners[e]=[]),this.listeners[e].push(r)}removeEventListener(e,r){if(!(e in this.listeners))return;let n=this.listeners[e];for(let i=0,s=n.length;i{let r=Date.now();return(...n)=>{r+e-Date.now()<0&&(t(...n),r=Date.now())}};var ie=class extends J{constructor(){super();this.scrollTop=0;this.lastY=0;this.width=0;this.height=0;this.showToolbar=!0;this.toolbar=document.querySelector(".tsd-page-toolbar"),this.secondaryNav=document.querySelector(".tsd-navigation.secondary"),window.addEventListener("scroll",ne(()=>this.onScroll(),10)),window.addEventListener("resize",ne(()=>this.onResize(),10)),this.onResize(),this.onScroll()}triggerResize(){let r=new CustomEvent("resize",{detail:{width:this.width,height:this.height}});this.dispatchEvent(r)}onResize(){this.width=window.innerWidth||0,this.height=window.innerHeight||0;let r=new CustomEvent("resize",{detail:{width:this.width,height:this.height}});this.dispatchEvent(r)}onScroll(){this.scrollTop=window.scrollY||0;let r=new CustomEvent("scroll",{detail:{scrollTop:this.scrollTop}});this.dispatchEvent(r),this.hideShowToolbar()}hideShowToolbar(){var n;let r=this.showToolbar;this.showToolbar=this.lastY>=this.scrollTop||this.scrollTop<=0,r!==this.showToolbar&&(this.toolbar.classList.toggle("tsd-page-toolbar--hide"),(n=this.secondaryNav)==null||n.classList.toggle("tsd-navigation--toolbar-hide")),this.lastY=this.scrollTop}},Q=ie;Q.instance=new ie;var X=class extends k{constructor(r){super(r);this.anchors=[];this.index=-1;Q.instance.addEventListener("resize",()=>this.onResize()),Q.instance.addEventListener("scroll",n=>this.onScroll(n)),this.createAnchors()}createAnchors(){let r=window.location.href;r.indexOf("#")!=-1&&(r=r.substr(0,r.indexOf("#"))),this.el.querySelectorAll("a").forEach(n=>{let i=n.href;if(i.indexOf("#")==-1||i.substr(0,r.length)!=r)return;let s=i.substr(i.indexOf("#")+1),o=document.querySelector("a.tsd-anchor[name="+s+"]"),a=n.parentNode;!o||!a||this.anchors.push({link:a,anchor:o,position:0})}),this.onResize()}onResize(){let r;for(let i=0,s=this.anchors.length;ii.position-s.position);let n=new CustomEvent("scroll",{detail:{scrollTop:Q.instance.scrollTop}});this.onScroll(n)}onScroll(r){let n=r.detail.scrollTop+5,i=this.anchors,s=i.length-1,o=this.index;for(;o>-1&&i[o].position>n;)o-=1;for(;o-1&&this.anchors[this.index].link.classList.remove("focus"),this.index=o,this.index>-1&&this.anchors[this.index].link.classList.add("focus"))}};var he=(t,e=100)=>{let r;return(...n)=>{clearTimeout(r),r=setTimeout(()=>t(n),e)}};var ge=Fe(pe());function ye(){let t=document.getElementById("tsd-search");if(!t)return;let e=document.getElementById("search-script");t.classList.add("loading"),e&&(e.addEventListener("error",()=>{t.classList.remove("loading"),t.classList.add("failure")}),e.addEventListener("load",()=>{t.classList.remove("loading"),t.classList.add("ready")}),window.searchData&&t.classList.remove("loading"));let r=document.querySelector("#tsd-search input"),n=document.querySelector("#tsd-search .results");if(!r||!n)throw new Error("The input field or the result list wrapper was not found");let i=!1;n.addEventListener("mousedown",()=>i=!0),n.addEventListener("mouseup",()=>{i=!1,t.classList.remove("has-focus")}),r.addEventListener("focus",()=>t.classList.add("has-focus")),r.addEventListener("blur",()=>{i||(i=!1,t.classList.remove("has-focus"))});let s={base:t.dataset.base+"/"};Ae(t,n,r,s)}function Ae(t,e,r,n){r.addEventListener("input",he(()=>{He(t,e,r,n)},200));let i=!1;r.addEventListener("keydown",s=>{i=!0,s.key=="Enter"?ze(e,r):s.key=="Escape"?r.blur():s.key=="ArrowUp"?me(e,-1):s.key==="ArrowDown"?me(e,1):i=!1}),r.addEventListener("keypress",s=>{i&&s.preventDefault()}),document.body.addEventListener("keydown",s=>{s.altKey||s.ctrlKey||s.metaKey||!r.matches(":focus")&&s.key==="/"&&(r.focus(),s.preventDefault())})}function Ve(t,e){t.index||window.searchData&&(e.classList.remove("loading"),e.classList.add("ready"),t.data=window.searchData,t.index=ge.Index.load(window.searchData.index))}function He(t,e,r,n){if(Ve(n,t),!n.index||!n.data)return;e.textContent="";let i=r.value.trim(),s=i?n.index.search(`*${i}*`):[];for(let o=0,a=Math.min(10,s.length);o${ve(u.parent,i)}.${l}`);let h=document.createElement("li");h.classList.value=u.classes;let p=document.createElement("a");p.href=n.base+u.url,p.classList.add("tsd-kind-icon"),p.innerHTML=l,h.append(p),e.appendChild(h)}}function me(t,e){let r=t.querySelector(".current");if(!r)r=t.querySelector(e==1?"li:first-child":"li:last-child"),r&&r.classList.add("current");else{let n=r;if(e===1)do n=n.nextElementSibling;while(n instanceof HTMLElement&&n.offsetParent==null);else do n=n.previousElementSibling;while(n instanceof HTMLElement&&n.offsetParent==null);n&&(r.classList.remove("current"),n.classList.add("current"))}}function ze(t,e){let r=t.querySelector(".current");if(r||(r=t.querySelector("li:first-child")),r){let n=r.querySelector("a");n&&(window.location.href=n.href),e.blur()}}function ve(t,e){if(e==="")return t;let r=t.toLocaleLowerCase(),n=e.toLocaleLowerCase(),i=[],s=0,o=r.indexOf(n);for(;o!=-1;)i.push(se(t.substring(s,o)),`${se(t.substring(o,o+n.length))}`),s=o+n.length,o=r.indexOf(n,s);return i.push(se(t.substring(s))),i.join("")}var Ne={"&":"&","<":"<",">":">","'":"'",'"':"""};function se(t){return t.replace(/[&<>"'"]/g,e=>Ne[e])}var oe=class{constructor(e,r){this.signature=e,this.description=r}addClass(e){return this.signature.classList.add(e),this.description.classList.add(e),this}removeClass(e){return this.signature.classList.remove(e),this.description.classList.remove(e),this}},Z=class extends k{constructor(r){super(r);this.groups=[];this.index=-1;this.createGroups(),this.container&&(this.el.classList.add("active"),Array.from(this.el.children).forEach(n=>{n.addEventListener("touchstart",i=>this.onClick(i)),n.addEventListener("click",i=>this.onClick(i))}),this.container.classList.add("active"),this.setIndex(0))}setIndex(r){if(r<0&&(r=0),r>this.groups.length-1&&(r=this.groups.length-1),this.index==r)return;let n=this.groups[r];if(this.index>-1){let i=this.groups[this.index];i.removeClass("current").addClass("fade-out"),n.addClass("current"),n.addClass("fade-in"),Q.instance.triggerResize(),setTimeout(()=>{i.removeClass("fade-out"),n.removeClass("fade-in")},300)}else n.addClass("current"),Q.instance.triggerResize();this.index=r}createGroups(){let r=this.el.children;if(r.length<2)return;this.container=this.el.nextElementSibling;let n=this.container.children;this.groups=[];for(let i=0;i{n.signature===r.currentTarget&&this.setIndex(i)})}};var C="mousedown",Le="mousemove",_="mouseup",K={x:0,y:0},xe=!1,ae=!1,je=!1,A=!1,Ee=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);document.documentElement.classList.add(Ee?"is-mobile":"not-mobile");Ee&&"ontouchstart"in document.documentElement&&(je=!0,C="touchstart",Le="touchmove",_="touchend");document.addEventListener(C,t=>{ae=!0,A=!1;let e=C=="touchstart"?t.targetTouches[0]:t;K.y=e.pageY||0,K.x=e.pageX||0});document.addEventListener(Le,t=>{if(!!ae&&!A){let e=C=="touchstart"?t.targetTouches[0]:t,r=K.x-(e.pageX||0),n=K.y-(e.pageY||0);A=Math.sqrt(r*r+n*n)>10}});document.addEventListener(_,()=>{ae=!1});document.addEventListener("click",t=>{xe&&(t.preventDefault(),t.stopImmediatePropagation(),xe=!1)});var ee=class extends k{constructor(r){super(r);this.className=this.el.dataset.toggle||"",this.el.addEventListener(_,n=>this.onPointerUp(n)),this.el.addEventListener("click",n=>n.preventDefault()),document.addEventListener(C,n=>this.onDocumentPointerDown(n)),document.addEventListener(_,n=>this.onDocumentPointerUp(n))}setActive(r){if(this.active==r)return;this.active=r,document.documentElement.classList.toggle("has-"+this.className,r),this.el.classList.toggle("active",r);let n=(this.active?"to-has-":"from-has-")+this.className;document.documentElement.classList.add(n),setTimeout(()=>document.documentElement.classList.remove(n),500)}onPointerUp(r){A||(this.setActive(!0),r.preventDefault())}onDocumentPointerDown(r){if(this.active){if(r.target.closest(".col-menu, .tsd-filter-group"))return;this.setActive(!1)}}onDocumentPointerUp(r){if(!A&&this.active&&r.target.closest(".col-menu")){let n=r.target.closest("a");if(n){let i=window.location.href;i.indexOf("#")!=-1&&(i=i.substr(0,i.indexOf("#"))),n.href.substr(0,i.length)==i&&setTimeout(()=>this.setActive(!1),250)}}}};var te=class{constructor(e,r){this.key=e,this.value=r,this.defaultValue=r,this.initialize(),window.localStorage[this.key]&&this.setValue(this.fromLocalStorage(window.localStorage[this.key]))}initialize(){}setValue(e){if(this.value==e)return;let r=this.value;this.value=e,window.localStorage[this.key]=this.toLocalStorage(e),this.handleValueChange(r,e)}},re=class extends te{initialize(){let r=document.querySelector("#tsd-filter-"+this.key);!r||(this.checkbox=r,this.checkbox.addEventListener("change",()=>{this.setValue(this.checkbox.checked)}))}handleValueChange(r,n){!this.checkbox||(this.checkbox.checked=this.value,document.documentElement.classList.toggle("toggle-"+this.key,this.value!=this.defaultValue))}fromLocalStorage(r){return r=="true"}toLocalStorage(r){return r?"true":"false"}},le=class extends te{initialize(){document.documentElement.classList.add("toggle-"+this.key+this.value);let r=document.querySelector("#tsd-filter-"+this.key);if(!r)return;this.select=r;let n=()=>{this.select.classList.add("active")},i=()=>{this.select.classList.remove("active")};this.select.addEventListener(C,n),this.select.addEventListener("mouseover",n),this.select.addEventListener("mouseleave",i),this.select.querySelectorAll("li").forEach(s=>{s.addEventListener(_,o=>{r.classList.remove("active"),this.setValue(o.target.dataset.value||"")})}),document.addEventListener(C,s=>{this.select.contains(s.target)||this.select.classList.remove("active")})}handleValueChange(r,n){this.select.querySelectorAll("li.selected").forEach(o=>{o.classList.remove("selected")});let i=this.select.querySelector('li[data-value="'+n+'"]'),s=this.select.querySelector(".tsd-select-label");i&&s&&(i.classList.add("selected"),s.textContent=i.textContent),document.documentElement.classList.remove("toggle-"+r),document.documentElement.classList.add("toggle-"+n)}fromLocalStorage(r){return r}toLocalStorage(r){return r}},j=class extends k{constructor(r){super(r);this.optionVisibility=new le("visibility","private"),this.optionInherited=new re("inherited",!0),this.optionExternals=new re("externals",!0)}static isSupported(){try{return typeof window.localStorage!="undefined"}catch{return!1}}};function we(t){let e=localStorage.getItem("tsd-theme")||"os";t.value=e,be(e),t.addEventListener("change",()=>{localStorage.setItem("tsd-theme",t.value),be(t.value)})}function be(t){switch(t){case"os":document.body.classList.remove("light","dark");break;case"light":document.body.classList.remove("dark"),document.body.classList.add("light");break;case"dark":document.body.classList.remove("light"),document.body.classList.add("dark");break}}ye();N(X,".menu-highlight");N(Z,".tsd-signatures");N(ee,"a[data-toggle]");j.isSupported()?N(j,"#tsd-filter"):document.documentElement.classList.add("no-filter");var Te=document.getElementById("theme");Te&&we(Te);var Be=new Y;Object.defineProperty(window,"app",{value:Be});})(); /*! * lunr.Builder * Copyright (C) 2020 Oliver Nightingale diff --git a/backend/docs/assets/search.js b/backend/docs/assets/search.js index c336335c..af36ccda 100644 --- a/backend/docs/assets/search.js +++ b/backend/docs/assets/search.js @@ -1 +1 @@ -window.searchData = JSON.parse("{\"kinds\":{\"2\":\"Module\",\"64\":\"Function\",\"256\":\"Interface\",\"1024\":\"Property\"},\"rows\":[{\"id\":0,\"kind\":2,\"name\":\"routes/admin\",\"url\":\"modules/routes_admin.html\",\"classes\":\"tsd-kind-module\"},{\"id\":1,\"kind\":64,\"name\":\"getRouter\",\"url\":\"modules/routes_admin.html#getRouter\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/admin\"},{\"id\":2,\"kind\":2,\"name\":\"routes/coach\",\"url\":\"modules/routes_coach.html\",\"classes\":\"tsd-kind-module\"},{\"id\":3,\"kind\":64,\"name\":\"getRouter\",\"url\":\"modules/routes_coach.html#getRouter\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/coach\"},{\"id\":4,\"kind\":2,\"name\":\"routes/form\",\"url\":\"modules/routes_form.html\",\"classes\":\"tsd-kind-module\"},{\"id\":5,\"kind\":64,\"name\":\"getRouter\",\"url\":\"modules/routes_form.html#getRouter\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":6,\"kind\":2,\"name\":\"routes/login\",\"url\":\"modules/routes_login.html\",\"classes\":\"tsd-kind-module\"},{\"id\":7,\"kind\":64,\"name\":\"getRouter\",\"url\":\"modules/routes_login.html#getRouter\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/login\"},{\"id\":8,\"kind\":2,\"name\":\"routes/project\",\"url\":\"modules/routes_project.html\",\"classes\":\"tsd-kind-module\"},{\"id\":9,\"kind\":64,\"name\":\"getRouter\",\"url\":\"modules/routes_project.html#getRouter\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/project\"},{\"id\":10,\"kind\":2,\"name\":\"routes/student\",\"url\":\"modules/routes_student.html\",\"classes\":\"tsd-kind-module\"},{\"id\":11,\"kind\":64,\"name\":\"getRouter\",\"url\":\"modules/routes_student.html#getRouter\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/student\"},{\"id\":12,\"kind\":2,\"name\":\"orm_functions/applied_role\",\"url\":\"modules/orm_functions_applied_role.html\",\"classes\":\"tsd-kind-module\"},{\"id\":13,\"kind\":64,\"name\":\"createAppliedRole\",\"url\":\"modules/orm_functions_applied_role.html#createAppliedRole\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/applied_role\"},{\"id\":14,\"kind\":64,\"name\":\"getAppliedRolesByJobApplication\",\"url\":\"modules/orm_functions_applied_role.html#getAppliedRolesByJobApplication\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/applied_role\"},{\"id\":15,\"kind\":2,\"name\":\"orm_functions/job_application\",\"url\":\"modules/orm_functions_job_application.html\",\"classes\":\"tsd-kind-module\"},{\"id\":16,\"kind\":64,\"name\":\"getStudentEvaluationsTotal\",\"url\":\"modules/orm_functions_job_application.html#getStudentEvaluationsTotal\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/job_application\"},{\"id\":17,\"kind\":64,\"name\":\"getStudentEvaluationsFinal\",\"url\":\"modules/orm_functions_job_application.html#getStudentEvaluationsFinal\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/job_application\"},{\"id\":18,\"kind\":64,\"name\":\"getStudentEvaluationsTemp\",\"url\":\"modules/orm_functions_job_application.html#getStudentEvaluationsTemp\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/job_application\"},{\"id\":19,\"kind\":64,\"name\":\"getLatestApplicationRolesForStudent\",\"url\":\"modules/orm_functions_job_application.html#getLatestApplicationRolesForStudent\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/job_application\"},{\"id\":20,\"kind\":64,\"name\":\"deleteJobApplicationsFromStudent\",\"url\":\"modules/orm_functions_job_application.html#deleteJobApplicationsFromStudent\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/job_application\"},{\"id\":21,\"kind\":64,\"name\":\"changeEmailStatusOfJobApplication\",\"url\":\"modules/orm_functions_job_application.html#changeEmailStatusOfJobApplication\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/job_application\"},{\"id\":22,\"kind\":64,\"name\":\"deleteJobApplication\",\"url\":\"modules/orm_functions_job_application.html#deleteJobApplication\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/job_application\"},{\"id\":23,\"kind\":64,\"name\":\"createJobApplication\",\"url\":\"modules/orm_functions_job_application.html#createJobApplication\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/job_application\"},{\"id\":24,\"kind\":64,\"name\":\"getLatestJobApplicationOfStudent\",\"url\":\"modules/orm_functions_job_application.html#getLatestJobApplicationOfStudent\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/job_application\"},{\"id\":25,\"kind\":64,\"name\":\"getJobApplication\",\"url\":\"modules/orm_functions_job_application.html#getJobApplication\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/job_application\"},{\"id\":26,\"kind\":64,\"name\":\"getJobApplicationByYear\",\"url\":\"modules/orm_functions_job_application.html#getJobApplicationByYear\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/job_application\"},{\"id\":27,\"kind\":2,\"name\":\"orm_functions/osoc\",\"url\":\"modules/orm_functions_osoc.html\",\"classes\":\"tsd-kind-module\"},{\"id\":28,\"kind\":64,\"name\":\"createOsoc\",\"url\":\"modules/orm_functions_osoc.html#createOsoc\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/osoc\"},{\"id\":29,\"kind\":64,\"name\":\"getAllOsoc\",\"url\":\"modules/orm_functions_osoc.html#getAllOsoc\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/osoc\"},{\"id\":30,\"kind\":64,\"name\":\"getOsocByYear\",\"url\":\"modules/orm_functions_osoc.html#getOsocByYear\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/osoc\"},{\"id\":31,\"kind\":64,\"name\":\"getOsocBeforeYear\",\"url\":\"modules/orm_functions_osoc.html#getOsocBeforeYear\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/osoc\"},{\"id\":32,\"kind\":64,\"name\":\"getOsocAfterYear\",\"url\":\"modules/orm_functions_osoc.html#getOsocAfterYear\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/osoc\"},{\"id\":33,\"kind\":64,\"name\":\"updateOsoc\",\"url\":\"modules/orm_functions_osoc.html#updateOsoc\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/osoc\"},{\"id\":34,\"kind\":64,\"name\":\"deleteOsoc\",\"url\":\"modules/orm_functions_osoc.html#deleteOsoc\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/osoc\"},{\"id\":35,\"kind\":64,\"name\":\"deleteOsocByYear\",\"url\":\"modules/orm_functions_osoc.html#deleteOsocByYear\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/osoc\"},{\"id\":36,\"kind\":2,\"name\":\"orm_functions/role\",\"url\":\"modules/orm_functions_role.html\",\"classes\":\"tsd-kind-module\"},{\"id\":37,\"kind\":64,\"name\":\"createProjectRole\",\"url\":\"modules/orm_functions_role.html#createProjectRole\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/role\"},{\"id\":38,\"kind\":64,\"name\":\"getAllRoles\",\"url\":\"modules/orm_functions_role.html#getAllRoles\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/role\"},{\"id\":39,\"kind\":64,\"name\":\"getRole\",\"url\":\"modules/orm_functions_role.html#getRole\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/role\"},{\"id\":40,\"kind\":64,\"name\":\"getRolesByName\",\"url\":\"modules/orm_functions_role.html#getRolesByName\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/role\"},{\"id\":41,\"kind\":64,\"name\":\"getProjectRoleWithRoleName\",\"url\":\"modules/orm_functions_role.html#getProjectRoleWithRoleName\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/role\"},{\"id\":42,\"kind\":64,\"name\":\"updateRole\",\"url\":\"modules/orm_functions_role.html#updateRole\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/role\"},{\"id\":43,\"kind\":64,\"name\":\"deleteRole\",\"url\":\"modules/orm_functions_role.html#deleteRole\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/role\"},{\"id\":44,\"kind\":64,\"name\":\"deleteRoleByName\",\"url\":\"modules/orm_functions_role.html#deleteRoleByName\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/role\"},{\"id\":45,\"kind\":2,\"name\":\"orm_functions/attachment\",\"url\":\"modules/orm_functions_attachment.html\",\"classes\":\"tsd-kind-module\"},{\"id\":46,\"kind\":64,\"name\":\"createAttachment\",\"url\":\"modules/orm_functions_attachment.html#createAttachment\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/attachment\"},{\"id\":47,\"kind\":64,\"name\":\"deleteAttachment\",\"url\":\"modules/orm_functions_attachment.html#deleteAttachment\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/attachment\"},{\"id\":48,\"kind\":64,\"name\":\"deleteAllAttachmentsForApplication\",\"url\":\"modules/orm_functions_attachment.html#deleteAllAttachmentsForApplication\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/attachment\"},{\"id\":49,\"kind\":64,\"name\":\"getAttachmentById\",\"url\":\"modules/orm_functions_attachment.html#getAttachmentById\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/attachment\"},{\"id\":50,\"kind\":2,\"name\":\"orm_functions/job_application_skill\",\"url\":\"modules/orm_functions_job_application_skill.html\",\"classes\":\"tsd-kind-module\"},{\"id\":51,\"kind\":64,\"name\":\"createJobApplicationSkill\",\"url\":\"modules/orm_functions_job_application_skill.html#createJobApplicationSkill\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/job_application_skill\"},{\"id\":52,\"kind\":64,\"name\":\"getAllJobApplicationSkill\",\"url\":\"modules/orm_functions_job_application_skill.html#getAllJobApplicationSkill\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/job_application_skill\"},{\"id\":53,\"kind\":64,\"name\":\"getAllJobApplicationSkillByJobApplication\",\"url\":\"modules/orm_functions_job_application_skill.html#getAllJobApplicationSkillByJobApplication\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/job_application_skill\"},{\"id\":54,\"kind\":64,\"name\":\"getJobApplicationSkill\",\"url\":\"modules/orm_functions_job_application_skill.html#getJobApplicationSkill\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/job_application_skill\"},{\"id\":55,\"kind\":64,\"name\":\"updateJobApplicationSkill\",\"url\":\"modules/orm_functions_job_application_skill.html#updateJobApplicationSkill\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/job_application_skill\"},{\"id\":56,\"kind\":64,\"name\":\"deleteJobApplicationSkill\",\"url\":\"modules/orm_functions_job_application_skill.html#deleteJobApplicationSkill\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/job_application_skill\"},{\"id\":57,\"kind\":2,\"name\":\"orm_functions/person\",\"url\":\"modules/orm_functions_person.html\",\"classes\":\"tsd-kind-module\"},{\"id\":58,\"kind\":64,\"name\":\"createPerson\",\"url\":\"modules/orm_functions_person.html#createPerson\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/person\"},{\"id\":59,\"kind\":64,\"name\":\"getAllPersons\",\"url\":\"modules/orm_functions_person.html#getAllPersons\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/person\"},{\"id\":60,\"kind\":64,\"name\":\"getPasswordPersonByEmail\",\"url\":\"modules/orm_functions_person.html#getPasswordPersonByEmail\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/person\"},{\"id\":61,\"kind\":64,\"name\":\"searchPersonByName\",\"url\":\"modules/orm_functions_person.html#searchPersonByName\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/person\"},{\"id\":62,\"kind\":64,\"name\":\"searchPersonByLogin\",\"url\":\"modules/orm_functions_person.html#searchPersonByLogin\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/person\"},{\"id\":63,\"kind\":64,\"name\":\"updatePerson\",\"url\":\"modules/orm_functions_person.html#updatePerson\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/person\"},{\"id\":64,\"kind\":64,\"name\":\"deletePersonById\",\"url\":\"modules/orm_functions_person.html#deletePersonById\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/person\"},{\"id\":65,\"kind\":2,\"name\":\"orm_functions/session_key\",\"url\":\"modules/orm_functions_session_key.html\",\"classes\":\"tsd-kind-module\"},{\"id\":66,\"kind\":64,\"name\":\"addSessionKey\",\"url\":\"modules/orm_functions_session_key.html#addSessionKey\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/session_key\"},{\"id\":67,\"kind\":64,\"name\":\"checkSessionKey\",\"url\":\"modules/orm_functions_session_key.html#checkSessionKey\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/session_key\"},{\"id\":68,\"kind\":64,\"name\":\"changeSessionKey\",\"url\":\"modules/orm_functions_session_key.html#changeSessionKey\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/session_key\"},{\"id\":69,\"kind\":64,\"name\":\"removeAllKeysForUser\",\"url\":\"modules/orm_functions_session_key.html#removeAllKeysForUser\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/session_key\"},{\"id\":70,\"kind\":2,\"name\":\"orm_functions/contract\",\"url\":\"modules/orm_functions_contract.html\",\"classes\":\"tsd-kind-module\"},{\"id\":71,\"kind\":64,\"name\":\"createContract\",\"url\":\"modules/orm_functions_contract.html#createContract\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/contract\"},{\"id\":72,\"kind\":64,\"name\":\"updateContract\",\"url\":\"modules/orm_functions_contract.html#updateContract\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/contract\"},{\"id\":73,\"kind\":64,\"name\":\"removeContractsFromStudent\",\"url\":\"modules/orm_functions_contract.html#removeContractsFromStudent\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/contract\"},{\"id\":74,\"kind\":64,\"name\":\"removeContract\",\"url\":\"modules/orm_functions_contract.html#removeContract\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/contract\"},{\"id\":75,\"kind\":2,\"name\":\"orm_functions/language\",\"url\":\"modules/orm_functions_language.html\",\"classes\":\"tsd-kind-module\"},{\"id\":76,\"kind\":64,\"name\":\"createLanguage\",\"url\":\"modules/orm_functions_language.html#createLanguage\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/language\"},{\"id\":77,\"kind\":64,\"name\":\"getAllLanguages\",\"url\":\"modules/orm_functions_language.html#getAllLanguages\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/language\"},{\"id\":78,\"kind\":64,\"name\":\"getLanguage\",\"url\":\"modules/orm_functions_language.html#getLanguage\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/language\"},{\"id\":79,\"kind\":64,\"name\":\"getLanguageByName\",\"url\":\"modules/orm_functions_language.html#getLanguageByName\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/language\"},{\"id\":80,\"kind\":64,\"name\":\"updateLanguage\",\"url\":\"modules/orm_functions_language.html#updateLanguage\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/language\"},{\"id\":81,\"kind\":64,\"name\":\"deleteLanguage\",\"url\":\"modules/orm_functions_language.html#deleteLanguage\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/language\"},{\"id\":82,\"kind\":64,\"name\":\"deleteLanguageByName\",\"url\":\"modules/orm_functions_language.html#deleteLanguageByName\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/language\"},{\"id\":83,\"kind\":2,\"name\":\"orm_functions/project\",\"url\":\"modules/orm_functions_project.html\",\"classes\":\"tsd-kind-module\"},{\"id\":84,\"kind\":64,\"name\":\"createProject\",\"url\":\"modules/orm_functions_project.html#createProject\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project\"},{\"id\":85,\"kind\":64,\"name\":\"getAllProjects\",\"url\":\"modules/orm_functions_project.html#getAllProjects\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project\"},{\"id\":86,\"kind\":64,\"name\":\"getProjectByName\",\"url\":\"modules/orm_functions_project.html#getProjectByName\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project\"},{\"id\":87,\"kind\":64,\"name\":\"getProjectsByOsocEdition\",\"url\":\"modules/orm_functions_project.html#getProjectsByOsocEdition\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project\"},{\"id\":88,\"kind\":64,\"name\":\"getProjectsByPartner\",\"url\":\"modules/orm_functions_project.html#getProjectsByPartner\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project\"},{\"id\":89,\"kind\":64,\"name\":\"getProjectsByStartDate\",\"url\":\"modules/orm_functions_project.html#getProjectsByStartDate\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project\"},{\"id\":90,\"kind\":64,\"name\":\"getProjectsStartedAfterDate\",\"url\":\"modules/orm_functions_project.html#getProjectsStartedAfterDate\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project\"},{\"id\":91,\"kind\":64,\"name\":\"getProjectsStartedBeforeDate\",\"url\":\"modules/orm_functions_project.html#getProjectsStartedBeforeDate\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project\"},{\"id\":92,\"kind\":64,\"name\":\"getProjectsByEndDate\",\"url\":\"modules/orm_functions_project.html#getProjectsByEndDate\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project\"},{\"id\":93,\"kind\":64,\"name\":\"getProjectsEndedAfterDate\",\"url\":\"modules/orm_functions_project.html#getProjectsEndedAfterDate\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project\"},{\"id\":94,\"kind\":64,\"name\":\"getProjectsEndedBeforeDate\",\"url\":\"modules/orm_functions_project.html#getProjectsEndedBeforeDate\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project\"},{\"id\":95,\"kind\":64,\"name\":\"getProjectsByNumberPositions\",\"url\":\"modules/orm_functions_project.html#getProjectsByNumberPositions\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project\"},{\"id\":96,\"kind\":64,\"name\":\"getProjectsLessPositions\",\"url\":\"modules/orm_functions_project.html#getProjectsLessPositions\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project\"},{\"id\":97,\"kind\":64,\"name\":\"getProjectsMorePositions\",\"url\":\"modules/orm_functions_project.html#getProjectsMorePositions\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project\"},{\"id\":98,\"kind\":64,\"name\":\"updateProject\",\"url\":\"modules/orm_functions_project.html#updateProject\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project\"},{\"id\":99,\"kind\":64,\"name\":\"deleteProject\",\"url\":\"modules/orm_functions_project.html#deleteProject\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project\"},{\"id\":100,\"kind\":64,\"name\":\"deleteProjectByOsocEdition\",\"url\":\"modules/orm_functions_project.html#deleteProjectByOsocEdition\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project\"},{\"id\":101,\"kind\":64,\"name\":\"deleteProjectByPartner\",\"url\":\"modules/orm_functions_project.html#deleteProjectByPartner\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project\"},{\"id\":102,\"kind\":2,\"name\":\"orm_functions/student\",\"url\":\"modules/orm_functions_student.html\",\"classes\":\"tsd-kind-module\"},{\"id\":103,\"kind\":64,\"name\":\"createStudent\",\"url\":\"modules/orm_functions_student.html#createStudent\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/student\"},{\"id\":104,\"kind\":64,\"name\":\"getAllStudents\",\"url\":\"modules/orm_functions_student.html#getAllStudents\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/student\"},{\"id\":105,\"kind\":64,\"name\":\"getStudent\",\"url\":\"modules/orm_functions_student.html#getStudent\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/student\"},{\"id\":106,\"kind\":64,\"name\":\"updateStudent\",\"url\":\"modules/orm_functions_student.html#updateStudent\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/student\"},{\"id\":107,\"kind\":64,\"name\":\"deleteStudent\",\"url\":\"modules/orm_functions_student.html#deleteStudent\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/student\"},{\"id\":108,\"kind\":64,\"name\":\"searchStudentByGender\",\"url\":\"modules/orm_functions_student.html#searchStudentByGender\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/student\"},{\"id\":109,\"kind\":2,\"name\":\"orm_functions/evaluation\",\"url\":\"modules/orm_functions_evaluation.html\",\"classes\":\"tsd-kind-module\"},{\"id\":110,\"kind\":64,\"name\":\"checkIfFinalEvaluationExists\",\"url\":\"modules/orm_functions_evaluation.html#checkIfFinalEvaluationExists\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/evaluation\"},{\"id\":111,\"kind\":64,\"name\":\"createEvaluationForStudent\",\"url\":\"modules/orm_functions_evaluation.html#createEvaluationForStudent\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/evaluation\"},{\"id\":112,\"kind\":64,\"name\":\"updateEvaluationForStudent\",\"url\":\"modules/orm_functions_evaluation.html#updateEvaluationForStudent\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/evaluation\"},{\"id\":113,\"kind\":64,\"name\":\"getLoginUserByEvaluationId\",\"url\":\"modules/orm_functions_evaluation.html#getLoginUserByEvaluationId\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/evaluation\"},{\"id\":114,\"kind\":2,\"name\":\"orm_functions/login_user\",\"url\":\"modules/orm_functions_login_user.html\",\"classes\":\"tsd-kind-module\"},{\"id\":115,\"kind\":64,\"name\":\"createLoginUser\",\"url\":\"modules/orm_functions_login_user.html#createLoginUser\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/login_user\"},{\"id\":116,\"kind\":64,\"name\":\"getAllLoginUsers\",\"url\":\"modules/orm_functions_login_user.html#getAllLoginUsers\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/login_user\"},{\"id\":117,\"kind\":64,\"name\":\"getPasswordLoginUserByPerson\",\"url\":\"modules/orm_functions_login_user.html#getPasswordLoginUserByPerson\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/login_user\"},{\"id\":118,\"kind\":64,\"name\":\"getPasswordLoginUser\",\"url\":\"modules/orm_functions_login_user.html#getPasswordLoginUser\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/login_user\"},{\"id\":119,\"kind\":64,\"name\":\"searchLoginUserByPerson\",\"url\":\"modules/orm_functions_login_user.html#searchLoginUserByPerson\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/login_user\"},{\"id\":120,\"kind\":64,\"name\":\"searchAllAdminLoginUsers\",\"url\":\"modules/orm_functions_login_user.html#searchAllAdminLoginUsers\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/login_user\"},{\"id\":121,\"kind\":64,\"name\":\"searchAllCoachLoginUsers\",\"url\":\"modules/orm_functions_login_user.html#searchAllCoachLoginUsers\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/login_user\"},{\"id\":122,\"kind\":64,\"name\":\"searchAllAdminAndCoachLoginUsers\",\"url\":\"modules/orm_functions_login_user.html#searchAllAdminAndCoachLoginUsers\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/login_user\"},{\"id\":123,\"kind\":64,\"name\":\"updateLoginUser\",\"url\":\"modules/orm_functions_login_user.html#updateLoginUser\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/login_user\"},{\"id\":124,\"kind\":64,\"name\":\"deleteLoginUserById\",\"url\":\"modules/orm_functions_login_user.html#deleteLoginUserById\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/login_user\"},{\"id\":125,\"kind\":64,\"name\":\"deleteLoginUserByPersonId\",\"url\":\"modules/orm_functions_login_user.html#deleteLoginUserByPersonId\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/login_user\"},{\"id\":126,\"kind\":64,\"name\":\"getLoginUserById\",\"url\":\"modules/orm_functions_login_user.html#getLoginUserById\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/login_user\"},{\"id\":127,\"kind\":2,\"name\":\"orm_functions/project_role\",\"url\":\"modules/orm_functions_project_role.html\",\"classes\":\"tsd-kind-module\"},{\"id\":128,\"kind\":64,\"name\":\"createProjectRole\",\"url\":\"modules/orm_functions_project_role.html#createProjectRole\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project_role\"},{\"id\":129,\"kind\":64,\"name\":\"getProjectRolesByProject\",\"url\":\"modules/orm_functions_project_role.html#getProjectRolesByProject\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project_role\"},{\"id\":130,\"kind\":64,\"name\":\"getNumberOfRolesByProjectAndRole\",\"url\":\"modules/orm_functions_project_role.html#getNumberOfRolesByProjectAndRole\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project_role\"},{\"id\":131,\"kind\":64,\"name\":\"getProjectRoleNamesByProject\",\"url\":\"modules/orm_functions_project_role.html#getProjectRoleNamesByProject\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project_role\"},{\"id\":132,\"kind\":64,\"name\":\"updateProjectRole\",\"url\":\"modules/orm_functions_project_role.html#updateProjectRole\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project_role\"},{\"id\":133,\"kind\":64,\"name\":\"deleteProjectRole\",\"url\":\"modules/orm_functions_project_role.html#deleteProjectRole\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project_role\"},{\"id\":134,\"kind\":64,\"name\":\"getNumberOfFreePositions\",\"url\":\"modules/orm_functions_project_role.html#getNumberOfFreePositions\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project_role\"},{\"id\":135,\"kind\":2,\"name\":\"orm_functions/general_purpose\",\"url\":\"modules/orm_functions_general_purpose.html\",\"classes\":\"tsd-kind-module\"},{\"id\":136,\"kind\":64,\"name\":\"addStudentToProject\",\"url\":\"modules/orm_functions_general_purpose.html#addStudentToProject\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/general_purpose\"},{\"id\":137,\"kind\":2,\"name\":\"orm_functions/orm_types\",\"url\":\"modules/orm_functions_orm_types.html\",\"classes\":\"tsd-kind-module\"},{\"id\":138,\"kind\":256,\"name\":\"CreatePerson\",\"url\":\"interfaces/orm_functions_orm_types.CreatePerson.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":139,\"kind\":1024,\"name\":\"firstname\",\"url\":\"interfaces/orm_functions_orm_types.CreatePerson.html#firstname\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreatePerson\"},{\"id\":140,\"kind\":1024,\"name\":\"lastname\",\"url\":\"interfaces/orm_functions_orm_types.CreatePerson.html#lastname\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreatePerson\"},{\"id\":141,\"kind\":1024,\"name\":\"github\",\"url\":\"interfaces/orm_functions_orm_types.CreatePerson.html#github\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreatePerson\"},{\"id\":142,\"kind\":1024,\"name\":\"email\",\"url\":\"interfaces/orm_functions_orm_types.CreatePerson.html#email\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreatePerson\"},{\"id\":143,\"kind\":256,\"name\":\"UpdatePerson\",\"url\":\"interfaces/orm_functions_orm_types.UpdatePerson.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":144,\"kind\":1024,\"name\":\"personId\",\"url\":\"interfaces/orm_functions_orm_types.UpdatePerson.html#personId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdatePerson\"},{\"id\":145,\"kind\":1024,\"name\":\"firstname\",\"url\":\"interfaces/orm_functions_orm_types.UpdatePerson.html#firstname\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdatePerson\"},{\"id\":146,\"kind\":1024,\"name\":\"lastname\",\"url\":\"interfaces/orm_functions_orm_types.UpdatePerson.html#lastname\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdatePerson\"},{\"id\":147,\"kind\":1024,\"name\":\"github\",\"url\":\"interfaces/orm_functions_orm_types.UpdatePerson.html#github\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdatePerson\"},{\"id\":148,\"kind\":1024,\"name\":\"email\",\"url\":\"interfaces/orm_functions_orm_types.UpdatePerson.html#email\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdatePerson\"},{\"id\":149,\"kind\":256,\"name\":\"CreateLoginUser\",\"url\":\"interfaces/orm_functions_orm_types.CreateLoginUser.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":150,\"kind\":1024,\"name\":\"personId\",\"url\":\"interfaces/orm_functions_orm_types.CreateLoginUser.html#personId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateLoginUser\"},{\"id\":151,\"kind\":1024,\"name\":\"password\",\"url\":\"interfaces/orm_functions_orm_types.CreateLoginUser.html#password\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateLoginUser\"},{\"id\":152,\"kind\":1024,\"name\":\"isAdmin\",\"url\":\"interfaces/orm_functions_orm_types.CreateLoginUser.html#isAdmin\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateLoginUser\"},{\"id\":153,\"kind\":1024,\"name\":\"isCoach\",\"url\":\"interfaces/orm_functions_orm_types.CreateLoginUser.html#isCoach\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateLoginUser\"},{\"id\":154,\"kind\":1024,\"name\":\"accountStatus\",\"url\":\"interfaces/orm_functions_orm_types.CreateLoginUser.html#accountStatus\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateLoginUser\"},{\"id\":155,\"kind\":256,\"name\":\"UpdateLoginUser\",\"url\":\"interfaces/orm_functions_orm_types.UpdateLoginUser.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":156,\"kind\":1024,\"name\":\"loginUserId\",\"url\":\"interfaces/orm_functions_orm_types.UpdateLoginUser.html#loginUserId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateLoginUser\"},{\"id\":157,\"kind\":1024,\"name\":\"password\",\"url\":\"interfaces/orm_functions_orm_types.UpdateLoginUser.html#password\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateLoginUser\"},{\"id\":158,\"kind\":1024,\"name\":\"isAdmin\",\"url\":\"interfaces/orm_functions_orm_types.UpdateLoginUser.html#isAdmin\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateLoginUser\"},{\"id\":159,\"kind\":1024,\"name\":\"isCoach\",\"url\":\"interfaces/orm_functions_orm_types.UpdateLoginUser.html#isCoach\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateLoginUser\"},{\"id\":160,\"kind\":1024,\"name\":\"accountStatus\",\"url\":\"interfaces/orm_functions_orm_types.UpdateLoginUser.html#accountStatus\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateLoginUser\"},{\"id\":161,\"kind\":256,\"name\":\"CreateStudent\",\"url\":\"interfaces/orm_functions_orm_types.CreateStudent.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":162,\"kind\":1024,\"name\":\"personId\",\"url\":\"interfaces/orm_functions_orm_types.CreateStudent.html#personId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateStudent\"},{\"id\":163,\"kind\":1024,\"name\":\"gender\",\"url\":\"interfaces/orm_functions_orm_types.CreateStudent.html#gender\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateStudent\"},{\"id\":164,\"kind\":1024,\"name\":\"pronouns\",\"url\":\"interfaces/orm_functions_orm_types.CreateStudent.html#pronouns\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateStudent\"},{\"id\":165,\"kind\":1024,\"name\":\"phoneNumber\",\"url\":\"interfaces/orm_functions_orm_types.CreateStudent.html#phoneNumber\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateStudent\"},{\"id\":166,\"kind\":1024,\"name\":\"nickname\",\"url\":\"interfaces/orm_functions_orm_types.CreateStudent.html#nickname\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateStudent\"},{\"id\":167,\"kind\":1024,\"name\":\"alumni\",\"url\":\"interfaces/orm_functions_orm_types.CreateStudent.html#alumni\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateStudent\"},{\"id\":168,\"kind\":256,\"name\":\"UpdateStudent\",\"url\":\"interfaces/orm_functions_orm_types.UpdateStudent.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":169,\"kind\":1024,\"name\":\"studentId\",\"url\":\"interfaces/orm_functions_orm_types.UpdateStudent.html#studentId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateStudent\"},{\"id\":170,\"kind\":1024,\"name\":\"gender\",\"url\":\"interfaces/orm_functions_orm_types.UpdateStudent.html#gender\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateStudent\"},{\"id\":171,\"kind\":1024,\"name\":\"pronouns\",\"url\":\"interfaces/orm_functions_orm_types.UpdateStudent.html#pronouns\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateStudent\"},{\"id\":172,\"kind\":1024,\"name\":\"phoneNumber\",\"url\":\"interfaces/orm_functions_orm_types.UpdateStudent.html#phoneNumber\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateStudent\"},{\"id\":173,\"kind\":1024,\"name\":\"nickname\",\"url\":\"interfaces/orm_functions_orm_types.UpdateStudent.html#nickname\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateStudent\"},{\"id\":174,\"kind\":1024,\"name\":\"alumni\",\"url\":\"interfaces/orm_functions_orm_types.UpdateStudent.html#alumni\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateStudent\"},{\"id\":175,\"kind\":256,\"name\":\"CreateEvaluationForStudent\",\"url\":\"interfaces/orm_functions_orm_types.CreateEvaluationForStudent.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":176,\"kind\":1024,\"name\":\"loginUserId\",\"url\":\"interfaces/orm_functions_orm_types.CreateEvaluationForStudent.html#loginUserId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateEvaluationForStudent\"},{\"id\":177,\"kind\":1024,\"name\":\"jobApplicationId\",\"url\":\"interfaces/orm_functions_orm_types.CreateEvaluationForStudent.html#jobApplicationId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateEvaluationForStudent\"},{\"id\":178,\"kind\":1024,\"name\":\"decision\",\"url\":\"interfaces/orm_functions_orm_types.CreateEvaluationForStudent.html#decision\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateEvaluationForStudent\"},{\"id\":179,\"kind\":1024,\"name\":\"motivation\",\"url\":\"interfaces/orm_functions_orm_types.CreateEvaluationForStudent.html#motivation\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateEvaluationForStudent\"},{\"id\":180,\"kind\":1024,\"name\":\"isFinal\",\"url\":\"interfaces/orm_functions_orm_types.CreateEvaluationForStudent.html#isFinal\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateEvaluationForStudent\"},{\"id\":181,\"kind\":256,\"name\":\"UpdateEvaluationForStudent\",\"url\":\"interfaces/orm_functions_orm_types.UpdateEvaluationForStudent.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":182,\"kind\":1024,\"name\":\"evaluation_id\",\"url\":\"interfaces/orm_functions_orm_types.UpdateEvaluationForStudent.html#evaluation_id\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateEvaluationForStudent\"},{\"id\":183,\"kind\":1024,\"name\":\"loginUserId\",\"url\":\"interfaces/orm_functions_orm_types.UpdateEvaluationForStudent.html#loginUserId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateEvaluationForStudent\"},{\"id\":184,\"kind\":1024,\"name\":\"decision\",\"url\":\"interfaces/orm_functions_orm_types.UpdateEvaluationForStudent.html#decision\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateEvaluationForStudent\"},{\"id\":185,\"kind\":1024,\"name\":\"motivation\",\"url\":\"interfaces/orm_functions_orm_types.UpdateEvaluationForStudent.html#motivation\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateEvaluationForStudent\"},{\"id\":186,\"kind\":256,\"name\":\"CreateContract\",\"url\":\"interfaces/orm_functions_orm_types.CreateContract.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":187,\"kind\":1024,\"name\":\"studentId\",\"url\":\"interfaces/orm_functions_orm_types.CreateContract.html#studentId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateContract\"},{\"id\":188,\"kind\":1024,\"name\":\"projectRoleId\",\"url\":\"interfaces/orm_functions_orm_types.CreateContract.html#projectRoleId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateContract\"},{\"id\":189,\"kind\":1024,\"name\":\"information\",\"url\":\"interfaces/orm_functions_orm_types.CreateContract.html#information\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateContract\"},{\"id\":190,\"kind\":1024,\"name\":\"loginUserId\",\"url\":\"interfaces/orm_functions_orm_types.CreateContract.html#loginUserId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateContract\"},{\"id\":191,\"kind\":1024,\"name\":\"contractStatus\",\"url\":\"interfaces/orm_functions_orm_types.CreateContract.html#contractStatus\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateContract\"},{\"id\":192,\"kind\":256,\"name\":\"UpdateContract\",\"url\":\"interfaces/orm_functions_orm_types.UpdateContract.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":193,\"kind\":1024,\"name\":\"contractId\",\"url\":\"interfaces/orm_functions_orm_types.UpdateContract.html#contractId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateContract\"},{\"id\":194,\"kind\":1024,\"name\":\"loginUserId\",\"url\":\"interfaces/orm_functions_orm_types.UpdateContract.html#loginUserId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateContract\"},{\"id\":195,\"kind\":1024,\"name\":\"information\",\"url\":\"interfaces/orm_functions_orm_types.UpdateContract.html#information\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateContract\"},{\"id\":196,\"kind\":1024,\"name\":\"contractStatus\",\"url\":\"interfaces/orm_functions_orm_types.UpdateContract.html#contractStatus\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateContract\"},{\"id\":197,\"kind\":256,\"name\":\"CreateJobApplication\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplication.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":198,\"kind\":1024,\"name\":\"studentId\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplication.html#studentId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateJobApplication\"},{\"id\":199,\"kind\":1024,\"name\":\"responsibilities\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplication.html#responsibilities\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateJobApplication\"},{\"id\":200,\"kind\":1024,\"name\":\"funFact\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplication.html#funFact\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateJobApplication\"},{\"id\":201,\"kind\":1024,\"name\":\"studentVolunteerInfo\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplication.html#studentVolunteerInfo\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateJobApplication\"},{\"id\":202,\"kind\":1024,\"name\":\"studentCoach\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplication.html#studentCoach\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateJobApplication\"},{\"id\":203,\"kind\":1024,\"name\":\"osocId\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplication.html#osocId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateJobApplication\"},{\"id\":204,\"kind\":1024,\"name\":\"edus\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplication.html#edus\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateJobApplication\"},{\"id\":205,\"kind\":1024,\"name\":\"eduLevel\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplication.html#eduLevel\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateJobApplication\"},{\"id\":206,\"kind\":1024,\"name\":\"eduDuration\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplication.html#eduDuration\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateJobApplication\"},{\"id\":207,\"kind\":1024,\"name\":\"eduYear\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplication.html#eduYear\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateJobApplication\"},{\"id\":208,\"kind\":1024,\"name\":\"eduInstitute\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplication.html#eduInstitute\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateJobApplication\"},{\"id\":209,\"kind\":1024,\"name\":\"emailStatus\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplication.html#emailStatus\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateJobApplication\"},{\"id\":210,\"kind\":1024,\"name\":\"created_at\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplication.html#created_at\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateJobApplication\"},{\"id\":211,\"kind\":256,\"name\":\"UpdateOsoc\",\"url\":\"interfaces/orm_functions_orm_types.UpdateOsoc.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":212,\"kind\":1024,\"name\":\"osocId\",\"url\":\"interfaces/orm_functions_orm_types.UpdateOsoc.html#osocId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateOsoc\"},{\"id\":213,\"kind\":1024,\"name\":\"year\",\"url\":\"interfaces/orm_functions_orm_types.UpdateOsoc.html#year\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateOsoc\"},{\"id\":214,\"kind\":256,\"name\":\"CreateProject\",\"url\":\"interfaces/orm_functions_orm_types.CreateProject.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":215,\"kind\":1024,\"name\":\"name\",\"url\":\"interfaces/orm_functions_orm_types.CreateProject.html#name\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateProject\"},{\"id\":216,\"kind\":1024,\"name\":\"osocId\",\"url\":\"interfaces/orm_functions_orm_types.CreateProject.html#osocId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateProject\"},{\"id\":217,\"kind\":1024,\"name\":\"partner\",\"url\":\"interfaces/orm_functions_orm_types.CreateProject.html#partner\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateProject\"},{\"id\":218,\"kind\":1024,\"name\":\"startDate\",\"url\":\"interfaces/orm_functions_orm_types.CreateProject.html#startDate\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateProject\"},{\"id\":219,\"kind\":1024,\"name\":\"endDate\",\"url\":\"interfaces/orm_functions_orm_types.CreateProject.html#endDate\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateProject\"},{\"id\":220,\"kind\":1024,\"name\":\"positions\",\"url\":\"interfaces/orm_functions_orm_types.CreateProject.html#positions\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateProject\"},{\"id\":221,\"kind\":256,\"name\":\"UpdateProject\",\"url\":\"interfaces/orm_functions_orm_types.UpdateProject.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":222,\"kind\":1024,\"name\":\"projectId\",\"url\":\"interfaces/orm_functions_orm_types.UpdateProject.html#projectId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateProject\"},{\"id\":223,\"kind\":1024,\"name\":\"name\",\"url\":\"interfaces/orm_functions_orm_types.UpdateProject.html#name\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateProject\"},{\"id\":224,\"kind\":1024,\"name\":\"osocId\",\"url\":\"interfaces/orm_functions_orm_types.UpdateProject.html#osocId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateProject\"},{\"id\":225,\"kind\":1024,\"name\":\"partner\",\"url\":\"interfaces/orm_functions_orm_types.UpdateProject.html#partner\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateProject\"},{\"id\":226,\"kind\":1024,\"name\":\"startDate\",\"url\":\"interfaces/orm_functions_orm_types.UpdateProject.html#startDate\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateProject\"},{\"id\":227,\"kind\":1024,\"name\":\"endDate\",\"url\":\"interfaces/orm_functions_orm_types.UpdateProject.html#endDate\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateProject\"},{\"id\":228,\"kind\":1024,\"name\":\"positions\",\"url\":\"interfaces/orm_functions_orm_types.UpdateProject.html#positions\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateProject\"},{\"id\":229,\"kind\":256,\"name\":\"CreateProjectRole\",\"url\":\"interfaces/orm_functions_orm_types.CreateProjectRole.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":230,\"kind\":1024,\"name\":\"projectId\",\"url\":\"interfaces/orm_functions_orm_types.CreateProjectRole.html#projectId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateProjectRole\"},{\"id\":231,\"kind\":1024,\"name\":\"roleId\",\"url\":\"interfaces/orm_functions_orm_types.CreateProjectRole.html#roleId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateProjectRole\"},{\"id\":232,\"kind\":1024,\"name\":\"positions\",\"url\":\"interfaces/orm_functions_orm_types.CreateProjectRole.html#positions\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateProjectRole\"},{\"id\":233,\"kind\":256,\"name\":\"UpdateProjectRole\",\"url\":\"interfaces/orm_functions_orm_types.UpdateProjectRole.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":234,\"kind\":1024,\"name\":\"projectRoleId\",\"url\":\"interfaces/orm_functions_orm_types.UpdateProjectRole.html#projectRoleId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateProjectRole\"},{\"id\":235,\"kind\":1024,\"name\":\"projectId\",\"url\":\"interfaces/orm_functions_orm_types.UpdateProjectRole.html#projectId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateProjectRole\"},{\"id\":236,\"kind\":1024,\"name\":\"roleId\",\"url\":\"interfaces/orm_functions_orm_types.UpdateProjectRole.html#roleId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateProjectRole\"},{\"id\":237,\"kind\":1024,\"name\":\"positions\",\"url\":\"interfaces/orm_functions_orm_types.UpdateProjectRole.html#positions\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateProjectRole\"},{\"id\":238,\"kind\":256,\"name\":\"UpdateRole\",\"url\":\"interfaces/orm_functions_orm_types.UpdateRole.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":239,\"kind\":1024,\"name\":\"roleId\",\"url\":\"interfaces/orm_functions_orm_types.UpdateRole.html#roleId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateRole\"},{\"id\":240,\"kind\":1024,\"name\":\"name\",\"url\":\"interfaces/orm_functions_orm_types.UpdateRole.html#name\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateRole\"},{\"id\":241,\"kind\":256,\"name\":\"UpdateLanguage\",\"url\":\"interfaces/orm_functions_orm_types.UpdateLanguage.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":242,\"kind\":1024,\"name\":\"languageId\",\"url\":\"interfaces/orm_functions_orm_types.UpdateLanguage.html#languageId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateLanguage\"},{\"id\":243,\"kind\":1024,\"name\":\"name\",\"url\":\"interfaces/orm_functions_orm_types.UpdateLanguage.html#name\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateLanguage\"},{\"id\":244,\"kind\":256,\"name\":\"CreateJobApplicationSkill\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplicationSkill.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":245,\"kind\":1024,\"name\":\"jobApplicationId\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplicationSkill.html#jobApplicationId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateJobApplicationSkill\"},{\"id\":246,\"kind\":1024,\"name\":\"skill\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplicationSkill.html#skill\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateJobApplicationSkill\"},{\"id\":247,\"kind\":1024,\"name\":\"languageId\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplicationSkill.html#languageId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateJobApplicationSkill\"},{\"id\":248,\"kind\":1024,\"name\":\"level\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplicationSkill.html#level\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateJobApplicationSkill\"},{\"id\":249,\"kind\":1024,\"name\":\"isPreferred\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplicationSkill.html#isPreferred\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateJobApplicationSkill\"},{\"id\":250,\"kind\":1024,\"name\":\"isBest\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplicationSkill.html#isBest\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateJobApplicationSkill\"},{\"id\":251,\"kind\":256,\"name\":\"UpdateJobApplicationSkill\",\"url\":\"interfaces/orm_functions_orm_types.UpdateJobApplicationSkill.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":252,\"kind\":1024,\"name\":\"JobApplicationSkillId\",\"url\":\"interfaces/orm_functions_orm_types.UpdateJobApplicationSkill.html#JobApplicationSkillId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateJobApplicationSkill\"},{\"id\":253,\"kind\":1024,\"name\":\"JobApplicationId\",\"url\":\"interfaces/orm_functions_orm_types.UpdateJobApplicationSkill.html#JobApplicationId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateJobApplicationSkill\"},{\"id\":254,\"kind\":1024,\"name\":\"skill\",\"url\":\"interfaces/orm_functions_orm_types.UpdateJobApplicationSkill.html#skill\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateJobApplicationSkill\"},{\"id\":255,\"kind\":1024,\"name\":\"languageId\",\"url\":\"interfaces/orm_functions_orm_types.UpdateJobApplicationSkill.html#languageId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateJobApplicationSkill\"},{\"id\":256,\"kind\":1024,\"name\":\"level\",\"url\":\"interfaces/orm_functions_orm_types.UpdateJobApplicationSkill.html#level\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateJobApplicationSkill\"},{\"id\":257,\"kind\":1024,\"name\":\"isPreferred\",\"url\":\"interfaces/orm_functions_orm_types.UpdateJobApplicationSkill.html#isPreferred\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateJobApplicationSkill\"},{\"id\":258,\"kind\":1024,\"name\":\"is_best\",\"url\":\"interfaces/orm_functions_orm_types.UpdateJobApplicationSkill.html#is_best\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateJobApplicationSkill\"},{\"id\":259,\"kind\":256,\"name\":\"AddStudentToProject\",\"url\":\"interfaces/orm_functions_orm_types.AddStudentToProject.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":260,\"kind\":1024,\"name\":\"loginUserId\",\"url\":\"interfaces/orm_functions_orm_types.AddStudentToProject.html#loginUserId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.AddStudentToProject\"},{\"id\":261,\"kind\":1024,\"name\":\"studentId\",\"url\":\"interfaces/orm_functions_orm_types.AddStudentToProject.html#studentId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.AddStudentToProject\"},{\"id\":262,\"kind\":1024,\"name\":\"projectId\",\"url\":\"interfaces/orm_functions_orm_types.AddStudentToProject.html#projectId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.AddStudentToProject\"},{\"id\":263,\"kind\":1024,\"name\":\"roleName\",\"url\":\"interfaces/orm_functions_orm_types.AddStudentToProject.html#roleName\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.AddStudentToProject\"},{\"id\":264,\"kind\":1024,\"name\":\"information\",\"url\":\"interfaces/orm_functions_orm_types.AddStudentToProject.html#information\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.AddStudentToProject\"},{\"id\":265,\"kind\":256,\"name\":\"CreateProjectUser\",\"url\":\"interfaces/orm_functions_orm_types.CreateProjectUser.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":266,\"kind\":1024,\"name\":\"projectId\",\"url\":\"interfaces/orm_functions_orm_types.CreateProjectUser.html#projectId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateProjectUser\"},{\"id\":267,\"kind\":1024,\"name\":\"loginUserId\",\"url\":\"interfaces/orm_functions_orm_types.CreateProjectUser.html#loginUserId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateProjectUser\"},{\"id\":268,\"kind\":256,\"name\":\"CreateAppliedRole\",\"url\":\"interfaces/orm_functions_orm_types.CreateAppliedRole.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":269,\"kind\":1024,\"name\":\"jobApplicationId\",\"url\":\"interfaces/orm_functions_orm_types.CreateAppliedRole.html#jobApplicationId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateAppliedRole\"},{\"id\":270,\"kind\":1024,\"name\":\"roleId\",\"url\":\"interfaces/orm_functions_orm_types.CreateAppliedRole.html#roleId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateAppliedRole\"},{\"id\":271,\"kind\":2,\"name\":\"orm_functions/project_user\",\"url\":\"modules/orm_functions_project_user.html\",\"classes\":\"tsd-kind-module\"},{\"id\":272,\"kind\":64,\"name\":\"createProjectUser\",\"url\":\"modules/orm_functions_project_user.html#createProjectUser\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project_user\"}],\"index\":{\"version\":\"2.3.9\",\"fields\":[\"name\",\"parent\"],\"fieldVectors\":[[\"name/0\",[0,46.968]],[\"parent/0\",[]],[\"name/1\",[1,37.413]],[\"parent/1\",[0,4.519]],[\"name/2\",[2,46.968]],[\"parent/2\",[]],[\"name/3\",[1,37.413]],[\"parent/3\",[2,4.519]],[\"name/4\",[3,46.968]],[\"parent/4\",[]],[\"name/5\",[1,37.413]],[\"parent/5\",[3,4.519]],[\"name/6\",[4,46.968]],[\"parent/6\",[]],[\"name/7\",[1,37.413]],[\"parent/7\",[4,4.519]],[\"name/8\",[5,46.968]],[\"parent/8\",[]],[\"name/9\",[1,37.413]],[\"parent/9\",[5,4.519]],[\"name/10\",[6,46.968]],[\"parent/10\",[]],[\"name/11\",[1,37.413]],[\"parent/11\",[6,4.519]],[\"name/12\",[7,43.604]],[\"parent/12\",[]],[\"name/13\",[8,46.968]],[\"parent/13\",[7,4.195]],[\"name/14\",[9,52.077]],[\"parent/14\",[7,4.195]],[\"name/15\",[10,30.874]],[\"parent/15\",[]],[\"name/16\",[11,52.077]],[\"parent/16\",[10,2.97]],[\"name/17\",[12,52.077]],[\"parent/17\",[10,2.97]],[\"name/18\",[13,52.077]],[\"parent/18\",[10,2.97]],[\"name/19\",[14,52.077]],[\"parent/19\",[10,2.97]],[\"name/20\",[15,52.077]],[\"parent/20\",[10,2.97]],[\"name/21\",[16,52.077]],[\"parent/21\",[10,2.97]],[\"name/22\",[17,52.077]],[\"parent/22\",[10,2.97]],[\"name/23\",[18,46.968]],[\"parent/23\",[10,2.97]],[\"name/24\",[19,52.077]],[\"parent/24\",[10,2.97]],[\"name/25\",[20,52.077]],[\"parent/25\",[10,2.97]],[\"name/26\",[21,52.077]],[\"parent/26\",[10,2.97]],[\"name/27\",[22,33.618]],[\"parent/27\",[]],[\"name/28\",[23,52.077]],[\"parent/28\",[22,3.234]],[\"name/29\",[24,52.077]],[\"parent/29\",[22,3.234]],[\"name/30\",[25,52.077]],[\"parent/30\",[22,3.234]],[\"name/31\",[26,52.077]],[\"parent/31\",[22,3.234]],[\"name/32\",[27,52.077]],[\"parent/32\",[22,3.234]],[\"name/33\",[28,46.968]],[\"parent/33\",[22,3.234]],[\"name/34\",[29,52.077]],[\"parent/34\",[22,3.234]],[\"name/35\",[30,52.077]],[\"parent/35\",[22,3.234]],[\"name/36\",[31,33.618]],[\"parent/36\",[]],[\"name/37\",[32,43.604]],[\"parent/37\",[31,3.234]],[\"name/38\",[33,52.077]],[\"parent/38\",[31,3.234]],[\"name/39\",[34,52.077]],[\"parent/39\",[31,3.234]],[\"name/40\",[35,52.077]],[\"parent/40\",[31,3.234]],[\"name/41\",[36,52.077]],[\"parent/41\",[31,3.234]],[\"name/42\",[37,46.968]],[\"parent/42\",[31,3.234]],[\"name/43\",[38,52.077]],[\"parent/43\",[31,3.234]],[\"name/44\",[39,52.077]],[\"parent/44\",[31,3.234]],[\"name/45\",[40,39.084]],[\"parent/45\",[]],[\"name/46\",[41,52.077]],[\"parent/46\",[40,3.76]],[\"name/47\",[42,52.077]],[\"parent/47\",[40,3.76]],[\"name/48\",[43,52.077]],[\"parent/48\",[40,3.76]],[\"name/49\",[44,52.077]],[\"parent/49\",[40,3.76]],[\"name/50\",[45,35.982]],[\"parent/50\",[]],[\"name/51\",[46,46.968]],[\"parent/51\",[45,3.462]],[\"name/52\",[47,52.077]],[\"parent/52\",[45,3.462]],[\"name/53\",[48,52.077]],[\"parent/53\",[45,3.462]],[\"name/54\",[49,52.077]],[\"parent/54\",[45,3.462]],[\"name/55\",[50,46.968]],[\"parent/55\",[45,3.462]],[\"name/56\",[51,52.077]],[\"parent/56\",[45,3.462]],[\"name/57\",[52,34.731]],[\"parent/57\",[]],[\"name/58\",[53,46.968]],[\"parent/58\",[52,3.341]],[\"name/59\",[54,52.077]],[\"parent/59\",[52,3.341]],[\"name/60\",[55,52.077]],[\"parent/60\",[52,3.341]],[\"name/61\",[56,52.077]],[\"parent/61\",[52,3.341]],[\"name/62\",[57,52.077]],[\"parent/62\",[52,3.341]],[\"name/63\",[58,46.968]],[\"parent/63\",[52,3.341]],[\"name/64\",[59,52.077]],[\"parent/64\",[52,3.341]],[\"name/65\",[60,39.084]],[\"parent/65\",[]],[\"name/66\",[61,52.077]],[\"parent/66\",[60,3.76]],[\"name/67\",[62,52.077]],[\"parent/67\",[60,3.76]],[\"name/68\",[63,52.077]],[\"parent/68\",[60,3.76]],[\"name/69\",[64,52.077]],[\"parent/69\",[60,3.76]],[\"name/70\",[65,39.084]],[\"parent/70\",[]],[\"name/71\",[66,46.968]],[\"parent/71\",[65,3.76]],[\"name/72\",[67,46.968]],[\"parent/72\",[65,3.76]],[\"name/73\",[68,52.077]],[\"parent/73\",[65,3.76]],[\"name/74\",[69,52.077]],[\"parent/74\",[65,3.76]],[\"name/75\",[70,34.731]],[\"parent/75\",[]],[\"name/76\",[71,52.077]],[\"parent/76\",[70,3.341]],[\"name/77\",[72,52.077]],[\"parent/77\",[70,3.341]],[\"name/78\",[73,52.077]],[\"parent/78\",[70,3.341]],[\"name/79\",[74,52.077]],[\"parent/79\",[70,3.341]],[\"name/80\",[75,46.968]],[\"parent/80\",[70,3.341]],[\"name/81\",[76,52.077]],[\"parent/81\",[70,3.341]],[\"name/82\",[77,52.077]],[\"parent/82\",[70,3.341]],[\"name/83\",[78,26.427]],[\"parent/83\",[]],[\"name/84\",[79,46.968]],[\"parent/84\",[78,2.542]],[\"name/85\",[80,52.077]],[\"parent/85\",[78,2.542]],[\"name/86\",[81,52.077]],[\"parent/86\",[78,2.542]],[\"name/87\",[82,52.077]],[\"parent/87\",[78,2.542]],[\"name/88\",[83,52.077]],[\"parent/88\",[78,2.542]],[\"name/89\",[84,52.077]],[\"parent/89\",[78,2.542]],[\"name/90\",[85,52.077]],[\"parent/90\",[78,2.542]],[\"name/91\",[86,52.077]],[\"parent/91\",[78,2.542]],[\"name/92\",[87,52.077]],[\"parent/92\",[78,2.542]],[\"name/93\",[88,52.077]],[\"parent/93\",[78,2.542]],[\"name/94\",[89,52.077]],[\"parent/94\",[78,2.542]],[\"name/95\",[90,52.077]],[\"parent/95\",[78,2.542]],[\"name/96\",[91,52.077]],[\"parent/96\",[78,2.542]],[\"name/97\",[92,52.077]],[\"parent/97\",[78,2.542]],[\"name/98\",[93,46.968]],[\"parent/98\",[78,2.542]],[\"name/99\",[94,52.077]],[\"parent/99\",[78,2.542]],[\"name/100\",[95,52.077]],[\"parent/100\",[78,2.542]],[\"name/101\",[96,52.077]],[\"parent/101\",[78,2.542]],[\"name/102\",[97,35.982]],[\"parent/102\",[]],[\"name/103\",[98,46.968]],[\"parent/103\",[97,3.462]],[\"name/104\",[99,52.077]],[\"parent/104\",[97,3.462]],[\"name/105\",[100,52.077]],[\"parent/105\",[97,3.462]],[\"name/106\",[101,46.968]],[\"parent/106\",[97,3.462]],[\"name/107\",[102,52.077]],[\"parent/107\",[97,3.462]],[\"name/108\",[103,52.077]],[\"parent/108\",[97,3.462]],[\"name/109\",[104,39.084]],[\"parent/109\",[]],[\"name/110\",[105,52.077]],[\"parent/110\",[104,3.76]],[\"name/111\",[106,46.968]],[\"parent/111\",[104,3.76]],[\"name/112\",[107,46.968]],[\"parent/112\",[104,3.76]],[\"name/113\",[108,52.077]],[\"parent/113\",[104,3.76]],[\"name/114\",[109,30.104]],[\"parent/114\",[]],[\"name/115\",[110,46.968]],[\"parent/115\",[109,2.896]],[\"name/116\",[111,52.077]],[\"parent/116\",[109,2.896]],[\"name/117\",[112,52.077]],[\"parent/117\",[109,2.896]],[\"name/118\",[113,52.077]],[\"parent/118\",[109,2.896]],[\"name/119\",[114,52.077]],[\"parent/119\",[109,2.896]],[\"name/120\",[115,52.077]],[\"parent/120\",[109,2.896]],[\"name/121\",[116,52.077]],[\"parent/121\",[109,2.896]],[\"name/122\",[117,52.077]],[\"parent/122\",[109,2.896]],[\"name/123\",[118,46.968]],[\"parent/123\",[109,2.896]],[\"name/124\",[119,52.077]],[\"parent/124\",[109,2.896]],[\"name/125\",[120,52.077]],[\"parent/125\",[109,2.896]],[\"name/126\",[121,52.077]],[\"parent/126\",[109,2.896]],[\"name/127\",[122,34.731]],[\"parent/127\",[]],[\"name/128\",[32,43.604]],[\"parent/128\",[122,3.341]],[\"name/129\",[123,52.077]],[\"parent/129\",[122,3.341]],[\"name/130\",[124,52.077]],[\"parent/130\",[122,3.341]],[\"name/131\",[125,52.077]],[\"parent/131\",[122,3.341]],[\"name/132\",[126,46.968]],[\"parent/132\",[122,3.341]],[\"name/133\",[127,52.077]],[\"parent/133\",[122,3.341]],[\"name/134\",[128,52.077]],[\"parent/134\",[122,3.341]],[\"name/135\",[129,46.968]],[\"parent/135\",[]],[\"name/136\",[130,46.968]],[\"parent/136\",[129,4.519]],[\"name/137\",[131,24.145]],[\"parent/137\",[]],[\"name/138\",[53,46.968]],[\"parent/138\",[131,2.323]],[\"name/139\",[132,46.968]],[\"parent/139\",[133,3.953]],[\"name/140\",[134,46.968]],[\"parent/140\",[133,3.953]],[\"name/141\",[135,46.968]],[\"parent/141\",[133,3.953]],[\"name/142\",[136,46.968]],[\"parent/142\",[133,3.953]],[\"name/143\",[58,46.968]],[\"parent/143\",[131,2.323]],[\"name/144\",[137,43.604]],[\"parent/144\",[138,3.76]],[\"name/145\",[132,46.968]],[\"parent/145\",[138,3.76]],[\"name/146\",[134,46.968]],[\"parent/146\",[138,3.76]],[\"name/147\",[135,46.968]],[\"parent/147\",[138,3.76]],[\"name/148\",[136,46.968]],[\"parent/148\",[138,3.76]],[\"name/149\",[110,46.968]],[\"parent/149\",[131,2.323]],[\"name/150\",[137,43.604]],[\"parent/150\",[139,3.76]],[\"name/151\",[140,46.968]],[\"parent/151\",[139,3.76]],[\"name/152\",[141,46.968]],[\"parent/152\",[139,3.76]],[\"name/153\",[142,46.968]],[\"parent/153\",[139,3.76]],[\"name/154\",[143,46.968]],[\"parent/154\",[139,3.76]],[\"name/155\",[118,46.968]],[\"parent/155\",[131,2.323]],[\"name/156\",[144,35.982]],[\"parent/156\",[145,3.76]],[\"name/157\",[140,46.968]],[\"parent/157\",[145,3.76]],[\"name/158\",[141,46.968]],[\"parent/158\",[145,3.76]],[\"name/159\",[142,46.968]],[\"parent/159\",[145,3.76]],[\"name/160\",[143,46.968]],[\"parent/160\",[145,3.76]],[\"name/161\",[98,46.968]],[\"parent/161\",[131,2.323]],[\"name/162\",[137,43.604]],[\"parent/162\",[146,3.599]],[\"name/163\",[147,46.968]],[\"parent/163\",[146,3.599]],[\"name/164\",[148,46.968]],[\"parent/164\",[146,3.599]],[\"name/165\",[149,46.968]],[\"parent/165\",[146,3.599]],[\"name/166\",[150,46.968]],[\"parent/166\",[146,3.599]],[\"name/167\",[151,46.968]],[\"parent/167\",[146,3.599]],[\"name/168\",[101,46.968]],[\"parent/168\",[131,2.323]],[\"name/169\",[152,41.091]],[\"parent/169\",[153,3.599]],[\"name/170\",[147,46.968]],[\"parent/170\",[153,3.599]],[\"name/171\",[148,46.968]],[\"parent/171\",[153,3.599]],[\"name/172\",[149,46.968]],[\"parent/172\",[153,3.599]],[\"name/173\",[150,46.968]],[\"parent/173\",[153,3.599]],[\"name/174\",[151,46.968]],[\"parent/174\",[153,3.599]],[\"name/175\",[106,46.968]],[\"parent/175\",[131,2.323]],[\"name/176\",[144,35.982]],[\"parent/176\",[154,3.76]],[\"name/177\",[155,41.091]],[\"parent/177\",[154,3.76]],[\"name/178\",[156,46.968]],[\"parent/178\",[154,3.76]],[\"name/179\",[157,46.968]],[\"parent/179\",[154,3.76]],[\"name/180\",[158,52.077]],[\"parent/180\",[154,3.76]],[\"name/181\",[107,46.968]],[\"parent/181\",[131,2.323]],[\"name/182\",[159,52.077]],[\"parent/182\",[160,3.953]],[\"name/183\",[144,35.982]],[\"parent/183\",[160,3.953]],[\"name/184\",[156,46.968]],[\"parent/184\",[160,3.953]],[\"name/185\",[157,46.968]],[\"parent/185\",[160,3.953]],[\"name/186\",[66,46.968]],[\"parent/186\",[131,2.323]],[\"name/187\",[152,41.091]],[\"parent/187\",[161,3.76]],[\"name/188\",[162,46.968]],[\"parent/188\",[161,3.76]],[\"name/189\",[163,43.604]],[\"parent/189\",[161,3.76]],[\"name/190\",[144,35.982]],[\"parent/190\",[161,3.76]],[\"name/191\",[164,46.968]],[\"parent/191\",[161,3.76]],[\"name/192\",[67,46.968]],[\"parent/192\",[131,2.323]],[\"name/193\",[165,52.077]],[\"parent/193\",[166,3.953]],[\"name/194\",[144,35.982]],[\"parent/194\",[166,3.953]],[\"name/195\",[163,43.604]],[\"parent/195\",[166,3.953]],[\"name/196\",[164,46.968]],[\"parent/196\",[166,3.953]],[\"name/197\",[18,46.968]],[\"parent/197\",[131,2.323]],[\"name/198\",[152,41.091]],[\"parent/198\",[167,2.896]],[\"name/199\",[168,52.077]],[\"parent/199\",[167,2.896]],[\"name/200\",[169,52.077]],[\"parent/200\",[167,2.896]],[\"name/201\",[170,52.077]],[\"parent/201\",[167,2.896]],[\"name/202\",[171,52.077]],[\"parent/202\",[167,2.896]],[\"name/203\",[172,41.091]],[\"parent/203\",[167,2.896]],[\"name/204\",[173,52.077]],[\"parent/204\",[167,2.896]],[\"name/205\",[174,52.077]],[\"parent/205\",[167,2.896]],[\"name/206\",[175,52.077]],[\"parent/206\",[167,2.896]],[\"name/207\",[176,52.077]],[\"parent/207\",[167,2.896]],[\"name/208\",[177,52.077]],[\"parent/208\",[167,2.896]],[\"name/209\",[178,52.077]],[\"parent/209\",[167,2.896]],[\"name/210\",[179,52.077]],[\"parent/210\",[167,2.896]],[\"name/211\",[28,46.968]],[\"parent/211\",[131,2.323]],[\"name/212\",[172,41.091]],[\"parent/212\",[180,4.519]],[\"name/213\",[181,52.077]],[\"parent/213\",[180,4.519]],[\"name/214\",[79,46.968]],[\"parent/214\",[131,2.323]],[\"name/215\",[182,41.091]],[\"parent/215\",[183,3.599]],[\"name/216\",[172,41.091]],[\"parent/216\",[183,3.599]],[\"name/217\",[184,46.968]],[\"parent/217\",[183,3.599]],[\"name/218\",[185,46.968]],[\"parent/218\",[183,3.599]],[\"name/219\",[186,46.968]],[\"parent/219\",[183,3.599]],[\"name/220\",[187,41.091]],[\"parent/220\",[183,3.599]],[\"name/221\",[93,46.968]],[\"parent/221\",[131,2.323]],[\"name/222\",[188,39.084]],[\"parent/222\",[189,3.462]],[\"name/223\",[182,41.091]],[\"parent/223\",[189,3.462]],[\"name/224\",[172,41.091]],[\"parent/224\",[189,3.462]],[\"name/225\",[184,46.968]],[\"parent/225\",[189,3.462]],[\"name/226\",[185,46.968]],[\"parent/226\",[189,3.462]],[\"name/227\",[186,46.968]],[\"parent/227\",[189,3.462]],[\"name/228\",[187,41.091]],[\"parent/228\",[189,3.462]],[\"name/229\",[32,43.604]],[\"parent/229\",[131,2.323]],[\"name/230\",[188,39.084]],[\"parent/230\",[190,4.195]],[\"name/231\",[191,41.091]],[\"parent/231\",[190,4.195]],[\"name/232\",[187,41.091]],[\"parent/232\",[190,4.195]],[\"name/233\",[126,46.968]],[\"parent/233\",[131,2.323]],[\"name/234\",[162,46.968]],[\"parent/234\",[192,3.953]],[\"name/235\",[188,39.084]],[\"parent/235\",[192,3.953]],[\"name/236\",[191,41.091]],[\"parent/236\",[192,3.953]],[\"name/237\",[187,41.091]],[\"parent/237\",[192,3.953]],[\"name/238\",[37,46.968]],[\"parent/238\",[131,2.323]],[\"name/239\",[191,41.091]],[\"parent/239\",[193,4.519]],[\"name/240\",[182,41.091]],[\"parent/240\",[193,4.519]],[\"name/241\",[75,46.968]],[\"parent/241\",[131,2.323]],[\"name/242\",[194,43.604]],[\"parent/242\",[195,4.519]],[\"name/243\",[182,41.091]],[\"parent/243\",[195,4.519]],[\"name/244\",[46,46.968]],[\"parent/244\",[131,2.323]],[\"name/245\",[155,41.091]],[\"parent/245\",[196,3.599]],[\"name/246\",[197,46.968]],[\"parent/246\",[196,3.599]],[\"name/247\",[194,43.604]],[\"parent/247\",[196,3.599]],[\"name/248\",[198,46.968]],[\"parent/248\",[196,3.599]],[\"name/249\",[199,46.968]],[\"parent/249\",[196,3.599]],[\"name/250\",[200,52.077]],[\"parent/250\",[196,3.599]],[\"name/251\",[50,46.968]],[\"parent/251\",[131,2.323]],[\"name/252\",[201,52.077]],[\"parent/252\",[202,3.462]],[\"name/253\",[155,41.091]],[\"parent/253\",[202,3.462]],[\"name/254\",[197,46.968]],[\"parent/254\",[202,3.462]],[\"name/255\",[194,43.604]],[\"parent/255\",[202,3.462]],[\"name/256\",[198,46.968]],[\"parent/256\",[202,3.462]],[\"name/257\",[199,46.968]],[\"parent/257\",[202,3.462]],[\"name/258\",[203,52.077]],[\"parent/258\",[202,3.462]],[\"name/259\",[130,46.968]],[\"parent/259\",[131,2.323]],[\"name/260\",[144,35.982]],[\"parent/260\",[204,3.76]],[\"name/261\",[152,41.091]],[\"parent/261\",[204,3.76]],[\"name/262\",[188,39.084]],[\"parent/262\",[204,3.76]],[\"name/263\",[205,52.077]],[\"parent/263\",[204,3.76]],[\"name/264\",[163,43.604]],[\"parent/264\",[204,3.76]],[\"name/265\",[206,46.968]],[\"parent/265\",[131,2.323]],[\"name/266\",[188,39.084]],[\"parent/266\",[207,4.519]],[\"name/267\",[144,35.982]],[\"parent/267\",[207,4.519]],[\"name/268\",[8,46.968]],[\"parent/268\",[131,2.323]],[\"name/269\",[155,41.091]],[\"parent/269\",[208,4.519]],[\"name/270\",[191,41.091]],[\"parent/270\",[208,4.519]],[\"name/271\",[209,46.968]],[\"parent/271\",[]],[\"name/272\",[206,46.968]],[\"parent/272\",[209,4.519]]],\"invertedIndex\":[[\"accountstatus\",{\"_index\":143,\"name\":{\"154\":{},\"160\":{}},\"parent\":{}}],[\"addsessionkey\",{\"_index\":61,\"name\":{\"66\":{}},\"parent\":{}}],[\"addstudenttoproject\",{\"_index\":130,\"name\":{\"136\":{},\"259\":{}},\"parent\":{}}],[\"alumni\",{\"_index\":151,\"name\":{\"167\":{},\"174\":{}},\"parent\":{}}],[\"changeemailstatusofjobapplication\",{\"_index\":16,\"name\":{\"21\":{}},\"parent\":{}}],[\"changesessionkey\",{\"_index\":63,\"name\":{\"68\":{}},\"parent\":{}}],[\"checkiffinalevaluationexists\",{\"_index\":105,\"name\":{\"110\":{}},\"parent\":{}}],[\"checksessionkey\",{\"_index\":62,\"name\":{\"67\":{}},\"parent\":{}}],[\"contractid\",{\"_index\":165,\"name\":{\"193\":{}},\"parent\":{}}],[\"contractstatus\",{\"_index\":164,\"name\":{\"191\":{},\"196\":{}},\"parent\":{}}],[\"createappliedrole\",{\"_index\":8,\"name\":{\"13\":{},\"268\":{}},\"parent\":{}}],[\"createattachment\",{\"_index\":41,\"name\":{\"46\":{}},\"parent\":{}}],[\"createcontract\",{\"_index\":66,\"name\":{\"71\":{},\"186\":{}},\"parent\":{}}],[\"created_at\",{\"_index\":179,\"name\":{\"210\":{}},\"parent\":{}}],[\"createevaluationforstudent\",{\"_index\":106,\"name\":{\"111\":{},\"175\":{}},\"parent\":{}}],[\"createjobapplication\",{\"_index\":18,\"name\":{\"23\":{},\"197\":{}},\"parent\":{}}],[\"createjobapplicationskill\",{\"_index\":46,\"name\":{\"51\":{},\"244\":{}},\"parent\":{}}],[\"createlanguage\",{\"_index\":71,\"name\":{\"76\":{}},\"parent\":{}}],[\"createloginuser\",{\"_index\":110,\"name\":{\"115\":{},\"149\":{}},\"parent\":{}}],[\"createosoc\",{\"_index\":23,\"name\":{\"28\":{}},\"parent\":{}}],[\"createperson\",{\"_index\":53,\"name\":{\"58\":{},\"138\":{}},\"parent\":{}}],[\"createproject\",{\"_index\":79,\"name\":{\"84\":{},\"214\":{}},\"parent\":{}}],[\"createprojectrole\",{\"_index\":32,\"name\":{\"37\":{},\"128\":{},\"229\":{}},\"parent\":{}}],[\"createprojectuser\",{\"_index\":206,\"name\":{\"265\":{},\"272\":{}},\"parent\":{}}],[\"createstudent\",{\"_index\":98,\"name\":{\"103\":{},\"161\":{}},\"parent\":{}}],[\"decision\",{\"_index\":156,\"name\":{\"178\":{},\"184\":{}},\"parent\":{}}],[\"deleteallattachmentsforapplication\",{\"_index\":43,\"name\":{\"48\":{}},\"parent\":{}}],[\"deleteattachment\",{\"_index\":42,\"name\":{\"47\":{}},\"parent\":{}}],[\"deletejobapplication\",{\"_index\":17,\"name\":{\"22\":{}},\"parent\":{}}],[\"deletejobapplicationsfromstudent\",{\"_index\":15,\"name\":{\"20\":{}},\"parent\":{}}],[\"deletejobapplicationskill\",{\"_index\":51,\"name\":{\"56\":{}},\"parent\":{}}],[\"deletelanguage\",{\"_index\":76,\"name\":{\"81\":{}},\"parent\":{}}],[\"deletelanguagebyname\",{\"_index\":77,\"name\":{\"82\":{}},\"parent\":{}}],[\"deleteloginuserbyid\",{\"_index\":119,\"name\":{\"124\":{}},\"parent\":{}}],[\"deleteloginuserbypersonid\",{\"_index\":120,\"name\":{\"125\":{}},\"parent\":{}}],[\"deleteosoc\",{\"_index\":29,\"name\":{\"34\":{}},\"parent\":{}}],[\"deleteosocbyyear\",{\"_index\":30,\"name\":{\"35\":{}},\"parent\":{}}],[\"deletepersonbyid\",{\"_index\":59,\"name\":{\"64\":{}},\"parent\":{}}],[\"deleteproject\",{\"_index\":94,\"name\":{\"99\":{}},\"parent\":{}}],[\"deleteprojectbyosocedition\",{\"_index\":95,\"name\":{\"100\":{}},\"parent\":{}}],[\"deleteprojectbypartner\",{\"_index\":96,\"name\":{\"101\":{}},\"parent\":{}}],[\"deleteprojectrole\",{\"_index\":127,\"name\":{\"133\":{}},\"parent\":{}}],[\"deleterole\",{\"_index\":38,\"name\":{\"43\":{}},\"parent\":{}}],[\"deleterolebyname\",{\"_index\":39,\"name\":{\"44\":{}},\"parent\":{}}],[\"deletestudent\",{\"_index\":102,\"name\":{\"107\":{}},\"parent\":{}}],[\"eduduration\",{\"_index\":175,\"name\":{\"206\":{}},\"parent\":{}}],[\"eduinstitute\",{\"_index\":177,\"name\":{\"208\":{}},\"parent\":{}}],[\"edulevel\",{\"_index\":174,\"name\":{\"205\":{}},\"parent\":{}}],[\"edus\",{\"_index\":173,\"name\":{\"204\":{}},\"parent\":{}}],[\"eduyear\",{\"_index\":176,\"name\":{\"207\":{}},\"parent\":{}}],[\"email\",{\"_index\":136,\"name\":{\"142\":{},\"148\":{}},\"parent\":{}}],[\"emailstatus\",{\"_index\":178,\"name\":{\"209\":{}},\"parent\":{}}],[\"enddate\",{\"_index\":186,\"name\":{\"219\":{},\"227\":{}},\"parent\":{}}],[\"evaluation_id\",{\"_index\":159,\"name\":{\"182\":{}},\"parent\":{}}],[\"firstname\",{\"_index\":132,\"name\":{\"139\":{},\"145\":{}},\"parent\":{}}],[\"funfact\",{\"_index\":169,\"name\":{\"200\":{}},\"parent\":{}}],[\"gender\",{\"_index\":147,\"name\":{\"163\":{},\"170\":{}},\"parent\":{}}],[\"getalljobapplicationskill\",{\"_index\":47,\"name\":{\"52\":{}},\"parent\":{}}],[\"getalljobapplicationskillbyjobapplication\",{\"_index\":48,\"name\":{\"53\":{}},\"parent\":{}}],[\"getalllanguages\",{\"_index\":72,\"name\":{\"77\":{}},\"parent\":{}}],[\"getallloginusers\",{\"_index\":111,\"name\":{\"116\":{}},\"parent\":{}}],[\"getallosoc\",{\"_index\":24,\"name\":{\"29\":{}},\"parent\":{}}],[\"getallpersons\",{\"_index\":54,\"name\":{\"59\":{}},\"parent\":{}}],[\"getallprojects\",{\"_index\":80,\"name\":{\"85\":{}},\"parent\":{}}],[\"getallroles\",{\"_index\":33,\"name\":{\"38\":{}},\"parent\":{}}],[\"getallstudents\",{\"_index\":99,\"name\":{\"104\":{}},\"parent\":{}}],[\"getappliedrolesbyjobapplication\",{\"_index\":9,\"name\":{\"14\":{}},\"parent\":{}}],[\"getattachmentbyid\",{\"_index\":44,\"name\":{\"49\":{}},\"parent\":{}}],[\"getjobapplication\",{\"_index\":20,\"name\":{\"25\":{}},\"parent\":{}}],[\"getjobapplicationbyyear\",{\"_index\":21,\"name\":{\"26\":{}},\"parent\":{}}],[\"getjobapplicationskill\",{\"_index\":49,\"name\":{\"54\":{}},\"parent\":{}}],[\"getlanguage\",{\"_index\":73,\"name\":{\"78\":{}},\"parent\":{}}],[\"getlanguagebyname\",{\"_index\":74,\"name\":{\"79\":{}},\"parent\":{}}],[\"getlatestapplicationrolesforstudent\",{\"_index\":14,\"name\":{\"19\":{}},\"parent\":{}}],[\"getlatestjobapplicationofstudent\",{\"_index\":19,\"name\":{\"24\":{}},\"parent\":{}}],[\"getloginuserbyevaluationid\",{\"_index\":108,\"name\":{\"113\":{}},\"parent\":{}}],[\"getloginuserbyid\",{\"_index\":121,\"name\":{\"126\":{}},\"parent\":{}}],[\"getnumberoffreepositions\",{\"_index\":128,\"name\":{\"134\":{}},\"parent\":{}}],[\"getnumberofrolesbyprojectandrole\",{\"_index\":124,\"name\":{\"130\":{}},\"parent\":{}}],[\"getosocafteryear\",{\"_index\":27,\"name\":{\"32\":{}},\"parent\":{}}],[\"getosocbeforeyear\",{\"_index\":26,\"name\":{\"31\":{}},\"parent\":{}}],[\"getosocbyyear\",{\"_index\":25,\"name\":{\"30\":{}},\"parent\":{}}],[\"getpasswordloginuser\",{\"_index\":113,\"name\":{\"118\":{}},\"parent\":{}}],[\"getpasswordloginuserbyperson\",{\"_index\":112,\"name\":{\"117\":{}},\"parent\":{}}],[\"getpasswordpersonbyemail\",{\"_index\":55,\"name\":{\"60\":{}},\"parent\":{}}],[\"getprojectbyname\",{\"_index\":81,\"name\":{\"86\":{}},\"parent\":{}}],[\"getprojectrolenamesbyproject\",{\"_index\":125,\"name\":{\"131\":{}},\"parent\":{}}],[\"getprojectrolesbyproject\",{\"_index\":123,\"name\":{\"129\":{}},\"parent\":{}}],[\"getprojectrolewithrolename\",{\"_index\":36,\"name\":{\"41\":{}},\"parent\":{}}],[\"getprojectsbyenddate\",{\"_index\":87,\"name\":{\"92\":{}},\"parent\":{}}],[\"getprojectsbynumberpositions\",{\"_index\":90,\"name\":{\"95\":{}},\"parent\":{}}],[\"getprojectsbyosocedition\",{\"_index\":82,\"name\":{\"87\":{}},\"parent\":{}}],[\"getprojectsbypartner\",{\"_index\":83,\"name\":{\"88\":{}},\"parent\":{}}],[\"getprojectsbystartdate\",{\"_index\":84,\"name\":{\"89\":{}},\"parent\":{}}],[\"getprojectsendedafterdate\",{\"_index\":88,\"name\":{\"93\":{}},\"parent\":{}}],[\"getprojectsendedbeforedate\",{\"_index\":89,\"name\":{\"94\":{}},\"parent\":{}}],[\"getprojectslesspositions\",{\"_index\":91,\"name\":{\"96\":{}},\"parent\":{}}],[\"getprojectsmorepositions\",{\"_index\":92,\"name\":{\"97\":{}},\"parent\":{}}],[\"getprojectsstartedafterdate\",{\"_index\":85,\"name\":{\"90\":{}},\"parent\":{}}],[\"getprojectsstartedbeforedate\",{\"_index\":86,\"name\":{\"91\":{}},\"parent\":{}}],[\"getrole\",{\"_index\":34,\"name\":{\"39\":{}},\"parent\":{}}],[\"getrolesbyname\",{\"_index\":35,\"name\":{\"40\":{}},\"parent\":{}}],[\"getrouter\",{\"_index\":1,\"name\":{\"1\":{},\"3\":{},\"5\":{},\"7\":{},\"9\":{},\"11\":{}},\"parent\":{}}],[\"getstudent\",{\"_index\":100,\"name\":{\"105\":{}},\"parent\":{}}],[\"getstudentevaluationsfinal\",{\"_index\":12,\"name\":{\"17\":{}},\"parent\":{}}],[\"getstudentevaluationstemp\",{\"_index\":13,\"name\":{\"18\":{}},\"parent\":{}}],[\"getstudentevaluationstotal\",{\"_index\":11,\"name\":{\"16\":{}},\"parent\":{}}],[\"github\",{\"_index\":135,\"name\":{\"141\":{},\"147\":{}},\"parent\":{}}],[\"information\",{\"_index\":163,\"name\":{\"189\":{},\"195\":{},\"264\":{}},\"parent\":{}}],[\"is_best\",{\"_index\":203,\"name\":{\"258\":{}},\"parent\":{}}],[\"isadmin\",{\"_index\":141,\"name\":{\"152\":{},\"158\":{}},\"parent\":{}}],[\"isbest\",{\"_index\":200,\"name\":{\"250\":{}},\"parent\":{}}],[\"iscoach\",{\"_index\":142,\"name\":{\"153\":{},\"159\":{}},\"parent\":{}}],[\"isfinal\",{\"_index\":158,\"name\":{\"180\":{}},\"parent\":{}}],[\"ispreferred\",{\"_index\":199,\"name\":{\"249\":{},\"257\":{}},\"parent\":{}}],[\"jobapplicationid\",{\"_index\":155,\"name\":{\"177\":{},\"245\":{},\"253\":{},\"269\":{}},\"parent\":{}}],[\"jobapplicationskillid\",{\"_index\":201,\"name\":{\"252\":{}},\"parent\":{}}],[\"languageid\",{\"_index\":194,\"name\":{\"242\":{},\"247\":{},\"255\":{}},\"parent\":{}}],[\"lastname\",{\"_index\":134,\"name\":{\"140\":{},\"146\":{}},\"parent\":{}}],[\"level\",{\"_index\":198,\"name\":{\"248\":{},\"256\":{}},\"parent\":{}}],[\"loginuserid\",{\"_index\":144,\"name\":{\"156\":{},\"176\":{},\"183\":{},\"190\":{},\"194\":{},\"260\":{},\"267\":{}},\"parent\":{}}],[\"motivation\",{\"_index\":157,\"name\":{\"179\":{},\"185\":{}},\"parent\":{}}],[\"name\",{\"_index\":182,\"name\":{\"215\":{},\"223\":{},\"240\":{},\"243\":{}},\"parent\":{}}],[\"nickname\",{\"_index\":150,\"name\":{\"166\":{},\"173\":{}},\"parent\":{}}],[\"orm_functions/applied_role\",{\"_index\":7,\"name\":{\"12\":{}},\"parent\":{\"13\":{},\"14\":{}}}],[\"orm_functions/attachment\",{\"_index\":40,\"name\":{\"45\":{}},\"parent\":{\"46\":{},\"47\":{},\"48\":{},\"49\":{}}}],[\"orm_functions/contract\",{\"_index\":65,\"name\":{\"70\":{}},\"parent\":{\"71\":{},\"72\":{},\"73\":{},\"74\":{}}}],[\"orm_functions/evaluation\",{\"_index\":104,\"name\":{\"109\":{}},\"parent\":{\"110\":{},\"111\":{},\"112\":{},\"113\":{}}}],[\"orm_functions/general_purpose\",{\"_index\":129,\"name\":{\"135\":{}},\"parent\":{\"136\":{}}}],[\"orm_functions/job_application\",{\"_index\":10,\"name\":{\"15\":{}},\"parent\":{\"16\":{},\"17\":{},\"18\":{},\"19\":{},\"20\":{},\"21\":{},\"22\":{},\"23\":{},\"24\":{},\"25\":{},\"26\":{}}}],[\"orm_functions/job_application_skill\",{\"_index\":45,\"name\":{\"50\":{}},\"parent\":{\"51\":{},\"52\":{},\"53\":{},\"54\":{},\"55\":{},\"56\":{}}}],[\"orm_functions/language\",{\"_index\":70,\"name\":{\"75\":{}},\"parent\":{\"76\":{},\"77\":{},\"78\":{},\"79\":{},\"80\":{},\"81\":{},\"82\":{}}}],[\"orm_functions/login_user\",{\"_index\":109,\"name\":{\"114\":{}},\"parent\":{\"115\":{},\"116\":{},\"117\":{},\"118\":{},\"119\":{},\"120\":{},\"121\":{},\"122\":{},\"123\":{},\"124\":{},\"125\":{},\"126\":{}}}],[\"orm_functions/orm_types\",{\"_index\":131,\"name\":{\"137\":{}},\"parent\":{\"138\":{},\"143\":{},\"149\":{},\"155\":{},\"161\":{},\"168\":{},\"175\":{},\"181\":{},\"186\":{},\"192\":{},\"197\":{},\"211\":{},\"214\":{},\"221\":{},\"229\":{},\"233\":{},\"238\":{},\"241\":{},\"244\":{},\"251\":{},\"259\":{},\"265\":{},\"268\":{}}}],[\"orm_functions/orm_types.addstudenttoproject\",{\"_index\":204,\"name\":{},\"parent\":{\"260\":{},\"261\":{},\"262\":{},\"263\":{},\"264\":{}}}],[\"orm_functions/orm_types.createappliedrole\",{\"_index\":208,\"name\":{},\"parent\":{\"269\":{},\"270\":{}}}],[\"orm_functions/orm_types.createcontract\",{\"_index\":161,\"name\":{},\"parent\":{\"187\":{},\"188\":{},\"189\":{},\"190\":{},\"191\":{}}}],[\"orm_functions/orm_types.createevaluationforstudent\",{\"_index\":154,\"name\":{},\"parent\":{\"176\":{},\"177\":{},\"178\":{},\"179\":{},\"180\":{}}}],[\"orm_functions/orm_types.createjobapplication\",{\"_index\":167,\"name\":{},\"parent\":{\"198\":{},\"199\":{},\"200\":{},\"201\":{},\"202\":{},\"203\":{},\"204\":{},\"205\":{},\"206\":{},\"207\":{},\"208\":{},\"209\":{},\"210\":{}}}],[\"orm_functions/orm_types.createjobapplicationskill\",{\"_index\":196,\"name\":{},\"parent\":{\"245\":{},\"246\":{},\"247\":{},\"248\":{},\"249\":{},\"250\":{}}}],[\"orm_functions/orm_types.createloginuser\",{\"_index\":139,\"name\":{},\"parent\":{\"150\":{},\"151\":{},\"152\":{},\"153\":{},\"154\":{}}}],[\"orm_functions/orm_types.createperson\",{\"_index\":133,\"name\":{},\"parent\":{\"139\":{},\"140\":{},\"141\":{},\"142\":{}}}],[\"orm_functions/orm_types.createproject\",{\"_index\":183,\"name\":{},\"parent\":{\"215\":{},\"216\":{},\"217\":{},\"218\":{},\"219\":{},\"220\":{}}}],[\"orm_functions/orm_types.createprojectrole\",{\"_index\":190,\"name\":{},\"parent\":{\"230\":{},\"231\":{},\"232\":{}}}],[\"orm_functions/orm_types.createprojectuser\",{\"_index\":207,\"name\":{},\"parent\":{\"266\":{},\"267\":{}}}],[\"orm_functions/orm_types.createstudent\",{\"_index\":146,\"name\":{},\"parent\":{\"162\":{},\"163\":{},\"164\":{},\"165\":{},\"166\":{},\"167\":{}}}],[\"orm_functions/orm_types.updatecontract\",{\"_index\":166,\"name\":{},\"parent\":{\"193\":{},\"194\":{},\"195\":{},\"196\":{}}}],[\"orm_functions/orm_types.updateevaluationforstudent\",{\"_index\":160,\"name\":{},\"parent\":{\"182\":{},\"183\":{},\"184\":{},\"185\":{}}}],[\"orm_functions/orm_types.updatejobapplicationskill\",{\"_index\":202,\"name\":{},\"parent\":{\"252\":{},\"253\":{},\"254\":{},\"255\":{},\"256\":{},\"257\":{},\"258\":{}}}],[\"orm_functions/orm_types.updatelanguage\",{\"_index\":195,\"name\":{},\"parent\":{\"242\":{},\"243\":{}}}],[\"orm_functions/orm_types.updateloginuser\",{\"_index\":145,\"name\":{},\"parent\":{\"156\":{},\"157\":{},\"158\":{},\"159\":{},\"160\":{}}}],[\"orm_functions/orm_types.updateosoc\",{\"_index\":180,\"name\":{},\"parent\":{\"212\":{},\"213\":{}}}],[\"orm_functions/orm_types.updateperson\",{\"_index\":138,\"name\":{},\"parent\":{\"144\":{},\"145\":{},\"146\":{},\"147\":{},\"148\":{}}}],[\"orm_functions/orm_types.updateproject\",{\"_index\":189,\"name\":{},\"parent\":{\"222\":{},\"223\":{},\"224\":{},\"225\":{},\"226\":{},\"227\":{},\"228\":{}}}],[\"orm_functions/orm_types.updateprojectrole\",{\"_index\":192,\"name\":{},\"parent\":{\"234\":{},\"235\":{},\"236\":{},\"237\":{}}}],[\"orm_functions/orm_types.updaterole\",{\"_index\":193,\"name\":{},\"parent\":{\"239\":{},\"240\":{}}}],[\"orm_functions/orm_types.updatestudent\",{\"_index\":153,\"name\":{},\"parent\":{\"169\":{},\"170\":{},\"171\":{},\"172\":{},\"173\":{},\"174\":{}}}],[\"orm_functions/osoc\",{\"_index\":22,\"name\":{\"27\":{}},\"parent\":{\"28\":{},\"29\":{},\"30\":{},\"31\":{},\"32\":{},\"33\":{},\"34\":{},\"35\":{}}}],[\"orm_functions/person\",{\"_index\":52,\"name\":{\"57\":{}},\"parent\":{\"58\":{},\"59\":{},\"60\":{},\"61\":{},\"62\":{},\"63\":{},\"64\":{}}}],[\"orm_functions/project\",{\"_index\":78,\"name\":{\"83\":{}},\"parent\":{\"84\":{},\"85\":{},\"86\":{},\"87\":{},\"88\":{},\"89\":{},\"90\":{},\"91\":{},\"92\":{},\"93\":{},\"94\":{},\"95\":{},\"96\":{},\"97\":{},\"98\":{},\"99\":{},\"100\":{},\"101\":{}}}],[\"orm_functions/project_role\",{\"_index\":122,\"name\":{\"127\":{}},\"parent\":{\"128\":{},\"129\":{},\"130\":{},\"131\":{},\"132\":{},\"133\":{},\"134\":{}}}],[\"orm_functions/project_user\",{\"_index\":209,\"name\":{\"271\":{}},\"parent\":{\"272\":{}}}],[\"orm_functions/role\",{\"_index\":31,\"name\":{\"36\":{}},\"parent\":{\"37\":{},\"38\":{},\"39\":{},\"40\":{},\"41\":{},\"42\":{},\"43\":{},\"44\":{}}}],[\"orm_functions/session_key\",{\"_index\":60,\"name\":{\"65\":{}},\"parent\":{\"66\":{},\"67\":{},\"68\":{},\"69\":{}}}],[\"orm_functions/student\",{\"_index\":97,\"name\":{\"102\":{}},\"parent\":{\"103\":{},\"104\":{},\"105\":{},\"106\":{},\"107\":{},\"108\":{}}}],[\"osocid\",{\"_index\":172,\"name\":{\"203\":{},\"212\":{},\"216\":{},\"224\":{}},\"parent\":{}}],[\"partner\",{\"_index\":184,\"name\":{\"217\":{},\"225\":{}},\"parent\":{}}],[\"password\",{\"_index\":140,\"name\":{\"151\":{},\"157\":{}},\"parent\":{}}],[\"personid\",{\"_index\":137,\"name\":{\"144\":{},\"150\":{},\"162\":{}},\"parent\":{}}],[\"phonenumber\",{\"_index\":149,\"name\":{\"165\":{},\"172\":{}},\"parent\":{}}],[\"positions\",{\"_index\":187,\"name\":{\"220\":{},\"228\":{},\"232\":{},\"237\":{}},\"parent\":{}}],[\"projectid\",{\"_index\":188,\"name\":{\"222\":{},\"230\":{},\"235\":{},\"262\":{},\"266\":{}},\"parent\":{}}],[\"projectroleid\",{\"_index\":162,\"name\":{\"188\":{},\"234\":{}},\"parent\":{}}],[\"pronouns\",{\"_index\":148,\"name\":{\"164\":{},\"171\":{}},\"parent\":{}}],[\"removeallkeysforuser\",{\"_index\":64,\"name\":{\"69\":{}},\"parent\":{}}],[\"removecontract\",{\"_index\":69,\"name\":{\"74\":{}},\"parent\":{}}],[\"removecontractsfromstudent\",{\"_index\":68,\"name\":{\"73\":{}},\"parent\":{}}],[\"responsibilities\",{\"_index\":168,\"name\":{\"199\":{}},\"parent\":{}}],[\"roleid\",{\"_index\":191,\"name\":{\"231\":{},\"236\":{},\"239\":{},\"270\":{}},\"parent\":{}}],[\"rolename\",{\"_index\":205,\"name\":{\"263\":{}},\"parent\":{}}],[\"routes/admin\",{\"_index\":0,\"name\":{\"0\":{}},\"parent\":{\"1\":{}}}],[\"routes/coach\",{\"_index\":2,\"name\":{\"2\":{}},\"parent\":{\"3\":{}}}],[\"routes/form\",{\"_index\":3,\"name\":{\"4\":{}},\"parent\":{\"5\":{}}}],[\"routes/login\",{\"_index\":4,\"name\":{\"6\":{}},\"parent\":{\"7\":{}}}],[\"routes/project\",{\"_index\":5,\"name\":{\"8\":{}},\"parent\":{\"9\":{}}}],[\"routes/student\",{\"_index\":6,\"name\":{\"10\":{}},\"parent\":{\"11\":{}}}],[\"searchalladminandcoachloginusers\",{\"_index\":117,\"name\":{\"122\":{}},\"parent\":{}}],[\"searchalladminloginusers\",{\"_index\":115,\"name\":{\"120\":{}},\"parent\":{}}],[\"searchallcoachloginusers\",{\"_index\":116,\"name\":{\"121\":{}},\"parent\":{}}],[\"searchloginuserbyperson\",{\"_index\":114,\"name\":{\"119\":{}},\"parent\":{}}],[\"searchpersonbylogin\",{\"_index\":57,\"name\":{\"62\":{}},\"parent\":{}}],[\"searchpersonbyname\",{\"_index\":56,\"name\":{\"61\":{}},\"parent\":{}}],[\"searchstudentbygender\",{\"_index\":103,\"name\":{\"108\":{}},\"parent\":{}}],[\"skill\",{\"_index\":197,\"name\":{\"246\":{},\"254\":{}},\"parent\":{}}],[\"startdate\",{\"_index\":185,\"name\":{\"218\":{},\"226\":{}},\"parent\":{}}],[\"studentcoach\",{\"_index\":171,\"name\":{\"202\":{}},\"parent\":{}}],[\"studentid\",{\"_index\":152,\"name\":{\"169\":{},\"187\":{},\"198\":{},\"261\":{}},\"parent\":{}}],[\"studentvolunteerinfo\",{\"_index\":170,\"name\":{\"201\":{}},\"parent\":{}}],[\"updatecontract\",{\"_index\":67,\"name\":{\"72\":{},\"192\":{}},\"parent\":{}}],[\"updateevaluationforstudent\",{\"_index\":107,\"name\":{\"112\":{},\"181\":{}},\"parent\":{}}],[\"updatejobapplicationskill\",{\"_index\":50,\"name\":{\"55\":{},\"251\":{}},\"parent\":{}}],[\"updatelanguage\",{\"_index\":75,\"name\":{\"80\":{},\"241\":{}},\"parent\":{}}],[\"updateloginuser\",{\"_index\":118,\"name\":{\"123\":{},\"155\":{}},\"parent\":{}}],[\"updateosoc\",{\"_index\":28,\"name\":{\"33\":{},\"211\":{}},\"parent\":{}}],[\"updateperson\",{\"_index\":58,\"name\":{\"63\":{},\"143\":{}},\"parent\":{}}],[\"updateproject\",{\"_index\":93,\"name\":{\"98\":{},\"221\":{}},\"parent\":{}}],[\"updateprojectrole\",{\"_index\":126,\"name\":{\"132\":{},\"233\":{}},\"parent\":{}}],[\"updaterole\",{\"_index\":37,\"name\":{\"42\":{},\"238\":{}},\"parent\":{}}],[\"updatestudent\",{\"_index\":101,\"name\":{\"106\":{},\"168\":{}},\"parent\":{}}],[\"year\",{\"_index\":181,\"name\":{\"213\":{}},\"parent\":{}}]],\"pipeline\":[]}}"); \ No newline at end of file +window.searchData = JSON.parse("{\"kinds\":{\"2\":\"Module\",\"32\":\"Variable\",\"64\":\"Function\",\"256\":\"Interface\",\"1024\":\"Property\",\"65536\":\"Type literal\",\"4194304\":\"Type alias\"},\"rows\":[{\"id\":0,\"kind\":2,\"name\":\"orm_functions/applied_role\",\"url\":\"modules/orm_functions_applied_role.html\",\"classes\":\"tsd-kind-module\"},{\"id\":1,\"kind\":64,\"name\":\"createAppliedRole\",\"url\":\"modules/orm_functions_applied_role.html#createAppliedRole\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/applied_role\"},{\"id\":2,\"kind\":64,\"name\":\"getAppliedRolesByJobApplication\",\"url\":\"modules/orm_functions_applied_role.html#getAppliedRolesByJobApplication\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/applied_role\"},{\"id\":3,\"kind\":64,\"name\":\"deleteAppliedRolesByJobApplication\",\"url\":\"modules/orm_functions_applied_role.html#deleteAppliedRolesByJobApplication\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/applied_role\"},{\"id\":4,\"kind\":2,\"name\":\"orm_functions/attachment\",\"url\":\"modules/orm_functions_attachment.html\",\"classes\":\"tsd-kind-module\"},{\"id\":5,\"kind\":64,\"name\":\"createAttachment\",\"url\":\"modules/orm_functions_attachment.html#createAttachment\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/attachment\"},{\"id\":6,\"kind\":64,\"name\":\"deleteAttachment\",\"url\":\"modules/orm_functions_attachment.html#deleteAttachment\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/attachment\"},{\"id\":7,\"kind\":64,\"name\":\"deleteAllAttachmentsForApplication\",\"url\":\"modules/orm_functions_attachment.html#deleteAllAttachmentsForApplication\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/attachment\"},{\"id\":8,\"kind\":64,\"name\":\"getAttachmentById\",\"url\":\"modules/orm_functions_attachment.html#getAttachmentById\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/attachment\"},{\"id\":9,\"kind\":2,\"name\":\"orm_functions/contract\",\"url\":\"modules/orm_functions_contract.html\",\"classes\":\"tsd-kind-module\"},{\"id\":10,\"kind\":64,\"name\":\"createContract\",\"url\":\"modules/orm_functions_contract.html#createContract\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/contract\"},{\"id\":11,\"kind\":64,\"name\":\"updateContract\",\"url\":\"modules/orm_functions_contract.html#updateContract\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/contract\"},{\"id\":12,\"kind\":64,\"name\":\"removeContractsFromStudent\",\"url\":\"modules/orm_functions_contract.html#removeContractsFromStudent\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/contract\"},{\"id\":13,\"kind\":64,\"name\":\"removeContract\",\"url\":\"modules/orm_functions_contract.html#removeContract\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/contract\"},{\"id\":14,\"kind\":64,\"name\":\"contractsForStudent\",\"url\":\"modules/orm_functions_contract.html#contractsForStudent\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/contract\"},{\"id\":15,\"kind\":64,\"name\":\"contractsByProject\",\"url\":\"modules/orm_functions_contract.html#contractsByProject\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/contract\"},{\"id\":16,\"kind\":64,\"name\":\"sortedContractsByOsocEdition\",\"url\":\"modules/orm_functions_contract.html#sortedContractsByOsocEdition\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/contract\"},{\"id\":17,\"kind\":2,\"name\":\"orm_functions/evaluation\",\"url\":\"modules/orm_functions_evaluation.html\",\"classes\":\"tsd-kind-module\"},{\"id\":18,\"kind\":64,\"name\":\"checkIfFinalEvaluationExists\",\"url\":\"modules/orm_functions_evaluation.html#checkIfFinalEvaluationExists\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/evaluation\"},{\"id\":19,\"kind\":64,\"name\":\"createEvaluationForStudent\",\"url\":\"modules/orm_functions_evaluation.html#createEvaluationForStudent\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/evaluation\"},{\"id\":20,\"kind\":64,\"name\":\"updateEvaluationForStudent\",\"url\":\"modules/orm_functions_evaluation.html#updateEvaluationForStudent\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/evaluation\"},{\"id\":21,\"kind\":64,\"name\":\"getLoginUserByEvaluationId\",\"url\":\"modules/orm_functions_evaluation.html#getLoginUserByEvaluationId\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/evaluation\"},{\"id\":22,\"kind\":64,\"name\":\"getEvaluationByPartiesFor\",\"url\":\"modules/orm_functions_evaluation.html#getEvaluationByPartiesFor\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/evaluation\"},{\"id\":23,\"kind\":64,\"name\":\"deleteEvaluationsByJobApplication\",\"url\":\"modules/orm_functions_evaluation.html#deleteEvaluationsByJobApplication\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/evaluation\"},{\"id\":24,\"kind\":2,\"name\":\"orm_functions/general_purpose\",\"url\":\"modules/orm_functions_general_purpose.html\",\"classes\":\"tsd-kind-module\"},{\"id\":25,\"kind\":64,\"name\":\"addStudentToProject\",\"url\":\"modules/orm_functions_general_purpose.html#addStudentToProject\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/general_purpose\"},{\"id\":26,\"kind\":2,\"name\":\"orm_functions/job_application\",\"url\":\"modules/orm_functions_job_application.html\",\"classes\":\"tsd-kind-module\"},{\"id\":27,\"kind\":64,\"name\":\"getStudentEvaluationsTotal\",\"url\":\"modules/orm_functions_job_application.html#getStudentEvaluationsTotal\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/job_application\"},{\"id\":28,\"kind\":64,\"name\":\"getStudentEvaluationsFinal\",\"url\":\"modules/orm_functions_job_application.html#getStudentEvaluationsFinal\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/job_application\"},{\"id\":29,\"kind\":64,\"name\":\"getStudentEvaluationsTemp\",\"url\":\"modules/orm_functions_job_application.html#getStudentEvaluationsTemp\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/job_application\"},{\"id\":30,\"kind\":64,\"name\":\"getLatestApplicationRolesForStudent\",\"url\":\"modules/orm_functions_job_application.html#getLatestApplicationRolesForStudent\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/job_application\"},{\"id\":31,\"kind\":64,\"name\":\"deleteJobApplicationsFromStudent\",\"url\":\"modules/orm_functions_job_application.html#deleteJobApplicationsFromStudent\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/job_application\"},{\"id\":32,\"kind\":64,\"name\":\"changeEmailStatusOfJobApplication\",\"url\":\"modules/orm_functions_job_application.html#changeEmailStatusOfJobApplication\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/job_application\"},{\"id\":33,\"kind\":64,\"name\":\"deleteJobApplication\",\"url\":\"modules/orm_functions_job_application.html#deleteJobApplication\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/job_application\"},{\"id\":34,\"kind\":64,\"name\":\"createJobApplication\",\"url\":\"modules/orm_functions_job_application.html#createJobApplication\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/job_application\"},{\"id\":35,\"kind\":64,\"name\":\"getLatestJobApplicationOfStudent\",\"url\":\"modules/orm_functions_job_application.html#getLatestJobApplicationOfStudent\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/job_application\"},{\"id\":36,\"kind\":64,\"name\":\"getJobApplication\",\"url\":\"modules/orm_functions_job_application.html#getJobApplication\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/job_application\"},{\"id\":37,\"kind\":64,\"name\":\"getJobApplicationByYear\",\"url\":\"modules/orm_functions_job_application.html#getJobApplicationByYear\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/job_application\"},{\"id\":38,\"kind\":2,\"name\":\"orm_functions/job_application_skill\",\"url\":\"modules/orm_functions_job_application_skill.html\",\"classes\":\"tsd-kind-module\"},{\"id\":39,\"kind\":64,\"name\":\"createJobApplicationSkill\",\"url\":\"modules/orm_functions_job_application_skill.html#createJobApplicationSkill\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/job_application_skill\"},{\"id\":40,\"kind\":64,\"name\":\"getAllJobApplicationSkill\",\"url\":\"modules/orm_functions_job_application_skill.html#getAllJobApplicationSkill\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/job_application_skill\"},{\"id\":41,\"kind\":64,\"name\":\"getAllJobApplicationSkillByJobApplication\",\"url\":\"modules/orm_functions_job_application_skill.html#getAllJobApplicationSkillByJobApplication\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/job_application_skill\"},{\"id\":42,\"kind\":64,\"name\":\"getJobApplicationSkill\",\"url\":\"modules/orm_functions_job_application_skill.html#getJobApplicationSkill\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/job_application_skill\"},{\"id\":43,\"kind\":64,\"name\":\"updateJobApplicationSkill\",\"url\":\"modules/orm_functions_job_application_skill.html#updateJobApplicationSkill\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/job_application_skill\"},{\"id\":44,\"kind\":64,\"name\":\"deleteJobApplicationSkill\",\"url\":\"modules/orm_functions_job_application_skill.html#deleteJobApplicationSkill\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/job_application_skill\"},{\"id\":45,\"kind\":64,\"name\":\"deleteSkillsByJobApplicationId\",\"url\":\"modules/orm_functions_job_application_skill.html#deleteSkillsByJobApplicationId\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/job_application_skill\"},{\"id\":46,\"kind\":2,\"name\":\"orm_functions/language\",\"url\":\"modules/orm_functions_language.html\",\"classes\":\"tsd-kind-module\"},{\"id\":47,\"kind\":64,\"name\":\"createLanguage\",\"url\":\"modules/orm_functions_language.html#createLanguage\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/language\"},{\"id\":48,\"kind\":64,\"name\":\"getAllLanguages\",\"url\":\"modules/orm_functions_language.html#getAllLanguages\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/language\"},{\"id\":49,\"kind\":64,\"name\":\"getLanguage\",\"url\":\"modules/orm_functions_language.html#getLanguage\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/language\"},{\"id\":50,\"kind\":64,\"name\":\"getLanguageByName\",\"url\":\"modules/orm_functions_language.html#getLanguageByName\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/language\"},{\"id\":51,\"kind\":64,\"name\":\"updateLanguage\",\"url\":\"modules/orm_functions_language.html#updateLanguage\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/language\"},{\"id\":52,\"kind\":64,\"name\":\"deleteLanguage\",\"url\":\"modules/orm_functions_language.html#deleteLanguage\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/language\"},{\"id\":53,\"kind\":64,\"name\":\"deleteLanguageByName\",\"url\":\"modules/orm_functions_language.html#deleteLanguageByName\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/language\"},{\"id\":54,\"kind\":2,\"name\":\"orm_functions/login_user\",\"url\":\"modules/orm_functions_login_user.html\",\"classes\":\"tsd-kind-module\"},{\"id\":55,\"kind\":64,\"name\":\"createLoginUser\",\"url\":\"modules/orm_functions_login_user.html#createLoginUser\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/login_user\"},{\"id\":56,\"kind\":64,\"name\":\"getAllLoginUsers\",\"url\":\"modules/orm_functions_login_user.html#getAllLoginUsers\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/login_user\"},{\"id\":57,\"kind\":64,\"name\":\"getPasswordLoginUserByPerson\",\"url\":\"modules/orm_functions_login_user.html#getPasswordLoginUserByPerson\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/login_user\"},{\"id\":58,\"kind\":64,\"name\":\"getPasswordLoginUser\",\"url\":\"modules/orm_functions_login_user.html#getPasswordLoginUser\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/login_user\"},{\"id\":59,\"kind\":64,\"name\":\"searchLoginUserByPerson\",\"url\":\"modules/orm_functions_login_user.html#searchLoginUserByPerson\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/login_user\"},{\"id\":60,\"kind\":64,\"name\":\"searchAllAdminLoginUsers\",\"url\":\"modules/orm_functions_login_user.html#searchAllAdminLoginUsers\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/login_user\"},{\"id\":61,\"kind\":64,\"name\":\"searchAllCoachLoginUsers\",\"url\":\"modules/orm_functions_login_user.html#searchAllCoachLoginUsers\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/login_user\"},{\"id\":62,\"kind\":64,\"name\":\"searchAllAdminAndCoachLoginUsers\",\"url\":\"modules/orm_functions_login_user.html#searchAllAdminAndCoachLoginUsers\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/login_user\"},{\"id\":63,\"kind\":64,\"name\":\"updateLoginUser\",\"url\":\"modules/orm_functions_login_user.html#updateLoginUser\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/login_user\"},{\"id\":64,\"kind\":64,\"name\":\"deleteLoginUserById\",\"url\":\"modules/orm_functions_login_user.html#deleteLoginUserById\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/login_user\"},{\"id\":65,\"kind\":64,\"name\":\"deleteLoginUserByPersonId\",\"url\":\"modules/orm_functions_login_user.html#deleteLoginUserByPersonId\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/login_user\"},{\"id\":66,\"kind\":64,\"name\":\"getLoginUserById\",\"url\":\"modules/orm_functions_login_user.html#getLoginUserById\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/login_user\"},{\"id\":67,\"kind\":64,\"name\":\"filterLoginUsers\",\"url\":\"modules/orm_functions_login_user.html#filterLoginUsers\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/login_user\"},{\"id\":68,\"kind\":64,\"name\":\"setCoach\",\"url\":\"modules/orm_functions_login_user.html#setCoach\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/login_user\"},{\"id\":69,\"kind\":64,\"name\":\"setAdmin\",\"url\":\"modules/orm_functions_login_user.html#setAdmin\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/login_user\"},{\"id\":70,\"kind\":2,\"name\":\"orm_functions/orm_types\",\"url\":\"modules/orm_functions_orm_types.html\",\"classes\":\"tsd-kind-module\"},{\"id\":71,\"kind\":256,\"name\":\"CreatePerson\",\"url\":\"interfaces/orm_functions_orm_types.CreatePerson.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":72,\"kind\":1024,\"name\":\"firstname\",\"url\":\"interfaces/orm_functions_orm_types.CreatePerson.html#firstname\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreatePerson\"},{\"id\":73,\"kind\":1024,\"name\":\"lastname\",\"url\":\"interfaces/orm_functions_orm_types.CreatePerson.html#lastname\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreatePerson\"},{\"id\":74,\"kind\":1024,\"name\":\"github\",\"url\":\"interfaces/orm_functions_orm_types.CreatePerson.html#github\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreatePerson\"},{\"id\":75,\"kind\":1024,\"name\":\"email\",\"url\":\"interfaces/orm_functions_orm_types.CreatePerson.html#email\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreatePerson\"},{\"id\":76,\"kind\":1024,\"name\":\"github_id\",\"url\":\"interfaces/orm_functions_orm_types.CreatePerson.html#github_id\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreatePerson\"},{\"id\":77,\"kind\":256,\"name\":\"UpdatePerson\",\"url\":\"interfaces/orm_functions_orm_types.UpdatePerson.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":78,\"kind\":1024,\"name\":\"personId\",\"url\":\"interfaces/orm_functions_orm_types.UpdatePerson.html#personId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdatePerson\"},{\"id\":79,\"kind\":1024,\"name\":\"firstname\",\"url\":\"interfaces/orm_functions_orm_types.UpdatePerson.html#firstname\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdatePerson\"},{\"id\":80,\"kind\":1024,\"name\":\"lastname\",\"url\":\"interfaces/orm_functions_orm_types.UpdatePerson.html#lastname\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdatePerson\"},{\"id\":81,\"kind\":1024,\"name\":\"github\",\"url\":\"interfaces/orm_functions_orm_types.UpdatePerson.html#github\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdatePerson\"},{\"id\":82,\"kind\":1024,\"name\":\"email\",\"url\":\"interfaces/orm_functions_orm_types.UpdatePerson.html#email\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdatePerson\"},{\"id\":83,\"kind\":256,\"name\":\"CreateLoginUser\",\"url\":\"interfaces/orm_functions_orm_types.CreateLoginUser.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":84,\"kind\":1024,\"name\":\"personId\",\"url\":\"interfaces/orm_functions_orm_types.CreateLoginUser.html#personId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateLoginUser\"},{\"id\":85,\"kind\":1024,\"name\":\"password\",\"url\":\"interfaces/orm_functions_orm_types.CreateLoginUser.html#password\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateLoginUser\"},{\"id\":86,\"kind\":1024,\"name\":\"isAdmin\",\"url\":\"interfaces/orm_functions_orm_types.CreateLoginUser.html#isAdmin\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateLoginUser\"},{\"id\":87,\"kind\":1024,\"name\":\"isCoach\",\"url\":\"interfaces/orm_functions_orm_types.CreateLoginUser.html#isCoach\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateLoginUser\"},{\"id\":88,\"kind\":1024,\"name\":\"accountStatus\",\"url\":\"interfaces/orm_functions_orm_types.CreateLoginUser.html#accountStatus\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateLoginUser\"},{\"id\":89,\"kind\":256,\"name\":\"UpdateLoginUser\",\"url\":\"interfaces/orm_functions_orm_types.UpdateLoginUser.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":90,\"kind\":1024,\"name\":\"loginUserId\",\"url\":\"interfaces/orm_functions_orm_types.UpdateLoginUser.html#loginUserId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateLoginUser\"},{\"id\":91,\"kind\":1024,\"name\":\"password\",\"url\":\"interfaces/orm_functions_orm_types.UpdateLoginUser.html#password\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateLoginUser\"},{\"id\":92,\"kind\":1024,\"name\":\"isAdmin\",\"url\":\"interfaces/orm_functions_orm_types.UpdateLoginUser.html#isAdmin\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateLoginUser\"},{\"id\":93,\"kind\":1024,\"name\":\"isCoach\",\"url\":\"interfaces/orm_functions_orm_types.UpdateLoginUser.html#isCoach\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateLoginUser\"},{\"id\":94,\"kind\":1024,\"name\":\"accountStatus\",\"url\":\"interfaces/orm_functions_orm_types.UpdateLoginUser.html#accountStatus\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateLoginUser\"},{\"id\":95,\"kind\":256,\"name\":\"CreateStudent\",\"url\":\"interfaces/orm_functions_orm_types.CreateStudent.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":96,\"kind\":1024,\"name\":\"personId\",\"url\":\"interfaces/orm_functions_orm_types.CreateStudent.html#personId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateStudent\"},{\"id\":97,\"kind\":1024,\"name\":\"gender\",\"url\":\"interfaces/orm_functions_orm_types.CreateStudent.html#gender\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateStudent\"},{\"id\":98,\"kind\":1024,\"name\":\"pronouns\",\"url\":\"interfaces/orm_functions_orm_types.CreateStudent.html#pronouns\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateStudent\"},{\"id\":99,\"kind\":1024,\"name\":\"phoneNumber\",\"url\":\"interfaces/orm_functions_orm_types.CreateStudent.html#phoneNumber\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateStudent\"},{\"id\":100,\"kind\":1024,\"name\":\"nickname\",\"url\":\"interfaces/orm_functions_orm_types.CreateStudent.html#nickname\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateStudent\"},{\"id\":101,\"kind\":1024,\"name\":\"alumni\",\"url\":\"interfaces/orm_functions_orm_types.CreateStudent.html#alumni\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateStudent\"},{\"id\":102,\"kind\":256,\"name\":\"UpdateStudent\",\"url\":\"interfaces/orm_functions_orm_types.UpdateStudent.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":103,\"kind\":1024,\"name\":\"studentId\",\"url\":\"interfaces/orm_functions_orm_types.UpdateStudent.html#studentId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateStudent\"},{\"id\":104,\"kind\":1024,\"name\":\"gender\",\"url\":\"interfaces/orm_functions_orm_types.UpdateStudent.html#gender\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateStudent\"},{\"id\":105,\"kind\":1024,\"name\":\"pronouns\",\"url\":\"interfaces/orm_functions_orm_types.UpdateStudent.html#pronouns\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateStudent\"},{\"id\":106,\"kind\":1024,\"name\":\"phoneNumber\",\"url\":\"interfaces/orm_functions_orm_types.UpdateStudent.html#phoneNumber\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateStudent\"},{\"id\":107,\"kind\":1024,\"name\":\"nickname\",\"url\":\"interfaces/orm_functions_orm_types.UpdateStudent.html#nickname\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateStudent\"},{\"id\":108,\"kind\":1024,\"name\":\"alumni\",\"url\":\"interfaces/orm_functions_orm_types.UpdateStudent.html#alumni\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateStudent\"},{\"id\":109,\"kind\":256,\"name\":\"CreateEvaluationForStudent\",\"url\":\"interfaces/orm_functions_orm_types.CreateEvaluationForStudent.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":110,\"kind\":1024,\"name\":\"loginUserId\",\"url\":\"interfaces/orm_functions_orm_types.CreateEvaluationForStudent.html#loginUserId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateEvaluationForStudent\"},{\"id\":111,\"kind\":1024,\"name\":\"jobApplicationId\",\"url\":\"interfaces/orm_functions_orm_types.CreateEvaluationForStudent.html#jobApplicationId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateEvaluationForStudent\"},{\"id\":112,\"kind\":1024,\"name\":\"decision\",\"url\":\"interfaces/orm_functions_orm_types.CreateEvaluationForStudent.html#decision\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateEvaluationForStudent\"},{\"id\":113,\"kind\":1024,\"name\":\"motivation\",\"url\":\"interfaces/orm_functions_orm_types.CreateEvaluationForStudent.html#motivation\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateEvaluationForStudent\"},{\"id\":114,\"kind\":1024,\"name\":\"isFinal\",\"url\":\"interfaces/orm_functions_orm_types.CreateEvaluationForStudent.html#isFinal\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateEvaluationForStudent\"},{\"id\":115,\"kind\":256,\"name\":\"UpdateEvaluationForStudent\",\"url\":\"interfaces/orm_functions_orm_types.UpdateEvaluationForStudent.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":116,\"kind\":1024,\"name\":\"evaluation_id\",\"url\":\"interfaces/orm_functions_orm_types.UpdateEvaluationForStudent.html#evaluation_id\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateEvaluationForStudent\"},{\"id\":117,\"kind\":1024,\"name\":\"loginUserId\",\"url\":\"interfaces/orm_functions_orm_types.UpdateEvaluationForStudent.html#loginUserId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateEvaluationForStudent\"},{\"id\":118,\"kind\":1024,\"name\":\"decision\",\"url\":\"interfaces/orm_functions_orm_types.UpdateEvaluationForStudent.html#decision\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateEvaluationForStudent\"},{\"id\":119,\"kind\":1024,\"name\":\"motivation\",\"url\":\"interfaces/orm_functions_orm_types.UpdateEvaluationForStudent.html#motivation\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateEvaluationForStudent\"},{\"id\":120,\"kind\":256,\"name\":\"CreateContract\",\"url\":\"interfaces/orm_functions_orm_types.CreateContract.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":121,\"kind\":1024,\"name\":\"studentId\",\"url\":\"interfaces/orm_functions_orm_types.CreateContract.html#studentId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateContract\"},{\"id\":122,\"kind\":1024,\"name\":\"projectRoleId\",\"url\":\"interfaces/orm_functions_orm_types.CreateContract.html#projectRoleId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateContract\"},{\"id\":123,\"kind\":1024,\"name\":\"information\",\"url\":\"interfaces/orm_functions_orm_types.CreateContract.html#information\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateContract\"},{\"id\":124,\"kind\":1024,\"name\":\"loginUserId\",\"url\":\"interfaces/orm_functions_orm_types.CreateContract.html#loginUserId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateContract\"},{\"id\":125,\"kind\":1024,\"name\":\"contractStatus\",\"url\":\"interfaces/orm_functions_orm_types.CreateContract.html#contractStatus\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateContract\"},{\"id\":126,\"kind\":256,\"name\":\"UpdateContract\",\"url\":\"interfaces/orm_functions_orm_types.UpdateContract.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":127,\"kind\":1024,\"name\":\"contractId\",\"url\":\"interfaces/orm_functions_orm_types.UpdateContract.html#contractId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateContract\"},{\"id\":128,\"kind\":1024,\"name\":\"loginUserId\",\"url\":\"interfaces/orm_functions_orm_types.UpdateContract.html#loginUserId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateContract\"},{\"id\":129,\"kind\":1024,\"name\":\"information\",\"url\":\"interfaces/orm_functions_orm_types.UpdateContract.html#information\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateContract\"},{\"id\":130,\"kind\":1024,\"name\":\"contractStatus\",\"url\":\"interfaces/orm_functions_orm_types.UpdateContract.html#contractStatus\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateContract\"},{\"id\":131,\"kind\":1024,\"name\":\"projectRoleId\",\"url\":\"interfaces/orm_functions_orm_types.UpdateContract.html#projectRoleId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateContract\"},{\"id\":132,\"kind\":256,\"name\":\"CreateJobApplication\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplication.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":133,\"kind\":1024,\"name\":\"studentId\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplication.html#studentId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateJobApplication\"},{\"id\":134,\"kind\":1024,\"name\":\"responsibilities\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplication.html#responsibilities\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateJobApplication\"},{\"id\":135,\"kind\":1024,\"name\":\"funFact\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplication.html#funFact\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateJobApplication\"},{\"id\":136,\"kind\":1024,\"name\":\"studentVolunteerInfo\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplication.html#studentVolunteerInfo\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateJobApplication\"},{\"id\":137,\"kind\":1024,\"name\":\"studentCoach\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplication.html#studentCoach\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateJobApplication\"},{\"id\":138,\"kind\":1024,\"name\":\"osocId\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplication.html#osocId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateJobApplication\"},{\"id\":139,\"kind\":1024,\"name\":\"edus\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplication.html#edus\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateJobApplication\"},{\"id\":140,\"kind\":1024,\"name\":\"eduLevel\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplication.html#eduLevel\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateJobApplication\"},{\"id\":141,\"kind\":1024,\"name\":\"eduDuration\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplication.html#eduDuration\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateJobApplication\"},{\"id\":142,\"kind\":1024,\"name\":\"eduYear\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplication.html#eduYear\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateJobApplication\"},{\"id\":143,\"kind\":1024,\"name\":\"eduInstitute\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplication.html#eduInstitute\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateJobApplication\"},{\"id\":144,\"kind\":1024,\"name\":\"emailStatus\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplication.html#emailStatus\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateJobApplication\"},{\"id\":145,\"kind\":1024,\"name\":\"createdAt\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplication.html#createdAt\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateJobApplication\"},{\"id\":146,\"kind\":256,\"name\":\"UpdateOsoc\",\"url\":\"interfaces/orm_functions_orm_types.UpdateOsoc.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":147,\"kind\":1024,\"name\":\"osocId\",\"url\":\"interfaces/orm_functions_orm_types.UpdateOsoc.html#osocId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateOsoc\"},{\"id\":148,\"kind\":1024,\"name\":\"year\",\"url\":\"interfaces/orm_functions_orm_types.UpdateOsoc.html#year\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateOsoc\"},{\"id\":149,\"kind\":256,\"name\":\"CreateProject\",\"url\":\"interfaces/orm_functions_orm_types.CreateProject.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":150,\"kind\":1024,\"name\":\"name\",\"url\":\"interfaces/orm_functions_orm_types.CreateProject.html#name\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateProject\"},{\"id\":151,\"kind\":1024,\"name\":\"osocId\",\"url\":\"interfaces/orm_functions_orm_types.CreateProject.html#osocId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateProject\"},{\"id\":152,\"kind\":1024,\"name\":\"partner\",\"url\":\"interfaces/orm_functions_orm_types.CreateProject.html#partner\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateProject\"},{\"id\":153,\"kind\":1024,\"name\":\"startDate\",\"url\":\"interfaces/orm_functions_orm_types.CreateProject.html#startDate\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateProject\"},{\"id\":154,\"kind\":1024,\"name\":\"endDate\",\"url\":\"interfaces/orm_functions_orm_types.CreateProject.html#endDate\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateProject\"},{\"id\":155,\"kind\":1024,\"name\":\"positions\",\"url\":\"interfaces/orm_functions_orm_types.CreateProject.html#positions\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateProject\"},{\"id\":156,\"kind\":256,\"name\":\"UpdateProject\",\"url\":\"interfaces/orm_functions_orm_types.UpdateProject.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":157,\"kind\":1024,\"name\":\"projectId\",\"url\":\"interfaces/orm_functions_orm_types.UpdateProject.html#projectId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateProject\"},{\"id\":158,\"kind\":1024,\"name\":\"name\",\"url\":\"interfaces/orm_functions_orm_types.UpdateProject.html#name\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateProject\"},{\"id\":159,\"kind\":1024,\"name\":\"osocId\",\"url\":\"interfaces/orm_functions_orm_types.UpdateProject.html#osocId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateProject\"},{\"id\":160,\"kind\":1024,\"name\":\"partner\",\"url\":\"interfaces/orm_functions_orm_types.UpdateProject.html#partner\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateProject\"},{\"id\":161,\"kind\":1024,\"name\":\"startDate\",\"url\":\"interfaces/orm_functions_orm_types.UpdateProject.html#startDate\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateProject\"},{\"id\":162,\"kind\":1024,\"name\":\"endDate\",\"url\":\"interfaces/orm_functions_orm_types.UpdateProject.html#endDate\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateProject\"},{\"id\":163,\"kind\":1024,\"name\":\"positions\",\"url\":\"interfaces/orm_functions_orm_types.UpdateProject.html#positions\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateProject\"},{\"id\":164,\"kind\":256,\"name\":\"CreateProjectRole\",\"url\":\"interfaces/orm_functions_orm_types.CreateProjectRole.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":165,\"kind\":1024,\"name\":\"projectId\",\"url\":\"interfaces/orm_functions_orm_types.CreateProjectRole.html#projectId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateProjectRole\"},{\"id\":166,\"kind\":1024,\"name\":\"roleId\",\"url\":\"interfaces/orm_functions_orm_types.CreateProjectRole.html#roleId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateProjectRole\"},{\"id\":167,\"kind\":1024,\"name\":\"positions\",\"url\":\"interfaces/orm_functions_orm_types.CreateProjectRole.html#positions\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateProjectRole\"},{\"id\":168,\"kind\":256,\"name\":\"UpdateProjectRole\",\"url\":\"interfaces/orm_functions_orm_types.UpdateProjectRole.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":169,\"kind\":1024,\"name\":\"projectRoleId\",\"url\":\"interfaces/orm_functions_orm_types.UpdateProjectRole.html#projectRoleId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateProjectRole\"},{\"id\":170,\"kind\":1024,\"name\":\"projectId\",\"url\":\"interfaces/orm_functions_orm_types.UpdateProjectRole.html#projectId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateProjectRole\"},{\"id\":171,\"kind\":1024,\"name\":\"roleId\",\"url\":\"interfaces/orm_functions_orm_types.UpdateProjectRole.html#roleId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateProjectRole\"},{\"id\":172,\"kind\":1024,\"name\":\"positions\",\"url\":\"interfaces/orm_functions_orm_types.UpdateProjectRole.html#positions\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateProjectRole\"},{\"id\":173,\"kind\":256,\"name\":\"UpdateRole\",\"url\":\"interfaces/orm_functions_orm_types.UpdateRole.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":174,\"kind\":1024,\"name\":\"roleId\",\"url\":\"interfaces/orm_functions_orm_types.UpdateRole.html#roleId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateRole\"},{\"id\":175,\"kind\":1024,\"name\":\"name\",\"url\":\"interfaces/orm_functions_orm_types.UpdateRole.html#name\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateRole\"},{\"id\":176,\"kind\":256,\"name\":\"UpdateLanguage\",\"url\":\"interfaces/orm_functions_orm_types.UpdateLanguage.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":177,\"kind\":1024,\"name\":\"languageId\",\"url\":\"interfaces/orm_functions_orm_types.UpdateLanguage.html#languageId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateLanguage\"},{\"id\":178,\"kind\":1024,\"name\":\"name\",\"url\":\"interfaces/orm_functions_orm_types.UpdateLanguage.html#name\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateLanguage\"},{\"id\":179,\"kind\":256,\"name\":\"CreateJobApplicationSkill\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplicationSkill.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":180,\"kind\":1024,\"name\":\"jobApplicationId\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplicationSkill.html#jobApplicationId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateJobApplicationSkill\"},{\"id\":181,\"kind\":1024,\"name\":\"skill\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplicationSkill.html#skill\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateJobApplicationSkill\"},{\"id\":182,\"kind\":1024,\"name\":\"languageId\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplicationSkill.html#languageId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateJobApplicationSkill\"},{\"id\":183,\"kind\":1024,\"name\":\"level\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplicationSkill.html#level\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateJobApplicationSkill\"},{\"id\":184,\"kind\":1024,\"name\":\"isPreferred\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplicationSkill.html#isPreferred\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateJobApplicationSkill\"},{\"id\":185,\"kind\":1024,\"name\":\"isBest\",\"url\":\"interfaces/orm_functions_orm_types.CreateJobApplicationSkill.html#isBest\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateJobApplicationSkill\"},{\"id\":186,\"kind\":256,\"name\":\"UpdateJobApplicationSkill\",\"url\":\"interfaces/orm_functions_orm_types.UpdateJobApplicationSkill.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":187,\"kind\":1024,\"name\":\"JobApplicationSkillId\",\"url\":\"interfaces/orm_functions_orm_types.UpdateJobApplicationSkill.html#JobApplicationSkillId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateJobApplicationSkill\"},{\"id\":188,\"kind\":1024,\"name\":\"JobApplicationId\",\"url\":\"interfaces/orm_functions_orm_types.UpdateJobApplicationSkill.html#JobApplicationId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateJobApplicationSkill\"},{\"id\":189,\"kind\":1024,\"name\":\"skill\",\"url\":\"interfaces/orm_functions_orm_types.UpdateJobApplicationSkill.html#skill\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateJobApplicationSkill\"},{\"id\":190,\"kind\":1024,\"name\":\"languageId\",\"url\":\"interfaces/orm_functions_orm_types.UpdateJobApplicationSkill.html#languageId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateJobApplicationSkill\"},{\"id\":191,\"kind\":1024,\"name\":\"level\",\"url\":\"interfaces/orm_functions_orm_types.UpdateJobApplicationSkill.html#level\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateJobApplicationSkill\"},{\"id\":192,\"kind\":1024,\"name\":\"isPreferred\",\"url\":\"interfaces/orm_functions_orm_types.UpdateJobApplicationSkill.html#isPreferred\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateJobApplicationSkill\"},{\"id\":193,\"kind\":1024,\"name\":\"is_best\",\"url\":\"interfaces/orm_functions_orm_types.UpdateJobApplicationSkill.html#is_best\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateJobApplicationSkill\"},{\"id\":194,\"kind\":256,\"name\":\"AddStudentToProject\",\"url\":\"interfaces/orm_functions_orm_types.AddStudentToProject.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":195,\"kind\":1024,\"name\":\"loginUserId\",\"url\":\"interfaces/orm_functions_orm_types.AddStudentToProject.html#loginUserId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.AddStudentToProject\"},{\"id\":196,\"kind\":1024,\"name\":\"studentId\",\"url\":\"interfaces/orm_functions_orm_types.AddStudentToProject.html#studentId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.AddStudentToProject\"},{\"id\":197,\"kind\":1024,\"name\":\"projectId\",\"url\":\"interfaces/orm_functions_orm_types.AddStudentToProject.html#projectId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.AddStudentToProject\"},{\"id\":198,\"kind\":1024,\"name\":\"roleName\",\"url\":\"interfaces/orm_functions_orm_types.AddStudentToProject.html#roleName\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.AddStudentToProject\"},{\"id\":199,\"kind\":1024,\"name\":\"information\",\"url\":\"interfaces/orm_functions_orm_types.AddStudentToProject.html#information\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.AddStudentToProject\"},{\"id\":200,\"kind\":256,\"name\":\"CreateProjectUser\",\"url\":\"interfaces/orm_functions_orm_types.CreateProjectUser.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":201,\"kind\":1024,\"name\":\"projectId\",\"url\":\"interfaces/orm_functions_orm_types.CreateProjectUser.html#projectId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateProjectUser\"},{\"id\":202,\"kind\":1024,\"name\":\"loginUserId\",\"url\":\"interfaces/orm_functions_orm_types.CreateProjectUser.html#loginUserId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateProjectUser\"},{\"id\":203,\"kind\":256,\"name\":\"CreateAppliedRole\",\"url\":\"interfaces/orm_functions_orm_types.CreateAppliedRole.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":204,\"kind\":1024,\"name\":\"jobApplicationId\",\"url\":\"interfaces/orm_functions_orm_types.CreateAppliedRole.html#jobApplicationId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateAppliedRole\"},{\"id\":205,\"kind\":1024,\"name\":\"roleId\",\"url\":\"interfaces/orm_functions_orm_types.CreateAppliedRole.html#roleId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateAppliedRole\"},{\"id\":206,\"kind\":256,\"name\":\"CreateTemplate\",\"url\":\"interfaces/orm_functions_orm_types.CreateTemplate.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":207,\"kind\":1024,\"name\":\"ownerId\",\"url\":\"interfaces/orm_functions_orm_types.CreateTemplate.html#ownerId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateTemplate\"},{\"id\":208,\"kind\":1024,\"name\":\"name\",\"url\":\"interfaces/orm_functions_orm_types.CreateTemplate.html#name\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateTemplate\"},{\"id\":209,\"kind\":1024,\"name\":\"content\",\"url\":\"interfaces/orm_functions_orm_types.CreateTemplate.html#content\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateTemplate\"},{\"id\":210,\"kind\":1024,\"name\":\"subject\",\"url\":\"interfaces/orm_functions_orm_types.CreateTemplate.html#subject\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateTemplate\"},{\"id\":211,\"kind\":1024,\"name\":\"cc\",\"url\":\"interfaces/orm_functions_orm_types.CreateTemplate.html#cc\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.CreateTemplate\"},{\"id\":212,\"kind\":256,\"name\":\"UpdateTemplate\",\"url\":\"interfaces/orm_functions_orm_types.UpdateTemplate.html\",\"classes\":\"tsd-kind-interface tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":213,\"kind\":1024,\"name\":\"templateId\",\"url\":\"interfaces/orm_functions_orm_types.UpdateTemplate.html#templateId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateTemplate\"},{\"id\":214,\"kind\":1024,\"name\":\"ownerId\",\"url\":\"interfaces/orm_functions_orm_types.UpdateTemplate.html#ownerId\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateTemplate\"},{\"id\":215,\"kind\":1024,\"name\":\"name\",\"url\":\"interfaces/orm_functions_orm_types.UpdateTemplate.html#name\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateTemplate\"},{\"id\":216,\"kind\":1024,\"name\":\"content\",\"url\":\"interfaces/orm_functions_orm_types.UpdateTemplate.html#content\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateTemplate\"},{\"id\":217,\"kind\":1024,\"name\":\"subject\",\"url\":\"interfaces/orm_functions_orm_types.UpdateTemplate.html#subject\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateTemplate\"},{\"id\":218,\"kind\":1024,\"name\":\"cc\",\"url\":\"interfaces/orm_functions_orm_types.UpdateTemplate.html#cc\",\"classes\":\"tsd-kind-property tsd-parent-kind-interface\",\"parent\":\"orm_functions/orm_types.UpdateTemplate\"},{\"id\":219,\"kind\":4194304,\"name\":\"FilterSort\",\"url\":\"modules/orm_functions_orm_types.html#FilterSort\",\"classes\":\"tsd-kind-type-alias tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":220,\"kind\":4194304,\"name\":\"FilterString\",\"url\":\"modules/orm_functions_orm_types.html#FilterString\",\"classes\":\"tsd-kind-type-alias tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":221,\"kind\":4194304,\"name\":\"FilterNumber\",\"url\":\"modules/orm_functions_orm_types.html#FilterNumber\",\"classes\":\"tsd-kind-type-alias tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":222,\"kind\":4194304,\"name\":\"FilterNumberArray\",\"url\":\"modules/orm_functions_orm_types.html#FilterNumberArray\",\"classes\":\"tsd-kind-type-alias tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":223,\"kind\":4194304,\"name\":\"FilterStringArray\",\"url\":\"modules/orm_functions_orm_types.html#FilterStringArray\",\"classes\":\"tsd-kind-type-alias tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":224,\"kind\":4194304,\"name\":\"FilterBoolean\",\"url\":\"modules/orm_functions_orm_types.html#FilterBoolean\",\"classes\":\"tsd-kind-type-alias tsd-parent-kind-module\",\"parent\":\"orm_functions/orm_types\"},{\"id\":225,\"kind\":2,\"name\":\"orm_functions/osoc\",\"url\":\"modules/orm_functions_osoc.html\",\"classes\":\"tsd-kind-module\"},{\"id\":226,\"kind\":64,\"name\":\"createOsoc\",\"url\":\"modules/orm_functions_osoc.html#createOsoc\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/osoc\"},{\"id\":227,\"kind\":64,\"name\":\"getAllOsoc\",\"url\":\"modules/orm_functions_osoc.html#getAllOsoc\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/osoc\"},{\"id\":228,\"kind\":64,\"name\":\"getLatestOsoc\",\"url\":\"modules/orm_functions_osoc.html#getLatestOsoc\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/osoc\"},{\"id\":229,\"kind\":64,\"name\":\"getOsocByYear\",\"url\":\"modules/orm_functions_osoc.html#getOsocByYear\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/osoc\"},{\"id\":230,\"kind\":64,\"name\":\"getOsocBeforeYear\",\"url\":\"modules/orm_functions_osoc.html#getOsocBeforeYear\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/osoc\"},{\"id\":231,\"kind\":64,\"name\":\"getOsocAfterYear\",\"url\":\"modules/orm_functions_osoc.html#getOsocAfterYear\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/osoc\"},{\"id\":232,\"kind\":64,\"name\":\"updateOsoc\",\"url\":\"modules/orm_functions_osoc.html#updateOsoc\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/osoc\"},{\"id\":233,\"kind\":64,\"name\":\"deleteOsoc\",\"url\":\"modules/orm_functions_osoc.html#deleteOsoc\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/osoc\"},{\"id\":234,\"kind\":64,\"name\":\"deleteOsocByYear\",\"url\":\"modules/orm_functions_osoc.html#deleteOsocByYear\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/osoc\"},{\"id\":235,\"kind\":64,\"name\":\"deleteOsocFromDB\",\"url\":\"modules/orm_functions_osoc.html#deleteOsocFromDB\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/osoc\"},{\"id\":236,\"kind\":64,\"name\":\"getNewestOsoc\",\"url\":\"modules/orm_functions_osoc.html#getNewestOsoc\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/osoc\"},{\"id\":237,\"kind\":64,\"name\":\"filterOsocs\",\"url\":\"modules/orm_functions_osoc.html#filterOsocs\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/osoc\"},{\"id\":238,\"kind\":2,\"name\":\"orm_functions/password_reset\",\"url\":\"modules/orm_functions_password_reset.html\",\"classes\":\"tsd-kind-module\"},{\"id\":239,\"kind\":64,\"name\":\"createOrUpdateReset\",\"url\":\"modules/orm_functions_password_reset.html#createOrUpdateReset\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/password_reset\"},{\"id\":240,\"kind\":64,\"name\":\"findResetByCode\",\"url\":\"modules/orm_functions_password_reset.html#findResetByCode\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/password_reset\"},{\"id\":241,\"kind\":64,\"name\":\"deleteResetWithLoginUser\",\"url\":\"modules/orm_functions_password_reset.html#deleteResetWithLoginUser\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/password_reset\"},{\"id\":242,\"kind\":64,\"name\":\"deleteResetWithResetId\",\"url\":\"modules/orm_functions_password_reset.html#deleteResetWithResetId\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/password_reset\"},{\"id\":243,\"kind\":2,\"name\":\"orm_functions/person\",\"url\":\"modules/orm_functions_person.html\",\"classes\":\"tsd-kind-module\"},{\"id\":244,\"kind\":64,\"name\":\"createPerson\",\"url\":\"modules/orm_functions_person.html#createPerson\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/person\"},{\"id\":245,\"kind\":64,\"name\":\"getAllPersons\",\"url\":\"modules/orm_functions_person.html#getAllPersons\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/person\"},{\"id\":246,\"kind\":64,\"name\":\"getPasswordPersonByEmail\",\"url\":\"modules/orm_functions_person.html#getPasswordPersonByEmail\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/person\"},{\"id\":247,\"kind\":64,\"name\":\"getPasswordPersonByGithub\",\"url\":\"modules/orm_functions_person.html#getPasswordPersonByGithub\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/person\"},{\"id\":248,\"kind\":64,\"name\":\"searchPersonByName\",\"url\":\"modules/orm_functions_person.html#searchPersonByName\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/person\"},{\"id\":249,\"kind\":64,\"name\":\"searchPersonByLogin\",\"url\":\"modules/orm_functions_person.html#searchPersonByLogin\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/person\"},{\"id\":250,\"kind\":64,\"name\":\"updatePerson\",\"url\":\"modules/orm_functions_person.html#updatePerson\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/person\"},{\"id\":251,\"kind\":64,\"name\":\"deletePersonById\",\"url\":\"modules/orm_functions_person.html#deletePersonById\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/person\"},{\"id\":252,\"kind\":2,\"name\":\"orm_functions/project\",\"url\":\"modules/orm_functions_project.html\",\"classes\":\"tsd-kind-module\"},{\"id\":253,\"kind\":64,\"name\":\"createProject\",\"url\":\"modules/orm_functions_project.html#createProject\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project\"},{\"id\":254,\"kind\":64,\"name\":\"getAllProjects\",\"url\":\"modules/orm_functions_project.html#getAllProjects\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project\"},{\"id\":255,\"kind\":64,\"name\":\"getProjectById\",\"url\":\"modules/orm_functions_project.html#getProjectById\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project\"},{\"id\":256,\"kind\":64,\"name\":\"getProjectByName\",\"url\":\"modules/orm_functions_project.html#getProjectByName\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project\"},{\"id\":257,\"kind\":64,\"name\":\"getProjectsByOsocEdition\",\"url\":\"modules/orm_functions_project.html#getProjectsByOsocEdition\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project\"},{\"id\":258,\"kind\":64,\"name\":\"getProjectsByPartner\",\"url\":\"modules/orm_functions_project.html#getProjectsByPartner\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project\"},{\"id\":259,\"kind\":64,\"name\":\"getProjectsByStartDate\",\"url\":\"modules/orm_functions_project.html#getProjectsByStartDate\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project\"},{\"id\":260,\"kind\":64,\"name\":\"getProjectsStartedAfterDate\",\"url\":\"modules/orm_functions_project.html#getProjectsStartedAfterDate\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project\"},{\"id\":261,\"kind\":64,\"name\":\"getProjectsStartedBeforeDate\",\"url\":\"modules/orm_functions_project.html#getProjectsStartedBeforeDate\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project\"},{\"id\":262,\"kind\":64,\"name\":\"getProjectsByEndDate\",\"url\":\"modules/orm_functions_project.html#getProjectsByEndDate\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project\"},{\"id\":263,\"kind\":64,\"name\":\"getProjectsEndedAfterDate\",\"url\":\"modules/orm_functions_project.html#getProjectsEndedAfterDate\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project\"},{\"id\":264,\"kind\":64,\"name\":\"getProjectsEndedBeforeDate\",\"url\":\"modules/orm_functions_project.html#getProjectsEndedBeforeDate\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project\"},{\"id\":265,\"kind\":64,\"name\":\"getProjectsByNumberPositions\",\"url\":\"modules/orm_functions_project.html#getProjectsByNumberPositions\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project\"},{\"id\":266,\"kind\":64,\"name\":\"getProjectsLessPositions\",\"url\":\"modules/orm_functions_project.html#getProjectsLessPositions\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project\"},{\"id\":267,\"kind\":64,\"name\":\"getProjectsMorePositions\",\"url\":\"modules/orm_functions_project.html#getProjectsMorePositions\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project\"},{\"id\":268,\"kind\":64,\"name\":\"updateProject\",\"url\":\"modules/orm_functions_project.html#updateProject\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project\"},{\"id\":269,\"kind\":64,\"name\":\"deleteProject\",\"url\":\"modules/orm_functions_project.html#deleteProject\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project\"},{\"id\":270,\"kind\":64,\"name\":\"deleteProjectByOsocEdition\",\"url\":\"modules/orm_functions_project.html#deleteProjectByOsocEdition\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project\"},{\"id\":271,\"kind\":64,\"name\":\"deleteProjectByPartner\",\"url\":\"modules/orm_functions_project.html#deleteProjectByPartner\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project\"},{\"id\":272,\"kind\":64,\"name\":\"filterProjects\",\"url\":\"modules/orm_functions_project.html#filterProjects\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project\"},{\"id\":273,\"kind\":2,\"name\":\"orm_functions/project_role\",\"url\":\"modules/orm_functions_project_role.html\",\"classes\":\"tsd-kind-module\"},{\"id\":274,\"kind\":64,\"name\":\"createProjectRole\",\"url\":\"modules/orm_functions_project_role.html#createProjectRole\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project_role\"},{\"id\":275,\"kind\":64,\"name\":\"getProjectRolesByProject\",\"url\":\"modules/orm_functions_project_role.html#getProjectRolesByProject\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project_role\"},{\"id\":276,\"kind\":64,\"name\":\"getNumberOfRolesByProjectAndRole\",\"url\":\"modules/orm_functions_project_role.html#getNumberOfRolesByProjectAndRole\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project_role\"},{\"id\":277,\"kind\":64,\"name\":\"getProjectRoleNamesByProject\",\"url\":\"modules/orm_functions_project_role.html#getProjectRoleNamesByProject\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project_role\"},{\"id\":278,\"kind\":64,\"name\":\"updateProjectRole\",\"url\":\"modules/orm_functions_project_role.html#updateProjectRole\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project_role\"},{\"id\":279,\"kind\":64,\"name\":\"deleteProjectRole\",\"url\":\"modules/orm_functions_project_role.html#deleteProjectRole\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project_role\"},{\"id\":280,\"kind\":64,\"name\":\"getNumberOfFreePositions\",\"url\":\"modules/orm_functions_project_role.html#getNumberOfFreePositions\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project_role\"},{\"id\":281,\"kind\":64,\"name\":\"getProjectRoleById\",\"url\":\"modules/orm_functions_project_role.html#getProjectRoleById\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project_role\"},{\"id\":282,\"kind\":2,\"name\":\"orm_functions/project_user\",\"url\":\"modules/orm_functions_project_user.html\",\"classes\":\"tsd-kind-module\"},{\"id\":283,\"kind\":64,\"name\":\"createProjectUser\",\"url\":\"modules/orm_functions_project_user.html#createProjectUser\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project_user\"},{\"id\":284,\"kind\":64,\"name\":\"getUsersFor\",\"url\":\"modules/orm_functions_project_user.html#getUsersFor\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/project_user\"},{\"id\":285,\"kind\":2,\"name\":\"orm_functions/role\",\"url\":\"modules/orm_functions_role.html\",\"classes\":\"tsd-kind-module\"},{\"id\":286,\"kind\":64,\"name\":\"createRole\",\"url\":\"modules/orm_functions_role.html#createRole\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/role\"},{\"id\":287,\"kind\":64,\"name\":\"getAllRoles\",\"url\":\"modules/orm_functions_role.html#getAllRoles\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/role\"},{\"id\":288,\"kind\":64,\"name\":\"getRole\",\"url\":\"modules/orm_functions_role.html#getRole\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/role\"},{\"id\":289,\"kind\":64,\"name\":\"getRolesByName\",\"url\":\"modules/orm_functions_role.html#getRolesByName\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/role\"},{\"id\":290,\"kind\":64,\"name\":\"getProjectRoleWithRoleName\",\"url\":\"modules/orm_functions_role.html#getProjectRoleWithRoleName\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/role\"},{\"id\":291,\"kind\":64,\"name\":\"updateRole\",\"url\":\"modules/orm_functions_role.html#updateRole\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/role\"},{\"id\":292,\"kind\":64,\"name\":\"deleteRole\",\"url\":\"modules/orm_functions_role.html#deleteRole\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/role\"},{\"id\":293,\"kind\":64,\"name\":\"deleteRoleByName\",\"url\":\"modules/orm_functions_role.html#deleteRoleByName\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/role\"},{\"id\":294,\"kind\":2,\"name\":\"orm_functions/session_key\",\"url\":\"modules/orm_functions_session_key.html\",\"classes\":\"tsd-kind-module\"},{\"id\":295,\"kind\":64,\"name\":\"addSessionKey\",\"url\":\"modules/orm_functions_session_key.html#addSessionKey\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/session_key\"},{\"id\":296,\"kind\":64,\"name\":\"checkSessionKey\",\"url\":\"modules/orm_functions_session_key.html#checkSessionKey\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/session_key\"},{\"id\":297,\"kind\":64,\"name\":\"refreshKey\",\"url\":\"modules/orm_functions_session_key.html#refreshKey\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/session_key\"},{\"id\":298,\"kind\":64,\"name\":\"removeAllKeysForUser\",\"url\":\"modules/orm_functions_session_key.html#removeAllKeysForUser\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/session_key\"},{\"id\":299,\"kind\":64,\"name\":\"removeAllKeysForLoginUserId\",\"url\":\"modules/orm_functions_session_key.html#removeAllKeysForLoginUserId\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/session_key\"},{\"id\":300,\"kind\":2,\"name\":\"orm_functions/student\",\"url\":\"modules/orm_functions_student.html\",\"classes\":\"tsd-kind-module\"},{\"id\":301,\"kind\":64,\"name\":\"createStudent\",\"url\":\"modules/orm_functions_student.html#createStudent\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/student\"},{\"id\":302,\"kind\":64,\"name\":\"getAllStudents\",\"url\":\"modules/orm_functions_student.html#getAllStudents\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/student\"},{\"id\":303,\"kind\":64,\"name\":\"getStudent\",\"url\":\"modules/orm_functions_student.html#getStudent\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/student\"},{\"id\":304,\"kind\":64,\"name\":\"updateStudent\",\"url\":\"modules/orm_functions_student.html#updateStudent\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/student\"},{\"id\":305,\"kind\":64,\"name\":\"deleteStudent\",\"url\":\"modules/orm_functions_student.html#deleteStudent\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/student\"},{\"id\":306,\"kind\":64,\"name\":\"searchStudentByGender\",\"url\":\"modules/orm_functions_student.html#searchStudentByGender\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/student\"},{\"id\":307,\"kind\":64,\"name\":\"filterStudents\",\"url\":\"modules/orm_functions_student.html#filterStudents\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/student\"},{\"id\":308,\"kind\":2,\"name\":\"orm_functions/template\",\"url\":\"modules/orm_functions_template.html\",\"classes\":\"tsd-kind-module\"},{\"id\":309,\"kind\":64,\"name\":\"getAllTemplates\",\"url\":\"modules/orm_functions_template.html#getAllTemplates\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/template\"},{\"id\":310,\"kind\":64,\"name\":\"getTemplateById\",\"url\":\"modules/orm_functions_template.html#getTemplateById\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/template\"},{\"id\":311,\"kind\":64,\"name\":\"getTemplatesByName\",\"url\":\"modules/orm_functions_template.html#getTemplatesByName\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/template\"},{\"id\":312,\"kind\":64,\"name\":\"createTemplate\",\"url\":\"modules/orm_functions_template.html#createTemplate\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/template\"},{\"id\":313,\"kind\":64,\"name\":\"updateTemplate\",\"url\":\"modules/orm_functions_template.html#updateTemplate\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/template\"},{\"id\":314,\"kind\":64,\"name\":\"deleteTemplate\",\"url\":\"modules/orm_functions_template.html#deleteTemplate\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"orm_functions/template\"},{\"id\":315,\"kind\":2,\"name\":\"routes/admin\",\"url\":\"modules/routes_admin.html\",\"classes\":\"tsd-kind-module\"},{\"id\":316,\"kind\":64,\"name\":\"listAdmins\",\"url\":\"modules/routes_admin.html#listAdmins\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/admin\"},{\"id\":317,\"kind\":64,\"name\":\"modAdmin\",\"url\":\"modules/routes_admin.html#modAdmin\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/admin\"},{\"id\":318,\"kind\":64,\"name\":\"deleteAdmin\",\"url\":\"modules/routes_admin.html#deleteAdmin\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/admin\"},{\"id\":319,\"kind\":64,\"name\":\"getRouter\",\"url\":\"modules/routes_admin.html#getRouter\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/admin\"},{\"id\":320,\"kind\":2,\"name\":\"routes/coach\",\"url\":\"modules/routes_coach.html\",\"classes\":\"tsd-kind-module\"},{\"id\":321,\"kind\":64,\"name\":\"listCoaches\",\"url\":\"modules/routes_coach.html#listCoaches\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/coach\"},{\"id\":322,\"kind\":64,\"name\":\"modCoach\",\"url\":\"modules/routes_coach.html#modCoach\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/coach\"},{\"id\":323,\"kind\":64,\"name\":\"deleteCoach\",\"url\":\"modules/routes_coach.html#deleteCoach\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/coach\"},{\"id\":324,\"kind\":64,\"name\":\"getCoachRequests\",\"url\":\"modules/routes_coach.html#getCoachRequests\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/coach\"},{\"id\":325,\"kind\":64,\"name\":\"getRouter\",\"url\":\"modules/routes_coach.html#getRouter\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/coach\"},{\"id\":326,\"kind\":2,\"name\":\"routes/followup\",\"url\":\"modules/routes_followup.html\",\"classes\":\"tsd-kind-module\"},{\"id\":327,\"kind\":64,\"name\":\"listFollowups\",\"url\":\"modules/routes_followup.html#listFollowups\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/followup\"},{\"id\":328,\"kind\":64,\"name\":\"getFollowup\",\"url\":\"modules/routes_followup.html#getFollowup\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/followup\"},{\"id\":329,\"kind\":64,\"name\":\"updateFollowup\",\"url\":\"modules/routes_followup.html#updateFollowup\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/followup\"},{\"id\":330,\"kind\":64,\"name\":\"getRouter\",\"url\":\"modules/routes_followup.html#getRouter\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/followup\"},{\"id\":331,\"kind\":2,\"name\":\"routes/form\",\"url\":\"modules/routes_form.html\",\"classes\":\"tsd-kind-module\"},{\"id\":332,\"kind\":64,\"name\":\"filterQuestion\",\"url\":\"modules/routes_form.html#filterQuestion\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":333,\"kind\":64,\"name\":\"filterChosenOption\",\"url\":\"modules/routes_form.html#filterChosenOption\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":334,\"kind\":64,\"name\":\"checkWordInAnswer\",\"url\":\"modules/routes_form.html#checkWordInAnswer\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":335,\"kind\":64,\"name\":\"checkQuestionsExist\",\"url\":\"modules/routes_form.html#checkQuestionsExist\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":336,\"kind\":64,\"name\":\"getBirthName\",\"url\":\"modules/routes_form.html#getBirthName\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":337,\"kind\":64,\"name\":\"getLastName\",\"url\":\"modules/routes_form.html#getLastName\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":338,\"kind\":64,\"name\":\"getEmail\",\"url\":\"modules/routes_form.html#getEmail\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":339,\"kind\":64,\"name\":\"jsonToPerson\",\"url\":\"modules/routes_form.html#jsonToPerson\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":340,\"kind\":64,\"name\":\"getPronouns\",\"url\":\"modules/routes_form.html#getPronouns\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":341,\"kind\":64,\"name\":\"getGender\",\"url\":\"modules/routes_form.html#getGender\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":342,\"kind\":64,\"name\":\"getPhoneNumber\",\"url\":\"modules/routes_form.html#getPhoneNumber\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":343,\"kind\":64,\"name\":\"getNickname\",\"url\":\"modules/routes_form.html#getNickname\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":344,\"kind\":64,\"name\":\"getAlumni\",\"url\":\"modules/routes_form.html#getAlumni\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":345,\"kind\":64,\"name\":\"jsonToStudent\",\"url\":\"modules/routes_form.html#jsonToStudent\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":346,\"kind\":64,\"name\":\"getResponsibilities\",\"url\":\"modules/routes_form.html#getResponsibilities\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":347,\"kind\":64,\"name\":\"getFunFact\",\"url\":\"modules/routes_form.html#getFunFact\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":348,\"kind\":64,\"name\":\"getVolunteerInfo\",\"url\":\"modules/routes_form.html#getVolunteerInfo\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":349,\"kind\":64,\"name\":\"isStudentCoach\",\"url\":\"modules/routes_form.html#isStudentCoach\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":350,\"kind\":64,\"name\":\"getEducations\",\"url\":\"modules/routes_form.html#getEducations\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":351,\"kind\":64,\"name\":\"getEducationLevel\",\"url\":\"modules/routes_form.html#getEducationLevel\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":352,\"kind\":64,\"name\":\"getEducationDuration\",\"url\":\"modules/routes_form.html#getEducationDuration\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":353,\"kind\":64,\"name\":\"getEducationYear\",\"url\":\"modules/routes_form.html#getEducationYear\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":354,\"kind\":64,\"name\":\"getEducationUniversity\",\"url\":\"modules/routes_form.html#getEducationUniversity\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":355,\"kind\":64,\"name\":\"jsonToJobApplication\",\"url\":\"modules/routes_form.html#jsonToJobApplication\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":356,\"kind\":64,\"name\":\"getMostFluentLanguage\",\"url\":\"modules/routes_form.html#getMostFluentLanguage\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":357,\"kind\":64,\"name\":\"getEnglishLevel\",\"url\":\"modules/routes_form.html#getEnglishLevel\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":358,\"kind\":64,\"name\":\"getBestSkill\",\"url\":\"modules/routes_form.html#getBestSkill\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":359,\"kind\":64,\"name\":\"jsonToSkills\",\"url\":\"modules/routes_form.html#jsonToSkills\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":360,\"kind\":64,\"name\":\"getCV\",\"url\":\"modules/routes_form.html#getCV\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":361,\"kind\":64,\"name\":\"getPortfolio\",\"url\":\"modules/routes_form.html#getPortfolio\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":362,\"kind\":64,\"name\":\"getMotivation\",\"url\":\"modules/routes_form.html#getMotivation\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":363,\"kind\":64,\"name\":\"jsonToAttachments\",\"url\":\"modules/routes_form.html#jsonToAttachments\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":364,\"kind\":64,\"name\":\"getAppliedRoles\",\"url\":\"modules/routes_form.html#getAppliedRoles\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":365,\"kind\":64,\"name\":\"jsonToRoles\",\"url\":\"modules/routes_form.html#jsonToRoles\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":366,\"kind\":64,\"name\":\"addPersonToDatabase\",\"url\":\"modules/routes_form.html#addPersonToDatabase\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":367,\"kind\":64,\"name\":\"addStudentToDatabase\",\"url\":\"modules/routes_form.html#addStudentToDatabase\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":368,\"kind\":64,\"name\":\"addJobApplicationToDatabase\",\"url\":\"modules/routes_form.html#addJobApplicationToDatabase\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":369,\"kind\":64,\"name\":\"addSkillsToDatabase\",\"url\":\"modules/routes_form.html#addSkillsToDatabase\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":370,\"kind\":64,\"name\":\"addAttachmentsToDatabase\",\"url\":\"modules/routes_form.html#addAttachmentsToDatabase\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":371,\"kind\":64,\"name\":\"addRolesToDatabase\",\"url\":\"modules/routes_form.html#addRolesToDatabase\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":372,\"kind\":64,\"name\":\"createForm\",\"url\":\"modules/routes_form.html#createForm\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":373,\"kind\":64,\"name\":\"getRouter\",\"url\":\"modules/routes_form.html#getRouter\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/form\"},{\"id\":374,\"kind\":2,\"name\":\"routes/form_keys.json\",\"url\":\"modules/routes_form_keys_json.html\",\"classes\":\"tsd-kind-module\"},{\"id\":375,\"kind\":1024,\"name\":\"export=\",\"url\":\"modules/routes_form_keys_json.html#export_\",\"classes\":\"tsd-kind-property tsd-parent-kind-module\",\"parent\":\"routes/form_keys.json\"},{\"id\":376,\"kind\":65536,\"name\":\"__type\",\"url\":\"modules/routes_form_keys_json.html#__type\",\"classes\":\"tsd-kind-type-literal tsd-parent-kind-module\",\"parent\":\"routes/form_keys.json\"},{\"id\":377,\"kind\":1024,\"name\":\"liveInBelgium\",\"url\":\"modules/routes_form_keys_json.html#__type.liveInBelgium\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"routes/form_keys.json.__type\"},{\"id\":378,\"kind\":1024,\"name\":\"volunteerInfo\",\"url\":\"modules/routes_form_keys_json.html#__type.volunteerInfo\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"routes/form_keys.json.__type\"},{\"id\":379,\"kind\":1024,\"name\":\"workInJuly\",\"url\":\"modules/routes_form_keys_json.html#__type.workInJuly\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"routes/form_keys.json.__type\"},{\"id\":380,\"kind\":1024,\"name\":\"responsibilities\",\"url\":\"modules/routes_form_keys_json.html#__type.responsibilities\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"routes/form_keys.json.__type\"},{\"id\":381,\"kind\":1024,\"name\":\"birthName\",\"url\":\"modules/routes_form_keys_json.html#__type.birthName\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"routes/form_keys.json.__type\"},{\"id\":382,\"kind\":1024,\"name\":\"lastName\",\"url\":\"modules/routes_form_keys_json.html#__type.lastName\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"routes/form_keys.json.__type\"},{\"id\":383,\"kind\":1024,\"name\":\"nickname\",\"url\":\"modules/routes_form_keys_json.html#__type.nickname\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"routes/form_keys.json.__type\"},{\"id\":384,\"kind\":1024,\"name\":\"nicknameInput\",\"url\":\"modules/routes_form_keys_json.html#__type.nicknameInput\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"routes/form_keys.json.__type\"},{\"id\":385,\"kind\":1024,\"name\":\"gender\",\"url\":\"modules/routes_form_keys_json.html#__type.gender\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"routes/form_keys.json.__type\"},{\"id\":386,\"kind\":1024,\"name\":\"addPronouns\",\"url\":\"modules/routes_form_keys_json.html#__type.addPronouns\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"routes/form_keys.json.__type\"},{\"id\":387,\"kind\":1024,\"name\":\"preferredPronouns\",\"url\":\"modules/routes_form_keys_json.html#__type.preferredPronouns\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"routes/form_keys.json.__type\"},{\"id\":388,\"kind\":1024,\"name\":\"pronounsInput\",\"url\":\"modules/routes_form_keys_json.html#__type.pronounsInput\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"routes/form_keys.json.__type\"},{\"id\":389,\"kind\":1024,\"name\":\"mostFluentLanguage\",\"url\":\"modules/routes_form_keys_json.html#__type.mostFluentLanguage\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"routes/form_keys.json.__type\"},{\"id\":390,\"kind\":1024,\"name\":\"mostFluentLanguageInput\",\"url\":\"modules/routes_form_keys_json.html#__type.mostFluentLanguageInput\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"routes/form_keys.json.__type\"},{\"id\":391,\"kind\":1024,\"name\":\"englishLevel\",\"url\":\"modules/routes_form_keys_json.html#__type.englishLevel\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"routes/form_keys.json.__type\"},{\"id\":392,\"kind\":1024,\"name\":\"phoneNumber\",\"url\":\"modules/routes_form_keys_json.html#__type.phoneNumber\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"routes/form_keys.json.__type\"},{\"id\":393,\"kind\":1024,\"name\":\"emailAddress\",\"url\":\"modules/routes_form_keys_json.html#__type.emailAddress\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"routes/form_keys.json.__type\"},{\"id\":394,\"kind\":1024,\"name\":\"cvUpload\",\"url\":\"modules/routes_form_keys_json.html#__type.cvUpload\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"routes/form_keys.json.__type\"},{\"id\":395,\"kind\":1024,\"name\":\"cvLink\",\"url\":\"modules/routes_form_keys_json.html#__type.cvLink\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"routes/form_keys.json.__type\"},{\"id\":396,\"kind\":1024,\"name\":\"portfolioUpload\",\"url\":\"modules/routes_form_keys_json.html#__type.portfolioUpload\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"routes/form_keys.json.__type\"},{\"id\":397,\"kind\":1024,\"name\":\"portfolioLink\",\"url\":\"modules/routes_form_keys_json.html#__type.portfolioLink\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"routes/form_keys.json.__type\"},{\"id\":398,\"kind\":1024,\"name\":\"motivationUpload\",\"url\":\"modules/routes_form_keys_json.html#__type.motivationUpload\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"routes/form_keys.json.__type\"},{\"id\":399,\"kind\":1024,\"name\":\"motivationLink\",\"url\":\"modules/routes_form_keys_json.html#__type.motivationLink\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"routes/form_keys.json.__type\"},{\"id\":400,\"kind\":1024,\"name\":\"motivationInput\",\"url\":\"modules/routes_form_keys_json.html#__type.motivationInput\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"routes/form_keys.json.__type\"},{\"id\":401,\"kind\":1024,\"name\":\"funFact\",\"url\":\"modules/routes_form_keys_json.html#__type.funFact\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"routes/form_keys.json.__type\"},{\"id\":402,\"kind\":1024,\"name\":\"edus\",\"url\":\"modules/routes_form_keys_json.html#__type.edus\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"routes/form_keys.json.__type\"},{\"id\":403,\"kind\":1024,\"name\":\"edusInput\",\"url\":\"modules/routes_form_keys_json.html#__type.edusInput\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"routes/form_keys.json.__type\"},{\"id\":404,\"kind\":1024,\"name\":\"eduLevel\",\"url\":\"modules/routes_form_keys_json.html#__type.eduLevel\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"routes/form_keys.json.__type\"},{\"id\":405,\"kind\":1024,\"name\":\"eduLevelInput\",\"url\":\"modules/routes_form_keys_json.html#__type.eduLevelInput\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"routes/form_keys.json.__type\"},{\"id\":406,\"kind\":1024,\"name\":\"eduDuration\",\"url\":\"modules/routes_form_keys_json.html#__type.eduDuration\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"routes/form_keys.json.__type\"},{\"id\":407,\"kind\":1024,\"name\":\"eduYear\",\"url\":\"modules/routes_form_keys_json.html#__type.eduYear\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"routes/form_keys.json.__type\"},{\"id\":408,\"kind\":1024,\"name\":\"eduInstitute\",\"url\":\"modules/routes_form_keys_json.html#__type.eduInstitute\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"routes/form_keys.json.__type\"},{\"id\":409,\"kind\":1024,\"name\":\"appliedRole\",\"url\":\"modules/routes_form_keys_json.html#__type.appliedRole\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"routes/form_keys.json.__type\"},{\"id\":410,\"kind\":1024,\"name\":\"appliedRoleInput\",\"url\":\"modules/routes_form_keys_json.html#__type.appliedRoleInput\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"routes/form_keys.json.__type\"},{\"id\":411,\"kind\":1024,\"name\":\"bestSkill\",\"url\":\"modules/routes_form_keys_json.html#__type.bestSkill\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"routes/form_keys.json.__type\"},{\"id\":412,\"kind\":1024,\"name\":\"alumni\",\"url\":\"modules/routes_form_keys_json.html#__type.alumni\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"routes/form_keys.json.__type\"},{\"id\":413,\"kind\":1024,\"name\":\"studentCoach\",\"url\":\"modules/routes_form_keys_json.html#__type.studentCoach\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"routes/form_keys.json.__type\"},{\"id\":414,\"kind\":2,\"name\":\"routes/github\",\"url\":\"modules/routes_github.html\",\"classes\":\"tsd-kind-module\"},{\"id\":415,\"kind\":64,\"name\":\"getHome\",\"url\":\"modules/routes_github.html#getHome\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/github\"},{\"id\":416,\"kind\":64,\"name\":\"genState\",\"url\":\"modules/routes_github.html#genState\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/github\"},{\"id\":417,\"kind\":64,\"name\":\"checkState\",\"url\":\"modules/routes_github.html#checkState\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/github\"},{\"id\":418,\"kind\":64,\"name\":\"ghIdentity\",\"url\":\"modules/routes_github.html#ghIdentity\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/github\"},{\"id\":419,\"kind\":64,\"name\":\"ghExchangeAccessToken\",\"url\":\"modules/routes_github.html#ghExchangeAccessToken\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/github\"},{\"id\":420,\"kind\":64,\"name\":\"parseGHLogin\",\"url\":\"modules/routes_github.html#parseGHLogin\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/github\"},{\"id\":421,\"kind\":64,\"name\":\"githubNameChange\",\"url\":\"modules/routes_github.html#githubNameChange\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/github\"},{\"id\":422,\"kind\":64,\"name\":\"ghSignupOrLogin\",\"url\":\"modules/routes_github.html#ghSignupOrLogin\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/github\"},{\"id\":423,\"kind\":64,\"name\":\"getRouter\",\"url\":\"modules/routes_github.html#getRouter\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/github\"},{\"id\":424,\"kind\":32,\"name\":\"states\",\"url\":\"modules/routes_github.html#states\",\"classes\":\"tsd-kind-variable tsd-parent-kind-module\",\"parent\":\"routes/github\"},{\"id\":425,\"kind\":2,\"name\":\"routes/login\",\"url\":\"modules/routes_login.html\",\"classes\":\"tsd-kind-module\"},{\"id\":426,\"kind\":64,\"name\":\"login\",\"url\":\"modules/routes_login.html#login\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/login\"},{\"id\":427,\"kind\":64,\"name\":\"logout\",\"url\":\"modules/routes_login.html#logout\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/login\"},{\"id\":428,\"kind\":64,\"name\":\"getRouter\",\"url\":\"modules/routes_login.html#getRouter\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/login\"},{\"id\":429,\"kind\":2,\"name\":\"routes/project\",\"url\":\"modules/routes_project.html\",\"classes\":\"tsd-kind-module\"},{\"id\":430,\"kind\":64,\"name\":\"createProject\",\"url\":\"modules/routes_project.html#createProject\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/project\"},{\"id\":431,\"kind\":64,\"name\":\"listProjects\",\"url\":\"modules/routes_project.html#listProjects\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/project\"},{\"id\":432,\"kind\":64,\"name\":\"getProject\",\"url\":\"modules/routes_project.html#getProject\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/project\"},{\"id\":433,\"kind\":64,\"name\":\"modProject\",\"url\":\"modules/routes_project.html#modProject\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/project\"},{\"id\":434,\"kind\":64,\"name\":\"deleteProject\",\"url\":\"modules/routes_project.html#deleteProject\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/project\"},{\"id\":435,\"kind\":64,\"name\":\"getDraftedStudents\",\"url\":\"modules/routes_project.html#getDraftedStudents\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/project\"},{\"id\":436,\"kind\":64,\"name\":\"getFreeSpotsFor\",\"url\":\"modules/routes_project.html#getFreeSpotsFor\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/project\"},{\"id\":437,\"kind\":64,\"name\":\"createProjectRoleFor\",\"url\":\"modules/routes_project.html#createProjectRoleFor\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/project\"},{\"id\":438,\"kind\":64,\"name\":\"modProjectStudent\",\"url\":\"modules/routes_project.html#modProjectStudent\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/project\"},{\"id\":439,\"kind\":64,\"name\":\"unAssignStudent\",\"url\":\"modules/routes_project.html#unAssignStudent\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/project\"},{\"id\":440,\"kind\":64,\"name\":\"getProjectConflicts\",\"url\":\"modules/routes_project.html#getProjectConflicts\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/project\"},{\"id\":441,\"kind\":64,\"name\":\"filterProjects\",\"url\":\"modules/routes_project.html#filterProjects\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/project\"},{\"id\":442,\"kind\":64,\"name\":\"getRouter\",\"url\":\"modules/routes_project.html#getRouter\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/project\"},{\"id\":443,\"kind\":2,\"name\":\"routes/reset\",\"url\":\"modules/routes_reset.html\",\"classes\":\"tsd-kind-module\"},{\"id\":444,\"kind\":64,\"name\":\"sendMail\",\"url\":\"modules/routes_reset.html#sendMail\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/reset\"},{\"id\":445,\"kind\":64,\"name\":\"requestReset\",\"url\":\"modules/routes_reset.html#requestReset\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/reset\"},{\"id\":446,\"kind\":64,\"name\":\"checkCode\",\"url\":\"modules/routes_reset.html#checkCode\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/reset\"},{\"id\":447,\"kind\":64,\"name\":\"resetPassword\",\"url\":\"modules/routes_reset.html#resetPassword\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/reset\"},{\"id\":448,\"kind\":64,\"name\":\"getRouter\",\"url\":\"modules/routes_reset.html#getRouter\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/reset\"},{\"id\":449,\"kind\":64,\"name\":\"createEmail\",\"url\":\"modules/routes_reset.html#createEmail\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/reset\"},{\"id\":450,\"kind\":2,\"name\":\"routes/role\",\"url\":\"modules/routes_role.html\",\"classes\":\"tsd-kind-module\"},{\"id\":451,\"kind\":64,\"name\":\"listStudentRoles\",\"url\":\"modules/routes_role.html#listStudentRoles\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/role\"},{\"id\":452,\"kind\":64,\"name\":\"createStudentRole\",\"url\":\"modules/routes_role.html#createStudentRole\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/role\"},{\"id\":453,\"kind\":64,\"name\":\"getRouter\",\"url\":\"modules/routes_role.html#getRouter\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/role\"},{\"id\":454,\"kind\":2,\"name\":\"routes/session_key.json\",\"url\":\"modules/routes_session_key_json.html\",\"classes\":\"tsd-kind-module\"},{\"id\":455,\"kind\":1024,\"name\":\"export=\",\"url\":\"modules/routes_session_key_json.html#export_\",\"classes\":\"tsd-kind-property tsd-parent-kind-module\",\"parent\":\"routes/session_key.json\"},{\"id\":456,\"kind\":65536,\"name\":\"__type\",\"url\":\"modules/routes_session_key_json.html#__type\",\"classes\":\"tsd-kind-type-literal tsd-parent-kind-module\",\"parent\":\"routes/session_key.json\"},{\"id\":457,\"kind\":1024,\"name\":\"valid_period\",\"url\":\"modules/routes_session_key_json.html#__type.valid_period\",\"classes\":\"tsd-kind-property tsd-parent-kind-type-literal\",\"parent\":\"routes/session_key.json.__type\"},{\"id\":458,\"kind\":2,\"name\":\"routes/student\",\"url\":\"modules/routes_student.html\",\"classes\":\"tsd-kind-module\"},{\"id\":459,\"kind\":64,\"name\":\"listStudents\",\"url\":\"modules/routes_student.html#listStudents\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/student\"},{\"id\":460,\"kind\":64,\"name\":\"getStudent\",\"url\":\"modules/routes_student.html#getStudent\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/student\"},{\"id\":461,\"kind\":64,\"name\":\"deleteStudent\",\"url\":\"modules/routes_student.html#deleteStudent\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/student\"},{\"id\":462,\"kind\":64,\"name\":\"createStudentSuggestion\",\"url\":\"modules/routes_student.html#createStudentSuggestion\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/student\"},{\"id\":463,\"kind\":64,\"name\":\"getStudentSuggestions\",\"url\":\"modules/routes_student.html#getStudentSuggestions\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/student\"},{\"id\":464,\"kind\":64,\"name\":\"createStudentConfirmation\",\"url\":\"modules/routes_student.html#createStudentConfirmation\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/student\"},{\"id\":465,\"kind\":64,\"name\":\"filterStudents\",\"url\":\"modules/routes_student.html#filterStudents\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/student\"},{\"id\":466,\"kind\":64,\"name\":\"getRouter\",\"url\":\"modules/routes_student.html#getRouter\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/student\"},{\"id\":467,\"kind\":2,\"name\":\"routes/template\",\"url\":\"modules/routes_template.html\",\"classes\":\"tsd-kind-module\"},{\"id\":468,\"kind\":64,\"name\":\"getAllTemplates\",\"url\":\"modules/routes_template.html#getAllTemplates\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/template\"},{\"id\":469,\"kind\":64,\"name\":\"getSingleTemplate\",\"url\":\"modules/routes_template.html#getSingleTemplate\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/template\"},{\"id\":470,\"kind\":64,\"name\":\"createTemplate\",\"url\":\"modules/routes_template.html#createTemplate\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/template\"},{\"id\":471,\"kind\":64,\"name\":\"updateTemplate\",\"url\":\"modules/routes_template.html#updateTemplate\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/template\"},{\"id\":472,\"kind\":64,\"name\":\"deleteTemplate\",\"url\":\"modules/routes_template.html#deleteTemplate\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/template\"},{\"id\":473,\"kind\":64,\"name\":\"getRouter\",\"url\":\"modules/routes_template.html#getRouter\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/template\"},{\"id\":474,\"kind\":2,\"name\":\"routes/user\",\"url\":\"modules/routes_user.html\",\"classes\":\"tsd-kind-module\"},{\"id\":475,\"kind\":64,\"name\":\"listUsers\",\"url\":\"modules/routes_user.html#listUsers\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/user\"},{\"id\":476,\"kind\":64,\"name\":\"createUserRequest\",\"url\":\"modules/routes_user.html#createUserRequest\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/user\"},{\"id\":477,\"kind\":64,\"name\":\"setAccountStatus\",\"url\":\"modules/routes_user.html#setAccountStatus\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/user\"},{\"id\":478,\"kind\":64,\"name\":\"createUserAcceptance\",\"url\":\"modules/routes_user.html#createUserAcceptance\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/user\"},{\"id\":479,\"kind\":64,\"name\":\"deleteUserRequest\",\"url\":\"modules/routes_user.html#deleteUserRequest\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/user\"},{\"id\":480,\"kind\":64,\"name\":\"filterUsers\",\"url\":\"modules/routes_user.html#filterUsers\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/user\"},{\"id\":481,\"kind\":64,\"name\":\"userModSelf\",\"url\":\"modules/routes_user.html#userModSelf\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/user\"},{\"id\":482,\"kind\":64,\"name\":\"getCurrentUser\",\"url\":\"modules/routes_user.html#getCurrentUser\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/user\"},{\"id\":483,\"kind\":64,\"name\":\"getRouter\",\"url\":\"modules/routes_user.html#getRouter\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/user\"},{\"id\":484,\"kind\":2,\"name\":\"routes/verify\",\"url\":\"modules/routes_verify.html\",\"classes\":\"tsd-kind-module\"},{\"id\":485,\"kind\":64,\"name\":\"verifyKey\",\"url\":\"modules/routes_verify.html#verifyKey\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/verify\"},{\"id\":486,\"kind\":64,\"name\":\"getRouter\",\"url\":\"modules/routes_verify.html#getRouter\",\"classes\":\"tsd-kind-function tsd-parent-kind-module\",\"parent\":\"routes/verify\"}],\"index\":{\"version\":\"2.3.9\",\"fields\":[\"name\",\"parent\"],\"fieldVectors\":[[\"name/0\",[0,46.862]],[\"parent/0\",[]],[\"name/1\",[1,52.74]],[\"parent/1\",[0,4.542]],[\"name/2\",[2,57.849]],[\"parent/2\",[0,4.542]],[\"name/3\",[3,57.849]],[\"parent/3\",[0,4.542]],[\"name/4\",[4,44.856]],[\"parent/4\",[]],[\"name/5\",[5,57.849]],[\"parent/5\",[4,4.348]],[\"name/6\",[6,57.849]],[\"parent/6\",[4,4.348]],[\"name/7\",[7,57.849]],[\"parent/7\",[4,4.348]],[\"name/8\",[8,57.849]],[\"parent/8\",[4,4.348]],[\"name/9\",[9,40.502]],[\"parent/9\",[]],[\"name/10\",[10,52.74]],[\"parent/10\",[9,3.926]],[\"name/11\",[11,52.74]],[\"parent/11\",[9,3.926]],[\"name/12\",[12,57.849]],[\"parent/12\",[9,3.926]],[\"name/13\",[13,57.849]],[\"parent/13\",[9,3.926]],[\"name/14\",[14,57.849]],[\"parent/14\",[9,3.926]],[\"name/15\",[15,57.849]],[\"parent/15\",[9,3.926]],[\"name/16\",[16,57.849]],[\"parent/16\",[9,3.926]],[\"name/17\",[17,41.754]],[\"parent/17\",[]],[\"name/18\",[18,57.849]],[\"parent/18\",[17,4.047]],[\"name/19\",[19,52.74]],[\"parent/19\",[17,4.047]],[\"name/20\",[20,52.74]],[\"parent/20\",[17,4.047]],[\"name/21\",[21,57.849]],[\"parent/21\",[17,4.047]],[\"name/22\",[22,57.849]],[\"parent/22\",[17,4.047]],[\"name/23\",[23,57.849]],[\"parent/23\",[17,4.047]],[\"name/24\",[24,52.74]],[\"parent/24\",[]],[\"name/25\",[25,52.74]],[\"parent/25\",[24,5.112]],[\"name/26\",[26,36.646]],[\"parent/26\",[]],[\"name/27\",[27,57.849]],[\"parent/27\",[26,3.552]],[\"name/28\",[28,57.849]],[\"parent/28\",[26,3.552]],[\"name/29\",[29,57.849]],[\"parent/29\",[26,3.552]],[\"name/30\",[30,57.849]],[\"parent/30\",[26,3.552]],[\"name/31\",[31,57.849]],[\"parent/31\",[26,3.552]],[\"name/32\",[32,57.849]],[\"parent/32\",[26,3.552]],[\"name/33\",[33,57.849]],[\"parent/33\",[26,3.552]],[\"name/34\",[34,52.74]],[\"parent/34\",[26,3.552]],[\"name/35\",[35,57.849]],[\"parent/35\",[26,3.552]],[\"name/36\",[36,57.849]],[\"parent/36\",[26,3.552]],[\"name/37\",[37,57.849]],[\"parent/37\",[26,3.552]],[\"name/38\",[38,40.502]],[\"parent/38\",[]],[\"name/39\",[39,52.74]],[\"parent/39\",[38,3.926]],[\"name/40\",[40,57.849]],[\"parent/40\",[38,3.926]],[\"name/41\",[41,57.849]],[\"parent/41\",[38,3.926]],[\"name/42\",[42,57.849]],[\"parent/42\",[38,3.926]],[\"name/43\",[43,52.74]],[\"parent/43\",[38,3.926]],[\"name/44\",[44,57.849]],[\"parent/44\",[38,3.926]],[\"name/45\",[45,57.849]],[\"parent/45\",[38,3.926]],[\"name/46\",[46,40.502]],[\"parent/46\",[]],[\"name/47\",[47,57.849]],[\"parent/47\",[46,3.926]],[\"name/48\",[48,57.849]],[\"parent/48\",[46,3.926]],[\"name/49\",[49,57.849]],[\"parent/49\",[46,3.926]],[\"name/50\",[50,57.849]],[\"parent/50\",[46,3.926]],[\"name/51\",[51,52.74]],[\"parent/51\",[46,3.926]],[\"name/52\",[52,57.849]],[\"parent/52\",[46,3.926]],[\"name/53\",[53,57.849]],[\"parent/53\",[46,3.926]],[\"name/54\",[54,33.87]],[\"parent/54\",[]],[\"name/55\",[55,52.74]],[\"parent/55\",[54,3.283]],[\"name/56\",[56,57.849]],[\"parent/56\",[54,3.283]],[\"name/57\",[57,57.849]],[\"parent/57\",[54,3.283]],[\"name/58\",[58,57.849]],[\"parent/58\",[54,3.283]],[\"name/59\",[59,57.849]],[\"parent/59\",[54,3.283]],[\"name/60\",[60,57.849]],[\"parent/60\",[54,3.283]],[\"name/61\",[61,57.849]],[\"parent/61\",[54,3.283]],[\"name/62\",[62,57.849]],[\"parent/62\",[54,3.283]],[\"name/63\",[63,52.74]],[\"parent/63\",[54,3.283]],[\"name/64\",[64,57.849]],[\"parent/64\",[54,3.283]],[\"name/65\",[65,57.849]],[\"parent/65\",[54,3.283]],[\"name/66\",[66,57.849]],[\"parent/66\",[54,3.283]],[\"name/67\",[67,57.849]],[\"parent/67\",[54,3.283]],[\"name/68\",[68,57.849]],[\"parent/68\",[54,3.283]],[\"name/69\",[69,57.849]],[\"parent/69\",[54,3.283]],[\"name/70\",[70,27.091]],[\"parent/70\",[]],[\"name/71\",[71,52.74]],[\"parent/71\",[70,2.626]],[\"name/72\",[72,52.74]],[\"parent/72\",[73,4.348]],[\"name/73\",[74,49.376]],[\"parent/73\",[73,4.348]],[\"name/74\",[75,52.74]],[\"parent/74\",[73,4.348]],[\"name/75\",[76,52.74]],[\"parent/75\",[73,4.348]],[\"name/76\",[77,57.849]],[\"parent/76\",[73,4.348]],[\"name/77\",[78,52.74]],[\"parent/77\",[70,2.626]],[\"name/78\",[79,49.376]],[\"parent/78\",[80,4.348]],[\"name/79\",[72,52.74]],[\"parent/79\",[80,4.348]],[\"name/80\",[74,49.376]],[\"parent/80\",[80,4.348]],[\"name/81\",[75,52.74]],[\"parent/81\",[80,4.348]],[\"name/82\",[76,52.74]],[\"parent/82\",[80,4.348]],[\"name/83\",[55,52.74]],[\"parent/83\",[70,2.626]],[\"name/84\",[79,49.376]],[\"parent/84\",[81,4.348]],[\"name/85\",[82,52.74]],[\"parent/85\",[81,4.348]],[\"name/86\",[83,52.74]],[\"parent/86\",[81,4.348]],[\"name/87\",[84,52.74]],[\"parent/87\",[81,4.348]],[\"name/88\",[85,52.74]],[\"parent/88\",[81,4.348]],[\"name/89\",[63,52.74]],[\"parent/89\",[70,2.626]],[\"name/90\",[86,41.754]],[\"parent/90\",[87,4.348]],[\"name/91\",[82,52.74]],[\"parent/91\",[87,4.348]],[\"name/92\",[83,52.74]],[\"parent/92\",[87,4.348]],[\"name/93\",[84,52.74]],[\"parent/93\",[87,4.348]],[\"name/94\",[85,52.74]],[\"parent/94\",[87,4.348]],[\"name/95\",[88,52.74]],[\"parent/95\",[70,2.626]],[\"name/96\",[79,49.376]],[\"parent/96\",[89,4.186]],[\"name/97\",[90,49.376]],[\"parent/97\",[89,4.186]],[\"name/98\",[91,52.74]],[\"parent/98\",[89,4.186]],[\"name/99\",[92,49.376]],[\"parent/99\",[89,4.186]],[\"name/100\",[93,49.376]],[\"parent/100\",[89,4.186]],[\"name/101\",[94,49.376]],[\"parent/101\",[89,4.186]],[\"name/102\",[95,52.74]],[\"parent/102\",[70,2.626]],[\"name/103\",[96,46.862]],[\"parent/103\",[97,4.186]],[\"name/104\",[90,49.376]],[\"parent/104\",[97,4.186]],[\"name/105\",[91,52.74]],[\"parent/105\",[97,4.186]],[\"name/106\",[92,49.376]],[\"parent/106\",[97,4.186]],[\"name/107\",[93,49.376]],[\"parent/107\",[97,4.186]],[\"name/108\",[94,49.376]],[\"parent/108\",[97,4.186]],[\"name/109\",[19,52.74]],[\"parent/109\",[70,2.626]],[\"name/110\",[86,41.754]],[\"parent/110\",[98,4.348]],[\"name/111\",[99,46.862]],[\"parent/111\",[98,4.348]],[\"name/112\",[100,52.74]],[\"parent/112\",[98,4.348]],[\"name/113\",[101,52.74]],[\"parent/113\",[98,4.348]],[\"name/114\",[102,57.849]],[\"parent/114\",[98,4.348]],[\"name/115\",[20,52.74]],[\"parent/115\",[70,2.626]],[\"name/116\",[103,57.849]],[\"parent/116\",[104,4.542]],[\"name/117\",[86,41.754]],[\"parent/117\",[104,4.542]],[\"name/118\",[100,52.74]],[\"parent/118\",[104,4.542]],[\"name/119\",[101,52.74]],[\"parent/119\",[104,4.542]],[\"name/120\",[10,52.74]],[\"parent/120\",[70,2.626]],[\"name/121\",[96,46.862]],[\"parent/121\",[105,4.348]],[\"name/122\",[106,49.376]],[\"parent/122\",[105,4.348]],[\"name/123\",[107,49.376]],[\"parent/123\",[105,4.348]],[\"name/124\",[86,41.754]],[\"parent/124\",[105,4.348]],[\"name/125\",[108,52.74]],[\"parent/125\",[105,4.348]],[\"name/126\",[11,52.74]],[\"parent/126\",[70,2.626]],[\"name/127\",[109,57.849]],[\"parent/127\",[110,4.348]],[\"name/128\",[86,41.754]],[\"parent/128\",[110,4.348]],[\"name/129\",[107,49.376]],[\"parent/129\",[110,4.348]],[\"name/130\",[108,52.74]],[\"parent/130\",[110,4.348]],[\"name/131\",[106,49.376]],[\"parent/131\",[110,4.348]],[\"name/132\",[34,52.74]],[\"parent/132\",[70,2.626]],[\"name/133\",[96,46.862]],[\"parent/133\",[111,3.477]],[\"name/134\",[112,52.74]],[\"parent/134\",[111,3.477]],[\"name/135\",[113,52.74]],[\"parent/135\",[111,3.477]],[\"name/136\",[114,57.849]],[\"parent/136\",[111,3.477]],[\"name/137\",[115,52.74]],[\"parent/137\",[111,3.477]],[\"name/138\",[116,46.862]],[\"parent/138\",[111,3.477]],[\"name/139\",[117,52.74]],[\"parent/139\",[111,3.477]],[\"name/140\",[118,52.74]],[\"parent/140\",[111,3.477]],[\"name/141\",[119,52.74]],[\"parent/141\",[111,3.477]],[\"name/142\",[120,52.74]],[\"parent/142\",[111,3.477]],[\"name/143\",[121,52.74]],[\"parent/143\",[111,3.477]],[\"name/144\",[122,57.849]],[\"parent/144\",[111,3.477]],[\"name/145\",[123,57.849]],[\"parent/145\",[111,3.477]],[\"name/146\",[124,52.74]],[\"parent/146\",[70,2.626]],[\"name/147\",[116,46.862]],[\"parent/147\",[125,5.112]],[\"name/148\",[126,57.849]],[\"parent/148\",[125,5.112]],[\"name/149\",[127,49.376]],[\"parent/149\",[70,2.626]],[\"name/150\",[128,43.185]],[\"parent/150\",[129,4.186]],[\"name/151\",[116,46.862]],[\"parent/151\",[129,4.186]],[\"name/152\",[130,52.74]],[\"parent/152\",[129,4.186]],[\"name/153\",[131,52.74]],[\"parent/153\",[129,4.186]],[\"name/154\",[132,52.74]],[\"parent/154\",[129,4.186]],[\"name/155\",[133,46.862]],[\"parent/155\",[129,4.186]],[\"name/156\",[134,52.74]],[\"parent/156\",[70,2.626]],[\"name/157\",[135,44.856]],[\"parent/157\",[136,4.047]],[\"name/158\",[128,43.185]],[\"parent/158\",[136,4.047]],[\"name/159\",[116,46.862]],[\"parent/159\",[136,4.047]],[\"name/160\",[130,52.74]],[\"parent/160\",[136,4.047]],[\"name/161\",[131,52.74]],[\"parent/161\",[136,4.047]],[\"name/162\",[132,52.74]],[\"parent/162\",[136,4.047]],[\"name/163\",[133,46.862]],[\"parent/163\",[136,4.047]],[\"name/164\",[137,52.74]],[\"parent/164\",[70,2.626]],[\"name/165\",[135,44.856]],[\"parent/165\",[138,4.786]],[\"name/166\",[139,46.862]],[\"parent/166\",[138,4.786]],[\"name/167\",[133,46.862]],[\"parent/167\",[138,4.786]],[\"name/168\",[140,52.74]],[\"parent/168\",[70,2.626]],[\"name/169\",[106,49.376]],[\"parent/169\",[141,4.542]],[\"name/170\",[135,44.856]],[\"parent/170\",[141,4.542]],[\"name/171\",[139,46.862]],[\"parent/171\",[141,4.542]],[\"name/172\",[133,46.862]],[\"parent/172\",[141,4.542]],[\"name/173\",[142,52.74]],[\"parent/173\",[70,2.626]],[\"name/174\",[139,46.862]],[\"parent/174\",[143,5.112]],[\"name/175\",[128,43.185]],[\"parent/175\",[143,5.112]],[\"name/176\",[51,52.74]],[\"parent/176\",[70,2.626]],[\"name/177\",[144,49.376]],[\"parent/177\",[145,5.112]],[\"name/178\",[128,43.185]],[\"parent/178\",[145,5.112]],[\"name/179\",[39,52.74]],[\"parent/179\",[70,2.626]],[\"name/180\",[99,46.862]],[\"parent/180\",[146,4.186]],[\"name/181\",[147,52.74]],[\"parent/181\",[146,4.186]],[\"name/182\",[144,49.376]],[\"parent/182\",[146,4.186]],[\"name/183\",[148,52.74]],[\"parent/183\",[146,4.186]],[\"name/184\",[149,52.74]],[\"parent/184\",[146,4.186]],[\"name/185\",[150,57.849]],[\"parent/185\",[146,4.186]],[\"name/186\",[43,52.74]],[\"parent/186\",[70,2.626]],[\"name/187\",[151,57.849]],[\"parent/187\",[152,4.047]],[\"name/188\",[99,46.862]],[\"parent/188\",[152,4.047]],[\"name/189\",[147,52.74]],[\"parent/189\",[152,4.047]],[\"name/190\",[144,49.376]],[\"parent/190\",[152,4.047]],[\"name/191\",[148,52.74]],[\"parent/191\",[152,4.047]],[\"name/192\",[149,52.74]],[\"parent/192\",[152,4.047]],[\"name/193\",[153,57.849]],[\"parent/193\",[152,4.047]],[\"name/194\",[25,52.74]],[\"parent/194\",[70,2.626]],[\"name/195\",[86,41.754]],[\"parent/195\",[154,4.348]],[\"name/196\",[96,46.862]],[\"parent/196\",[154,4.348]],[\"name/197\",[135,44.856]],[\"parent/197\",[154,4.348]],[\"name/198\",[155,57.849]],[\"parent/198\",[154,4.348]],[\"name/199\",[107,49.376]],[\"parent/199\",[154,4.348]],[\"name/200\",[156,52.74]],[\"parent/200\",[70,2.626]],[\"name/201\",[135,44.856]],[\"parent/201\",[157,5.112]],[\"name/202\",[86,41.754]],[\"parent/202\",[157,5.112]],[\"name/203\",[1,52.74]],[\"parent/203\",[70,2.626]],[\"name/204\",[99,46.862]],[\"parent/204\",[158,5.112]],[\"name/205\",[139,46.862]],[\"parent/205\",[158,5.112]],[\"name/206\",[159,49.376]],[\"parent/206\",[70,2.626]],[\"name/207\",[160,52.74]],[\"parent/207\",[161,4.348]],[\"name/208\",[128,43.185]],[\"parent/208\",[161,4.348]],[\"name/209\",[162,52.74]],[\"parent/209\",[161,4.348]],[\"name/210\",[163,52.74]],[\"parent/210\",[161,4.348]],[\"name/211\",[164,52.74]],[\"parent/211\",[161,4.348]],[\"name/212\",[165,49.376]],[\"parent/212\",[70,2.626]],[\"name/213\",[166,57.849]],[\"parent/213\",[167,4.186]],[\"name/214\",[160,52.74]],[\"parent/214\",[167,4.186]],[\"name/215\",[128,43.185]],[\"parent/215\",[167,4.186]],[\"name/216\",[162,52.74]],[\"parent/216\",[167,4.186]],[\"name/217\",[163,52.74]],[\"parent/217\",[167,4.186]],[\"name/218\",[164,52.74]],[\"parent/218\",[167,4.186]],[\"name/219\",[168,57.849]],[\"parent/219\",[70,2.626]],[\"name/220\",[169,57.849]],[\"parent/220\",[70,2.626]],[\"name/221\",[170,57.849]],[\"parent/221\",[70,2.626]],[\"name/222\",[171,57.849]],[\"parent/222\",[70,2.626]],[\"name/223\",[172,57.849]],[\"parent/223\",[70,2.626]],[\"name/224\",[173,57.849]],[\"parent/224\",[70,2.626]],[\"name/225\",[174,35.876]],[\"parent/225\",[]],[\"name/226\",[175,57.849]],[\"parent/226\",[174,3.477]],[\"name/227\",[176,57.849]],[\"parent/227\",[174,3.477]],[\"name/228\",[177,57.849]],[\"parent/228\",[174,3.477]],[\"name/229\",[178,57.849]],[\"parent/229\",[174,3.477]],[\"name/230\",[179,57.849]],[\"parent/230\",[174,3.477]],[\"name/231\",[180,57.849]],[\"parent/231\",[174,3.477]],[\"name/232\",[124,52.74]],[\"parent/232\",[174,3.477]],[\"name/233\",[181,57.849]],[\"parent/233\",[174,3.477]],[\"name/234\",[182,57.849]],[\"parent/234\",[174,3.477]],[\"name/235\",[183,57.849]],[\"parent/235\",[174,3.477]],[\"name/236\",[184,57.849]],[\"parent/236\",[174,3.477]],[\"name/237\",[185,57.849]],[\"parent/237\",[174,3.477]],[\"name/238\",[186,44.856]],[\"parent/238\",[]],[\"name/239\",[187,57.849]],[\"parent/239\",[186,4.348]],[\"name/240\",[188,57.849]],[\"parent/240\",[186,4.348]],[\"name/241\",[189,57.849]],[\"parent/241\",[186,4.348]],[\"name/242\",[190,57.849]],[\"parent/242\",[186,4.348]],[\"name/243\",[191,39.39]],[\"parent/243\",[]],[\"name/244\",[71,52.74]],[\"parent/244\",[191,3.818]],[\"name/245\",[192,57.849]],[\"parent/245\",[191,3.818]],[\"name/246\",[193,57.849]],[\"parent/246\",[191,3.818]],[\"name/247\",[194,57.849]],[\"parent/247\",[191,3.818]],[\"name/248\",[195,57.849]],[\"parent/248\",[191,3.818]],[\"name/249\",[196,57.849]],[\"parent/249\",[191,3.818]],[\"name/250\",[78,52.74]],[\"parent/250\",[191,3.818]],[\"name/251\",[197,57.849]],[\"parent/251\",[191,3.818]],[\"name/252\",[198,31.223]],[\"parent/252\",[]],[\"name/253\",[127,49.376]],[\"parent/253\",[198,3.026]],[\"name/254\",[199,57.849]],[\"parent/254\",[198,3.026]],[\"name/255\",[200,57.849]],[\"parent/255\",[198,3.026]],[\"name/256\",[201,57.849]],[\"parent/256\",[198,3.026]],[\"name/257\",[202,57.849]],[\"parent/257\",[198,3.026]],[\"name/258\",[203,57.849]],[\"parent/258\",[198,3.026]],[\"name/259\",[204,57.849]],[\"parent/259\",[198,3.026]],[\"name/260\",[205,57.849]],[\"parent/260\",[198,3.026]],[\"name/261\",[206,57.849]],[\"parent/261\",[198,3.026]],[\"name/262\",[207,57.849]],[\"parent/262\",[198,3.026]],[\"name/263\",[208,57.849]],[\"parent/263\",[198,3.026]],[\"name/264\",[209,57.849]],[\"parent/264\",[198,3.026]],[\"name/265\",[210,57.849]],[\"parent/265\",[198,3.026]],[\"name/266\",[211,57.849]],[\"parent/266\",[198,3.026]],[\"name/267\",[212,57.849]],[\"parent/267\",[198,3.026]],[\"name/268\",[134,52.74]],[\"parent/268\",[198,3.026]],[\"name/269\",[213,52.74]],[\"parent/269\",[198,3.026]],[\"name/270\",[214,57.849]],[\"parent/270\",[198,3.026]],[\"name/271\",[215,57.849]],[\"parent/271\",[198,3.026]],[\"name/272\",[216,52.74]],[\"parent/272\",[198,3.026]],[\"name/273\",[217,39.39]],[\"parent/273\",[]],[\"name/274\",[137,52.74]],[\"parent/274\",[217,3.818]],[\"name/275\",[218,57.849]],[\"parent/275\",[217,3.818]],[\"name/276\",[219,57.849]],[\"parent/276\",[217,3.818]],[\"name/277\",[220,57.849]],[\"parent/277\",[217,3.818]],[\"name/278\",[140,52.74]],[\"parent/278\",[217,3.818]],[\"name/279\",[221,57.849]],[\"parent/279\",[217,3.818]],[\"name/280\",[222,57.849]],[\"parent/280\",[217,3.818]],[\"name/281\",[223,57.849]],[\"parent/281\",[217,3.818]],[\"name/282\",[224,49.376]],[\"parent/282\",[]],[\"name/283\",[156,52.74]],[\"parent/283\",[224,4.786]],[\"name/284\",[225,57.849]],[\"parent/284\",[224,4.786]],[\"name/285\",[226,39.39]],[\"parent/285\",[]],[\"name/286\",[227,57.849]],[\"parent/286\",[226,3.818]],[\"name/287\",[228,57.849]],[\"parent/287\",[226,3.818]],[\"name/288\",[229,57.849]],[\"parent/288\",[226,3.818]],[\"name/289\",[230,57.849]],[\"parent/289\",[226,3.818]],[\"name/290\",[231,57.849]],[\"parent/290\",[226,3.818]],[\"name/291\",[142,52.74]],[\"parent/291\",[226,3.818]],[\"name/292\",[232,57.849]],[\"parent/292\",[226,3.818]],[\"name/293\",[233,57.849]],[\"parent/293\",[226,3.818]],[\"name/294\",[234,43.185]],[\"parent/294\",[]],[\"name/295\",[235,57.849]],[\"parent/295\",[234,4.186]],[\"name/296\",[236,57.849]],[\"parent/296\",[234,4.186]],[\"name/297\",[237,57.849]],[\"parent/297\",[234,4.186]],[\"name/298\",[238,57.849]],[\"parent/298\",[234,4.186]],[\"name/299\",[239,57.849]],[\"parent/299\",[234,4.186]],[\"name/300\",[240,40.502]],[\"parent/300\",[]],[\"name/301\",[88,52.74]],[\"parent/301\",[240,3.926]],[\"name/302\",[241,57.849]],[\"parent/302\",[240,3.926]],[\"name/303\",[242,52.74]],[\"parent/303\",[240,3.926]],[\"name/304\",[95,52.74]],[\"parent/304\",[240,3.926]],[\"name/305\",[243,52.74]],[\"parent/305\",[240,3.926]],[\"name/306\",[244,57.849]],[\"parent/306\",[240,3.926]],[\"name/307\",[245,52.74]],[\"parent/307\",[240,3.926]],[\"name/308\",[246,41.754]],[\"parent/308\",[]],[\"name/309\",[247,52.74]],[\"parent/309\",[246,4.047]],[\"name/310\",[248,57.849]],[\"parent/310\",[246,4.047]],[\"name/311\",[249,57.849]],[\"parent/311\",[246,4.047]],[\"name/312\",[159,49.376]],[\"parent/312\",[246,4.047]],[\"name/313\",[165,49.376]],[\"parent/313\",[246,4.047]],[\"name/314\",[250,52.74]],[\"parent/314\",[246,4.047]],[\"name/315\",[251,44.856]],[\"parent/315\",[]],[\"name/316\",[252,57.849]],[\"parent/316\",[251,4.348]],[\"name/317\",[253,57.849]],[\"parent/317\",[251,4.348]],[\"name/318\",[254,57.849]],[\"parent/318\",[251,4.348]],[\"name/319\",[255,35.876]],[\"parent/319\",[251,4.348]],[\"name/320\",[256,43.185]],[\"parent/320\",[]],[\"name/321\",[257,57.849]],[\"parent/321\",[256,4.186]],[\"name/322\",[258,57.849]],[\"parent/322\",[256,4.186]],[\"name/323\",[259,57.849]],[\"parent/323\",[256,4.186]],[\"name/324\",[260,57.849]],[\"parent/324\",[256,4.186]],[\"name/325\",[255,35.876]],[\"parent/325\",[256,4.186]],[\"name/326\",[261,44.856]],[\"parent/326\",[]],[\"name/327\",[262,57.849]],[\"parent/327\",[261,4.348]],[\"name/328\",[263,57.849]],[\"parent/328\",[261,4.348]],[\"name/329\",[264,57.849]],[\"parent/329\",[261,4.348]],[\"name/330\",[255,35.876]],[\"parent/330\",[261,4.348]],[\"name/331\",[265,24.176]],[\"parent/331\",[]],[\"name/332\",[266,57.849]],[\"parent/332\",[265,2.343]],[\"name/333\",[267,57.849]],[\"parent/333\",[265,2.343]],[\"name/334\",[268,57.849]],[\"parent/334\",[265,2.343]],[\"name/335\",[269,57.849]],[\"parent/335\",[265,2.343]],[\"name/336\",[270,57.849]],[\"parent/336\",[265,2.343]],[\"name/337\",[271,57.849]],[\"parent/337\",[265,2.343]],[\"name/338\",[272,57.849]],[\"parent/338\",[265,2.343]],[\"name/339\",[273,57.849]],[\"parent/339\",[265,2.343]],[\"name/340\",[274,57.849]],[\"parent/340\",[265,2.343]],[\"name/341\",[275,57.849]],[\"parent/341\",[265,2.343]],[\"name/342\",[276,57.849]],[\"parent/342\",[265,2.343]],[\"name/343\",[277,57.849]],[\"parent/343\",[265,2.343]],[\"name/344\",[278,57.849]],[\"parent/344\",[265,2.343]],[\"name/345\",[279,57.849]],[\"parent/345\",[265,2.343]],[\"name/346\",[280,57.849]],[\"parent/346\",[265,2.343]],[\"name/347\",[281,57.849]],[\"parent/347\",[265,2.343]],[\"name/348\",[282,57.849]],[\"parent/348\",[265,2.343]],[\"name/349\",[283,57.849]],[\"parent/349\",[265,2.343]],[\"name/350\",[284,57.849]],[\"parent/350\",[265,2.343]],[\"name/351\",[285,57.849]],[\"parent/351\",[265,2.343]],[\"name/352\",[286,57.849]],[\"parent/352\",[265,2.343]],[\"name/353\",[287,57.849]],[\"parent/353\",[265,2.343]],[\"name/354\",[288,57.849]],[\"parent/354\",[265,2.343]],[\"name/355\",[289,57.849]],[\"parent/355\",[265,2.343]],[\"name/356\",[290,57.849]],[\"parent/356\",[265,2.343]],[\"name/357\",[291,57.849]],[\"parent/357\",[265,2.343]],[\"name/358\",[292,57.849]],[\"parent/358\",[265,2.343]],[\"name/359\",[293,57.849]],[\"parent/359\",[265,2.343]],[\"name/360\",[294,57.849]],[\"parent/360\",[265,2.343]],[\"name/361\",[295,57.849]],[\"parent/361\",[265,2.343]],[\"name/362\",[296,57.849]],[\"parent/362\",[265,2.343]],[\"name/363\",[297,57.849]],[\"parent/363\",[265,2.343]],[\"name/364\",[298,57.849]],[\"parent/364\",[265,2.343]],[\"name/365\",[299,57.849]],[\"parent/365\",[265,2.343]],[\"name/366\",[300,57.849]],[\"parent/366\",[265,2.343]],[\"name/367\",[301,57.849]],[\"parent/367\",[265,2.343]],[\"name/368\",[302,57.849]],[\"parent/368\",[265,2.343]],[\"name/369\",[303,57.849]],[\"parent/369\",[265,2.343]],[\"name/370\",[304,57.849]],[\"parent/370\",[265,2.343]],[\"name/371\",[305,57.849]],[\"parent/371\",[265,2.343]],[\"name/372\",[306,57.849]],[\"parent/372\",[265,2.343]],[\"name/373\",[255,35.876]],[\"parent/373\",[265,2.343]],[\"name/374\",[307,49.376]],[\"parent/374\",[]],[\"name/375\",[308,52.74]],[\"parent/375\",[307,4.786]],[\"name/376\",[309,52.74]],[\"parent/376\",[307,4.786]],[\"name/377\",[310,57.849]],[\"parent/377\",[311,2.487]],[\"name/378\",[312,57.849]],[\"parent/378\",[311,2.487]],[\"name/379\",[313,57.849]],[\"parent/379\",[311,2.487]],[\"name/380\",[112,52.74]],[\"parent/380\",[311,2.487]],[\"name/381\",[314,57.849]],[\"parent/381\",[311,2.487]],[\"name/382\",[74,49.376]],[\"parent/382\",[311,2.487]],[\"name/383\",[93,49.376]],[\"parent/383\",[311,2.487]],[\"name/384\",[315,57.849]],[\"parent/384\",[311,2.487]],[\"name/385\",[90,49.376]],[\"parent/385\",[311,2.487]],[\"name/386\",[316,57.849]],[\"parent/386\",[311,2.487]],[\"name/387\",[317,57.849]],[\"parent/387\",[311,2.487]],[\"name/388\",[318,57.849]],[\"parent/388\",[311,2.487]],[\"name/389\",[319,57.849]],[\"parent/389\",[311,2.487]],[\"name/390\",[320,57.849]],[\"parent/390\",[311,2.487]],[\"name/391\",[321,57.849]],[\"parent/391\",[311,2.487]],[\"name/392\",[92,49.376]],[\"parent/392\",[311,2.487]],[\"name/393\",[322,57.849]],[\"parent/393\",[311,2.487]],[\"name/394\",[323,57.849]],[\"parent/394\",[311,2.487]],[\"name/395\",[324,57.849]],[\"parent/395\",[311,2.487]],[\"name/396\",[325,57.849]],[\"parent/396\",[311,2.487]],[\"name/397\",[326,57.849]],[\"parent/397\",[311,2.487]],[\"name/398\",[327,57.849]],[\"parent/398\",[311,2.487]],[\"name/399\",[328,57.849]],[\"parent/399\",[311,2.487]],[\"name/400\",[329,57.849]],[\"parent/400\",[311,2.487]],[\"name/401\",[113,52.74]],[\"parent/401\",[311,2.487]],[\"name/402\",[117,52.74]],[\"parent/402\",[311,2.487]],[\"name/403\",[330,57.849]],[\"parent/403\",[311,2.487]],[\"name/404\",[118,52.74]],[\"parent/404\",[311,2.487]],[\"name/405\",[331,57.849]],[\"parent/405\",[311,2.487]],[\"name/406\",[119,52.74]],[\"parent/406\",[311,2.487]],[\"name/407\",[120,52.74]],[\"parent/407\",[311,2.487]],[\"name/408\",[121,52.74]],[\"parent/408\",[311,2.487]],[\"name/409\",[332,57.849]],[\"parent/409\",[311,2.487]],[\"name/410\",[333,57.849]],[\"parent/410\",[311,2.487]],[\"name/411\",[334,57.849]],[\"parent/411\",[311,2.487]],[\"name/412\",[94,49.376]],[\"parent/412\",[311,2.487]],[\"name/413\",[115,52.74]],[\"parent/413\",[311,2.487]],[\"name/414\",[335,37.48]],[\"parent/414\",[]],[\"name/415\",[336,57.849]],[\"parent/415\",[335,3.633]],[\"name/416\",[337,57.849]],[\"parent/416\",[335,3.633]],[\"name/417\",[338,57.849]],[\"parent/417\",[335,3.633]],[\"name/418\",[339,57.849]],[\"parent/418\",[335,3.633]],[\"name/419\",[340,57.849]],[\"parent/419\",[335,3.633]],[\"name/420\",[341,57.849]],[\"parent/420\",[335,3.633]],[\"name/421\",[342,57.849]],[\"parent/421\",[335,3.633]],[\"name/422\",[343,57.849]],[\"parent/422\",[335,3.633]],[\"name/423\",[255,35.876]],[\"parent/423\",[335,3.633]],[\"name/424\",[344,57.849]],[\"parent/424\",[335,3.633]],[\"name/425\",[345,46.862]],[\"parent/425\",[]],[\"name/426\",[346,57.849]],[\"parent/426\",[345,4.542]],[\"name/427\",[347,57.849]],[\"parent/427\",[345,4.542]],[\"name/428\",[255,35.876]],[\"parent/428\",[345,4.542]],[\"name/429\",[348,35.162]],[\"parent/429\",[]],[\"name/430\",[127,49.376]],[\"parent/430\",[348,3.408]],[\"name/431\",[349,57.849]],[\"parent/431\",[348,3.408]],[\"name/432\",[350,57.849]],[\"parent/432\",[348,3.408]],[\"name/433\",[351,57.849]],[\"parent/433\",[348,3.408]],[\"name/434\",[213,52.74]],[\"parent/434\",[348,3.408]],[\"name/435\",[352,57.849]],[\"parent/435\",[348,3.408]],[\"name/436\",[353,57.849]],[\"parent/436\",[348,3.408]],[\"name/437\",[354,57.849]],[\"parent/437\",[348,3.408]],[\"name/438\",[355,57.849]],[\"parent/438\",[348,3.408]],[\"name/439\",[356,57.849]],[\"parent/439\",[348,3.408]],[\"name/440\",[357,57.849]],[\"parent/440\",[348,3.408]],[\"name/441\",[216,52.74]],[\"parent/441\",[348,3.408]],[\"name/442\",[255,35.876]],[\"parent/442\",[348,3.408]],[\"name/443\",[358,41.754]],[\"parent/443\",[]],[\"name/444\",[359,57.849]],[\"parent/444\",[358,4.047]],[\"name/445\",[360,57.849]],[\"parent/445\",[358,4.047]],[\"name/446\",[361,57.849]],[\"parent/446\",[358,4.047]],[\"name/447\",[362,57.849]],[\"parent/447\",[358,4.047]],[\"name/448\",[255,35.876]],[\"parent/448\",[358,4.047]],[\"name/449\",[363,57.849]],[\"parent/449\",[358,4.047]],[\"name/450\",[364,46.862]],[\"parent/450\",[]],[\"name/451\",[365,57.849]],[\"parent/451\",[364,4.542]],[\"name/452\",[366,57.849]],[\"parent/452\",[364,4.542]],[\"name/453\",[255,35.876]],[\"parent/453\",[364,4.542]],[\"name/454\",[367,49.376]],[\"parent/454\",[]],[\"name/455\",[308,52.74]],[\"parent/455\",[367,4.786]],[\"name/456\",[309,52.74]],[\"parent/456\",[367,4.786]],[\"name/457\",[368,57.849]],[\"parent/457\",[369,5.607]],[\"name/458\",[370,39.39]],[\"parent/458\",[]],[\"name/459\",[371,57.849]],[\"parent/459\",[370,3.818]],[\"name/460\",[242,52.74]],[\"parent/460\",[370,3.818]],[\"name/461\",[243,52.74]],[\"parent/461\",[370,3.818]],[\"name/462\",[372,57.849]],[\"parent/462\",[370,3.818]],[\"name/463\",[373,57.849]],[\"parent/463\",[370,3.818]],[\"name/464\",[374,57.849]],[\"parent/464\",[370,3.818]],[\"name/465\",[245,52.74]],[\"parent/465\",[370,3.818]],[\"name/466\",[255,35.876]],[\"parent/466\",[370,3.818]],[\"name/467\",[375,41.754]],[\"parent/467\",[]],[\"name/468\",[247,52.74]],[\"parent/468\",[375,4.047]],[\"name/469\",[376,57.849]],[\"parent/469\",[375,4.047]],[\"name/470\",[159,49.376]],[\"parent/470\",[375,4.047]],[\"name/471\",[165,49.376]],[\"parent/471\",[375,4.047]],[\"name/472\",[250,52.74]],[\"parent/472\",[375,4.047]],[\"name/473\",[255,35.876]],[\"parent/473\",[375,4.047]],[\"name/474\",[377,38.389]],[\"parent/474\",[]],[\"name/475\",[378,57.849]],[\"parent/475\",[377,3.721]],[\"name/476\",[379,57.849]],[\"parent/476\",[377,3.721]],[\"name/477\",[380,57.849]],[\"parent/477\",[377,3.721]],[\"name/478\",[381,57.849]],[\"parent/478\",[377,3.721]],[\"name/479\",[382,57.849]],[\"parent/479\",[377,3.721]],[\"name/480\",[383,57.849]],[\"parent/480\",[377,3.721]],[\"name/481\",[384,57.849]],[\"parent/481\",[377,3.721]],[\"name/482\",[385,57.849]],[\"parent/482\",[377,3.721]],[\"name/483\",[255,35.876]],[\"parent/483\",[377,3.721]],[\"name/484\",[386,49.376]],[\"parent/484\",[]],[\"name/485\",[387,57.849]],[\"parent/485\",[386,4.786]],[\"name/486\",[255,35.876]],[\"parent/486\",[386,4.786]]],\"invertedIndex\":[[\"__type\",{\"_index\":309,\"name\":{\"376\":{},\"456\":{}},\"parent\":{}}],[\"accountstatus\",{\"_index\":85,\"name\":{\"88\":{},\"94\":{}},\"parent\":{}}],[\"addattachmentstodatabase\",{\"_index\":304,\"name\":{\"370\":{}},\"parent\":{}}],[\"addjobapplicationtodatabase\",{\"_index\":302,\"name\":{\"368\":{}},\"parent\":{}}],[\"addpersontodatabase\",{\"_index\":300,\"name\":{\"366\":{}},\"parent\":{}}],[\"addpronouns\",{\"_index\":316,\"name\":{\"386\":{}},\"parent\":{}}],[\"addrolestodatabase\",{\"_index\":305,\"name\":{\"371\":{}},\"parent\":{}}],[\"addsessionkey\",{\"_index\":235,\"name\":{\"295\":{}},\"parent\":{}}],[\"addskillstodatabase\",{\"_index\":303,\"name\":{\"369\":{}},\"parent\":{}}],[\"addstudenttodatabase\",{\"_index\":301,\"name\":{\"367\":{}},\"parent\":{}}],[\"addstudenttoproject\",{\"_index\":25,\"name\":{\"25\":{},\"194\":{}},\"parent\":{}}],[\"alumni\",{\"_index\":94,\"name\":{\"101\":{},\"108\":{},\"412\":{}},\"parent\":{}}],[\"appliedrole\",{\"_index\":332,\"name\":{\"409\":{}},\"parent\":{}}],[\"appliedroleinput\",{\"_index\":333,\"name\":{\"410\":{}},\"parent\":{}}],[\"bestskill\",{\"_index\":334,\"name\":{\"411\":{}},\"parent\":{}}],[\"birthname\",{\"_index\":314,\"name\":{\"381\":{}},\"parent\":{}}],[\"cc\",{\"_index\":164,\"name\":{\"211\":{},\"218\":{}},\"parent\":{}}],[\"changeemailstatusofjobapplication\",{\"_index\":32,\"name\":{\"32\":{}},\"parent\":{}}],[\"checkcode\",{\"_index\":361,\"name\":{\"446\":{}},\"parent\":{}}],[\"checkiffinalevaluationexists\",{\"_index\":18,\"name\":{\"18\":{}},\"parent\":{}}],[\"checkquestionsexist\",{\"_index\":269,\"name\":{\"335\":{}},\"parent\":{}}],[\"checksessionkey\",{\"_index\":236,\"name\":{\"296\":{}},\"parent\":{}}],[\"checkstate\",{\"_index\":338,\"name\":{\"417\":{}},\"parent\":{}}],[\"checkwordinanswer\",{\"_index\":268,\"name\":{\"334\":{}},\"parent\":{}}],[\"content\",{\"_index\":162,\"name\":{\"209\":{},\"216\":{}},\"parent\":{}}],[\"contractid\",{\"_index\":109,\"name\":{\"127\":{}},\"parent\":{}}],[\"contractsbyproject\",{\"_index\":15,\"name\":{\"15\":{}},\"parent\":{}}],[\"contractsforstudent\",{\"_index\":14,\"name\":{\"14\":{}},\"parent\":{}}],[\"contractstatus\",{\"_index\":108,\"name\":{\"125\":{},\"130\":{}},\"parent\":{}}],[\"createappliedrole\",{\"_index\":1,\"name\":{\"1\":{},\"203\":{}},\"parent\":{}}],[\"createattachment\",{\"_index\":5,\"name\":{\"5\":{}},\"parent\":{}}],[\"createcontract\",{\"_index\":10,\"name\":{\"10\":{},\"120\":{}},\"parent\":{}}],[\"createdat\",{\"_index\":123,\"name\":{\"145\":{}},\"parent\":{}}],[\"createemail\",{\"_index\":363,\"name\":{\"449\":{}},\"parent\":{}}],[\"createevaluationforstudent\",{\"_index\":19,\"name\":{\"19\":{},\"109\":{}},\"parent\":{}}],[\"createform\",{\"_index\":306,\"name\":{\"372\":{}},\"parent\":{}}],[\"createjobapplication\",{\"_index\":34,\"name\":{\"34\":{},\"132\":{}},\"parent\":{}}],[\"createjobapplicationskill\",{\"_index\":39,\"name\":{\"39\":{},\"179\":{}},\"parent\":{}}],[\"createlanguage\",{\"_index\":47,\"name\":{\"47\":{}},\"parent\":{}}],[\"createloginuser\",{\"_index\":55,\"name\":{\"55\":{},\"83\":{}},\"parent\":{}}],[\"createorupdatereset\",{\"_index\":187,\"name\":{\"239\":{}},\"parent\":{}}],[\"createosoc\",{\"_index\":175,\"name\":{\"226\":{}},\"parent\":{}}],[\"createperson\",{\"_index\":71,\"name\":{\"71\":{},\"244\":{}},\"parent\":{}}],[\"createproject\",{\"_index\":127,\"name\":{\"149\":{},\"253\":{},\"430\":{}},\"parent\":{}}],[\"createprojectrole\",{\"_index\":137,\"name\":{\"164\":{},\"274\":{}},\"parent\":{}}],[\"createprojectrolefor\",{\"_index\":354,\"name\":{\"437\":{}},\"parent\":{}}],[\"createprojectuser\",{\"_index\":156,\"name\":{\"200\":{},\"283\":{}},\"parent\":{}}],[\"createrole\",{\"_index\":227,\"name\":{\"286\":{}},\"parent\":{}}],[\"createstudent\",{\"_index\":88,\"name\":{\"95\":{},\"301\":{}},\"parent\":{}}],[\"createstudentconfirmation\",{\"_index\":374,\"name\":{\"464\":{}},\"parent\":{}}],[\"createstudentrole\",{\"_index\":366,\"name\":{\"452\":{}},\"parent\":{}}],[\"createstudentsuggestion\",{\"_index\":372,\"name\":{\"462\":{}},\"parent\":{}}],[\"createtemplate\",{\"_index\":159,\"name\":{\"206\":{},\"312\":{},\"470\":{}},\"parent\":{}}],[\"createuseracceptance\",{\"_index\":381,\"name\":{\"478\":{}},\"parent\":{}}],[\"createuserrequest\",{\"_index\":379,\"name\":{\"476\":{}},\"parent\":{}}],[\"cvlink\",{\"_index\":324,\"name\":{\"395\":{}},\"parent\":{}}],[\"cvupload\",{\"_index\":323,\"name\":{\"394\":{}},\"parent\":{}}],[\"decision\",{\"_index\":100,\"name\":{\"112\":{},\"118\":{}},\"parent\":{}}],[\"deleteadmin\",{\"_index\":254,\"name\":{\"318\":{}},\"parent\":{}}],[\"deleteallattachmentsforapplication\",{\"_index\":7,\"name\":{\"7\":{}},\"parent\":{}}],[\"deleteappliedrolesbyjobapplication\",{\"_index\":3,\"name\":{\"3\":{}},\"parent\":{}}],[\"deleteattachment\",{\"_index\":6,\"name\":{\"6\":{}},\"parent\":{}}],[\"deletecoach\",{\"_index\":259,\"name\":{\"323\":{}},\"parent\":{}}],[\"deleteevaluationsbyjobapplication\",{\"_index\":23,\"name\":{\"23\":{}},\"parent\":{}}],[\"deletejobapplication\",{\"_index\":33,\"name\":{\"33\":{}},\"parent\":{}}],[\"deletejobapplicationsfromstudent\",{\"_index\":31,\"name\":{\"31\":{}},\"parent\":{}}],[\"deletejobapplicationskill\",{\"_index\":44,\"name\":{\"44\":{}},\"parent\":{}}],[\"deletelanguage\",{\"_index\":52,\"name\":{\"52\":{}},\"parent\":{}}],[\"deletelanguagebyname\",{\"_index\":53,\"name\":{\"53\":{}},\"parent\":{}}],[\"deleteloginuserbyid\",{\"_index\":64,\"name\":{\"64\":{}},\"parent\":{}}],[\"deleteloginuserbypersonid\",{\"_index\":65,\"name\":{\"65\":{}},\"parent\":{}}],[\"deleteosoc\",{\"_index\":181,\"name\":{\"233\":{}},\"parent\":{}}],[\"deleteosocbyyear\",{\"_index\":182,\"name\":{\"234\":{}},\"parent\":{}}],[\"deleteosocfromdb\",{\"_index\":183,\"name\":{\"235\":{}},\"parent\":{}}],[\"deletepersonbyid\",{\"_index\":197,\"name\":{\"251\":{}},\"parent\":{}}],[\"deleteproject\",{\"_index\":213,\"name\":{\"269\":{},\"434\":{}},\"parent\":{}}],[\"deleteprojectbyosocedition\",{\"_index\":214,\"name\":{\"270\":{}},\"parent\":{}}],[\"deleteprojectbypartner\",{\"_index\":215,\"name\":{\"271\":{}},\"parent\":{}}],[\"deleteprojectrole\",{\"_index\":221,\"name\":{\"279\":{}},\"parent\":{}}],[\"deleteresetwithloginuser\",{\"_index\":189,\"name\":{\"241\":{}},\"parent\":{}}],[\"deleteresetwithresetid\",{\"_index\":190,\"name\":{\"242\":{}},\"parent\":{}}],[\"deleterole\",{\"_index\":232,\"name\":{\"292\":{}},\"parent\":{}}],[\"deleterolebyname\",{\"_index\":233,\"name\":{\"293\":{}},\"parent\":{}}],[\"deleteskillsbyjobapplicationid\",{\"_index\":45,\"name\":{\"45\":{}},\"parent\":{}}],[\"deletestudent\",{\"_index\":243,\"name\":{\"305\":{},\"461\":{}},\"parent\":{}}],[\"deletetemplate\",{\"_index\":250,\"name\":{\"314\":{},\"472\":{}},\"parent\":{}}],[\"deleteuserrequest\",{\"_index\":382,\"name\":{\"479\":{}},\"parent\":{}}],[\"eduduration\",{\"_index\":119,\"name\":{\"141\":{},\"406\":{}},\"parent\":{}}],[\"eduinstitute\",{\"_index\":121,\"name\":{\"143\":{},\"408\":{}},\"parent\":{}}],[\"edulevel\",{\"_index\":118,\"name\":{\"140\":{},\"404\":{}},\"parent\":{}}],[\"edulevelinput\",{\"_index\":331,\"name\":{\"405\":{}},\"parent\":{}}],[\"edus\",{\"_index\":117,\"name\":{\"139\":{},\"402\":{}},\"parent\":{}}],[\"edusinput\",{\"_index\":330,\"name\":{\"403\":{}},\"parent\":{}}],[\"eduyear\",{\"_index\":120,\"name\":{\"142\":{},\"407\":{}},\"parent\":{}}],[\"email\",{\"_index\":76,\"name\":{\"75\":{},\"82\":{}},\"parent\":{}}],[\"emailaddress\",{\"_index\":322,\"name\":{\"393\":{}},\"parent\":{}}],[\"emailstatus\",{\"_index\":122,\"name\":{\"144\":{}},\"parent\":{}}],[\"enddate\",{\"_index\":132,\"name\":{\"154\":{},\"162\":{}},\"parent\":{}}],[\"englishlevel\",{\"_index\":321,\"name\":{\"391\":{}},\"parent\":{}}],[\"evaluation_id\",{\"_index\":103,\"name\":{\"116\":{}},\"parent\":{}}],[\"export\",{\"_index\":308,\"name\":{\"375\":{},\"455\":{}},\"parent\":{}}],[\"filterboolean\",{\"_index\":173,\"name\":{\"224\":{}},\"parent\":{}}],[\"filterchosenoption\",{\"_index\":267,\"name\":{\"333\":{}},\"parent\":{}}],[\"filterloginusers\",{\"_index\":67,\"name\":{\"67\":{}},\"parent\":{}}],[\"filternumber\",{\"_index\":170,\"name\":{\"221\":{}},\"parent\":{}}],[\"filternumberarray\",{\"_index\":171,\"name\":{\"222\":{}},\"parent\":{}}],[\"filterosocs\",{\"_index\":185,\"name\":{\"237\":{}},\"parent\":{}}],[\"filterprojects\",{\"_index\":216,\"name\":{\"272\":{},\"441\":{}},\"parent\":{}}],[\"filterquestion\",{\"_index\":266,\"name\":{\"332\":{}},\"parent\":{}}],[\"filtersort\",{\"_index\":168,\"name\":{\"219\":{}},\"parent\":{}}],[\"filterstring\",{\"_index\":169,\"name\":{\"220\":{}},\"parent\":{}}],[\"filterstringarray\",{\"_index\":172,\"name\":{\"223\":{}},\"parent\":{}}],[\"filterstudents\",{\"_index\":245,\"name\":{\"307\":{},\"465\":{}},\"parent\":{}}],[\"filterusers\",{\"_index\":383,\"name\":{\"480\":{}},\"parent\":{}}],[\"findresetbycode\",{\"_index\":188,\"name\":{\"240\":{}},\"parent\":{}}],[\"firstname\",{\"_index\":72,\"name\":{\"72\":{},\"79\":{}},\"parent\":{}}],[\"funfact\",{\"_index\":113,\"name\":{\"135\":{},\"401\":{}},\"parent\":{}}],[\"gender\",{\"_index\":90,\"name\":{\"97\":{},\"104\":{},\"385\":{}},\"parent\":{}}],[\"genstate\",{\"_index\":337,\"name\":{\"416\":{}},\"parent\":{}}],[\"getalljobapplicationskill\",{\"_index\":40,\"name\":{\"40\":{}},\"parent\":{}}],[\"getalljobapplicationskillbyjobapplication\",{\"_index\":41,\"name\":{\"41\":{}},\"parent\":{}}],[\"getalllanguages\",{\"_index\":48,\"name\":{\"48\":{}},\"parent\":{}}],[\"getallloginusers\",{\"_index\":56,\"name\":{\"56\":{}},\"parent\":{}}],[\"getallosoc\",{\"_index\":176,\"name\":{\"227\":{}},\"parent\":{}}],[\"getallpersons\",{\"_index\":192,\"name\":{\"245\":{}},\"parent\":{}}],[\"getallprojects\",{\"_index\":199,\"name\":{\"254\":{}},\"parent\":{}}],[\"getallroles\",{\"_index\":228,\"name\":{\"287\":{}},\"parent\":{}}],[\"getallstudents\",{\"_index\":241,\"name\":{\"302\":{}},\"parent\":{}}],[\"getalltemplates\",{\"_index\":247,\"name\":{\"309\":{},\"468\":{}},\"parent\":{}}],[\"getalumni\",{\"_index\":278,\"name\":{\"344\":{}},\"parent\":{}}],[\"getappliedroles\",{\"_index\":298,\"name\":{\"364\":{}},\"parent\":{}}],[\"getappliedrolesbyjobapplication\",{\"_index\":2,\"name\":{\"2\":{}},\"parent\":{}}],[\"getattachmentbyid\",{\"_index\":8,\"name\":{\"8\":{}},\"parent\":{}}],[\"getbestskill\",{\"_index\":292,\"name\":{\"358\":{}},\"parent\":{}}],[\"getbirthname\",{\"_index\":270,\"name\":{\"336\":{}},\"parent\":{}}],[\"getcoachrequests\",{\"_index\":260,\"name\":{\"324\":{}},\"parent\":{}}],[\"getcurrentuser\",{\"_index\":385,\"name\":{\"482\":{}},\"parent\":{}}],[\"getcv\",{\"_index\":294,\"name\":{\"360\":{}},\"parent\":{}}],[\"getdraftedstudents\",{\"_index\":352,\"name\":{\"435\":{}},\"parent\":{}}],[\"geteducationduration\",{\"_index\":286,\"name\":{\"352\":{}},\"parent\":{}}],[\"geteducationlevel\",{\"_index\":285,\"name\":{\"351\":{}},\"parent\":{}}],[\"geteducations\",{\"_index\":284,\"name\":{\"350\":{}},\"parent\":{}}],[\"geteducationuniversity\",{\"_index\":288,\"name\":{\"354\":{}},\"parent\":{}}],[\"geteducationyear\",{\"_index\":287,\"name\":{\"353\":{}},\"parent\":{}}],[\"getemail\",{\"_index\":272,\"name\":{\"338\":{}},\"parent\":{}}],[\"getenglishlevel\",{\"_index\":291,\"name\":{\"357\":{}},\"parent\":{}}],[\"getevaluationbypartiesfor\",{\"_index\":22,\"name\":{\"22\":{}},\"parent\":{}}],[\"getfollowup\",{\"_index\":263,\"name\":{\"328\":{}},\"parent\":{}}],[\"getfreespotsfor\",{\"_index\":353,\"name\":{\"436\":{}},\"parent\":{}}],[\"getfunfact\",{\"_index\":281,\"name\":{\"347\":{}},\"parent\":{}}],[\"getgender\",{\"_index\":275,\"name\":{\"341\":{}},\"parent\":{}}],[\"gethome\",{\"_index\":336,\"name\":{\"415\":{}},\"parent\":{}}],[\"getjobapplication\",{\"_index\":36,\"name\":{\"36\":{}},\"parent\":{}}],[\"getjobapplicationbyyear\",{\"_index\":37,\"name\":{\"37\":{}},\"parent\":{}}],[\"getjobapplicationskill\",{\"_index\":42,\"name\":{\"42\":{}},\"parent\":{}}],[\"getlanguage\",{\"_index\":49,\"name\":{\"49\":{}},\"parent\":{}}],[\"getlanguagebyname\",{\"_index\":50,\"name\":{\"50\":{}},\"parent\":{}}],[\"getlastname\",{\"_index\":271,\"name\":{\"337\":{}},\"parent\":{}}],[\"getlatestapplicationrolesforstudent\",{\"_index\":30,\"name\":{\"30\":{}},\"parent\":{}}],[\"getlatestjobapplicationofstudent\",{\"_index\":35,\"name\":{\"35\":{}},\"parent\":{}}],[\"getlatestosoc\",{\"_index\":177,\"name\":{\"228\":{}},\"parent\":{}}],[\"getloginuserbyevaluationid\",{\"_index\":21,\"name\":{\"21\":{}},\"parent\":{}}],[\"getloginuserbyid\",{\"_index\":66,\"name\":{\"66\":{}},\"parent\":{}}],[\"getmostfluentlanguage\",{\"_index\":290,\"name\":{\"356\":{}},\"parent\":{}}],[\"getmotivation\",{\"_index\":296,\"name\":{\"362\":{}},\"parent\":{}}],[\"getnewestosoc\",{\"_index\":184,\"name\":{\"236\":{}},\"parent\":{}}],[\"getnickname\",{\"_index\":277,\"name\":{\"343\":{}},\"parent\":{}}],[\"getnumberoffreepositions\",{\"_index\":222,\"name\":{\"280\":{}},\"parent\":{}}],[\"getnumberofrolesbyprojectandrole\",{\"_index\":219,\"name\":{\"276\":{}},\"parent\":{}}],[\"getosocafteryear\",{\"_index\":180,\"name\":{\"231\":{}},\"parent\":{}}],[\"getosocbeforeyear\",{\"_index\":179,\"name\":{\"230\":{}},\"parent\":{}}],[\"getosocbyyear\",{\"_index\":178,\"name\":{\"229\":{}},\"parent\":{}}],[\"getpasswordloginuser\",{\"_index\":58,\"name\":{\"58\":{}},\"parent\":{}}],[\"getpasswordloginuserbyperson\",{\"_index\":57,\"name\":{\"57\":{}},\"parent\":{}}],[\"getpasswordpersonbyemail\",{\"_index\":193,\"name\":{\"246\":{}},\"parent\":{}}],[\"getpasswordpersonbygithub\",{\"_index\":194,\"name\":{\"247\":{}},\"parent\":{}}],[\"getphonenumber\",{\"_index\":276,\"name\":{\"342\":{}},\"parent\":{}}],[\"getportfolio\",{\"_index\":295,\"name\":{\"361\":{}},\"parent\":{}}],[\"getproject\",{\"_index\":350,\"name\":{\"432\":{}},\"parent\":{}}],[\"getprojectbyid\",{\"_index\":200,\"name\":{\"255\":{}},\"parent\":{}}],[\"getprojectbyname\",{\"_index\":201,\"name\":{\"256\":{}},\"parent\":{}}],[\"getprojectconflicts\",{\"_index\":357,\"name\":{\"440\":{}},\"parent\":{}}],[\"getprojectrolebyid\",{\"_index\":223,\"name\":{\"281\":{}},\"parent\":{}}],[\"getprojectrolenamesbyproject\",{\"_index\":220,\"name\":{\"277\":{}},\"parent\":{}}],[\"getprojectrolesbyproject\",{\"_index\":218,\"name\":{\"275\":{}},\"parent\":{}}],[\"getprojectrolewithrolename\",{\"_index\":231,\"name\":{\"290\":{}},\"parent\":{}}],[\"getprojectsbyenddate\",{\"_index\":207,\"name\":{\"262\":{}},\"parent\":{}}],[\"getprojectsbynumberpositions\",{\"_index\":210,\"name\":{\"265\":{}},\"parent\":{}}],[\"getprojectsbyosocedition\",{\"_index\":202,\"name\":{\"257\":{}},\"parent\":{}}],[\"getprojectsbypartner\",{\"_index\":203,\"name\":{\"258\":{}},\"parent\":{}}],[\"getprojectsbystartdate\",{\"_index\":204,\"name\":{\"259\":{}},\"parent\":{}}],[\"getprojectsendedafterdate\",{\"_index\":208,\"name\":{\"263\":{}},\"parent\":{}}],[\"getprojectsendedbeforedate\",{\"_index\":209,\"name\":{\"264\":{}},\"parent\":{}}],[\"getprojectslesspositions\",{\"_index\":211,\"name\":{\"266\":{}},\"parent\":{}}],[\"getprojectsmorepositions\",{\"_index\":212,\"name\":{\"267\":{}},\"parent\":{}}],[\"getprojectsstartedafterdate\",{\"_index\":205,\"name\":{\"260\":{}},\"parent\":{}}],[\"getprojectsstartedbeforedate\",{\"_index\":206,\"name\":{\"261\":{}},\"parent\":{}}],[\"getpronouns\",{\"_index\":274,\"name\":{\"340\":{}},\"parent\":{}}],[\"getresponsibilities\",{\"_index\":280,\"name\":{\"346\":{}},\"parent\":{}}],[\"getrole\",{\"_index\":229,\"name\":{\"288\":{}},\"parent\":{}}],[\"getrolesbyname\",{\"_index\":230,\"name\":{\"289\":{}},\"parent\":{}}],[\"getrouter\",{\"_index\":255,\"name\":{\"319\":{},\"325\":{},\"330\":{},\"373\":{},\"423\":{},\"428\":{},\"442\":{},\"448\":{},\"453\":{},\"466\":{},\"473\":{},\"483\":{},\"486\":{}},\"parent\":{}}],[\"getsingletemplate\",{\"_index\":376,\"name\":{\"469\":{}},\"parent\":{}}],[\"getstudent\",{\"_index\":242,\"name\":{\"303\":{},\"460\":{}},\"parent\":{}}],[\"getstudentevaluationsfinal\",{\"_index\":28,\"name\":{\"28\":{}},\"parent\":{}}],[\"getstudentevaluationstemp\",{\"_index\":29,\"name\":{\"29\":{}},\"parent\":{}}],[\"getstudentevaluationstotal\",{\"_index\":27,\"name\":{\"27\":{}},\"parent\":{}}],[\"getstudentsuggestions\",{\"_index\":373,\"name\":{\"463\":{}},\"parent\":{}}],[\"gettemplatebyid\",{\"_index\":248,\"name\":{\"310\":{}},\"parent\":{}}],[\"gettemplatesbyname\",{\"_index\":249,\"name\":{\"311\":{}},\"parent\":{}}],[\"getusersfor\",{\"_index\":225,\"name\":{\"284\":{}},\"parent\":{}}],[\"getvolunteerinfo\",{\"_index\":282,\"name\":{\"348\":{}},\"parent\":{}}],[\"ghexchangeaccesstoken\",{\"_index\":340,\"name\":{\"419\":{}},\"parent\":{}}],[\"ghidentity\",{\"_index\":339,\"name\":{\"418\":{}},\"parent\":{}}],[\"ghsignuporlogin\",{\"_index\":343,\"name\":{\"422\":{}},\"parent\":{}}],[\"github\",{\"_index\":75,\"name\":{\"74\":{},\"81\":{}},\"parent\":{}}],[\"github_id\",{\"_index\":77,\"name\":{\"76\":{}},\"parent\":{}}],[\"githubnamechange\",{\"_index\":342,\"name\":{\"421\":{}},\"parent\":{}}],[\"information\",{\"_index\":107,\"name\":{\"123\":{},\"129\":{},\"199\":{}},\"parent\":{}}],[\"is_best\",{\"_index\":153,\"name\":{\"193\":{}},\"parent\":{}}],[\"isadmin\",{\"_index\":83,\"name\":{\"86\":{},\"92\":{}},\"parent\":{}}],[\"isbest\",{\"_index\":150,\"name\":{\"185\":{}},\"parent\":{}}],[\"iscoach\",{\"_index\":84,\"name\":{\"87\":{},\"93\":{}},\"parent\":{}}],[\"isfinal\",{\"_index\":102,\"name\":{\"114\":{}},\"parent\":{}}],[\"ispreferred\",{\"_index\":149,\"name\":{\"184\":{},\"192\":{}},\"parent\":{}}],[\"isstudentcoach\",{\"_index\":283,\"name\":{\"349\":{}},\"parent\":{}}],[\"jobapplicationid\",{\"_index\":99,\"name\":{\"111\":{},\"180\":{},\"188\":{},\"204\":{}},\"parent\":{}}],[\"jobapplicationskillid\",{\"_index\":151,\"name\":{\"187\":{}},\"parent\":{}}],[\"jsontoattachments\",{\"_index\":297,\"name\":{\"363\":{}},\"parent\":{}}],[\"jsontojobapplication\",{\"_index\":289,\"name\":{\"355\":{}},\"parent\":{}}],[\"jsontoperson\",{\"_index\":273,\"name\":{\"339\":{}},\"parent\":{}}],[\"jsontoroles\",{\"_index\":299,\"name\":{\"365\":{}},\"parent\":{}}],[\"jsontoskills\",{\"_index\":293,\"name\":{\"359\":{}},\"parent\":{}}],[\"jsontostudent\",{\"_index\":279,\"name\":{\"345\":{}},\"parent\":{}}],[\"languageid\",{\"_index\":144,\"name\":{\"177\":{},\"182\":{},\"190\":{}},\"parent\":{}}],[\"lastname\",{\"_index\":74,\"name\":{\"73\":{},\"80\":{},\"382\":{}},\"parent\":{}}],[\"level\",{\"_index\":148,\"name\":{\"183\":{},\"191\":{}},\"parent\":{}}],[\"listadmins\",{\"_index\":252,\"name\":{\"316\":{}},\"parent\":{}}],[\"listcoaches\",{\"_index\":257,\"name\":{\"321\":{}},\"parent\":{}}],[\"listfollowups\",{\"_index\":262,\"name\":{\"327\":{}},\"parent\":{}}],[\"listprojects\",{\"_index\":349,\"name\":{\"431\":{}},\"parent\":{}}],[\"liststudentroles\",{\"_index\":365,\"name\":{\"451\":{}},\"parent\":{}}],[\"liststudents\",{\"_index\":371,\"name\":{\"459\":{}},\"parent\":{}}],[\"listusers\",{\"_index\":378,\"name\":{\"475\":{}},\"parent\":{}}],[\"liveinbelgium\",{\"_index\":310,\"name\":{\"377\":{}},\"parent\":{}}],[\"login\",{\"_index\":346,\"name\":{\"426\":{}},\"parent\":{}}],[\"loginuserid\",{\"_index\":86,\"name\":{\"90\":{},\"110\":{},\"117\":{},\"124\":{},\"128\":{},\"195\":{},\"202\":{}},\"parent\":{}}],[\"logout\",{\"_index\":347,\"name\":{\"427\":{}},\"parent\":{}}],[\"modadmin\",{\"_index\":253,\"name\":{\"317\":{}},\"parent\":{}}],[\"modcoach\",{\"_index\":258,\"name\":{\"322\":{}},\"parent\":{}}],[\"modproject\",{\"_index\":351,\"name\":{\"433\":{}},\"parent\":{}}],[\"modprojectstudent\",{\"_index\":355,\"name\":{\"438\":{}},\"parent\":{}}],[\"mostfluentlanguage\",{\"_index\":319,\"name\":{\"389\":{}},\"parent\":{}}],[\"mostfluentlanguageinput\",{\"_index\":320,\"name\":{\"390\":{}},\"parent\":{}}],[\"motivation\",{\"_index\":101,\"name\":{\"113\":{},\"119\":{}},\"parent\":{}}],[\"motivationinput\",{\"_index\":329,\"name\":{\"400\":{}},\"parent\":{}}],[\"motivationlink\",{\"_index\":328,\"name\":{\"399\":{}},\"parent\":{}}],[\"motivationupload\",{\"_index\":327,\"name\":{\"398\":{}},\"parent\":{}}],[\"name\",{\"_index\":128,\"name\":{\"150\":{},\"158\":{},\"175\":{},\"178\":{},\"208\":{},\"215\":{}},\"parent\":{}}],[\"nickname\",{\"_index\":93,\"name\":{\"100\":{},\"107\":{},\"383\":{}},\"parent\":{}}],[\"nicknameinput\",{\"_index\":315,\"name\":{\"384\":{}},\"parent\":{}}],[\"orm_functions/applied_role\",{\"_index\":0,\"name\":{\"0\":{}},\"parent\":{\"1\":{},\"2\":{},\"3\":{}}}],[\"orm_functions/attachment\",{\"_index\":4,\"name\":{\"4\":{}},\"parent\":{\"5\":{},\"6\":{},\"7\":{},\"8\":{}}}],[\"orm_functions/contract\",{\"_index\":9,\"name\":{\"9\":{}},\"parent\":{\"10\":{},\"11\":{},\"12\":{},\"13\":{},\"14\":{},\"15\":{},\"16\":{}}}],[\"orm_functions/evaluation\",{\"_index\":17,\"name\":{\"17\":{}},\"parent\":{\"18\":{},\"19\":{},\"20\":{},\"21\":{},\"22\":{},\"23\":{}}}],[\"orm_functions/general_purpose\",{\"_index\":24,\"name\":{\"24\":{}},\"parent\":{\"25\":{}}}],[\"orm_functions/job_application\",{\"_index\":26,\"name\":{\"26\":{}},\"parent\":{\"27\":{},\"28\":{},\"29\":{},\"30\":{},\"31\":{},\"32\":{},\"33\":{},\"34\":{},\"35\":{},\"36\":{},\"37\":{}}}],[\"orm_functions/job_application_skill\",{\"_index\":38,\"name\":{\"38\":{}},\"parent\":{\"39\":{},\"40\":{},\"41\":{},\"42\":{},\"43\":{},\"44\":{},\"45\":{}}}],[\"orm_functions/language\",{\"_index\":46,\"name\":{\"46\":{}},\"parent\":{\"47\":{},\"48\":{},\"49\":{},\"50\":{},\"51\":{},\"52\":{},\"53\":{}}}],[\"orm_functions/login_user\",{\"_index\":54,\"name\":{\"54\":{}},\"parent\":{\"55\":{},\"56\":{},\"57\":{},\"58\":{},\"59\":{},\"60\":{},\"61\":{},\"62\":{},\"63\":{},\"64\":{},\"65\":{},\"66\":{},\"67\":{},\"68\":{},\"69\":{}}}],[\"orm_functions/orm_types\",{\"_index\":70,\"name\":{\"70\":{}},\"parent\":{\"71\":{},\"77\":{},\"83\":{},\"89\":{},\"95\":{},\"102\":{},\"109\":{},\"115\":{},\"120\":{},\"126\":{},\"132\":{},\"146\":{},\"149\":{},\"156\":{},\"164\":{},\"168\":{},\"173\":{},\"176\":{},\"179\":{},\"186\":{},\"194\":{},\"200\":{},\"203\":{},\"206\":{},\"212\":{},\"219\":{},\"220\":{},\"221\":{},\"222\":{},\"223\":{},\"224\":{}}}],[\"orm_functions/orm_types.addstudenttoproject\",{\"_index\":154,\"name\":{},\"parent\":{\"195\":{},\"196\":{},\"197\":{},\"198\":{},\"199\":{}}}],[\"orm_functions/orm_types.createappliedrole\",{\"_index\":158,\"name\":{},\"parent\":{\"204\":{},\"205\":{}}}],[\"orm_functions/orm_types.createcontract\",{\"_index\":105,\"name\":{},\"parent\":{\"121\":{},\"122\":{},\"123\":{},\"124\":{},\"125\":{}}}],[\"orm_functions/orm_types.createevaluationforstudent\",{\"_index\":98,\"name\":{},\"parent\":{\"110\":{},\"111\":{},\"112\":{},\"113\":{},\"114\":{}}}],[\"orm_functions/orm_types.createjobapplication\",{\"_index\":111,\"name\":{},\"parent\":{\"133\":{},\"134\":{},\"135\":{},\"136\":{},\"137\":{},\"138\":{},\"139\":{},\"140\":{},\"141\":{},\"142\":{},\"143\":{},\"144\":{},\"145\":{}}}],[\"orm_functions/orm_types.createjobapplicationskill\",{\"_index\":146,\"name\":{},\"parent\":{\"180\":{},\"181\":{},\"182\":{},\"183\":{},\"184\":{},\"185\":{}}}],[\"orm_functions/orm_types.createloginuser\",{\"_index\":81,\"name\":{},\"parent\":{\"84\":{},\"85\":{},\"86\":{},\"87\":{},\"88\":{}}}],[\"orm_functions/orm_types.createperson\",{\"_index\":73,\"name\":{},\"parent\":{\"72\":{},\"73\":{},\"74\":{},\"75\":{},\"76\":{}}}],[\"orm_functions/orm_types.createproject\",{\"_index\":129,\"name\":{},\"parent\":{\"150\":{},\"151\":{},\"152\":{},\"153\":{},\"154\":{},\"155\":{}}}],[\"orm_functions/orm_types.createprojectrole\",{\"_index\":138,\"name\":{},\"parent\":{\"165\":{},\"166\":{},\"167\":{}}}],[\"orm_functions/orm_types.createprojectuser\",{\"_index\":157,\"name\":{},\"parent\":{\"201\":{},\"202\":{}}}],[\"orm_functions/orm_types.createstudent\",{\"_index\":89,\"name\":{},\"parent\":{\"96\":{},\"97\":{},\"98\":{},\"99\":{},\"100\":{},\"101\":{}}}],[\"orm_functions/orm_types.createtemplate\",{\"_index\":161,\"name\":{},\"parent\":{\"207\":{},\"208\":{},\"209\":{},\"210\":{},\"211\":{}}}],[\"orm_functions/orm_types.updatecontract\",{\"_index\":110,\"name\":{},\"parent\":{\"127\":{},\"128\":{},\"129\":{},\"130\":{},\"131\":{}}}],[\"orm_functions/orm_types.updateevaluationforstudent\",{\"_index\":104,\"name\":{},\"parent\":{\"116\":{},\"117\":{},\"118\":{},\"119\":{}}}],[\"orm_functions/orm_types.updatejobapplicationskill\",{\"_index\":152,\"name\":{},\"parent\":{\"187\":{},\"188\":{},\"189\":{},\"190\":{},\"191\":{},\"192\":{},\"193\":{}}}],[\"orm_functions/orm_types.updatelanguage\",{\"_index\":145,\"name\":{},\"parent\":{\"177\":{},\"178\":{}}}],[\"orm_functions/orm_types.updateloginuser\",{\"_index\":87,\"name\":{},\"parent\":{\"90\":{},\"91\":{},\"92\":{},\"93\":{},\"94\":{}}}],[\"orm_functions/orm_types.updateosoc\",{\"_index\":125,\"name\":{},\"parent\":{\"147\":{},\"148\":{}}}],[\"orm_functions/orm_types.updateperson\",{\"_index\":80,\"name\":{},\"parent\":{\"78\":{},\"79\":{},\"80\":{},\"81\":{},\"82\":{}}}],[\"orm_functions/orm_types.updateproject\",{\"_index\":136,\"name\":{},\"parent\":{\"157\":{},\"158\":{},\"159\":{},\"160\":{},\"161\":{},\"162\":{},\"163\":{}}}],[\"orm_functions/orm_types.updateprojectrole\",{\"_index\":141,\"name\":{},\"parent\":{\"169\":{},\"170\":{},\"171\":{},\"172\":{}}}],[\"orm_functions/orm_types.updaterole\",{\"_index\":143,\"name\":{},\"parent\":{\"174\":{},\"175\":{}}}],[\"orm_functions/orm_types.updatestudent\",{\"_index\":97,\"name\":{},\"parent\":{\"103\":{},\"104\":{},\"105\":{},\"106\":{},\"107\":{},\"108\":{}}}],[\"orm_functions/orm_types.updatetemplate\",{\"_index\":167,\"name\":{},\"parent\":{\"213\":{},\"214\":{},\"215\":{},\"216\":{},\"217\":{},\"218\":{}}}],[\"orm_functions/osoc\",{\"_index\":174,\"name\":{\"225\":{}},\"parent\":{\"226\":{},\"227\":{},\"228\":{},\"229\":{},\"230\":{},\"231\":{},\"232\":{},\"233\":{},\"234\":{},\"235\":{},\"236\":{},\"237\":{}}}],[\"orm_functions/password_reset\",{\"_index\":186,\"name\":{\"238\":{}},\"parent\":{\"239\":{},\"240\":{},\"241\":{},\"242\":{}}}],[\"orm_functions/person\",{\"_index\":191,\"name\":{\"243\":{}},\"parent\":{\"244\":{},\"245\":{},\"246\":{},\"247\":{},\"248\":{},\"249\":{},\"250\":{},\"251\":{}}}],[\"orm_functions/project\",{\"_index\":198,\"name\":{\"252\":{}},\"parent\":{\"253\":{},\"254\":{},\"255\":{},\"256\":{},\"257\":{},\"258\":{},\"259\":{},\"260\":{},\"261\":{},\"262\":{},\"263\":{},\"264\":{},\"265\":{},\"266\":{},\"267\":{},\"268\":{},\"269\":{},\"270\":{},\"271\":{},\"272\":{}}}],[\"orm_functions/project_role\",{\"_index\":217,\"name\":{\"273\":{}},\"parent\":{\"274\":{},\"275\":{},\"276\":{},\"277\":{},\"278\":{},\"279\":{},\"280\":{},\"281\":{}}}],[\"orm_functions/project_user\",{\"_index\":224,\"name\":{\"282\":{}},\"parent\":{\"283\":{},\"284\":{}}}],[\"orm_functions/role\",{\"_index\":226,\"name\":{\"285\":{}},\"parent\":{\"286\":{},\"287\":{},\"288\":{},\"289\":{},\"290\":{},\"291\":{},\"292\":{},\"293\":{}}}],[\"orm_functions/session_key\",{\"_index\":234,\"name\":{\"294\":{}},\"parent\":{\"295\":{},\"296\":{},\"297\":{},\"298\":{},\"299\":{}}}],[\"orm_functions/student\",{\"_index\":240,\"name\":{\"300\":{}},\"parent\":{\"301\":{},\"302\":{},\"303\":{},\"304\":{},\"305\":{},\"306\":{},\"307\":{}}}],[\"orm_functions/template\",{\"_index\":246,\"name\":{\"308\":{}},\"parent\":{\"309\":{},\"310\":{},\"311\":{},\"312\":{},\"313\":{},\"314\":{}}}],[\"osocid\",{\"_index\":116,\"name\":{\"138\":{},\"147\":{},\"151\":{},\"159\":{}},\"parent\":{}}],[\"ownerid\",{\"_index\":160,\"name\":{\"207\":{},\"214\":{}},\"parent\":{}}],[\"parseghlogin\",{\"_index\":341,\"name\":{\"420\":{}},\"parent\":{}}],[\"partner\",{\"_index\":130,\"name\":{\"152\":{},\"160\":{}},\"parent\":{}}],[\"password\",{\"_index\":82,\"name\":{\"85\":{},\"91\":{}},\"parent\":{}}],[\"personid\",{\"_index\":79,\"name\":{\"78\":{},\"84\":{},\"96\":{}},\"parent\":{}}],[\"phonenumber\",{\"_index\":92,\"name\":{\"99\":{},\"106\":{},\"392\":{}},\"parent\":{}}],[\"portfoliolink\",{\"_index\":326,\"name\":{\"397\":{}},\"parent\":{}}],[\"portfolioupload\",{\"_index\":325,\"name\":{\"396\":{}},\"parent\":{}}],[\"positions\",{\"_index\":133,\"name\":{\"155\":{},\"163\":{},\"167\":{},\"172\":{}},\"parent\":{}}],[\"preferredpronouns\",{\"_index\":317,\"name\":{\"387\":{}},\"parent\":{}}],[\"projectid\",{\"_index\":135,\"name\":{\"157\":{},\"165\":{},\"170\":{},\"197\":{},\"201\":{}},\"parent\":{}}],[\"projectroleid\",{\"_index\":106,\"name\":{\"122\":{},\"131\":{},\"169\":{}},\"parent\":{}}],[\"pronouns\",{\"_index\":91,\"name\":{\"98\":{},\"105\":{}},\"parent\":{}}],[\"pronounsinput\",{\"_index\":318,\"name\":{\"388\":{}},\"parent\":{}}],[\"refreshkey\",{\"_index\":237,\"name\":{\"297\":{}},\"parent\":{}}],[\"removeallkeysforloginuserid\",{\"_index\":239,\"name\":{\"299\":{}},\"parent\":{}}],[\"removeallkeysforuser\",{\"_index\":238,\"name\":{\"298\":{}},\"parent\":{}}],[\"removecontract\",{\"_index\":13,\"name\":{\"13\":{}},\"parent\":{}}],[\"removecontractsfromstudent\",{\"_index\":12,\"name\":{\"12\":{}},\"parent\":{}}],[\"requestreset\",{\"_index\":360,\"name\":{\"445\":{}},\"parent\":{}}],[\"resetpassword\",{\"_index\":362,\"name\":{\"447\":{}},\"parent\":{}}],[\"responsibilities\",{\"_index\":112,\"name\":{\"134\":{},\"380\":{}},\"parent\":{}}],[\"roleid\",{\"_index\":139,\"name\":{\"166\":{},\"171\":{},\"174\":{},\"205\":{}},\"parent\":{}}],[\"rolename\",{\"_index\":155,\"name\":{\"198\":{}},\"parent\":{}}],[\"routes/admin\",{\"_index\":251,\"name\":{\"315\":{}},\"parent\":{\"316\":{},\"317\":{},\"318\":{},\"319\":{}}}],[\"routes/coach\",{\"_index\":256,\"name\":{\"320\":{}},\"parent\":{\"321\":{},\"322\":{},\"323\":{},\"324\":{},\"325\":{}}}],[\"routes/followup\",{\"_index\":261,\"name\":{\"326\":{}},\"parent\":{\"327\":{},\"328\":{},\"329\":{},\"330\":{}}}],[\"routes/form\",{\"_index\":265,\"name\":{\"331\":{}},\"parent\":{\"332\":{},\"333\":{},\"334\":{},\"335\":{},\"336\":{},\"337\":{},\"338\":{},\"339\":{},\"340\":{},\"341\":{},\"342\":{},\"343\":{},\"344\":{},\"345\":{},\"346\":{},\"347\":{},\"348\":{},\"349\":{},\"350\":{},\"351\":{},\"352\":{},\"353\":{},\"354\":{},\"355\":{},\"356\":{},\"357\":{},\"358\":{},\"359\":{},\"360\":{},\"361\":{},\"362\":{},\"363\":{},\"364\":{},\"365\":{},\"366\":{},\"367\":{},\"368\":{},\"369\":{},\"370\":{},\"371\":{},\"372\":{},\"373\":{}}}],[\"routes/form_keys.json\",{\"_index\":307,\"name\":{\"374\":{}},\"parent\":{\"375\":{},\"376\":{}}}],[\"routes/form_keys.json.__type\",{\"_index\":311,\"name\":{},\"parent\":{\"377\":{},\"378\":{},\"379\":{},\"380\":{},\"381\":{},\"382\":{},\"383\":{},\"384\":{},\"385\":{},\"386\":{},\"387\":{},\"388\":{},\"389\":{},\"390\":{},\"391\":{},\"392\":{},\"393\":{},\"394\":{},\"395\":{},\"396\":{},\"397\":{},\"398\":{},\"399\":{},\"400\":{},\"401\":{},\"402\":{},\"403\":{},\"404\":{},\"405\":{},\"406\":{},\"407\":{},\"408\":{},\"409\":{},\"410\":{},\"411\":{},\"412\":{},\"413\":{}}}],[\"routes/github\",{\"_index\":335,\"name\":{\"414\":{}},\"parent\":{\"415\":{},\"416\":{},\"417\":{},\"418\":{},\"419\":{},\"420\":{},\"421\":{},\"422\":{},\"423\":{},\"424\":{}}}],[\"routes/login\",{\"_index\":345,\"name\":{\"425\":{}},\"parent\":{\"426\":{},\"427\":{},\"428\":{}}}],[\"routes/project\",{\"_index\":348,\"name\":{\"429\":{}},\"parent\":{\"430\":{},\"431\":{},\"432\":{},\"433\":{},\"434\":{},\"435\":{},\"436\":{},\"437\":{},\"438\":{},\"439\":{},\"440\":{},\"441\":{},\"442\":{}}}],[\"routes/reset\",{\"_index\":358,\"name\":{\"443\":{}},\"parent\":{\"444\":{},\"445\":{},\"446\":{},\"447\":{},\"448\":{},\"449\":{}}}],[\"routes/role\",{\"_index\":364,\"name\":{\"450\":{}},\"parent\":{\"451\":{},\"452\":{},\"453\":{}}}],[\"routes/session_key.json\",{\"_index\":367,\"name\":{\"454\":{}},\"parent\":{\"455\":{},\"456\":{}}}],[\"routes/session_key.json.__type\",{\"_index\":369,\"name\":{},\"parent\":{\"457\":{}}}],[\"routes/student\",{\"_index\":370,\"name\":{\"458\":{}},\"parent\":{\"459\":{},\"460\":{},\"461\":{},\"462\":{},\"463\":{},\"464\":{},\"465\":{},\"466\":{}}}],[\"routes/template\",{\"_index\":375,\"name\":{\"467\":{}},\"parent\":{\"468\":{},\"469\":{},\"470\":{},\"471\":{},\"472\":{},\"473\":{}}}],[\"routes/user\",{\"_index\":377,\"name\":{\"474\":{}},\"parent\":{\"475\":{},\"476\":{},\"477\":{},\"478\":{},\"479\":{},\"480\":{},\"481\":{},\"482\":{},\"483\":{}}}],[\"routes/verify\",{\"_index\":386,\"name\":{\"484\":{}},\"parent\":{\"485\":{},\"486\":{}}}],[\"searchalladminandcoachloginusers\",{\"_index\":62,\"name\":{\"62\":{}},\"parent\":{}}],[\"searchalladminloginusers\",{\"_index\":60,\"name\":{\"60\":{}},\"parent\":{}}],[\"searchallcoachloginusers\",{\"_index\":61,\"name\":{\"61\":{}},\"parent\":{}}],[\"searchloginuserbyperson\",{\"_index\":59,\"name\":{\"59\":{}},\"parent\":{}}],[\"searchpersonbylogin\",{\"_index\":196,\"name\":{\"249\":{}},\"parent\":{}}],[\"searchpersonbyname\",{\"_index\":195,\"name\":{\"248\":{}},\"parent\":{}}],[\"searchstudentbygender\",{\"_index\":244,\"name\":{\"306\":{}},\"parent\":{}}],[\"sendmail\",{\"_index\":359,\"name\":{\"444\":{}},\"parent\":{}}],[\"setaccountstatus\",{\"_index\":380,\"name\":{\"477\":{}},\"parent\":{}}],[\"setadmin\",{\"_index\":69,\"name\":{\"69\":{}},\"parent\":{}}],[\"setcoach\",{\"_index\":68,\"name\":{\"68\":{}},\"parent\":{}}],[\"skill\",{\"_index\":147,\"name\":{\"181\":{},\"189\":{}},\"parent\":{}}],[\"sortedcontractsbyosocedition\",{\"_index\":16,\"name\":{\"16\":{}},\"parent\":{}}],[\"startdate\",{\"_index\":131,\"name\":{\"153\":{},\"161\":{}},\"parent\":{}}],[\"states\",{\"_index\":344,\"name\":{\"424\":{}},\"parent\":{}}],[\"studentcoach\",{\"_index\":115,\"name\":{\"137\":{},\"413\":{}},\"parent\":{}}],[\"studentid\",{\"_index\":96,\"name\":{\"103\":{},\"121\":{},\"133\":{},\"196\":{}},\"parent\":{}}],[\"studentvolunteerinfo\",{\"_index\":114,\"name\":{\"136\":{}},\"parent\":{}}],[\"subject\",{\"_index\":163,\"name\":{\"210\":{},\"217\":{}},\"parent\":{}}],[\"templateid\",{\"_index\":166,\"name\":{\"213\":{}},\"parent\":{}}],[\"unassignstudent\",{\"_index\":356,\"name\":{\"439\":{}},\"parent\":{}}],[\"updatecontract\",{\"_index\":11,\"name\":{\"11\":{},\"126\":{}},\"parent\":{}}],[\"updateevaluationforstudent\",{\"_index\":20,\"name\":{\"20\":{},\"115\":{}},\"parent\":{}}],[\"updatefollowup\",{\"_index\":264,\"name\":{\"329\":{}},\"parent\":{}}],[\"updatejobapplicationskill\",{\"_index\":43,\"name\":{\"43\":{},\"186\":{}},\"parent\":{}}],[\"updatelanguage\",{\"_index\":51,\"name\":{\"51\":{},\"176\":{}},\"parent\":{}}],[\"updateloginuser\",{\"_index\":63,\"name\":{\"63\":{},\"89\":{}},\"parent\":{}}],[\"updateosoc\",{\"_index\":124,\"name\":{\"146\":{},\"232\":{}},\"parent\":{}}],[\"updateperson\",{\"_index\":78,\"name\":{\"77\":{},\"250\":{}},\"parent\":{}}],[\"updateproject\",{\"_index\":134,\"name\":{\"156\":{},\"268\":{}},\"parent\":{}}],[\"updateprojectrole\",{\"_index\":140,\"name\":{\"168\":{},\"278\":{}},\"parent\":{}}],[\"updaterole\",{\"_index\":142,\"name\":{\"173\":{},\"291\":{}},\"parent\":{}}],[\"updatestudent\",{\"_index\":95,\"name\":{\"102\":{},\"304\":{}},\"parent\":{}}],[\"updatetemplate\",{\"_index\":165,\"name\":{\"212\":{},\"313\":{},\"471\":{}},\"parent\":{}}],[\"usermodself\",{\"_index\":384,\"name\":{\"481\":{}},\"parent\":{}}],[\"valid_period\",{\"_index\":368,\"name\":{\"457\":{}},\"parent\":{}}],[\"verifykey\",{\"_index\":387,\"name\":{\"485\":{}},\"parent\":{}}],[\"volunteerinfo\",{\"_index\":312,\"name\":{\"378\":{}},\"parent\":{}}],[\"workinjuly\",{\"_index\":313,\"name\":{\"379\":{}},\"parent\":{}}],[\"year\",{\"_index\":126,\"name\":{\"148\":{}},\"parent\":{}}]],\"pipeline\":[]}}"); \ No newline at end of file diff --git a/backend/docs/assets/style.css b/backend/docs/assets/style.css index a16ed029..6127b27c 100644 --- a/backend/docs/assets/style.css +++ b/backend/docs/assets/style.css @@ -766,12 +766,13 @@ footer .tsd-legend { .tsd-flag { display: inline-block; - padding: 1px 5px; + padding: 0.25em 0.4em; border-radius: 4px; color: var(--color-comment-tag-text); background-color: var(--color-comment-tag); text-indent: 0; - font-size: 14px; + font-size: 75%; + line-height: 1; font-weight: normal; } diff --git a/backend/docs/index.html b/backend/docs/index.html index 3fe478f9..2118b236 100644 --- a/backend/docs/index.html +++ b/backend/docs/index.html @@ -1,4 +1,4 @@ -backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

backend

Legend

  • Variable
  • Function
  • Type alias
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/interfaces/orm_functions_orm_types.AddStudentToProject.html b/backend/docs/interfaces/orm_functions_orm_types.AddStudentToProject.html index 96337c31..cb4860b3 100644 --- a/backend/docs/interfaces/orm_functions_orm_types.AddStudentToProject.html +++ b/backend/docs/interfaces/orm_functions_orm_types.AddStudentToProject.html @@ -1,11 +1,11 @@ -AddStudentToProject | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • AddStudentToProject

Index

Properties

information?: null | string
+AddStudentToProject | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Hierarchy

  • AddStudentToProject

Index

Properties

information?: null | string

extra information

-
loginUserId: number
+
loginUserId: number

the id of the loginsuer that wants to add a student to the project

-
projectId: number
+
projectId: number

the id of the project that the student will be added to

-
roleName: string
+
roleName: string

the name of the role the student will be added for

-
studentId: number
+
studentId: number

the id of the student that will be added to the project

-

Legend

  • Interface
  • Property
  • Function

Settings

Theme

Generated using TypeDoc

\ No newline at end of file +

Legend

  • Variable
  • Function
  • Type alias
  • Interface
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/interfaces/orm_functions_orm_types.CreateAppliedRole.html b/backend/docs/interfaces/orm_functions_orm_types.CreateAppliedRole.html index 6390fa08..48712399 100644 --- a/backend/docs/interfaces/orm_functions_orm_types.CreateAppliedRole.html +++ b/backend/docs/interfaces/orm_functions_orm_types.CreateAppliedRole.html @@ -1,7 +1,7 @@ -CreateAppliedRole | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu
+CreateAppliedRole | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Legend

  • Interface
  • Property
  • Function

Settings

Theme

Generated using TypeDoc

\ No newline at end of file +

Legend

  • Variable
  • Function
  • Type alias
  • Interface
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/interfaces/orm_functions_orm_types.CreateContract.html b/backend/docs/interfaces/orm_functions_orm_types.CreateContract.html index cd052141..afe46df1 100644 --- a/backend/docs/interfaces/orm_functions_orm_types.CreateContract.html +++ b/backend/docs/interfaces/orm_functions_orm_types.CreateContract.html @@ -1,13 +1,13 @@ -CreateContract | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu
+CreateContract | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

interface for the object needed in createContract

-

Hierarchy

  • CreateContract

Index

Properties

contractStatus: contract_status_enum
+

Hierarchy

  • CreateContract

Index

Properties

contractStatus: contract_status_enum

status of the contract (draft, approved, cancelled,...)

-
information?: null | string
+
information?: null | string

extra information

-
loginUserId: number
+
loginUserId: number

the loginUser that created this contract

-
projectRoleId: number
+
projectRoleId: number

the role for which the contract is

-
studentId: number
+
studentId: number

the student that receives the contract

-

Legend

  • Interface
  • Property
  • Function

Settings

Theme

Generated using TypeDoc

\ No newline at end of file +

Legend

  • Variable
  • Function
  • Type alias
  • Interface
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/interfaces/orm_functions_orm_types.CreateEvaluationForStudent.html b/backend/docs/interfaces/orm_functions_orm_types.CreateEvaluationForStudent.html index c5a44eae..d9d91a5d 100644 --- a/backend/docs/interfaces/orm_functions_orm_types.CreateEvaluationForStudent.html +++ b/backend/docs/interfaces/orm_functions_orm_types.CreateEvaluationForStudent.html @@ -1,13 +1,13 @@ -CreateEvaluationForStudent | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu
+CreateEvaluationForStudent | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

interface for the object needed in createEvaluationForStudent

-

Hierarchy

  • CreateEvaluationForStudent

Index

Properties

decision: decision_enum
+

Hierarchy

  • CreateEvaluationForStudent

Index

Properties

decision: decision_enum

the decision that is made (yes, maybe, no)

-
isFinal: boolean
+
isFinal: boolean

is this evaluation final, or not

-
jobApplicationId: number
+
jobApplicationId: number

the jobApplication about that the evaluation is about

-
loginUserId: number
+
loginUserId: number

loginUserId of the loginUser that creates this evaluation

-
motivation?: null | string
+
motivation?: null | string

motivation for the made decision

-

Legend

  • Interface
  • Property
  • Function

Settings

Theme

Generated using TypeDoc

\ No newline at end of file +

Legend

  • Variable
  • Function
  • Type alias
  • Interface
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/interfaces/orm_functions_orm_types.CreateJobApplication.html b/backend/docs/interfaces/orm_functions_orm_types.CreateJobApplication.html index d40b1e1e..5ab791fb 100644 --- a/backend/docs/interfaces/orm_functions_orm_types.CreateJobApplication.html +++ b/backend/docs/interfaces/orm_functions_orm_types.CreateJobApplication.html @@ -1,29 +1,29 @@ -CreateJobApplication | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu
+CreateJobApplication | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

interface for the object needed in createJobApplication

-

Hierarchy

  • CreateJobApplication

Index

Properties

created_at: string
+

Hierarchy

  • CreateJobApplication

Index

Properties

createdAt: string

keeps track of when we received this application (used to pick the latest one)

-
eduDuration?: null | number
+
eduDuration: null | number

how long this student has been studying for

-
eduInstitute?: null | string
+
eduInstitute: null | string

institute the student is studying at

-
eduLevel?: null | string
+
eduLevel: string

information about the education level of the student

-
eduYear?: null | number
+
eduYear: null | string

expected graduation year

-
edus?: string[]
+
edus: string[]

information about the educations of the student

-
emailStatus: email_status_enum
+
emailStatus: email_status_enum

information about a confirmation email for the evaluation

-
funFact?: null | string
+
funFact: string

a fun fact about the student

-
osocId: number
+
osocId: number

id of the osoc edition this job application is for

-
responsibilities?: null | string
+
responsibilities?: null | string

the responsibilities the students has during the summer that might keep him from working for osoc

-
studentCoach: boolean
+
studentCoach: boolean

boolean that indicates if the student is a student-coach or not

-
studentId: number
+
studentId: number

the student who's application this is

-
studentVolunteerInfo: string
+
studentVolunteerInfo: string

string that has info if the student is available to work, and if he wants to work as volunteer for free or not

-

Legend

  • Interface
  • Property
  • Function

Settings

Theme

Generated using TypeDoc

\ No newline at end of file +

Legend

  • Variable
  • Function
  • Type alias
  • Interface
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/interfaces/orm_functions_orm_types.CreateJobApplicationSkill.html b/backend/docs/interfaces/orm_functions_orm_types.CreateJobApplicationSkill.html index 12e69d0a..b7e64096 100644 --- a/backend/docs/interfaces/orm_functions_orm_types.CreateJobApplicationSkill.html +++ b/backend/docs/interfaces/orm_functions_orm_types.CreateJobApplicationSkill.html @@ -1,15 +1,15 @@ -CreateJobApplicationSkill | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu
+CreateJobApplicationSkill | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

interface for the object needed to create a project

-

Hierarchy

  • CreateJobApplicationSkill

Index

Properties

isBest: boolean
+

Hierarchy

  • CreateJobApplicationSkill

Index

Properties

isBest: boolean

true if this skill is the best skill of the applicant

-
isPreferred: boolean
+
isPreferred: boolean

true if this skill is the preffered skill of the applicant

-
jobApplicationId: number
+
jobApplicationId: number

the jobapplicaton id to which the skill is linked

-
languageId: number
+
languageId: null | number

the language id to which this skill is linked

-
level: number
+
level: null | number

the level of the skill of the applicant

-
skill: string
+
skill: null | string

the skill of this job application

-

Legend

  • Interface
  • Property
  • Function

Settings

Theme

Generated using TypeDoc

\ No newline at end of file +

Legend

  • Variable
  • Function
  • Type alias
  • Interface
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/interfaces/orm_functions_orm_types.CreateLoginUser.html b/backend/docs/interfaces/orm_functions_orm_types.CreateLoginUser.html index df13344d..961a7bbb 100644 --- a/backend/docs/interfaces/orm_functions_orm_types.CreateLoginUser.html +++ b/backend/docs/interfaces/orm_functions_orm_types.CreateLoginUser.html @@ -1,13 +1,13 @@ -CreateLoginUser | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu
+CreateLoginUser | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

interface for the object needed to create a login user

-

Hierarchy

  • CreateLoginUser

Index

Properties

accountStatus: account_status_enum
+

Hierarchy

  • CreateLoginUser

Index

Properties

accountStatus: account_status_enum

the status of the account we are trying to create

-
isAdmin: boolean
+
isAdmin: boolean

true if the login user is an admin in the osoc system, otherwise false

-
isCoach: boolean
+
isCoach: boolean

true if the login user is a coach in the osoc system, otherwise false

-
password?: null | string
+
password?: null | string

the password hash of the login user if email is used

-
personId?: number
+
personId: number

the person_id of the person the login user will be associated with

-

Legend

  • Interface
  • Property
  • Function

Settings

Theme

Generated using TypeDoc

\ No newline at end of file +

Legend

  • Variable
  • Function
  • Type alias
  • Interface
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/interfaces/orm_functions_orm_types.CreatePerson.html b/backend/docs/interfaces/orm_functions_orm_types.CreatePerson.html index bcc55a1d..b2003004 100644 --- a/backend/docs/interfaces/orm_functions_orm_types.CreatePerson.html +++ b/backend/docs/interfaces/orm_functions_orm_types.CreatePerson.html @@ -1,11 +1,13 @@ -CreatePerson | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu
+CreatePerson | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

interface for the object needed to create a person

-

Hierarchy

  • CreatePerson

Index

Properties

email?: string
+

Hierarchy

  • CreatePerson

Index

Properties

email?: string

the person's email, may not be null if github is null

-
firstname: string
+
firstname: string

the person's firstname

-
github?: string
+
github?: string

the person's github account, only one of github/email can be used

-
lastname: string
+
github_id?: string
+

the person's github id, if github is used

+
lastname: string

the person's lastname

-

Legend

  • Interface
  • Property
  • Function

Settings

Theme

Generated using TypeDoc

\ No newline at end of file +

Legend

  • Variable
  • Function
  • Type alias
  • Interface
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/interfaces/orm_functions_orm_types.CreateProject.html b/backend/docs/interfaces/orm_functions_orm_types.CreateProject.html index 45757ff9..3cb39bf5 100644 --- a/backend/docs/interfaces/orm_functions_orm_types.CreateProject.html +++ b/backend/docs/interfaces/orm_functions_orm_types.CreateProject.html @@ -1,15 +1,15 @@ -CreateProject | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu
+CreateProject | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

interface for the object needed to create a project

-

Hierarchy

  • CreateProject

Index

Properties

endDate: Date
+

Hierarchy

  • CreateProject

Index

Properties

endDate: Date

the end date of the project

-
name: string
+
name: string

the name of the project

-
osocId: number
+
osocId: number

the id of the osoc edition this project belongs to

-
partner: string
+
partner: string

the partner for who this project is made

-
positions: number
+
positions: number

the amount of people who need to assigned to the project

-
startDate: Date
+
startDate: Date

the start date of the project

-

Legend

  • Interface
  • Property
  • Function

Settings

Theme

Generated using TypeDoc

\ No newline at end of file +

Legend

  • Variable
  • Function
  • Type alias
  • Interface
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/interfaces/orm_functions_orm_types.CreateProjectRole.html b/backend/docs/interfaces/orm_functions_orm_types.CreateProjectRole.html index 2d4f4b06..a4ceae74 100644 --- a/backend/docs/interfaces/orm_functions_orm_types.CreateProjectRole.html +++ b/backend/docs/interfaces/orm_functions_orm_types.CreateProjectRole.html @@ -1,9 +1,9 @@ -CreateProjectRole | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu
+CreateProjectRole | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

interface for the object needed to create a project

-

Hierarchy

  • CreateProjectRole

Index

Properties

positions: number
+

Hierarchy

  • CreateProjectRole

Index

Properties

positions: number

the number of positions that are needed for this role

-
projectId: number
+
projectId: number

the id of the project this role belongs to

-
roleId: number
+
roleId: number

the id of the role this project role represents

-

Legend

  • Interface
  • Property
  • Function

Settings

Theme

Generated using TypeDoc

\ No newline at end of file +

Legend

  • Variable
  • Function
  • Type alias
  • Interface
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/interfaces/orm_functions_orm_types.CreateProjectUser.html b/backend/docs/interfaces/orm_functions_orm_types.CreateProjectUser.html index 9579bcda..50c851b5 100644 --- a/backend/docs/interfaces/orm_functions_orm_types.CreateProjectUser.html +++ b/backend/docs/interfaces/orm_functions_orm_types.CreateProjectUser.html @@ -1,7 +1,7 @@ -CreateProjectUser | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu
+CreateProjectUser | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Legend

  • Interface
  • Property
  • Function

Settings

Theme

Generated using TypeDoc

\ No newline at end of file +

Legend

  • Variable
  • Function
  • Type alias
  • Interface
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/interfaces/orm_functions_orm_types.CreateStudent.html b/backend/docs/interfaces/orm_functions_orm_types.CreateStudent.html index 1b7121c1..b65c2f5d 100644 --- a/backend/docs/interfaces/orm_functions_orm_types.CreateStudent.html +++ b/backend/docs/interfaces/orm_functions_orm_types.CreateStudent.html @@ -1,15 +1,15 @@ -CreateStudent | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu
+CreateStudent | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

interface for the object needed to create a student

-

Hierarchy

  • CreateStudent

Index

Properties

alumni: boolean
+

Hierarchy

  • CreateStudent

Index

Properties

alumni: boolean

true if the student is an alumni in the osoc system, otherwise false

-
gender: string
+
gender: string

the person's gender

-
nickname?: null | string
+
nickname?: null | string

student's nickname

-
personId?: number
+
personId?: number

the person_id of the person the student will be associated with

-
phoneNumber: string
+
phoneNumber: string

student's phone number

-
pronouns?: string[]
+
pronouns?: null | string

the pronouns the student wants to be addressed with

-

Legend

  • Interface
  • Property
  • Function

Settings

Theme

Generated using TypeDoc

\ No newline at end of file +

Legend

  • Variable
  • Function
  • Type alias
  • Interface
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/interfaces/orm_functions_orm_types.CreateTemplate.html b/backend/docs/interfaces/orm_functions_orm_types.CreateTemplate.html new file mode 100644 index 00000000..ca311984 --- /dev/null +++ b/backend/docs/interfaces/orm_functions_orm_types.CreateTemplate.html @@ -0,0 +1 @@ +CreateTemplate | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Legend

  • Variable
  • Function
  • Type alias
  • Interface
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/interfaces/orm_functions_orm_types.UpdateContract.html b/backend/docs/interfaces/orm_functions_orm_types.UpdateContract.html index 31d495c8..ac962625 100644 --- a/backend/docs/interfaces/orm_functions_orm_types.UpdateContract.html +++ b/backend/docs/interfaces/orm_functions_orm_types.UpdateContract.html @@ -1,11 +1,13 @@ -UpdateContract | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu
+UpdateContract | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

interface for the object needed in updateContract

-

Hierarchy

  • UpdateContract

Index

Properties

contractId: number
+

Hierarchy

  • UpdateContract

Index

Properties

contractId: number

the contract we are changing

-
contractStatus?: contract_status_enum
+
contractStatus?: contract_status_enum

status of the contract (draft, approved, cancelled,...)

-
information?: null | string
+
information?: null | string

optional information

-
loginUserId: number
+
loginUserId: number

id of the login user that is making these changes

-

Legend

  • Interface
  • Property
  • Function

Settings

Theme

Generated using TypeDoc

\ No newline at end of file +
projectRoleId?: number
+

updated role (id) for the student

+

Legend

  • Variable
  • Function
  • Type alias
  • Interface
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/interfaces/orm_functions_orm_types.UpdateEvaluationForStudent.html b/backend/docs/interfaces/orm_functions_orm_types.UpdateEvaluationForStudent.html index c9d009e5..fb1dffc1 100644 --- a/backend/docs/interfaces/orm_functions_orm_types.UpdateEvaluationForStudent.html +++ b/backend/docs/interfaces/orm_functions_orm_types.UpdateEvaluationForStudent.html @@ -1,11 +1,11 @@ -UpdateEvaluationForStudent | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu
+UpdateEvaluationForStudent | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

interface for the object needed in updateEvaluationForStudent

-

Hierarchy

  • UpdateEvaluationForStudent

Index

Properties

decision?: decision_enum
+

Hierarchy

  • UpdateEvaluationForStudent

Index

Properties

decision?: decision_enum

the decision that is made (yes, maybe, no)

-
evaluation_id: number
+
evaluation_id: number

the evaluation that we are updating

-
loginUserId: number
+
loginUserId: number

loginUserId of the loginUser that creates this evaluation

-
motivation?: null | string
+
motivation?: null | string

motivation for the made decision

-

Legend

  • Interface
  • Property
  • Function

Settings

Theme

Generated using TypeDoc

\ No newline at end of file +

Legend

  • Variable
  • Function
  • Type alias
  • Interface
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/interfaces/orm_functions_orm_types.UpdateJobApplicationSkill.html b/backend/docs/interfaces/orm_functions_orm_types.UpdateJobApplicationSkill.html index 6c07d34e..edea8eff 100644 --- a/backend/docs/interfaces/orm_functions_orm_types.UpdateJobApplicationSkill.html +++ b/backend/docs/interfaces/orm_functions_orm_types.UpdateJobApplicationSkill.html @@ -1,17 +1,17 @@ -UpdateJobApplicationSkill | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu
+UpdateJobApplicationSkill | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

interface for the object needed to update a job application skill's data

-

Hierarchy

  • UpdateJobApplicationSkill

Index

Properties

JobApplicationId: number
+

Hierarchy

  • UpdateJobApplicationSkill

Index

Properties

JobApplicationId: number

undefined if unchanged or new job application

-
JobApplicationSkillId: number
+
JobApplicationSkillId: number

the jobapplicaton we are updating

-
isPreferred: boolean
+
isPreferred: boolean

undefined if unchanged or the new preffered status

-
is_best: boolean
+
is_best: boolean

undefined if unchanged or the new is best status

-
languageId: number
+
languageId: null | number

undefined if unchanged or the new language of the job application skill

-
level: number
+
level: number

undefined if unchanged or the new level

-
skill: string
+
skill: null | string

undefined if unchanged or the new skill

-

Legend

  • Interface
  • Property
  • Function

Settings

Theme

Generated using TypeDoc

\ No newline at end of file +

Legend

  • Variable
  • Function
  • Type alias
  • Interface
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/interfaces/orm_functions_orm_types.UpdateLanguage.html b/backend/docs/interfaces/orm_functions_orm_types.UpdateLanguage.html index 3f1e2fd0..e8fc960c 100644 --- a/backend/docs/interfaces/orm_functions_orm_types.UpdateLanguage.html +++ b/backend/docs/interfaces/orm_functions_orm_types.UpdateLanguage.html @@ -1,7 +1,7 @@ -UpdateLanguage | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu
+UpdateLanguage | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Legend

  • Interface
  • Property
  • Function

Settings

Theme

Generated using TypeDoc

\ No newline at end of file +

Legend

  • Variable
  • Function
  • Type alias
  • Interface
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/interfaces/orm_functions_orm_types.UpdateLoginUser.html b/backend/docs/interfaces/orm_functions_orm_types.UpdateLoginUser.html index fefdb598..4bf2483b 100644 --- a/backend/docs/interfaces/orm_functions_orm_types.UpdateLoginUser.html +++ b/backend/docs/interfaces/orm_functions_orm_types.UpdateLoginUser.html @@ -1,13 +1,13 @@ -UpdateLoginUser | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu
+UpdateLoginUser | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

interface for the object needed to update a login user's data

-

Hierarchy

  • UpdateLoginUser

Index

Properties

accountStatus: account_status_enum
+

Hierarchy

  • UpdateLoginUser

Index

Properties

accountStatus: account_status_enum

undefined if unchanged or the new account status that indicates the login user status

-
isAdmin: boolean
+
isAdmin?: boolean

undefined if unchanged or the new boolean value that indicates if this login user is an admin

-
isCoach: boolean
+
isCoach?: boolean

undefined if unchanged or the new boolean value that indicates if this login user is a coach

-
loginUserId: number
+
loginUserId: number

the login user who's info we are updating

-
password?: null | string
+
password?: null | string

undefined if unchanged or the new password

-

Legend

  • Interface
  • Property
  • Function

Settings

Theme

Generated using TypeDoc

\ No newline at end of file +

Legend

  • Variable
  • Function
  • Type alias
  • Interface
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/interfaces/orm_functions_orm_types.UpdateOsoc.html b/backend/docs/interfaces/orm_functions_orm_types.UpdateOsoc.html index 5db04f9e..d586bba5 100644 --- a/backend/docs/interfaces/orm_functions_orm_types.UpdateOsoc.html +++ b/backend/docs/interfaces/orm_functions_orm_types.UpdateOsoc.html @@ -1,7 +1,7 @@ -UpdateOsoc | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu
+UpdateOsoc | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Legend

  • Interface
  • Property
  • Function

Settings

Theme

Generated using TypeDoc

\ No newline at end of file +

Legend

  • Variable
  • Function
  • Type alias
  • Interface
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/interfaces/orm_functions_orm_types.UpdatePerson.html b/backend/docs/interfaces/orm_functions_orm_types.UpdatePerson.html index 6511dc90..efd1a99a 100644 --- a/backend/docs/interfaces/orm_functions_orm_types.UpdatePerson.html +++ b/backend/docs/interfaces/orm_functions_orm_types.UpdatePerson.html @@ -1,13 +1,13 @@ -UpdatePerson | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu
+UpdatePerson | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

interface for the object needed to update a person's data

-

Hierarchy

  • UpdatePerson

Index

Properties

email?: null | string
+

Hierarchy

  • UpdatePerson

Index

Properties

email?: null | string

undefined if unchanged or the new email

-
firstname?: string
+
firstname?: string

undefined if unchanged or new firstname

-
github?: null | string
+
github?: null | string

undefined if unchanged or the new github

-
lastname?: string
+
lastname?: string

undefined if unchanged or the new lastname

-
personId: number
+
personId: number

the person who's info we are updating

-

Legend

  • Interface
  • Property
  • Function

Settings

Theme

Generated using TypeDoc

\ No newline at end of file +

Legend

  • Variable
  • Function
  • Type alias
  • Interface
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/interfaces/orm_functions_orm_types.UpdateProject.html b/backend/docs/interfaces/orm_functions_orm_types.UpdateProject.html index 0b166288..f7f8102b 100644 --- a/backend/docs/interfaces/orm_functions_orm_types.UpdateProject.html +++ b/backend/docs/interfaces/orm_functions_orm_types.UpdateProject.html @@ -1,17 +1,17 @@ -UpdateProject | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu
+UpdateProject | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

interface for the object needed to update a project's data

-

Hierarchy

  • UpdateProject

Index

Properties

endDate: Date
+

Hierarchy

  • UpdateProject

Index

Properties

endDate?: Date

undefined if unchanged or the new end date of the project

-
name: string
+
name?: string

undefined if unchanged or new project name

-
osocId: number
+
osocId?: number

undefined if unchanged or the new osoc id

-
partner: string
+
partner?: string

undefined if unchanged or the new partner of the project

-
positions: number
+
positions?: number

undefined if unchanged or the new number of positions of the project

-
projectId: number
+
projectId: number

the project we are updating

-
startDate: Date
+
startDate?: Date

undefined if unchanged or the new start date of the project

-

Legend

  • Interface
  • Property
  • Function

Settings

Theme

Generated using TypeDoc

\ No newline at end of file +

Legend

  • Variable
  • Function
  • Type alias
  • Interface
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/interfaces/orm_functions_orm_types.UpdateProjectRole.html b/backend/docs/interfaces/orm_functions_orm_types.UpdateProjectRole.html index c6b8ca7c..5895b73a 100644 --- a/backend/docs/interfaces/orm_functions_orm_types.UpdateProjectRole.html +++ b/backend/docs/interfaces/orm_functions_orm_types.UpdateProjectRole.html @@ -1,11 +1,11 @@ -UpdateProjectRole | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu
+UpdateProjectRole | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

interface for the object needed to update a project's data

-

Hierarchy

  • UpdateProjectRole

Index

Properties

positions: number
+

Hierarchy

  • UpdateProjectRole

Index

Properties

positions: number

undefined if unchanged or the new number of positions for this role in the project

-
projectId: number
+
projectId: number

undefined if unchanged or the new project id

-
projectRoleId: number
+
projectRoleId: number

the project we are updating

-
roleId: number
+
roleId: number

undefined if unchanged or the new role id

-

Legend

  • Interface
  • Property
  • Function

Settings

Theme

Generated using TypeDoc

\ No newline at end of file +

Legend

  • Variable
  • Function
  • Type alias
  • Interface
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/interfaces/orm_functions_orm_types.UpdateRole.html b/backend/docs/interfaces/orm_functions_orm_types.UpdateRole.html index d3a88369..a5189a05 100644 --- a/backend/docs/interfaces/orm_functions_orm_types.UpdateRole.html +++ b/backend/docs/interfaces/orm_functions_orm_types.UpdateRole.html @@ -1,7 +1,7 @@ -UpdateRole | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu
+UpdateRole | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Legend

  • Interface
  • Property
  • Function

Settings

Theme

Generated using TypeDoc

\ No newline at end of file +

Legend

  • Variable
  • Function
  • Type alias
  • Interface
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/interfaces/orm_functions_orm_types.UpdateStudent.html b/backend/docs/interfaces/orm_functions_orm_types.UpdateStudent.html index 327698cf..c9d939ea 100644 --- a/backend/docs/interfaces/orm_functions_orm_types.UpdateStudent.html +++ b/backend/docs/interfaces/orm_functions_orm_types.UpdateStudent.html @@ -1,15 +1,15 @@ -UpdateStudent | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu
+UpdateStudent | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

interface for the object needed to update a student's data

-

Hierarchy

  • UpdateStudent

Index

Properties

alumni?: boolean
+

Hierarchy

  • UpdateStudent

Index

Properties

alumni?: boolean

undefined if unchanged or the new boolean value that indicates if this student is an alumni

-
gender?: string
+
gender?: string

undefined if unchanged or the new gender

-
nickname?: null | string
+
nickname?: null | string

undefined if unchanged or the new nickname

-
phoneNumber?: string
+
phoneNumber?: string

undefined if unchanged or the new phone number

-
pronouns?: string[]
+
pronouns?: null | string

undefined if unchanged or new list of pronouns

-
studentId: number
+
studentId: number

the student who's info we are updating

-

Legend

  • Interface
  • Property
  • Function

Settings

Theme

Generated using TypeDoc

\ No newline at end of file +

Legend

  • Variable
  • Function
  • Type alias
  • Interface
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/interfaces/orm_functions_orm_types.UpdateTemplate.html b/backend/docs/interfaces/orm_functions_orm_types.UpdateTemplate.html new file mode 100644 index 00000000..77871852 --- /dev/null +++ b/backend/docs/interfaces/orm_functions_orm_types.UpdateTemplate.html @@ -0,0 +1 @@ +UpdateTemplate | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Legend

  • Variable
  • Function
  • Type alias
  • Interface
  • Property

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/modules.html b/backend/docs/modules.html index 0ebf8b7f..e636153d 100644 --- a/backend/docs/modules.html +++ b/backend/docs/modules.html @@ -1 +1 @@ -backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

backend

Legend

  • Function
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file +backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

backend

Legend

  • Variable
  • Function
  • Type alias
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/modules/orm_functions_applied_role.html b/backend/docs/modules/orm_functions_applied_role.html index b514b69a..87c266ac 100644 --- a/backend/docs/modules/orm_functions_applied_role.html +++ b/backend/docs/modules/orm_functions_applied_role.html @@ -1,2 +1,3 @@ -orm_functions/applied_role | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module orm_functions/applied_role

Legend

  • Function
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file +orm_functions/applied_role | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module orm_functions/applied_role

Index

Functions

  • deleteAppliedRolesByJobApplication(jobApplicationId: number): Promise<BatchPayload>
  • getAppliedRolesByJobApplication(jobApplicationId: number): Promise<applied_role[]>

Legend

  • Variable
  • Function
  • Type alias
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/modules/orm_functions_attachment.html b/backend/docs/modules/orm_functions_attachment.html index 41ad1bba..da457824 100644 --- a/backend/docs/modules/orm_functions_attachment.html +++ b/backend/docs/modules/orm_functions_attachment.html @@ -1,9 +1,9 @@ -orm_functions/attachment | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module orm_functions/attachment

Index

Functions

  • createAttachment(jobApplicationId: number, data: string, type: type_enum): Promise<attachment>
  • deleteAttachment(attachmentId: number): Promise<attachment>
  • getAttachmentById(attachmentId: number): Promise<null | attachment>

Legend

  • Variable
  • Function
  • Type alias
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/modules/orm_functions_contract.html b/backend/docs/modules/orm_functions_contract.html index 2ad83499..22302f20 100644 --- a/backend/docs/modules/orm_functions_contract.html +++ b/backend/docs/modules/orm_functions_contract.html @@ -1,13 +1,18 @@ -orm_functions/contract | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module orm_functions/contract

Index

Functions

Legend

  • Variable
  • Function
  • Type alias
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/modules/orm_functions_evaluation.html b/backend/docs/modules/orm_functions_evaluation.html index 36cb46c7..6e649da0 100644 --- a/backend/docs/modules/orm_functions_evaluation.html +++ b/backend/docs/modules/orm_functions_evaluation.html @@ -1,6 +1,11 @@ -orm_functions/evaluation | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module orm_functions/evaluation

Index

Functions

  • checkIfFinalEvaluationExists(jobApplicationId: number): Promise<null | { evaluation_id: number }>
  • deleteEvaluationsByJobApplication(jobApplicationId: number): Promise<BatchPayload>
  • getEvaluationByPartiesFor(userId: number, studentId: number, osocId: number): Promise<evaluation[]>
  • +

    return all evaluations created by the user with userId, for student with studentID in osoc edition with given osocId

    +

    Parameters

    • userId: number
    • studentId: number
    • osocId: number

    Returns Promise<evaluation[]>

  • getLoginUserByEvaluationId(evaluationId: number): Promise<null | (evaluation & { login_user: login_user & { person: person } })>
  • +

    returns the loginUser with person data of the person that created the evaluation with given id

    +

    Parameters

    • evaluationId: number

    Returns Promise<null | (evaluation & { login_user: login_user & { person: person } })>

Legend

  • Variable
  • Function
  • Type alias
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/modules/orm_functions_general_purpose.html b/backend/docs/modules/orm_functions_general_purpose.html index b343323a..db97090c 100644 --- a/backend/docs/modules/orm_functions_general_purpose.html +++ b/backend/docs/modules/orm_functions_general_purpose.html @@ -1,2 +1,2 @@ -orm_functions/general_purpose | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module orm_functions/general_purpose

Legend

  • Function
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file +orm_functions/general_purpose | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module orm_functions/general_purpose

Legend

  • Variable
  • Function
  • Type alias
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/modules/orm_functions_job_application.html b/backend/docs/modules/orm_functions_job_application.html index 508a76af..72a5852c 100644 --- a/backend/docs/modules/orm_functions_job_application.html +++ b/backend/docs/modules/orm_functions_job_application.html @@ -1,17 +1,17 @@ -orm_functions/job_application | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module orm_functions/job_application

Index

Functions

  • changeEmailStatusOfJobApplication(jobApplicationId: number, emailStatus: email_status_enum): Promise<job_application>
  • getJobApplication(jobApplicationId: number): Promise<null | (job_application & { applied_role: applied_role[]; attachment: attachment[]; job_application_skill: job_application_skill[] })>
  • Parameters

    • jobApplicationId: number

    Returns Promise<null | (job_application & { applied_role: applied_role[]; attachment: attachment[]; job_application_skill: job_application_skill[] })>

    the found job application

    +
  • getJobApplicationByYear(year: number): Promise<(job_application & { applied_role: applied_role[]; attachment: attachment[]; job_application_skill: job_application_skill[] })[]>
  • Parameters

    • year: number

    Returns Promise<(job_application & { applied_role: applied_role[]; attachment: attachment[]; job_application_skill: job_application_skill[] })[]>

    all the job applications associated with the given year

    +
  • getLatestApplicationRolesForStudent(studentId: number): Promise<null | { applied_role: { role_id: number }[] }>
  • Parameters

    • studentId: number

    Returns Promise<null | { applied_role: { role_id: number }[] }>

    the list of selected roles in the application (if it exists)

    +
  • getLatestJobApplicationOfStudent(studentId: number): Promise<null | (job_application & { applied_role: applied_role[]; attachment: attachment[]; job_application_skill: job_application_skill[] })>
  • Parameters

    • studentId: number

    Returns Promise<null | (job_application & { applied_role: applied_role[]; attachment: attachment[]; job_application_skill: job_application_skill[] })>

    the found job applications of the given student

    +
  • getStudentEvaluationsFinal(studentId: number): Promise<{ evaluation: { decision: decision_enum; evaluation_id: number; is_final: boolean; login_user: { login_user_id: number; person: { email: null | string; firstname: string; github: null | string; lastname: string; person_id: number } }; motivation: null | string }[]; osoc: { year: number } }[]>
  • Parameters

    • studentId: number

    Returns Promise<{ evaluation: { decision: decision_enum; evaluation_id: number; is_final: boolean; login_user: { login_user_id: number; person: { email: null | string; firstname: string; github: null | string; lastname: string; person_id: number } }; motivation: null | string }[]; osoc: { year: number } }[]>

    al the evaluations associated with this student together with the osoc year

    +
  • getStudentEvaluationsTemp(studentId: number): Promise<{ evaluation: { decision: decision_enum; evaluation_id: number; is_final: boolean; login_user: { login_user_id: number; person: { email: null | string; firstname: string; github: null | string; lastname: string; person_id: number } }; motivation: null | string }[]; osoc: { year: number } }[]>
  • Parameters

    • studentId: number

    Returns Promise<{ evaluation: { decision: decision_enum; evaluation_id: number; is_final: boolean; login_user: { login_user_id: number; person: { email: null | string; firstname: string; github: null | string; lastname: string; person_id: number } }; motivation: null | string }[]; osoc: { year: number } }[]>

    al the evaluations associated with this student together with the osoc year

    +
  • getStudentEvaluationsTotal(studentId: number): Promise<{ evaluation: { decision: decision_enum; evaluation_id: number; is_final: boolean; login_user: { login_user_id: number; person: { email: null | string; firstname: string; github: null | string; lastname: string; person_id: number } }; motivation: null | string }[]; osoc: { year: number } }[]>
  • Parameters

    • studentId: number

    Returns Promise<{ evaluation: { decision: decision_enum; evaluation_id: number; is_final: boolean; login_user: { login_user_id: number; person: { email: null | string; firstname: string; github: null | string; lastname: string; person_id: number } }; motivation: null | string }[]; osoc: { year: number } }[]>

    al the evaluations associated with this student together with the osoc year

    +

Legend

  • Variable
  • Function
  • Type alias
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/modules/orm_functions_job_application_skill.html b/backend/docs/modules/orm_functions_job_application_skill.html index 283c54e3..142797a4 100644 --- a/backend/docs/modules/orm_functions_job_application_skill.html +++ b/backend/docs/modules/orm_functions_job_application_skill.html @@ -1,8 +1,11 @@ -orm_functions/job_application_skill | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module orm_functions/job_application_skill

Index

Functions

  • deleteJobApplicationSkill(jobApplicationSkillId: number): Promise<job_application_skill & { job_application: job_application }>
  • deleteSkillsByJobApplicationId(jobApplicationId: number): Promise<BatchPayload>
  • +

    deletes all jobApplicationSkills for the given JobApplicationId

    +

    Parameters

    • jobApplicationId: number

    Returns Promise<BatchPayload>

    the number of deleted records in a promise

    +
  • getAllJobApplicationSkill(): Promise<job_application_skill[]>
  • getAllJobApplicationSkillByJobApplication(jobApplicationId: number): Promise<job_application_skill[]>
  • getJobApplicationSkill(jobApplicationSkillId: number): Promise<null | job_application_skill>

Legend

  • Variable
  • Function
  • Type alias
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/modules/orm_functions_language.html b/backend/docs/modules/orm_functions_language.html index f47468bd..81478b44 100644 --- a/backend/docs/modules/orm_functions_language.html +++ b/backend/docs/modules/orm_functions_language.html @@ -1,9 +1,9 @@ -orm_functions/language | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module orm_functions/language

Index

Functions

  • createLanguage(name: string): Promise<language>
  • deleteLanguage(languageId: number): Promise<language>
  • deleteLanguageByName(name: string): Promise<language>
  • getAllLanguages(): Promise<language[]>
  • getLanguage(languageId: number): Promise<null | language>
  • returns:

    object with the name of the language

    +

    Parameters

    • languageId: number

    Returns Promise<null | language>

  • getLanguageByName(name: string): Promise<null | language>
  • returns:

    the language object with a matching name

    +

    Parameters

    • name: string

    Returns Promise<null | language>

Legend

  • Variable
  • Function
  • Type alias
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/modules/orm_functions_login_user.html b/backend/docs/modules/orm_functions_login_user.html index cd80f5bd..2d77c2f9 100644 --- a/backend/docs/modules/orm_functions_login_user.html +++ b/backend/docs/modules/orm_functions_login_user.html @@ -1,19 +1,40 @@ -orm_functions/login_user | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module orm_functions/login_user

Index

Functions

  • deleteLoginUserById(loginUserId: number): Promise<login_user & { person: person }>
  • getPasswordLoginUser(loginUserId: number): Promise<null | { password: null | string }>
  • returns:

    password of the login user object

    +

    Parameters

    • loginUserId: number

    Returns Promise<null | { password: null | string }>

  • getPasswordLoginUserByPerson(personId: number): Promise<null | { password: null | string }>
  • returns:

    password of the login user object

    +

    Parameters

    • personId: number

    Returns Promise<null | { password: null | string }>

  • searchAllAdminAndCoachLoginUsers(bool: boolean): Promise<(login_user & { person: person })[]>
  • returns:

    all login user objects where the bool matches with both the admin and coach status

    +

    Parameters

    • bool: boolean

    Returns Promise<(login_user & { person: person })[]>

  • searchAllAdminLoginUsers(isAdmin: boolean): Promise<(login_user & { person: person })[]>
  • returns:

    all login user objects that match with the admin status

    +

    Parameters

    • isAdmin: boolean

    Returns Promise<(login_user & { person: person })[]>

  • searchAllCoachLoginUsers(isCoach: boolean): Promise<(login_user & { person: person })[]>
  • returns:

    all login user objects that match with the coach status

    +

    Parameters

    • isCoach: boolean

    Returns Promise<(login_user & { person: person })[]>

  • searchLoginUserByPerson(personId: number): Promise<null | (login_user & { person: person })>
  • returns:

    login user object

    +

    Parameters

    • personId: number

    Returns Promise<null | (login_user & { person: person })>

  • setAdmin(loginUserId: number, isAdmin: boolean): Promise<login_user & { person: person }>
  • +

    Sets the is_admin field of the loginuser to IsAdmin

    +

    Parameters

    • loginUserId: number
      +

      the id of the loginUser whose field we are updating

      +
    • isAdmin: boolean
      +

      the new value of is_admin

      +

    Returns Promise<login_user & { person: person }>

    a promise with the updated entry, the person object is also included

    +
  • setCoach(loginUserId: number, isCoach: boolean): Promise<login_user & { person: person }>
  • +

    Sets the is_coach field of the loginUser to isCoach

    +

    Parameters

    • loginUserId: number
    • isCoach: boolean

    Returns Promise<login_user & { person: person }>

    a promise with the updated entry, the person object is also included

    +
  • updateLoginUser(loginUser: UpdateLoginUser): Promise<login_user & { person: person }>

Legend

  • Variable
  • Function
  • Type alias
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/modules/orm_functions_orm_types.html b/backend/docs/modules/orm_functions_orm_types.html index 47f7ddbf..8971805f 100644 --- a/backend/docs/modules/orm_functions_orm_types.html +++ b/backend/docs/modules/orm_functions_orm_types.html @@ -1 +1,11 @@ -orm_functions/orm_types | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module orm_functions/orm_types

Legend

  • Function
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file +orm_functions/orm_types | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module orm_functions/orm_types

Index

Type aliases

FilterBoolean: boolean | undefined
+

type to use in a filter query for booleans

+
FilterNumber: number | undefined
+

type to use in a filter query for numbers

+
FilterNumberArray: number[] | undefined
+

type to use in a filter query for array of numbers

+
FilterSort: "asc" | "desc" | undefined
+

type to use in a filter query for sorting

+
FilterString: string | undefined
+

type to use in a filter query for strings

+
FilterStringArray: string[] | undefined

Legend

  • Variable
  • Function
  • Type alias
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/modules/orm_functions_osoc.html b/backend/docs/modules/orm_functions_osoc.html index eef4a0a7..c000cf77 100644 --- a/backend/docs/modules/orm_functions_osoc.html +++ b/backend/docs/modules/orm_functions_osoc.html @@ -1,12 +1,19 @@ -orm_functions/osoc | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module orm_functions/osoc

Index

Functions

  • createOsoc(year: number): Promise<osoc>
  • deleteOsoc(osocId: number): Promise<osoc>
  • deleteOsocFromDB(osocId: number): Promise<void>
  • filterOsocs(yearFilter: FilterNumber, yearSort: FilterSort): Promise<(osoc & { _count: { project: number } })[]>
  • Parameters

    • yearFilter: FilterNumber
      +

      year that we are filtering on (or undefined if not filtering on year)

      +
    • yearSort: FilterSort
      +

      asc or desc if we are sorting on year, undefined if we are not sorting on year

      +

    Returns Promise<(osoc & { _count: { project: number } })[]>

    the filtered osoc editions with their project count in a promise

    +
  • getAllOsoc(): Promise<(osoc & { _count: { project: number } })[]>
  • Returns Promise<(osoc & { _count: { project: number } })[]>

    a list of all the osoc objects in the database

    +
  • getLatestOsoc(): Promise<null | osoc>
  • getNewestOsoc(): Promise<null | osoc>
  • getOsocAfterYear(year: number): Promise<osoc[]>
  • returns:

    all the osoc objects that took place after the supplied year

    +

    Parameters

    • year: number

    Returns Promise<osoc[]>

  • getOsocBeforeYear(year: number): Promise<osoc[]>
  • returns:

    all the osoc objects that took place before the supplied year

    +

    Parameters

    • year: number

    Returns Promise<osoc[]>

  • getOsocByYear(year: number): Promise<null | osoc>

Legend

  • Variable
  • Function
  • Type alias
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/modules/orm_functions_password_reset.html b/backend/docs/modules/orm_functions_password_reset.html new file mode 100644 index 00000000..8b94ae32 --- /dev/null +++ b/backend/docs/modules/orm_functions_password_reset.html @@ -0,0 +1,18 @@ +orm_functions/password_reset | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module orm_functions/password_reset

Index

Functions

  • createOrUpdateReset(loginUserId: number, resetId: string, validUntil: Date): Promise<password_reset>
  • +

    creates a new entry for password reset if none exists yet. +overwrites the info of the old reset entry if a reset entry already exists

    +

    Parameters

    • loginUserId: number
      +

      the id of the loginUser whose passport we want to reset

      +
    • resetId: string
      +

      the unique id to reset the passport of the login user

      +
    • validUntil: Date
      +

      timestamp that shows until when the resetId is valid

      +

    Returns Promise<password_reset>

    the created/updated entry from the database in a promise

    +
  • deleteResetWithLoginUser(loginUserId: number): Promise<password_reset>
  • Parameters

    • loginUserId: number
      +

      the id of the loginUser whose reset entry we want to + delete

      +

    Returns Promise<password_reset>

    the deleted entry (or null if there was no entry) inside a promise

    +
  • deleteResetWithResetId(resetId: string): Promise<password_reset>
  • Parameters

    • resetId: string
      +

      the resetId of the entry we want to delete

      +

    Returns Promise<password_reset>

    the deleted entry (or null if there was no entry) inside a promise

    +
  • findResetByCode(resetCode: string): Promise<null | password_reset>

Legend

  • Variable
  • Function
  • Type alias
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/modules/orm_functions_person.html b/backend/docs/modules/orm_functions_person.html index f37544f9..e876663e 100644 --- a/backend/docs/modules/orm_functions_person.html +++ b/backend/docs/modules/orm_functions_person.html @@ -1,10 +1,11 @@ -orm_functions/person | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module orm_functions/person

Index

Functions

  • deletePersonById(personId: number): Promise<person>
  • searchPersonByName(name: string): Promise<person[]>
  • returns:

    a list of all the person objects in the database that match

    +

    Parameters

    • name: string

    Returns Promise<person[]>

Legend

  • Variable
  • Function
  • Type alias
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/modules/orm_functions_project.html b/backend/docs/modules/orm_functions_project.html index 9bb01879..316d652e 100644 --- a/backend/docs/modules/orm_functions_project.html +++ b/backend/docs/modules/orm_functions_project.html @@ -1,24 +1,40 @@ -orm_functions/project | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module orm_functions/project

Index

Functions

  • deleteProject(projectId: number): Promise<project>
  • Parameters

    • projectNameFilter: FilterString
      +

      project name that we are filtering on (or undefined if not filtering on name)

      +
    • clientNameFilter: FilterString
      +

      client name that we are filtering on (or undefined if not filtering on name)

      +
    • assignedCoachesFilterArray: FilterNumberArray
      +

      assigned coaches that we are filtering on (or undefined if not filtering on assigned coaches)

      +
    • fullyAssignedFilter: FilterBoolean
      +

      fully assigned status that we are filtering on (or undefined if not filtering on assigned)

      +
    • projectNameSort: FilterSort
      +

      asc or desc if we want to sort on project name, undefined if we are not sorting on project name

      +
    • clientNameSort: FilterSort
      +

      asc or desc if we want to sort on client name, undefined if we are not sorting on client name

      +
    • fullyAssignedSort: FilterSort
      +

      asc or desc if we are sorting on fully assigned, undefined if we are not sorting on fully assigned

      +

    Returns Promise<(project & { project_role: { positions: number; role: { name: string } }[]; project_user: { login_user: { is_coach: boolean; login_user_id: number } }[] })[]>

    the filtered students with their person data and other filter fields in a promise

    +
  • getAllProjects(): Promise<project[]>
  • getProjectById(projectId: number): Promise<null | project>
  • returns:

    object with all the info about this project

    +

    Parameters

    • projectId: number

    Returns Promise<null | project>

  • getProjectByName(projectName: string): Promise<project[]>
  • returns:

    object with all the info about this project

    +

    Parameters

    • projectName: string

    Returns Promise<project[]>

  • getProjectsByEndDate(endDate: Date): Promise<project[]>
  • Parameters

    • endDate: Date

    Returns Promise<project[]>

    all the projects with a matching end date in the database

    +
  • getProjectsByNumberPositions(positions: number): Promise<project[]>
  • Parameters

    • positions: number

    Returns Promise<project[]>

    all the project objects that have the exact amount of positions

    +
  • getProjectsByOsocEdition(osocId: number): Promise<project[]>
  • getProjectsByPartner(partner: string): Promise<project[]>
  • getProjectsByStartDate(startDate: Date): Promise<project[]>
  • Parameters

    • startDate: Date

    Returns Promise<project[]>

    all the projects with a matching start date in the database

    +
  • getProjectsEndedAfterDate(date: Date): Promise<project[]>
  • getProjectsEndedBeforeDate(date: Date): Promise<project[]>
  • getProjectsLessPositions(positions: number): Promise<project[]>
  • Parameters

    • positions: number

    Returns Promise<project[]>

    all the project objects that have less positions

    +
  • getProjectsMorePositions(positions: number): Promise<project[]>
  • Parameters

    • positions: number

    Returns Promise<project[]>

    all the project objects that have more positions

    +
  • getProjectsStartedAfterDate(date: Date): Promise<project[]>
  • Parameters

    • date: Date

    Returns Promise<project[]>

    all the projects that started after the supplied date

    +
  • getProjectsStartedBeforeDate(date: Date): Promise<project[]>
  • Parameters

    • date: Date

    Returns Promise<project[]>

    all the projects that started before the supplied date

    +

Legend

  • Variable
  • Function
  • Type alias
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/modules/orm_functions_project_role.html b/backend/docs/modules/orm_functions_project_role.html index d9ca367f..225b95f8 100644 --- a/backend/docs/modules/orm_functions_project_role.html +++ b/backend/docs/modules/orm_functions_project_role.html @@ -1,9 +1,12 @@ -orm_functions/project_role | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module orm_functions/project_role

Index

Functions

  • deleteProjectRole(projectRoleId: number): Promise<project_role>
  • getNumberOfFreePositions(projectRoleId: number): Promise<null | number>
  • Parameters

    • projectRoleId: number

    Returns Promise<null | number>

    the number of free positions if free (in a promise) else null (if not + found in db)

    +
  • getNumberOfRolesByProjectAndRole(projectId: number, projectRoleId: number): Promise<project_role[]>
  • returns:

    returns the number of positions for the projectrole of that project

    +

    Parameters

    • projectId: number
    • projectRoleId: number

    Returns Promise<project_role[]>

  • getProjectRoleById(projectRoleId: number): Promise<null | { project_id: number; project_role_id: number; role: role; role_id: number }>
  • Parameters

    • projectRoleId: number

    Returns Promise<null | { project_id: number; project_role_id: number; role: role; role_id: number }>

  • getProjectRoleNamesByProject(projectId: number): Promise<(project_role & { role: role })[]>
  • returns:

    returns all the projectroles object togheter with the role objects +for that project

    +

    Parameters

    • projectId: number

    Returns Promise<(project_role & { role: role })[]>

  • getProjectRolesByProject(projectId: number): Promise<project_role[]>

Legend

  • Variable
  • Function
  • Type alias
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/modules/orm_functions_project_user.html b/backend/docs/modules/orm_functions_project_user.html index 1c20c6e3..60e3d087 100644 --- a/backend/docs/modules/orm_functions_project_user.html +++ b/backend/docs/modules/orm_functions_project_user.html @@ -1 +1,5 @@ -orm_functions/project_user | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module orm_functions/project_user

Legend

  • Function
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file +orm_functions/project_user | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module orm_functions/project_user

Legend

  • Variable
  • Function
  • Type alias
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/modules/orm_functions_role.html b/backend/docs/modules/orm_functions_role.html index 2c3aec10..0d964ade 100644 --- a/backend/docs/modules/orm_functions_role.html +++ b/backend/docs/modules/orm_functions_role.html @@ -1,14 +1,14 @@ -orm_functions/role | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module orm_functions/role

Index

Functions

  • createProjectRole(name: string): Promise<role>
  • deleteRole(roleId: number): Promise<role>
  • getRole(roleId: number): Promise<null | role>
  • returns:

    object with the name of the role

    +

    Parameters

    • roleId: number

    Returns Promise<null | role>

  • getRolesByName(name: string): Promise<null | role>
  • returns:

    all the role objects with a matching name

    +

    Parameters

    • name: string

    Returns Promise<null | role>

Legend

  • Variable
  • Function
  • Type alias
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/modules/orm_functions_session_key.html b/backend/docs/modules/orm_functions_session_key.html index ba3187f6..f9755316 100644 --- a/backend/docs/modules/orm_functions_session_key.html +++ b/backend/docs/modules/orm_functions_session_key.html @@ -1 +1,20 @@ -orm_functions/session_key | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module orm_functions/session_key

Index

Functions

  • addSessionKey(loginUserId: number, key: string): Promise<session_keys>
  • changeSessionKey(key: string, newkey: string): Promise<session_keys>
  • checkSessionKey(key: string): Promise<{ login_user_id: number }>
  • removeAllKeysForUser(key: string): Promise<BatchPayload>

Legend

  • Function
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file +orm_functions/session_key | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module orm_functions/session_key

Index

Functions

  • addSessionKey(loginUserId: number, key: string, date: Date): Promise<session_keys>
  • +

    adds session key to loginUser

    +

    Parameters

    • loginUserId: number
      +

      the id of the loginUser for whom we are adding a session key

      +
    • key: string
      +

      the new session key

      +
    • date: Date

    Returns Promise<session_keys>

    the new record in the database in a promise

    +
  • checkSessionKey(key: string): Promise<null | { login_user_id: number }>
  • +

    checks if the session key exists

    +

    Parameters

    • key: string

    Returns Promise<null | { login_user_id: number }>

    the loginUser associated with the key in a promise if the keys is valid otherwise an error in a promise

    +
  • refreshKey(key: string, date: Date): Promise<session_keys>
  • removeAllKeysForLoginUserId(loginUserId: number): Promise<BatchPayload>
  • Parameters

    • loginUserId: number
      +

      the id of the loginUser whose session keys we want to remove

      +

    Returns Promise<BatchPayload>

    a promise with the number of deleted records

    +
  • removeAllKeysForUser(key: string): Promise<{ count: number }>
  • +

    deletes all session keys of the user that has the given key

    +

    Parameters

    • key: string
      +

      a key of a user whose keys we want to delete

      +

    Returns Promise<{ count: number }>

    the number of deleted records in a promise

    +

Legend

  • Variable
  • Function
  • Type alias
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/modules/orm_functions_student.html b/backend/docs/modules/orm_functions_student.html index d977a8d3..e7e43deb 100644 --- a/backend/docs/modules/orm_functions_student.html +++ b/backend/docs/modules/orm_functions_student.html @@ -1,8 +1,33 @@ -orm_functions/student | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module orm_functions/student

Index

Functions

  • deleteStudent(studentId: number): Promise<student & { person: person }>
  • filterStudents(firstNameFilter: FilterString, lastNameFilter: FilterString, emailFilter: FilterString, roleFilterArray: FilterStringArray, alumniFilter: FilterBoolean, coachFilter: FilterBoolean, statusFilter: undefined | decision_enum, osocYear: number, emailStatusFilter: undefined | email_status_enum, firstNameSort: FilterSort, lastNameSort: FilterSort, emailSort: FilterSort, alumniSort: FilterSort): Promise<(student & { job_application: { applied_role: (applied_role & { role: { name: string } })[]; evaluation: { decision: decision_enum }[]; student_coach: boolean }[]; person: person })[]>
  • Parameters

    • firstNameFilter: FilterString
      +

      firstname that we are filtering on (or undefined if not filtering on name)

      +
    • lastNameFilter: FilterString
      +

      firstname that we are filtering on (or undefined if not filtering on name)

      +
    • emailFilter: FilterString
      +

      email that we are filtering on (or undefined if not filtering on email)

      +
    • roleFilterArray: FilterStringArray
      +

      role that we are filtering on (or undefined if not filtering on role)

      +
    • alumniFilter: FilterBoolean
      +

      alumni status that we are filtering on (or undefined if not filtering on alumni status)

      +
    • coachFilter: FilterBoolean
      +

      coach status that we are filtering on (or undefined if not filtering on coach status)

      +
    • statusFilter: undefined | decision_enum
      +

      decision status that we are filtering on (or undefined if not filtering on status)

      +
    • osocYear: number
    • emailStatusFilter: undefined | email_status_enum
      +

      email status that we are filtering on (or undefined if not filtering on email status)

      +
    • firstNameSort: FilterSort
      +

      asc or desc if we want to sort on firstname, undefined if we are not sorting on firstname

      +
    • lastNameSort: FilterSort
      +

      asc or desc if we want to sort on lastname, undefined if we are not sorting on lastname

      +
    • emailSort: FilterSort
      +

      asc or desc if we are sorting on email, undefined if we are not sorting on email

      +
    • alumniSort: FilterSort
      +

      asc or desc if we are sorting on alumni status, undefined if we are not sorting on alumni status

      +

    Returns Promise<(student & { job_application: { applied_role: (applied_role & { role: { name: string } })[]; evaluation: { decision: decision_enum }[]; student_coach: boolean }[]; person: person })[]>

    the filtered students with their person data and other filter fields in a promise

    +
  • getAllStudents(): Promise<(student & { person: person })[]>
  • Returns Promise<(student & { person: person })[]>

    a list of all the student objects in the database together with their personal info

    +
  • getStudent(studentId: number): Promise<null | (student & { person: person })>
  • returns:

    object with all the info about this student together with their personal info

    +

    Parameters

    • studentId: number

    Returns Promise<null | (student & { person: person })>

  • searchStudentByGender(gender: string): Promise<(student & { person: person })[]>
  • returns:

    a list of all the person objects in the database that match

    +

    Parameters

    • gender: string

    Returns Promise<(student & { person: person })[]>

  • updateStudent(student: UpdateStudent): Promise<student & { person: person }>

Legend

  • Variable
  • Function
  • Type alias
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/modules/orm_functions_template.html b/backend/docs/modules/orm_functions_template.html new file mode 100644 index 00000000..4ab497cc --- /dev/null +++ b/backend/docs/modules/orm_functions_template.html @@ -0,0 +1 @@ +orm_functions/template | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module orm_functions/template

Index

Functions

  • deleteTemplate(id: number): Promise<template_email>
  • getAllTemplates(): Promise<template_email[]>
  • getTemplateById(templateId: number): Promise<null | template_email>
  • getTemplatesByName(name: string): Promise<(template_email & { login_user: login_user })[]>

Legend

  • Variable
  • Function
  • Type alias
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/modules/routes_admin.html b/backend/docs/modules/routes_admin.html index 76e912a1..e9d2608e 100644 --- a/backend/docs/modules/routes_admin.html +++ b/backend/docs/modules/routes_admin.html @@ -1,5 +1,23 @@ -routes/admin | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module routes/admin

Index

Functions

Functions

  • getRouter(): express.Router
  • +routes/admin | backend
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module routes/admin

    Index

    Functions

    • deleteAdmin(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>): Promise<Responses.Empty>
    • +

      Attempts to delete an admin from the system.

      +

      Parameters

      • req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>
        +

        The Express.js request to extract all required data from.

        +

      Returns Promise<Responses.Empty>

      See the API documentation. Successes are passed using +Promise.resolve, failures using Promise.reject.

      +
    • getRouter(): express.Router
    • Gets the router for all /admin/ related endpoints.

      -

      Returns express.Router

      An Epress.js {@link express.Router} routing all /admin/ +

    Returns express.Router

    An Express.js {@link express.Router} routing all /admin/ endpoints.

    -

Legend

  • Function
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file +
  • listAdmins(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>): Promise<Responses.AdminList>
  • +

    Attempts to list all admins in the system.

    +

    Parameters

    • req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>
      +

      The Express.js request to extract all required data from.

      +

    Returns Promise<Responses.AdminList>

    See the API documentation. Successes are passed using +Promise.resolve, failures using Promise.reject.

    +
  • modAdmin(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>): Promise<Responses.Admin>
  • +

    Attempts to update a certain admin in the system.

    +

    Parameters

    • req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>
      +

      The Express.js request to extract all required data from.

      +

    Returns Promise<Responses.Admin>

    See the API documentation. Successes are passed using +Promise.resolve, failures using Promise.reject.

    +

Legend

  • Variable
  • Function
  • Type alias
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/modules/routes_coach.html b/backend/docs/modules/routes_coach.html index a7e47a51..dd24e858 100644 --- a/backend/docs/modules/routes_coach.html +++ b/backend/docs/modules/routes_coach.html @@ -1,5 +1,29 @@ -routes/coach | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module routes/coach

Index

Functions

Functions

  • getRouter(): express.Router
  • listCoaches(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>): Promise<Responses.CoachList>
  • +

    Attempts to list all coaches in the system.

    +

    Parameters

    • req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>
      +

      The Express.js request to extract all required data from.

      +

    Returns Promise<Responses.CoachList>

    See the API documentation. Successes are passed using +Promise.resolve, failures using Promise.reject.

    +
  • modCoach(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>): Promise<Responses.PartialCoach>
  • +

    Attempts to modify a certain coach in the system.

    +

    Parameters

    • req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>
      +

      The Express.js request to extract all required data from.

      +

    Returns Promise<Responses.PartialCoach>

    See the API documentation. Successes are passed using +Promise.resolve, failures using Promise.reject.

    +

Legend

  • Variable
  • Function
  • Type alias
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/modules/routes_followup.html b/backend/docs/modules/routes_followup.html new file mode 100644 index 00000000..ee13f54a --- /dev/null +++ b/backend/docs/modules/routes_followup.html @@ -0,0 +1,19 @@ +routes/followup | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module routes/followup

Index

Functions

  • getFollowup(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>): Promise<Responses.SingleFollowup>
  • +

    Attempts to get a single followup.

    +

    Parameters

    • req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>
      +

      The Express.js request to extract all required data from.

      +

    Returns Promise<Responses.SingleFollowup>

    See the API documentation. Successes are passed using +Promise.resolve, failures using Promise.reject.

    +
  • getRouter(): Router
  • listFollowups(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>): Promise<Responses.FollowupList>
  • +

    Attempts to list all followups in the system.

    +

    Parameters

    • req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>
      +

      The Express.js request to extract all required data from.

      +

    Returns Promise<Responses.FollowupList>

    See the API documentation. Successes are passed using +Promise.resolve, failures using Promise.reject.

    +
  • updateFollowup(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>): Promise<Responses.SingleFollowup>
  • +

    Attempts to update a single followup.

    +

    Parameters

    • req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>
      +

      The Express.js request to extract all required data from.

      +

    Returns Promise<Responses.SingleFollowup>

    See the API documentation. Successes are passed using +Promise.resolve, failures using Promise.reject.

    +

Legend

  • Variable
  • Function
  • Type alias
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/modules/routes_form.html b/backend/docs/modules/routes_form.html index 45bfe74b..777c29aa 100644 --- a/backend/docs/modules/routes_form.html +++ b/backend/docs/modules/routes_form.html @@ -1,5 +1,260 @@ -routes/form | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module routes/form

Index

Functions

Functions

  • getRouter(): express.Router
  • +routes/form | backend
    Options
    All
    • Public
    • Public/Protected
    • All
    Menu

    Module routes/form

    Index

    Functions

    • addAttachmentsToDatabase(formResponse: FormAttachment, job_applicationId: Id): Promise<Responses.Empty>
    • +

      Attempts to add attachments to the database.

      +

      Parameters

      • formResponse: FormAttachment
        +

        The response with the data.

        +
      • job_applicationId: Id
        +

        The job_application id object.

        +

      Returns Promise<Responses.Empty>

      See the API documentation. Successes are passed using + Promise.resolve, failures using Promise.reject.

      +
    • addJobApplicationToDatabase(formResponse: FormJobApplication, student_id: Id): Promise<Responses.Id>
    • +

      Attempts to add a job application to the database.

      +

      Parameters

      • formResponse: FormJobApplication
        +

        The response with the data.

        +
      • student_id: Id
        +

        The student id object.

        +

      Returns Promise<Responses.Id>

      See the API documentation. Successes are passed using + Promise.resolve, failures using Promise.reject.

      +
    • addPersonToDatabase(formResponse: FormPerson): Promise<Responses.Id>
    • +

      Attempts to add a person to the database.

      +

      Parameters

      • formResponse: FormPerson
        +

        The response with the data.

        +

      Returns Promise<Responses.Id>

      See the API documentation. Successes are passed using + Promise.resolve, failures using Promise.reject.

      +
    • addRolesToDatabase(formResponse: FormRoles, job_applicationId: Id): Promise<Responses.Empty>
    • +

      Attempts to add roles to the database.

      +

      Parameters

      • formResponse: FormRoles
        +

        The response with the data.

        +
      • job_applicationId: Id
        +

        The job_application id object.

        +

      Returns Promise<Responses.Empty>

      See the API documentation. Successes are passed using + Promise.resolve, failures using Promise.reject.

      +
    • addSkillsToDatabase(formResponse: FormJobApplicationSkill, job_applicationId: Id): Promise<Responses.Empty>
    • +

      Attempts to add a job application skill to the database.

      +

      Parameters

      • formResponse: FormJobApplicationSkill
        +

        The response with the data.

        +
      • job_applicationId: Id
        +

        The job application id.

        +

      Returns Promise<Responses.Empty>

      See the API documentation. Successes are passed using + Promise.resolve, failures using Promise.reject.

      +
    • addStudentToDatabase(formResponse: FormStudent, personId: Id): Promise<Responses.Id_alumni>
    • +

      Attempts to add a student to the database.

      +

      Parameters

      • formResponse: FormStudent
        +

        The response with the data.

        +
      • personId: Id
        +

        The id of a person.

        +

      Returns Promise<Responses.Id_alumni>

      See the API documentation. Successes are passed using + Promise.resolve, failures using Promise.reject.

      +
    • checkQuestionsExist(questions: FormResponse<Question>[]): boolean
    • Parameters

      • questions: FormResponse<Question>[]

      Returns boolean

    • checkWordInAnswer(question: Question, word: string): Responses.FormResponse<boolean>
    • +

      This function checks if the answer on a certain question contains a word.

      +

      Parameters

      • question: Question
        +

        The question.

        +
      • word: string
        +

        the word we are searching for in the answer.

        +

      Returns Responses.FormResponse<boolean>

      True if the question options contain the word, else false.

      +
    • createForm(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>): Promise<Responses.Empty>
    • +

      Attempts to create a new form in the system.

      +

      Parameters

      • req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>
        +

        The Express.js request to extract all required data from.

        +

      Returns Promise<Responses.Empty>

      See the API documentation. Successes are passed using + Promise.resolve, failures using Promise.reject.

      +
    • filterChosenOption(question: Question): Responses.FormResponse<Requests.Option>
    • +

      This function searches the chosen option for a given question.

      +

      Parameters

      • question: Question
        +

        The question.

        +

      Returns Responses.FormResponse<Requests.Option>

      The option that corresponds with the given answer.

      +
    • filterQuestion(form: Form, key: string): Responses.FormResponse<Requests.Question>
    • +

      This function searches a question with a given key in the form.

      +

      Parameters

      • form: Form
        +

        The form with the answers.

        +
      • key: string
        +

        The key of the question.

        +

      Returns Responses.FormResponse<Requests.Question>

      The question that corresponds with the given key.

      +
    • getAlumni(form: Form): Promise<boolean>
    • +

      Parse the form to the email of this student.

      +

      Parameters

      • form: Form
        +

        The form with the answers.

        +

      Returns Promise<boolean>

      See the API documentation. Successes are passed using + Promise.resolve, failures using Promise.reject.

      +
    • getAppliedRoles(form: Form): Promise<string[]>
    • +

      Parse the form to the roles of this student.

      +

      Parameters

      • form: Form
        +

        The form with the answers.

        +

      Returns Promise<string[]>

      See the API documentation. Successes are passed using + Promise.resolve, failures using Promise.reject.

      +
    • getBestSkill(form: Form): Promise<string>
    • +

      Parse the form to the best skill this student.

      +

      Parameters

      • form: Form
        +

        The form with the answers.

        +

      Returns Promise<string>

      See the API documentation. Successes are passed using + Promise.resolve, failures using Promise.reject.

      +
    • getBirthName(form: Form): Promise<string>
    • +

      Parse the form to the birth name of this student.

      +

      Parameters

      • form: Form
        +

        The form with the answers.

        +

      Returns Promise<string>

      See the API documentation. Successes are passed using + Promise.resolve, failures using Promise.reject.

      +
    • getCV(form: Form): Promise<Responses.FormAttachmentResponse>
    • +

      Parse the form to the cv of this student.

      +

      Parameters

      • form: Form
        +

        The form with the answers.

        +

      Returns Promise<Responses.FormAttachmentResponse>

      See the API documentation. Successes are passed using + Promise.resolve, failures using Promise.reject.

      +
    • getEducationDuration(form: Form): Promise<number | null>
    • +

      Parse the form to the duration of the education of this student.

      +

      Parameters

      • form: Form
        +

        The form with the answers.

        +

      Returns Promise<number | null>

      See the API documentation. Successes are passed using + Promise.resolve, failures using Promise.reject.

      +
    • getEducationLevel(form: Form): Promise<string>
    • +

      Parse the form to the education levels of this student.

      +

      Parameters

      • form: Form
        +

        The form with the answers.

        +

      Returns Promise<string>

      See the API documentation. Successes are passed using + Promise.resolve, failures using Promise.reject.

      +
    • getEducationUniversity(form: Form): Promise<string | null>
    • +

      Parse the form to the university of this student.

      +

      Parameters

      • form: Form
        +

        The form with the answers.

        +

      Returns Promise<string | null>

      See the API documentation. Successes are passed using + Promise.resolve, failures using Promise.reject.

      +
    • getEducationYear(form: Form): Promise<string | null>
    • +

      Parse the form to the current year of the education of this student.

      +

      Parameters

      • form: Form
        +

        The form with the answers.

        +

      Returns Promise<string | null>

      See the API documentation. Successes are passed using + Promise.resolve, failures using Promise.reject.

      +
    • getEducations(form: Form): Promise<string[]>
    • +

      Parse the form to the educations of this student.

      +

      Parameters

      • form: Form
        +

        The form with the answers.

        +

      Returns Promise<string[]>

      See the API documentation. Successes are passed using + Promise.resolve, failures using Promise.reject.

      +
    • getEmail(form: Form): Promise<string>
    • +

      Parse the form to the email of this student.

      +

      Parameters

      • form: Form
        +

        The form with the answers.

        +

      Returns Promise<string>

      See the API documentation. Successes are passed using + Promise.resolve, failures using Promise.reject.

      +
    • getEnglishLevel(form: Form): Promise<number>
    • +

      Parse the form to the english level of this student.

      +

      Parameters

      • form: Form
        +

        The form with the answers.

        +

      Returns Promise<number>

      See the API documentation. Successes are passed using + Promise.resolve, failures using Promise.reject.

      +
    • getFunFact(form: Form): Promise<string>
    • +

      Parse the form to the fun fact of this student.

      +

      Parameters

      • form: Form
        +

        The form with the answers.

        +

      Returns Promise<string>

      See the API documentation. Successes are passed using + Promise.resolve, failures using Promise.reject.

      +
    • getGender(form: Form): Promise<string>
    • +

      Parse the form to the gender of this student.

      +

      Parameters

      • form: Form
        +

        The form with the answers.

        +

      Returns Promise<string>

      See the API documentation. Successes are passed using + Promise.resolve, failures using Promise.reject.

      +
    • getLastName(form: Form): Promise<string>
    • +

      Parse the form to the last name of this student.

      +

      Parameters

      • form: Form
        +

        The form with the answers.

        +

      Returns Promise<string>

      See the API documentation. Successes are passed using + Promise.resolve, failures using Promise.reject.

      +
    • getMostFluentLanguage(form: Form): Promise<string>
    • +

      Parse the form to the most fluent language of this student.

      +

      Parameters

      • form: Form
        +

        The form with the answers.

        +

      Returns Promise<string>

      See the API documentation. Successes are passed using + Promise.resolve, failures using Promise.reject.

      +
    • getMotivation(form: Form): Promise<Responses.FormAttachmentResponse>
    • +

      Parse the form to the motivation of this student.

      +

      Parameters

      • form: Form
        +

        The form with the answers.

        +

      Returns Promise<Responses.FormAttachmentResponse>

      See the API documentation. Successes are passed using + Promise.resolve, failures using Promise.reject.

      +
    • getNickname(form: Form): Promise<string | null>
    • +

      Parse the form to the email of this student.

      +

      Parameters

      • form: Form
        +

        The form with the answers.

        +

      Returns Promise<string | null>

      See the API documentation. Successes are passed using + Promise.resolve, failures using Promise.reject.

      +
    • getPhoneNumber(form: Form): Promise<string>
    • +

      Parse the form to the phone number of this student.

      +

      Parameters

      • form: Form
        +

        The form with the answers.

        +

      Returns Promise<string>

      See the API documentation. Successes are passed using + Promise.resolve, failures using Promise.reject.

      +
    • getPortfolio(form: Form): Promise<Responses.FormAttachmentResponse>
    • +

      Parse the form to the portfolio of this student.

      +

      Parameters

      • form: Form
        +

        The form with the answers.

        +

      Returns Promise<Responses.FormAttachmentResponse>

      See the API documentation. Successes are passed using + Promise.resolve, failures using Promise.reject.

      +
    • getPronouns(form: Form): Promise<string | null>
    • +

      Parse the form to the pronouns of this student.

      +

      Parameters

      • form: Form
        +

        The form with the answers.

        +

      Returns Promise<string | null>

      See the API documentation. Successes are passed using + Promise.resolve, failures using Promise.reject.

      +
    • getResponsibilities(form: Form): Promise<string | null>
    • +

      Parse the form to the responsibilities of this student.

      +

      Parameters

      • form: Form
        +

        The form with the answers.

        +

      Returns Promise<string | null>

      See the API documentation. Successes are passed using + Promise.resolve, failures using Promise.reject.

      +
    • getRouter(): express.Router
    • Gets the router for all /form/ related endpoints.

      Returns express.Router

      An Express.js {@link express.Router} routing all /form/ endpoints.

      -

    Legend

    • Function
    • Interface

    Settings

    Theme

    Generated using TypeDoc

    \ No newline at end of file +
  • getVolunteerInfo(form: Form): Promise<string>
  • +

    Parse the form to the volunteer info of this student.

    +

    Parameters

    • form: Form
      +

      The form with the answers.

      +

    Returns Promise<string>

    See the API documentation. Successes are passed using + Promise.resolve, failures using Promise.reject.

    +
  • isStudentCoach(form: Form, hasAlreadyParticipated: boolean): Promise<boolean | null>
  • +

    Parse the form to the choice of being a student coach.

    +

    Parameters

    • form: Form
      +

      The form with the answers.

      +
    • hasAlreadyParticipated: boolean
      +

      true if the student from the job application has already taken part in osoc previous years

      +

    Returns Promise<boolean | null>

    See the API documentation. Successes are passed using + Promise.resolve, failures using Promise.reject.

    +
  • jsonToAttachments(form: Form): Promise<Responses.FormAttachment>
  • +

    Attempts to parse the answers in the form into attachment entities.

    +

    Parameters

    • form: Form
      +

      The form with the answers.

      +

    Returns Promise<Responses.FormAttachment>

    See the API documentation. Successes are passed using + Promise.resolve, failures using Promise.reject.

    +
  • jsonToJobApplication(form: Form, hasAlreadyTakenPart: boolean): Promise<Responses.FormJobApplication>
  • +

    Attempts to parse the answers in the form into a job application entity.

    +

    Parameters

    • form: Form
      +

      The form with the answers.

      +
    • hasAlreadyTakenPart: boolean
      +

      true if the student has already taken part in osoc in a prev edition

      +

    Returns Promise<Responses.FormJobApplication>

    See the API documentation. Successes are passed using + Promise.resolve, failures using Promise.reject.

    +
  • jsonToPerson(form: Form): Promise<Responses.FormPerson>
  • +

    Attempts to parse the answers in the form into a person entity.

    +

    Parameters

    • form: Form
      +

      The form with the answers.

      +

    Returns Promise<Responses.FormPerson>

    See the API documentation. Successes are passed using + Promise.resolve, failures using Promise.reject.

    +
  • jsonToRoles(form: Form): Promise<Responses.FormRoles>
  • +

    Attempts to parse the answers in the form into role entities.

    +

    Parameters

    • form: Form
      +

      The form with the answers.

      +

    Returns Promise<Responses.FormRoles>

    See the API documentation. Successes are passed using + Promise.resolve, failures using Promise.reject.

    +
  • jsonToSkills(form: Form): Promise<Responses.FormJobApplicationSkill>
  • +

    Attempts to parse the answers in the form into job application skills entities.

    +

    Parameters

    • form: Form
      +

      The form with the answers.

      +

    Returns Promise<Responses.FormJobApplicationSkill>

    See the API documentation. Successes are passed using + Promise.resolve, failures using Promise.reject.

    +
  • jsonToStudent(form: Form): Promise<Responses.FormStudent>
  • +

    Attempts to parse the answers in the form into a student entity.

    +

    Parameters

    • form: Form
      +

      The form with the answers.

      +

    Returns Promise<Responses.FormStudent>

    See the API documentation. Successes are passed using + Promise.resolve, failures using Promise.reject.

    +

Legend

  • Variable
  • Function
  • Type alias
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/modules/routes_form_keys_json.html b/backend/docs/modules/routes_form_keys_json.html new file mode 100644 index 00000000..60126343 --- /dev/null +++ b/backend/docs/modules/routes_form_keys_json.html @@ -0,0 +1 @@ +routes/form_keys.json | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module routes/form_keys.json

Index

Properties

Properties

export=: { addPronouns: string; alumni: string; appliedRole: string; appliedRoleInput: string; bestSkill: string; birthName: string; cvLink: string; cvUpload: string; eduDuration: string; eduInstitute: string; eduLevel: string; eduLevelInput: string; eduYear: string; edus: string; edusInput: string; emailAddress: string; englishLevel: string; funFact: string; gender: string; lastName: string; liveInBelgium: string; mostFluentLanguage: string; mostFluentLanguageInput: string; motivationInput: string; motivationLink: string; motivationUpload: string; nickname: string; nicknameInput: string; phoneNumber: string; portfolioLink: string; portfolioUpload: string; preferredPronouns: string; pronounsInput: string; responsibilities: string; studentCoach: string; volunteerInfo: string; workInJuly: string }

Type declaration

  • addPronouns: string
  • alumni: string
  • appliedRole: string
  • appliedRoleInput: string
  • bestSkill: string
  • birthName: string
  • cvLink: string
  • cvUpload: string
  • eduDuration: string
  • eduInstitute: string
  • eduLevel: string
  • eduLevelInput: string
  • eduYear: string
  • edus: string
  • edusInput: string
  • emailAddress: string
  • englishLevel: string
  • funFact: string
  • gender: string
  • lastName: string
  • liveInBelgium: string
  • mostFluentLanguage: string
  • mostFluentLanguageInput: string
  • motivationInput: string
  • motivationLink: string
  • motivationUpload: string
  • nickname: string
  • nicknameInput: string
  • phoneNumber: string
  • portfolioLink: string
  • portfolioUpload: string
  • preferredPronouns: string
  • pronounsInput: string
  • responsibilities: string
  • studentCoach: string
  • volunteerInfo: string
  • workInJuly: string

Legend

  • Variable
  • Function
  • Type alias
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/modules/routes_github.html b/backend/docs/modules/routes_github.html new file mode 100644 index 00000000..97b87f3c --- /dev/null +++ b/backend/docs/modules/routes_github.html @@ -0,0 +1,33 @@ +routes/github | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module routes/github

Index

Variables

states: string[] = []

Functions

  • checkState(state: string): boolean
  • +

    Checks if a state exists, then removes that state from the set of valid +states.

    +

    Parameters

    • state: string
      +

      The state to check.

      +

    Returns boolean

    True if the state is valid, otherwise false.

    +
  • genState(): string
  • +

    Generates a new state for GitHub, then adds it to the current states. This + state is a a string of 64 cryptographically random bytes.

    +

    Returns string

    The new state.

    +
  • getHome(): string
  • +

    Gets the home URL for the github callback.

    +

    Returns string

    The home URL.

    +
  • getRouter(): express.Router
  • ghExchangeAccessToken(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>, res: Response<any, Record<string, any>>): Promise<void>
  • +

    Step two of the GitHub OAuth process: exchanging the temporary code for an +access token. This function first asks an access token, then checks if a +a user exists in our system and if not, creates one.

    +

    Parameters

    • req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>
    • res: Response<any, Record<string, any>>

    Returns Promise<void>

  • ghIdentity(resp: Response<any, Record<string, any>>): Promise<void>
  • +

    Step one of the GitHub OAuth process: getting the identity of the user. +This calls GitHub's login, then asks Github to redirect the user to +/github/challenge on our server. This function only generates a new state, +makes it valid and then redirects.

    +

    Parameters

    • resp: Response<any, Record<string, any>>

    Returns Promise<void>

  • ghSignupOrLogin(login: GHLogin): Promise<Responses.Login>
  • +

    Callback for the actual signup and/or login. Uses the provided GitHub data +to access our underlying database. If the user doesn't exist, their account +is created. If the user exists, we check if we need to update their name +and/or GitHub handle.

    +

    Parameters

    • login: GHLogin

    Returns Promise<Responses.Login>

  • githubNameChange(login: GHLogin, person: { firstname: string; github: null | string; login_user: null | { account_status: account_status_enum; is_admin: boolean; is_coach: boolean; login_user_id: number; password: null | string }; person_id: number }): boolean
  • +

    Checks if we need to update a GitHub user's name and/or handle.

    +

    Parameters

    • login: GHLogin
    • person: { firstname: string; github: null | string; login_user: null | { account_status: account_status_enum; is_admin: boolean; is_coach: boolean; login_user_id: number; password: null | string }; person_id: number }
      • firstname: string
      • github: null | string
      • login_user: null | { account_status: account_status_enum; is_admin: boolean; is_coach: boolean; login_user_id: number; password: null | string }
      • person_id: number

    Returns boolean

  • parseGHLogin(data: Anything): Promise<Requests.GHLogin>
  • +

    Checks if all required fields are available. We need (at least) a login, +name and id in the request.

    +

    Parameters

    • data: Anything

    Returns Promise<Requests.GHLogin>

Legend

  • Variable
  • Function
  • Type alias
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/modules/routes_login.html b/backend/docs/modules/routes_login.html index a266fc16..03d556e8 100644 --- a/backend/docs/modules/routes_login.html +++ b/backend/docs/modules/routes_login.html @@ -1,5 +1,17 @@ -routes/login | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module routes/login

Index

Functions

Functions

  • getRouter(): express.Router
  • login(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>): Promise<Responses.Login>
  • +

    Attempts to log a user into the system.

    +

    Parameters

    • req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>
      +

      The Express.js request to extract all required data from.

      +

    Returns Promise<Responses.Login>

    See the API documentation. Successes are passed using +Promise.resolve, failures using Promise.reject.

    +
  • logout(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>): Promise<Responses.Empty>
  • +

    Attempts to log a user out of the system.

    +

    Parameters

    • req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>
      +

      The Express.js request to extract all required data from.

      +

    Returns Promise<Responses.Empty>

    See the API documentation. Successes are passed using +Promise.resolve, failures using Promise.reject.

    +

Legend

  • Variable
  • Function
  • Type alias
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/modules/routes_project.html b/backend/docs/modules/routes_project.html index 4dc8712b..b608747a 100644 --- a/backend/docs/modules/routes_project.html +++ b/backend/docs/modules/routes_project.html @@ -1,5 +1,41 @@ -routes/project | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module routes/project

Index

Functions

Functions

  • getRouter(): express.Router
  • listProjects(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>): Promise<Responses.ProjectList>
  • +

    Attempts to list all projects in the system.

    +

    Parameters

    • req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>
      +

      The Express.js request to extract all required data from.

      +

    Returns Promise<Responses.ProjectList>

    See the API documentation. Successes are passed using +Promise.resolve, failures using Promise.reject.

    +
  • modProject(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>): Promise<Responses.Project>
  • Parameters

    • req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>

    Returns Promise<Responses.Project>

  • modProjectStudent(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>): Promise<Responses.ModProjectStudent>
  • Parameters

    • req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>

    Returns Promise<Responses.ModProjectStudent>

  • unAssignStudent(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>): Promise<Responses.Empty>
  • Parameters

    • req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>

    Returns Promise<Responses.Empty>

Legend

  • Variable
  • Function
  • Type alias
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/modules/routes_reset.html b/backend/docs/modules/routes_reset.html new file mode 100644 index 00000000..a88b4621 --- /dev/null +++ b/backend/docs/modules/routes_reset.html @@ -0,0 +1,20 @@ +routes/reset | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module routes/reset

Index

Functions

  • checkCode(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>): Promise<Responses.Empty>
  • +

    Route used to check if a reset code is valid. +Use route /reset/:id with a 'GET' request.

    +

    Parameters

    • req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>
      +

    Returns Promise<Responses.Empty>

  • createEmail(resetID: string): string
  • +

    Returns the html body for the email with the reset code applied.

    +

    Parameters

    • resetID: string
      +

      The ID that validates the password reset.

      +

    Returns string

  • getRouter(): express.Router
  • requestReset(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>): Promise<Responses.Empty>
  • +

    Handles password reset requests +If the email in the 'GET' body is correct, an email with a reset link will be +sent. If not returns an error. Route: /reset

    +

    Parameters

    • req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>
      +

      Request body should be of form { email: string }

      +

    Returns Promise<Responses.Empty>

  • resetPassword(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>): Promise<Responses.Key>
  • +

    Route that will reset the password when the code and password are valid. +Use route /reset/:id with a 'POST' request with body of form +{ password: string }

    +

    Parameters

    • req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>
      +

    Returns Promise<Responses.Key>

  • sendMail(mail: Email): Promise<SentMessageInfo>

Legend

  • Variable
  • Function
  • Type alias
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/modules/routes_role.html b/backend/docs/modules/routes_role.html new file mode 100644 index 00000000..0fa4740f --- /dev/null +++ b/backend/docs/modules/routes_role.html @@ -0,0 +1,17 @@ +routes/role | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module routes/role

Index

Functions

  • createStudentRole(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>): Promise<Responses.PartialStudent>
  • +

    Attempts to create a new role in the system.

    +

    Parameters

    • req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>
      +

      The Express.js request to extract all required data from.

      +

    Returns Promise<Responses.PartialStudent>

    See the API documentation. Successes are passed using +Promise.resolve, failures using Promise.reject.

    +
  • getRouter(): express.Router
  • +

    Gets the router for all /role/ related endpoints.

    +

    Returns express.Router

    An Express.js {@link express.Router} routing all /role/ +endpoints.

    +
  • listStudentRoles(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>): Promise<Responses.StudentList>
  • +

    Attempts to list all roles in the system.

    +

    Parameters

    • req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>
      +

      The Express.js request to extract all required data from.

      +

    Returns Promise<Responses.StudentList>

    See the API documentation. Successes are passed using +Promise.resolve, failures using Promise.reject.

    +

Legend

  • Variable
  • Function
  • Type alias
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/modules/routes_session_key_json.html b/backend/docs/modules/routes_session_key_json.html new file mode 100644 index 00000000..a8e1a979 --- /dev/null +++ b/backend/docs/modules/routes_session_key_json.html @@ -0,0 +1 @@ +routes/session_key.json | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module routes/session_key.json

Legend

  • Variable
  • Function
  • Type alias
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/modules/routes_student.html b/backend/docs/modules/routes_student.html index b117f8e8..fda15e59 100644 --- a/backend/docs/modules/routes_student.html +++ b/backend/docs/modules/routes_student.html @@ -1,5 +1,47 @@ -routes/student | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module routes/student

Index

Functions

Functions

  • getRouter(): express.Router
  • getStudent(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>): Promise<Responses.Student>
  • +

    Attempts to get all data for a certain student in the system.

    +

    Parameters

    • req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>
      +

      The Express.js request to extract all required data from.

      +

    Returns Promise<Responses.Student>

    See the API documentation. Successes are passed using +Promise.resolve, failures using Promise.reject.

    +
  • getStudentSuggestions(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>): Promise<Responses.SuggestionInfo>
  • +

    Attempts to list all suggestions for a certain student.

    +

    Parameters

    • req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>
      +

      The Express.js request to extract all required data from.

      +

    Returns Promise<Responses.SuggestionInfo>

    See the API documentation. Successes are passed using +Promise.resolve, failures using Promise.reject.

    +
  • listStudents(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>): Promise<Responses.StudentList>
  • +

    Attempts to list all students in the system.

    +

    Parameters

    • req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>
      +

      The Express.js request to extract all required data from.

      +

    Returns Promise<Responses.StudentList>

    See the API documentation. Successes are passed using +Promise.resolve, failures using Promise.reject.

    +

Legend

  • Variable
  • Function
  • Type alias
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/modules/routes_template.html b/backend/docs/modules/routes_template.html new file mode 100644 index 00000000..82e62fc1 --- /dev/null +++ b/backend/docs/modules/routes_template.html @@ -0,0 +1 @@ +routes/template | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module routes/template

Index

Functions

  • createTemplate(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>): Promise<Responses.Template>
  • Parameters

    • req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>

    Returns Promise<Responses.Template>

  • deleteTemplate(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>): Promise<Responses.Empty>
  • Parameters

    • req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>

    Returns Promise<Responses.Empty>

  • getAllTemplates(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>): Promise<Responses.TemplateList>
  • Parameters

    • req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>

    Returns Promise<Responses.TemplateList>

  • getRouter(): express.Router
  • getSingleTemplate(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>): Promise<Responses.Template>
  • Parameters

    • req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>

    Returns Promise<Responses.Template>

  • updateTemplate(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>): Promise<Responses.Template>
  • Parameters

    • req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>

    Returns Promise<Responses.Template>

Legend

  • Variable
  • Function
  • Type alias
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/modules/routes_user.html b/backend/docs/modules/routes_user.html new file mode 100644 index 00000000..c9f3af27 --- /dev/null +++ b/backend/docs/modules/routes_user.html @@ -0,0 +1,42 @@ +routes/user | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module routes/user

Index

Functions

  • createUserAcceptance(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>): Promise<Responses.PartialUser>
  • +

    Attempts to accept a request for becoming a login_user.

    +

    Parameters

    • req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>
      +

      The Express.js request to extract all required data from.

      +

    Returns Promise<Responses.PartialUser>

    See the API documentation. Successes are passed using +Promise.resolve, failures using Promise.reject.

    +
  • createUserRequest(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>): Promise<Responses.Id>
  • +

    Attempts to create a new user in the system.

    +

    Parameters

    • req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>
      +

      The Express.js request to extract all required data from.

      +

    Returns Promise<Responses.Id>

    See the API documentation. Successes are passed using +Promise.resolve, failures using Promise.reject.

    +
  • deleteUserRequest(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>): Promise<Responses.PartialUser>
  • +

    Attempts to deny a request for becoming a coach.

    +

    Parameters

    • req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>
      +

      The Express.js request to extract all required data from.

      +

    Returns Promise<Responses.PartialUser>

    See the API documentation. Successes are passed using +Promise.resolve, failures using Promise.reject.

    +
  • filterUsers(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>): Promise<Responses.UserList>
  • +

    Attempts to filter users in the system by name, email, status, coach or +admin.

    +

    Parameters

    • req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>
      +

      The Express.js request to extract all required data from.

      +

    Returns Promise<Responses.UserList>

    See the API documentation. Successes are passed using +Promise.resolve, failures using Promise.reject.

    +
  • getCurrentUser(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>): Promise<Responses.User>
  • +

    Attempts to get all data for a certain student in the system.

    +

    Parameters

    • req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>
      +

      The Express.js request to extract all required data from.

      +

    Returns Promise<Responses.User>

    See the API documentation. Successes are passed using +Promise.resolve, failures using Promise.reject.

    +
  • getRouter(): express.Router
  • +

    Gets the router for all /user/ related endpoints.

    +

    Returns express.Router

    An Express.js {@link express.Router} routing all /user/ +endpoints.

    +
  • listUsers(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>): Promise<Responses.UserList>
  • +

    Attempts to list all students in the system.

    +

    Parameters

    • req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>
      +

      The Express.js request to extract all required data from.

      +

    Returns Promise<Responses.UserList>

    See the API documentation. Successes are passed using +Promise.resolve, failures using Promise.reject.

    +
  • setAccountStatus(person_id: number, stat: account_status_enum, key: string, is_admin: boolean, is_coach: boolean): Promise<Responses.PartialUser>
  • Parameters

    • person_id: number
    • stat: account_status_enum
    • key: string
    • is_admin: boolean
    • is_coach: boolean

    Returns Promise<Responses.PartialUser>

  • userModSelf(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>): Promise<Responses.Key>
  • Parameters

    • req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>

    Returns Promise<Responses.Key>

Legend

  • Variable
  • Function
  • Type alias
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/docs/modules/routes_verify.html b/backend/docs/modules/routes_verify.html new file mode 100644 index 00000000..72908c6b --- /dev/null +++ b/backend/docs/modules/routes_verify.html @@ -0,0 +1,11 @@ +routes/verify | backend
Options
All
  • Public
  • Public/Protected
  • All
Menu

Module routes/verify

Index

Functions

  • getRouter(): express.Router
  • +

    Gets the router for all /verify/ related endpoints.

    +

    Returns express.Router

    An Express.js {@link express.Router} routing all /user/ +endpoints.

    +
  • verifyKey(req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>): Promise<Responses.VerifyKey>
  • +

    Attempts to verify if the session key is valid.

    +

    Parameters

    • req: Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>
      +

      The Express.js request to extract all required data from.

      +

    Returns Promise<Responses.VerifyKey>

    See the API documentation. Successes are passed using +Promise.resolve, failures using Promise.reject.

    +

Legend

  • Variable
  • Function
  • Type alias
  • Interface

Settings

Theme

Generated using TypeDoc

\ No newline at end of file diff --git a/backend/endpoints.ts b/backend/endpoints.ts index 130f09a1..816094a3 100644 --- a/backend/endpoints.ts +++ b/backend/endpoints.ts @@ -1,28 +1,47 @@ -import express from 'express'; +import express from "express"; -import * as config from './config.json'; -import * as admin from './routes/admin'; -import * as coach from './routes/coach'; -import * as form from './routes/form'; -import * as login from './routes/login'; -import * as project from './routes/project'; -import * as student from './routes/student'; -import * as util from './utility'; +import * as config from "./config.json"; +import * as admin from "./routes/admin"; +import * as coach from "./routes/coach"; +import * as followup from "./routes/followup"; +import * as form from "./routes/form"; +import * as github from "./routes/github"; +import * as login from "./routes/login"; +import * as osoc from "./routes/osoc"; +import * as project from "./routes/project"; +import * as reset from "./routes/reset"; +import * as role from "./routes/role"; +import * as student from "./routes/student"; +import * as user from "./routes/user"; +import * as verify from "./routes/verify"; +import * as util from "./utility"; + +import * as template from "./routes/template"; /** * Attaches all endpoints to the application. * @param app The Express.js application to attach to. */ export function attach(app: express.Application): void { - config.global.homes.forEach(home => { - app.use(home + '/login', login.getRouter()); - app.use(home + '/student', student.getRouter()); - app.use(home + '/coach', coach.getRouter()); - app.use(home + '/admin', admin.getRouter()); - app.use(home + '/project', project.getRouter()); - app.use(home + '/form', form.getRouter); - }); + config.global.homes.forEach((home) => { + app.use(home + "/login", login.getRouter()); + app.use(home + "/student", student.getRouter()); + app.use(home + "/coach", coach.getRouter()); + app.use(home + "/admin", admin.getRouter()); + app.use(home + "/project", project.getRouter()); + app.use(home + "/form", form.getRouter()); + app.use(home + "/osoc", osoc.getRouter()); + app.use(home + "/reset", reset.getRouter()); + app.use(home + "/github", github.getRouter()); + app.use(home + "/user", user.getRouter()); + app.use(home + "/role", role.getRouter()); + app.use(home + "/template", template.getRouter()); + app.use(home + "/followup", followup.getRouter()); + app.use(home + "/verify", verify.getRouter()); + }); - app.use((req: express.Request, res: express.Response): Promise => - util.replyError(res, util.errors.cookNonExistent(req.url))); + app.use( + (req: express.Request, res: express.Response): Promise => + util.replyError(res, util.errors.cookNonExistent(req.url)) + ); } diff --git a/backend/index.ts b/backend/index.ts index b1b8263a..db576000 100644 --- a/backend/index.ts +++ b/backend/index.ts @@ -1,23 +1,63 @@ -import body from 'body-parser'; -import express from 'express'; - -import * as config from './config.json'; -import * as ep from './endpoints' -import * as util from './utility'; -import cors from 'cors'; +import body from "body-parser"; +import cors from "cors"; +import express from "express"; +import path from "path"; +import * as config from "./config.json"; +import * as ep from "./endpoints"; +import * as util from "./utility"; +import { createServer } from "http"; +import { Server } from "socket.io"; +import { + ClientToServerEvents, + InterServerEvents, + ServerToClientEvents, + SocketData, +} from "./types"; +import { registerLoginUserHandlers } from "./websocket_events/login_user"; const app: express.Application = express(); const port: number = config.port; +const httpServer = createServer(app); + +// require makes it A LOT easier to use this. Import gives some weird behaviour +// that is not easy to work around +// eslint-disable-next-line @typescript-eslint/no-var-requires +require("dotenv").config({ + path: path.join(__dirname, `./.env.${process.env.NODE_ENV}`), +}); + +const io = new Server< + ClientToServerEvents, + ServerToClientEvents, + InterServerEvents, + SocketData +>(httpServer, { + cors: { + origin: process.env.FRONTEND, + }, + path: "/socket.io", // we locate the place for websockets at `/socket.io` inside the express server +}); -app.use(body.urlencoded({extended: true})); +app.use(body.urlencoded({ extended: true })); app.use(express.json()); app.use(cors()); app.use((req, _, next) => util.logRequest(req, next)); +app.use((req, _, next) => { + util.queryToBody(req); + next(); +}); ep.attach(app); -config.global.homes.forEach(home => util.addInvalidVerbs(app, home + "/")); +config.global.homes.forEach((home) => util.addInvalidVerbs(app, home + "/")); + +/** + * install all the socket.io listeners. + * We do this by using function "register...." that contain the listeners + */ +io.on("connection", (socket) => { + registerLoginUserHandlers(io, socket); +}); -// Server setup -app.listen(port, () => { +httpServer.listen(port, () => { console.log(`TypeScript with Express - http://localhost:${port}/`); + http://localhost:${port}/`); }); diff --git a/backend/jest.config.js b/backend/jest.config.js index 83201305..57be77c2 100644 --- a/backend/jest.config.js +++ b/backend/jest.config.js @@ -1,7 +1,7 @@ /** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */ module.exports = { - preset: 'ts-jest', - testEnvironment: 'node', - setupFiles: ["./tests/tests-setup.ts"], - setupFilesAfterEnv: ["./tests/orm_integration/integration_setup.ts"] -}; \ No newline at end of file + preset: "ts-jest", + testEnvironment: "node", + setupFiles: ["./tests/tests-setup.ts"], + setupFilesAfterEnv: ["./tests/orm_integration/integration_setup.ts"], +}; diff --git a/backend/orm_functions/applied_role.ts b/backend/orm_functions/applied_role.ts index 6a535b7d..0ed20389 100644 --- a/backend/orm_functions/applied_role.ts +++ b/backend/orm_functions/applied_role.ts @@ -1,30 +1,47 @@ -import prisma from '../prisma/prisma' -import {CreateAppliedRole} from './orm_types'; +import prisma from "../prisma/prisma"; +import { CreateAppliedRole } from "./orm_types"; /** - * + * * @param appliedRole: applied role object with the needed information */ -export async function createAppliedRole(appliedRole: CreateAppliedRole){ +export async function createAppliedRole(appliedRole: CreateAppliedRole) { const result = await prisma.applied_role.create({ data: { job_application_id: appliedRole.jobApplicationId, - role_id: appliedRole.roleId + role_id: appliedRole.roleId, }, }); return result; } /** - * - * @param jobApplicationId: this is the jobapplication of the appliedroles we are looking up in the database + * + * @param jobApplicationId: this is the job application of the applied roles we are looking for in the database * @returns: all the applied roles */ - export async function getAppliedRolesByJobApplication(jobApplicationId: number) { +export async function getAppliedRolesByJobApplication( + jobApplicationId: number +) { const result = prisma.applied_role.findMany({ - where: { - job_application_id: jobApplicationId + where: { + job_application_id: jobApplicationId, }, }); return result; } + +/** + * + * @param jobApplicationId: the id of the jobApplication whose related applied roles we want to delete + * @returns the number of deleted records in a promise + */ +export async function deleteAppliedRolesByJobApplication( + jobApplicationId: number +) { + return await prisma.applied_role.deleteMany({ + where: { + job_application_id: jobApplicationId, + }, + }); +} diff --git a/backend/orm_functions/attachment.ts b/backend/orm_functions/attachment.ts index e97c53a5..5230c8f4 100644 --- a/backend/orm_functions/attachment.ts +++ b/backend/orm_functions/attachment.ts @@ -1,49 +1,54 @@ -import {type_enum} from "@prisma/client"; +import { type_enum } from "@prisma/client"; import prisma from "../prisma/prisma"; /** * create an attachment for job_application_id - * + * * @param jobApplicationId: the application to which this attachment belongs - * @param data: url where we can find this attachment back OR the raw string with the data + * @param data: list of url where we can find this attachment OR a list raw strings with the data * @param type: the type of the attachment (CV, PORTFOLIO, FILE) * @returns the created attachment */ - export async function createAttachment(jobApplicationId: number, data: string, type: type_enum) { +export async function createAttachment( + jobApplicationId: number, + data: string[], + type: type_enum[] +) { return await prisma.attachment.create({ data: { job_application_id: jobApplicationId, data: data, - type: type - } + type: type, + }, }); } /** - * + * * @param attachmentId: the attachment we are going to remove * @returns the removed entry in the database */ -export async function deleteAttachment(attachmentId:number) { +export async function deleteAttachment(attachmentId: number) { return await prisma.attachment.delete({ where: { - attachment_id: attachmentId - } + attachment_id: attachmentId, + }, }); } - /** - * removes all attachments associated with given application - * + * removes all attachments associated with the given job application + * * @param jobApplicationId: the attachments that belong to this job_application are going to be removed * @returns the number of removed attachments {count: number} */ -export async function deleteAllAttachmentsForApplication(jobApplicationId:number) { +export async function deleteAllAttachmentsForApplication( + jobApplicationId: number +) { return await prisma.attachment.deleteMany({ where: { - job_application_id: jobApplicationId - } + job_application_id: jobApplicationId, + }, }); } @@ -54,8 +59,8 @@ export async function deleteAllAttachmentsForApplication(jobApplicationId:number */ export async function getAttachmentById(attachmentId: number) { return await prisma.attachment.findUnique({ - where : { - attachment_id: attachmentId - } + where: { + attachment_id: attachmentId, + }, }); -} \ No newline at end of file +} diff --git a/backend/orm_functions/contract.ts b/backend/orm_functions/contract.ts index 453b5863..6a2d8679 100644 --- a/backend/orm_functions/contract.ts +++ b/backend/orm_functions/contract.ts @@ -1,13 +1,16 @@ -import prisma from '../prisma/prisma'; -import {CreateContract, UpdateContract} from './orm_types'; +import prisma from "../prisma/prisma"; + +import { CreateContract, UpdateContract } from "./orm_types"; /** - * add contract created by login_user_id for student_id that has the role project_role_id - * - * @param contract: CreateContract object with all the info about the contract we want to create (who made the contract for which student for which job) + * add contract created by login_user_id for student_id that has the role + * project_role_id + * + * @param contract: CreateContract object with all the info about the contract + * we want to create (who made the contract for which student for which job) * @returns created contract object in the database */ - export async function createContract(contract: CreateContract) { +export async function createContract(contract: CreateContract) { return await prisma.contract.create({ data: { student_id: contract.studentId, @@ -15,54 +18,105 @@ import {CreateContract, UpdateContract} from './orm_types'; information: contract.information, created_by_login_user_id: contract.loginUserId, contract_status: contract.contractStatus, - } + }, }); } /** - * add contract created by login_user_id for student_id that has the role project_role_id - * - * @param contract: the updated contract. Only the information and contractStatus field can be changed. - * The created_by_login_user_id is updated to the user that made these last changes + * add contract created by login_user_id for student_id that has the role + * project_role_id + * + * @param contract: the updated contract. Only the information and + * contractStatus field can be changed. + * The created_by_login_user_id is updated to the user that made these last + * changes * @returns the updated contract */ export async function updateContract(contract: UpdateContract) { return await prisma.contract.update({ - where: { - contract_id: contract.contractId - }, + where: { contract_id: contract.contractId }, data: { created_by_login_user_id: contract.loginUserId, contract_status: contract.contractStatus, - information: contract.information - } + information: contract.information, + project_role_id: contract.projectRoleId, + }, }); } /** * remove all the contracts associated with studentId - * - * @param studentId: the id of the student who's contracts we are removing + * + * @param studentId: the id of the student whose contracts we are removing * @returns the number of removed contracts {count: number} - */ + */ export async function removeContractsFromStudent(studentId: number) { return await prisma.contract.deleteMany({ - where: { - student_id: studentId - } + where: { student_id: studentId }, }); } /** - * remove the contract with contractId - * + * remove the contract with contractId + * * @param contractId: the id of the contract we are removing * @returns the removed contract - */ + */ export async function removeContract(contractId: number) { - return await prisma.contract.delete({ - where: { - contract_id: contractId - } + return await prisma.contract.delete({ where: { contract_id: contractId } }); +} + +/** + * find all contracts for the given student + * + * @param studentId: the id of the student + * @returns the contract(s) + */ +export async function contractsForStudent(studentId: number) { + return await prisma.contract.findMany({ + where: { student_id: studentId }, + select: { + contract_id: true, + project_role: { + select: { + project_id: true, + project: { select: { osoc_id: true } }, + }, + }, + student: true, + }, + }); +} + +export async function contractsByProject(projectId: number) { + return await prisma.contract.findMany({ + where: { project_role: { project_id: projectId } }, + select: { + student: { + select: { + student_id: true, + gender: true, + pronouns: true, + phone_number: true, + nickname: true, + alumni: true, + person: true, + }, + }, + project_role: true, + contract_status: true, + contract_id: true, + }, }); -} \ No newline at end of file +} + +export async function sortedContractsByOsocEdition(osocId: number) { + return await prisma.contract.findMany({ + where: { project_role: { project: { osoc_id: osocId } } }, + orderBy: { student_id: "asc" }, + select: { + project_role: { select: { project: true } }, + student_id: true, + }, + }); +} diff --git a/backend/orm_functions/evaluation.ts b/backend/orm_functions/evaluation.ts index ee7573aa..5de85fe1 100644 --- a/backend/orm_functions/evaluation.ts +++ b/backend/orm_functions/evaluation.ts @@ -1,10 +1,15 @@ -import prisma from '../prisma/prisma' -import {CreateEvaluationForStudent, UpdateEvaluationForStudent} from './orm_types'; +import prisma from "../prisma/prisma"; + +import { + CreateEvaluationForStudent, + UpdateEvaluationForStudent, +} from "./orm_types"; /** * helper function of createEvaluationForStudent - * - * @param jobApplicationId: the jobApplicationId we are checking for if there is already a final evaluation + * + * @param jobApplicationId: the jobApplicationId we are checking for if there is + * already a final evaluation * @returns the found evaluation or null if no final evaluation exists yet */ export async function checkIfFinalEvaluationExists(jobApplicationId: number) { @@ -19,23 +24,27 @@ export async function checkIfFinalEvaluationExists(jobApplicationId: number) { }); } -// TODO: check if this really works? /** - * - * @param evaluation: this has an object that contains all the information for a new evaluation. - * however if the evaluation is final AND there is already another evaluation, then we modify this earlier "final" decision + * + * @param evaluation: this has an object that contains all the information for a + * new evaluation. + * however, if the evaluation is final AND there is already another evaluation, + * then we modify this earlier "final" decision * @returns a promise with the created evaluation */ -export async function createEvaluationForStudent(evaluation: CreateEvaluationForStudent) { - +export async function createEvaluationForStudent( + evaluation: CreateEvaluationForStudent +) { if (evaluation.isFinal) { - const foundEvaluation = await checkIfFinalEvaluationExists(evaluation.jobApplicationId); + const foundEvaluation = await checkIfFinalEvaluationExists( + evaluation.jobApplicationId + ); if (foundEvaluation) { return await updateEvaluationForStudent({ evaluation_id: foundEvaluation.evaluation_id, loginUserId: evaluation.loginUserId, decision: evaluation.decision, - motivation: evaluation.motivation + motivation: evaluation.motivation, }); } } @@ -45,17 +54,20 @@ export async function createEvaluationForStudent(evaluation: CreateEvaluationFor job_application_id: evaluation.jobApplicationId, decision: evaluation.decision, motivation: evaluation.motivation, - is_final: evaluation.isFinal - } + is_final: evaluation.isFinal, + }, }); } /** - * - * @param evaluation: the updated evaluation. This evaluation only contains some field because we don't want everything changeable. + * + * @param evaluation: the updated evaluation. This evaluation only contains some + * field because we don't want everything changeable. * @returns the updated evaluation. */ -export async function updateEvaluationForStudent(evaluation:UpdateEvaluationForStudent) { +export async function updateEvaluationForStudent( + evaluation: UpdateEvaluationForStudent +) { return await prisma.evaluation.update({ where: { evaluation_id: evaluation.evaluation_id, @@ -64,21 +76,52 @@ export async function updateEvaluationForStudent(evaluation:UpdateEvaluationForS login_user_id: evaluation.loginUserId, decision: evaluation.decision, motivation: evaluation.motivation, - } + }, }); } +/** + * returns the loginUser with person data of the person that created the evaluation with given id + * @param evaluationId: id of the evaluation whose creator we are searching. + */ export async function getLoginUserByEvaluationId(evaluationId: number) { return await prisma.evaluation.findUnique({ - where : { - evaluation_id: evaluationId + where: { evaluation_id: evaluationId }, + include: { login_user: { include: { person: true } } }, + }); +} + +/** + * return all evaluations created by the user with userId, for student with studentID in osoc edition with given osocId + * + * @param userId: the creator of the evaluation + * @param studentId: the student about who the evaluation is + * @param osocId: the id of the osoc edition the evaluation belongs to + */ +export async function getEvaluationByPartiesFor( + userId: number, + studentId: number, + osocId: number +) { + return await prisma.evaluation.findMany({ + where: { + login_user_id: userId, + job_application: { student_id: studentId, osoc_id: osocId }, + }, + }); +} + +/** + * + * @param jobApplicationId: the id of the jobApplication whose evaluations we want to delete + * @returns the number of deleted records in a promise + */ +export async function deleteEvaluationsByJobApplication( + jobApplicationId: number +) { + return await prisma.evaluation.deleteMany({ + where: { + job_application_id: jobApplicationId, }, - include: { - login_user: { - include: { - person: true - } - } - } }); -} \ No newline at end of file +} diff --git a/backend/orm_functions/general_purpose.ts b/backend/orm_functions/general_purpose.ts index e1c87440..c7faef2d 100644 --- a/backend/orm_functions/general_purpose.ts +++ b/backend/orm_functions/general_purpose.ts @@ -1,7 +1,7 @@ -import {getProjectRoleWithRoleName} from "./role"; -import {AddStudentToProject} from "./orm_types"; -import {contract_status_enum} from "@prisma/client"; -import {createContract} from "./contract"; +import { getProjectRoleWithRoleName } from "./role"; +import { AddStudentToProject } from "./orm_types"; +import { contract_status_enum } from "@prisma/client"; +import { createContract } from "./contract"; /** * @@ -9,15 +9,18 @@ import {createContract} from "./contract"; * @return the created contract */ export async function addStudentToProject(requestInfo: AddStudentToProject) { - const project_role = await getProjectRoleWithRoleName(requestInfo.roleName, requestInfo.projectId); + const project_role = await getProjectRoleWithRoleName( + requestInfo.roleName, + requestInfo.projectId + ); if (project_role) { return await createContract({ studentId: requestInfo.studentId, projectRoleId: project_role.project_role_id, information: requestInfo.information, loginUserId: requestInfo.loginUserId, - contractStatus: contract_status_enum.DRAFT + contractStatus: contract_status_enum.DRAFT, }); } - return project_role -} \ No newline at end of file + return project_role; +} diff --git a/backend/orm_functions/job_application.ts b/backend/orm_functions/job_application.ts index e6b8731d..4123de0e 100644 --- a/backend/orm_functions/job_application.ts +++ b/backend/orm_functions/job_application.ts @@ -1,49 +1,64 @@ -import {email_status_enum} from "@prisma/client"; +import { email_status_enum } from "@prisma/client"; import prisma from "../prisma/prisma"; -import {CreateJobApplication} from "./orm_types"; +import { CreateJobApplication } from "./orm_types"; /** - * - * @param studentId: the student who's evaluations we are looking for (final and temporary evaluations) + * + * @param studentId: the student whose evaluations we are looking for (final and temporary evaluations) * @returns al the evaluations associated with this student together with the osoc year */ - export async function getStudentEvaluationsTotal(studentId: number) { +export async function getStudentEvaluationsTotal(studentId: number) { return await prisma.job_application.findMany({ where: { - student_id: studentId + student_id: studentId, }, select: { osoc: { select: { year: true, - } + }, }, evaluation: { select: { decision: true, motivation: true, evaluation_id: true, - } - } - } + is_final: true, + login_user: { + select: { + login_user_id: true, + person: { + select: { + person_id: true, + firstname: true, + lastname: true, + email: true, + github: true, + }, + }, + }, + }, + }, + }, + }, }); } /** - * - * @param studentId: the student who's evaluations we are looking for, but only final decisions + * + * @param studentId: the student whose evaluations we are looking for, but only final decisions * @returns al the evaluations associated with this student together with the osoc year */ export async function getStudentEvaluationsFinal(studentId: number) { return await prisma.job_application.findMany({ where: { - student_id: studentId + student_id: studentId, }, select: { osoc: { select: { year: true, - } + }, }, evaluation: { where: { @@ -53,27 +68,42 @@ export async function getStudentEvaluationsFinal(studentId: number) { decision: true, motivation: true, evaluation_id: true, - } - } - } + is_final: true, + login_user: { + select: { + login_user_id: true, + person: { + select: { + person_id: true, + firstname: true, + lastname: true, + email: true, + github: true, + }, + }, + }, + }, + }, + }, + }, }); } /** - * - * @param studentId: the student who's evaluations we are looking for, but only temporary decisions + * + * @param studentId: the student whose evaluations we are looking for, but only temporary decisions * @returns al the evaluations associated with this student together with the osoc year */ export async function getStudentEvaluationsTemp(studentId: number) { return await prisma.job_application.findMany({ where: { - student_id: studentId + student_id: studentId, }, select: { osoc: { select: { year: true, - } + }, }, evaluation: { where: { @@ -83,70 +113,89 @@ export async function getStudentEvaluationsTemp(studentId: number) { decision: true, motivation: true, evaluation_id: true, - } - } - } + is_final: true, + login_user: { + select: { + login_user_id: true, + person: { + select: { + person_id: true, + firstname: true, + lastname: true, + email: true, + github: true, + }, + }, + }, + }, + }, + }, + }, }); } /** * - * @param studentId: the id of the student who's selected roles we are searching from his most recent job application + * @param studentId: the id of the student whose selected roles we are searching from his most recent job application * @return the list of selected roles in the application (if it exists) */ export async function getLatestApplicationRolesForStudent(studentId: number) { return await prisma.job_application.findFirst({ where: { - student_id: studentId + student_id: studentId, }, - orderBy: { // use descending order in combination with findFirst to search further for the applied roles in the latest application - created_at: "desc" + orderBy: { + // use descending order in combination with findFirst to search further for the applied roles in the latest application + created_at: "desc", }, select: { applied_role: { select: { - role_id: true - } - } - } + role_id: true, + }, + }, + }, }); } /** * removes all job applications from a given student - * - * @param studentId: the student who's applications will be deleted + * + * @param studentId: the student whose applications will be deleted * @returns the number of deleted job applications {count: number} */ - export async function deleteJobApplicationsFromStudent(studentId: number) { +export async function deleteJobApplicationsFromStudent(studentId: number) { return await prisma.job_application.deleteMany({ where: { - student_id: studentId - } + student_id: studentId, + }, }); } /** * change the email status of a given job application - * - * @param jobApplicationId: the application who's email status we want to change + * + * @param jobApplicationId: the application whose email status we want to change * @param emailStatus: the new email status * @returns: the updated database record */ -export async function changeEmailStatusOfJobApplication(jobApplicationId: number, emailStatus: email_status_enum) { +export async function changeEmailStatusOfJobApplication( + jobApplicationId: number, + emailStatus: email_status_enum +) { return await prisma.job_application.update({ where: { - job_application_id: jobApplicationId + job_application_id: jobApplicationId, }, data: { - email_status: emailStatus - } + email_status: emailStatus, + }, }); } /** * remove a specific job application - * + * * @param jobApplicationId: the id of the job application we want to remove * @returns the removed job application */ @@ -154,17 +203,17 @@ export async function deleteJobApplication(jobApplicationId: number) { return await prisma.job_application.delete({ where: { job_application_id: jobApplicationId, - } + }, }); } - /** - * + * * @param jobApplication: CreateJobApplicationObject that contains all the information about the job application */ -export async function createJobApplication(jobApplication: CreateJobApplication) { - +export async function createJobApplication( + jobApplication: CreateJobApplication +) { return await prisma.job_application.create({ data: { student_id: jobApplication.studentId, @@ -179,35 +228,35 @@ export async function createJobApplication(jobApplication: CreateJobApplication) edu_year: jobApplication.eduYear, edu_institute: jobApplication.eduInstitute, email_status: jobApplication.emailStatus, - created_at: jobApplication.created_at - } + created_at: new Date(jobApplication.createdAt), + }, }); } /** - * - * @param studentId: the student who's job applications we are looking for + * + * @param studentId: the student whose job applications we are looking for * @returns the found job applications of the given student */ export async function getLatestJobApplicationOfStudent(studentId: number) { return await prisma.job_application.findFirst({ where: { - student_id: studentId + student_id: studentId, }, - orderBy: { // order descending == get newest first - created_at: 'desc' + orderBy: { + // order descending == get newest first + created_at: "desc", }, include: { attachment: true, job_application_skill: true, - applied_role: true - } + applied_role: true, + }, }); } - /** - * + * * @param jobApplicationId: the job application we are looking for * @returns the found job application */ @@ -219,29 +268,27 @@ export async function getJobApplication(jobApplicationId: number) { include: { attachment: true, job_application_skill: true, - applied_role: true - } + applied_role: true, + }, }); } /** - * + * * @param year: the year that we are looking up all the job applications for * @returns all the job applications associated with the given year */ - export async function getJobApplicationByYear(year:number) { +export async function getJobApplicationByYear(year: number) { return await prisma.job_application.findMany({ where: { osoc: { - year: year - } + year: year, + }, }, include: { attachment: true, job_application_skill: true, - applied_role: true - } + applied_role: true, + }, }); } - - diff --git a/backend/orm_functions/job_application_skill.ts b/backend/orm_functions/job_application_skill.ts index 46fb09ce..134ff947 100644 --- a/backend/orm_functions/job_application_skill.ts +++ b/backend/orm_functions/job_application_skill.ts @@ -1,11 +1,16 @@ import prisma from "../prisma/prisma"; -import {CreateJobApplicationSkill, UpdateJobApplicationSkill} from './orm_types'; +import { + CreateJobApplicationSkill, + UpdateJobApplicationSkill, +} from "./orm_types"; /** - * + * * @param jobApplicationSkill: job application skill object with the needed information */ - export async function createJobApplicationSkill(jobApplicationSkill: CreateJobApplicationSkill) { +export async function createJobApplicationSkill( + jobApplicationSkill: CreateJobApplicationSkill +) { return prisma.job_application_skill.create({ data: { job_application_id: jobApplicationSkill.jobApplicationId, @@ -13,51 +18,55 @@ import {CreateJobApplicationSkill, UpdateJobApplicationSkill} from './orm_types' language_id: jobApplicationSkill.languageId, level: jobApplicationSkill.level, is_preferred: jobApplicationSkill.isPreferred, - is_best: jobApplicationSkill.isBest - } + is_best: jobApplicationSkill.isBest, + }, }); -} +} /** - * + * * @returns a list of all the jobapplicationskill objects in the database */ - export async function getAllJobApplicationSkill() { - return await prisma.job_application_skill.findMany() +export async function getAllJobApplicationSkill() { + return await prisma.job_application_skill.findMany(); } /** - * + * * @param jobApplicationId: this is the id of the job we are looking up in the database * @returns: object with all the info about this job_application_skill */ - export async function getAllJobApplicationSkillByJobApplication(jobApplicationId: number) { +export async function getAllJobApplicationSkillByJobApplication( + jobApplicationId: number +) { return await prisma.job_application_skill.findMany({ where: { job_application_id: jobApplicationId, - } + }, }); } /** - * + * * @param jobApplicationSkillId: this is the id of the job application skill we are looking up in the database * @returns: object with all the info about this job_application_skill */ - export async function getJobApplicationSkill(jobApplicationSkillId: number) { +export async function getJobApplicationSkill(jobApplicationSkillId: number) { return await prisma.job_application_skill.findUnique({ where: { job_application_skill_id: jobApplicationSkillId, - } + }, }); } /** - * + * * @param jobApplicationSkill: UpdateJobapplicationSkill object with the values that need to be updated - * @returns the updated entry in the database + * @returns the updated entry in the database */ - export async function updateJobApplicationSkill(jobApplicationSkill: UpdateJobApplicationSkill) { +export async function updateJobApplicationSkill( + jobApplicationSkill: UpdateJobApplicationSkill +) { return await prisma.job_application_skill.update({ where: { job_application_skill_id: jobApplicationSkill.JobApplicationSkillId, @@ -68,23 +77,37 @@ import {CreateJobApplicationSkill, UpdateJobApplicationSkill} from './orm_types' language_id: jobApplicationSkill.languageId, level: jobApplicationSkill.level, is_preferred: jobApplicationSkill.isPreferred, - is_best: jobApplicationSkill.is_best - } + is_best: jobApplicationSkill.is_best, + }, }); } /** - * + * * @param jobApplicationSkillId the job application skill we are deleting from the table * @returns the job application, this info can be used to further remove the job application */ - export async function deleteJobApplicationSkill(jobApplicationSkillId: number) { +export async function deleteJobApplicationSkill(jobApplicationSkillId: number) { return await prisma.job_application_skill.delete({ where: { job_application_skill_id: jobApplicationSkillId, }, - include: { // returns the job application, this info can be used to further remove the job application - job_application: true - } + include: { + // returns the job application, this info can be used to further remove the job application + job_application: true, + }, }); -} \ No newline at end of file +} + +/** + * deletes all jobApplicationSkills for the given JobApplicationId + * @param jobApplicationId: the id of the jobApplication whose skills we want to delete + * @returns the number of deleted records in a promise + */ +export async function deleteSkillsByJobApplicationId(jobApplicationId: number) { + return await prisma.job_application_skill.deleteMany({ + where: { + job_application_id: jobApplicationId, + }, + }); +} diff --git a/backend/orm_functions/language.ts b/backend/orm_functions/language.ts index 8d7d63a9..65c64672 100644 --- a/backend/orm_functions/language.ts +++ b/backend/orm_functions/language.ts @@ -1,90 +1,90 @@ -import prisma from '../prisma/prisma' -import {UpdateLanguage} from './orm_types'; +import prisma from "../prisma/prisma"; +import { UpdateLanguage } from "./orm_types"; /** - * + * * @param name: the name of the language we want to add to the database */ - export async function createLanguage(name: string){ +export async function createLanguage(name: string) { return await prisma.language.create({ data: { - name: name + name: name, }, }); } /** - * + * * @returns a list of all the language objects in the database */ export async function getAllLanguages() { - return await prisma.language.findMany() + return await prisma.language.findMany(); } /** - * + * * @param languageId: this is the id of the language we are looking up in the database * @returns: object with the name of the language */ - export async function getLanguage(languageId: number) { +export async function getLanguage(languageId: number) { return await prisma.language.findUnique({ where: { language_id: languageId, - } + }, }); } /** - * + * * @param name: this is the name of the language we are looking up in the database * @returns: the language object with a matching name */ - export async function getLanguageByName(name: string) { +export async function getLanguageByName(name: string) { return await prisma.language.findUnique({ where: { name: name, - } + }, }); } /** - * + * * @param language: UpdateLanguage object with the values that need to be updated - * @returns the updated entry in the database + * @returns the updated entry in the database */ - export async function updateLanguage(language: UpdateLanguage) { +export async function updateLanguage(language: UpdateLanguage) { return await prisma.language.update({ where: { language_id: language.languageId, }, data: { name: language.name, - } + }, }); } /** - * + * * @param languageId: the language we are deleting from the language-table * @returns the deleted record in the database */ - export async function deleteLanguage(languageId: number) { +export async function deleteLanguage(languageId: number) { return await prisma.language.delete({ where: { language_id: languageId, - } + }, }); } /** - * + * * @param name the name of the language we are deleting from the language-table * @returns the deleted record in the database */ - export async function deleteLanguageByName(name: string) { +export async function deleteLanguageByName(name: string) { return await prisma.language.delete({ where: { name: name, - } + }, }); } diff --git a/backend/orm_functions/login_user.ts b/backend/orm_functions/login_user.ts index ec433da4..c3eecdf5 100644 --- a/backend/orm_functions/login_user.ts +++ b/backend/orm_functions/login_user.ts @@ -1,22 +1,29 @@ -import prisma from '../prisma/prisma' +import prisma from "../prisma/prisma"; -import {CreateLoginUser, UpdateLoginUser} from './orm_types'; +import { + CreateLoginUser, + FilterBoolean, + FilterSort, + FilterString, + UpdateLoginUser, +} from "./orm_types"; +import { account_status_enum } from "@prisma/client"; /** * * @param loginUser: login user object with the needed information */ export async function createLoginUser(loginUser: CreateLoginUser) { - const result = await prisma.login_user.create({ - data : { - person_id : loginUser.personId, - password : loginUser.password, - is_admin : loginUser.isAdmin, - is_coach : loginUser.isCoach, - account_status : loginUser.accountStatus - }, - }); - return result; + const result = await prisma.login_user.create({ + data: { + person_id: loginUser.personId, + password: loginUser.password, + is_admin: loginUser.isAdmin, + is_coach: loginUser.isCoach, + account_status: loginUser.accountStatus, + }, + }); + return result; } /** @@ -24,7 +31,7 @@ export async function createLoginUser(loginUser: CreateLoginUser) { * @returns a list of all the login user objects in the database */ export async function getAllLoginUsers() { - return await prisma.login_user.findMany({include : {person : true}}); + return await prisma.login_user.findMany({ include: { person: true } }); } /** @@ -34,9 +41,11 @@ export async function getAllLoginUsers() { * @returns: password of the login user object */ export async function getPasswordLoginUserByPerson(personId: number) { - const result = await prisma.login_user.findUnique( - {where : {person_id : personId}, select : {password : true}}); - return result; + const result = await prisma.login_user.findUnique({ + where: { person_id: personId }, + select: { password: true }, + }); + return result; } /** @@ -46,9 +55,11 @@ export async function getPasswordLoginUserByPerson(personId: number) { * @returns: password of the login user object */ export async function getPasswordLoginUser(loginUserId: number) { - const result = await prisma.login_user.findUnique( - {where : {login_user_id : loginUserId}, select : {password : true}}); - return result; + const result = await prisma.login_user.findUnique({ + where: { login_user_id: loginUserId }, + select: { password: true }, + }); + return result; } /** @@ -57,14 +68,14 @@ export async function getPasswordLoginUser(loginUserId: number) { * the database * @returns: login user object */ -export async function searchLoginUserByPerson(personId: number){ +export async function searchLoginUserByPerson(personId: number) { const result = await prisma.login_user.findUnique({ - where: { - person_id: personId + where: { + person_id: personId, }, - include : { + include: { person: true, - } + }, }); return result; } @@ -75,13 +86,13 @@ export async function searchLoginUserByPerson(personId: number){ * @returns: all login user objects that match with the admin status */ export async function searchAllAdminLoginUsers(isAdmin: boolean) { - const result = await prisma.login_user.findMany({ - where : {is_admin : isAdmin}, - include : { - person : true, - } - }); - return result; + const result = await prisma.login_user.findMany({ + where: { is_admin: isAdmin }, + include: { + person: true, + }, + }); + return result; } /** @@ -90,13 +101,13 @@ export async function searchAllAdminLoginUsers(isAdmin: boolean) { * @returns: all login user objects that match with the coach status */ export async function searchAllCoachLoginUsers(isCoach: boolean) { - const result = await prisma.login_user.findMany({ - where : {is_coach : isCoach}, - include : { - person : true, - } - }); - return result; + const result = await prisma.login_user.findMany({ + where: { is_coach: isCoach }, + include: { + person: true, + }, + }); + return result; } /** @@ -104,61 +115,62 @@ export async function searchAllCoachLoginUsers(isCoach: boolean) { * @param bool: boolean value for the admin and coach status * @returns: all login user objects where the bool matches with both the admin and coach status */ -export async function searchAllAdminAndCoachLoginUsers(bool: boolean){ +export async function searchAllAdminAndCoachLoginUsers(bool: boolean) { const result = await prisma.login_user.findMany({ - where: { + where: { AND: [ { - is_admin: bool + is_admin: bool, }, { - is_coach: bool + is_coach: bool, }, ], }, include: { person: true, - } + }, }); return result; } /** - * + * * @param loginUser: UpdateLoginUser object with the values that need to be updated - * @returns the updated entry in the database + * @returns the updated entry in the database */ -export async function updateLoginUser(loginUser: UpdateLoginUser){ +export async function updateLoginUser(loginUser: UpdateLoginUser) { const result = await prisma.login_user.update({ - where : { - login_user_id: loginUser.loginUserId + where: { + login_user_id: loginUser.loginUserId, }, data: { password: loginUser.password, is_admin: loginUser.isAdmin, is_coach: loginUser.isCoach, - account_status: loginUser.accountStatus + account_status: loginUser.accountStatus, }, include: { person: true, - } + }, }); return result; } /** - * + * * @param loginUserId the login user who's info we are deleting from the login user-table * @returns personal info about the login user, this info can be used to further remove the person */ -export async function deleteLoginUserById(loginUserId: number){ +export async function deleteLoginUserById(loginUserId: number) { const result = await prisma.login_user.delete({ - where: { - login_user_id: loginUserId + where: { + login_user_id: loginUserId, + }, + include: { + // returns the person info of the removed login user + person: true, }, - include: { // returns the person info of the removed login user - person: true - } }); return result; } @@ -171,14 +183,14 @@ export async function deleteLoginUserById(loginUserId: number){ * remove the person */ export async function deleteLoginUserByPersonId(personId: number) { - const result = await prisma.login_user.delete({ - where : {person_id : personId}, - include : { - // returns the person info of the removed login user - person : true - } - }); - return result; + const result = await prisma.login_user.delete({ + where: { person_id: personId }, + include: { + // returns the person info of the removed login user + person: true, + }, + }); + return result; } /** @@ -188,10 +200,102 @@ export async function deleteLoginUserByPersonId(personId: number) { * loginUser has the given id */ export async function getLoginUserById(loginUserId: number) { - return await prisma.login_user.findUnique({ - where : { - login_user_id : loginUserId, - }, - include : {person : true} - }); + return await prisma.login_user.findUnique({ + where: { + login_user_id: loginUserId, + }, + include: { person: true }, + }); +} + +/** + * + * @param nameFilter name that we are filtering on (or undefined if not filtering on name) + * @param emailFilter email that we are filtering on (or undefined if not filtering on email) + * @param coachFilter coachstatus that we are filtering on (or undefined if not filtering on coach) + * @param adminFilter adminstatus that we are filtering on (or undefined if not filtering on admin) + * @param nameSort asc or desc if we want to sort on name, undefined if we are not sorting on name + * @param emailSort asc or desc if we are sorting on email, undefined if we are not sorting on email + * @param statusFilter a given email status to filter on or undefined if we are not filtering on a status + * @param isCoach: true or false if we want only coaches resp no coaches or undefined if we don't want to filter on this field + * @param isAdmin: true or false if we want only admins resp no admins or undefined if we don't want to filter on this field + * @returns the filtered loginUsers with their person data in a promise + */ + +export async function filterLoginUsers( + nameFilter: FilterString, + emailFilter: FilterString, + nameSort: FilterSort, + emailSort: FilterSort, + statusFilter: account_status_enum | undefined, + isCoach: FilterBoolean, + isAdmin: FilterBoolean +) { + // execute the query + return await prisma.login_user.findMany({ + where: { + person: { + firstname: { + contains: nameFilter, + mode: "insensitive", + }, + email: { + contains: emailFilter, + mode: "insensitive", + }, + }, + account_status: statusFilter, + is_coach: isCoach, + is_admin: isAdmin, + }, + orderBy: [ + { person: { firstname: nameSort } }, + { person: { email: emailSort } }, + ], + include: { + person: true, + }, + }); +} + +/** + * Sets the is_coach field of the loginUser to isCoach + * + * @param loginUserId: the id of the loginUser whose field we are updating + * @param isCoach: the new value of the is_coach field for the loginUser + * @return a promise with the updated entry, the person object is also included + */ +export async function setCoach(loginUserId: number, isCoach: boolean) { + return await prisma.login_user.update({ + where: { + login_user_id: loginUserId, + }, + data: { + is_coach: isCoach, + }, + include: { + person: true, + }, + }); +} + +/** + * Sets the is_admin field of the loginuser to IsAdmin + * + * @param loginUserId the id of the loginUser whose field we are updating + * @param isAdmin the new value of is_admin + * @returns a promise with the updated entry, the person object is also included + */ +export async function setAdmin(loginUserId: number, isAdmin: boolean) { + return await prisma.login_user.update({ + where: { + login_user_id: loginUserId, + }, + data: { + is_admin: isAdmin, + }, + include: { + person: true, + }, + }); } diff --git a/backend/orm_functions/orm_types.ts b/backend/orm_functions/orm_types.ts index ea53d545..77ad8df1 100644 --- a/backend/orm_functions/orm_types.ts +++ b/backend/orm_functions/orm_types.ts @@ -1,105 +1,113 @@ -import { contract_status_enum, decision_enum, email_status_enum, account_status_enum } from "@prisma/client" - +import { + contract_status_enum, + decision_enum, + email_status_enum, + account_status_enum, +} from "@prisma/client"; /** * interface for the object needed to create a person */ - export interface CreatePerson { +export interface CreatePerson { /** * the person's firstname */ - firstname : string, + firstname: string; /** * the person's lastname */ - lastname : string, + lastname: string; /** * the person's github account, only one of github/email can be used */ - github? : string, - /** + github?: string; + /** * the person's email, may not be null if github is null */ - email? : string + email?: string; + /** + * the person's github id, if github is used + */ + github_id?: string; } /** * interface for the object needed to update a person's data */ - export interface UpdatePerson { +export interface UpdatePerson { /** * the person who's info we are updating */ - personId: number, + personId: number; /** * undefined if unchanged or new firstname */ - firstname?: string, + firstname?: string; /** * undefined if unchanged or the new lastname */ - lastname?: string, + lastname?: string; /** * undefined if unchanged or the new github */ - github?: string | null, + github?: string | null; /** * undefined if unchanged or the new email */ - email?: string | null, + email?: string | null; } /** * interface for the object needed to create a login user */ - export interface CreateLoginUser { +export interface CreateLoginUser { /** * the person_id of the person the login user will be associated with */ - personId?: number, + personId: number; /** * the password hash of the login user if email is used */ - password?: string | null, + password?: string | null; /** * true if the login user is an admin in the osoc system, otherwise false */ - isAdmin: boolean, + isAdmin: boolean; /** * true if the login user is a coach in the osoc system, otherwise false */ - isCoach: boolean, + isCoach: boolean; /** * the status of the account we are trying to create */ - accountStatus: account_status_enum + accountStatus: account_status_enum; } /** * interface for the object needed to update a login user's data */ - export interface UpdateLoginUser { +export interface UpdateLoginUser { /** * the login user who's info we are updating */ - loginUserId: number, + loginUserId: number; /** * undefined if unchanged or the new password */ - password?: string | null, + password?: string | null; /** - * undefined if unchanged or the new boolean value that indicates if this login user is an admin + * undefined if unchanged or the new boolean value that indicates if this login user is an admin */ - isAdmin: boolean, + isAdmin?: boolean; /** * undefined if unchanged or the new boolean value that indicates if this login user is a coach */ - isCoach: boolean, + isCoach?: boolean; /** * undefined if unchanged or the new account status that indicates the login user status */ - accountStatus: account_status_enum + accountStatus: account_status_enum; } /** @@ -109,27 +117,27 @@ export interface CreateStudent { /** * the person_id of the person the student will be associated with */ - personId?: number, + personId?: number; /** * the person's gender */ - gender : string, + gender: string; /** * the pronouns the student wants to be addressed with */ - pronouns?: string[], + pronouns?: string | null; /** * student's phone number */ - phoneNumber: string, + phoneNumber: string; /** * student's nickname */ - nickname?: string | null, + nickname?: string | null; /** * true if the student is an alumni in the osoc system, otherwise false */ - alumni: boolean + alumni: boolean; } /** @@ -139,27 +147,27 @@ export interface UpdateStudent { /** * the student who's info we are updating */ - studentId: number, + studentId: number; /** * undefined if unchanged or the new gender */ - gender?: string, + gender?: string; /** * undefined if unchanged or new list of pronouns */ - pronouns?: string[], + pronouns?: string | null; /** * undefined if unchanged or the new phone number */ - phoneNumber?: string, + phoneNumber?: string; /** * undefined if unchanged or the new nickname */ - nickname?: string | null, + nickname?: string | null; /** - * undefined if unchanged or the new boolean value that indicates if this student is an alumni + * undefined if unchanged or the new boolean value that indicates if this student is an alumni */ - alumni?: boolean + alumni?: boolean; } /** @@ -169,23 +177,23 @@ export interface CreateEvaluationForStudent { /** * loginUserId of the loginUser that creates this evaluation */ - loginUserId: number, + loginUserId: number; /** * the jobApplication about that the evaluation is about */ - jobApplicationId: number, + jobApplicationId: number; /** * the decision that is made (yes, maybe, no) */ - decision: decision_enum, + decision: decision_enum; /** * motivation for the made decision */ - motivation?: string | null, + motivation?: string | null; /** * is this evaluation final, or not */ - isFinal: boolean + isFinal: boolean; } /** @@ -195,19 +203,19 @@ export interface UpdateEvaluationForStudent { /** * the evaluation that we are updating */ - evaluation_id: number, + evaluation_id: number; /** * loginUserId of the loginUser that creates this evaluation */ - loginUserId: number, - /** - * the decision that is made (yes, maybe, no) - */ - decision?: decision_enum, - /** - * motivation for the made decision - */ - motivation?: string | null, + loginUserId: number; + /** + * the decision that is made (yes, maybe, no) + */ + decision?: decision_enum; + /** + * motivation for the made decision + */ + motivation?: string | null; } /** @@ -217,23 +225,23 @@ export interface CreateContract { /** * the student that receives the contract */ - studentId: number, + studentId: number; /** * the role for which the contract is */ - projectRoleId: number, + projectRoleId: number; /** * extra information */ - information?: string | null, + information?: string | null; /** * the loginUser that created this contract */ - loginUserId: number, + loginUserId: number; /** * status of the contract (draft, approved, cancelled,...) */ - contractStatus: contract_status_enum + contractStatus: contract_status_enum; } /** @@ -243,19 +251,23 @@ export interface UpdateContract { /** * the contract we are changing */ - contractId: number, + contractId: number; /** * id of the login user that is making these changes */ - loginUserId: number, + loginUserId: number; /** * optional information */ - information?: string | null, + information?: string | null; /** * status of the contract (draft, approved, cancelled,...) */ - contractStatus?: contract_status_enum + contractStatus?: contract_status_enum; + /** + * updated role (id) for the student + */ + projectRoleId?: number; } /** @@ -265,69 +277,69 @@ export interface CreateJobApplication { /** * the student who's application this is */ - studentId: number, + studentId: number; /** * the responsibilities the students has during the summer that might keep him from working for osoc */ - responsibilities?: string | null, + responsibilities?: string | null; /** * a fun fact about the student */ - funFact?: string | null, + funFact: string; /** * string that has info if the student is available to work, and if he wants to work as volunteer for free or not */ - studentVolunteerInfo: string, + studentVolunteerInfo: string; /** * boolean that indicates if the student is a student-coach or not */ - studentCoach: boolean, + studentCoach: boolean; /** * id of the osoc edition this job application is for */ - osocId: number, + osocId: number; /** * information about the educations of the student */ - edus?: string[], + edus: string[]; /** * information about the education level of the student */ - eduLevel?: string | null, + eduLevel: string; /** * how long this student has been studying for */ - eduDuration?: number | null, + eduDuration: number | null; /** * expected graduation year */ - eduYear?: number | null, + eduYear: string | null; /** * institute the student is studying at */ - eduInstitute?: string | null, + eduInstitute: string | null; /** * information about a confirmation email for the evaluation */ - emailStatus: email_status_enum, + emailStatus: email_status_enum; /** * keeps track of when we received this application (used to pick the latest one) */ - created_at: string // this has to be a timezone formatted string: eg '2022-03-14 23:10:00+01' + createdAt: string; // this has to be a timezone formatted string: eg '2022-03-14 23:10:00+01' } /** * interface for the object needed in updateOsoc */ - export interface UpdateOsoc { +export interface UpdateOsoc { /** * the osoc edition we are changing */ - osocId: number, + osocId: number; /** * the year we want to set */ - year: number + year: number; } /** @@ -337,27 +349,27 @@ export interface CreateProject { /** * the name of the project */ - name: string, + name: string; /** * the id of the osoc edition this project belongs to */ - osocId: number, + osocId: number; /** * the partner for who this project is made */ - partner: string, + partner: string; /** * the start date of the project */ - startDate: Date, + startDate: Date; /** * the end date of the project */ - endDate: Date, + endDate: Date; /** * the amount of people who need to assigned to the project */ - positions: number + positions: number; } /** @@ -367,49 +379,49 @@ export interface UpdateProject { /** * the project we are updating */ - projectId: number, + projectId: number; /** * undefined if unchanged or new project name */ - name: string, + name?: string; /** * undefined if unchanged or the new osoc id */ - osocId: number, + osocId?: number; /** * undefined if unchanged or the new partner of the project */ - partner: string, + partner?: string; /** * undefined if unchanged or the new start date of the project */ - startDate: Date + startDate?: Date; /** * undefined if unchanged or the new end date of the project */ - endDate: Date, + endDate?: Date; /** * undefined if unchanged or the new number of positions of the project */ - positions: number + positions?: number; } /** * interface for the object needed to create a project */ - export interface CreateProjectRole { +export interface CreateProjectRole { /** * the id of the project this role belongs to */ - projectId: number, + projectId: number; /** * the id of the role this project role represents */ - roleId: number, + roleId: number; /** * the number of positions that are needed for this role */ - positions: number + positions: number; } /** @@ -419,77 +431,77 @@ export interface UpdateProjectRole { /** * the project we are updating */ - projectRoleId: number, + projectRoleId: number; /** * undefined if unchanged or the new project id */ - projectId: number, + projectId: number; /** * undefined if unchanged or the new role id */ - roleId: number, + roleId: number; /** * undefined if unchanged or the new number of positions for this role in the project */ - positions: number + positions: number; } /** * interface for the object needed in updateRole */ - export interface UpdateRole { +export interface UpdateRole { /** * the role object we are changing */ - roleId: number, + roleId: number; /** * the name we want to set */ - name: string + name: string; } /** * interface for the object needed in updateRole */ - export interface UpdateLanguage { +export interface UpdateLanguage { /** * the language object we are changing */ - languageId: number, + languageId: number; /** * the name we want to set */ - name: string + name: string; } /** * interface for the object needed to create a project */ - export interface CreateJobApplicationSkill { +export interface CreateJobApplicationSkill { /** * the jobapplicaton id to which the skill is linked */ - jobApplicationId: number, + jobApplicationId: number; /** * the skill of this job application */ - skill: string, + skill: string | null; /** * the language id to which this skill is linked */ - languageId: number, + languageId: number | null; /** * the level of the skill of the applicant */ - level: number, + level: number | null; /** * true if this skill is the preffered skill of the applicant */ - isPreferred: boolean, + isPreferred: boolean; /** * true if this skill is the best skill of the applicant */ - isBest: boolean + isBest: boolean; } /** @@ -499,81 +511,127 @@ export interface UpdateJobApplicationSkill { /** * the jobapplicaton we are updating */ - JobApplicationSkillId: number, + JobApplicationSkillId: number; /** * undefined if unchanged or new job application */ - JobApplicationId: number, + JobApplicationId: number; /** * undefined if unchanged or the new skill */ - skill: string, + skill: string | null; /** * undefined if unchanged or the new language of the job application skill */ - languageId: number, + languageId: number | null; /** * undefined if unchanged or the new level */ - level: number + level: number; /** * undefined if unchanged or the new preffered status */ - isPreferred: boolean, + isPreferred: boolean; /** * undefined if unchanged or the new is best status */ - is_best: boolean + is_best: boolean; } export interface AddStudentToProject { /** * the id of the loginsuer that wants to add a student to the project */ - loginUserId: number, + loginUserId: number; /** * the id of the student that will be added to the project */ - studentId: number, + studentId: number; /** * the id of the project that the student will be added to */ - projectId: number, + projectId: number; /** * the name of the role the student will be added for */ - roleName: string, + roleName: string; /** * extra information */ - information?: string | null, - + information?: string | null; } /** * interface for the object needed to create a project user */ - export interface CreateProjectUser { +export interface CreateProjectUser { /** * the id of the project this user belongs to */ - projectId: number, + projectId: number; /** * the id of the login user this user belongs to */ - loginUserId: number, + loginUserId: number; } /** * interface for the object needed to create an applied role */ - export interface CreateAppliedRole { +export interface CreateAppliedRole { /** * the id of the job application this applied role belongs to */ - jobApplicationId: number, + jobApplicationId: number; /** * the id of the role this applied role belongs to */ - roleId: number, + roleId: number; +} + +export interface CreateTemplate { + ownerId: number; + name: string; + content: string; + subject?: string; + cc?: string; +} + +export interface UpdateTemplate { + templateId: number; + ownerId?: number; + name?: string; + content?: string; + subject?: string; + cc?: string; } + +/** + * type to use in a filter query for sorting + */ +export type FilterSort = "asc" | "desc" | undefined; + +/** + * type to use in a filter query for strings + */ +export type FilterString = string | undefined; + +/** + * type to use in a filter query for numbers + */ +export type FilterNumber = number | undefined; + +/** + * type to use in a filter query for array of numbers + */ +export type FilterNumberArray = number[] | undefined; + +/* + * type to use in a filter query for array of strings + */ +export type FilterStringArray = string[] | undefined; + +/** + * type to use in a filter query for booleans + */ +export type FilterBoolean = boolean | undefined; diff --git a/backend/orm_functions/osoc.ts b/backend/orm_functions/osoc.ts index 89a87172..2a33e09d 100644 --- a/backend/orm_functions/osoc.ts +++ b/backend/orm_functions/osoc.ts @@ -1,41 +1,59 @@ -import prisma from '../prisma/prisma' -import {UpdateOsoc} from './orm_types'; +import prisma from "../prisma/prisma"; +import { UpdateOsoc, FilterNumber, FilterSort } from "./orm_types"; /** - * + * * @param year: create osoc edition, only needs year */ -export async function createOsoc(year: number){ +export async function createOsoc(year: number) { return await prisma.osoc.create({ data: { - year: year + year: year, }, }); } /** - * + * * @returns a list of all the osoc objects in the database */ export async function getAllOsoc() { - return prisma.osoc.findMany(); + return prisma.osoc.findMany({ + include: { + _count: { + select: { project: true }, + }, + }, + }); +} + +/** + * + * @returns the latest the osoc edition in the database + */ +export async function getLatestOsoc() { + return prisma.osoc.findFirst({ + orderBy: { + year: "desc", + }, + }); } /** - * + * * @param year: this is the year of the osoc we are looking up in the database * @returns: osoc object */ export async function getOsocByYear(year: number) { return prisma.osoc.findUnique({ where: { - year: year + year: year, }, }); } /** - * + * * @param year: this is the year of the osoc we are looking up in the database * @returns: all the osoc objects that took place before the supplied year */ @@ -50,7 +68,7 @@ export async function getOsocBeforeYear(year: number) { } /** - * + * * @param year: this is the year of the osoc we are looking up in the database * @returns: all the osoc objects that took place after the supplied year */ @@ -65,43 +83,197 @@ export async function getOsocAfterYear(year: number) { } /** - * + * * @param osoc: UpdateOsoc object with the values that need to be updated - * @returns the updated entry in the database + * @returns the updated entry in the database */ -export async function updateOsoc(osoc: UpdateOsoc){ +export async function updateOsoc(osoc: UpdateOsoc) { return await prisma.osoc.update({ where: { - osoc_id: osoc.osocId + osoc_id: osoc.osocId, }, data: { - year: osoc.year + year: osoc.year, }, }); } /** - * + * * @param osocId the osoc edition we are deleting from the osoc-table * @returns the deleted osoc record */ -export async function deleteOsoc(osocId: number){ +export async function deleteOsoc(osocId: number) { return await prisma.osoc.delete({ where: { - osoc_id: osocId - } + osoc_id: osocId, + }, }); } /** - * + * * @param year the year we are deleting from the osoc-table * @returns the deleted osoc record */ -export async function deleteOsocByYear(year: number){ +export async function deleteOsocByYear(year: number) { return await prisma.osoc.delete({ where: { - year: year - } + year: year, + }, + }); +} + +/** + * + * @param osoc_id the osoc edition that we are deleting from the database + */ +export async function deleteOsocFromDB(osocId: number) { + const project_ids = await prisma.project.findMany({ + where: { + osoc_id: osocId, + }, + select: { + project_id: true, + }, + }); + + const project_roles_ids = await prisma.project_role.findMany({ + where: { + project_id: { + in: project_ids.map((X) => X.project_id), + }, + }, + select: { + project_role_id: true, + }, + }); + + const job_application_ids = await prisma.job_application.findMany({ + where: { + osoc_id: osocId, + }, + select: { + job_application_id: true, + }, + }); + + // Remove all the linked projectUsers + await prisma.project_user.deleteMany({ + where: { + project_id: { + in: project_ids.map((X) => X.project_id), + }, + }, + }); + + // Remove all the linked contracts + await prisma.contract.deleteMany({ + where: { + project_role_id: { + in: project_roles_ids.map((X) => X.project_role_id), + }, + }, + }); + + // Remove all the linked projectroles + await prisma.project_role.deleteMany({ + where: { + project_id: { + in: project_ids.map((X) => X.project_id), + }, + }, + }); + + // Remove all the linked projects + await prisma.project.deleteMany({ + where: { + osoc_id: osocId, + }, + }); + + // Remove all the linked evaluations + await prisma.evaluation.deleteMany({ + where: { + job_application_id: { + in: job_application_ids.map((X) => X.job_application_id), + }, + }, + }); + + // Remove all the linked applied roles + await prisma.applied_role.deleteMany({ + where: { + job_application_id: { + in: job_application_ids.map((X) => X.job_application_id), + }, + }, + }); + + // Remove all the linked job application skills + await prisma.job_application_skill.deleteMany({ + where: { + job_application_id: { + in: job_application_ids.map((X) => X.job_application_id), + }, + }, + }); + + // Remove all the linked attachments + await prisma.attachment.deleteMany({ + where: { + job_application_id: { + in: job_application_ids.map((X) => X.job_application_id), + }, + }, + }); + + // Remove all the linked job applications + await prisma.job_application.deleteMany({ + where: { + osoc_id: osocId, + }, + }); + + await prisma.osoc.delete({ + where: { + osoc_id: osocId, + }, + }); +} + +/** + * @returns the newest Osoc edition + */ +export async function getNewestOsoc() { + return await prisma.osoc.findFirst({ + orderBy: { + year: "desc", + }, + }); +} + +/** + * + * @param yearFilter year that we are filtering on (or undefined if not filtering on year) + * @param yearSort asc or desc if we are sorting on year, undefined if we are not sorting on year + * @returns the filtered osoc editions with their project count in a promise + */ +export async function filterOsocs( + yearFilter: FilterNumber, + yearSort: FilterSort +) { + return await prisma.osoc.findMany({ + where: { + year: yearFilter, + }, + orderBy: { + year: yearSort, + }, + include: { + _count: { + select: { project: true }, + }, + }, }); } diff --git a/backend/orm_functions/password_reset.ts b/backend/orm_functions/password_reset.ts new file mode 100644 index 00000000..441867b8 --- /dev/null +++ b/backend/orm_functions/password_reset.ts @@ -0,0 +1,59 @@ +import prisma from "../prisma/prisma"; + +/** + * creates a new entry for password reset if none exists yet. + * overwrites the info of the old reset entry if a reset entry already exists + * + * @param loginUserId the id of the loginUser whose passport we want to reset + * @param resetId the unique id to reset the passport of the login user + * @param validUntil timestamp that shows until when the resetId is valid + * @return the created/updated entry from the database in a promise + */ +export async function createOrUpdateReset( + loginUserId: number, + resetId: string, + validUntil: Date +) { + return await prisma.password_reset.upsert({ + where: { + login_user_id: loginUserId, + }, + create: { + login_user_id: loginUserId, + reset_id: resetId, + valid_until: validUntil, + }, + update: { reset_id: resetId, valid_until: validUntil }, + }); +} + +export async function findResetByCode(resetCode: string) { + return await prisma.password_reset.findUnique({ + where: { reset_id: resetCode }, + }); +} + +/** + * + * @param loginUserId the id of the loginUser whose reset entry we want to + * delete + * @returns the deleted entry (or null if there was no entry) inside a promise + */ +export async function deleteResetWithLoginUser(loginUserId: number) { + return await prisma.password_reset.delete({ + where: { login_user_id: loginUserId }, + }); +} + +/** + * + * @param resetId the resetId of the entry we want to delete + * @returns the deleted entry (or null if there was no entry) inside a promise + */ +export async function deleteResetWithResetId(resetId: string) { + return await prisma.password_reset.delete({ + where: { + reset_id: resetId, + }, + }); +} diff --git a/backend/orm_functions/person.ts b/backend/orm_functions/person.ts index 50ff605b..dc1797b2 100644 --- a/backend/orm_functions/person.ts +++ b/backend/orm_functions/person.ts @@ -1,27 +1,30 @@ -import prisma from '../prisma/prisma' +import prisma from "../prisma/prisma"; -import {CreatePerson, UpdatePerson} from './orm_types'; +import { CreatePerson, UpdatePerson } from "./orm_types"; /** * * @param person: person object with the needed information */ export async function createPerson(person: CreatePerson) { - return await prisma.person.create({ - data : { - firstname : person.firstname, - lastname : person.lastname, - github : person.github, - email : person.email - }, - }); + return await prisma.person.create({ + data: { + firstname: person.firstname, + lastname: person.lastname, + github: person.github, + email: person.email, + github_id: person.github_id, + }, + }); } /** * * @returns a list of all the person objects in the database */ -export async function getAllPersons() { return await prisma.person.findMany() } +export async function getAllPersons() { + return await prisma.person.findMany(); +} /** * @@ -30,14 +33,46 @@ export async function getAllPersons() { return await prisma.person.findMany() } * @returns: password of the login user matching with the person */ export async function getPasswordPersonByEmail(email: string) { - return await prisma.person.findUnique({ - where : {email : email}, - select : { - login_user : { - select : {password : true, login_user_id : true, account_status : true} - } - } - }); + return await prisma.person.findUnique({ + where: { email: email }, + select: { + login_user: { + select: { + password: true, + login_user_id: true, + account_status: true, + is_admin: true, + is_coach: true, + }, + }, + }, + }); +} + +/** + * + * @param github: this is the GitHub username of the person we are looking up in + * the database + * @returns: password of the login user matching with the person + */ +export async function getPasswordPersonByGithub(github: string) { + return await prisma.person.findUnique({ + where: { github_id: github }, + select: { + github: true, + person_id: true, + firstname: true, + login_user: { + select: { + password: true, + login_user_id: true, + account_status: true, + is_admin: true, + is_coach: true, + }, + }, + }, + }); } /** @@ -47,18 +82,18 @@ export async function getPasswordPersonByEmail(email: string) { * @returns: a list of all the person objects in the database that match */ export async function searchPersonByName(name: string) { - return await prisma.person.findMany({ - where : { - OR : [ - { - firstname : {contains : name}, + return await prisma.person.findMany({ + where: { + OR: [ + { + firstname: { contains: name }, + }, + { + lastname: { contains: name }, + }, + ], }, - { - lastname : {contains : name}, - }, - ], - }, - }); + }); } /** @@ -68,18 +103,18 @@ export async function searchPersonByName(name: string) { * the email or github */ export async function searchPersonByLogin(login: string) { - return prisma.person.findMany({ - where : { - OR : [ - { - email : {contains : login}, - }, - { - github : {contains : login}, + return prisma.person.findMany({ + where: { + OR: [ + { + email: { contains: login }, + }, + { + github: { contains: login }, + }, + ], }, - ], - }, - }); + }); } /** @@ -88,15 +123,15 @@ export async function searchPersonByLogin(login: string) { * @returns the updated entry in the database */ export async function updatePerson(person: UpdatePerson) { - return await prisma.person.update({ - where : {person_id : person.personId}, - data : { - firstname : person.firstname, - lastname : person.lastname, - github : person.github, - email : person.email - }, - }); + return await prisma.person.update({ + where: { person_id: person.personId }, + data: { + firstname: person.firstname, + lastname: person.lastname, + github: person.github, + email: person.email, + }, + }); } /** @@ -105,5 +140,5 @@ export async function updatePerson(person: UpdatePerson) { * @returns the deleted person record */ export async function deletePersonById(personId: number) { - return await prisma.person.delete({where : {person_id : personId}}); + return await prisma.person.delete({ where: { person_id: personId } }); } diff --git a/backend/orm_functions/project.ts b/backend/orm_functions/project.ts index efd516b5..dd3d0fd7 100644 --- a/backend/orm_functions/project.ts +++ b/backend/orm_functions/project.ts @@ -1,11 +1,18 @@ -import prisma from '../prisma/prisma' -import {CreateProject, UpdateProject} from './orm_types'; +import prisma from "../prisma/prisma"; +import { + CreateProject, + UpdateProject, + FilterString, + FilterSort, + FilterBoolean, + FilterNumberArray, +} from "./orm_types"; /** - * + * * @param project: project object with the needed information */ -export async function createProject(project: CreateProject){ +export async function createProject(project: CreateProject) { const result = await prisma.project.create({ data: { name: project.name, @@ -13,87 +20,101 @@ export async function createProject(project: CreateProject){ partner: project.partner, start_date: project.startDate, end_date: project.endDate, - positions: project.positions + positions: project.positions, }, }); return result; } /** - * + * * @returns a list of all the project objects in the database */ export async function getAllProjects() { const result = await prisma.project.findMany(); - return result + return result; +} + +/** + * + * @param projectId: this is the id of the project we are looking up in the database + * @returns: object with all the info about this project + */ +export async function getProjectById(projectId: number) { + const result = prisma.project.findUnique({ + where: { + project_id: projectId, + }, + }); + return result; } /** - * + * * @param projectName: this is the name of the project we are looking up in the database * @returns: object with all the info about this project */ export async function getProjectByName(projectName: string) { const result = prisma.project.findMany({ - where: { - name: projectName + where: { + name: projectName, }, }); return result; } /** - * + * * @param osocId: this is the id of the osoc edition for wich we want al the projects * @returns: all projects with all the info */ export async function getProjectsByOsocEdition(osocId: number) { const result = prisma.project.findMany({ - where: { - osoc_id: osocId + where: { + osoc_id: osocId, }, }); return result; } /** - * + * * @param partner: this is the name of the partner for which we want the project - * @returns all the project objects for that partner + * @returns all the project objects for that partner */ export async function getProjectsByPartner(partner: string) { const result = prisma.project.findMany({ - where: { - partner: partner + where: { + partner: partner, }, }); return result; } /** - * + * * @param startDate: the start date of the project we are looking for - * @returns all the projects with a matching start date in the database + * @returns all the projects with a matching start date in the database */ export async function getProjectsByStartDate(startDate: Date) { const result = prisma.project.findMany({ - where: { - start_date: startDate + where: { + start_date: startDate, }, }); return result; } /** - * + * * @param date: the start date of the project we are looking for - * @returns all the projects that started after the supplied date + * @returns all the projects that started after the supplied date */ export async function getProjectsStartedAfterDate(date: Date) { const result = prisma.project.findMany({ where: { start_date: { - gte: date, + gte: date, }, }, }); @@ -101,15 +122,15 @@ export async function getProjectsStartedAfterDate(date: Date) { } /** - * + * * @param date: the start date of the project we are looking for - * @returns all the projects that started before the supplied date + * @returns all the projects that started before the supplied date */ export async function getProjectsStartedBeforeDate(date: Date) { const result = prisma.project.findMany({ where: { start_date: { - lte: date, + lte: date, }, }, }); @@ -117,29 +138,29 @@ export async function getProjectsStartedBeforeDate(date: Date) { } /** - * + * * @param endDate: the end date of the project we are looking for - * @returns all the projects with a matching end date in the database + * @returns all the projects with a matching end date in the database */ export async function getProjectsByEndDate(endDate: Date) { const result = prisma.project.findMany({ - where: { - end_date: endDate + where: { + end_date: endDate, }, }); return result; } /** - * + * * @param date: the end date of the project we are looking for - * @returns all the projects that ended after the supplied date + * @returns all the projects that ended after the supplied date */ export async function getProjectsEndedAfterDate(date: Date) { const result = prisma.project.findMany({ where: { end_date: { - gte: date, + gte: date, }, }, }); @@ -147,15 +168,15 @@ export async function getProjectsEndedAfterDate(date: Date) { } /** - * + * * @param date: the end date of the project we are looking for - * @returns all the projects that ended before the supplied date + * @returns all the projects that ended before the supplied date */ export async function getProjectsEndedBeforeDate(date: Date) { const result = prisma.project.findMany({ where: { end_date: { - lte: date, + lte: date, }, }, }); @@ -163,60 +184,60 @@ export async function getProjectsEndedBeforeDate(date: Date) { } /** - * + * * @param positions: this is the number of positions in the project * @returns all the project objects that have the exact amount of positions */ export async function getProjectsByNumberPositions(positions: number) { const result = prisma.project.findMany({ - where: { - positions: positions + where: { + positions: positions, }, }); return result; } /** - * + * * @param positions: this is the number of positions in the project * @returns all the project objects that have less positions */ export async function getProjectsLessPositions(positions: number) { const result = prisma.project.findMany({ - where: { + where: { positions: { lt: positions, - }, - }, + }, + }, }); return result; } /** - * + * * @param positions: this is the number of positions in the project * @returns all the project objects that have more positions */ export async function getProjectsMorePositions(positions: number) { const result = prisma.project.findMany({ - where: { + where: { positions: { gt: positions, - }, - }, + }, + }, }); return result; } /** - * + * * @param project: UpdateProject object with the values that need to be updated - * @returns the updated entry in the database + * @returns the updated entry in the database */ -export async function updateProject(project: UpdateProject){ +export async function updateProject(project: UpdateProject) { const result = await prisma.project.update({ where: { - project_id :project.projectId + project_id: project.projectId, }, data: { name: project.name, @@ -224,50 +245,206 @@ export async function updateProject(project: UpdateProject){ partner: project.partner, start_date: project.startDate, end_date: project.endDate, - positions: project.positions + positions: project.positions, }, }); return result; } /** - * + * * @param projectId the project that we are deleting from the project-table - * @returns TODO what does this return? + * @returns return deleted project, with all its fields */ -export async function deleteProject(projectId: number){ +export async function deleteProject(projectId: number) { const result = await prisma.project.delete({ where: { - project_id: projectId - } + project_id: projectId, + }, }); return result; } /** - * + * * @param osocId the osoc id of all the projects we want to delete - * @returns TODO what does this return? + * @returns returns batch payload object, with holds count of number of deleted objects */ -export async function deleteProjectByOsocEdition(osocId: number){ +export async function deleteProjectByOsocEdition(osocId: number) { const result = await prisma.project.deleteMany({ where: { - osoc_id: osocId - } + osoc_id: osocId, + }, }); return result; } /** - * + * * @param partner the partner of who we want to delete all the projects - * @returns TODO what does this return? + * @returns returns batch payload object, with holds count of number of deleted objects */ -export async function deleteProjectByPartner(partner: string){ +export async function deleteProjectByPartner(partner: string) { const result = await prisma.project.deleteMany({ where: { - partner: partner - } + partner: partner, + }, }); return result; } + +/** + * + * @param projectNameFilter project name that we are filtering on (or undefined if not filtering on name) + * @param clientNameFilter client name that we are filtering on (or undefined if not filtering on name) + * @param assignedCoachesFilterArray assigned coaches that we are filtering on (or undefined if not filtering on assigned coaches) + * @param fullyAssignedFilter fully assigned status that we are filtering on (or undefined if not filtering on assigned) + * @param projectNameSort asc or desc if we want to sort on project name, undefined if we are not sorting on project name + * @param clientNameSort asc or desc if we want to sort on client name, undefined if we are not sorting on client name + * @param fullyAssignedSort asc or desc if we are sorting on fully assigned, undefined if we are not sorting on fully assigned + * @returns the filtered students with their person data and other filter fields in a promise + */ +export async function filterProjects( + projectNameFilter: FilterString, + clientNameFilter: FilterString, + assignedCoachesFilterArray: FilterNumberArray, + fullyAssignedFilter: FilterBoolean, + projectNameSort: FilterSort, + clientNameSort: FilterSort, + fullyAssignedSort: FilterSort +) { + const projects = await prisma.project.findMany({ + include: { + project_role: { + include: { + _count: { + select: { contract: true }, + }, + }, + }, + }, + }); + + let assignedCoachesArray = undefined; + if (assignedCoachesFilterArray !== undefined) { + assignedCoachesArray = { + some: { + login_user_id: { in: assignedCoachesFilterArray }, + }, + }; + } + + const filtered_projects = await prisma.project.findMany({ + where: { + name: { + contains: projectNameFilter, + mode: "insensitive", + }, + partner: { + contains: clientNameFilter, + mode: "insensitive", + }, + project_user: assignedCoachesArray, + }, + orderBy: { + name: projectNameSort, + partner: clientNameSort, + }, + include: { + project_user: { + select: { + login_user: { + select: { + login_user_id: true, + is_coach: true, + }, + }, + }, + }, + project_role: { + select: { + positions: true, + role: { + select: { + name: true, + }, + }, + }, + }, + }, + }); + + if ( + fullyAssignedFilter != undefined && + fullyAssignedFilter && + filtered_projects.length !== 0 + ) { + return filtered_projects.filter((project) => { + const project_found = projects.find( + (elem) => elem.project_id === project.project_id + ); + + if (project_found != undefined) { + let sum = 0; + for (const c of project_found.project_role) { + sum += c._count.contract; + } + return project.positions === sum; + } + + return false; + }); + } + + if (fullyAssignedSort == "desc" || fullyAssignedSort == "asc") { + filtered_projects.sort((x, y) => { + const project_x_found = projects.find( + (elem) => elem.project_id === x.project_id + ); + + const project_y_found = projects.find( + (elem) => elem.project_id === y.project_id + ); + + if (project_x_found != undefined && project_y_found != undefined) { + let sum_x = 0; + for (const c of project_x_found.project_role) { + sum_x += c._count.contract; + } + + let sum_y = 0; + for (const c of project_y_found.project_role) { + sum_y += c._count.contract; + } + + const fullyAssignedX = x.positions === sum_x ? 1 : 0; + const fullyAssignedY = y.positions === sum_y ? 1 : 0; + + return fullyAssignedX - fullyAssignedY; + } + + return 0; + }); + } + + if (fullyAssignedSort == "desc") { + filtered_projects.reverse(); + } + + /*if (fullyAssignedSort == "asc") { + filtered_projects.sort( + (x, y) => + +(x.positions == projects[x.project_id]._sum.positions) - + +(y.positions == projects[y.project_id]._sum.positions) + ); + } + if (fullyAssignedSort == "desc") { + filtered_projects.sort( + (x, y) => + +(x.positions == projects[x.project_id]._sum.positions) - + +(y.positions == projects[y.project_id]._sum.positions) + ); + filtered_projects.reverse(); + }*/ + return filtered_projects; +} diff --git a/backend/orm_functions/project_role.ts b/backend/orm_functions/project_role.ts index 484f4a57..44b659d3 100644 --- a/backend/orm_functions/project_role.ts +++ b/backend/orm_functions/project_role.ts @@ -1,124 +1,129 @@ -import prisma from '../prisma/prisma' -import {CreateProjectRole, UpdateProjectRole} from './orm_types'; +import prisma from "../prisma/prisma"; + +import { CreateProjectRole, UpdateProjectRole } from "./orm_types"; /** - * + * * @param projectRole: project role object with the needed information */ -export async function createProjectRole(projectRole: CreateProjectRole){ +export async function createProjectRole(projectRole: CreateProjectRole) { const result = await prisma.project_role.create({ data: { project_id: projectRole.projectId, role_id: projectRole.roleId, - positions: projectRole.positions + positions: projectRole.positions, }, }); return result; } /** - * - * @param projectId: this is the id of the project for which we want all the roles + * + * @param projectId: this is the id of the project for which we want all the + * roles * @returns: all the project role objects for that project */ export async function getProjectRolesByProject(projectId: number) { const result = prisma.project_role.findMany({ - where: { - project_id: projectId - }, + where: { project_id: projectId }, }); return result; } /** - * - * @param projectId: this is the id of the project for which we want the number of positions - * @param projectRoleId: this is the id of the projectRole for which we want the number of positions + * + * @param projectId: this is the id of the project for which we want the number + * of positions + * @param projectRoleId: this is the id of the projectRole for which we want the + * number of positions * @returns: returns the number of positions for the projectrole of that project */ -export async function getNumberOfRolesByProjectAndRole(projectId : number, projectRoleId : number) { +export async function getNumberOfRolesByProjectAndRole( + projectId: number, + projectRoleId: number +) { const result = prisma.project_role.findMany({ - where: { + where: { AND: [ - { - project_id: projectId - }, - { - project_role_id: projectRoleId - }, + { project_id: projectId }, + { project_role_id: projectRoleId }, ], - } + }, }); return result; } // Get all Project Role Names for a certain Project /** - * - * @param projectId: this is the id of the project for which we want all the roles - * @returns: returns all the projectroles object togheter with the role objects for that project + * + * @param projectId: this is the id of the project for which we want all the + * roles + * @returns: returns all the projectroles object togheter with the role objects + * for that project */ export async function getProjectRoleNamesByProject(projectId: number) { const result = prisma.project_role.findMany({ - where: { - project_id: projectId - }, - include: { - role: true - } + where: { project_id: projectId }, + include: { role: true }, }); return result; } /** - * - * @param projectRole: UpdateProject object with the values that need to be updated - * @returns the updated entry in the database + * + * @param projectRole: UpdateProject object with the values that need to be + * updated + * @returns the updated entry in the database */ -export async function updateProjectRole(projectRole: UpdateProjectRole){ - const result = await prisma.project_role.update({ - where: { - project_role_id: projectRole.projectRoleId - }, - data: { - positions: projectRole.positions - }, +export async function updateProjectRole(projectRole: UpdateProjectRole) { + const result = await prisma.project_role.update({ + where: { project_role_id: projectRole.projectRoleId }, + data: { positions: projectRole.positions }, }); return result; } /** - * - * @param projectRoleId the projectRole we are deleting from the project role-table + * + * @param projectRoleId the projectRole we are deleting from the project + * role-table * @returns TODO: what does this return? */ -export async function deleteProjectRole(projectRoleId: number){ +export async function deleteProjectRole(projectRoleId: number) { const result = await prisma.project_role.delete({ - where: { - project_role_id: projectRoleId - } + where: { project_role_id: projectRoleId }, }); return result; } /** * - * @param projectRoleId: the id of the projectRule we are searching the number of free positions for - * @return the number of free positions if free (in a promise) else null (if not found in db) + * @param projectRoleId: the id of the projectRule we are searching the number + * of free positions for + * @return the number of free positions if free (in a promise) else null (if not + * found in db) */ export async function getNumberOfFreePositions(projectRoleId: number) { const result = await prisma.project_role.findUnique({ - where: { - project_role_id: projectRoleId - }, - select : { - positions: true, - contract: true - } + where: { project_role_id: projectRoleId }, + select: { positions: true, contract: true }, }); if (result) { - return result.positions - result.contract.length + return result.positions - result.contract.length; } return result; -} \ No newline at end of file +} + +export async function getProjectRoleById(projectRoleId: number) { + const result = await prisma.project_role.findUnique({ + where: { project_role_id: projectRoleId }, + select: { + project_role_id: true, + project_id: true, + role_id: true, + role: true, + }, + }); + return result; +} diff --git a/backend/orm_functions/project_user.ts b/backend/orm_functions/project_user.ts index d39b1b56..c9d0804d 100644 --- a/backend/orm_functions/project_user.ts +++ b/backend/orm_functions/project_user.ts @@ -1,16 +1,37 @@ -import prisma from '../prisma/prisma' -import {CreateProjectUser} from './orm_types'; +import prisma from "../prisma/prisma"; + +import { CreateProjectUser } from "./orm_types"; /** - * + * * @param projectUser: project user object with the needed information */ -export async function createProjectUser(projectUser: CreateProjectUser){ +export async function createProjectUser(projectUser: CreateProjectUser) { const result = await prisma.project_user.create({ data: { login_user_id: projectUser.loginUserId, - project_id: projectUser.projectId + project_id: projectUser.projectId, }, }); return result; } + +/** + * Gets the users associated with the given project. + * @param project The ID of the project to get users for. + */ +export async function getUsersFor(project: number) { + return await prisma.project_user.findMany({ + where: { project_id: project }, + select: { + login_user: { + select: { + login_user_id: true, + is_admin: true, + is_coach: true, + person: true, + }, + }, + }, + }); +} diff --git a/backend/orm_functions/role.ts b/backend/orm_functions/role.ts index 515c3d96..0cb114a0 100644 --- a/backend/orm_functions/role.ts +++ b/backend/orm_functions/role.ts @@ -1,50 +1,50 @@ -import prisma from '../prisma/prisma' -import {UpdateRole} from './orm_types'; +import prisma from "../prisma/prisma"; +import { UpdateRole } from "./orm_types"; /** - * + * * @param name: the name of the role we want to add to the database */ - export async function createProjectRole(name: string){ +export async function createRole(name: string) { const result = await prisma.role.create({ data: { - name: name + name: name, }, }); return result; } /** - * + * * @returns a list of all the role objects in the database */ export async function getAllRoles() { - return await prisma.role.findMany() + return await prisma.role.findMany(); } /** - * + * * @param roleId: this is the id of the role we are looking up in the database * @returns: object with the name of the role */ - export async function getRole(roleId: number) { +export async function getRole(roleId: number) { return await prisma.role.findUnique({ where: { role_id: roleId, - } + }, }); } /** - * + * * @param name: this is the name of the role we are looking up in the database * @returns: all the role objects with a matching name */ - export async function getRolesByName(name: string) { +export async function getRolesByName(name: string) { return await prisma.role.findUnique({ where: { name: name, - } + }, }); } @@ -54,64 +54,67 @@ export async function getAllRoles() { * @param projectId: the id of the project the searched project_role belongs to * @returns the project_role or an error from the database */ -export async function getProjectRoleWithRoleName(name: string, projectId: number) { - const id = await prisma.role.findUnique({ - where : { - name : name, - }, - select: { - role_id: true - } - }); - if (id) { - return await prisma.project_role.findFirst({ - where: { - role_id: id.role_id, - project_id: projectId - } - }); - } - return id +export async function getProjectRoleWithRoleName( + name: string, + projectId: number +) { + const id = await prisma.role.findUnique({ + where: { + name: name, + }, + select: { + role_id: true, + }, + }); + if (id) { + return await prisma.project_role.findFirst({ + where: { + role_id: id.role_id, + project_id: projectId, + }, + }); + } + return id; } /** - * + * * @param role: UpdateRole object with the values that need to be updated - * @returns the updated entry in the database + * @returns the updated entry in the database */ - export async function updateRole(role: UpdateRole) { +export async function updateRole(role: UpdateRole) { return await prisma.role.update({ where: { role_id: role.roleId, }, data: { name: role.name, - } + }, }); } /** - * + * * @param roleId the role we are deleting from the role-table - * @returns TODO: what does this return + * @returns a promise with the deleted record inside */ - export async function deleteRole(roleId: number) { +export async function deleteRole(roleId: number) { return await prisma.role.delete({ where: { role_id: roleId, - } + }, }); } /** - * + * * @param name the name of the role we are deleting from the role-table - * @returns TODO: what does this return + * @returns a promise with the deleted record inside */ - export async function deleteRoleByName(name: string) { +export async function deleteRoleByName(name: string) { return await prisma.role.delete({ where: { name: name, - } + }, }); } diff --git a/backend/orm_functions/session_key.ts b/backend/orm_functions/session_key.ts index 4796d6c2..6c389f60 100644 --- a/backend/orm_functions/session_key.ts +++ b/backend/orm_functions/session_key.ts @@ -1,29 +1,90 @@ -import prisma from '../prisma/prisma'; +import prisma from "../prisma/prisma"; -export async function addSessionKey(loginUserId: number, key: string) { - const result = await prisma.session_keys.create( - {data : {login_user_id : loginUserId, session_key : key}}); - return result; +/** + * adds session key to loginUser + * + * @param loginUserId the id of the loginUser for whom we are adding a session key + * @param key the new session key + * @param date + * @returns the new record in the database in a promise + */ +export async function addSessionKey( + loginUserId: number, + key: string, + date: Date +) { + return await prisma.session_keys.create({ + data: { + login_user_id: loginUserId, + session_key: key, + valid_until: date, + }, + }); } +/** + * checks if the session key exists + * + * @param key: the key whose validity we want to check + * @returns the loginUser associated with the key in a promise if the keys is valid otherwise an error in a promise + */ export async function checkSessionKey(key: string) { - const result = await prisma.session_keys.findUnique({ - where : {session_key : key}, - select : {login_user_id : true}, - rejectOnNotFound : true - }); - return result; + return await prisma.session_keys.findFirst({ + where: { + AND: [{ session_key: key }, { valid_until: { gte: new Date() } }], + }, + select: { + login_user_id: true, + }, + }); } -export async function changeSessionKey(key: string, newkey: string) { - const result = await prisma.session_keys.update( - {where : {session_key : key}, data : {session_key : newkey}}); - return result; +/** + * + * @param key: the old key we want to overwrite + * @param date + * @returns the updated record in a promise + */ +export async function refreshKey(key: string, date: Date) { + return await prisma.session_keys.update({ + where: { + session_key: key, + }, + data: { + valid_until: date, + }, + }); } +/** + * deletes all session keys of the user that has the given key + * + * @param key a key of a user whose keys we want to delete + * @returns the number of deleted records in a promise + */ export async function removeAllKeysForUser(key: string) { - const result = await checkSessionKey(key).then( - uid => prisma.session_keys.deleteMany( - {where : {login_user_id : uid.login_user_id}})); - return result; + return await checkSessionKey(key).then((uid) => { + if (uid != null) { + return prisma.session_keys.deleteMany({ + where: { + login_user_id: uid.login_user_id, + }, + }); + } else { + return { count: 0 }; + } + }); +} + +/** + * + * @param loginUserId the id of the loginUser whose session keys we want to remove + * @returns a promise with the number of deleted records + */ +export async function removeAllKeysForLoginUserId(loginUserId: number) { + return await prisma.session_keys.deleteMany({ + where: { + login_user_id: loginUserId, + }, + }); } diff --git a/backend/orm_functions/student.ts b/backend/orm_functions/student.ts index 31d4abd0..d92260e7 100644 --- a/backend/orm_functions/student.ts +++ b/backend/orm_functions/student.ts @@ -1,9 +1,16 @@ -import prisma from '../prisma/prisma' -import {CreateStudent, UpdateStudent} from './orm_types'; +import { decision_enum, email_status_enum } from "@prisma/client"; +import prisma from "../prisma/prisma"; +import { + CreateStudent, + FilterSort, + FilterString, + FilterBoolean, + UpdateStudent, + FilterStringArray, +} from "./orm_types"; -// TODO: how do we make sure there is no student for this person_id yet? /** - * + * * @param student: student object with the needed information */ export async function createStudent(student: CreateStudent) { @@ -15,24 +22,24 @@ export async function createStudent(student: CreateStudent) { phone_number: student.phoneNumber, nickname: student.nickname, alumni: student.alumni, - } + }, }); -} +} /** - * + * * @returns a list of all the student objects in the database together with their personal info */ export async function getAllStudents() { return await prisma.student.findMany({ include: { person: true, - } - }) + }, + }); } /** - * + * * @param studentId: this is the id of the student we are looking up in the database * @returns: object with all the info about this student together with their personal info */ @@ -43,14 +50,14 @@ export async function getStudent(studentId: number) { }, include: { person: true, - } + }, }); } /** - * + * * @param student: UpdateStudent object with the values that need to be updated - * @returns the updated entry in the database + * @returns the updated entry in the database */ export async function updateStudent(student: UpdateStudent) { return await prisma.student.update({ @@ -62,15 +69,16 @@ export async function updateStudent(student: UpdateStudent) { phone_number: student.phoneNumber, nickname: student.nickname, alumni: student.alumni, + gender: student.gender, }, include: { person: true, - } + }, }); } /** - * + * * @param studentId the student who's info we are deleting from the student-table * @returns personal info about the student, this info can be used to further remove the personal info about this student in other tables */ @@ -79,9 +87,10 @@ export async function deleteStudent(studentId: number) { where: { student_id: studentId, }, - include: { // returns the person info of the removed student, can later be used to also remove everything from this person? - person: true - } + include: { + // returns the person info of the removed student, can later be used to also remove everything from this person? + person: true, + }, }); } @@ -90,13 +99,127 @@ export async function deleteStudent(studentId: number) { * @param gender: This is the gender of the persons we are looking, can be firstname as lastname * @returns: a list of all the person objects in the database that match */ -export async function searchStudentByGender(gender: string){ +export async function searchStudentByGender(gender: string) { return prisma.student.findMany({ where: { - gender: gender + gender: gender, }, include: { person: true, - } + }, + }); +} + +/** + * + * @param firstNameFilter firstname that we are filtering on (or undefined if not filtering on name) + * @param lastNameFilter firstname that we are filtering on (or undefined if not filtering on name) + * @param emailFilter email that we are filtering on (or undefined if not filtering on email) + * @param roleFilterArray role that we are filtering on (or undefined if not filtering on role) + * @param alumniFilter alumni status that we are filtering on (or undefined if not filtering on alumni status) + * @param coachFilter coach status that we are filtering on (or undefined if not filtering on coach status) + * @param statusFilter decision status that we are filtering on (or undefined if not filtering on status) + * @param emailStatusFilter email status that we are filtering on (or undefined if not filtering on email status) + * @param osocYear: the osoc year the application belongs to (or undefined if not filtering on year) + * @param firstNameSort asc or desc if we want to sort on firstname, undefined if we are not sorting on firstname + * @param lastNameSort asc or desc if we want to sort on lastname, undefined if we are not sorting on lastname + * @param emailSort asc or desc if we are sorting on email, undefined if we are not sorting on email + * @param alumniSort asc or desc if we are sorting on alumni status, undefined if we are not sorting on alumni status + * @returns the filtered students with their person data and other filter fields in a promise + */ +export async function filterStudents( + firstNameFilter: FilterString, + lastNameFilter: FilterString, + emailFilter: FilterString, + roleFilterArray: FilterStringArray, + alumniFilter: FilterBoolean, + coachFilter: FilterBoolean, + statusFilter: decision_enum | undefined, + osocYear: number, + emailStatusFilter: email_status_enum | undefined, + firstNameSort: FilterSort, + lastNameSort: FilterSort, + emailSort: FilterSort, + alumniSort: FilterSort +) { + // manually create filter object for evaluation because evaluation doesn't need to exist + // and then the whole object needs to be undefined + let evaluationFilter; + if (statusFilter) { + evaluationFilter = { + some: { + decision: statusFilter, + }, + }; + } else { + evaluationFilter = undefined; + } + + console.log(alumniFilter); + return await prisma.student.findMany({ + where: { + job_application: { + some: { + email_status: emailStatusFilter, + student_coach: coachFilter, + osoc: { + year: osocYear, + }, + applied_role: { + some: { + role: { + name: { in: roleFilterArray }, + }, + }, + }, + evaluation: evaluationFilter, + }, + }, + person: { + firstname: { + contains: firstNameFilter, + mode: "insensitive", + }, + lastname: { + contains: lastNameFilter, + mode: "insensitive", + }, + email: { + contains: emailFilter, + mode: "insensitive", + }, + }, + alumni: alumniFilter, + }, + orderBy: { + person: { + firstname: firstNameSort, + lastname: lastNameSort, + email: emailSort, + }, + alumni: alumniSort, + }, + include: { + person: true, + job_application: { + select: { + student_coach: true, + applied_role: { + include: { + role: { + select: { + name: true, + }, + }, + }, + }, + evaluation: { + select: { + decision: true, + }, + }, + }, + }, + }, }); } diff --git a/backend/orm_functions/template.ts b/backend/orm_functions/template.ts new file mode 100644 index 00000000..cdb211bf --- /dev/null +++ b/backend/orm_functions/template.ts @@ -0,0 +1,49 @@ +import prisma from "../prisma/prisma"; + +import { CreateTemplate, UpdateTemplate } from "./orm_types"; + +export async function getAllTemplates() { + return prisma.template_email.findMany(); +} + +export async function getTemplateById(templateId: number) { + return prisma.template_email.findUnique({ + where: { template_email_id: templateId }, + }); +} + +export async function getTemplatesByName(name: string) { + return prisma.template_email.findMany({ + where: { name: name }, + include: { login_user: true }, + }); +} + +export async function createTemplate(data: CreateTemplate) { + return prisma.template_email.create({ + data: { + owner_id: data.ownerId, + name: data.name, + content: data.content, + cc: data.cc, + subject: data.subject, + }, + }); +} + +export async function updateTemplate(data: UpdateTemplate) { + return prisma.template_email.update({ + where: { template_email_id: data.templateId }, + data: { + content: data.content, + name: data.name, + owner_id: data.ownerId, + cc: data.cc, + subject: data.subject, + }, + }); +} + +export async function deleteTemplate(id: number) { + return prisma.template_email.delete({ where: { template_email_id: id } }); +} diff --git a/backend/package-lock.json b/backend/package-lock.json index c2948d8e..7fa907cb 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -14,23 +14,32 @@ "@types/cors": "^2.8.12", "@types/express": "^4.17.13", "@types/node": "^17.0.21", + "@types/nodemailer": "^6.4.4", "@types/uuid": "^8.3.4", + "axios": "^0.27.0", "body-parser": "^1.19.2", "cors": "^2.8.5", + "cross-env": "^7.0.3", "express": "^4.17.3", + "googleapis": "^100.0.0", + "nodemailer": "^6.7.3", "passport": "^0.5.2", - "prisma": "3.11", - "ts-jest": "^27.1.3" + "prisma": "3.13", + "socket.io": "^4.4.1", + "ts-jest": "^27.1.3", + "validator": "^13.7.0" }, "devDependencies": { "@types/jest": "^27.4.1", + "@types/validator": "^13.7.0", + "callable-instance": "^2.0.0", "dotenv-cli": "^5.0.0", "eslint-plugin-jest": "^26.1.1", "jest": "^27.5.1", "jest-mock-extended": "^2.0.4", "node-ts": "^5.1.2", "nodemon": "^2.0.15", - "ts-node": "^10.5.0", + "ts-node": "^10.7.0", "typedoc": "^0.22.13", "typescript": "^4.5.5", "uuid": "^8.3.2" @@ -1079,12 +1088,12 @@ } }, "node_modules/@prisma/client": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-3.11.0.tgz", - "integrity": "sha512-d42o/tlalaWMmNOR4r5BiR6YYTYEV82eZ2lNKOm5ht3WyYwI9e+zy2MyZnNO4Fx5e08RAhW+GRVcEgKl5faUaQ==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-3.13.0.tgz", + "integrity": "sha512-lnEA2tTyVbO5mS1ehmHJQKBDiKB8shaR6s3azwj3Azfi5XHIfnqmkolLCvUeFYnkDCNVzGXJpUgKwQt/UOOYVQ==", "hasInstallScript": true, "dependencies": { - "@prisma/engines-version": "3.11.0-48.b371888aaf8f51357c7457d836b86d12da91658b" + "@prisma/engines-version": "3.13.0-17.efdf9b1183dddfd4258cd181a72125755215ab7b" }, "engines": { "node": ">=12.6" @@ -1099,15 +1108,15 @@ } }, "node_modules/@prisma/engines": { - "version": "3.11.0-48.b371888aaf8f51357c7457d836b86d12da91658b", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-3.11.0-48.b371888aaf8f51357c7457d836b86d12da91658b.tgz", - "integrity": "sha512-m9iZd5F5vP6A2IvKWfHpOO/qK8OOO9nbsV/pdyEkF/1WNe0E8SIWFBKb+HcMLkG9OFbDDBy8QItXmp/mIULuwQ==", + "version": "3.13.0-17.efdf9b1183dddfd4258cd181a72125755215ab7b", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-3.13.0-17.efdf9b1183dddfd4258cd181a72125755215ab7b.tgz", + "integrity": "sha512-Ip9CcCeUocH61eXu4BUGpvl5KleQyhcUVLpWCv+0ZmDv44bFaDpREqjGHHdRupvPN/ugB6gTlD9b9ewdj02yVA==", "hasInstallScript": true }, "node_modules/@prisma/engines-version": { - "version": "3.11.0-48.b371888aaf8f51357c7457d836b86d12da91658b", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-3.11.0-48.b371888aaf8f51357c7457d836b86d12da91658b.tgz", - "integrity": "sha512-bhMW1XybXZyqCf+9QqjP7Oi7xgVHcISVyOZNMm51qeZsy12M1RtHaCcXUFeMMV0JOCZZuPFVr3+0KVpQqK35CQ==" + "version": "3.13.0-17.efdf9b1183dddfd4258cd181a72125755215ab7b", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-3.13.0-17.efdf9b1183dddfd4258cd181a72125755215ab7b.tgz", + "integrity": "sha512-TGp9rvgJIKo8NgvAHSwOosbut9mTA7VC6/rpQI9gh+ySSRjdQFhbGyNUiOcQrlI9Ob2DWeO7y4HEnhdKxYiECg==" }, "node_modules/@rauschma/stringio": { "version": "1.4.0", @@ -1149,6 +1158,14 @@ "@sinonjs/commons": "^1.7.0" } }, + "node_modules/@socket.io/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@socket.io/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-dOlCBKnDw4iShaIsH/bxujKTM18+2TOAsYz+KSc11Am38H4q5Xw8Bbz97ZYdrVNM+um3p7w86Bvvmcn9q+5+eQ==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/@szmarczak/http-timer": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", @@ -1239,6 +1256,11 @@ "@types/node": "*" } }, + "node_modules/@types/component-emitter": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.11.tgz", + "integrity": "sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ==" + }, "node_modules/@types/connect": { "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", @@ -1247,6 +1269,11 @@ "@types/node": "*" } }, + "node_modules/@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, "node_modules/@types/cors": { "version": "2.8.12", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", @@ -1324,9 +1351,17 @@ "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" }, "node_modules/@types/node": { - "version": "17.0.23", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.23.tgz", - "integrity": "sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw==" + "version": "17.0.29", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.29.tgz", + "integrity": "sha512-tx5jMmMFwx7wBwq/V7OohKDVb/JwJU5qCVkeLMh1//xycAJ/ESuw9aJ9SEtlCZDYi2pBfe4JkisSoAtbOsBNAA==" + }, + "node_modules/@types/nodemailer": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.4.tgz", + "integrity": "sha512-Ksw4t7iliXeYGvIQcSIgWQ5BLuC/mljIEbjf615svhZL10PE9t+ei8O9gDaD3FPCasUJn9KTLwz2JFJyiiyuqw==", + "dependencies": { + "@types/node": "*" + } }, "node_modules/@types/prettier": { "version": "2.4.4", @@ -1362,6 +1397,12 @@ "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==" }, + "node_modules/@types/validator": { + "version": "13.7.2", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.2.tgz", + "integrity": "sha512-KFcchQ3h0OPQgFirBRPZr5F/sVjxZsOrQHedj3zi8AH3Zv/hOLx2OLR4hxR5HcfoU+33n69ZuOfzthKVdMoTiw==", + "dev": true + }, "node_modules/@types/yargs": { "version": "16.0.4", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", @@ -1544,6 +1585,17 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -1759,11 +1811,41 @@ "node": ">=8" } }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "engines": { + "node": ">=8" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, + "node_modules/axios": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "dependencies": { + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/babel-jest": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz", @@ -1856,6 +1938,41 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/bignumber.js": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.2.tgz", + "integrity": "sha512-GAcQvbpsM0pUb0zw1EI0KhQEZ+lRwR5fYaAp3vPOYuP7aDvGy6cVN6XHLauvF8SOga2y0dcLcjt3iQDTSEliyw==", + "engines": { + "node": "*" + } + }, "node_modules/binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -1866,23 +1983,26 @@ } }, "node_modules/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", + "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.8.1", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.9.7", - "raw-body": "2.4.3", - "type-is": "~1.6.18" + "on-finished": "2.4.1", + "qs": "6.10.3", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, "node_modules/boxen": { @@ -1973,6 +2093,11 @@ "node-int64": "^0.4.0" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -2028,6 +2153,24 @@ "node": ">=8" } }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callable-instance": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callable-instance/-/callable-instance-2.0.0.tgz", + "integrity": "sha512-wOBp/J1CRZLsbFxG1alxefJjoG1BW/nocXkUanAe2+leiD/+cVr00j8twSZoDiRy03o5vibq9pbrZc+EDjjUTw==", + "dev": true + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2208,6 +2351,11 @@ "node": ">= 0.8" } }, + "node_modules/component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2293,6 +2441,23 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "devOptional": true }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -2416,17 +2581,21 @@ } }, "node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } }, "node_modules/detect-newline": { "version": "3.1.0", @@ -2519,9 +2688,9 @@ } }, "node_modules/dotenv-cli": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-5.0.0.tgz", - "integrity": "sha512-0Cb2WMDJ805hTD7m43gXXFLraoE5KwrKmGW2dAzYvSEB96tlKI2hmcJ/9In4s2FfvkAFk3SjNQcLeKLoRSXhKA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-5.1.0.tgz", + "integrity": "sha512-NoEZAlKo9WVrG0b3i9mBxdD6INdDuGqdgR74t68t8084QcI077/1MnPerRW1odl+9uULhcdnQp2U0pYVppKHOA==", "dev": true, "dependencies": { "cross-spawn": "^7.0.3", @@ -2548,6 +2717,14 @@ "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", "dev": true }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -2591,6 +2768,78 @@ "once": "^1.4.0" } }, + "node_modules/engine.io": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.0.tgz", + "integrity": "sha512-4KzwW3F3bk+KlzSOY57fj/Jx6LyRQ1nbcyIadehl+AnXjKT7gDO0ORdRi/84ixvMKTym6ZKuxvbzN62HDDU1Lg==", + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.2.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.3.tgz", + "integrity": "sha512-BtQxwF27XUNnSafQLvDi0dQ8s3i6VgzSoQMJacpIcGNrlUdfHSKbgm3jmjCVvQluGzqwujQMPAoMai3oYSTurg==", + "dependencies": { + "@socket.io/base64-arraybuffer": "~1.0.2" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/engine.io/node_modules/ws": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -2704,9 +2953,9 @@ } }, "node_modules/eslint-plugin-jest": { - "version": "26.1.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-26.1.3.tgz", - "integrity": "sha512-Pju+T7MFpo5VFhFlwrkK/9jRUu18r2iugvgyrWOnnGRaVTFFmFXp+xFJpHyqmjjLmGJPKLeEFLVTAxezkApcpQ==", + "version": "26.1.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-26.1.5.tgz", + "integrity": "sha512-su89aDuljL9bTjEufTXmKUMSFe2kZUL9bi7+woq+C2ukHZordhtfPm4Vg+tdioHBaKf8v3/FXW9uV0ksqhYGFw==", "dev": true, "dependencies": { "@typescript-eslint/utils": "^5.10.0" @@ -3008,6 +3257,14 @@ "node": ">= 0.6" } }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "engines": { + "node": ">=6" + } + }, "node_modules/execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -3064,37 +3321,38 @@ } }, "node_modules/express": { - "version": "4.17.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz", - "integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.0.tgz", + "integrity": "sha512-EJEXxiTQJS3lIPrU1AE2vRuT7X7E+0KBbpm5GSoK524yl0K8X+er8zS2P14E64eqsVNoWbMCT7MpmQ+ErAhgRg==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.19.2", + "body-parser": "1.20.0", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.4.2", + "cookie": "0.5.0", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "~1.1.2", + "depd": "2.0.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "~1.1.2", + "finalhandler": "1.2.0", "fresh": "0.5.2", + "http-errors": "2.0.0", "merge-descriptors": "1.0.1", "methods": "~1.1.2", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", "proxy-addr": "~2.0.7", - "qs": "6.9.7", + "qs": "6.10.3", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.17.2", - "serve-static": "1.14.2", + "send": "0.18.0", + "serve-static": "1.15.0", "setprototypeof": "1.2.0", - "statuses": "~1.5.0", + "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -3103,6 +3361,19 @@ "node": ">= 0.10.0" } }, + "node_modules/express/node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -3136,6 +3407,11 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" }, + "node_modules/fast-text-encoding": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz", + "integrity": "sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==" + }, "node_modules/fastq": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", @@ -3178,16 +3454,16 @@ } }, "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", - "statuses": "~1.5.0", + "statuses": "2.0.1", "unpipe": "~1.0.0" }, "engines": { @@ -3227,6 +3503,25 @@ "dev": true, "peer": true }, + "node_modules/follow-redirects": { + "version": "1.14.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", + "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/form-data": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", @@ -3286,6 +3581,33 @@ "dev": true, "peer": true }, + "node_modules/gaxios": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.2.tgz", + "integrity": "sha512-T+ap6GM6UZ0c4E6yb1y/hy2UB6hTrqhglp3XfmU9qbLCGRYhLVV5aRPpC4EmoG8N8zOnkYCgoBz+ScvGAARY6Q==", + "dependencies": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gcp-metadata": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.3.1.tgz", + "integrity": "sha512-x850LS5N7V1F3UcV7PoupzGsyD6iVwTVvsh3tbXfkctZnBnjW5yu5z1/3k3SehF7TyoTIe78rJs02GMMy+LF+A==", + "dependencies": { + "gaxios": "^4.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -3302,6 +3624,19 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", @@ -3396,6 +3731,67 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/google-auth-library": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.14.1.tgz", + "integrity": "sha512-5Rk7iLNDFhFeBYc3s8l1CqzbEBcdhwR193RlD4vSNFajIcINKI8W8P0JLmBpwymHqqWbX34pJDQu39cSy/6RsA==", + "dependencies": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/google-p12-pem": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.3.tgz", + "integrity": "sha512-MC0jISvzymxePDVembypNefkAQp+DRP7dBE+zNUPaIjEspIlYg0++OrsNr248V9tPbz6iqtZ7rX1hxWA5B8qBQ==", + "dependencies": { + "node-forge": "^1.0.0" + }, + "bin": { + "gp12-pem": "build/src/bin/gp12-pem.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/googleapis": { + "version": "100.0.0", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-100.0.0.tgz", + "integrity": "sha512-RToFQGY54B756IDbjdyjb1vWFmn03bYpXHB2lIf0eq2UBYsIbYOLZ0kqSomfJnpclEukwEmMF7Jn6Wsev871ew==", + "dependencies": { + "google-auth-library": "^7.0.2", + "googleapis-common": "^5.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/googleapis-common": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-5.1.0.tgz", + "integrity": "sha512-RXrif+Gzhq1QAzfjxulbGvAY3FPj8zq/CYcvgjzDbaBNCD6bUl+86I7mUs4DKWHGruuK26ijjR/eDpWIDgNROA==", + "dependencies": { + "extend": "^3.0.2", + "gaxios": "^4.0.0", + "google-auth-library": "^7.14.0", + "qs": "^6.7.0", + "url-template": "^2.0.8", + "uuid": "^8.0.0" + }, + "engines": { + "node": ">=10.10.0" + } + }, "node_modules/got": { "version": "9.6.0", "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", @@ -3423,6 +3819,19 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==" }, + "node_modules/gtoken": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.2.tgz", + "integrity": "sha512-gkvEKREW7dXWF8NV8pVrKfW7WqReAmjjkMBh6lNCCGOM4ucS0r0YyXXl0r/9Yj8wcW/32ISkfc8h5mPTDbtifQ==", + "dependencies": { + "gaxios": "^4.0.0", + "google-p12-pem": "^3.1.3", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -3442,6 +3851,17 @@ "node": ">=4" } }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/has-yarn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", @@ -3474,18 +3894,18 @@ "dev": true }, "node_modules/http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "dependencies": { - "depd": "~1.1.2", + "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", + "statuses": "2.0.1", "toidentifier": "1.0.1" }, "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/http-proxy-agent": { @@ -4302,9 +4722,9 @@ } }, "node_modules/jest-mock-extended": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/jest-mock-extended/-/jest-mock-extended-2.0.4.tgz", - "integrity": "sha512-MgL3B3GjURQFjjPGqbCANydA5BFNPygv0mYp4Tjfxohh9MWwxxX8Eq2p6ncCt/Vt+RAnaLlDaI7gwrDRD7Pt9A==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/jest-mock-extended/-/jest-mock-extended-2.0.5.tgz", + "integrity": "sha512-cQjKRqzZ/hUyy3AdmB7Aa+0DB45dt/eEApwfZFZzup9oefGnw2QodW/BLR39aCpPkK0Qb54P5OKYRXJ98kQ8cQ==", "dev": true, "dependencies": { "ts-essentials": "^7.0.3" @@ -4654,6 +5074,14 @@ "node": ">=4" } }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, "node_modules/json-buffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", @@ -4699,6 +5127,25 @@ "integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==", "dev": true }, + "node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "node_modules/keyv": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", @@ -4982,6 +5429,52 @@ "node": ">= 0.6" } }, + "node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "engines": { + "node": ">= 6.13.0" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -5011,6 +5504,14 @@ "integrity": "sha512-GZ7bu5A6+4DtG7q9GsoHXy3ALcgeIHP4NnL0Vv2wu0uUB/yQex26v0tf6/na1mm0+bS9Uw+0DFex7aaKr2qawQ==", "dev": true }, + "node_modules/nodemailer": { + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.7.3.tgz", + "integrity": "sha512-KUdDsspqx89sD4UUyUKzdlUOper3hRkDVkrKh/89G+d9WKsU5ox51NWS4tB1XR5dPUdR4SP0E3molyEfOvSa3g==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/nodemon": { "version": "2.0.15", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.15.tgz", @@ -5111,10 +5612,18 @@ "node": ">=0.10.0" } }, + "node_modules/object-inspect": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", + "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "dependencies": { "ee-first": "1.1.1" }, @@ -5418,12 +5927,13 @@ } }, "node_modules/prisma": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-3.11.0.tgz", - "integrity": "sha512-8SdsLPhKR3mOfoo2o73h9mNn3v5kA/RqGA26Sv6qDS78Eh2uepPqt5e8/nwj5EOblYm5HEGuitaXQrOCLb6uTw==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-3.13.0.tgz", + "integrity": "sha512-oO1auBnBtieGdiN+57IgsA9Vr7Sy4HkILi1KSaUG4mpKfEbnkTGnLOxAqjLed+K2nsG/GtE1tJBtB7JxN1a78Q==", "hasInstallScript": true, "dependencies": { - "@prisma/engines": "3.11.0-48.b371888aaf8f51357c7457d836b86d12da91658b" + "@prisma/engines": "3.13.0-17.efdf9b1183dddfd4258cd181a72125755215ab7b", + "ts-pattern": "^4.0.1" }, "bin": { "prisma": "build/index.js", @@ -5499,9 +6009,12 @@ } }, "node_modules/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==", + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "dependencies": { + "side-channel": "^1.0.4" + }, "engines": { "node": ">=0.6" }, @@ -5538,12 +6051,12 @@ } }, "node_modules/raw-body": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz", - "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", "dependencies": { "bytes": "3.1.2", - "http-errors": "1.8.1", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" }, @@ -5799,23 +6312,23 @@ } }, "node_modules/send": { - "version": "0.17.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", - "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", "dependencies": { "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", + "depd": "2.0.0", + "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "1.8.1", + "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "statuses": "2.0.1" }, "engines": { "node": ">= 0.8.0" @@ -5827,14 +6340,14 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/serve-static": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", - "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", "dependencies": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.17.2" + "send": "0.18.0" }, "engines": { "node": ">= 0.8.0" @@ -5875,24 +6388,113 @@ "vscode-textmate": "5.2.0" } }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/socket.io": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.0.tgz", + "integrity": "sha512-slTYqU2jCgMjXwresG8grhUi/cC6GjzmcfqArzaH3BN/9I/42eZk9yamNvZJdBfTubkjEdKAKs12NEztId+bUA==", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.2", + "engine.io": "~6.2.0", + "socket.io-adapter": "~2.4.0", + "socket.io-parser": "~4.0.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz", + "integrity": "sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==" + }, + "node_modules/socket.io-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz", + "integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==", + "dependencies": { + "@types/component-emitter": "^1.2.10", + "component-emitter": "~1.3.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" + "node_modules/socket.io-parser/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "node_modules/socket.io/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, "engines": { - "node": ">=8" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, + "node_modules/socket.io/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -5927,11 +6529,11 @@ } }, "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/string-length": { @@ -6180,9 +6782,9 @@ } }, "node_modules/ts-jest": { - "version": "27.1.3", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-27.1.3.tgz", - "integrity": "sha512-6Nlura7s6uM9BVUAoqLH7JHyMXjz8gluryjpPXxr3IxZdAXnU6FhjvVLHFtfd1vsE1p8zD1OJfskkc0jhTSnkA==", + "version": "27.1.4", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-27.1.4.tgz", + "integrity": "sha512-qjkZlVPWVctAezwsOD1OPzbZ+k7zA5z3oxII4dGdZo5ggX/PL7kvwTM0pXTr10fAtbiVpJaL3bWd502zAhpgSQ==", "dependencies": { "bs-logger": "0.x", "fast-json-stable-stringify": "2.x", @@ -6203,7 +6805,6 @@ "@babel/core": ">=7.0.0-beta.0 <8", "@types/jest": "^27.0.0", "babel-jest": ">=27.0.0 <28", - "esbuild": "~0.14.0", "jest": "^27.0.0", "typescript": ">=3.8 <5.0" }, @@ -6279,6 +6880,11 @@ } } }, + "node_modules/ts-pattern": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/ts-pattern/-/ts-pattern-4.0.2.tgz", + "integrity": "sha512-eHqR/7A6fcw05vCOfnL6RwgGJbVi9G/YHTdYdjYmElhDdJ1SMn7pWs+6+YuxygaFwQS/g+cIDlu+UD8IVpur1A==" + }, "node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -6352,9 +6958,9 @@ } }, "node_modules/typedoc": { - "version": "0.22.13", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.22.13.tgz", - "integrity": "sha512-NHNI7Dr6JHa/I3+c62gdRNXBIyX7P33O9TafGLd07ur3MqzcKgwTvpg18EtvCLHJyfeSthAtCLpM7WkStUmDuQ==", + "version": "0.22.15", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.22.15.tgz", + "integrity": "sha512-CMd1lrqQbFvbx6S9G6fL4HKp3GoIuhujJReWqlIvSb2T26vGai+8Os3Mde7Pn832pXYemd9BMuuYWhFpL5st0Q==", "dev": true, "dependencies": { "glob": "^7.2.0", @@ -6395,9 +7001,9 @@ } }, "node_modules/typescript": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.2.tgz", - "integrity": "sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg==", + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", + "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -6505,6 +7111,11 @@ "node": ">=4" } }, + "node_modules/url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha1-/FZaPMy/93MMd19WQflVV5FDnyE=" + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -6517,7 +7128,6 @@ "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, "bin": { "uuid": "dist/bin/uuid" } @@ -6556,6 +7166,14 @@ "node": ">= 8" } }, + "node_modules/validator": { + "version": "13.7.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz", + "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -7579,22 +8197,22 @@ } }, "@prisma/client": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-3.11.0.tgz", - "integrity": "sha512-d42o/tlalaWMmNOR4r5BiR6YYTYEV82eZ2lNKOm5ht3WyYwI9e+zy2MyZnNO4Fx5e08RAhW+GRVcEgKl5faUaQ==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-3.13.0.tgz", + "integrity": "sha512-lnEA2tTyVbO5mS1ehmHJQKBDiKB8shaR6s3azwj3Azfi5XHIfnqmkolLCvUeFYnkDCNVzGXJpUgKwQt/UOOYVQ==", "requires": { - "@prisma/engines-version": "3.11.0-48.b371888aaf8f51357c7457d836b86d12da91658b" + "@prisma/engines-version": "3.13.0-17.efdf9b1183dddfd4258cd181a72125755215ab7b" } }, "@prisma/engines": { - "version": "3.11.0-48.b371888aaf8f51357c7457d836b86d12da91658b", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-3.11.0-48.b371888aaf8f51357c7457d836b86d12da91658b.tgz", - "integrity": "sha512-m9iZd5F5vP6A2IvKWfHpOO/qK8OOO9nbsV/pdyEkF/1WNe0E8SIWFBKb+HcMLkG9OFbDDBy8QItXmp/mIULuwQ==" + "version": "3.13.0-17.efdf9b1183dddfd4258cd181a72125755215ab7b", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-3.13.0-17.efdf9b1183dddfd4258cd181a72125755215ab7b.tgz", + "integrity": "sha512-Ip9CcCeUocH61eXu4BUGpvl5KleQyhcUVLpWCv+0ZmDv44bFaDpREqjGHHdRupvPN/ugB6gTlD9b9ewdj02yVA==" }, "@prisma/engines-version": { - "version": "3.11.0-48.b371888aaf8f51357c7457d836b86d12da91658b", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-3.11.0-48.b371888aaf8f51357c7457d836b86d12da91658b.tgz", - "integrity": "sha512-bhMW1XybXZyqCf+9QqjP7Oi7xgVHcISVyOZNMm51qeZsy12M1RtHaCcXUFeMMV0JOCZZuPFVr3+0KVpQqK35CQ==" + "version": "3.13.0-17.efdf9b1183dddfd4258cd181a72125755215ab7b", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-3.13.0-17.efdf9b1183dddfd4258cd181a72125755215ab7b.tgz", + "integrity": "sha512-TGp9rvgJIKo8NgvAHSwOosbut9mTA7VC6/rpQI9gh+ySSRjdQFhbGyNUiOcQrlI9Ob2DWeO7y4HEnhdKxYiECg==" }, "@rauschma/stringio": { "version": "1.4.0", @@ -7635,6 +8253,11 @@ "@sinonjs/commons": "^1.7.0" } }, + "@socket.io/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@socket.io/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-dOlCBKnDw4iShaIsH/bxujKTM18+2TOAsYz+KSc11Am38H4q5Xw8Bbz97ZYdrVNM+um3p7w86Bvvmcn9q+5+eQ==" + }, "@szmarczak/http-timer": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", @@ -7719,6 +8342,11 @@ "@types/node": "*" } }, + "@types/component-emitter": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.11.tgz", + "integrity": "sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ==" + }, "@types/connect": { "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", @@ -7727,6 +8355,11 @@ "@types/node": "*" } }, + "@types/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" + }, "@types/cors": { "version": "2.8.12", "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", @@ -7804,9 +8437,17 @@ "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" }, "@types/node": { - "version": "17.0.23", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.23.tgz", - "integrity": "sha512-UxDxWn7dl97rKVeVS61vErvw086aCYhDLyvRQZ5Rk65rZKepaFdm53GeqXaKBuOhED4e9uWq34IC3TdSdJJ2Gw==" + "version": "17.0.29", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.29.tgz", + "integrity": "sha512-tx5jMmMFwx7wBwq/V7OohKDVb/JwJU5qCVkeLMh1//xycAJ/ESuw9aJ9SEtlCZDYi2pBfe4JkisSoAtbOsBNAA==" + }, + "@types/nodemailer": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.4.tgz", + "integrity": "sha512-Ksw4t7iliXeYGvIQcSIgWQ5BLuC/mljIEbjf615svhZL10PE9t+ei8O9gDaD3FPCasUJn9KTLwz2JFJyiiyuqw==", + "requires": { + "@types/node": "*" + } }, "@types/prettier": { "version": "2.4.4", @@ -7842,6 +8483,12 @@ "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==" }, + "@types/validator": { + "version": "13.7.2", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.2.tgz", + "integrity": "sha512-KFcchQ3h0OPQgFirBRPZr5F/sVjxZsOrQHedj3zi8AH3Zv/hOLx2OLR4hxR5HcfoU+33n69ZuOfzthKVdMoTiw==", + "dev": true + }, "@types/yargs": { "version": "16.0.4", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", @@ -7965,6 +8612,14 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "requires": { + "event-target-shim": "^5.0.0" + } + }, "accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -8121,11 +8776,37 @@ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true }, + "arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==" + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, + "axios": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz", + "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==", + "requires": { + "follow-redirects": "^1.14.9", + "form-data": "^4.0.0" + }, + "dependencies": { + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + } + } + }, "babel-jest": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz", @@ -8197,6 +8878,21 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" + }, + "bignumber.js": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.0.2.tgz", + "integrity": "sha512-GAcQvbpsM0pUb0zw1EI0KhQEZ+lRwR5fYaAp3vPOYuP7aDvGy6cVN6XHLauvF8SOga2y0dcLcjt3iQDTSEliyw==" + }, "binary-extensions": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", @@ -8204,20 +8900,22 @@ "dev": true }, "body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", + "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", "requires": { "bytes": "3.1.2", "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.8.1", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.9.7", - "raw-body": "2.4.3", - "type-is": "~1.6.18" + "on-finished": "2.4.1", + "qs": "6.10.3", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" } }, "boxen": { @@ -8286,6 +8984,11 @@ "node-int64": "^0.4.0" } }, + "buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + }, "buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -8328,6 +9031,21 @@ } } }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "callable-instance": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callable-instance/-/callable-instance-2.0.0.tgz", + "integrity": "sha512-wOBp/J1CRZLsbFxG1alxefJjoG1BW/nocXkUanAe2+leiD/+cVr00j8twSZoDiRy03o5vibq9pbrZc+EDjjUTw==", + "dev": true + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -8455,6 +9173,11 @@ "delayed-stream": "~1.0.0" } }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -8527,6 +9250,14 @@ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "devOptional": true }, + "cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "requires": { + "cross-spawn": "^7.0.1" + } + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -8628,14 +9359,14 @@ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" }, "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" }, "detect-newline": { "version": "3.1.0", @@ -8703,9 +9434,9 @@ "dev": true }, "dotenv-cli": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-5.0.0.tgz", - "integrity": "sha512-0Cb2WMDJ805hTD7m43gXXFLraoE5KwrKmGW2dAzYvSEB96tlKI2hmcJ/9In4s2FfvkAFk3SjNQcLeKLoRSXhKA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-5.1.0.tgz", + "integrity": "sha512-NoEZAlKo9WVrG0b3i9mBxdD6INdDuGqdgR74t68t8084QcI077/1MnPerRW1odl+9uULhcdnQp2U0pYVppKHOA==", "dev": true, "requires": { "cross-spawn": "^7.0.3", @@ -8726,6 +9457,14 @@ "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=", "dev": true }, + "ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "requires": { + "safe-buffer": "^5.0.1" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -8760,6 +9499,52 @@ "once": "^1.4.0" } }, + "engine.io": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.0.tgz", + "integrity": "sha512-4KzwW3F3bk+KlzSOY57fj/Jx6LyRQ1nbcyIadehl+AnXjKT7gDO0ORdRi/84ixvMKTym6ZKuxvbzN62HDDU1Lg==", + "requires": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.2.3" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "ws": { + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", + "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "requires": {} + } + } + }, + "engine.io-parser": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.3.tgz", + "integrity": "sha512-BtQxwF27XUNnSafQLvDi0dQ8s3i6VgzSoQMJacpIcGNrlUdfHSKbgm3jmjCVvQluGzqwujQMPAoMai3oYSTurg==", + "requires": { + "@socket.io/base64-arraybuffer": "~1.0.2" + } + }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -8959,9 +9744,9 @@ } }, "eslint-plugin-jest": { - "version": "26.1.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-26.1.3.tgz", - "integrity": "sha512-Pju+T7MFpo5VFhFlwrkK/9jRUu18r2iugvgyrWOnnGRaVTFFmFXp+xFJpHyqmjjLmGJPKLeEFLVTAxezkApcpQ==", + "version": "26.1.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-26.1.5.tgz", + "integrity": "sha512-su89aDuljL9bTjEufTXmKUMSFe2kZUL9bi7+woq+C2ukHZordhtfPm4Vg+tdioHBaKf8v3/FXW9uV0ksqhYGFw==", "dev": true, "requires": { "@typescript-eslint/utils": "^5.10.0" @@ -9052,6 +9837,11 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" + }, "execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", @@ -9092,42 +9882,55 @@ } }, "express": { - "version": "4.17.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz", - "integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==", + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.0.tgz", + "integrity": "sha512-EJEXxiTQJS3lIPrU1AE2vRuT7X7E+0KBbpm5GSoK524yl0K8X+er8zS2P14E64eqsVNoWbMCT7MpmQ+ErAhgRg==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.19.2", + "body-parser": "1.20.0", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.4.2", + "cookie": "0.5.0", "cookie-signature": "1.0.6", "debug": "2.6.9", - "depd": "~1.1.2", + "depd": "2.0.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "~1.1.2", + "finalhandler": "1.2.0", "fresh": "0.5.2", + "http-errors": "2.0.0", "merge-descriptors": "1.0.1", "methods": "~1.1.2", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", "proxy-addr": "~2.0.7", - "qs": "6.9.7", + "qs": "6.10.3", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.17.2", - "serve-static": "1.14.2", + "send": "0.18.0", + "serve-static": "1.15.0", "setprototypeof": "1.2.0", - "statuses": "~1.5.0", + "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" + }, + "dependencies": { + "cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==" + } } }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -9158,6 +9961,11 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" }, + "fast-text-encoding": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz", + "integrity": "sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==" + }, "fastq": { "version": "1.13.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", @@ -9194,16 +10002,16 @@ } }, "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", "requires": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "parseurl": "~1.3.3", - "statuses": "~1.5.0", + "statuses": "2.0.1", "unpipe": "~1.0.0" } }, @@ -9234,6 +10042,11 @@ "dev": true, "peer": true }, + "follow-redirects": { + "version": "1.14.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", + "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==" + }, "form-data": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", @@ -9277,6 +10090,27 @@ "dev": true, "peer": true }, + "gaxios": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.2.tgz", + "integrity": "sha512-T+ap6GM6UZ0c4E6yb1y/hy2UB6hTrqhglp3XfmU9qbLCGRYhLVV5aRPpC4EmoG8N8zOnkYCgoBz+ScvGAARY6Q==", + "requires": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.1" + } + }, + "gcp-metadata": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.3.1.tgz", + "integrity": "sha512-x850LS5N7V1F3UcV7PoupzGsyD6iVwTVvsh3tbXfkctZnBnjW5yu5z1/3k3SehF7TyoTIe78rJs02GMMy+LF+A==", + "requires": { + "gaxios": "^4.0.0", + "json-bigint": "^1.0.0" + } + }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -9287,6 +10121,16 @@ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, "get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", @@ -9351,6 +10195,52 @@ "slash": "^3.0.0" } }, + "google-auth-library": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.14.1.tgz", + "integrity": "sha512-5Rk7iLNDFhFeBYc3s8l1CqzbEBcdhwR193RlD4vSNFajIcINKI8W8P0JLmBpwymHqqWbX34pJDQu39cSy/6RsA==", + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^4.0.0", + "gcp-metadata": "^4.2.0", + "gtoken": "^5.0.4", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + } + }, + "google-p12-pem": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.3.tgz", + "integrity": "sha512-MC0jISvzymxePDVembypNefkAQp+DRP7dBE+zNUPaIjEspIlYg0++OrsNr248V9tPbz6iqtZ7rX1hxWA5B8qBQ==", + "requires": { + "node-forge": "^1.0.0" + } + }, + "googleapis": { + "version": "100.0.0", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-100.0.0.tgz", + "integrity": "sha512-RToFQGY54B756IDbjdyjb1vWFmn03bYpXHB2lIf0eq2UBYsIbYOLZ0kqSomfJnpclEukwEmMF7Jn6Wsev871ew==", + "requires": { + "google-auth-library": "^7.0.2", + "googleapis-common": "^5.0.2" + } + }, + "googleapis-common": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-5.1.0.tgz", + "integrity": "sha512-RXrif+Gzhq1QAzfjxulbGvAY3FPj8zq/CYcvgjzDbaBNCD6bUl+86I7mUs4DKWHGruuK26ijjR/eDpWIDgNROA==", + "requires": { + "extend": "^3.0.2", + "gaxios": "^4.0.0", + "google-auth-library": "^7.14.0", + "qs": "^6.7.0", + "url-template": "^2.0.8", + "uuid": "^8.0.0" + } + }, "got": { "version": "9.6.0", "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", @@ -9375,6 +10265,16 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.9.tgz", "integrity": "sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ==" }, + "gtoken": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.3.2.tgz", + "integrity": "sha512-gkvEKREW7dXWF8NV8pVrKfW7WqReAmjjkMBh6lNCCGOM4ucS0r0YyXXl0r/9Yj8wcW/32ISkfc8h5mPTDbtifQ==", + "requires": { + "gaxios": "^4.0.0", + "google-p12-pem": "^3.1.3", + "jws": "^4.0.0" + } + }, "has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -9388,6 +10288,11 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, "has-yarn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz", @@ -9414,14 +10319,14 @@ "dev": true }, "http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", "requires": { - "depd": "~1.1.2", + "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", + "statuses": "2.0.1", "toidentifier": "1.0.1" } }, @@ -10023,9 +10928,9 @@ } }, "jest-mock-extended": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/jest-mock-extended/-/jest-mock-extended-2.0.4.tgz", - "integrity": "sha512-MgL3B3GjURQFjjPGqbCANydA5BFNPygv0mYp4Tjfxohh9MWwxxX8Eq2p6ncCt/Vt+RAnaLlDaI7gwrDRD7Pt9A==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/jest-mock-extended/-/jest-mock-extended-2.0.5.tgz", + "integrity": "sha512-cQjKRqzZ/hUyy3AdmB7Aa+0DB45dt/eEApwfZFZzup9oefGnw2QodW/BLR39aCpPkK0Qb54P5OKYRXJ98kQ8cQ==", "dev": true, "requires": { "ts-essentials": "^7.0.3" @@ -10299,6 +11204,14 @@ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" }, + "json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "requires": { + "bignumber.js": "^9.0.0" + } + }, "json-buffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", @@ -10338,6 +11251,25 @@ "integrity": "sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==", "dev": true }, + "jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "requires": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "requires": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, "keyv": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", @@ -10551,6 +11483,40 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" }, + "node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "requires": { + "whatwg-url": "^5.0.0" + }, + "dependencies": { + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } + }, + "node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==" + }, "node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -10579,6 +11545,11 @@ } } }, + "nodemailer": { + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.7.3.tgz", + "integrity": "sha512-KUdDsspqx89sD4UUyUKzdlUOper3hRkDVkrKh/89G+d9WKsU5ox51NWS4tB1XR5dPUdR4SP0E3molyEfOvSa3g==" + }, "nodemon": { "version": "2.0.15", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-2.0.15.tgz", @@ -10652,10 +11623,15 @@ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, + "object-inspect": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", + "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==" + }, "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "requires": { "ee-first": "1.1.1" } @@ -10869,11 +11845,12 @@ } }, "prisma": { - "version": "3.11.0", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-3.11.0.tgz", - "integrity": "sha512-8SdsLPhKR3mOfoo2o73h9mNn3v5kA/RqGA26Sv6qDS78Eh2uepPqt5e8/nwj5EOblYm5HEGuitaXQrOCLb6uTw==", + "version": "3.13.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-3.13.0.tgz", + "integrity": "sha512-oO1auBnBtieGdiN+57IgsA9Vr7Sy4HkILi1KSaUG4mpKfEbnkTGnLOxAqjLed+K2nsG/GtE1tJBtB7JxN1a78Q==", "requires": { - "@prisma/engines": "3.11.0-48.b371888aaf8f51357c7457d836b86d12da91658b" + "@prisma/engines": "3.13.0-17.efdf9b1183dddfd4258cd181a72125755215ab7b", + "ts-pattern": "^4.0.1" } }, "prompts": { @@ -10930,9 +11907,12 @@ } }, "qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==" + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "requires": { + "side-channel": "^1.0.4" + } }, "queue-microtask": { "version": "1.2.3", @@ -10946,12 +11926,12 @@ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "raw-body": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz", - "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", "requires": { "bytes": "3.1.2", - "http-errors": "1.8.1", + "http-errors": "2.0.0", "iconv-lite": "0.4.24", "unpipe": "1.0.0" } @@ -11122,23 +12102,23 @@ } }, "send": { - "version": "0.17.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", - "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", "requires": { "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", + "depd": "2.0.0", + "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "1.8.1", + "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "statuses": "2.0.1" }, "dependencies": { "ms": { @@ -11149,14 +12129,14 @@ } }, "serve-static": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", - "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", "requires": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.17.2" + "send": "0.18.0" } }, "setprototypeof": { @@ -11188,6 +12168,16 @@ "vscode-textmate": "5.2.0" } }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, "signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -11203,6 +12193,64 @@ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" }, + "socket.io": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.0.tgz", + "integrity": "sha512-slTYqU2jCgMjXwresG8grhUi/cC6GjzmcfqArzaH3BN/9I/42eZk9yamNvZJdBfTubkjEdKAKs12NEztId+bUA==", + "requires": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.2", + "engine.io": "~6.2.0", + "socket.io-adapter": "~2.4.0", + "socket.io-parser": "~4.0.4" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "socket.io-adapter": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz", + "integrity": "sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==" + }, + "socket.io-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz", + "integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==", + "requires": { + "@types/component-emitter": "^1.2.10", + "component-emitter": "~1.3.0", + "debug": "~4.3.1" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -11231,9 +12279,9 @@ } }, "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==" }, "string-length": { "version": "4.0.2", @@ -11415,9 +12463,9 @@ "requires": {} }, "ts-jest": { - "version": "27.1.3", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-27.1.3.tgz", - "integrity": "sha512-6Nlura7s6uM9BVUAoqLH7JHyMXjz8gluryjpPXxr3IxZdAXnU6FhjvVLHFtfd1vsE1p8zD1OJfskkc0jhTSnkA==", + "version": "27.1.4", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-27.1.4.tgz", + "integrity": "sha512-qjkZlVPWVctAezwsOD1OPzbZ+k7zA5z3oxII4dGdZo5ggX/PL7kvwTM0pXTr10fAtbiVpJaL3bWd502zAhpgSQ==", "requires": { "bs-logger": "0.x", "fast-json-stable-stringify": "2.x", @@ -11460,6 +12508,11 @@ "yn": "3.1.1" } }, + "ts-pattern": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/ts-pattern/-/ts-pattern-4.0.2.tgz", + "integrity": "sha512-eHqR/7A6fcw05vCOfnL6RwgGJbVi9G/YHTdYdjYmElhDdJ1SMn7pWs+6+YuxygaFwQS/g+cIDlu+UD8IVpur1A==" + }, "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -11512,9 +12565,9 @@ } }, "typedoc": { - "version": "0.22.13", - "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.22.13.tgz", - "integrity": "sha512-NHNI7Dr6JHa/I3+c62gdRNXBIyX7P33O9TafGLd07ur3MqzcKgwTvpg18EtvCLHJyfeSthAtCLpM7WkStUmDuQ==", + "version": "0.22.15", + "resolved": "https://registry.npmjs.org/typedoc/-/typedoc-0.22.15.tgz", + "integrity": "sha512-CMd1lrqQbFvbx6S9G6fL4HKp3GoIuhujJReWqlIvSb2T26vGai+8Os3Mde7Pn832pXYemd9BMuuYWhFpL5st0Q==", "dev": true, "requires": { "glob": "^7.2.0", @@ -11545,9 +12598,9 @@ } }, "typescript": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.2.tgz", - "integrity": "sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg==" + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", + "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==" }, "undefsafe": { "version": "2.0.5", @@ -11626,6 +12679,11 @@ "prepend-http": "^2.0.0" } }, + "url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha1-/FZaPMy/93MMd19WQflVV5FDnyE=" + }, "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -11634,8 +12692,7 @@ "uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" }, "v8-compile-cache": { "version": "2.3.0", @@ -11667,6 +12724,11 @@ } } }, + "validator": { + "version": "13.7.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz", + "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/backend/package.json b/backend/package.json index da397416..1efe3e50 100644 --- a/backend/package.json +++ b/backend/package.json @@ -4,14 +4,15 @@ "description": "", "main": "index.js", "scripts": { - "start": "nodemon index.ts", - "migrate:postgres": "npx dotenv -e prisma/.env.test -- npx prisma migrate dev --name postgres-init", + "dev": "cross-env NODE_ENV=development nodemon index.ts", + "debug": "cross-env NODE_ENV=development nodemon --legacy-watch --exec 'node --inspect=0.0.0.0:9229 --require ts-node/register index.ts'", + "prod": "cross-env NODE_ENV=production nodemon index.ts", + "migrate:postgres": "npx dotenv -e prisma/.env.test -- npx prisma db push", "dockerIntegration:up": "docker-compose -f docker-compose.integrationtest.yml up -d --build", "docker:down": "docker-compose down", "integrationTests": "npm run dockerIntegration:up && npm run migrate:postgres && jest -i", "test": "jest", - "coverage": "jest --coverage", - "codecov": "jest --ci --coverage --outputFile=coverage.json --json" + "coverage": "npm run dockerIntegration:up && npm run migrate:postgres && jest -i --coverage" }, "author": "", "license": "ISC", @@ -21,23 +22,32 @@ "@types/cors": "^2.8.12", "@types/express": "^4.17.13", "@types/node": "^17.0.21", + "@types/nodemailer": "^6.4.4", "@types/uuid": "^8.3.4", + "axios": "^0.27.0", "body-parser": "^1.19.2", "cors": "^2.8.5", + "cross-env": "^7.0.3", "express": "^4.17.3", + "googleapis": "^100.0.0", + "nodemailer": "^6.7.3", "passport": "^0.5.2", - "prisma": "3.11", - "ts-jest": "^27.1.3" + "prisma": "3.13", + "socket.io": "^4.4.1", + "ts-jest": "^27.1.3", + "validator": "^13.7.0" }, "devDependencies": { "@types/jest": "^27.4.1", + "@types/validator": "^13.7.0", + "callable-instance": "^2.0.0", "dotenv-cli": "^5.0.0", "eslint-plugin-jest": "^26.1.1", "jest": "^27.5.1", "jest-mock-extended": "^2.0.4", "node-ts": "^5.1.2", "nodemon": "^2.0.15", - "ts-node": "^10.5.0", + "ts-node": "^10.7.0", "typedoc": "^0.22.13", "typescript": "^4.5.5", "uuid": "^8.3.2" diff --git a/backend/prisma/prisma.ts b/backend/prisma/prisma.ts index 74fb0714..63f8bbdf 100644 --- a/backend/prisma/prisma.ts +++ b/backend/prisma/prisma.ts @@ -1,4 +1,4 @@ -import { PrismaClient } from '@prisma/client' +import { PrismaClient } from "@prisma/client"; const prisma = new PrismaClient(); export default prisma; diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index ac33a7c9..ab2a317c 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -18,8 +18,8 @@ model applied_role { model attachment { attachment_id Int @id @default(autoincrement()) job_application_id Int @default(autoincrement()) - data String - type type_enum + data String[] + type type_enum[] job_application job_application @relation(fields: [job_application_id], references: [job_application_id], onDelete: NoAction, onUpdate: NoAction) } @@ -51,13 +51,13 @@ model job_application { student_id Int @default(autoincrement()) student_volunteer_info String responsibilities String? - fun_fact String? + fun_fact String student_coach Boolean osoc_id Int edus String[] - edu_level String? + edu_level String edu_duration Int? - edu_year Int? + edu_year String? edu_institute String? email_status email_status_enum created_at DateTime @db.Timestamptz(6) @@ -72,13 +72,13 @@ model job_application { model job_application_skill { job_application_skill_id Int @id @default(autoincrement()) job_application_id Int @default(autoincrement()) - skill String - language_id Int @default(autoincrement()) + skill String? + language_id Int? level Int? @db.SmallInt - is_preferred Boolean? - is_best Boolean? + is_preferred Boolean + is_best Boolean job_application job_application @relation(fields: [job_application_id], references: [job_application_id], onDelete: NoAction, onUpdate: NoAction) - language language @relation(fields: [language_id], references: [language_id], onDelete: NoAction, onUpdate: NoAction) + language language? @relation(fields: [language_id], references: [language_id], onDelete: NoAction, onUpdate: NoAction) } model language { @@ -97,8 +97,10 @@ model login_user { person person @relation(fields: [person_id], references: [person_id], onDelete: NoAction, onUpdate: NoAction) contract contract[] evaluation evaluation[] + password_reset password_reset? project_user project_user[] session_keys session_keys[] + template_email template_email[] } model osoc { @@ -114,6 +116,7 @@ model person { github String? @unique firstname String lastname String + github_id String? @unique login_user login_user? student student? } @@ -161,7 +164,7 @@ model student { student_id Int @id @default(autoincrement()) person_id Int @unique @default(autoincrement()) gender String - pronouns String[] + pronouns String? phone_number String nickname String? alumni Boolean @@ -173,10 +176,31 @@ model student { model session_keys { session_key_id Int @id @default(autoincrement()) login_user_id Int @default(autoincrement()) + valid_until DateTime @db.Timestamptz(6) session_key String @unique @db.VarChar(128) login_user login_user @relation(fields: [login_user_id], references: [login_user_id], onDelete: NoAction, onUpdate: NoAction) } +model password_reset { + password_reset_id Int @id @default(autoincrement()) + login_user_id Int @unique @default(autoincrement()) + reset_id String @unique @db.VarChar(128) + valid_until DateTime @db.Timestamptz(6) + login_user login_user @relation(fields: [login_user_id], references: [login_user_id], onDelete: NoAction, onUpdate: NoAction) +} + +model template_email { + template_email_id Int @id @default(autoincrement()) + owner_id Int @default(autoincrement()) + name String + content String + subject String? + cc String? + login_user login_user @relation(fields: [owner_id], references: [login_user_id], onDelete: NoAction, onUpdate: NoAction) + + @@unique([owner_id, name]) +} + enum decision_enum { YES NO @@ -212,5 +236,4 @@ enum account_status_enum { ACTIVATED PENDING DISABLED - UNVERIFIED } diff --git a/backend/request.ts b/backend/request.ts index 59962526..8088eaa8 100644 --- a/backend/request.ts +++ b/backend/request.ts @@ -1,32 +1,40 @@ -import {account_status_enum} from '@prisma/client'; -import express from 'express'; +import { account_status_enum } from "@prisma/client"; +import express from "express"; +import * as validator from "validator"; -import {Anything, InternalTypes, Requests} from './types'; -import {errors} from './utility'; +import * as config from "./config.json"; +import { + Anything, + Decision, + FollowupType, + InternalTypes, + Requests, +} from "./types"; +import { errors, getSessionKey } from "./utility"; /** * We use 3 types of requests: those requiring no special values, those * requiring a key, and those requiring both a key and an ID. */ -type RequestType = "Neither"|"Key"|"Id"; +type RequestType = "Neither" | "Key" | "Id"; /** * Ease of access for the three request types. This type holds all three as * const fields. */ interface RequestTypes { - neither: RequestType; - key: RequestType; - id: RequestType; + neither: RequestType; + key: RequestType; + id: RequestType; } /** * Implementation of the {@link RequestTypes} interface. */ const types: RequestTypes = { - neither : "Neither", - key : "Key", - id : "Id" + neither: "Neither", + key: "Key", + id: "Id", }; /** @@ -35,7 +43,7 @@ const types: RequestTypes = { * @returns A Promise rejecting with an Argument Error. */ function rejector(): Promise { - return Promise.reject(errors.cookArgumentError()); + return Promise.reject(errors.cookArgumentError()); } /** @@ -47,38 +55,55 @@ function rejector(): Promise { * @returns `true` if and only if the object contains all of the fields. */ function anyHasFields(obj: Anything, fields: string[]): boolean { - for (const f of fields) { - if (!(f in obj)) { - console.log("!!! Missing argument " + f + " in `" + JSON.stringify(obj) + - "`!!!"); - return false; + for (const f of fields) { + if (!(f in obj)) { + console.log( + "!!! Missing argument " + + f + + " in `" + + JSON.stringify(obj) + + "`!!!" + ); + return false; + } } - } - return true; + return true; } /** * Checks if a request has the required fields. If the request is a Key request - * or and ID request, the `req.body.sessionkey` field is also checked for - * existence. If the request is an ID request, the `req.params.id` field is also - * checked for existence. + * or and ID request, the `Authorization` header is also checked for existence + * and semantics; it has to start with the correct value (as defined in the + * `config.json`). If the request is an ID request, the `req.params.id` field + * is also checked for existence. * @param req The request to check. * @param fields The fields that should be present. * @param reqType The type of request. * @returns A Promise which will resolve to nothing if all of the fields are * present, or reject with an Argument Error if any of the fields is not * present. If the request is expected to be a key or ID request, but it doesn't - * hold a `req.body.sessionkey`, a promise rejecting with an Unauthenticated + * hold a `getSessionKey(req)`, a promise rejecting with an Unauthenticated * Error is returned instead. */ -function hasFields(req: express.Request, fields: string[], - reqType: RequestType): Promise { - if ((reqType == types.key || reqType == types.id) && - (!("sessionkey" in req.body) || req.body.sessionkey == undefined)) - return Promise.reject(errors.cookUnauthenticated()); - if (reqType == types.id && !("id" in req.params)) - return rejector(); - return anyHasFields(req.body, fields) ? Promise.resolve() : rejector(); +function hasFields( + req: express.Request, + fields: string[], + reqType: RequestType +): Promise { + if (reqType == types.key || reqType == types.id) { + const authHeader = req.headers.authorization; + if ( + authHeader == undefined || + !authHeader.startsWith(config.global.authScheme) + ) { + return Promise.reject(errors.cookUnauthenticated()); + } + } + // if ((reqType == types.key || reqType == types.id) && + // (!("sessionkey" in req.body) || req.body.sessionkey == undefined)) + // return Promise.reject(errors.cookUnauthenticated()); + if (reqType == types.id && !("id" in req.params)) return rejector(); + return anyHasFields(req.body, fields) ? Promise.resolve() : rejector(); } /** @@ -88,7 +113,7 @@ function hasFields(req: express.Request, fields: string[], * @returns `true` if at least one field is present, otherwise `false`. */ function atLeastOneField(req: express.Request, fields: string[]): boolean { - return fields.some(s => (s in req.body)); + return fields.some((s) => s in req.body); } /** @@ -98,8 +123,8 @@ function atLeastOneField(req: express.Request, fields: string[]): boolean { * @param key The key to find in the object. * @returns `obj[key]` if the key is present, otherwise `undefined`. */ -function maybe(obj: Anything, key: string): T|undefined { - return (key in obj) ? (obj[key] as T) : undefined; +function maybe(obj: Anything, key: string): T | undefined { + return key in obj ? (obj[key] as T) : undefined; } /** @@ -108,11 +133,14 @@ function maybe(obj: Anything, key: string): T|undefined { * @returns A Promise resolving to the parsed data or rejecting with an * Argument or Unauthenticated error. */ -async function parseKeyRequest(req: express.Request): - Promise { - return hasFields(req, [], types.key).then(() => Promise.resolve({ - sessionkey : req.body.sessionkey - })); +async function parseKeyRequest( + req: express.Request +): Promise { + return hasFields(req, [], types.key).then(() => + Promise.resolve({ + sessionkey: getSessionKey(req), + }) + ); } /** @@ -121,12 +149,15 @@ async function parseKeyRequest(req: express.Request): * @returns A Promise resolving to the parsed data or rejecting with an * Argument or Unauthenticated error. */ -async function parseKeyIdRequest(req: express.Request): - Promise { - return hasFields(req, [], types.id).then(() => Promise.resolve({ - sessionkey : req.body.sessionkey, - id : Number(req.params.id) - })); +async function parseKeyIdRequest( + req: express.Request +): Promise { + return hasFields(req, [], types.id).then(() => + Promise.resolve({ + sessionkey: getSessionKey(req), + id: Number(req.params.id), + }) + ); } /** @@ -135,20 +166,21 @@ async function parseKeyIdRequest(req: express.Request): * @returns A Promise resolving to the parsed data or rejecting with an * Argument or Unauthenticated error. */ -async function parseUpdateLoginUser(req: express.Request): - Promise { - return hasFields(req, [ "isAdmin", "isCoach", "accountStatus" ], types.id) - .then(() => { +async function parseUpdateLoginUser( + req: express.Request +): Promise { + return hasFields(req, [], types.id).then(() => { return Promise.resolve({ - sessionkey : req.body.sessionkey, - id : Number(req.params.id), - isAdmin : maybe(req.body, "isAdmin") as boolean, - isCoach : maybe(req.body, "isCoach") as boolean, - pass : maybe(req.body, "pass") as string, - accountStatus : maybe(req.body, "accountStatus") as - account_status_enum + sessionkey: getSessionKey(req), + id: Number(req.params.id), + isAdmin: maybe(req.body, "isAdmin") as boolean, + isCoach: maybe(req.body, "isCoach") as boolean, + accountStatus: maybe( + req.body, + "accountStatus" + ) as account_status_enum, }); - }); + }); } /** @@ -157,11 +189,19 @@ async function parseUpdateLoginUser(req: express.Request): * @returns A Promise resolving to the parsed data or rejecting with an * Argument or Unauthenticated error. */ -export async function parseLoginRequest(req: express.Request): - Promise { - return hasFields(req, [ "name", "pass" ], types.neither) - .then(() => - Promise.resolve({name : req.body.name, pass : req.body.pass})); +export async function parseLoginRequest( + req: express.Request +): Promise { + return hasFields(req, ["name", "pass"], types.neither).then(() => { + if (!validator.default.isEmail(req.body.name)) { + return rejector(); + } else { + const email = validator.default + .normalizeEmail(req.body.name) + .toString(); + return Promise.resolve({ name: email, pass: req.body.pass }); + } + }); } /** @@ -170,78 +210,278 @@ export async function parseLoginRequest(req: express.Request): * @returns A Promise resolving to the parsed data or rejecting with an * Argument or Unauthenticated error. */ -export async function parseUpdateStudentRequest(req: express.Request): - Promise { - const bodyF = [ - "emailOrGithub", "firstName", "lastName", "gender", "pronouns", "phone", - "nickname", "alumni", "education" - ]; +export async function parseUpdateStudentRequest( + req: express.Request +): Promise { + const bodyF = [ + "emailOrGithub", + "firstName", + "lastName", + "gender", + "pronouns", + "phone", + "nickname", + "alumni", + "education", + ]; + + return hasFields(req, [], types.id).then(() => { + if (!atLeastOneField(req, bodyF)) return rejector(); + + return Promise.resolve({ + sessionkey: getSessionKey(req), + id: Number(req.params.id), + emailOrGithub: maybe(req.body, "emailOrGithub"), + firstName: maybe(req.body, "firstName"), + lastName: maybe(req.body, "lastName"), + gender: maybe(req.body, "gender"), + pronouns: maybe(req.body, "pronouns"), + phone: maybe(req.body, "phone"), + education: maybe(req.body, "education"), + alumni: maybe(req.body, "alumni"), + nickname: maybe(req.body, "nickname"), + }); + }); +} + +/** + * Parses a request to `POST /student//suggest`. + * @param req The request to check. + * @returns A Promise resolving to the parsed data or rejecting with an + * Argument or Unauthenticated error. + */ +export async function parseSuggestStudentRequest( + req: express.Request +): Promise { + return hasFields(req, ["suggestion"], types.id).then(() => { + const sug: unknown = req.body.suggestion; + if (sug != Decision.YES && sug != Decision.MAYBE && sug != Decision.NO) + return rejector(); + + return Promise.resolve({ + sessionkey: getSessionKey(req), + id: Number(req.params.id), + suggestion: sug as InternalTypes.Suggestion, + reason: maybe(req.body, "reason"), + }); + }); +} + +/** + * Parses a request to `GET /student//suggest`. + * @param req The request to check. + * @returns A Promise resolving to the parsed data or rejecting with an + * Argument or Unauthenticated error. + */ +export async function parseGetSuggestionsStudentRequest( + req: express.Request +): Promise { + return hasFields(req, [], types.id).then(() => { + if ("year" in req.body) { + return Promise.resolve({ + sessionkey: getSessionKey(req), + id: Number(req.params.id), + year: Number(req.body.year), + }); + } else { + return Promise.resolve({ + sessionkey: getSessionKey(req), + id: Number(req.params.id), + }); + } + }); +} - return hasFields(req, [], types.id).then(() => { - if (!atLeastOneField(req, bodyF)) - return rejector(); +/** + * Parses a request to `GET /osoc/filter`. + * @param req The request to check. + * @returns A Promise resolving to the parsed data or rejecting with an + * Argument or Unauthenticated error. + */ +export async function parseFilterOsocsRequest( + req: express.Request +): Promise { + let year = maybe(req.body, "yearFilter"); + if ("yearFilter" in req.body) { + year = parseInt(req.body.yearFilter); + } + + for (const filter of [maybe(req.body, "yearSort")]) { + if (filter != undefined && filter !== "asc" && filter !== "desc") { + return rejector(); + } + } return Promise.resolve({ - sessionkey : req.body.sessionkey, - id : Number(req.params.id), - emailOrGithub : maybe(req.body, "emailOrGithub"), - firstName : maybe(req.body, "firstName"), - lastName : maybe(req.body, "lastName"), - gender : maybe(req.body, "gender"), - pronouns : maybe(req.body, "pronouns"), - phone : maybe(req.body, "phone"), - education : maybe(req.body, "education"), - alumni : maybe(req.body, "alumni"), - nickname : maybe(req.body, "nickname") + sessionkey: getSessionKey(req), + yearFilter: year, + yearSort: maybe(req.body, "yearSort"), }); - }); } /** - * Parses a request to `POST /student//sugest`. + * Parses a request to `GET /student/filter`. * @param req The request to check. * @returns A Promise resolving to the parsed data or rejecting with an * Argument or Unauthenticated error. */ -export async function parseSuggestStudentRequest(req: express.Request): - Promise { - return hasFields(req, [ "suggestion", "senderId" ], types.id).then(() => { - const sug: unknown = req.body.suggestion; - if (sug != "YES" && sug != "MAYBE" && sug != "NO" && - req.body.senderId !== null) - return rejector(); +export async function parseFilterStudentsRequest( + req: express.Request +): Promise { + let mail = maybe(req.body, "emailFilter"); + let roles = maybe(req.body, "roleFilter"); + if ( + "statusFilter" in req.body && + req.body.statusFilter !== Decision.YES && + req.body.statusFilter !== Decision.MAYBE && + req.body.statusFilter !== Decision.NO + ) { + return rejector(); + } else { + if ( + "emailFilter" in req.body && + validator.default.isEmail(req.body.emailFilter) + ) { + mail = validator.default + .normalizeEmail(req.body.emailFilter) + .toString(); + } else if ("emailFilter" in req.body) { + mail = req.body.emailFilter as string; + } + } + if ("roleFilter" in req.body) { + roles = req.body.roleFilter.split(","); + } + + for (const filter of [ + maybe(req.body, "firstNameSort"), + maybe(req.body, "lastNameSort"), + maybe(req.body, "emailSort"), + maybe(req.body, "roleSort"), + maybe(req.body, "alumniSort"), + ]) { + if (filter != undefined && filter !== "asc" && filter !== "desc") { + return rejector(); + } + } + + let osoc_year = new Date().getFullYear(); + if ("osocYear" in req.body) { + osoc_year = Number(req.body.osocYear); + } + + let alumniFilter = maybe(req.body, "alumniFilter"); + if ("alumniFilter" in req.body) { + alumniFilter = Boolean(req.body.alumniFilter); + } + let coachFilter = maybe(req.body, "coachFilter"); + if ("coachFilter" in req.body) { + coachFilter = Boolean(req.body.coachFilter); + } return Promise.resolve({ - sessionkey : req.body.sessionkey, - id : Number(req.params.id), - suggestion : sug as InternalTypes.Suggestion, - reason : maybe(req.body, "reason"), - senderId : Number(req.body.senderId) + sessionkey: getSessionKey(req), + osocYear: osoc_year, + firstNameFilter: maybe(req.body, "firstNameFilter"), + lastNameFilter: maybe(req.body, "lastNameFilter"), + emailFilter: mail, + roleFilter: roles, + alumniFilter: alumniFilter, + coachFilter: coachFilter, + statusFilter: maybe(req.body, "statusFilter"), + emailStatusFilter: maybe(req.body, "emailStatusFilter"), + firstNameSort: maybe(req.body, "firstNameSort"), + lastNameSort: maybe(req.body, "lastNameSort"), + emailSort: maybe(req.body, "emailSort"), + alumniSort: maybe(req.body, "alumniSort"), }); - }); } /** - * Parses a request to `POST /student//confirm`. + * Parses a request to `GET /user/filter`. * @param req The request to check. * @returns A Promise resolving to the parsed data or rejecting with an * Argument or Unauthenticated error. */ -export async function parseFinalizeDecisionRequest(req: express.Request): - Promise { - return hasFields(req, [], types.id).then(() => { - if ("reply" in req.body) { - if (req.body.reply != "YES" && req.body.reply != "MAYBE" && - req.body.reply != "NO") +export async function parseFilterUsersRequest( + req: express.Request +): Promise { + let mail = undefined; + let isCoachFilter = undefined; + if ("isCoachFilter" in req.body) { + isCoachFilter = req.body.isCoachFilter === "true"; + } + let isAdminFilter = undefined; + if ("isAdminFilter" in req.body) { + isAdminFilter = req.body.isAdminFilter === "true"; + } + if ( + "statusFilter" in req.body && + req.body.statusFilter !== "ACTIVATED" && + req.body.statusFilter !== "PENDING" && + req.body.statusFilter !== "DISABLED" + ) { return rejector(); + } else { + if ( + "emailFilter" in req.body && + validator.default.isEmail(req.body.emailFilter) + ) { + mail = validator.default + .normalizeEmail(req.body.emailFilter) + .toString(); + } else if ("emailFilter" in req.body) { + mail = req.body.emailFilter as string; + } + } + + for (const filter of [ + maybe(req.body, "nameSort"), + maybe(req.body, "emailSort"), + ]) { + if (filter != undefined && filter !== "asc" && filter !== "desc") { + return rejector(); + } } return Promise.resolve({ - sessionkey : req.body.sessionkey, - id : Number(req.params.id), - reply : maybe(req.body, "reply") + sessionkey: getSessionKey(req), + nameFilter: maybe(req.body, "nameFilter"), + emailFilter: mail, + statusFilter: maybe(req.body, "statusFilter"), + nameSort: maybe(req.body, "nameSort"), + emailSort: maybe(req.body, "emailSort"), + isCoachFilter: isCoachFilter, + isAdminFilter: isAdminFilter, + }); +} + +/** + * Parses a request to `POST /student//confirm`. + * @param req The request to check. + * @returns A Promise resolving to the parsed data or rejecting with an + * Argument or Unauthenticated error. + */ +export async function parseFinalizeDecisionRequest( + req: express.Request +): Promise { + return hasFields(req, [], types.id).then(() => { + if ("reply" in req.body) { + if ( + req.body.reply != Decision.YES && + req.body.reply != Decision.MAYBE && + req.body.reply != Decision.NO + ) + return rejector(); + } + + return Promise.resolve({ + sessionkey: getSessionKey(req), + id: Number(req.params.id), + reason: maybe(req.body, "reason"), + reply: maybe(req.body, "reply"), + }); }); - }); } /** @@ -250,16 +490,18 @@ export async function parseFinalizeDecisionRequest(req: express.Request): * @returns A Promise resolving to the parsed data or rejecting with an * Argument or Unauthenticated error. */ -export async function parseRequestCoachRequest(req: express.Request): - Promise { - return hasFields(req, [ "firstName", "lastName", "emailOrGithub" ], - types.neither) - .then(() => Promise.resolve({ - firstName : req.body.firstName, - lastName : req.body.lastName, - emailOrGithub : req.body.emailOrGithub, - pass : maybe(req.body, "pass") - })); +export async function parseRequestUserRequest( + req: express.Request +): Promise { + return hasFields(req, ["firstName", "email", "pass"], types.neither).then( + () => + Promise.resolve({ + firstName: req.body.firstName, + lastName: "", + email: req.body.email, + pass: req.body.pass, + }) + ); } /** @@ -268,18 +510,24 @@ export async function parseRequestCoachRequest(req: express.Request): * @returns A Promise resolving to the parsed data or rejecting with an * Argument or Unauthenticated error. */ -export async function parseNewProjectRequest(req: express.Request): - Promise { - return hasFields(req, [ "name", "partner", "start", "end", "positions" ], - types.key) - .then(() => Promise.resolve({ - sessionkey : req.body.sessionkey, - name : req.body.name, - partner : req.body.partner, - start : req.body.start, - end : req.body.end, - positions : req.body.positions - })); +export async function parseNewProjectRequest( + req: express.Request +): Promise { + return hasFields( + req, + ["name", "partner", "start", "end", "positions", "osocId"], + types.key + ).then(() => + Promise.resolve({ + sessionkey: getSessionKey(req), + name: req.body.name, + partner: req.body.partner, + start: req.body.start, + end: req.body.end, + osocId: req.body.osocId, + positions: req.body.positions, + }) + ); } /** @@ -288,24 +536,73 @@ export async function parseNewProjectRequest(req: express.Request): * @returns A Promise resolving to the parsed data or rejecting with an * Argument or Unauthenticated error. */ -export async function parseUpdateProjectRequest(req: express.Request): - Promise { - const options = [ "name", "partner", "start", "end", "positions" ]; +export async function parseUpdateProjectRequest( + req: express.Request +): Promise { + const options = ["name", "partner", "start", "end", "positions"]; + + return hasFields(req, [], types.id).then(() => { + if (!atLeastOneField(req, options)) return rejector(); + + return Promise.resolve({ + sessionkey: getSessionKey(req), + id: Number(req.params.id), + name: maybe(req.body, "name"), + partner: maybe(req.body, "partner"), + start: maybe(req.body, "start"), + end: maybe(req.body, "end"), + positions: maybe(req.body, "positions"), + }); + }); +} - return hasFields(req, [], types.id).then(() => { - if (!atLeastOneField(req, options)) - return rejector(); +/** + * Parses a request to `GET /project/filter`. + * @param req The request to check. + * @returns A Promise resolving to the parsed data or rejecting with an + * Argument or Unauthenticated error. + */ +export async function parseFilterProjectsRequest( + req: express.Request +): Promise { + for (const filter of [ + maybe(req.body, "projectNameSort"), + maybe(req.body, "clientNameSort"), + maybe(req.body, "fullyAssignedSort"), + ]) { + if (filter != undefined && filter !== "asc" && filter !== "desc") { + return rejector(); + } + } + + let assignedCoachesFilterArray = maybe( + req.body, + "assignedCoachesFilterArray" + ); + if ("assignedCoachesFilterArray" in req.body) { + if (typeof req.body.assignedCoachesFilterArray === "string") { + assignedCoachesFilterArray = req.body.assignedCoachesFilterArray + .slice(1, -1) + .split(",") + .map((num: string) => Number(num)); + } + } + + let fullyAssignedFilter = maybe(req.body, "fullyAssignedFilter"); + if ("fullyAssignedFilter" in req.body) { + fullyAssignedFilter = req.body.fullyAssignedFilter === "true"; + } return Promise.resolve({ - sessionkey : req.body.sessionkey, - id : Number(req.params.id), - name : maybe(req.body, "name"), - partner : maybe(req.body, "partner"), - start : maybe(req.body, "start"), - end : maybe(req.body, "end"), - positions : maybe(req.body, "positions") + sessionkey: getSessionKey(req), + projectNameFilter: maybe(req.body, "projectNameFilter"), + clientNameFilter: maybe(req.body, "clientNameFilter"), + assignedCoachesFilterArray: assignedCoachesFilterArray, + fullyAssignedFilter: fullyAssignedFilter, + projectNameSort: maybe(req.body, "projectNameSort"), + clientNameSort: maybe(req.body, "clientNameSort"), + fullyAssignedSort: maybe(req.body, "fullyAssignedSort"), }); - }); } /** @@ -314,15 +611,17 @@ export async function parseUpdateProjectRequest(req: express.Request): * @returns A Promise resolving to the parsed data or rejecting with an * Argument or Unauthenticated error. */ -export async function parseDraftStudentRequest(req: express.Request): - Promise { - return hasFields(req, [ "studentId", "roles" ], types.id) - .then(() => Promise.resolve({ - sessionkey : req.body.sessionkey, - id : Number(req.params.id), - studentId : req.body.studentId, - roles : req.body.roles - })); +export async function parseDraftStudentRequest( + req: express.Request +): Promise { + return hasFields(req, ["studentId", "role"], types.id).then(() => + Promise.resolve({ + sessionkey: getSessionKey(req), + id: Number(req.params.id), + studentId: req.body.studentId, + role: req.body.role, + }) + ); } /** @@ -331,19 +630,26 @@ export async function parseDraftStudentRequest(req: express.Request): * @returns A Promise resolving to the parsed data or rejecting with an * Argument or Unauthenticated error. */ -export async function parseSetFollowupStudentRequest(req: express.Request): - Promise { - return hasFields(req, [ "type" ], types.id).then(() => { - const type = req.body.type; - if (type != "hold-tight" && type != "confirmed" && type != "cancelled") - return rejector(); +export async function parseSetFollowupStudentRequest( + req: express.Request +): Promise { + return hasFields(req, ["type"], types.id).then(() => { + const type: string = req.body.type; + if ( + type != "SCHEDULED" && + type != "SENT" && + type != "FAILED" && + type != "NONE" && + type != "DRAFT" + ) + return rejector(); - return Promise.resolve({ - sessionkey : req.body.sessionkey, - id : Number(req.params.id), - type : type + return Promise.resolve({ + sessionkey: getSessionKey(req), + id: Number(req.params.id), + type: type as FollowupType, + }); }); - }); } /** @@ -352,17 +658,18 @@ export async function parseSetFollowupStudentRequest(req: express.Request): * @returns A Promise resolving to the parsed data or rejecting with an * Argument or Unauthenticated error. */ -export async function parseNewTemplateRequest(req: express.Request): - Promise { - return hasFields(req, [ "name", "content" ], types.key) - .then(() => Promise.resolve({ - sessionkey : req.body.sessionkey, - name : req.body.name, - subject : maybe(req.body, "subject"), - desc : maybe(req.body, "desc"), - cc : maybe(req.body, "cc"), - content : req.body.content - })); +export async function parseNewTemplateRequest( + req: express.Request +): Promise { + return hasFields(req, ["name", "content"], types.key).then(() => + Promise.resolve({ + sessionkey: getSessionKey(req), + name: req.body.name, + subject: maybe(req.body, "subject"), + cc: maybe(req.body, "cc"), + content: req.body.content, + }) + ); } /** @@ -371,22 +678,22 @@ export async function parseNewTemplateRequest(req: express.Request): * @returns A Promise resolving to the parsed data or rejecting with an * Argument or Unauthenticated error. */ -export async function parseUpdateTemplateRequest(req: express.Request): - Promise { - return hasFields(req, [], types.id).then(() => { - if (!atLeastOneField(req, [ "name", "desc", "subject", "cc", "content" ])) - return rejector(); +export async function parseUpdateTemplateRequest( + req: express.Request +): Promise { + return hasFields(req, [], types.id).then(() => { + if (!atLeastOneField(req, ["name", "subject", "cc", "content"])) + return rejector(); - return Promise.resolve({ - sessionkey : req.body.sessionkey, - id : Number(req.params.id), - name : maybe(req.body, "name"), - desc : maybe(req.body, "desc"), - subject : maybe(req.body, "subject"), - cc : maybe(req.body, "cc"), - content : maybe(req.body, "content") + return Promise.resolve({ + sessionkey: getSessionKey(req), + id: Number(req.params.id), + name: maybe(req.body, "name"), + subject: maybe(req.body, "subject"), + cc: maybe(req.body, "cc"), + content: maybe(req.body, "content"), + }); }); - }); } /** @@ -395,18 +702,145 @@ export async function parseUpdateTemplateRequest(req: express.Request): * @returns A Promise resolving to the parsed data or rejecting with an * Argument or Unauthenticated error. */ -export async function parseFormRequest(req: express.Request): - Promise { - return hasFields(req, [ "eventId", "eventType", "createdAt", "data" ], - types.neither) - .then(() => { +export async function parseFormRequest( + req: express.Request +): Promise { + return hasFields(req, ["data"], types.neither).then(() => { + if ( + req.body.data.fields === undefined || + req.body.data.fields === null + ) { + return rejector(); + } + for (const question of req.body.data.fields) { + if ( + question.key === undefined || + question.key === null || + question.value === undefined + ) { + return rejector(); + } + } return Promise.resolve({ - eventId : req.body.eventId, - eventType : req.body.eventType, - createdAt : req.body.createdAt, - data : req.body.data + createdAt: maybe(req.body, "createdAt"), + data: req.body.data, }); - }); + }); +} + +export async function parseRequestResetRequest( + req: express.Request +): Promise { + return hasFields(req, ["email"], types.neither).then(() => + Promise.resolve({ + email: validator.default.normalizeEmail(req.body.email).toString(), + }) + ); +} + +export async function parseCheckResetCodeRequest( + req: express.Request +): Promise { + if (!("id" in req.params)) + return Promise.reject(errors.cookArgumentError()); + return Promise.resolve({ code: req.params.id }); +} + +export async function parseResetPasswordRequest( + req: express.Request +): Promise { + if (!("id" in req.params) || !("password" in req.body)) + return Promise.reject(errors.cookArgumentError()); + return Promise.resolve({ + code: req.params.id, + password: req.body.password, + }); +} + +/** + * Parses a request to `POST /student/role`. + * @param req The request to check. + * @returns A Promise resolving to the parsed data or rejecting with an + * Argument or Unauthenticated error. + */ +export async function parseStudentRoleRequest( + req: express.Request +): Promise { + return hasFields(req, ["name"], types.neither).then(() => + Promise.resolve({ + sessionkey: getSessionKey(req), + name: req.body.name, + }) + ); +} + +export async function parseRemoveAssigneeRequest( + req: express.Request +): Promise { + return hasFields(req, ["student"], types.id).then(() => + Promise.resolve({ + sessionkey: getSessionKey(req), + studentId: req.body.student, + id: Number(req.params.id), + }) + ); +} + +export async function parseUserModSelfRequest( + req: express.Request +): Promise { + return hasFields(req, [], types.key).then(() => { + if ("pass" in req.body) { + try { + if ( + !("oldpass" in req.body.pass) || + !("newpass" in req.body.pass) + ) { + return rejector(); + } + } catch (e) { + return rejector(); + } + } + return Promise.resolve({ + sessionkey: getSessionKey(req), + pass: maybe(req.body, "pass") as { + oldpass: string; + newpass: string; + }, + name: maybe(req.body, "name") as string, + }); + }); +} + +/** + * Parses a request requiring both a key and an ID. + * @param req The request to check. + * @returns A Promise resolving to the parsed data or rejecting with an + * Argument or Unauthenticated error. + */ +export async function parseAcceptNewUserRequest( + req: express.Request +): Promise { + return hasFields(req, ["is_admin", "is_coach"], types.id).then(() => + Promise.resolve({ + sessionkey: getSessionKey(req), + id: Number(req.params.id), + is_admin: req.body.is_admin, + is_coach: req.body.is_coach, + }) + ); +} + +export async function parseNewOsocEditionRequest( + req: express.Request +): Promise { + return hasFields(req, ["year"], types.neither).then(() => + Promise.resolve({ + sessionkey: getSessionKey(req), + year: parseInt(req.body.year), + }) + ); } /** @@ -419,6 +853,11 @@ export const parseLogoutRequest = parseKeyRequest; * {@link parseKeyRequest}. */ export const parseStudentAllRequest = parseKeyRequest; +/** + * A request to `GET /roles/all` only requires a session key + * {@link parseKeyRequest}. + */ +export const parseRolesAllRequest = parseKeyRequest; /** * A request to `GET /coach/all` only requires a session key * {@link parseKeyRequest}. @@ -434,6 +873,11 @@ export const parseGetAllCoachRequestsRequest = parseKeyRequest; * {@link parseKeyRequest}. */ export const parseAdminAllRequest = parseKeyRequest; +/** + * A request to `GET /user/all` only requires a session key + * {@link parseKeyRequest}. + */ +export const parseUserAllRequest = parseKeyRequest; /** * A request to `GET /project/all` only requires a session key * {@link parseKeyRequest}. @@ -454,6 +898,21 @@ export const parseFollowupAllRequest = parseKeyRequest; * {@link parseKeyRequest}. */ export const parseTemplateListRequest = parseKeyRequest; +/** + * A request to `GET /project/conflicts` only requires a session key + * {@link parseKeyRequest} + */ +export const parseProjectConflictsRequest = parseKeyRequest; +/** + * A request to `GET /user/current` only requires a session key + * {@link parseKeyRequest}. + */ +export const parseCurrentUserRequest = parseKeyRequest; +/** + * A request to `GET /verify` only requires a session key + * {@link parseKeyRequest}. + */ +export const parseVerifyRequest = parseKeyRequest; /** * A request to `GET /student/` only requires a session key and an ID @@ -465,12 +924,6 @@ export const parseSingleStudentRequest = parseKeyIdRequest; * {@link parseKeyIdRequest}. */ export const parseDeleteStudentRequest = parseKeyIdRequest; -/** - * A request to `GET /student//suggest` only requires a session key and an - * ID - * {@link parseKeyIdRequest}. - */ -export const parseStudentGetSuggestsRequest = parseKeyIdRequest; /** * A request to `GET /coach/` only requires a session key and an ID * {@link parseKeyIdRequest}. @@ -486,11 +939,6 @@ export const parseDeleteCoachRequest = parseKeyIdRequest; * {@link parseKeyIdRequest}. */ export const parseGetCoachRequestRequest = parseKeyIdRequest; -/** - * A request to `POST /coach/request/` only requires a session key and an - * ID {@link parseKeyIdRequest}. - */ -export const parseAcceptNewCoachRequest = parseKeyIdRequest; /** * A request to `DELETE /coach/request/` only requires a session key and an * ID @@ -548,3 +996,19 @@ export const parseUpdateCoachRequest = parseUpdateLoginUser; * {@link parseUpdateLoginUser}. */ export const parseUpdateAdminRequest = parseUpdateLoginUser; +/** + * A request to `GET /osoc/all` only requires a session key + * {@link parseKeyRequest}. + */ +export const parseOsocAllRequest = parseKeyRequest; +/** + * Parses a request to `POST /osoc/`. + * @param req The request to check. + * @returns A Promise resolving to the parsed data or rejecting with an + * Argument or Unauthenticated error. + */ +/** + * A request to `DELETE /osoc/` only requires a session key and an ID + * {@link parseKeyIdRequest}. + */ +export const parseDeleteOsocEditionRequest = parseKeyIdRequest; diff --git a/backend/routes/admin.ts b/backend/routes/admin.ts index dc2ffb06..9fbd4e6b 100644 --- a/backend/routes/admin.ts +++ b/backend/routes/admin.ts @@ -1,10 +1,14 @@ -import {account_status_enum} from '@prisma/client'; -import express from 'express'; +import { account_status_enum } from "@prisma/client"; +import express from "express"; import * as ormL from "../orm_functions/login_user"; -import * as rq from '../request'; -import {Responses} from '../types'; -import * as util from '../utility'; +import * as ormP from "../orm_functions/person"; +import * as ormSe from "../orm_functions/session_key"; +import * as rq from "../request"; +import { Responses } from "../types"; +import * as util from "../utility"; +import { errors } from "../utility"; +import { removeAllKeysForLoginUserId } from "../orm_functions/session_key"; /** * Attempts to list all admins in the system. @@ -12,61 +16,85 @@ import * as util from '../utility'; * @returns See the API documentation. Successes are passed using * `Promise.resolve`, failures using `Promise.reject`. */ -async function listAdmins(req: express.Request): Promise { - return rq.parseAdminAllRequest(req) - .then(parsed => util.checkSessionKey(parsed)) - .then( - async parsed => - ormL.searchAllCoachLoginUsers(true) - .then(obj => - obj.map(val => ({ - person_data : { - id : val.person.person_id, - name : val.person.firstname + " " + - val.person.lastname - }, - coach : val.is_coach, - admin : val.is_admin, - activated : val.account_status as string - }))) - .then( - obj => Promise.resolve( - {sessionkey : parsed.data.sessionkey, data : obj}))); +export async function listAdmins( + req: express.Request +): Promise { + return rq + .parseAdminAllRequest(req) + .then((parsed) => util.isAdmin(parsed)) + .then(async () => + ormL + .searchAllAdminLoginUsers(true) + .then((obj) => + obj.map((val) => ({ + person_data: { + id: val.person.person_id, + name: + val.person.firstname + + " " + + val.person.lastname, + email: val.person.email, + github: val.person.github, + }, + coach: val.is_coach, + admin: val.is_admin, + activated: val.account_status as string, + })) + ) + .then((obj) => + Promise.resolve({ + data: obj, + }) + ) + ); } /** - * Attempts to get all data for a certain admin in the system. + * Attempts to update a certain admin in the system. * @param req The Express.js request to extract all required data from. * @returns See the API documentation. Successes are passed using * `Promise.resolve`, failures using `Promise.reject`. */ -async function getAdmin(req: express.Request): Promise { - return rq.parseSingleAdminRequest(req) - .then(parsed => util.isAdmin(parsed)) - .then(() => - Promise.reject({http : 410, reason : 'Deprecated endpoint.'})); -} - -async function modAdmin(req: express.Request): Promise { - return rq.parseUpdateAdminRequest(req) - .then(parsed => util.checkSessionKey(parsed)) - .then(async parsed => { - return ormL - .updateLoginUser({ - loginUserId : parsed.data.id, - password : parsed.data.pass, - isAdmin : parsed.data.isAdmin, - isCoach : parsed.data.isCoach, - accountStatus : parsed.data.accountStatus as account_status_enum - }) - .then(res => Promise.resolve({ - sessionkey : parsed.data.sessionkey, - data : { - id : res.person_id, - name : res.person.firstname + " " + res.person.lastname - } - })); - }); +export async function modAdmin(req: express.Request): Promise { + return rq + .parseUpdateAdminRequest(req) + .then((parsed) => util.isAdmin(parsed)) + .then((parsed) => util.mutable(parsed, parsed.data.id)) + .then(async (parsed) => { + if (parsed.data.id !== parsed.userId) { + return ormL + .updateLoginUser({ + loginUserId: parsed.data.id, + isAdmin: parsed.data.isAdmin, + isCoach: parsed.data.isCoach, + accountStatus: parsed.data + .accountStatus as account_status_enum, + }) + .then(async (res) => { + console.log(res.is_admin); + console.log(res.is_coach); + if (!res.is_admin && !res.is_coach) { + await ormL.updateLoginUser({ + loginUserId: res.login_user_id, + isAdmin: false, + isCoach: false, + accountStatus: account_status_enum.DISABLED, + }); + await ormSe.removeAllKeysForLoginUserId( + res.login_user_id + ); + } + return Promise.resolve({ + id: res.login_user_id, + name: + res.person.firstname + + " " + + res.person.lastname, + }); + }); + } + return Promise.reject(errors.cookInvalidID()); + }); } /** @@ -75,32 +103,66 @@ async function modAdmin(req: express.Request): Promise { * @returns See the API documentation. Successes are passed using * `Promise.resolve`, failures using `Promise.reject`. */ -async function deleteAdmin(req: express.Request): Promise { - return rq.parseDeleteAdminRequest(req) - .then(parsed => util.isAdmin(parsed)) - .then(async parsed => { - return ormL.deleteLoginUserByPersonId(parsed.data.id) - .then(() => Promise.resolve({sessionkey : parsed.data.sessionkey})); - }); +export async function deleteAdmin( + req: express.Request +): Promise { + return rq + .parseDeleteAdminRequest(req) + .then((parsed) => util.isAdmin(parsed)) + .then((parsed) => util.mutable(parsed, parsed.data.id)) + .then(async (parsed) => { + return ormL + .searchLoginUserByPerson(parsed.data.id) + .then((logUs) => { + if ( + logUs !== null && + logUs.login_user_id !== parsed.userId + ) { + return removeAllKeysForLoginUserId(logUs.login_user_id) + .then(() => { + return Promise.resolve({}); + }) + .then(() => { + return ormL + .deleteLoginUserByPersonId(parsed.data.id) + .then(() => { + return ormP + .deletePersonById(parsed.data.id) + .then(() => Promise.resolve({})) + .catch(() => + Promise.reject( + errors.cookServerError() + ) + ); + }) + .catch(() => + Promise.reject(errors.cookServerError()) + ); + }) + .catch(() => + Promise.reject(errors.cookServerError()) + ); + } + return Promise.reject(errors.cookInvalidID()); + }); + }); } /** * Gets the router for all `/admin/` related endpoints. - * @returns An Epress.js {@link express.Router} routing all `/admin/` + * @returns An Express.js {@link express.Router} routing all `/admin/` * endpoints. */ export function getRouter(): express.Router { - const router: express.Router = express.Router(); + const router: express.Router = express.Router(); - util.setupRedirect(router, '/admin'); - util.route(router, "get", "/all", listAdmins); - util.route(router, "get", "/:id", getAdmin); + util.setupRedirect(router, "/admin"); + util.route(router, "get", "/all", listAdmins); - util.route(router, "post", "/:id", modAdmin); - router.delete('/:id', (req, res) => - util.respOrErrorNoReinject(res, deleteAdmin(req))); + util.route(router, "post", "/:id", modAdmin); + util.route(router, "delete", "/:id", deleteAdmin); - util.addAllInvalidVerbs(router, [ "/", "/all", "/:id" ]); + util.addAllInvalidVerbs(router, ["/", "/all", "/:id"]); - return router; + return router; } diff --git a/backend/routes/coach.ts b/backend/routes/coach.ts index 291300c2..3bf0d3f1 100644 --- a/backend/routes/coach.ts +++ b/backend/routes/coach.ts @@ -1,11 +1,15 @@ -import {account_status_enum} from '@prisma/client'; -import express from 'express'; - -import * as ormLU from '../orm_functions/login_user'; -import * as ormP from '../orm_functions/person'; -import * as rq from '../request'; -import {InternalTypes, Responses} from '../types'; -import * as util from '../utility'; +import { account_status_enum } from "@prisma/client"; +import express from "express"; + +import * as ormLU from "../orm_functions/login_user"; +import * as ormP from "../orm_functions/person"; +import * as rq from "../request"; +import { Responses } from "../types"; +import * as util from "../utility"; +import * as ormSe from "../orm_functions/session_key"; +import { errors } from "../utility"; +import * as ormL from "../orm_functions/login_user"; +import { removeAllKeysForLoginUserId } from "../orm_functions/session_key"; /** * Attempts to list all coaches in the system. @@ -13,40 +17,34 @@ import * as util from '../utility'; * @returns See the API documentation. Successes are passed using * `Promise.resolve`, failures using `Promise.reject`. */ -async function listCoaches(req: express.Request): Promise { - return rq.parseCoachAllRequest(req) - .then(parsed => util.checkSessionKey(parsed)) - .then( - async parsed => - ormLU.searchAllCoachLoginUsers(true) - .then(obj => - obj.map(val => ({ - person_data : { - id : val.person.person_id, - name : val.person.firstname + " " + - val.person.lastname - }, - coach : val.is_coach, - admin : val.is_admin, - activated : val.account_status as string - }))) - .then( - obj => Promise.resolve( - {sessionkey : parsed.data.sessionkey, data : obj}))); -} - -/** - * Attempts to get all data for a certain coach in the system. - * @param req The Express.js request to extract all required data from. - * @returns See the API documentation. Successes are passed using - * `Promise.resolve`, failures using `Promise.reject`. - */ -async function getCoach(req: express.Request): Promise { - return rq.parseSingleCoachRequest(req) - .then(parsed => util.checkSessionKey(parsed)) - .then(() => { - return Promise.reject({http : 410, reason : 'Deprecated endpoint.'}); - }); +export async function listCoaches( + req: express.Request +): Promise { + return rq + .parseCoachAllRequest(req) + .then((parsed) => util.isAdmin(parsed)) + .then(async () => + ormLU + .searchAllCoachLoginUsers(true) + .then((obj) => + obj.map((val) => ({ + person_data: { + id: val.person.person_id, + name: val.person.firstname, + email: val.person.email, + github: val.person.github, + }, + coach: val.is_coach, + admin: val.is_admin, + activated: val.account_status as string, + })) + ) + .then((obj) => + Promise.resolve({ + data: obj, + }) + ) + ); } /** @@ -55,27 +53,47 @@ async function getCoach(req: express.Request): Promise { * @returns See the API documentation. Successes are passed using * `Promise.resolve`, failures using `Promise.reject`. */ -async function modCoach(req: express.Request): - Promise> { - return rq.parseUpdateCoachRequest(req) - .then(parsed => util.checkSessionKey(parsed)) - .then(async parsed => { - return ormLU - .updateLoginUser({ - loginUserId : parsed.data.id, - password : parsed.data.pass, - isAdmin : parsed.data.isAdmin, - isCoach : parsed.data.isCoach, - accountStatus : parsed.data.accountStatus as account_status_enum - }) - .then(res => Promise.resolve({ - sessionkey : parsed.data.sessionkey, - data : { - id : res.person_id, - name : res.person.firstname + " " + res.person.lastname - } - })); - }); +export async function modCoach( + req: express.Request +): Promise { + return rq + .parseUpdateCoachRequest(req) + .then((parsed) => util.isAdmin(parsed)) + .then((parsed) => util.mutable(parsed, parsed.data.id)) + .then(async (parsed) => { + if (parsed.data.id !== parsed.userId) { + return ormLU + .updateLoginUser({ + loginUserId: parsed.data.id, + isAdmin: parsed.data.isAdmin, + isCoach: parsed.data.isCoach, + accountStatus: parsed.data + .accountStatus as account_status_enum, + }) + .then(async (res) => { + if (!res.is_admin && !res.is_coach) { + await ormSe.removeAllKeysForLoginUserId( + res.login_user_id + ); + await ormL.updateLoginUser({ + loginUserId: res.login_user_id, + isAdmin: false, + isCoach: false, + accountStatus: account_status_enum.DISABLED, + }); + } + return Promise.resolve({ + id: res.login_user_id, + name: + res.person.firstname + + " " + + res.person.lastname, + }); + }); + } else { + return Promise.reject(errors.cookInvalidID()); + } + }); } /** @@ -84,13 +102,49 @@ async function modCoach(req: express.Request): * @returns See the API documentation. Successes are passed using * `Promise.resolve`, failures using `Promise.reject`. */ -async function deleteCoach(req: express.Request): Promise { - return rq.parseDeleteCoachRequest(req) - .then(parsed => util.isAdmin(parsed)) - .then(async parsed => { - return ormLU.deleteLoginUserByPersonId(parsed.data.id) - .then(() => Promise.resolve({sessionkey : parsed.data.sessionkey})); - }); +export async function deleteCoach( + req: express.Request +): Promise { + return rq + .parseDeleteCoachRequest(req) + .then((parsed) => util.isAdmin(parsed)) + .then((parsed) => util.mutable(parsed, parsed.data.id)) + .then(async (parsed) => { + return ormL + .searchLoginUserByPerson(parsed.data.id) + .then((logUs) => { + if ( + logUs !== null && + logUs.login_user_id !== parsed.userId + ) { + return removeAllKeysForLoginUserId(logUs.login_user_id) + .then(() => { + return Promise.resolve({}); + }) + .then(() => { + return ormL + .deleteLoginUserByPersonId(parsed.data.id) + .then(() => { + return ormP + .deletePersonById(parsed.data.id) + .then(() => Promise.resolve({})) + .catch(() => + Promise.reject( + errors.cookServerError() + ) + ); + }) + .catch(() => + Promise.reject(errors.cookServerError()) + ); + }) + .catch(() => + Promise.reject(errors.cookServerError()) + ); + } + return Promise.reject(errors.cookInvalidID()); + }); + }); } /** @@ -99,127 +153,36 @@ async function deleteCoach(req: express.Request): Promise { * @returns See the API documentation. Successes are passed using * `Promise.resolve`, failures using `Promise.reject`. */ -async function getCoachRequests(req: express.Request): - Promise { - return rq.parseGetAllCoachRequestsRequest(req) - .then(parsed => util.isAdmin(parsed)) - .then(async parsed => { - return ormLU.getAllLoginUsers() - .then(obj => obj.filter(v => v.is_coach && - v.account_status == 'PENDING') - .map(v => ({ - person_data : { - id : v.person.person_id, - name : v.person.firstname + " " + - v.person.lastname - }, - coach : v.is_coach, - admin : v.is_admin, - activated : v.account_status as string - }))) - .then(arr => Promise.resolve( - {sessionkey : parsed.data.sessionkey, data : arr})); - }); -} - -/** - * Attempts to create a new user in the system. - * @param req The Express.js request to extract all required data from. - * @returns See the API documentation. Successes are passed using - * `Promise.resolve`, failures using `Promise.reject`. - */ -async function createCoachRequest(req: express.Request): - Promise { - return rq.parseRequestCoachRequest(req).then(async parsed => { - if (parsed.pass == undefined) { - console.log(" -> WARNING coach request without password - " + - "currently only accepting email-based applications."); - return Promise.reject(util.errors.cookArgumentError()); - } - return ormP - .createPerson({ - firstname : parsed.firstName, - lastname : parsed.lastName, - email : parsed.emailOrGithub - }) - .then(person => { - console.log("Created a person: " + person); - return ormLU.createLoginUser({ - personId : person.person_id, - password : parsed.pass, - isAdmin : false, - isCoach : true, - accountStatus : 'PENDING' - }) - }) - .then(user => { - console.log("Attached a login user: " + user); - return Promise.resolve({id : user.login_user_id}); +export async function getCoachRequests( + req: express.Request +): Promise { + return rq + .parseGetAllCoachRequestsRequest(req) + .then((parsed) => util.isAdmin(parsed)) + .then(async () => { + return ormLU + .getAllLoginUsers() + .then((obj) => + obj + .filter( + (v) => v.is_coach && v.account_status == "PENDING" + ) + .map((v) => ({ + person_data: { + id: v.person.person_id, + name: v.person.firstname, + }, + coach: v.is_coach, + admin: v.is_admin, + activated: v.account_status as string, + })) + ) + .then((arr) => + Promise.resolve({ + data: arr, + }) + ); }); - }); -} - -/** - * Attempts to get the details of a coach request in the system. - * @param req The Express.js request to extract all required data from. - * @returns See the API documentation. Successes are passed using - * `Promise.resolve`, failures using `Promise.reject`. - */ -async function getCoachRequest(req: express.Request): - Promise> { - return rq.parseGetCoachRequestRequest(req) - .then(parsed => util.isAdmin(parsed)) - .then(() => { - return Promise.reject({http : 410, reason : 'Deprecated endpoint.'}); - }); -} - -async function setAccountStatus(lu_id: number, stat: account_status_enum, - key: string): - Promise> { - return ormLU.searchLoginUserByPerson(lu_id) - .then(obj => obj == null ? Promise.reject(util.errors.cookInvalidID()) - : ormLU.updateLoginUser({ - loginUserId : obj.login_user_id, - isAdmin : obj.is_admin, - isCoach : obj.is_coach, - accountStatus : stat - })) - .then(res => Promise.resolve({ - sessionkey : key, - data : { - id : res.person_id, - name : res.person.firstname + " " + res.person.lastname - } - })); -} - -/** - * Attempts to accept a request for becoming a coach. - * @param req The Express.js request to extract all required data from. - * @returns See the API documentation. Successes are passed using - * `Promise.resolve`, failures using `Promise.reject`. - */ -async function createCoachAcceptance(req: express.Request): - Promise> { - return rq.parseAcceptNewCoachRequest(req) - .then(parsed => util.isAdmin(parsed)) - .then(async parsed => setAccountStatus(parsed.data.id, 'ACTIVATED', - parsed.data.sessionkey)); -} - -/** - * Attempts to deny a request for becoming a coach. - * @param req The Express.js request to extract all required data from. - * @returns See the API documentation. Successes are passed using - * `Promise.resolve`, failures using `Promise.reject`. - */ -async function deleteCoachRequest(req: express.Request): - Promise { - return rq.parseAcceptNewCoachRequest(req) - .then(parsed => util.isAdmin(parsed)) - .then(async parsed => setAccountStatus(parsed.data.id, 'DISABLED', - parsed.data.sessionkey)); } /** @@ -228,27 +191,16 @@ async function deleteCoachRequest(req: express.Request): * endpoints. */ export function getRouter(): express.Router { - const router: express.Router = express.Router({strict : true}); - util.setupRedirect(router, '/coach'); - util.route(router, "get", "/all", listCoaches); - - util.route(router, "get", "/request", getCoachRequests); - router.post('/request', (req, res) => util.respOrErrorNoReinject( - res, createCoachRequest(req))); - util.route(router, "get", "/request/:id", getCoachRequest); - - util.route(router, "post", "/request/:id", createCoachAcceptance); - router.delete('/request/:id', (req, res) => util.respOrErrorNoReinject( - res, deleteCoachRequest(req))); + const router: express.Router = express.Router({ strict: true }); + util.setupRedirect(router, "/coach"); + util.route(router, "get", "/all", listCoaches); - util.route(router, "get", "/:id", getCoach); + util.route(router, "get", "/request", getCoachRequests); - util.route(router, "post", "/:id", modCoach); - router.delete('/:id', (req, res) => - util.respOrErrorNoReinject(res, deleteCoach(req))); + util.route(router, "post", "/:id", modCoach); + util.route(router, "delete", "/:id", deleteCoach); - util.addAllInvalidVerbs(router, - [ "/", "/all", "/:id", "/request", "/request/:id" ]); + util.addAllInvalidVerbs(router, ["/", "/all", "/:id", "/request"]); - return router; + return router; } diff --git a/backend/routes/followup.ts b/backend/routes/followup.ts new file mode 100644 index 00000000..56079d50 --- /dev/null +++ b/backend/routes/followup.ts @@ -0,0 +1,113 @@ +import express from "express"; + +import * as ormJA from "../orm_functions/job_application"; +import * as ormOsoc from "../orm_functions/osoc"; +import * as rq from "../request"; +import { Responses } from "../types"; +import * as util from "../utility"; + +/** + * Attempts to list all followups in the system. + * @param req The Express.js request to extract all required data from. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export async function listFollowups( + req: express.Request +): Promise { + return rq + .parseFollowupAllRequest(req) + .then((parsed) => util.checkSessionKey(parsed)) + .then(() => + ormOsoc + .getLatestOsoc() + .then((osoc) => util.getOrReject(osoc)) + .then(async (osoc) => + ormJA + .getJobApplicationByYear(osoc.year) + .then((arr) => + arr.map((v) => ({ + student: v.student_id, + application: v.job_application_id, + status: v.email_status, + })) + ) + .then((res) => + Promise.resolve({ + data: res, + }) + ) + ) + ); +} + +/** + * Attempts to get a single followup. + * @param req The Express.js request to extract all required data from. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export async function getFollowup( + req: express.Request +): Promise { + return rq + .parseGetFollowupStudentRequest(req) + .then((parsed) => util.checkSessionKey(parsed)) + .then((checked) => + ormJA + .getLatestJobApplicationOfStudent(checked.data.id) + .then((data) => util.getOrReject(data)) + .then((ja) => + Promise.resolve({ + student: ja.student_id, + application: ja.job_application_id, + status: ja.email_status, + }) + ) + ); +} + +/** + * Attempts to update a single followup. + * @param req The Express.js request to extract all required data from. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export async function updateFollowup( + req: express.Request +): Promise { + return rq + .parseSetFollowupStudentRequest(req) + .then((parsed) => util.isAdmin(parsed)) + .then((checked) => + ormJA + .getLatestJobApplicationOfStudent(checked.data.id) + .then((ja) => util.getOrReject(ja)) + .then((ja) => + ormJA.changeEmailStatusOfJobApplication( + ja.job_application_id, + checked.data.type + ) + ) + .then((res) => + Promise.resolve({ + student: res.student_id, + application: res.job_application_id, + status: res.email_status, + }) + ) + ); +} + +export function getRouter() { + const router: express.Router = express.Router(); + + util.setupRedirect(router, "/followup"); + util.route(router, "get", "/all", listFollowups); + util.route(router, "get", "/:id", getFollowup); + util.route(router, "post", "/:id", updateFollowup); + + util.addAllInvalidVerbs(router, ["/", "/all"]); + + return router; +} diff --git a/backend/routes/form.ts b/backend/routes/form.ts index ac6ccae5..933a4c3d 100644 --- a/backend/routes/form.ts +++ b/backend/routes/form.ts @@ -1,32 +1,149 @@ -import express from 'express'; +/* eslint-disable @typescript-eslint/no-unused-vars */ +import express from "express"; -import * as ormP from '../orm_functions/person'; -import * as ormSt from '../orm_functions/student'; -import * as rq from '../request'; -import {Requests, Responses} from '../types'; +import * as ormP from "../orm_functions/person"; +import * as ormSt from "../orm_functions/student"; +import * as ormOs from "../orm_functions/osoc"; +import * as ormJo from "../orm_functions/job_application"; +import * as ormJoSk from "../orm_functions/job_application_skill"; +import * as ormLa from "../orm_functions/language"; +import * as ormAtt from "../orm_functions/attachment"; +import * as ormRo from "../orm_functions/role"; +import * as ormAppRo from "../orm_functions/applied_role"; +import { Requests, Responses } from "../types"; import * as util from "../utility"; +import { errors } from "../utility"; +import { type_enum } from "@prisma/client"; +import * as validator from "validator"; +import * as rq from "../request"; +import * as config from "./form_keys.json"; /** * This function searches a question with a given key in the form. * @param form The form with the answers. + * @param key The key of the question. * @returns The question that corresponds with the given key. */ -function filterQuestion(form: Requests.Form, key: string): Requests.Question { - return form.data.fields.filter(question => question.key == key)[0]; +export function filterQuestion( + form: Requests.Form, + key: string +): Responses.FormResponse { + const filteredQuestion = form.data.fields.filter( + (question) => question.key == key + ); + return filteredQuestion.length > 0 + ? { data: filteredQuestion[0] } + : { data: null }; } /** - * This function checks if the answer on a certain question is 'yes' or 'no'. - * @param question The question with 'yes' and 'no' as possible answers. - * @returns A boolean that answers the question. + * This function searches the chosen option for a given question. + * @param question The question. + * @returns The option that corresponds with the given answer. */ -function checkYesAnswer(question: Requests.Question): boolean { - if (question.options !== undefined) { - return question.options.filter(option => option.id === question.value)[0] - .text.toLowerCase() - .includes("yes"); - } - return false; +export function filterChosenOption( + question: Requests.Question +): Responses.FormResponse { + if (question.options != undefined) { + const filteredOption = question.options.filter( + (option) => + option.id != undefined && + option.text != undefined && + option.id === question.value + ); + if (filteredOption.length === 0) { + return { data: null }; + } + return { data: filteredOption[0] }; + } + return { data: null }; +} + +/** + * This function checks if the answer on a certain question contains a word. + * @param question The question. + * @param word the word we are searching for in the answer. + * @returns True if the question options contain the word, else false. + */ +export function checkWordInAnswer( + question: Requests.Question, + word: string +): Responses.FormResponse { + const chosenOption: Responses.FormResponse = + filterChosenOption(question); + return chosenOption.data != null + ? { data: chosenOption.data.text.toLowerCase().includes(word) } + : { data: null }; +} + +export function checkQuestionsExist( + questions: Responses.FormResponse[] +): boolean { + const checkErrorInForm: Responses.FormResponse[] = + questions.filter((dataError) => dataError.data == null); + return checkErrorInForm.length === 0; +} + +/* parse form to person + ***********************/ + +/** + * Parse the form to the birth name of this student. + * @param form The form with the answers. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export function getBirthName(form: Requests.Form): Promise { + const questionBirthName: Responses.FormResponse = + filterQuestion(form, config.birthName); + const questionsExist: boolean = checkQuestionsExist([questionBirthName]); + if (!questionsExist || questionBirthName.data?.value == null) { + return Promise.reject(errors.cookArgumentError()); + } + + return Promise.resolve(questionBirthName.data.value as string); +} + +/** + * Parse the form to the last name of this student. + * @param form The form with the answers. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export function getLastName(form: Requests.Form): Promise { + const questionLastName: Responses.FormResponse = + filterQuestion(form, config.lastName); + const questionsExist: boolean = checkQuestionsExist([questionLastName]); + if (!questionsExist || questionLastName.data?.value == null) { + return Promise.reject(errors.cookArgumentError()); + } + + return Promise.resolve(questionLastName.data.value as string); +} + +/** + * Parse the form to the email of this student. + * @param form The form with the answers. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export function getEmail(form: Requests.Form): Promise { + const questionEmail: Responses.FormResponse = + filterQuestion(form, config.emailAddress); + const questionsExist: boolean = checkQuestionsExist([questionEmail]); + if ( + !questionsExist || + questionEmail.data?.value == null || + !validator.default.isEmail(questionEmail.data.value as string) + ) { + return Promise.reject(errors.cookArgumentError()); + } + + return Promise.resolve( + validator.default + .normalizeEmail(questionEmail.data.value as string) + .toString() + ); } /** @@ -35,33 +152,185 @@ function checkYesAnswer(question: Requests.Question): boolean { * @returns See the API documentation. Successes are passed using * `Promise.resolve`, failures using `Promise.reject`. */ -async function jsonToPerson(form: Requests.Form): Promise { - const questionBirthName: Requests.Question = - filterQuestion(form, "question_npDErJ"); - const questionLastName: Requests.Question = - filterQuestion(form, "question_319eXp"); - const questionEmail: Requests.Question = - filterQuestion(form, "question_mY46PB"); +export async function jsonToPerson( + form: Requests.Form +): Promise { + const birthName = await getBirthName(form); + const lastName = await getLastName(form); + const email = await getEmail(form); + + return Promise.resolve({ + birthName: birthName, + lastName: lastName, + email: email, + }); +} + +/* parse form to student + ************************/ + +/** + * Parse the form to the pronouns of this student. + * @param form The form with the answers. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export function getPronouns(form: Requests.Form): Promise { + const questionPronouns: Responses.FormResponse = + filterQuestion(form, config.addPronouns); + const questionPreferredPronouns: Responses.FormResponse = + filterQuestion(form, config.preferredPronouns); + const questionEnterPronouns: Responses.FormResponse = + filterQuestion(form, config.pronounsInput); + + const questionsExist: boolean = checkQuestionsExist([ + questionPronouns, + questionPreferredPronouns, + questionEnterPronouns, + ]); + if (!questionsExist || questionPronouns.data?.value == null) { + return Promise.reject(errors.cookArgumentError()); + } + + let pronouns = ""; + + const wordInAnswer: Responses.FormResponse = checkWordInAnswer( + questionPronouns.data, + "yes" + ); + + if (wordInAnswer.data == null) { + return Promise.resolve(null); + } + + if (wordInAnswer.data) { + const chosenOption: Responses.FormResponse = + filterChosenOption( + questionPreferredPronouns.data as Requests.Question + ); + if ( + chosenOption.data == null || + chosenOption.data?.id.length === 0 || + questionPreferredPronouns.data?.value == null || + checkWordInAnswer(questionPreferredPronouns.data, "other").data == + null + ) { + return Promise.reject(util.errors.cookArgumentError()); + } else if ( + !checkWordInAnswer(questionPreferredPronouns.data, "other").data && + chosenOption.data?.text != undefined + ) { + pronouns = chosenOption.data.text; + } else { + if (questionEnterPronouns.data?.value == null) { + return Promise.resolve(null); + } + pronouns = questionEnterPronouns.data.value as string; + } + } + + return Promise.resolve(pronouns); +} + +/** + * Parse the form to the gender of this student. + * @param form The form with the answers. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export function getGender(form: Requests.Form): Promise { + const questionGender: Responses.FormResponse = + filterQuestion(form, config.gender); + const questionsExist: boolean = checkQuestionsExist([questionGender]); + if (!questionsExist || questionGender.data?.value == null) { + return Promise.reject(errors.cookArgumentError()); + } + + const chosenGender: Responses.FormResponse = + filterChosenOption(questionGender.data); + + if (chosenGender.data == null || chosenGender.data.id.length === 0) { + return Promise.reject(errors.cookArgumentError()); + } + + return Promise.resolve(chosenGender.data.text); +} + +/** + * Parse the form to the phone number of this student. + * @param form The form with the answers. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export function getPhoneNumber(form: Requests.Form): Promise { + const questionPhoneNumber: Responses.FormResponse = + filterQuestion(form, config.phoneNumber); + const questionsExist: boolean = checkQuestionsExist([questionPhoneNumber]); + if (!questionsExist || questionPhoneNumber.data?.value == null) { + return Promise.reject(errors.cookArgumentError()); + } + + return Promise.resolve(questionPhoneNumber.data.value as string); +} + +/** + * Parse the form to the email of this student. + * @param form The form with the answers. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export function getNickname(form: Requests.Form): Promise { + const questionCheckNickname: Responses.FormResponse = + filterQuestion(form, config.nickname); + const questionEnterNickname: Responses.FormResponse = + filterQuestion(form, config.nicknameInput); + + const questionsExist: boolean = checkQuestionsExist([ + questionCheckNickname, + questionEnterNickname, + ]); + if (!questionsExist || questionCheckNickname.data?.value == null) { + return Promise.reject(errors.cookArgumentError()); + } + + let nickname = null; + const addNickName = checkWordInAnswer(questionCheckNickname.data, "yes"); + if (addNickName.data != null && addNickName.data) { + if (questionEnterNickname.data?.value == null) { + return Promise.reject(errors.cookArgumentError()); + } + nickname = questionEnterNickname.data.value as string; + } + + return Promise.resolve(nickname); +} + +/** + * Parse the form to the email of this student. + * @param form The form with the answers. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export function getAlumni(form: Requests.Form): Promise { + const questionCheckAlumni: Responses.FormResponse = + filterQuestion(form, config.alumni); - if (questionBirthName.value == null || questionLastName.value == null || - questionEmail.value == null) { - return Promise.reject(util.errors.cookArgumentError()); - } + const questionsExist: boolean = checkQuestionsExist([questionCheckAlumni]); + + if (!questionsExist || questionCheckAlumni.data?.value == null) { + return Promise.reject(errors.cookArgumentError()); + } - // TODO check email + const wordInAnswer: boolean | null = checkWordInAnswer( + questionCheckAlumni.data, + "yes" + ).data; - return ormP - .createPerson({ - firstname : questionBirthName.value, - lastname : questionLastName.value, - email : questionEmail.value - }) - .then(person => {return Promise.resolve({ - person_id : person.person_id, - firstname : person.firstname, - lastname : person.lastname, - email : questionEmail.value - })}); + if (wordInAnswer == null) { + return Promise.reject(errors.cookArgumentError()); + } + + return Promise.resolve(wordInAnswer); } /** @@ -70,138 +339,1124 @@ async function jsonToPerson(form: Requests.Form): Promise { * @returns See the API documentation. Successes are passed using * `Promise.resolve`, failures using `Promise.reject`. */ -async function jsonToStudent(form: Requests.Form, person: Responses.Person): - Promise { - // The pronouns of this student - const questionAddPronouns: Requests.Question = - filterQuestion(form, "question_3yJQMg"); - const questionPreferedPronouns: Requests.Question = - filterQuestion(form, "question_3X4aLg"); - const questionEnterPronouns: Requests.Question = - filterQuestion(form, "question_w8ZBq5"); +export async function jsonToStudent( + form: Requests.Form +): Promise { + const pronouns = await getPronouns(form); + const gender = await getGender(form); + const phoneNumber = await getPhoneNumber(form); + const nickname = await getNickname(form); + const alumni = await getAlumni(form); + + return Promise.resolve({ + pronouns: pronouns, + gender: gender, + phoneNumber: phoneNumber, + nickname: nickname, + alumni: alumni, + }); +} + +/* parse form to job application + ********************************/ + +/** + * Parse the form to the responsibilities of this student. + * @param form The form with the answers. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export function getResponsibilities( + form: Requests.Form +): Promise { + const questionCheckResponsibilities: Responses.FormResponse = + filterQuestion(form, config.responsibilities); + + const questionsExist: boolean = checkQuestionsExist([ + questionCheckResponsibilities, + ]); + + if (!questionsExist) { + return Promise.reject(errors.cookArgumentError()); + } - let pronouns: string[] = []; + if (questionCheckResponsibilities.data?.value == undefined) { + return Promise.resolve(null); + } + + return Promise.resolve(questionCheckResponsibilities.data?.value as string); +} - if (checkYesAnswer(questionAddPronouns) && - questionPreferedPronouns.options !== undefined) { - const chosenValue = questionPreferedPronouns.options?.filter( - option => option.id === questionPreferedPronouns.value)[0]; - if (chosenValue.text !== "other") { - pronouns = chosenValue.text.split("/"); +/** + * Parse the form to the fun fact of this student. + * @param form The form with the answers. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export function getFunFact(form: Requests.Form): Promise { + const questionFunFact: Responses.FormResponse = + filterQuestion(form, config.funFact); + + const questionsExist: boolean = checkQuestionsExist([questionFunFact]); + + if (!questionsExist || questionFunFact.data?.value == null) { + return Promise.reject(errors.cookArgumentError()); + } + + return Promise.resolve(questionFunFact.data.value as string); +} + +/** + * Parse the form to the volunteer info of this student. + * @param form The form with the answers. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export function getVolunteerInfo(form: Requests.Form): Promise { + const questionCheckVolunteerInfo: Responses.FormResponse = + filterQuestion(form, config.volunteerInfo); + + const questionsExist: boolean = checkQuestionsExist([ + questionCheckVolunteerInfo, + ]); + + if (!questionsExist || questionCheckVolunteerInfo.data?.value == null) { + return Promise.reject(errors.cookArgumentError()); + } + + const chosenVolunteerInfo: Responses.FormResponse = + filterChosenOption(questionCheckVolunteerInfo.data); + + if ( + chosenVolunteerInfo.data == null || + chosenVolunteerInfo.data.id.length === 0 + ) { + return Promise.reject(errors.cookArgumentError()); + } + + return Promise.resolve(chosenVolunteerInfo.data.text); +} + +/** + * Parse the form to the choice of being a student coach. + * @param form The form with the answers. + * @param hasAlreadyParticipated true if the student from the job application has already taken part in osoc previous years + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export function isStudentCoach( + form: Requests.Form, + hasAlreadyParticipated: boolean +): Promise { + if (hasAlreadyParticipated) { + const questionStudentCoach: Responses.FormResponse = + filterQuestion(form, config.studentCoach); + + const questionsExist: boolean = checkQuestionsExist([ + questionStudentCoach, + ]); + + if (!questionsExist || questionStudentCoach.data?.value == null) { + return Promise.reject(errors.cookArgumentError()); + } + + const wordInAnswer: boolean | null = checkWordInAnswer( + questionStudentCoach.data, + "yes" + ).data; + + if (wordInAnswer == null) { + return Promise.reject(errors.cookArgumentError()); + } + + return Promise.resolve(wordInAnswer); + } + return Promise.resolve(null); +} + +/** + * Parse the form to the educations of this student. + * @param form The form with the answers. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export function getEducations(form: Requests.Form): Promise { + const questionCheckEducations: Responses.FormResponse = + filterQuestion(form, config.edus); + + const questionsExist: boolean = checkQuestionsExist([ + questionCheckEducations, + ]); + + if ( + !questionsExist || + questionCheckEducations.data?.value == null || + (questionCheckEducations.data.value as string[]).length === 0 || + (questionCheckEducations.data.value as string[]).length > 2 + ) { + return Promise.reject(errors.cookArgumentError()); + } + + const educations: string[] = []; + + const questionValue = questionCheckEducations.data.value as string[]; + + for (let i = 0; i < questionValue.length; i++) { + if (questionCheckEducations.data.options != undefined) { + const filteredOption = questionCheckEducations.data.options.filter( + (option) => option.id === questionValue?.[i] + ); + if (filteredOption.length !== 1) { + return Promise.reject(errors.cookArgumentError()); + } + if (filteredOption[0].text.includes("Other")) { + const questionCheckOther: Responses.FormResponse = + filterQuestion(form, config.edusInput); + const questionsExistOther: boolean = checkQuestionsExist([ + questionCheckOther, + ]); + + if ( + !questionsExistOther || + questionCheckOther.data?.value == null + ) { + return Promise.reject(errors.cookArgumentError()); + } + + educations.push(questionCheckOther.data.value as string); + } else { + educations.push(filteredOption[0].text); + } + } + } + + return Promise.resolve(educations); +} + +/** + * Parse the form to the education levels of this student. + * @param form The form with the answers. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export function getEducationLevel(form: Requests.Form): Promise { + const questionCheckEducationLevel: Responses.FormResponse = + filterQuestion(form, config.eduLevel); + + const questionsExist: boolean = checkQuestionsExist([ + questionCheckEducationLevel, + ]); + + if ( + !questionsExist || + questionCheckEducationLevel.data?.value == null || + (questionCheckEducationLevel.data.value as string[]).length !== 1 + ) { + return Promise.reject(errors.cookArgumentError()); + } + + let educationLevel; + + const educationLevelValue = questionCheckEducationLevel.data + ?.value as string[]; + + if (questionCheckEducationLevel.data.options != undefined) { + const filteredOption = questionCheckEducationLevel.data.options.filter( + (option) => option.id === educationLevelValue?.[0] + ); + if (filteredOption.length !== 1) { + return Promise.reject(errors.cookArgumentError()); + } + if (filteredOption[0].text.includes("Other")) { + const questionCheckOther: Responses.FormResponse = + filterQuestion(form, config.eduLevelInput); + const questionsExistOther: boolean = checkQuestionsExist([ + questionCheckOther, + ]); + + if ( + !questionsExistOther || + questionCheckOther.data?.value == null + ) { + return Promise.reject(errors.cookArgumentError()); + } + + educationLevel = questionCheckOther.data.value as string; + } else { + educationLevel = filteredOption[0].text; + } } else { - pronouns = questionEnterPronouns.value.split("/"); - } - } - - // The gender of this student - const questionGender: Requests.Question = - filterQuestion(form, "question_wg9laO"); - let gender = ""; - if (questionGender.options !== undefined) { - gender = questionGender.options - .filter(option => option.id === questionGender.value)[0] - .text; - } - - // The phone number of this student - const questionPhoneNumber: Requests.Question = - filterQuestion(form, "question_wd9MEo"); - const phoneNumber = questionPhoneNumber.value; - - // The nickname of this student - const questionCheckNicknamePronouns: Requests.Question = - filterQuestion(form, "question_wME4XM"); - const questionEnterNicknamePronouns: Requests.Question = - filterQuestion(form, "question_mJOPqo"); - - let nickname; - - if (checkYesAnswer(questionCheckNicknamePronouns)) { - nickname = questionEnterNicknamePronouns.value; - } - - // Checks if this student has participated before - const questionAlumni: Requests.Question = - filterQuestion(form, "question_mVzejJ"); - let alumni = false; - - if (questionAlumni.options !== undefined) { - alumni = questionAlumni.options - ?.filter(option => option.id === questionAlumni.value)[0] - .text.includes("yes"); - } - - if (nickname !== undefined) { - return ormSt - .createStudent({ - personId : person.person_id, - gender : gender, - pronouns : pronouns, - phoneNumber : phoneNumber, - nickname : nickname, - alumni : alumni - }) - .then(() => { return Promise.resolve({}); }); - } else { - return ormSt - .createStudent({ - personId : person.person_id, - gender : gender, - pronouns : pronouns, - phoneNumber : phoneNumber, - alumni : alumni - }) - .then(() => { return Promise.resolve({}); }); - } + return Promise.reject(errors.cookArgumentError()); + } + + return Promise.resolve(educationLevel); +} + +/** + * Parse the form to the duration of the education of this student. + * @param form The form with the answers. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export function getEducationDuration( + form: Requests.Form +): Promise { + const questionEducationDuration: Responses.FormResponse = + filterQuestion(form, config.eduDuration); + + const questionsExist: boolean = checkQuestionsExist([ + questionEducationDuration, + ]); + + if (!questionsExist) { + return Promise.reject(errors.cookArgumentError()); + } + + if (questionEducationDuration.data?.value == null) { + return Promise.resolve(null); + } + + return Promise.resolve(Number(questionEducationDuration.data?.value)); +} + +/** + * Parse the form to the current year of the education of this student. + * @param form The form with the answers. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export function getEducationYear(form: Requests.Form): Promise { + const questionEducationYear: Responses.FormResponse = + filterQuestion(form, config.eduYear); + + const questionsExist: boolean = checkQuestionsExist([ + questionEducationYear, + ]); + + if (!questionsExist) { + return Promise.reject(errors.cookArgumentError()); + } + + if (questionEducationYear.data?.value == null) { + return Promise.resolve(null); + } + + return Promise.resolve(questionEducationYear.data.value as string); +} + +/** + * Parse the form to the university of this student. + * @param form The form with the answers. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export function getEducationUniversity( + form: Requests.Form +): Promise { + const questionEducationUniversity: Responses.FormResponse = + filterQuestion(form, config.eduInstitute); + + const questionsExist: boolean = checkQuestionsExist([ + questionEducationUniversity, + ]); + + if (!questionsExist) { + return Promise.reject(errors.cookArgumentError()); + } + + if (questionEducationUniversity.data?.value == null) { + return Promise.resolve(null); + } + + return Promise.resolve(questionEducationUniversity.data.value as string); } /** * Attempts to parse the answers in the form into a job application entity. * @param form The form with the answers. + * @param hasAlreadyTakenPart true if the student has already taken part in osoc in a prev edition * @returns See the API documentation. Successes are passed using * `Promise.resolve`, failures using `Promise.reject`. */ -/*function jsonToJobApplication(form: Requests.Form, student: -Responses.Student): Promise { - // The volunteer info of the student - const questionVolunteerInfo: Requests.Question = - filterQuestion(form, "question_wvPZM0"); +export async function jsonToJobApplication( + form: Requests.Form, + hasAlreadyTakenPart: boolean +): Promise { + const responsibilities = await getResponsibilities(form); + const funFact = await getFunFact(form); + const volunteerInfo = await getVolunteerInfo(form); + const studentCoach = await isStudentCoach(form, hasAlreadyTakenPart); + const latestOsoc = await ormOs.getLatestOsoc(); + if (latestOsoc == null) { + return Promise.reject(errors.cookArgumentError()); + } + const osocId = latestOsoc.osoc_id; + const educations = await getEducations(form); + const educationLevel = await getEducationLevel(form); + const educationDuration = await getEducationDuration(form); + const educationYear = await getEducationYear(form); + const educationInstitute = await getEducationUniversity(form); + const emailStatus = "NONE"; + let createdAt = new Date(Date.now()).toString(); + if (form.createdAt != undefined) { + createdAt = form.createdAt; + } - let volunteerInfo : string = ""; + return Promise.resolve({ + responsibilities: responsibilities, + funFact: funFact, + volunteerInfo: volunteerInfo, + studentCoach: studentCoach, + osocId: osocId, + educations: educations, + educationLevel: educationLevel, + educationDuration: educationDuration, + educationYear: educationYear, + educationInstitute: educationInstitute, + emailStatus: emailStatus, + createdAt: createdAt, + }); +} + +/* parse form to job application skills + ***************************************/ + +/** + * Parse the form to the most fluent language of this student. + * @param form The form with the answers. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export function getMostFluentLanguage(form: Requests.Form): Promise { + const questionMostFluentLanguage: Responses.FormResponse = + filterQuestion(form, config.mostFluentLanguage); + + const questionsExist: boolean = checkQuestionsExist([ + questionMostFluentLanguage, + ]); + + if (!questionsExist || questionMostFluentLanguage.data?.value == null) { + return Promise.reject(errors.cookArgumentError()); + } + + const chosenLanguage: Responses.FormResponse = + filterChosenOption(questionMostFluentLanguage.data); + + if (chosenLanguage.data == null) { + return Promise.reject(errors.cookArgumentError()); + } + + let language = ""; + + if (chosenLanguage.data.text.includes("Other")) { + const questionCheckOther: Responses.FormResponse = + filterQuestion(form, config.mostFluentLanguageInput); + const questionsExistOther: boolean = checkQuestionsExist([ + questionCheckOther, + ]); + + if (!questionsExistOther || questionCheckOther.data?.value == null) { + return Promise.reject(errors.cookArgumentError()); + } + + language = questionCheckOther.data.value as string; + } else { + language = chosenLanguage.data.text; + } + + return Promise.resolve(language); +} + +/** + * Parse the form to the english level of this student. + * @param form The form with the answers. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export function getEnglishLevel(form: Requests.Form): Promise { + const questionEnglishLevel: Responses.FormResponse = + filterQuestion(form, config.englishLevel); + + const questionsExist: boolean = checkQuestionsExist([questionEnglishLevel]); + + if (!questionsExist || questionEnglishLevel.data?.value == null) { + return Promise.reject(errors.cookArgumentError()); + } + + const chosenLanguage: Responses.FormResponse = + filterChosenOption(questionEnglishLevel.data); + + if (chosenLanguage.data == null) { + return Promise.reject(errors.cookArgumentError()); + } + + if (chosenLanguage.data.text.toLowerCase().includes("★★★★★")) { + return Promise.resolve(5); + } else if (chosenLanguage.data.text.toLowerCase().includes("★★★★")) { + return Promise.resolve(4); + } else if (chosenLanguage.data.text.toLowerCase().includes("★★★")) { + return Promise.resolve(3); + } else if (chosenLanguage.data.text.toLowerCase().includes("★★")) { + return Promise.resolve(2); + } + + return Promise.resolve(1); +} + +/** + * Parse the form to the best skill this student. + * @param form The form with the answers. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export function getBestSkill(form: Requests.Form): Promise { + const questionBestSkill: Responses.FormResponse = + filterQuestion(form, config.bestSkill); + + const questionsExist: boolean = checkQuestionsExist([questionBestSkill]); + + if (!questionsExist || questionBestSkill.data?.value == null) { + return Promise.reject(errors.cookArgumentError()); + } + + return Promise.resolve(questionBestSkill.data.value as string); +} + +/** + * Attempts to parse the answers in the form into job application skills entities. + * @param form The form with the answers. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export async function jsonToSkills( + form: Requests.Form +): Promise { + const most_fluent_language = await getMostFluentLanguage(form); + const english_level = await getEnglishLevel(form); + const best_skill = await getBestSkill(form); + + return Promise.resolve({ + most_fluent_language: most_fluent_language, + english_level: english_level, + best_skill: best_skill, + }); +} + +/* parse form to attachments + ****************************/ + +/** + * Parse the form to the cv of this student. + * @param form The form with the answers. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export function getCV( + form: Requests.Form +): Promise { + const questionCVUpload: Responses.FormResponse = + filterQuestion(form, config.cvUpload); + const questionCVLink: Responses.FormResponse = + filterQuestion(form, config.cvLink); + + const questionsExist: boolean = checkQuestionsExist([ + questionCVUpload, + questionCVLink, + ]); + + const links: string[] = []; + const types: type_enum[] = []; + + if (!questionsExist) { + return Promise.reject(errors.cookArgumentError()); + } + if ( + questionCVUpload.data?.value == null && + questionCVLink.data?.value == null + ) { + return Promise.resolve({ data: [], types: [] }); + } + + if (questionCVLink.data?.value != null) { + if ((questionCVLink.data?.value as string).trim() != "") { + links.push(questionCVLink.data?.value as string); + types.push("CV_URL"); + } + } + + const cvUploadValue = questionCVUpload.data?.value as Requests.FormValues[]; + + if (questionCVUpload.data?.value != null) { + for (let linkIndex = 0; linkIndex < cvUploadValue.length; linkIndex++) { + if ( + (cvUploadValue[linkIndex] as Requests.FormValues).url == + undefined + ) { + return Promise.reject(errors.cookArgumentError()); + } + + if ( + (cvUploadValue[linkIndex] as Requests.FormValues).url.trim() != + "" + ) { + links.push( + (cvUploadValue[linkIndex] as Requests.FormValues).url + ); + types.push("CV_URL"); + } + } + } + + return Promise.resolve({ data: links, types: types }); +} + +/** + * Parse the form to the portfolio of this student. + * @param form The form with the answers. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export function getPortfolio( + form: Requests.Form +): Promise { + const questionPortfolioUpload: Responses.FormResponse = + filterQuestion(form, config.portfolioUpload); + const questionPortfolioLink: Responses.FormResponse = + filterQuestion(form, config.portfolioLink); + + const questionsExist: boolean = checkQuestionsExist([ + questionPortfolioUpload, + questionPortfolioLink, + ]); + + const links: string[] = []; + const types: type_enum[] = []; + + if (!questionsExist) { + return Promise.reject(errors.cookArgumentError()); + } + + if ( + questionPortfolioUpload.data?.value == null && + questionPortfolioLink.data?.value == null + ) { + return Promise.resolve({ data: [], types: [] }); + } + + if (questionPortfolioLink.data?.value != null) { + if ((questionPortfolioLink.data?.value as string).trim() != "") { + links.push(questionPortfolioLink.data?.value as string); + types.push("PORTFOLIO_URL"); + } + } + + const portfolioUploadValue = questionPortfolioUpload.data + ?.value as Requests.FormValues[]; + + if (questionPortfolioUpload.data?.value != null) { + for ( + let linkIndex = 0; + linkIndex < portfolioUploadValue.length; + linkIndex++ + ) { + if ( + (portfolioUploadValue[linkIndex] as Requests.FormValues).url == + undefined + ) { + return Promise.reject(errors.cookArgumentError()); + } + + if ( + ( + portfolioUploadValue[linkIndex] as Requests.FormValues + ).url.trim() != "" + ) { + links.push( + (portfolioUploadValue[linkIndex] as Requests.FormValues).url + ); + types.push("PORTFOLIO_URL"); + } + } + } + + return Promise.resolve({ data: links, types: types }); +} + +/** + * Parse the form to the motivation of this student. + * @param form The form with the answers. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export function getMotivation( + form: Requests.Form +): Promise { + const questionMotivationUpload: Responses.FormResponse = + filterQuestion(form, config.motivationUpload); + const questionMotivationLink: Responses.FormResponse = + filterQuestion(form, config.motivationLink); + const questionMotivationString: Responses.FormResponse = + filterQuestion(form, config.motivationInput); + + const questionsExist: boolean = checkQuestionsExist([ + questionMotivationUpload, + questionMotivationLink, + questionMotivationString, + ]); + + const data: string[] = []; + const types: type_enum[] = []; + + if (!questionsExist) { + return Promise.reject(errors.cookArgumentError()); + } + + if ( + questionMotivationUpload.data?.value == null && + questionMotivationLink.data?.value == null && + questionMotivationString.data?.value == null + ) { + return Promise.resolve({ data: [], types: [] }); + } + + if (questionMotivationLink.data?.value != null) { + if ((questionMotivationLink.data?.value as string).trim() != "") { + data.push(questionMotivationLink.data?.value as string); + types.push("MOTIVATION_URL"); + } + } - if(questionVolunteerInfo.options !== undefined) { - volunteerInfo = questionVolunteerInfo.options?.filter(option => -option.id === questionVolunteerInfo.value)[0].text; + const motivationUploadValue = questionMotivationUpload.data + ?.value as Requests.FormValues[]; + + if (questionMotivationUpload.data?.value != null) { + for ( + let linkIndex = 0; + linkIndex < motivationUploadValue.length; + linkIndex++ + ) { + if ( + (motivationUploadValue[linkIndex] as Requests.FormValues).url == + undefined + ) { + return Promise.reject(errors.cookArgumentError()); + } + + if ( + ( + motivationUploadValue[linkIndex] as Requests.FormValues + ).url.trim() != "" + ) { + data.push( + (motivationUploadValue[linkIndex] as Requests.FormValues) + .url + ); + types.push("MOTIVATION_URL"); + } + } + } + + if (questionMotivationString.data?.value != null) { + if ((questionMotivationString.data?.value as string).trim() != "") { + data.push(questionMotivationString.data?.value as string); + types.push("MOTIVATION_URL"); + } + } + + return Promise.resolve({ data: data, types: types }); +} + +/** + * Attempts to parse the answers in the form into attachment entities. + * @param form The form with the answers. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export async function jsonToAttachments( + form: Requests.Form +): Promise { + const cv_links = await getCV(form); + const portfolio_links = await getPortfolio(form); + const motivations = await getMotivation(form); + + return Promise.resolve({ + cv_links: cv_links, + portfolio_links: portfolio_links, + motivations: motivations, + }); +} + +/* parse form to applied roles + ******************************/ + +/** + * Parse the form to the roles of this student. + * @param form The form with the answers. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export function getAppliedRoles(form: Requests.Form): Promise { + const questionAppliedRoles: Responses.FormResponse = + filterQuestion(form, config.appliedRole); + + const questionsExist: boolean = checkQuestionsExist([questionAppliedRoles]); + + if ( + !questionsExist || + questionAppliedRoles.data?.value == null || + (questionAppliedRoles.data.value as string[]).length === 0 || + (questionAppliedRoles.data.value as string[]).length > 2 + ) { + return Promise.reject(errors.cookArgumentError()); + } + + const appliedRolesValue = questionAppliedRoles.data?.value as string[]; + + const appliedRoles: string[] = []; + + for (let i = 0; i < appliedRolesValue.length; i++) { + if (questionAppliedRoles.data.options != undefined) { + const filteredOption = questionAppliedRoles.data.options.filter( + (option) => option.id === appliedRolesValue?.[i] + ); + if (filteredOption.length !== 1) { + return Promise.reject(errors.cookArgumentError()); + } + if (filteredOption[0].text.includes("Other")) { + const questionCheckOther: Responses.FormResponse = + filterQuestion(form, config.appliedRoleInput); + const questionsExistOther: boolean = checkQuestionsExist([ + questionCheckOther, + ]); + + if ( + !questionsExistOther || + questionCheckOther.data?.value == null + ) { + return Promise.reject(errors.cookArgumentError()); + } + + appliedRoles.push(questionCheckOther.data.value as string); + } else { + appliedRoles.push(filteredOption[0].text); + } + } } - // The responsibilities of the student - const responsibilities: string | null = - filterQuestion(form, "question_wLPr9v").value; + return Promise.resolve(appliedRoles); +} + +/** + * Attempts to parse the answers in the form into role entities. + * @param form The form with the answers. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export async function jsonToRoles( + form: Requests.Form +): Promise { + const roles = await getAppliedRoles(form); + + return Promise.resolve({ roles: roles }); +} - // A fun fact of this student - const funFact : string | null = filterQuestion(form, "question_nPzxpV").value; +/** + * Attempts to add a person to the database. + * @param formResponse The response with the data. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export async function addPersonToDatabase( + formResponse: Responses.FormPerson +): Promise { + const allPersons = await ormP.getAllPersons(); - // Does this student want to be a student-coach - const questionStudentCoach: Requests.Question = - filterQuestion(form, "question_nPzxD5"); + const checkIfEmailInDb = allPersons.filter( + (person) => person.email === formResponse.email + ); - let studentCoach : Boolean = false; + let personId; - if(questionStudentCoach.options !== undefined && questionStudentCoach.value !== null && questionStudentCoach.value === "2055442c-a9a6-429d-9ada-045078295f86") { - studentCoach = true; + if (checkIfEmailInDb.length > 0) { + await ormP.updatePerson({ + personId: checkIfEmailInDb[0].person_id, + firstname: formResponse.birthName, + lastname: formResponse.lastName, + github: null, + email: formResponse.email, + }); + personId = checkIfEmailInDb[0].person_id; + } else { + const person = await ormP.createPerson({ + firstname: formResponse.birthName, + lastname: formResponse.lastName, + email: formResponse.email, + }); + personId = person.person_id; } - // The educations of the student - const questionStudentCoach: Requests.Question = - filterQuestion(form, "question_nPzxD5"); + return Promise.resolve({ id: personId }); +} + +/** + * Attempts to add a student to the database. + * @param formResponse The response with the data. + * @param personId The id of a person. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export async function addStudentToDatabase( + formResponse: Responses.FormStudent, + personId: Responses.Id +): Promise { + const allStudents = await ormSt.getAllStudents(); + + const checkIfIdInDb = allStudents.filter( + (student) => student.person_id === personId.id + ); - let studentCoach : Boolean = false; + let studentId; - if(questionStudentCoach.options !== undefined && questionStudentCoach.value !== null && questionStudentCoach.value === "2055442c-a9a6-429d-9ada-045078295f86") { - studentCoach = true; + if (checkIfIdInDb.length > 0) { + await ormSt.updateStudent({ + studentId: checkIfIdInDb[0].student_id, + gender: formResponse.gender, + pronouns: + formResponse.pronouns == null ? "" : formResponse.pronouns, + phoneNumber: formResponse.phoneNumber, + nickname: formResponse.nickname, + alumni: formResponse.alumni, + }); + studentId = checkIfIdInDb[0].student_id; + } else { + const student = await ormSt.createStudent({ + personId: personId.id, + gender: formResponse.gender, + pronouns: + formResponse.pronouns != null + ? formResponse.pronouns + : undefined, + phoneNumber: formResponse.phoneNumber, + nickname: + formResponse.nickname != null + ? formResponse.nickname + : undefined, + alumni: formResponse.alumni, + }); + studentId = student.student_id; } -}*/ + return Promise.resolve({ + id: studentId, + hasAlreadyTakenPart: formResponse.alumni, + }); +} + +/** + * Attempts to add a job application to the database. + * @param formResponse The response with the data. + * @param student_id The student id object. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export async function addJobApplicationToDatabase( + formResponse: Responses.FormJobApplication, + student_id: Responses.Id +): Promise { + const studentId = student_id.id; + + const latestJobApplication = await ormJo.getLatestJobApplicationOfStudent( + studentId + ); + + if ( + latestJobApplication !== null && + latestJobApplication.osoc_id === formResponse.osocId + ) { + await Promise.all([ + ormAppRo.deleteAppliedRolesByJobApplication( + latestJobApplication.job_application_id + ), + ormAtt.deleteAllAttachmentsForApplication( + latestJobApplication.job_application_id + ), + ormJoSk.deleteSkillsByJobApplicationId( + latestJobApplication.job_application_id + ), + ]); + await ormJo.deleteJobApplication( + latestJobApplication.job_application_id + ); + } + + const jobApplication = await ormJo.createJobApplication({ + studentId: studentId, + responsibilities: formResponse.responsibilities, + funFact: formResponse.funFact, + studentVolunteerInfo: formResponse.volunteerInfo, + studentCoach: + formResponse.studentCoach == null + ? false + : formResponse.studentCoach, + osocId: formResponse.osocId, + edus: formResponse.educations, + eduLevel: formResponse.educationLevel, + eduDuration: formResponse.educationDuration, + eduYear: formResponse.educationYear, + eduInstitute: formResponse.educationInstitute, + emailStatus: formResponse.emailStatus, + createdAt: formResponse.createdAt, + }); + + return Promise.resolve({ id: jobApplication.job_application_id }); +} + +/** + * Attempts to add a job application skill to the database. + * @param formResponse The response with the data. + * @param job_applicationId The job application id. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export async function addSkillsToDatabase( + formResponse: Responses.FormJobApplicationSkill, + job_applicationId: Responses.Id +): Promise { + const job_application_id = job_applicationId.id; + + let most_fl_la_id; + const getMostFluentLanguageInDb = await ormLa.getLanguageByName( + formResponse.most_fluent_language + ); + if (getMostFluentLanguageInDb == null) { + const most_fl_la = await ormLa.createLanguage( + formResponse.most_fluent_language + ); + most_fl_la_id = most_fl_la.language_id; + } else { + most_fl_la_id = getMostFluentLanguageInDb.language_id; + } + + if (!formResponse.most_fluent_language.includes("English")) { + await ormJoSk.createJobApplicationSkill({ + jobApplicationId: job_application_id, + skill: null, + languageId: most_fl_la_id, + level: null, + isPreferred: true, + isBest: false, + }); + } + + let english_id; + const getEnglishLanguage = await ormLa.getLanguageByName("English"); + if (getEnglishLanguage == null) { + const english_language = await ormLa.createLanguage("English"); + english_id = english_language.language_id; + } else { + english_id = getEnglishLanguage.language_id; + } + + await ormJoSk.createJobApplicationSkill({ + jobApplicationId: job_application_id, + skill: null, + languageId: english_id, + level: formResponse.english_level, + isPreferred: false, + isBest: false, + }); + + await ormJoSk.createJobApplicationSkill({ + jobApplicationId: job_application_id, + skill: formResponse.best_skill, + languageId: null, + level: null, + isPreferred: false, + isBest: true, + }); + + return Promise.resolve({}); +} + +/** + * Attempts to add attachments to the database. + * @param formResponse The response with the data. + * @param job_applicationId The job_application id object. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export async function addAttachmentsToDatabase( + formResponse: Responses.FormAttachment, + job_applicationId: Responses.Id +): Promise { + const job_application_id = job_applicationId.id; + + if (formResponse.cv_links.data.length > 0) { + await ormAtt.createAttachment( + job_application_id, + formResponse.cv_links.data, + formResponse.cv_links.types + ); + } + + if (formResponse.portfolio_links.data.length > 0) { + await ormAtt.createAttachment( + job_application_id, + formResponse.portfolio_links.data, + formResponse.portfolio_links.types + ); + } + + if (formResponse.motivations.data.length > 0) { + await ormAtt.createAttachment( + job_application_id, + formResponse.motivations.data, + formResponse.motivations.types + ); + } + + return Promise.resolve({}); +} + +/** + * Attempts to add roles to the database. + * @param formResponse The response with the data. + * @param job_applicationId The job_application id object. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export async function addRolesToDatabase( + formResponse: Responses.FormRoles, + job_applicationId: Responses.Id +): Promise { + const job_application_id = job_applicationId.id; + + for ( + let role_index = 0; + role_index < formResponse.roles.length; + role_index++ + ) { + const role_exists = await ormRo.getRolesByName( + formResponse.roles[role_index] + ); + if (role_exists == null) { + const created_role = await ormRo.createRole( + formResponse.roles[role_index] + ); + await ormAppRo.createAppliedRole({ + jobApplicationId: job_application_id, + roleId: created_role.role_id, + }); + } else { + await ormAppRo.createAppliedRole({ + jobApplicationId: job_application_id, + roleId: role_exists.role_id, + }); + } + } + + return Promise.resolve({}); +} /** * Attempts to create a new form in the system. @@ -209,22 +1464,78 @@ option.id === questionVolunteerInfo.value)[0].text; * @returns See the API documentation. Successes are passed using * `Promise.resolve`, failures using `Promise.reject`. */ -async function createForm(req: express.Request): Promise { - return rq.parseFormRequest(req).then(async form => { - // Checks if the student will be in Belgium in July and if the student can - // work enough in July. - if (!checkYesAnswer(filterQuestion(form, "question_wkNolR")) || - !checkYesAnswer(filterQuestion(form, "question_mKVEz8"))) { - return Promise.reject(util.errors.cookNonJSON("Invalid json")); +export async function createForm( + req: express.Request +): Promise { + const parsedRequest = await rq.parseFormRequest(req); + if (parsedRequest.data.fields == undefined) { + return Promise.reject(errors.cookArgumentError()); } - // TODO kan je 128 uur werken - // TODO gender verandert naar student + const questionInBelgium: Responses.FormResponse = + filterQuestion(parsedRequest, config.liveInBelgium); + const questionCanWorkEnough: Responses.FormResponse = + filterQuestion(parsedRequest, config.workInJuly); + + const questionsExist: boolean = checkQuestionsExist([ + questionInBelgium, + questionCanWorkEnough, + ]); + if ( + !questionsExist || + questionInBelgium.data?.value == null || + questionCanWorkEnough.data?.value == null + ) { + return Promise.reject(errors.cookArgumentError()); + } + + const wordInAnswerInBelgium: Responses.FormResponse = + checkWordInAnswer(questionInBelgium.data, "yes"); + const wordInAnswerCanWorkEnough: Responses.FormResponse = + checkWordInAnswer(questionCanWorkEnough.data, "yes"); + + if ( + wordInAnswerInBelgium.data == null || + wordInAnswerCanWorkEnough.data == null + ) { + return Promise.resolve({}); + } + + if (wordInAnswerInBelgium.data && wordInAnswerCanWorkEnough.data) { + const person: Responses.FormPerson = await jsonToPerson(parsedRequest); + const student: Responses.FormStudent = await jsonToStudent( + parsedRequest + ); + const jobApplication: Responses.FormJobApplication = + await jsonToJobApplication(parsedRequest, student.alumni); + const jobApplicationSkills: Responses.FormJobApplicationSkill = + await jsonToSkills(parsedRequest); + const attachments: Responses.FormAttachment = await jsonToAttachments( + parsedRequest + ); + const roles: Responses.FormRoles = await jsonToRoles(parsedRequest); + + const addedPersonToDatabase = await addPersonToDatabase(person); + const addedStudentToDatabase = await addStudentToDatabase(student, { + id: addedPersonToDatabase.id, + }); + const addedJobApplicationToDatabase = await addJobApplicationToDatabase( + jobApplication, + { id: addedStudentToDatabase.id } + ); + await addSkillsToDatabase(jobApplicationSkills, { + id: addedJobApplicationToDatabase.id, + }); + await addAttachmentsToDatabase(attachments, { + id: addedJobApplicationToDatabase.id, + }); + await addRolesToDatabase(roles, { + id: addedJobApplicationToDatabase.id, + }); + return Promise.resolve({}); + } - return jsonToPerson(form) - .then(person => { return jsonToStudent(form, person); }) - .then(() => { return Promise.resolve({}); }); - }); + return Promise.resolve({}); } /** @@ -233,12 +1544,13 @@ async function createForm(req: express.Request): Promise { * endpoints. */ export function getRouter(): express.Router { - const router: express.Router = express.Router(); + const router: express.Router = express.Router(); - router.post('/', - (req, res) => util.respOrErrorNoReinject(res, createForm(req))); + router.post("/", (req, res) => + util.respOrErrorNoReinject(res, createForm(req)) + ); - util.addAllInvalidVerbs(router, [ "/" ]); + util.addAllInvalidVerbs(router, ["/"]); - return router; + return router; } diff --git a/backend/routes/form_keys.json b/backend/routes/form_keys.json new file mode 100644 index 00000000..6fda4a98 --- /dev/null +++ b/backend/routes/form_keys.json @@ -0,0 +1,39 @@ +{ + "liveInBelgium": "question_mK177D", + "volunteerInfo": "question_wLpAAJ", + "workInJuly": "question_npOjjP", + "responsibilities": "question_31VZZb", + "birthName": "question_wMRkk8", + "lastName": "question_mJzaaX", + "nickname": "question_wgGyyJ", + "nicknameInput": "question_3yYKKd", + "gender": "question_3X0VVz", + "addPronouns": "question_w8xvvr", + "preferredPronouns": "question_n0O22A", + "pronounsInput": "question_wzYppg", + "mostFluentLanguage": "question_w5x66Q", + "mostFluentLanguageInput": "question_wddLLV", + "englishLevel": "question_mYaEEv", + "phoneNumber": "question_mDzrrE", + "emailAddress": "question_3lrooX", + "cvUpload": "question_mRPXXQ", + "cvLink": "question_woGrrN", + "portfolioUpload": "question_nGlNNO", + "portfolioLink": "question_mOGppM", + "motivationUpload": "question_mVJRR6", + "motivationLink": "question_nPOMMx", + "motivationInput": "question_3EWjj2", + "funFact": "question_nrPNNX", + "edus": "question_w4rWW5", + "edusInput": "question_3jPdd1", + "eduLevel": "question_w2PqqM", + "eduLevelInput": "question_3xYkkk", + "eduDuration": "question_mZ6555", + "eduYear": "question_3NW55p", + "eduInstitute": "question_3qAZZ5", + "appliedRole": "question_wQ5221", + "appliedRoleInput": "question_n9DGGX", + "bestSkill": "question_meeZZQ", + "alumni": "question_nW599R", + "studentCoach": "question_waYNN2" +} \ No newline at end of file diff --git a/backend/routes/github.ts b/backend/routes/github.ts new file mode 100644 index 00000000..5b285006 --- /dev/null +++ b/backend/routes/github.ts @@ -0,0 +1,270 @@ +import { account_status_enum } from "@prisma/client"; +import axios from "axios"; +import * as crypto from "crypto"; +import express from "express"; + +import * as config from "../config.json"; +import * as ormLU from "../orm_functions/login_user"; +import * as ormP from "../orm_functions/person"; +import * as ormSK from "../orm_functions/session_key"; +import { Anything, Requests, Responses } from "../types"; +import * as util from "../utility"; + +import * as session_key from "./session_key.json"; + +// holds states that are currently active. States are required to validate +// github callbacks. +export let states: string[] = []; + +/** + * Gets the home URL for the github callback. + * @returns The home URL. + */ +export function getHome(): string { + const root = `${process.env.GITHUB_AUTH_CALLBACK_URL}`; + // check if dev or production + console.log("Home is: " + root); + return root; +} + +/** + * Generates a new state for GitHub, then adds it to the current states. This + * state is a a string of 64 cryptographically random bytes. + * + * @returns The new state. + */ +export function genState(): string { + const state = crypto.randomBytes(64).join(""); + states.push(state); + return state; +} + +/** + * Checks if a state exists, then removes that state from the set of valid + * states. + * + * @param state The state to check. + * @returns True if the state is valid, otherwise false. + */ +export function checkState(state: string) { + if (!states.includes(state)) { + return false; + } + + states = states.filter((x) => x != state); + return true; +} + +// Step 1: redirect to github for identity +// Step 2: redirect to github for authentication +// Step 3: set session key, ... + +/** + * Step one of the GitHub OAuth process: getting the identity of the user. + * This calls GitHub's login, then asks Github to redirect the user to + * `/github/challenge` on our server. This function only generates a new state, + * makes it valid and then redirects. + */ +export function ghIdentity(resp: express.Response): Promise { + let url = "https://github.com/login/oauth/authorize?"; + url += "client_id=" + process.env.GITHUB_CLIENT_ID; // add client id + url += "&allow_signup=true"; // allow users to sign up to github itself + url += // set redirect + "&redirect_uri=" + + encodeURIComponent( + getHome() + config.global.preferred + "/github/challenge" + ); + url += "&state=" + genState(); + console.log("--- REDIRECTING TO GITHUB AT " + url + " ---"); + return util.redirect(resp, url); +} + +/** + * Step two of the GitHub OAuth process: exchanging the temporary code for an + * access token. This function first asks an access token, then checks if a + * a user exists in our system and if not, creates one. + */ +export async function ghExchangeAccessToken( + req: express.Request, + res: express.Response +): Promise { + if (!("code" in req.query)) { + return Promise.reject(config.apiErrors.github.argumentMissing); + } + + if (!("state" in req.query)) { + return Promise.reject(config.apiErrors.github.argumentMissing); + } + + if (!checkState(req.query.state as string)) { + return Promise.reject(config.apiErrors.github.illegalState); + } + + return axios + .post( + "https://github.com/login/oauth/access_token", + { + client_id: process.env.GITHUB_CLIENT_ID, + client_secret: process.env.GITHUB_SECRET, + code: req.query.code as string, + redirect_uri: + getHome() + config.global.preferred + "/github/login", + }, + { headers: { Accept: "application/json" } } + ) + .then((ares) => + axios.get("https://api.github.com/user", { + headers: { Authorization: "token " + ares.data.access_token }, + }) + ) + .then((ares) => parseGHLogin(ares.data)) + .then((login) => ghSignupOrLogin(login)) + .then((result) => + util.redirect( + res, + process.env.FRONTEND + "/login/" + result.sessionkey + ) + ) + .catch((err) => { + console.log("GITHUB ERROR " + err); + util.redirect( + res, + process.env.FRONTEND + + "/login?loginError=" + + config.apiErrors.github.other.reason + ); + return Promise.resolve(); + }); +} + +/** + * Checks if all required fields are available. We need (at least) a `login`, + * `name` and `id` in the request. + */ +export function parseGHLogin(data: Anything): Promise { + if ("login" in data && "name" in data && "id" in data) { + return Promise.resolve({ + login: data.login as string, + name: + data.name == null + ? (data.login as string) + : (data.name as string), + id: (data.id as number).toString(), + }); + } + return Promise.reject({}); +} + +/** + * Checks if we need to update a GitHub user's name and/or handle. + */ +export function githubNameChange( + login: Requests.GHLogin, + person: { + github: string | null; + person_id: number; + firstname: string; + login_user: { + password: string | null; + login_user_id: number; + account_status: account_status_enum; + is_admin: boolean; + is_coach: boolean; + } | null; + } +): boolean { + if (person.github != login.login) return true; + if (person.firstname != login.name) return true; + return false; +} + +/** + * Callback for the actual signup and/or login. Uses the provided GitHub data + * to access our underlying database. If the user doesn't exist, their account + * is created. If the user exists, we check if we need to update their name + * and/or GitHub handle. + */ +export async function ghSignupOrLogin( + login: Requests.GHLogin +): Promise { + return ormP + .getPasswordPersonByGithub(login.id) + .then(async (person) => { + if (person == null || person.login_user == null) { + return Promise.reject({ is_not_existent: true }); + } else if ( + person.github != null && + githubNameChange(login, person) + ) { + return ormP + .updatePerson({ + personId: person.person_id, + github: login.login, + firstname: login.name, + }) + .then(() => person.login_user); + } else { + return Promise.resolve(person.login_user); + } + }) + .catch(async (error) => { + if ("is_not_existent" in error && error.is_not_existent) { + return ormP + .createPerson({ + github: login.login, + firstname: login.name, + lastname: "", + github_id: login.id, + }) + .then((person) => + ormLU.createLoginUser({ + personId: person.person_id, + isAdmin: false, + isCoach: true, + accountStatus: "PENDING", + }) + ) + .then((res) => + Promise.resolve({ + password: res.password, + login_user_id: res.login_user_id, + account_status: res.account_status, + is_admin: false, + is_coach: true, + }) + ); + } else { + return Promise.reject(error); // pass on + } + }) + .then((data) => util.getOrReject(data)) + .then(async (loginuser) => { + const key: string = util.generateKey(); + const futureDate = new Date(Date.now()); + futureDate.setDate(futureDate.getDate() + session_key.valid_period); + return ormSK + .addSessionKey(loginuser.login_user_id, key, futureDate) + .then((newkey) => + Promise.resolve({ + sessionkey: newkey.session_key, + is_admin: loginuser.is_admin, + is_coach: loginuser.is_coach, + }) + ); + }); +} + +export function getRouter(): express.Router { + const router: express.Router = express.Router(); + + router.get("/", async (_, rs) => await ghIdentity(rs)); + router.get( + "/challenge", + async (req, res) => + await ghExchangeAccessToken(req, res).catch((e) => + util.replyError(res, e) + ) + ); + + return router; +} diff --git a/backend/routes/login.ts b/backend/routes/login.ts index af4aa250..61922e67 100644 --- a/backend/routes/login.ts +++ b/backend/routes/login.ts @@ -1,10 +1,19 @@ -import express from 'express'; +import express from "express"; -import {getPasswordPersonByEmail} from '../orm_functions/person'; -import {addSessionKey, removeAllKeysForUser} from '../orm_functions/session_key'; -import {parseLoginRequest, parseLogoutRequest} from '../request'; -import {Responses} from '../types'; -import * as util from '../utility'; +import { getPasswordPersonByEmail } from "../orm_functions/person"; +import { + addSessionKey, + removeAllKeysForUser, +} from "../orm_functions/session_key"; +import { parseLoginRequest, parseLogoutRequest } from "../request"; +import { Responses } from "../types"; +import * as util from "../utility"; + +import * as session_key from "./session_key.json"; + +function orDefault(v: T | undefined, def: T): T { + return v == undefined || false ? def : v; +} /** * Attempts to log a user into the system. @@ -12,22 +21,41 @@ import * as util from '../utility'; * @returns See the API documentation. Successes are passed using * `Promise.resolve`, failures using `Promise.reject`. */ -async function login(req: express.Request): Promise { - console.log("Calling login endpoint " + JSON.stringify(req.body)); - return parseLoginRequest(req).then( - parsed => getPasswordPersonByEmail(parsed.name).then(async pass => { - if (pass?.login_user?.password != parsed.pass) { - return Promise.reject( - {http : 409, reason : 'Invalid e-mail or password.'}); - } - if (pass?.login_user?.account_status != 'ACTIVATED') { - return Promise.reject( - {http : 409, reason : 'Account isn\'t activated yet.'}); - } - const key: string = util.generateKey(); - return addSessionKey(pass.login_user.login_user_id, key) - .then(ins => ({sessionkey : ins.session_key})); - })); +export async function login(req: express.Request): Promise { + console.log("Calling login endpoint " + JSON.stringify(req.body)); + return parseLoginRequest(req).then((parsed) => + getPasswordPersonByEmail(parsed.name).then(async (pass) => { + if ( + pass == null || + pass.login_user == null || + pass?.login_user?.password != parsed.pass + ) { + return Promise.reject({ + http: 409, + reason: "Invalid e-mail or password.", + }); + } + if (pass?.login_user?.account_status == "DISABLED") { + return Promise.reject({ + http: 409, + reason: "Account is disabled.", + }); + } + const key: string = util.generateKey(); + const futureDate = new Date(); + futureDate.setDate(futureDate.getDate() + session_key.valid_period); + return addSessionKey( + pass.login_user.login_user_id, + key, + futureDate + ).then((ins) => ({ + sessionkey: ins.session_key, + is_admin: orDefault(pass?.login_user?.is_admin, false), + is_coach: orDefault(pass?.login_user?.is_coach, false), + account_status: pass?.login_user?.account_status, + })); + }) + ); } /** @@ -36,14 +64,14 @@ async function login(req: express.Request): Promise { * @returns See the API documentation. Successes are passed using * `Promise.resolve`, failures using `Promise.reject`. */ -async function logout(req: express.Request): Promise { - return parseLogoutRequest(req) - .then(parsed => util.checkSessionKey(parsed)) - .then(checked => { - return removeAllKeysForUser(checked.data.sessionkey).then(() => { - return Promise.resolve({}); - }); - }) +export async function logout(req: express.Request): Promise { + return parseLogoutRequest(req) + .then((parsed) => util.checkSessionKey(parsed, false)) // logout can with pending account + .then(async (checked) => { + return removeAllKeysForUser(checked.data.sessionkey).then(() => { + return Promise.resolve({}); + }); + }); } /** @@ -52,11 +80,12 @@ async function logout(req: express.Request): Promise { * endpoints. */ export function getRouter(): express.Router { - const router: express.Router = express.Router(); + const router: express.Router = express.Router(); - router.post('/', (req, res) => util.respOrErrorNoReinject(res, login(req))); - router.delete('/', - (req, res) => util.respOrErrorNoReinject(res, logout(req))); - util.addInvalidVerbs(router, '/'); - return router; + router.post("/", (req, res) => util.respOrErrorNoReinject(res, login(req))); + router.delete("/", (req, res) => + util.respOrErrorNoReinject(res, logout(req)) + ); + util.addInvalidVerbs(router, "/"); + return router; } diff --git a/backend/routes/osoc.ts b/backend/routes/osoc.ts new file mode 100644 index 00000000..ccc3c5c8 --- /dev/null +++ b/backend/routes/osoc.ts @@ -0,0 +1,116 @@ +import express from "express"; +import * as rq from "../request"; +import { Responses } from "../types"; +import * as util from "../utility"; +import { errors } from "../utility"; +import * as ormO from "../orm_functions/osoc"; + +/** + * Attempts to create a new project in the system. + * @param req The Express.js request to extract all required data from. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export async function createOsocEdition( + req: express.Request +): Promise { + return rq + .parseNewOsocEditionRequest(req) + .then((parsed) => util.isAdmin(parsed)) + .then(async (parsed) => { + return ormO.createOsoc(parsed.data.year).then((osoc) => + Promise.resolve({ + data: { + id: osoc.osoc_id, + year: osoc.year, + }, + }) + ); + }); +} + +/** + * Attempts to list all osoc editions in the system. + * @param req The Express.js request to extract all required data from. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export async function listOsocEditions( + req: express.Request +): Promise { + const parsedRequest = await rq.parseOsocAllRequest(req); + const checkedSessionKey = await util + .checkSessionKey(parsedRequest) + .catch((res) => res); + if (checkedSessionKey.data == undefined) { + return Promise.reject(errors.cookInvalidID()); + } + const osocEditions = await ormO.getAllOsoc(); + + return Promise.resolve({ + data: osocEditions, + }); +} + +/** + * Attempts to filter osoc editions in the system by year. + * @param req The Express.js request to extract all required data from. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export async function filterYear( + req: express.Request +): Promise { + const parsedRequest = await rq.parseFilterOsocsRequest(req); + const checkedSessionKey = await util + .checkSessionKey(parsedRequest) + .catch((res) => res); + if (checkedSessionKey.data == undefined) { + return Promise.reject(errors.cookInvalidID()); + } + + const osocs = await ormO.filterOsocs( + checkedSessionKey.data.yearFilter, + checkedSessionKey.data.yearSort + ); + + return Promise.resolve({ + data: osocs, + }); +} + +/** + * @param req The Express.js request to extract all required data from. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export async function deleteOsocEditionRequest( + req: express.Request +): Promise { + return rq + .parseDeleteOsocEditionRequest(req) + .then((parsed) => util.isAdmin(parsed)) + .then(async (parsed) => { + return ormO + .deleteOsocFromDB(parsed.data.id) + .then(() => Promise.resolve({})); + }); +} + +/** + * Gets the router for all `/osoc/` related endpoints. + * @returns An Express.js {@link express.Router} routing all `/osoc/` + * endpoints. + */ +export function getRouter(): express.Router { + const router: express.Router = express.Router(); + + util.setupRedirect(router, "/osoc"); + util.route(router, "get", "/all", listOsocEditions); + util.route(router, "get", "/filter", filterYear); + util.route(router, "post", "/create", createOsocEdition); + util.route(router, "delete", "/:id", deleteOsocEditionRequest); + util.addAllInvalidVerbs(router, ["/", "/all, /filter, /create, /:id"]); + + return router; +} diff --git a/backend/routes/project.ts b/backend/routes/project.ts index 696a72ae..ac36b20d 100644 --- a/backend/routes/project.ts +++ b/backend/routes/project.ts @@ -1,8 +1,16 @@ -import express from 'express'; +import express from "express"; -import * as rq from '../request'; -import {Responses} from '../types'; -import * as util from '../utility'; +import * as ormCtr from "../orm_functions/contract"; +import * as ormEv from "../orm_functions/evaluation"; +import * as ormOsoc from "../orm_functions/osoc"; +import * as ormPr from "../orm_functions/project"; +import * as ormPrRole from "../orm_functions/project_role"; +import * as ormPU from "../orm_functions/project_user"; +import * as ormRole from "../orm_functions/role"; +import * as rq from "../request"; +import { InternalTypes, Responses, StringDict } from "../types"; +import * as util from "../utility"; +import { errors } from "../utility"; /** * Attempts to create a new project in the system. @@ -10,15 +18,34 @@ import * as util from '../utility'; * @returns See the API documentation. Successes are passed using * `Promise.resolve`, failures using `Promise.reject`. */ -async function createProject(req: express.Request): - Promise> { - return rq.parseNewProjectRequest(req) - .then(parsed => util.isAdmin(parsed)) - .then(parsed => { - // INSERTION LOGIC - return Promise.resolve( - {data : '', sessionkey : parsed.data.sessionkey}); - }); +export async function createProject( + req: express.Request +): Promise { + return rq + .parseNewProjectRequest(req) + .then((parsed) => util.isAdmin(parsed)) + .then(async (parsed) => { + return ormPr + .createProject({ + name: parsed.data.name, + partner: parsed.data.partner, + startDate: new Date(parsed.data.start), + endDate: new Date(parsed.data.end), + positions: Number(parsed.data.positions), + osocId: Number(parsed.data.osocId), + }) + .then((project) => + Promise.resolve({ + id: project.project_id, + name: project.name, + partner: project.partner, + start_date: project.start_date.toString(), + end_date: project.end_date.toString(), + positions: project.positions, + osoc_id: project.osoc_id, + }) + ); + }); } /** @@ -27,15 +54,44 @@ async function createProject(req: express.Request): * @returns See the API documentation. Successes are passed using * `Promise.resolve`, failures using `Promise.reject`. */ -async function listProjects(req: express.Request): - Promise { - return rq.parseProjectAllRequest(req) - .then(parsed => util.checkSessionKey(parsed)) - .then(parsed => { - // INSERTION LOGIC - return Promise.resolve( - {data : [], sessionkey : parsed.data.sessionkey}); - }); +export async function listProjects( + req: express.Request +): Promise { + return rq + .parseProjectAllRequest(req) + .then((parsed) => util.checkSessionKey(parsed)) + .then(async () => + ormPr + .getAllProjects() + .then((obj) => + Promise.all( + obj.map(async (val) => { + const students = await ormCtr.contractsByProject( + val.project_id + ); + const users = await ormPU.getUsersFor( + val.project_id + ); + return Promise.resolve({ + id: Number(val.project_id), + name: val.name, + partner: val.partner, + start_date: val.start_date.toString(), + end_date: val.end_date.toString(), + positions: val.positions, + osoc_id: val.osoc_id, + students: students, + coaches: users, + }); + }) + ) + ) + .then((obj) => + Promise.resolve({ + data: obj, + }) + ) + ); } /** @@ -44,25 +100,69 @@ async function listProjects(req: express.Request): * @returns See the API documentation. Successes are passed using * `Promise.resolve`, failures using `Promise.reject`. */ -async function getProject(req: express.Request): Promise { - return rq.parseSingleProjectRequest(req) - .then(parsed => util.isAdmin(parsed)) - .then(parsed => { - // INSERTION LOGIC - return Promise.resolve( - {data : '', sessionkey : parsed.data.sessionkey}); - }); +export async function getProject( + req: express.Request +): Promise { + return rq + .parseSingleProjectRequest(req) + .then((parsed) => util.isAdmin(parsed)) + .then((parsed) => util.isValidID(parsed.data, "project")) + .then(async (parsed) => + ormPr.getProjectById(parsed.id).then(async (obj) => { + if (obj !== null) { + const students = await ormCtr.contractsByProject( + obj.project_id + ); + const users = await ormPU.getUsersFor(obj.project_id); + return Promise.resolve({ + id: Number(obj.project_id), + name: obj.name, + partner: obj.partner, + start_date: obj.start_date.toString(), + end_date: obj.end_date.toString(), + positions: obj.positions, + osoc_id: obj.osoc_id, + students: students, + coaches: users, + }); + } else { + return Promise.reject(errors.cookInvalidID()); + } + }) + ); } -async function modProject(req: express.Request): - Promise> { - return rq.parseUpdateProjectRequest(req) - .then(parsed => util.isAdmin(parsed)) - .then(parsed => { - // UPDATING LOGIC - return Promise.resolve( - {data : '', sessionkey : parsed.data.sessionkey}); - }); +export async function modProject( + req: express.Request +): Promise { + return rq + .parseUpdateProjectRequest(req) + .then((parsed) => util.isAdmin(parsed)) + .then((parsed) => util.isValidID(parsed.data, "project")) + .then(async (parsed) => { + // UPDATING LOGIC + return ormPr + .updateProject({ + projectId: parsed.id, + name: parsed.name, + partner: parsed.partner, + startDate: parsed.start, + endDate: parsed.end, + positions: parsed.positions, + osocId: parsed.osocId, + }) + .then((project) => + Promise.resolve({ + id: project.project_id, + name: project.name, + partner: project.partner, + start_date: project.start_date.toString(), + end_date: project.end_date.toString(), + positions: project.positions, + osoc_id: project.osoc_id, + }) + ); + }); } /** @@ -71,62 +171,349 @@ async function modProject(req: express.Request): * @returns See the API documentation. Successes are passed using * `Promise.resolve`, failures using `Promise.reject`. */ -async function deleteProject(req: express.Request): Promise { - return rq.parseDeleteProjectRequest(req) - .then(parsed => util.isAdmin(parsed)) - .then(parsed => { - // REMOVING LOGIC - return Promise.resolve({sessionkey : parsed.data.sessionkey}); - }); +export async function deleteProject( + req: express.Request +): Promise { + return rq + .parseDeleteProjectRequest(req) + .then((parsed) => util.isAdmin(parsed)) + .then((parsed) => util.isValidID(parsed.data, "project")) + .then(async (parsed) => { + return ormPr + .deleteProject(parsed.id) + .then(() => Promise.resolve({})); + }); } /** - * Attempts to get all data for the requests of coaches in the system. + * Attempts to get all drafted students in the system for a project. * @param req The Express.js request to extract all required data from. * @returns See the API documentation. Successes are passed using * `Promise.resolve`, failures using `Promise.reject`. */ -async function getDraftedStudents(req: express.Request): - Promise { - - return rq.parseGetDraftedStudentsRequest(req) - .then(parsed => util.checkSessionKey(parsed)) - .then(parsed => { - // INSERTION LOGIC - return Promise.resolve({ - data : {id : 0, name : '', students : []}, - sessionkey : parsed.data.sessionkey +export async function getDraftedStudents( + req: express.Request +): Promise { + return rq + .parseGetDraftedStudentsRequest(req) + .then((parsed) => util.checkSessionKey(parsed)) + .then(async (parsed) => { + const prName = await ormPr + .getProjectById(parsed.data.id) + .then((pr) => pr?.name); + + return ormCtr.contractsByProject(parsed.data.id).then(async (arr) => + Promise.resolve({ + id: parsed.data.id, + name: util.getOrDefault(prName, "(unnamed project)"), + students: arr.map((obj) => ({ + student: obj.student, + status: obj.contract_status, + })), + }) + ); + }); +} + +export async function getFreeSpotsFor( + role: string, + project: number +): Promise<{ count: number; role: number }> { + return ormPrRole + .getProjectRolesByProject(project) + .then((roles) => + Promise.all( + roles.map(async (r) => + ormRole.getRole(r.role_id).then((upd) => + Promise.resolve({ + project_role_id: r.project_role_id, + role_id: r.role_id, + block: upd, + }) + ) + ) + ) + ) + .then((roles) => roles.filter((r) => r.block?.name == role)) + .then(async (rest) => { + console.log("Resulting roles: " + JSON.stringify(rest)); + if (rest.length != 1) return Promise.reject(); + return ormPrRole + .getNumberOfFreePositions(rest[0].project_role_id) + .then((n) => { + if (n == null) return Promise.reject(); + return Promise.resolve({ + count: n, + role: rest[0].project_role_id, + }); + }); + }); +} + +export async function createProjectRoleFor( + project: number, + role: string +): Promise<{ count: number; role: number }> { + return ormRole + .getRolesByName(role) + .then((r) => { + if (r == null) + return Promise.reject({ + http: 409, + reason: "That role doesn't exist.", + }); + return ormPrRole.createProjectRole({ + projectId: project, + roleId: r.role_id, + positions: 1, + }); + }) + .then((res) => + Promise.resolve({ count: res.positions, role: res.project_role_id }) + ); +} + +export async function modProjectStudent( + req: express.Request +): Promise { + return rq + .parseDraftStudentRequest(req) + .then((parsed) => util.isAdmin(parsed)) + .then(async (parsed) => { + console.log( + "Attempting to modify project " + + parsed.data.id + + " by moving student " + + parsed.data.studentId + + " to role `" + + parsed.data.role + + "`" + ); + return ormCtr + .contractsByProject(parsed.data.id) + .then((arr) => + arr.filter( + (v) => v.student.student_id == parsed.data.studentId + ) + ) + .then((arr) => { + if (arr.length == 0) { + return Promise.reject({ + http: 204, + reason: "The selected student is not assigned to this project.", + }); + } + + if (arr.length > 1) { + return Promise.reject({ + http: 409, + reason: "The request is ambiguous.", + }); + } + + return Promise.resolve(arr[0]); + }) + .then(async (ctr) => { + return getFreeSpotsFor(parsed.data.role, parsed.data.id) + .catch(() => + createProjectRoleFor( + parsed.data.id, + parsed.data.role + ) + ) + .then((remaining) => { + if (remaining.count <= 0) { + return Promise.reject({ + http: 409, + reason: "Can't add this role to the student. There are no more vacant spots.", + }); + } + + return ormCtr.updateContract({ + contractId: ctr.contract_id, + loginUserId: parsed.userId, + projectRoleId: remaining.role, + }); + }); + }) + .then((res) => + ormPrRole.getProjectRoleById(res.project_role_id) + ) + .then((res) => + Promise.resolve({ + drafted: true, + role: util.getOrDefault(res?.role.name, ""), + }) + ); + }); +} + +export async function unAssignStudent( + req: express.Request +): Promise { + return rq + .parseRemoveAssigneeRequest(req) + .then((parsed) => util.checkSessionKey(parsed)) + .then(async (checked) => { + return ormCtr + .contractsForStudent(Number(checked.data.studentId)) + .then((ctrs) => + ctrs.filter( + (contr) => + contr.project_role.project_id == checked.data.id + ) + ) + .then(async (found) => { + if (found.length == 0) { + return Promise.reject({ + http: 400, + reason: + "The student with ID " + + checked.data.studentId.toString() + + " is not assigned to project " + + checked.data.id, + }); + } + + for (const contr of found) { + await ormEv + .getEvaluationByPartiesFor( + checked.userId, + contr.student.student_id, + contr.project_role.project.osoc_id + ) + .then((evl) => { + if (evl.length != 1) { + return Promise.reject({ + http: 400, + reason: "Multiple evaluations match.", + }); + } + + return ormEv.updateEvaluationForStudent({ + evaluation_id: evl[0].evaluation_id, + loginUserId: checked.userId, + motivation: + util.getOrDefault( + evl[0].motivation, + "" + ) + + " [Removed assignee from project " + + checked.data.id + + "]", + }); + }) + .then(() => + ormCtr.removeContract(contr.contract_id) + ); + } + + return Promise.resolve({}); + }); }); - }); } -async function modProjectStudent(req: express.Request): - Promise { - return rq.parseDraftStudentRequest(req) - .then(parsed => util.isAdmin(parsed)) - .then(parsed => { - // INSERTION LOGIC - return Promise.resolve({ - data : {drafted : false, roles : []}, - sessionkey : parsed.data.sessionkey +export async function getProjectConflicts( + req: express.Request +): Promise { + return rq + .parseProjectConflictsRequest(req) + .then((parsed) => util.checkSessionKey(parsed)) + .then(async () => { + return ormOsoc + .getNewestOsoc() + .then((osoc) => util.getOrReject(osoc)) + .then((osoc) => + ormCtr.sortedContractsByOsocEdition(osoc.osoc_id) + ) + .then((contracts) => { + if (contracts.length == 0 || contracts.length == 1) + return Promise.resolve([]); + const res: StringDict = {}; + let latestid = contracts[0].student_id; + for (let i = 1; i < contracts.length; i++) { + if (contracts[i].student_id == latestid) { + const idStr: string = + contracts[i].student_id.toString(); + if (!(idStr in res)) { + res[idStr] = [contracts[i - 1], contracts[i]]; + } else { + res[idStr].push(contracts[i]); + } + } + latestid = contracts[i].student_id; + } + + const arr: InternalTypes.Conflict[] = []; + for (const idStr in res) { + arr.push({ + student: Number(idStr), + projects: res[idStr].map((p) => ({ + id: p.project_role.project.project_id, + name: p.project_role.project.name, + })), + }); + } + return Promise.resolve(arr); + }) + .then((arr) => + Promise.resolve({ + data: arr, + }) + ); }); - }); } -// TODO project conflicts -/*async function getProjectConflicts(req: express.Request): - Promise { - return util.checkSessionKey(req).then(async (_) => { - // check valid id - // if invalid: return Promise.reject(util.cookInvalidID()); - // if valid: modify a student of this project - let roles : string[] = []; - return Promise.resolve({ - data : {drafted : true, roles : roles}, - sessionkey : '' +/** + * Attempts to filter projects in the system by name, client, coaches or fully assigned. + * @param req The Express.js request to extract all required data from. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export async function filterProjects( + req: express.Request +): Promise { + const parsedRequest = await rq.parseFilterProjectsRequest(req); + const checkedSessionKey = await util + .checkSessionKey(parsedRequest) + .catch((res) => res); + if (checkedSessionKey.data == undefined) { + return Promise.reject(errors.cookInvalidID()); + } + + const projects = await ormPr.filterProjects( + checkedSessionKey.data.projectNameFilter, + checkedSessionKey.data.clientNameFilter, + checkedSessionKey.data.assignedCoachesFilterArray, + checkedSessionKey.data.fullyAssignedFilter, + checkedSessionKey.data.projectNameSort, + checkedSessionKey.data.clientNameSort, + checkedSessionKey.data.fullyAssignedSort + ); + + const projectlist = []; + + for (const project of projects) { + const contracts = await ormCtr.contractsByProject(project.project_id); + const users = await ormPU.getUsersFor(project.project_id); + + projectlist.push({ + id: project.project_id, + name: project.name, + partner: project.partner, + start_date: project.start_date, + end_data: project.end_date, + positions: project.positions, + osoc_id: project.osoc_id, + contracts: contracts, + coaches: users, }); + } + + return Promise.resolve({ + data: projectlist, }); -}*/ +} /** * Gets the router for all `/coaches/` related endpoints. @@ -134,25 +521,33 @@ async function modProjectStudent(req: express.Request): * endpoints. */ export function getRouter(): express.Router { - const router: express.Router = express.Router(); + const router: express.Router = express.Router(); + + util.setupRedirect(router, "/project"); + util.route(router, "get", "/filter", filterProjects); + util.route(router, "get", "/all", listProjects); + + util.route(router, "get", "/:id", getProject); + util.route(router, "post", "/", createProject); + + util.route(router, "post", "/:id", modProject); + util.route(router, "delete", "/:id", deleteProject); - util.setupRedirect(router, '/project'); - util.route(router, "get", "/all", listProjects); - util.route(router, "post", "/:id", createProject); - util.route(router, "get", "/:id", getProject); + util.route(router, "get", "/:id/draft", getDraftedStudents); + util.route(router, "post", "/:id/draft", modProjectStudent); - util.route(router, "post", "/:id", modProject); - router.delete('/:id', (req, res) => util.respOrErrorNoReinject( - res, deleteProject(req))); + util.route(router, "delete", "/:id/assignee", unAssignStudent); - util.route(router, "get", "/:id/draft", getDraftedStudents); - util.route(router, "post", "/:id/draft", modProjectStudent); - // TODO project conflicts - // util.route(router, "get", "/conflicts", getProjectConflicts); + util.route(router, "get", "/conflicts", getProjectConflicts); - // TODO add project conflicts - util.addAllInvalidVerbs( - router, [ "/", "/all", "/:id", "/:id/draft", "/request/:id" ]); + // TODO add project conflicts + util.addAllInvalidVerbs(router, [ + "/", + "/all", + "/:id", + "/:id/draft", + "/request/:id", + ]); - return router; + return router; } diff --git a/backend/routes/reset.ts b/backend/routes/reset.ts new file mode 100644 index 00000000..23987e65 --- /dev/null +++ b/backend/routes/reset.ts @@ -0,0 +1,263 @@ +import express from "express"; +import * as gapi from "googleapis"; +import * as nodemailer from "nodemailer"; + +import * as config from "../config.json"; +import * as ormLU from "../orm_functions/login_user"; +import * as ormPR from "../orm_functions/password_reset"; +import * as ormP from "../orm_functions/person"; +import * as ormSK from "../orm_functions/session_key"; +import * as rq from "../request"; +import { Email, Responses } from "../types"; +import * as util from "../utility"; +import * as session_key from "./session_key.json"; + +export async function sendMail(mail: Email) { + const oauthclient = new gapi.Auth.OAuth2Client( + process.env.GOOGLE_CLIENT_ID, + process.env.GOOGLE_CLIENT_SECRET + ); + oauthclient.setCredentials({ + refresh_token: process.env.GOOGLE_REFRESH_TOKEN, + }); + const accesstoken = await oauthclient + .getAccessToken() + .then((token) => { + if (token != null && token.token != null) { + return token.token; + } else { + return Promise.reject( + new Error("Received token from Google OAuth was null") + ); + } + }) + .catch((e) => console.log("Email error:" + JSON.stringify(e))); + + const transp = nodemailer.createTransport({ + service: "gmail", + auth: { + type: "OAUTH2", + user: "osoc2.be@gmail.com", + clientId: process.env.GOOGLE_CLIENT_ID, + clientSecret: process.env.GOOGLE_CLIENT_SECRET, + refreshToken: process.env.GOOGLE_REFRESH_TOKEN, + accessToken: accesstoken as string, + }, + }); + + return transp + .sendMail({ + from: config.email.from, + to: mail.to, + subject: mail.subject, + html: mail.html, + }) + .then((res) => { + transp.close(); + return Promise.resolve(res); + }) + .catch((e) => { + console.log("Email error: " + JSON.stringify(e)); + return Promise.reject(config.apiErrors.reset.sendEmail); + }); +} + +/** + * Handles password reset requests + * If the email in the 'GET' body is correct, an email with a reset link will be + * sent. If not returns an error. Route: `/reset` + * @param req Request body should be of form { email: string } + */ +export async function requestReset( + req: express.Request +): Promise { + return rq.parseRequestResetRequest(req).then((parsed) => + ormP.getPasswordPersonByEmail(parsed.email).then(async (person) => { + if (person == null || person.login_user == null) { + return Promise.reject(config.apiErrors.reset.invalidEmail); + } + const date: Date = new Date(Date.now()); + date.setHours(date.getHours() + 24); + return ormPR + .createOrUpdateReset( + person.login_user.login_user_id, + util.generateKey(), + date + ) + .catch((e) => { + console.log(e); + return Promise.reject(); + }) + .then(async (code) => { + return sendMail({ + to: parsed.email, + subject: config.email.header, + html: createEmail(code.reset_id), + }).then((data) => { + console.log(data); + nodemailer.getTestMessageUrl(data); + return Promise.resolve({}); + }); + }); + }) + ); +} + +/** + * Route used to check if a reset code is valid. + * Use route `/reset/:id` with a 'GET' request. + * @param req + */ +export async function checkCode( + req: express.Request +): Promise { + return rq + .parseCheckResetCodeRequest(req) + .then((parsed) => ormPR.findResetByCode(parsed.code)) + .then((res) => { + if (res == null || res.valid_until < new Date(Date.now())) + return Promise.reject(); + + return Promise.resolve({}); + }) + .catch(() => Promise.reject(config.apiErrors.reset.resetFailed)); +} + +function setSessionKey(req: express.Request, key: string): void { + req.headers.authorization = config.global.authScheme + " " + key; +} + +/** + * Route that will reset the password when the code and password are valid. + * Use route `/reset/:id` with a 'POST' request with body of form + * { password: string } + * @param req + */ +export async function resetPassword( + req: express.Request +): Promise { + return rq.parseResetPasswordRequest(req).then((parsed) => + ormPR.findResetByCode(parsed.code).then(async (code) => { + if (code == null || code.valid_until < new Date(Date.now())) + return Promise.reject(util.errors.cookArgumentError()); + + return ormLU + .getLoginUserById(code.login_user_id) + .then((user) => { + if (user == null) return Promise.reject(); + return ormLU.updateLoginUser({ + loginUserId: user.login_user_id, + isAdmin: user.is_admin, + isCoach: user.is_coach, + accountStatus: user?.account_status, + password: parsed.password, + }); + }) + .then((user) => { + console.log(JSON.stringify(user)); + const futureDate = new Date(); + futureDate.setDate( + futureDate.getDate() + session_key.valid_period + ); + return ormSK.addSessionKey( + user.login_user_id, + util.generateKey(), + futureDate + ); + }) + .then(async (key) => { + return ormPR + .deleteResetWithResetId(code.reset_id) + .then(() => + Promise.resolve({ sessionkey: key.session_key }) + ); + }) + .then((v) => { + setSessionKey(req, v.sessionkey); + return v; + }) + .catch(() => + Promise.reject(config.apiErrors.reset.resetFailed) + ); + }) + ); +} + +export function getRouter(): express.Router { + const router: express.Router = express.Router(); + + router.post("/", (req, res) => + util.respOrErrorNoReinject(res, requestReset(req)) + ); + router.get("/:id", (req, res) => + util.respOrErrorNoReinject(res, checkCode(req)) + ); + util.route(router, "post", "/:id", resetPassword); + + util.addAllInvalidVerbs(router, ["/", "/:id"]); + + return router; +} + +/** + * Returns the html body for the email with the reset code applied. + * @param resetID The ID that validates the password reset. + */ +export function createEmail(resetID: string) { + return ` + + + Selections - Password Reset + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

Selections

+
You have requested a password reset for your OSOC Selections account. + Please click the link below to reset your password.
Note: This link is only valid for 24 hours.
Reset Password
If you believe that the password reset was not requested by you, please contact us as soon as possibe at osoc2.be@gmail.com
+ + + `; +} diff --git a/backend/routes/role.ts b/backend/routes/role.ts new file mode 100644 index 00000000..2a18ad40 --- /dev/null +++ b/backend/routes/role.ts @@ -0,0 +1,64 @@ +import express from "express"; +import { Responses } from "../types"; +import * as rq from "../request"; +import * as util from "../utility"; +import * as ormRo from "../orm_functions/role"; + +/** + * Attempts to list all roles in the system. + * @param req The Express.js request to extract all required data from. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export async function listStudentRoles( + req: express.Request +): Promise { + return rq + .parseRolesAllRequest(req) + .then((parsed) => util.checkSessionKey(parsed)) + .then(() => { + return ormRo.getAllRoles().then((roles) => + Promise.resolve({ + data: roles, + }) + ); + }); +} + +/** + * Attempts to create a new role in the system. + * @param req The Express.js request to extract all required data from. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export async function createStudentRole( + req: express.Request +): Promise { + return rq + .parseStudentRoleRequest(req) + .then((parsed) => util.checkSessionKey(parsed)) + .then(async (parsed) => { + return ormRo.createRole(parsed.data.name).then((role) => { + return Promise.resolve({ name: role.name, id: role.role_id }); + }); + }); +} + +/** + * Gets the router for all `/role/` related endpoints. + * @returns An Express.js {@link express.Router} routing all `/role/` + * endpoints. + */ +export function getRouter(): express.Router { + const router: express.Router = express.Router(); + + util.setupRedirect(router, "/role"); + + util.route(router, "get", "/all", listStudentRoles); + + util.route(router, "post", "/create", createStudentRole); + + util.addAllInvalidVerbs(router, ["/", "/all", "/create"]); + + return router; +} diff --git a/backend/routes/session_key.json b/backend/routes/session_key.json new file mode 100644 index 00000000..8028767c --- /dev/null +++ b/backend/routes/session_key.json @@ -0,0 +1 @@ +{"valid_period": 20} \ No newline at end of file diff --git a/backend/routes/student.ts b/backend/routes/student.ts index c3e4d0fa..e38ed238 100644 --- a/backend/routes/student.ts +++ b/backend/routes/student.ts @@ -1,13 +1,17 @@ -import express from 'express'; +import express from "express"; -import * as ormEv from '../orm_functions/evaluation'; -import * as ormJo from '../orm_functions/job_application'; -import * as ormLa from '../orm_functions/language'; -import * as ormSt from '../orm_functions/student'; -import * as rq from '../request'; -import {InternalTypes, Responses} from '../types'; -import * as util from '../utility'; -import {errors} from '../utility'; +import * as ormEv from "../orm_functions/evaluation"; +import * as ormJo from "../orm_functions/job_application"; +import * as ormLa from "../orm_functions/language"; +import * as ormRo from "../orm_functions/role"; +import * as ormSt from "../orm_functions/student"; +import * as ormLU from "../orm_functions/login_user"; +import * as ormOs from "../orm_functions/osoc"; +import * as rq from "../request"; +import { Responses } from "../types"; +import * as util from "../utility"; +import { errors } from "../utility"; +import * as ormP from "../orm_functions/person"; /** * Attempts to list all students in the system. @@ -15,54 +19,75 @@ import {errors} from '../utility'; * @returns See the API documentation. Successes are passed using * `Promise.resolve`, failures using `Promise.reject`. */ -async function listStudents(req: express.Request): - Promise { - return rq.parseStudentAllRequest(req) - .then(parsed => util.checkSessionKey(parsed)) - .then(parsed => { - const studentList: InternalTypes.Student[] = []; - ormSt.getAllStudents().then( - students => {students.forEach( - student => { - ormJo.getLatestJobApplicationOfStudent(student.student_id) - .then(jobApplication => { - if (jobApplication !== null) { - ormJo.getStudentEvaluationsTotal(student.student_id) - .then(evaluations => { - const languages: string[] = []; - jobApplication.job_application_skill.forEach( - skill => { - ormLa.getLanguage(skill.language_id) - .then(language => { - if (language !== null) { - languages.push(language.name); - } else { - return Promise.reject( - errors.cookInvalidID); - } - })}); - studentList.push({ - firstname : student.person.firstname, - lastname : student.person.lastname, - email : student.person.email, - // gender : student.gender, - pronouns : student.pronouns, - phoneNumber : student.phone_number, - nickname : student.nickname, - alumni : student.alumni, - languages : languages, - jobApplication : jobApplication, - evaluations : evaluations - }) - }) - } else { - return Promise.reject(errors.cookInvalidID); - } - })})}); - - return Promise.resolve( - {data : studentList, sessionkey : parsed.data.sessionkey}); - }); +export async function listStudents( + req: express.Request +): Promise { + const parsedRequest = await rq.parseStudentAllRequest(req); + const checkedSessionKey = await util + .checkSessionKey(parsedRequest) + .catch((res) => res); + if (checkedSessionKey.data == undefined) { + return Promise.reject(errors.cookInvalidID()); + } + const studentList: object[] = []; + const students = await ormSt.getAllStudents(); + for (let studentIndex = 0; studentIndex < students.length; studentIndex++) { + const jobApplication = await ormJo.getLatestJobApplicationOfStudent( + students[studentIndex].student_id + ); + if (jobApplication != null) { + const roles = []; + for (const applied_role of jobApplication.applied_role) { + const role = await ormRo.getRole(applied_role.role_id); + if (role != null) { + roles.push(role.name); + } else { + return Promise.reject(errors.cookInvalidID()); + } + } + + const evaluations = await ormJo.getStudentEvaluationsTotal( + students[studentIndex].student_id + ); + + for ( + let skillIndex = 0; + skillIndex < jobApplication.job_application_skill.length; + skillIndex++ + ) { + if ( + jobApplication.job_application_skill[skillIndex] + .language_id != null + ) { + const language = await ormLa.getLanguage( + Number( + jobApplication.job_application_skill[skillIndex] + .language_id + ) + ); + if (language != null) { + jobApplication.job_application_skill[skillIndex].skill = + language.name; + } else { + return Promise.reject(errors.cookInvalidID()); + } + } + } + + studentList.push({ + student: students[studentIndex], + jobApplication: jobApplication, + evaluations: evaluations, + roles: roles, + }); + } else { + return Promise.reject(errors.cookInvalidID()); + } + } + + return Promise.resolve({ + data: studentList, + }); } /** @@ -71,89 +96,61 @@ async function listStudents(req: express.Request): * @returns See the API documentation. Successes are passed using * `Promise.resolve`, failures using `Promise.reject`. */ -async function getStudent(req: express.Request): Promise { - return rq.parseSingleStudentRequest(req) - .then(parsed => util.checkSessionKey(parsed)) - .then(parsed => util.isValidID(parsed.data, "student")) - .then(async parsed => { - // FETCHING LOGIC - return ormSt.getStudent(parsed.id).then(async student => { - if (student !== null) { - return ormJo - .getLatestJobApplicationOfStudent(student.student_id) - .then(async jobApplication => { - if (jobApplication !== null) { - return ormJo - .getStudentEvaluationsTotal(student.student_id) - .then(evaluations => { - const languages: string[] = []; - jobApplication.job_application_skill.forEach( - skill => { - ormLa.getLanguage(skill.language_id) - .then(language => { - if (language !== null) { - languages.push(language.name); - } else { - return Promise.reject( - errors.cookInvalidID); - } - })}); - return Promise.resolve({ - data : { - firstname : student.person.firstname, - lastname : student.person.lastname, - email : student.person.email, - // gender : student.gender, - pronouns : student.pronouns, - phoneNumber : student.phone_number, - nickname : student.nickname, - alumni : student.alumni, - languages : languages, - jobApplication : jobApplication, - evaluations : evaluations, - }, - sessionkey : parsed.sessionkey - }) - }) - } else { - return Promise.reject(errors.cookInvalidID); - } - }) - } else { - return Promise.reject(errors.cookInvalidID()); - } - })}); -} +export async function getStudent( + req: express.Request +): Promise { + const parsedRequest = await rq.parseSingleStudentRequest(req); + const checkedSessionKey = await util + .checkSessionKey(parsedRequest) + .catch((res) => res); + if (checkedSessionKey.data == undefined) { + return Promise.reject(errors.cookInvalidID()); + } -/** - * Attempts to update a student. - * @param req The Express.js request to extract all required data from. - * @returns See the API documentation. Successes are passed using - * `Promise.resolve`, failures using `Promise.reject`. - */ -async function modStudent(req: express.Request): Promise { - return rq.parseUpdateStudentRequest(req) - .then(parsed => util.isAdmin(parsed)) - .then(parsed => util.isValidID(parsed.data, 'student')) - .then(async parsed => {// UPDATE LOGIC - return ormSt - .updateStudent({ - studentId : parsed.id, - gender : parsed.gender, - pronouns : parsed.pronouns, - phoneNumber : parsed.phone, - nickname : parsed.nickname, - alumni : parsed.alumni - }) - .then(student => {return Promise.resolve({ - data : { - pronouns : student.pronouns, - phone_number : student.phone_number, - nickname : student.nickname, - alumni : student.alumni, - }, - sessionkey : parsed.sessionkey - })})}); + const student = await ormSt.getStudent(checkedSessionKey.data.id); + if (student == null) { + return Promise.reject(errors.cookInvalidID()); + } + + const jobApplication = await ormJo.getLatestJobApplicationOfStudent( + student.student_id + ); + if (jobApplication == null) { + return Promise.reject(errors.cookInvalidID()); + } + + const roles = []; + for (const applied_role of jobApplication.applied_role) { + const role = await ormRo.getRole(applied_role.role_id); + if (role != null) { + roles.push(role.name); + } else { + return Promise.reject(errors.cookInvalidID()); + } + } + + const evaluations = await ormJo.getStudentEvaluationsTotal( + student.student_id + ); + + for (const job_application_skill of jobApplication.job_application_skill) { + if (job_application_skill.language_id != null) { + const language = await ormLa.getLanguage( + job_application_skill.language_id + ); + if (language == null) { + return Promise.reject(errors.cookInvalidID()); + } + job_application_skill.skill = language.name; + } + } + + return Promise.resolve({ + student: student, + jobApplication: jobApplication, + evaluations: evaluations, + roles: roles, + }); } /** @@ -162,14 +159,21 @@ async function modStudent(req: express.Request): Promise { * @returns See the API documentation. Successes are passed using * `Promise.resolve`, failures using `Promise.reject`. */ -async function deleteStudent(req: express.Request): Promise { - return rq.parseDeleteStudentRequest(req) - .then(parsed => util.isAdmin(parsed)) - .then(parsed => util.isValidID(parsed.data, 'student')) - .then(async parsed => {// DELETE LOGIC - return ormSt.deleteStudent(parsed.id).then( - () => {return Promise.resolve( - {sessionkey : parsed.sessionkey})})}); +export async function deleteStudent( + req: express.Request +): Promise { + return rq + .parseDeleteStudentRequest(req) + .then((parsed) => util.isAdmin(parsed)) + .then(async (parsed) => { + return ormSt + .deleteStudent(parsed.data.id) + .then(() => + ormP + .deletePersonById(parsed.data.id) + .then(() => Promise.resolve({})) + ); + }); } /** @@ -178,85 +182,138 @@ async function deleteStudent(req: express.Request): Promise { * @returns See the API documentation. Successes are passed using * `Promise.resolve`, failures using `Promise.reject`. */ -async function createStudentSuggestion(req: express.Request): - Promise { - return rq.parseSuggestStudentRequest(req) - .then(parsed => util.checkSessionKey(parsed)) - .then(async parsed => { - // SUGGESTING LOGIC - return ormSt.getStudent(parsed.data.id).then(async student => { - if (student !== null) { - return ormJo - .getLatestJobApplicationOfStudent(student.student_id) - .then(async jobApplication => { - if (jobApplication !== null) { - return ormEv - .createEvaluationForStudent({ - loginUserId : parsed.userId, - jobApplicationId : - jobApplication.job_application_id, - decision : parsed.data.suggestion, - motivation : parsed.data.reason, - isFinal : true - }) - .then( - () => {return Promise.resolve( - {sessionkey : parsed.data.sessionkey})}) - } else { - return Promise.reject(errors.cookInvalidID()); - } - }) - } else { - return Promise.reject(errors.cookInvalidID()); - } - })}); +export async function createStudentSuggestion( + req: express.Request +): Promise { + const parsedRequest = await rq.parseSuggestStudentRequest(req); + const checkedSessionKey = await util + .checkSessionKey(parsedRequest) + .catch((res) => res); + if (checkedSessionKey.data == undefined) { + return Promise.reject(errors.cookInvalidID()); + } + + const student = await ormSt.getStudent(checkedSessionKey.data.id); + if (student == null) { + return Promise.reject(errors.cookInvalidID()); + } + + const osocYear = await ormOs.getLatestOsoc(); + + if (osocYear == null) { + return Promise.reject(errors.cookNoDataError()); + } + + const suggestionsTotal = ( + await ormJo.getStudentEvaluationsTemp(student.student_id) + ).filter( + (suggestion) => + suggestion.osoc.year === osocYear.year && + suggestion.evaluation.some( + (evaluation) => + evaluation.login_user.login_user_id === + checkedSessionKey.userId + ) + ); + + const jobApplication = await ormJo.getLatestJobApplicationOfStudent( + student.student_id + ); + if (jobApplication == null) { + return Promise.reject(errors.cookInvalidID()); + } + + let newEvaluation; + if (suggestionsTotal.length > 0) { + const suggestion = suggestionsTotal[0].evaluation.filter( + (evaluation) => + evaluation.login_user.login_user_id === checkedSessionKey.userId + ); + + newEvaluation = await ormEv.updateEvaluationForStudent({ + evaluation_id: suggestion[0].evaluation_id, + loginUserId: checkedSessionKey.userId, + decision: checkedSessionKey.data.suggestion, + motivation: checkedSessionKey.data.reason, + }); + } else { + newEvaluation = await ormEv.createEvaluationForStudent({ + loginUserId: checkedSessionKey.userId, + jobApplicationId: jobApplication.job_application_id, + decision: checkedSessionKey.data.suggestion, + motivation: checkedSessionKey.data.reason, + isFinal: false, + }); + } + + const loginUser = await ormLU.getLoginUserById(newEvaluation.login_user_id); + if (loginUser === null) { + return Promise.reject(errors.cookInvalidID()); + } + + return Promise.resolve({ + evaluation_id: newEvaluation.evaluation_id, + senderFirstname: loginUser.person.firstname, + senderLastname: loginUser.person.lastname, + reason: newEvaluation.motivation, + decision: newEvaluation.decision, + isFinal: newEvaluation.is_final, + }); } /** - * Attempts to list all student suggestions in the system. + * Attempts to list all suggestions for a certain student. * @param req The Express.js request to extract all required data from. * @returns See the API documentation. Successes are passed using * `Promise.resolve`, failures using `Promise.reject`. */ -async function getStudentSuggestions(req: express.Request): - Promise { - return rq.parseStudentGetSuggestsRequest(req) - .then(parsed => util.checkSessionKey(parsed)) - .then(parsed => util.isValidID(parsed.data, 'student')) - .then(parsed => { - // FETCHING LOGIC - /*let suggestionsList : InternalTypes.SuggestionInfo[] = []; - ormSt.getStudent(parsed.id) - .then(student => { - if (student !== null) { - ormJo.getLatestJobApplicationOfStudent(student.student_id).then(jobApplication - => { if(jobApplication !== null) { - ormJo.getStudentEvaluationsTemp(student.student_id).then(suggestions - => { suggestions.forEach(suggestion => { suggestionsList.push({ - suggestion: suggestion, - sender: - ormEv.getLoginUserByEvaluationId(suggestion.evaluation.evaluation_id) - }) - }) - }) - } else { - return Promise.reject(errors.cookInvalidID()); - } - }) - } else { - return Promise.reject(errors.cookInvalidID()); - } - })*/ +export async function getStudentSuggestions( + req: express.Request +): Promise { + const parsedRequest = await rq.parseGetSuggestionsStudentRequest(req); + const checkedSessionKey = await util + .checkSessionKey(parsedRequest) + .catch((res) => res); + if (checkedSessionKey.data == undefined) { + return Promise.reject(errors.cookInvalidID()); + } + + const student = await ormSt.getStudent(checkedSessionKey.data.id); + if (student == null) { + return Promise.reject(errors.cookInvalidID()); + } + const osoc = + checkedSessionKey.data.year == undefined + ? await ormOs.getLatestOsoc() + : checkedSessionKey.data.year; + if (osoc == null) { return Promise.resolve({ - data : [ { - suggestion : "YES", - sender : {id : 0, name : "Darth Vader"}, - reason : "no reason" - } ], - sessionkey : parsed.sessionkey + data: [], + sessionkey: checkedSessionKey.data.sessionkey, }); - }); + } + const suggestionsTotal = ( + await ormJo.getStudentEvaluationsTotal(student.student_id) + ).filter((suggestion) => suggestion.osoc.year === osoc.year); + + const suggestionsInfo = []; + for (const suggestion of suggestionsTotal) { + for (const evaluation of suggestion.evaluation) { + suggestionsInfo.push({ + evaluation_id: evaluation.evaluation_id, + senderFirstname: evaluation.login_user.person.firstname, + senderLastname: evaluation.login_user.person.lastname, + reason: evaluation.motivation, + decision: evaluation.decision, + isFinal: evaluation.is_final, + }); + } + } + + return Promise.resolve({ + data: suggestionsInfo, + }); } /** @@ -265,31 +322,126 @@ async function getStudentSuggestions(req: express.Request): * @returns See the API documentation. Successes are passed using * `Promise.resolve`, failures using `Promise.reject`. */ -async function createStudentConfirmation(req: express.Request): - Promise> { - return rq.parseFinalizeDecisionRequest(req) - .then(parsed => util.isAdmin(parsed)) - .then(parsed => util.isValidID(parsed.data, 'student')) - .then(parsed => { - // UPDATING LOGIC - /*return ormEv.createEvaluationForStudent({ - loginUserId: parsed. - })*/ - return Promise.resolve({data : 'YES', sessionkey : parsed.sessionkey}); - }); +export async function createStudentConfirmation( + req: express.Request +): Promise { + const parsedRequest = await rq.parseFinalizeDecisionRequest(req); + const checkedSessionKey = await util + .checkSessionKey(parsedRequest) + .catch((res) => res); + if (checkedSessionKey.data == undefined) { + return Promise.reject(errors.cookInvalidID()); + } + + const isAdminCheck = await util.isAdmin(parsedRequest); + + if (isAdminCheck.is_admin) { + const student = await ormSt.getStudent(checkedSessionKey.data.id); + if (student == null) { + return Promise.reject(errors.cookInvalidID()); + } + + const jobApplication = await ormJo.getLatestJobApplicationOfStudent( + student.student_id + ); + if (jobApplication == null) { + return Promise.reject(errors.cookInvalidID()); + } + + await ormEv.createEvaluationForStudent({ + loginUserId: checkedSessionKey.userId, + jobApplicationId: jobApplication.job_application_id, + decision: checkedSessionKey.data.reply, + motivation: checkedSessionKey.data.reason, + isFinal: true, + }); + + return Promise.resolve({}); + } + + return Promise.reject(errors.cookInsufficientRights()); } /** - * Attempts to filter students in the system by name, role, status or mail - * status. + * Attempts to filter students in the system by name, role, alumni, student coach, status or email. * @param req The Express.js request to extract all required data from. * @returns See the API documentation. Successes are passed using * `Promise.resolve`, failures using `Promise.reject`. */ -async function searchStudents(req: express.Request): - Promise { - // SEARCHING NOT DISCUSSED YET - NO PARSER EITHER - return Promise.resolve({data : [], sessionkey : req.body.sessionkey}); +export async function filterStudents( + req: express.Request +): Promise { + const parsedRequest = await rq.parseFilterStudentsRequest(req); + const checkedSessionKey = await util + .checkSessionKey(parsedRequest) + .catch((res) => res); + if (checkedSessionKey.data == undefined) { + return Promise.reject(errors.cookInvalidID()); + } + + const students = await ormSt.filterStudents( + checkedSessionKey.data.firstNameFilter, + checkedSessionKey.data.lastNameFilter, + checkedSessionKey.data.emailFilter, + checkedSessionKey.data.roleFilter, + checkedSessionKey.data.alumniFilter, + checkedSessionKey.data.coachFilter, + checkedSessionKey.data.statusFilter, + checkedSessionKey.data.osocYear, + checkedSessionKey.data.emailStatusFilter, + checkedSessionKey.data.firstNameSort, + checkedSessionKey.data.lastNameSort, + checkedSessionKey.data.emailSort, + checkedSessionKey.data.alumniSort + ); + + const studentlist = []; + + for (const student of students) { + const jobApplication = await ormJo.getLatestJobApplicationOfStudent( + student.student_id + ); + if (jobApplication == null) { + return Promise.reject(errors.cookInvalidID()); + } + + const roles = []; + for (const applied_role of jobApplication.applied_role) { + const role = await ormRo.getRole(applied_role.role_id); + if (role != null) { + roles.push(role.name); + } else { + return Promise.reject(errors.cookInvalidID()); + } + } + + const evaluations = await ormJo.getStudentEvaluationsTotal( + student.student_id + ); + + for (const job_application_skill of jobApplication.job_application_skill) { + if (job_application_skill.language_id != null) { + const language = await ormLa.getLanguage( + job_application_skill.language_id + ); + if (language == null) { + return Promise.reject(errors.cookInvalidID()); + } + job_application_skill.skill = language.name; + } + } + + studentlist.push({ + student: student, + jobApplication: jobApplication, + evaluations: evaluations, + roles: roles, + }); + } + + return Promise.resolve({ + data: studentlist, + }); } /** @@ -298,27 +450,28 @@ async function searchStudents(req: express.Request): * endpoints. */ export function getRouter(): express.Router { - const router: express.Router = express.Router(); - - util.setupRedirect(router, '/student'); - util.route(router, "get", "/all", listStudents); - util.route(router, "get", "/:id", getStudent); - util.route(router, "post", "/:id", modStudent); - router.delete('/:id', (req, res) => util.respOrErrorNoReinject( - res, deleteStudent(req))); + const router: express.Router = express.Router(); - router.post('/:id', (req, res) => util.respOrErrorNoReinject( - res, createStudentSuggestion(req))); + util.setupRedirect(router, "/student"); + util.route(router, "get", "/filter", filterStudents); + util.route(router, "get", "/all", listStudents); + util.route(router, "get", "/:id", getStudent); + util.route(router, "delete", "/:id", deleteStudent); - util.route(router, "get", "/:id/suggest", getStudentSuggestions); + util.route(router, "post", "/:id/suggest", createStudentSuggestion); - util.route(router, "post", "/:id/confirm", createStudentConfirmation); + util.route(router, "get", "/:id/suggest", getStudentSuggestions); - util.route(router, "get", "/search", searchStudents); + util.route(router, "post", "/:id/confirm", createStudentConfirmation); - util.addAllInvalidVerbs( - router, - [ "/", "/all", "/:id", "/:id/suggest", "/:id/confirm", "/search" ]); + util.addAllInvalidVerbs(router, [ + "/", + "/all", + "/:id", + "/:id/suggest", + "/:id/confirm", + "/filter", + ]); - return router; + return router; } diff --git a/backend/routes/template.ts b/backend/routes/template.ts new file mode 100644 index 00000000..d2cc4da3 --- /dev/null +++ b/backend/routes/template.ts @@ -0,0 +1,162 @@ +import express from "express"; + +import * as ormT from "../orm_functions/template"; +import * as rq from "../request"; +import { ApiError, Responses } from "../types"; +import * as util from "../utility"; + +const notOwnerError: ApiError = { + http: util.errors.cookInsufficientRights().http, + reason: "You can only modify/delete templates you own.", +}; + +export async function getAllTemplates( + req: express.Request +): Promise { + return rq + .parseTemplateListRequest(req) + .then((parsed) => util.checkSessionKey(parsed)) + .then(() => + ormT + .getAllTemplates() + .then((res) => + res.map((obj) => ({ + id: obj.template_email_id, + owner: obj.owner_id, + name: obj.name, + })) + ) + .then((res) => + Promise.resolve({ + data: res, + }) + ) + ); +} + +export async function getSingleTemplate( + req: express.Request +): Promise { + return rq + .parseGetTemplateRequest(req) + .then((parsed) => util.checkSessionKey(parsed)) + .then((checked) => + ormT + .getTemplateById(checked.data.id) + .then((res) => util.getOrReject(res)) + .then((res) => + Promise.resolve({ + id: res.template_email_id, + owner: res.owner_id, + name: res.name, + content: res.content, + }) + ) + ); +} + +export async function createTemplate( + req: express.Request +): Promise { + return rq + .parseNewTemplateRequest(req) + .then((parsed) => util.checkSessionKey(parsed)) + .then((checked) => + ormT + .createTemplate({ + ownerId: checked.userId, + name: checked.data.name, + content: checked.data.content, + cc: checked.data.cc, + subject: checked.data.subject, + }) + .then((res) => + Promise.resolve({ + id: res.template_email_id, + name: res.name, + content: res.content, + cc: res.cc, + owner: res.owner_id, + subject: res.subject, + }) + ) + ); +} + +export async function updateTemplate( + req: express.Request +): Promise { + return rq + .parseUpdateTemplateRequest(req) + .then((parsed) => util.checkSessionKey(parsed)) + .then(async (checked) => { + return ormT + .getTemplateById(checked.data.id) + .then((templ) => util.getOrReject(templ)) + .then((templ) => { + if (templ.owner_id != checked.userId) { + return Promise.reject(notOwnerError); + } + + return templ; + }) + .then((templ) => + ormT.updateTemplate({ + templateId: templ.template_email_id, + name: checked.data.name, + content: checked.data.content, + cc: checked.data.cc, + subject: checked.data.subject, + }) + ) + .then((res) => + Promise.resolve({ + content: res.content, + id: res.template_email_id, + owner: res.owner_id, + name: res.name, + cc: res.cc, + subject: res.subject, + }) + ); + }); +} + +export async function deleteTemplate( + req: express.Request +): Promise { + return rq + .parseDeleteTemplateRequest(req) + .then((parsed) => util.checkSessionKey(parsed)) + .then(async (checked) => { + return ormT + .getTemplateById(checked.data.id) + .then((templ) => util.getOrReject(templ)) + .then(async (templ) => { + if (templ.owner_id != checked.userId) { + return util + .isAdmin(checked.data) + .catch(() => Promise.reject(notOwnerError)) + .then(() => templ); + } + return Promise.resolve(templ); + }) + .then((templ) => ormT.deleteTemplate(templ.template_email_id)) + .then(() => Promise.resolve({})); + }); +} + +export function getRouter(): express.Router { + const router: express.Router = express.Router(); + + util.setupRedirect(router, "/template"); + util.route(router, "post", "/", createTemplate); + + util.route(router, "get", "/all", getAllTemplates); + + util.route(router, "get", "/:id", getSingleTemplate); + util.route(router, "post", "/:id", updateTemplate); + util.route(router, "delete", "/:id", deleteTemplate); + + return router; +} diff --git a/backend/routes/user.ts b/backend/routes/user.ts new file mode 100644 index 00000000..8de67adb --- /dev/null +++ b/backend/routes/user.ts @@ -0,0 +1,389 @@ +import { account_status_enum } from "@prisma/client"; +import express from "express"; +import * as validator from "validator"; + +import * as ormLU from "../orm_functions/login_user"; +import * as ormL from "../orm_functions/login_user"; +import * as ormP from "../orm_functions/person"; +import * as rq from "../request"; +import { Responses } from "../types"; +import * as util from "../utility"; +import { errors } from "../utility"; +import * as session_key from "./session_key.json"; +import { + addSessionKey, + removeAllKeysForUser, +} from "../orm_functions/session_key"; +import * as config from "../config.json"; + +/** + * Attempts to list all students in the system. + * @param req The Express.js request to extract all required data from. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export async function listUsers( + req: express.Request +): Promise { + const parsedRequest = await rq.parseUserAllRequest(req); + const checkedSessionKey = await util + .isAdmin(parsedRequest) + .catch((res) => res); + if (checkedSessionKey.data == undefined) { + return Promise.reject(errors.cookInvalidID()); + } + const loginUsers = await ormL.getAllLoginUsers(); + + loginUsers.map((val) => ({ + person_data: { + id: val.person.person_id, + name: val.person.firstname, + email: val.person.email, + github: val.person.github, + }, + coach: val.is_coach, + admin: val.is_admin, + activated: val.account_status as string, + })); + + return Promise.resolve({ + data: loginUsers, + }); +} + +/** + * Attempts to create a new user in the system. + * @param req The Express.js request to extract all required data from. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export async function createUserRequest( + req: express.Request +): Promise { + return rq.parseRequestUserRequest(req).then(async (parsed) => { + if (parsed.pass == undefined) { + console.log(" -> WARNING user request without password"); + return Promise.reject(util.errors.cookArgumentError()); + } + return ormP + .createPerson({ + firstname: parsed.firstName, + lastname: "", + email: validator.default + .normalizeEmail(parsed.email) + .toString(), + }) + .then((person) => { + console.log("Created a person: " + person); + return ormLU.createLoginUser({ + personId: person.person_id, + password: parsed.pass, + isAdmin: false, + isCoach: true, + accountStatus: "PENDING", + }); + }) + .then((user) => { + const key: string = util.generateKey(); + const futureDate = new Date(); + futureDate.setDate( + futureDate.getDate() + session_key.valid_period + ); + return addSessionKey(user.login_user_id, key, futureDate); + }) + .then((user) => { + console.log("Attached a login user: " + user); + return Promise.resolve({ + id: user.login_user_id, + sessionkey: user.session_key, + }); + }) + .catch((e) => { + if ("code" in e && e.code == "P2002") { + return Promise.reject({ + http: 400, + reason: "Can't register the same email address twice.", + }); + } + return Promise.reject(e); + }); + }); +} + +export async function setAccountStatus( + person_id: number, + stat: account_status_enum, + key: string, + is_admin: boolean, + is_coach: boolean +): Promise { + return ormLU + .searchLoginUserByPerson(person_id) + .then((obj) => + obj == null + ? Promise.reject(util.errors.cookInvalidID()) + : ormLU.updateLoginUser({ + loginUserId: obj.login_user_id, + isAdmin: is_admin, + isCoach: is_coach, + accountStatus: stat, + }) + ) + .then((res) => { + console.log(res.person.firstname); + return Promise.resolve({ + id: res.person_id, + name: res.person.firstname + " " + res.person.lastname, + }); + }); +} + +/** + * Attempts to accept a request for becoming a login_user. + * @param req The Express.js request to extract all required data from. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export async function createUserAcceptance( + req: express.Request +): Promise { + return rq + .parseAcceptNewUserRequest(req) + .then((parsed) => util.isAdmin(parsed)) + .then((parsed) => util.mutable(parsed, parsed.data.id)) + .then(async (parsed) => { + return ormL + .searchLoginUserByPerson(parsed.data.id) + .then((logUs) => { + if ( + logUs !== null && + logUs.account_status === account_status_enum.PENDING + ) { + return setAccountStatus( + parsed.data.id, + "ACTIVATED", + parsed.data.sessionkey, + parsed.data.is_admin + .toString() + .toLowerCase() + .trim() === "true", + parsed.data.is_coach + .toString() + .toLowerCase() + .trim() === "true" + ); + } + return Promise.reject(errors.cookInvalidID()); + }); + }); +} + +/** + * Attempts to deny a request for becoming a coach. + * @param req The Express.js request to extract all required data from. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export async function deleteUserRequest( + req: express.Request +): Promise { + return rq + .parseAcceptNewUserRequest(req) + .then((parsed) => util.isAdmin(parsed)) + .then((parsed) => util.mutable(parsed, parsed.data.id)) + .then(async (parsed) => { + return ormL + .searchLoginUserByPerson(parsed.data.id) + .then((logUs) => { + if ( + logUs !== null && + logUs.account_status === account_status_enum.PENDING + ) { + return setAccountStatus( + parsed.data.id, + "DISABLED", + parsed.data.sessionkey, + parsed.data.is_admin + .toString() + .toLowerCase() + .trim() === "true", + parsed.data.is_coach + .toString() + .toLowerCase() + .trim() === "true" + ); + } + return Promise.reject(errors.cookInvalidID()); + }); + }); +} + +/** + * Attempts to filter users in the system by name, email, status, coach or + * admin. + * @param req The Express.js request to extract all required data from. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export async function filterUsers( + req: express.Request +): Promise { + return rq + .parseFilterUsersRequest(req) + .then((parsed) => util.isAdmin(parsed)) + .then(async (parsed) => { + return ormLU + .filterLoginUsers( + parsed.data.nameFilter, + parsed.data.emailFilter, + parsed.data.nameSort, + parsed.data.emailSort, + parsed.data.statusFilter, + parsed.data.isCoachFilter, + parsed.data.isAdminFilter + ) + .then((users) => { + users.map((val) => ({ + person_data: { + id: val.person.person_id, + name: val.person.firstname, + email: val.person.email, + github: val.person.github, + }, + coach: val.is_coach, + admin: val.is_admin, + activated: val.account_status as string, + })); + return Promise.resolve({ data: users }); + }); + }); +} + +function setSessionKey(req: express.Request, key: string): void { + req.headers.authorization = config.global.authScheme + " " + key; +} + +export async function userModSelf( + req: express.Request +): Promise { + return rq + .parseUserModSelfRequest(req) + .then((parsed) => util.checkSessionKey(parsed, false)) + .then((checked) => { + return ormLU + .getLoginUserById(checked.userId) + .then((user) => util.getOrReject(user)) + .then((user) => { + if ( + checked.data.pass != undefined && + user.password != checked.data.pass.oldpass + ) + return Promise.reject(); + return Promise.resolve(user); + }) + .then((user) => + ormLU.updateLoginUser({ + loginUserId: checked.userId, + accountStatus: user.account_status, + password: checked.data.pass?.newpass, + }) + ) + .then((user) => Promise.resolve(user.person)) + .then((person) => { + if (checked.data.name != undefined) { + return ormP + .updatePerson({ + personId: person.person_id, + firstname: checked.data.name, + }) + .then(() => Promise.resolve()); + } + return Promise.resolve(); + }) + .then(() => Promise.resolve(checked)); + }) + .then((checked) => { + if (checked.data.pass == undefined) { + return Promise.resolve({ sessionkey: checked.data.sessionkey }); + } + return removeAllKeysForUser(checked.data.sessionkey) + .then(() => { + const key = util.generateKey(); + const time = new Date(Date.now()); + time.setDate(time.getDate() + session_key.valid_period); + return addSessionKey(checked.userId, key, time); + }) + .then((v) => { + setSessionKey(req, v.session_key); + return v; + }) + .then((v) => Promise.resolve({ sessionkey: v.session_key })); + }); +} + +/** + * Attempts to get all data for a certain student in the system. + * @param req The Express.js request to extract all required data from. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export async function getCurrentUser( + req: express.Request +): Promise { + const parsedRequest = await rq.parseCurrentUserRequest(req); + + const checkedSessionKey = await util + .checkSessionKey(parsedRequest) + .catch((res) => res); + if (checkedSessionKey.data == undefined) { + return Promise.reject(errors.cookInvalidID()); + } + + const login_user = await ormL + .getLoginUserById(checkedSessionKey.userId) + .then((obj) => util.getOrReject(obj)); + login_user.password = null; + + return Promise.resolve({ + data: { + login_user: login_user, + }, + sessionkey: checkedSessionKey.data.sessionkey, + }).then((obj) => { + console.log(JSON.stringify(obj)); + return Promise.resolve(obj); + }); +} +/** + * Gets the router for all `/user/` related endpoints. + * @returns An Express.js {@link express.Router} routing all `/user/` + * endpoints. + */ +export function getRouter(): express.Router { + const router: express.Router = express.Router(); + + util.setupRedirect(router, "/user"); + util.route(router, "get", "/filter", filterUsers); + util.route(router, "get", "/all", listUsers); + + util.route(router, "get", "/self", getCurrentUser); + util.route(router, "post", "/self", userModSelf); + + router.post("/request", (req, res) => + util.respOrErrorNoReinject(res, createUserRequest(req)) + ); + + util.route(router, "post", "/request/:id", createUserAcceptance); + util.route(router, "delete", "/request/:id", deleteUserRequest); + + util.addAllInvalidVerbs(router, [ + "/", + "/all", + "/request", + "/request/:id", + "/filter", + "/self", + ]); + + return router; +} diff --git a/backend/routes/verify.ts b/backend/routes/verify.ts new file mode 100644 index 00000000..8bedd1fb --- /dev/null +++ b/backend/routes/verify.ts @@ -0,0 +1,48 @@ +import express from "express"; + +import * as rq from "../request"; +import { Responses } from "../types"; +import * as util from "../utility"; + +/** + * Attempts to verify if the session key is valid. + * @param req The Express.js request to extract all required data from. + * @returns See the API documentation. Successes are passed using + * `Promise.resolve`, failures using `Promise.reject`. + */ +export async function verifyKey( + req: express.Request +): Promise { + const parsedRequest = await rq.parseVerifyRequest(req); + // verify works on PENDING accounts + const checkedSessionKey = await util + .checkSessionKey(parsedRequest, false) + .catch((res) => res); + if (checkedSessionKey.data === undefined) { + return Promise.resolve({ valid: false }); + } else { + return Promise.resolve({ + valid: true, + is_coach: checkedSessionKey.is_coach, + is_admin: checkedSessionKey.is_admin, + account_status: checkedSessionKey.accountStatus, + }); + } +} + +/** + * Gets the router for all `/verify/` related endpoints. + * @returns An Express.js {@link express.Router} routing all `/user/` + * endpoints. + */ +export function getRouter(): express.Router { + const router: express.Router = express.Router(); + + router.post("/", (req, res) => + util.respOrErrorNoReinject(res, verifyKey(req)) + ); + + util.addAllInvalidVerbs(router, ["/"]); + + return router; +} diff --git a/backend/tests/mocking/mocks.ts b/backend/tests/mocking/mocks.ts new file mode 100644 index 00000000..55b7ab11 --- /dev/null +++ b/backend/tests/mocking/mocks.ts @@ -0,0 +1,212 @@ +import CallableInstance from "callable-instance"; +import express from "express"; +import core from "express-serve-static-core"; +import { setTimeout } from "timers/promises"; + +type Call = ( + req: express.Request, + res: express.Response, + next: express.NextFunction +) => Promise; + +class Callback { + cb: Call | undefined; + + constructor() { + this.cb = undefined; + } + public set(cb: Call) { + if (this.cb === undefined) this.cb = cb; + } +} + +class CallbackCollection { + get: Callback; + put: Callback; + post: Callback; + delete: Callback; + patch: Callback; + + constructor() { + this.get = new Callback(); + this.put = new Callback(); + this.post = new Callback(); + this.delete = new Callback(); + this.patch = new Callback(); + } +} + +export class RouterInvalidVerbError extends Error {} +export class RouterInvalidEndpointError extends Error {} +export class RouterInvalidVerbEndpointError extends Error {} + +export class MockedRouter + extends CallableInstance<[express.Request, express.Response], Promise> + implements express.Router +{ + private verifier(route: string) { + if (!(route in this.callbacks)) { + this.callbacks[route] = new CallbackCollection(); + } + } + + callbacks: { [key: string]: CallbackCollection } = {}; + + all = ((pth: string, rt: express.RequestHandler) => { + this.verifier(pth); + this.get(pth, rt); + this.post(pth, rt); + this.put(pth, rt); + this.delete(pth, rt); + this.patch(pth, rt); + return this; + }) as core.IRouterMatcher; + + get = ((pth: string, rt: express.RequestHandler) => { + this.verifier(pth); + this.callbacks[pth].get.set(async (req, res, next) => { + await rt(req, res, next); + }); + }) as core.IRouterMatcher; + + post = ((pth: string, rt: express.RequestHandler) => { + this.verifier(pth); + this.callbacks[pth].post.set(async (req, res, next) => { + await rt(req, res, next); + }); + }) as core.IRouterMatcher; + + put = ((pth: string, rt: express.RequestHandler) => { + this.verifier(pth); + this.callbacks[pth].put.set(async (req, res, next) => { + await rt(req, res, next); + }); + }) as core.IRouterMatcher; + + delete = ((pth: string, rt: express.RequestHandler) => { + this.verifier(pth); + this.callbacks[pth].delete.set(async (req, res, next) => { + await rt(req, res, next); + }); + }) as core.IRouterMatcher; + + patch = ((pth: string, rt: express.RequestHandler) => { + this.verifier(pth); + this.callbacks[pth].patch.set(async (req, res, next) => { + await rt(req, res, next); + }); + }) as core.IRouterMatcher; + + options = jest.fn(); + head = jest.fn(); + checkout = jest.fn(); + connect = jest.fn(); + copy = jest.fn(); + lock = jest.fn(); + merge = jest.fn(); + mkactivity = jest.fn(); + mkcol = jest.fn(); + move = jest.fn(); + "m-search" = jest.fn(); + notify = jest.fn(); + param = jest.fn(); + propfind = jest.fn(); + proppatch = jest.fn(); + purge = jest.fn(); + report = jest.fn(); + search = jest.fn(); + subscribe = jest.fn(); + trace = jest.fn(); + unlock = jest.fn(); + unsubscribe = jest.fn(); + use = jest.fn(); + route = jest.fn(); + stack = []; + + constructor() { + super("__call__"); + } + + // simple mocking + public async __call__(req: express.Request, res: express.Response) { + return Promise.resolve().then(async () => { + const ep = req.path; + const verb = req.method; + + if (!(ep in this.callbacks)) { + throw getInvalidEndpointError(ep); + } + if ( + verb != "get" && + verb != "post" && + verb != "put" && + verb != "delete" && + verb != "patch" + ) { + throw getInvalidVerbError(verb); + } + const pVerb = verb as "get" | "post" | "put" | "delete" | "patch"; + const cb = this.callbacks[ep][pVerb].cb; + + if (cb == undefined) { + throw getInvalidVerbEndpointError(verb, ep); + } else { + console.log("[mockedRouter]: returning await..."); + return await Promise.resolve().then( + async () => + await cb(req, res, function () { + /*do nothing*/ + }) + ); + } + }); + } +} + +export function getMockRouter(): MockedRouter { + return new MockedRouter(); +} + +export function getInvalidVerbError(verb: string) { + return new RouterInvalidEndpointError("Invalid verb " + verb); +} +export function getInvalidEndpointError(ep: string) { + return new RouterInvalidEndpointError("Invalid endpoint " + ep); +} +export function getInvalidVerbEndpointError(verb: string, ep: string) { + return new RouterInvalidEndpointError( + "Invalid verb/endpoint combination: " + verb + " " + ep + ); +} + +export type Method = "get" | "post" | "put" | "patch" | "delete"; + +export type RouterPrepareCallback = (req: express.Request) => void; + +export async function expectRouter( + router: MockedRouter, + path: string, + method: Method, + req: express.Request, + res: express.Response +) { + req.path = path; + req.method = method; + await router(req, res); + await setTimeout(1000); +} + +export async function expectRouterThrow( + router: MockedRouter, + path: string, + method: Method, + req: express.Request, + res: express.Response, + err: T +) { + req.path = path; + req.method = method; + return expect( + expectRouter(router, path, method, req, res) + ).rejects.toStrictEqual(err); +} diff --git a/backend/tests/orm_integration/applied_role.test.ts b/backend/tests/orm_integration/applied_role.test.ts new file mode 100644 index 00000000..6a847d29 --- /dev/null +++ b/backend/tests/orm_integration/applied_role.test.ts @@ -0,0 +1,27 @@ +import prisma from "../../prisma/prisma"; +import { deleteAppliedRolesByJobApplication } from "../../orm_functions/applied_role"; + +it("should delete the applied roles for the job application", async () => { + const [job_applications, roles] = await Promise.all([ + prisma.job_application.findMany(), + prisma.role.findMany(), + ]); + + const deleted = await deleteAppliedRolesByJobApplication( + job_applications[1].job_application_id + ); + expect(deleted).toHaveProperty("count", 2); + + await prisma.applied_role.createMany({ + data: [ + { + job_application_id: job_applications[1].job_application_id, + role_id: roles[2].role_id, + }, + { + job_application_id: job_applications[1].job_application_id, + role_id: roles[3].role_id, + }, + ], + }); +}); diff --git a/backend/tests/orm_integration/attachment.test.ts b/backend/tests/orm_integration/attachment.test.ts new file mode 100644 index 00000000..0563ec9c --- /dev/null +++ b/backend/tests/orm_integration/attachment.test.ts @@ -0,0 +1,73 @@ +import prisma from "../../prisma/prisma"; +import { + createAttachment, + deleteAttachment, + getAttachmentById, + deleteAllAttachmentsForApplication, +} from "../../orm_functions/attachment"; +import { type_enum } from "@prisma/client"; + +const attachment1 = { + attachmentId: 0, + jobApplicationID: 0, + data: ["mycvlink.com"], + type: ["CV_URL"], +}; + +const attachment2 = { + attachmentId: 0, + jobApplicationID: 0, + data: ["myportfoliolink.com"], + type: ["PORTFOLIO_URL"], +}; + +it("should create 1 new attachment linked to a job application", async () => { + const job_application = await prisma.job_application.findFirst(); + if (job_application) { + const created_attachment = await createAttachment( + job_application.job_application_id, + attachment1.data, + attachment1.type as type_enum[] + ); + attachment1.jobApplicationID = created_attachment.job_application_id; + attachment1.attachmentId = created_attachment.attachment_id; + expect(created_attachment).toHaveProperty("data", attachment1.data); + expect(created_attachment).toHaveProperty("type", attachment1.type); + } +}); + +it("should create 1 new attachment linked to a job application", async () => { + const job_application = await prisma.job_application.findFirst(); + if (job_application) { + const created_attachment = await createAttachment( + job_application.job_application_id, + attachment2.data, + attachment2.type as type_enum[] + ); + attachment2.jobApplicationID = created_attachment.job_application_id; + attachment2.attachmentId = created_attachment.attachment_id; + expect(created_attachment).toHaveProperty("data", attachment2.data); + expect(created_attachment).toHaveProperty("type", attachment2.type); + } +}); + +it("should return the attachment, by searching for its attachment id", async () => { + const searched_attachment = await getAttachmentById( + attachment1.attachmentId + ); + expect(searched_attachment).toHaveProperty("data", attachment1.data); + expect(searched_attachment).toHaveProperty("type", attachment1.type); +}); + +it("should delete the attachment based upon attachment id", async () => { + const deleted_attachment = await deleteAttachment(attachment1.attachmentId); + expect(deleted_attachment).toHaveProperty("data", attachment1.data); + expect(deleted_attachment).toHaveProperty("type", attachment1.type); +}); + +it("should delete the attachment based upon job application id", async () => { + const deleted_attachments = await deleteAllAttachmentsForApplication( + attachment2.jobApplicationID + ); + expect(deleted_attachments).toHaveProperty("count", 1); +}); diff --git a/backend/tests/orm_integration/contract.test.ts b/backend/tests/orm_integration/contract.test.ts new file mode 100644 index 00000000..5e12cf7c --- /dev/null +++ b/backend/tests/orm_integration/contract.test.ts @@ -0,0 +1,225 @@ +import prisma from "../../prisma/prisma"; +import { + createContract, + updateContract, + removeContract, + removeContractsFromStudent, + contractsForStudent, + contractsByProject, + sortedContractsByOsocEdition, +} from "../../orm_functions/contract"; +import { CreateContract, UpdateContract } from "../../orm_functions/orm_types"; +import { contract_status_enum } from "@prisma/client"; + +const contract1: UpdateContract = { + contractId: 0, + information: "Contract details", + loginUserId: 0, + contractStatus: contract_status_enum.SENT, +}; + +const contract2: UpdateContract = { + contractId: 0, + information: "New Contract details", + loginUserId: 0, + contractStatus: contract_status_enum.SIGNED, +}; + +it("should create 1 new contract linked to a student", async () => { + const student = await prisma.student.findFirst(); + const project_role = await prisma.project_role.findFirst(); + const login_user = await prisma.login_user.findFirst(); + if (student && project_role && login_user) { + const contract: CreateContract = { + studentId: student.student_id, + projectRoleId: project_role.project_role_id, + information: "Contract details", + loginUserId: login_user.login_user_id, + contractStatus: contract_status_enum.SENT, + }; + const created_contract = await createContract(contract); + contract1.contractId = created_contract.contract_id; + contract2.contractId = created_contract.contract_id; + contract1.loginUserId = created_contract.created_by_login_user_id; + contract2.loginUserId = created_contract.created_by_login_user_id; + expect(created_contract).toHaveProperty( + "contract_id", + contract1.contractId + ); + expect(created_contract).toHaveProperty( + "information", + contract1.information + ); + expect(created_contract).toHaveProperty( + "created_by_login_user_id", + contract1.loginUserId + ); + expect(created_contract).toHaveProperty( + "contract_status", + contract1.contractStatus + ); + } +}); + +it("should create 1 new contract linked to a student", async () => { + const student = await prisma.student.findFirst(); + const project_role = await prisma.project_role.findFirst(); + const login_user = await prisma.login_user.findFirst(); + if (student && project_role && login_user) { + const contract: CreateContract = { + studentId: student.student_id, + projectRoleId: project_role.project_role_id, + information: "Student Contract details", + loginUserId: login_user.login_user_id, + contractStatus: contract_status_enum.CANCELLED, + }; + const created_contract = await createContract(contract); + expect(created_contract).toHaveProperty( + "information", + contract.information + ); + expect(created_contract).toHaveProperty( + "created_by_login_user_id", + contract.loginUserId + ); + expect(created_contract).toHaveProperty( + "contract_status", + contract.contractStatus + ); + } +}); + +it("should update contract based upon contract id", async () => { + const updated_contract = await updateContract(contract2); + expect(updated_contract).toHaveProperty( + "contract_id", + contract2.contractId + ); + expect(updated_contract).toHaveProperty( + "information", + contract2.information + ); + expect(updated_contract).toHaveProperty( + "created_by_login_user_id", + contract2.loginUserId + ); + expect(updated_contract).toHaveProperty( + "contract_status", + contract2.contractStatus + ); +}); + +it("should delete the contract based upon contract id", async () => { + const deleted_contract = await removeContract(contract2.contractId); + expect(deleted_contract).toHaveProperty( + "contract_id", + contract2.contractId + ); + expect(deleted_contract).toHaveProperty( + "information", + contract2.information + ); + expect(deleted_contract).toHaveProperty( + "created_by_login_user_id", + contract2.loginUserId + ); + expect(deleted_contract).toHaveProperty( + "contract_status", + contract2.contractStatus + ); +}); + +it("should delete the contracts based upon student", async () => { + const students = await prisma.student.findMany(); + const deleted_contracts = await removeContractsFromStudent( + students[0].student_id + ); + expect(deleted_contracts).toHaveProperty("count", 1); +}); + +it("should list all contracts linked to a student", async () => { + const student = await prisma.student.findFirst(); + const project_role = await prisma.project_role.findFirst(); + const login_user = await prisma.login_user.findFirst(); + if (student && project_role && login_user) { + const contract: CreateContract = { + studentId: student.student_id, + projectRoleId: project_role.project_role_id, + information: "Contract details", + loginUserId: login_user.login_user_id, + contractStatus: contract_status_enum.SENT, + }; + + await createContract(contract); + const all_contracts = await contractsForStudent(student.student_id); + + for (const contract of all_contracts) { + const contractId = contract.contract_id; + const projectRole = contract.project_role; + const studentFound = contract.student; + + expect(contract).toHaveProperty("contract_id", contractId); + expect(contract).toHaveProperty("project_role", projectRole); + expect(contract).toHaveProperty("student", studentFound); + } + } +}); + +it("should list all contracts linked to a project", async () => { + const student = await prisma.student.findFirst(); + const project_role = await prisma.project_role.findFirst(); + const login_user = await prisma.login_user.findFirst(); + if (student && project_role && login_user) { + const contract: CreateContract = { + studentId: student.student_id, + projectRoleId: project_role.project_role_id, + information: "Contract details", + loginUserId: login_user.login_user_id, + contractStatus: contract_status_enum.SENT, + }; + + await createContract(contract); + const all_contracts = await contractsByProject(project_role.project_id); + + for (const contract of all_contracts) { + const contractId = contract.contract_id; + const projectRole = contract.project_role; + const studentFound = contract.student; + const contractStatus = contract.contract_status; + + expect(contract).toHaveProperty("contract_id", contractId); + expect(contract).toHaveProperty("project_role", projectRole); + expect(contract).toHaveProperty("student", studentFound); + expect(contract).toHaveProperty("contract_status", contractStatus); + } + } +}); + +it("should list all contracts linked to an osoc year", async () => { + const student = await prisma.student.findFirst(); + const project_role = await prisma.project_role.findFirst(); + const login_user = await prisma.login_user.findFirst(); + const osoc_year = await prisma.osoc.findFirst(); + if (student && project_role && login_user && osoc_year) { + const contract: CreateContract = { + studentId: student.student_id, + projectRoleId: project_role.project_role_id, + information: "Contract details", + loginUserId: login_user.login_user_id, + contractStatus: contract_status_enum.SENT, + }; + + await createContract(contract); + const all_contracts = await sortedContractsByOsocEdition( + osoc_year.osoc_id + ); + + for (const contract of all_contracts) { + const projectRole = contract.project_role; + const studentId = contract.student_id; + + expect(contract).toHaveProperty("project_role", projectRole); + expect(contract).toHaveProperty("student_id", studentId); + } + } +}); diff --git a/backend/tests/orm_integration/evaluation.test.ts b/backend/tests/orm_integration/evaluation.test.ts new file mode 100644 index 00000000..434d26a6 --- /dev/null +++ b/backend/tests/orm_integration/evaluation.test.ts @@ -0,0 +1,120 @@ +import { + CreateEvaluationForStudent, + UpdateEvaluationForStudent, +} from "../../orm_functions/orm_types"; +import { + createEvaluationForStudent, + checkIfFinalEvaluationExists, + updateEvaluationForStudent, + getLoginUserByEvaluationId, +} from "../../orm_functions/evaluation"; +import prisma from "../../prisma/prisma"; +import { decision_enum } from "@prisma/client"; + +const evaluation1: UpdateEvaluationForStudent = { + evaluation_id: 1, + loginUserId: 1, + decision: decision_enum.YES, + motivation: "Definitely unicorn, all in for it", +}; + +let jobApplicationId = 0; +let createdEvaluation: CreateEvaluationForStudent; + +it("should create 1 new evaluation", async () => { + const login_user = await prisma.login_user.findFirst(); + const job_application_id = await prisma.job_application.findFirst(); + + if (login_user && job_application_id) { + const evaluation: CreateEvaluationForStudent = { + loginUserId: login_user.login_user_id, + jobApplicationId: job_application_id.job_application_id, + decision: decision_enum.MAYBE, + motivation: "Looks good", + isFinal: true, + }; + createdEvaluation = evaluation; + evaluation1.loginUserId = evaluation.loginUserId; + jobApplicationId = evaluation.jobApplicationId; + + const created_evaluation = await createEvaluationForStudent(evaluation); + evaluation1.evaluation_id = created_evaluation.evaluation_id; + expect(created_evaluation).toHaveProperty( + "login_user_id", + created_evaluation.login_user_id + ); + expect(created_evaluation).toHaveProperty( + "job_application_id", + created_evaluation.job_application_id + ); + expect(created_evaluation).toHaveProperty( + "decision", + created_evaluation.decision + ); + expect(created_evaluation).toHaveProperty( + "motivation", + created_evaluation.motivation + ); + expect(created_evaluation).toHaveProperty( + "is_final", + created_evaluation.is_final + ); + } +}); + +it("should return the evaluation, by searching for the job application id", async () => { + const searched_evaluation = await checkIfFinalEvaluationExists( + jobApplicationId + ); + expect(searched_evaluation).toHaveProperty( + "evaluation_id", + evaluation1.evaluation_id + ); +}); + +it("should return the evaluation, by searching for the evaluation id", async () => { + const searched_evaluation = await getLoginUserByEvaluationId( + evaluation1.evaluation_id + ); + expect(searched_evaluation).toHaveProperty( + "login_user_id", + createdEvaluation.loginUserId + ); + expect(searched_evaluation).toHaveProperty( + "job_application_id", + jobApplicationId + ); + expect(searched_evaluation).toHaveProperty( + "decision", + createdEvaluation.decision + ); + expect(searched_evaluation).toHaveProperty( + "motivation", + createdEvaluation.motivation + ); + expect(searched_evaluation).toHaveProperty( + "is_final", + createdEvaluation.isFinal + ); +}); + +it("should update evaluation based upon evaluation id", async () => { + const updated_evaluation = await updateEvaluationForStudent(evaluation1); + expect(updated_evaluation).toHaveProperty( + "login_user_id", + evaluation1.loginUserId + ); + expect(updated_evaluation).toHaveProperty( + "job_application_id", + jobApplicationId + ); + expect(updated_evaluation).toHaveProperty("decision", evaluation1.decision); + expect(updated_evaluation).toHaveProperty( + "motivation", + evaluation1.motivation + ); + expect(updated_evaluation).toHaveProperty( + "is_final", + createdEvaluation.isFinal + ); +}); diff --git a/backend/tests/orm_integration/integration_setup.ts b/backend/tests/orm_integration/integration_setup.ts index 0f168044..00d8db70 100644 --- a/backend/tests/orm_integration/integration_setup.ts +++ b/backend/tests/orm_integration/integration_setup.ts @@ -1,11 +1,12 @@ import prisma from "../../prisma/prisma"; +import { decision_enum, email_status_enum } from "@prisma/client"; beforeAll(async () => { // create persons await prisma.person.createMany({ data: [ { - email: 'email@testmail.com', + email: "email@testmail.com", firstname: "firstNameTest", lastname: "lastNameTest", }, @@ -14,46 +15,353 @@ beforeAll(async () => { firstname: "firstNameTest2", lastname: "lastNameTest2", }, + { + email: "testmail2@mail.com", + firstname: "first", + lastname: "last", + }, + { + email: "studentmail@mail.com", + firstname: "student", + lastname: "student", + }, + { + email: "coachmail@mail.com", + firstname: "coach", + lastname: "testcoach", + }, ], }); const persons = await prisma.person.findMany(); - + // create login users await prisma.login_user.createMany({ data: [ - { + { person_id: persons[0].person_id, password: "easy_password", is_admin: true, is_coach: true, - account_status: "ACTIVATED" + account_status: "ACTIVATED", }, { person_id: persons[1].person_id, password: "123_bad_pass", is_admin: true, is_coach: false, - account_status: "PENDING" + account_status: "PENDING", + }, + { + person_id: persons[4].person_id, + password: "Mypassword", + is_admin: true, + is_coach: true, + account_status: "ACTIVATED", }, ], }); + const login_users = await prisma.login_user.findMany(); + // create osoc editions await prisma.osoc.createMany({ - data: [ - { - year: 2022 - }, - { - year: 2023 - } - ] + data: [ + { + year: 2022, + }, + { + year: 2023, + }, + ], + }); + const osocs = await prisma.osoc.findMany(); + + // create projects + await prisma.project.createMany({ + data: [ + { + name: "project-test", + osoc_id: osocs[0].osoc_id, + partner: "partner-test", + start_date: new Date("2022-05-22"), + end_date: new Date("2022-06-31"), + positions: 10, + }, + { + name: "project-test-2", + osoc_id: osocs[1].osoc_id, + partner: "partner-test-2", + start_date: new Date("2022-09-15"), + end_date: new Date("2022-10-23"), + positions: 9, + }, + { + name: "project-test-3", + osoc_id: osocs[1].osoc_id, + partner: "partner-test-3", + start_date: new Date("2022-09-15"), + end_date: new Date("2022-10-23"), + positions: 9, + }, + ], + }); + + await prisma.student.createMany({ + data: [ + { + person_id: persons[2].person_id, + gender: "Male", + pronouns: "He/ Him", + phone_number: "112", + alumni: false, + }, + { + person_id: persons[1].person_id, + gender: "Female", + pronouns: "She/ Her", + phone_number: "107", + alumni: true, + }, + { + person_id: persons[3].person_id, + gender: "Female", + pronouns: "She/ Her", + phone_number: "111", + alumni: false, + }, + ], + }); + const students = await prisma.student.findMany(); + + // create job applications + await prisma.job_application.createMany({ + data: [ + { + student_id: students[0].student_id, + student_volunteer_info: "no volunteer", + responsibilities: "no responsibilities", + fun_fact: "this is a fun fact", + student_coach: false, + osoc_id: osocs[0].osoc_id, + edus: ["basic education"], + edu_level: "higher education", + edu_duration: 5, + edu_institute: "Ugent", + edu_year: "4", + email_status: email_status_enum.DRAFT, + created_at: new Date("December 17, 2021 14:24:00"), + }, + { + student_id: students[0].student_id, + student_volunteer_info: "I'd like to volunteer", + responsibilities: "no responsibilities2", + fun_fact: "this is a fun fact too", + student_coach: true, + osoc_id: osocs[0].osoc_id, + edus: ["higher education"], + edu_level: "MaNaMa", + edu_duration: 8, + edu_institute: "Ugent", + edu_year: "7", + email_status: email_status_enum.SENT, + created_at: new Date("December 20, 2021 03:24:00"), + }, + { + student_id: students[2].student_id, + student_volunteer_info: "no volunteer", + responsibilities: "no responsibilities", + fun_fact: "this is a fun fact", + student_coach: false, + osoc_id: osocs[0].osoc_id, + edus: ["something something"], + edu_level: "higher education", + edu_duration: 5, + edu_institute: "Ugent", + edu_year: "3", + email_status: email_status_enum.DRAFT, + created_at: new Date("December 25, 2021 14:24:00"), + }, + { + student_id: students[2].student_id, + student_volunteer_info: "I'd like to volunteer", + responsibilities: "no responsibilities2", + fun_fact: "this is a fun fact too", + student_coach: true, + osoc_id: osocs[1].osoc_id, + edus: ["higher education"], + edu_level: "MaNaMa", + edu_duration: 8, + edu_institute: "Ugent", + edu_year: "3", + email_status: email_status_enum.SENT, + created_at: new Date("December 31, 2021 03:24:00"), + }, + ], + }); + + const job_applications = await prisma.job_application.findMany(); + + // create evaluations + await prisma.evaluation.createMany({ + data: [ + { + login_user_id: login_users[0].login_user_id, + job_application_id: job_applications[0].job_application_id, + decision: decision_enum.MAYBE, + motivation: "low education level", + is_final: false, + }, + { + login_user_id: login_users[0].login_user_id, + job_application_id: job_applications[0].job_application_id, + decision: decision_enum.YES, + motivation: "awesome job application", + is_final: true, + }, + ], + }); + + // create roles + await prisma.role.createMany({ + data: [ + { + name: "Developer", + }, + { + name: "Marketeer", + }, + { + name: "Frontend", + }, + { + name: "Backend", + }, + ], + }); + + const projects = await prisma.project.findFirst(); + const role = await prisma.role.findFirst(); + if (projects && role) { + // create roles + await prisma.project_role.createMany({ + data: [ + { + project_id: projects.project_id, + role_id: role.role_id, + positions: 3, + }, + { + project_id: projects.project_id, + role_id: role.role_id, + positions: 1, + }, + ], + }); + } + const roles = await prisma.role.findMany(); + await prisma.applied_role.createMany({ + data: [ + { + job_application_id: job_applications[0].job_application_id, + role_id: roles[2].role_id, + }, + { + job_application_id: job_applications[1].job_application_id, + role_id: roles[2].role_id, + }, + { + job_application_id: job_applications[1].job_application_id, + role_id: roles[3].role_id, + }, + ], + }); + + // create languages + await prisma.language.createMany({ + data: [ + { + name: "Dutch", + }, + { + name: "French", + }, + ], + }); + + // create attachments + await prisma.attachment.createMany({ + data: [ + { + job_application_id: job_applications[1].job_application_id, + data: ["test-cv-link.com"], + type: ["CV_URL"], + }, + { + job_application_id: job_applications[1].job_application_id, + data: ["test-portfolio-link.com"], + type: ["PORTFOLIO_URL"], + }, + ], + }); + + const languages = await prisma.language.findMany(); + // create job application skills + await prisma.job_application_skill.createMany({ + data: [ + { + job_application_id: job_applications[1].job_application_id, + skill: "SQL", + language_id: languages[0].language_id, + level: 5, + is_preferred: false, + is_best: true, + }, + { + job_application_id: job_applications[0].job_application_id, + skill: "Python", + language_id: languages[0].language_id, + level: 4, + is_preferred: false, + is_best: false, + }, + ], + }); + + // create session keys + const futureDate = new Date(); + futureDate.setDate(futureDate.getDate() + 15); + await prisma.session_keys.createMany({ + data: [ + { + login_user_id: login_users[0].login_user_id, + session_key: "key", + valid_until: futureDate, + }, + { + login_user_id: login_users[0].login_user_id, + session_key: "key2", + valid_until: futureDate, + }, + ], + }); + + // create password reset + await prisma.password_reset.createMany({ + data: [ + { + login_user_id: login_users[2].login_user_id, + reset_id: "5444024611562312170969914212450321", + valid_until: new Date("2022-07-13"), + }, + ], }); }); afterAll(async () => { + const deletePasswordReset = prisma.password_reset.deleteMany(); + const deleteJobApplicationSkillDetails = + prisma.job_application_skill.deleteMany(); const deleteLanguageDetails = prisma.language.deleteMany(); - const deleteJobApplicationSkillDetails = prisma.job_application_skill.deleteMany(); const deleteAttachmentDetails = prisma.attachment.deleteMany(); const deleteAppliedRoleDetails = prisma.applied_role.deleteMany(); const deleteEvaluationDetails = prisma.evaluation.deleteMany(); @@ -70,8 +378,9 @@ afterAll(async () => { const deletePersonDetails = prisma.person.deleteMany(); await prisma.$transaction([ - deleteLanguageDetails, + deletePasswordReset, deleteJobApplicationSkillDetails, + deleteLanguageDetails, deleteAttachmentDetails, deleteAppliedRoleDetails, deleteEvaluationDetails, @@ -81,12 +390,12 @@ afterAll(async () => { deleteContractDetails, deleteProjectRoleDetails, deleteProjectDetails, + deleteRoleDetails, deleteOsocDetails, deleteStudentDetails, deleteLoginUserDetails, - deleteRoleDetails, deletePersonDetails, ]); - await prisma.$disconnect() -}); \ No newline at end of file + await prisma.$disconnect(); +}); diff --git a/backend/tests/orm_integration/job_application.test.ts b/backend/tests/orm_integration/job_application.test.ts new file mode 100644 index 00000000..b7ae3b63 --- /dev/null +++ b/backend/tests/orm_integration/job_application.test.ts @@ -0,0 +1,527 @@ +import { + changeEmailStatusOfJobApplication, + createJobApplication, + deleteJobApplication, + deleteJobApplicationsFromStudent, + getJobApplication, + getJobApplicationByYear, + getLatestApplicationRolesForStudent, + getLatestJobApplicationOfStudent, + getStudentEvaluationsFinal, + getStudentEvaluationsTemp, + getStudentEvaluationsTotal, +} from "../../orm_functions/job_application"; +import prisma from "../../prisma/prisma"; +import { decision_enum, email_status_enum } from "@prisma/client"; +import { CreateJobApplication } from "../../orm_functions/orm_types"; + +/** + * aid function to compare most fields of the expected job application and the found job application + * @param expected the application we expect as response from the database + * @param found the actual response from the database + */ +function job_application_check( + expected: { + job_application_id?: number; + student_id: number; + student_volunteer_info: string; + responsibilities: string | null; + fun_fact: string | null; + student_coach: boolean; + osoc_id: number; + edus: string[]; + edu_level: string | null; + edu_duration: number | null; + edu_institute: string | null; + edu_year: string | null; + created_at: Date; + email_status: email_status_enum; + }, + found: { + job_application_id: number; + student_id: number; + student_volunteer_info: string; + responsibilities: string | null; + fun_fact: string | null; + student_coach: boolean; + osoc_id: number; + edus: string[]; + edu_level: string | null; + edu_duration: number | null; + edu_institute: string | null; + edu_year: string | null; + created_at: Date; + email_status: email_status_enum; + } +) { + expect(found).toHaveProperty("student_id", expected.student_id); + expect(found).toHaveProperty( + "student_volunteer_info", + expected.student_volunteer_info + ); + expect(found).toHaveProperty("responsibilities", expected.responsibilities); + expect(found).toHaveProperty("fun_fact", expected.fun_fact); + expect(found).toHaveProperty("student_coach", expected.student_coach); + expect(found).toHaveProperty("osoc_id", expected.osoc_id); + expect(found).toHaveProperty("edus", expected.edus); + expect(found).toHaveProperty("edu_level", expected.edu_level); + expect(found).toHaveProperty("edu_duration", expected.edu_duration); + expect(found).toHaveProperty("edu_institute", expected.edu_institute); + expect(found).toHaveProperty("edu_year", expected.edu_year); + expect(found).toHaveProperty("created_at", expected.created_at); +} + +it("should return all student evaluations for the student with given id", async () => { + const [students, osocs] = await Promise.all([ + prisma.student.findMany(), + prisma.osoc.findMany(), + ]); + + const evaluations = [ + { + decision: decision_enum.MAYBE, + motivation: "low education level", + is_final: false, + }, + { + decision: decision_enum.YES, + motivation: "awesome job application", + is_final: true, + }, + ]; + + const foundApplications = await getStudentEvaluationsTotal( + students[0].student_id + ); + foundApplications.forEach((found_eval) => { + expect(found_eval).toHaveProperty("osoc", { year: osocs[0].year }); + expect(found_eval).toHaveProperty("evaluation"); + for (let i = 0; i < found_eval.evaluation.length; i++) { + const evals = evaluations[i]; + expect(found_eval.evaluation[i]).toHaveProperty( + "decision", + evals.decision + ); + expect(found_eval.evaluation[i]).toHaveProperty( + "motivation", + evals.motivation + ); + expect(found_eval.evaluation[i]).toHaveProperty("evaluation_id"); + expect(found_eval.evaluation[i]).toHaveProperty( + "is_final", + evals.is_final + ); + // check if all the needed fields are selected (with the other checks we already insured we found the right evaluation) + expect(found_eval.evaluation[i].login_user).toHaveProperty( + "login_user_id" + ); + expect(found_eval.evaluation[i].login_user.person).toHaveProperty( + "firstname" + ); + expect(found_eval.evaluation[i].login_user.person).toHaveProperty( + "lastname" + ); + expect(found_eval.evaluation[i].login_user.person).toHaveProperty( + "person_id" + ); + expect(found_eval.evaluation[i].login_user.person).toHaveProperty( + "github" + ); + expect(found_eval.evaluation[i].login_user.person).toHaveProperty( + "email" + ); + } + }); +}); + +it("should return all final student evaluations for the student with given id", async () => { + const [students, osocs] = await Promise.all([ + prisma.student.findMany(), + prisma.osoc.findMany(), + ]); + + const evaluations = [ + { + decision: decision_enum.YES, + motivation: "awesome job application", + }, + ]; + + const foundApplications = await getStudentEvaluationsFinal( + students[0].student_id + ); + foundApplications.forEach((found_eval) => { + expect(found_eval).toHaveProperty("osoc", { year: osocs[0].year }); + expect(found_eval).toHaveProperty("evaluation"); + for (let i = 0; i < found_eval.evaluation.length; i++) { + const evals = evaluations[i]; + expect(found_eval.evaluation[i]).toHaveProperty( + "decision", + evals.decision + ); + expect(found_eval.evaluation[i]).toHaveProperty( + "motivation", + evals.motivation + ); + expect(found_eval.evaluation[i]).toHaveProperty("evaluation_id"); + // check if all the needed fields are selected (with the other checks we already insured we found the right evaluation) + expect(found_eval.evaluation[i].login_user).toHaveProperty( + "login_user_id" + ); + expect(found_eval.evaluation[i].login_user.person).toHaveProperty( + "firstname" + ); + expect(found_eval.evaluation[i].login_user.person).toHaveProperty( + "lastname" + ); + expect(found_eval.evaluation[i].login_user.person).toHaveProperty( + "person_id" + ); + expect(found_eval.evaluation[i].login_user.person).toHaveProperty( + "github" + ); + expect(found_eval.evaluation[i].login_user.person).toHaveProperty( + "email" + ); + } + }); +}); + +it("should return all suggestion evaluations for the student with given id", async () => { + const [students, osocs] = await Promise.all([ + prisma.student.findMany(), + prisma.osoc.findMany(), + ]); + + const evaluations = [ + { + decision: decision_enum.MAYBE, + motivation: "low education level", + is_final: false, + }, + ]; + + const foundApplications = await getStudentEvaluationsTemp( + students[0].student_id + ); + foundApplications.forEach((found_eval) => { + expect(found_eval).toHaveProperty("osoc", { year: osocs[0].year }); + expect(found_eval).toHaveProperty("evaluation"); + for (let i = 0; i < found_eval.evaluation.length; i++) { + const evals = evaluations[i]; + expect(found_eval.evaluation[i]).toHaveProperty( + "decision", + evals.decision + ); + expect(found_eval.evaluation[i]).toHaveProperty( + "motivation", + evals.motivation + ); + expect(found_eval.evaluation[i]).toHaveProperty("evaluation_id"); + // check if all the needed fields are selected (with the other checks we already insured we found the right evaluation) + expect(found_eval.evaluation[i].login_user).toHaveProperty( + "login_user_id" + ); + expect(found_eval.evaluation[i].login_user.person).toHaveProperty( + "firstname" + ); + expect(found_eval.evaluation[i].login_user.person).toHaveProperty( + "lastname" + ); + expect(found_eval.evaluation[i].login_user.person).toHaveProperty( + "person_id" + ); + expect(found_eval.evaluation[i].login_user.person).toHaveProperty( + "github" + ); + expect(found_eval.evaluation[i].login_user.person).toHaveProperty( + "email" + ); + } + }); +}); + +it("should return the last roles a student applied for", async () => { + const [students, roles] = await Promise.all([ + prisma.student.findMany(), + prisma.role.findMany(), + ]); + + const roles_expected = [roles[2].role_id, roles[3].role_id]; + + const applied_roles = await getLatestApplicationRolesForStudent( + students[0].student_id + ); + if (applied_roles) { + applied_roles.applied_role.forEach(({ role_id }, index) => { + expect(role_id).toEqual(roles_expected[index]); + }); + expect(applied_roles.applied_role.length).toEqual( + roles_expected.length + ); + } +}); + +it("should delete all the job applications of the given student", async () => { + const [students, osocs] = await Promise.all([ + prisma.student.findMany(), + prisma.osoc.findMany(), + ]); + + const applics = [ + { + student_id: students[2].student_id, + student_volunteer_info: "no volunteer", + responsibilities: "no responsibilities", + fun_fact: "this is a fun fact", + student_coach: false, + osoc_id: osocs[0].osoc_id, + edus: ["something something"], + edu_level: "higher education", + edu_duration: 5, + edu_institute: "Ugent", + edu_year: "3", + email_status: email_status_enum.DRAFT, + created_at: new Date("December 25, 2021 14:24:00"), + }, + { + student_id: students[2].student_id, + student_volunteer_info: "I'd like to volunteer", + responsibilities: "no responsibilities2", + fun_fact: "this is a fun fact too", + student_coach: true, + osoc_id: osocs[1].osoc_id, + edus: ["higher education"], + edu_level: "MaNaMa", + edu_duration: 8, + edu_institute: "Ugent", + edu_year: "3", + email_status: email_status_enum.SENT, + created_at: new Date("December 31, 2021 03:24:00"), + }, + ]; + + const deleted = await deleteJobApplicationsFromStudent( + students[2].student_id + ); + expect(deleted.count).toEqual(applics.length); + + // add the deleted data back + await prisma.job_application.createMany({ + data: applics, + }); +}); + +it("should update the email status of the job application", async () => { + const [students, osocs, applics] = await Promise.all([ + prisma.student.findMany(), + prisma.osoc.findMany(), + prisma.job_application.findMany(), + ]); + + const applic = { + student_id: students[2].student_id, + student_volunteer_info: "no volunteer", + responsibilities: "no responsibilities", + fun_fact: "this is a fun fact", + student_coach: false, + osoc_id: osocs[0].osoc_id, + edus: ["something something"], + edu_level: "higher education", + edu_duration: 5, + edu_institute: "Ugent", + edu_year: "3", + email_status: email_status_enum.DRAFT, + created_at: new Date("December 25, 2021 14:24:00"), + }; + const changed = await changeEmailStatusOfJobApplication( + applics[2].job_application_id, + email_status_enum.SENT + ); + job_application_check(applic, changed); + expect(changed).toHaveProperty("email_status", email_status_enum.SENT); + expect(changed).toHaveProperty( + "job_application_id", + applics[2].job_application_id + ); +}); + +it("should delete the job application", async () => { + const [students, osocs, applics] = await Promise.all([ + prisma.student.findMany(), + prisma.osoc.findMany(), + prisma.job_application.findMany(), + ]); + + const app = { + student_id: students[2].student_id, + student_volunteer_info: "no volunteer", + responsibilities: "no responsibilities", + fun_fact: "this is a fun fact", + student_coach: false, + osoc_id: osocs[0].osoc_id, + edus: ["something something"], + edu_level: "higher education", + edu_duration: 5, + edu_institute: "Ugent", + edu_year: "3", + email_status: email_status_enum.SENT, + created_at: new Date("December 25, 2021 14:24:00"), + }; + + const deleted = await deleteJobApplication(applics[3].job_application_id); + job_application_check(app, deleted); + expect(deleted).toHaveProperty("email_status", app.email_status); + await prisma.job_application.create({ + data: app, + }); +}); + +it("should create a new job_application", async () => { + const [students, osocs] = await Promise.all([ + prisma.student.findMany(), + prisma.osoc.findMany(), + ]); + + const app = { + student_id: students[2].student_id, + student_volunteer_info: "no volunteer", + responsibilities: "nothing", + fun_fact: "this is a fun fact", + student_coach: true, + osoc_id: osocs[0].osoc_id, + edus: ["something edu"], + edu_level: "higher education", + edu_duration: 3, + edu_institute: "Hogent", + edu_year: "2", + email_status: email_status_enum.DRAFT, + created_at: new Date("January 2, 2022 14:24:00"), + }; + + const input: CreateJobApplication = { + studentId: students[2].student_id, + studentVolunteerInfo: "no volunteer", + responsibilities: "nothing", + funFact: "this is a fun fact", + studentCoach: true, + osocId: osocs[0].osoc_id, + edus: ["something edu"], + eduLevel: "higher education", + eduDuration: 3, + eduInstitute: "Hogent", + eduYear: "2", + emailStatus: email_status_enum.DRAFT, + createdAt: "January 2, 2022 14:24:00", + }; + + const created = await createJobApplication(input); + job_application_check(app, created); + expect(created).toHaveProperty("email_status", app.email_status); + + await deleteJobApplication(created.job_application_id); +}); + +/** + * aid function to get some data used in the tests + * + */ +function getDataAssociatedWithApplication( + job_application_id: number | undefined +) { + return Promise.all([ + prisma.attachment.findMany({ + where: { + job_application_id: job_application_id, + }, + }), + prisma.applied_role.findMany({ + where: { + job_application_id: job_application_id, + }, + }), + prisma.job_application_skill.findMany({ + where: { + job_application_id: job_application_id, + }, + }), + ]); +} + +it("should return the most recent job application of a student", async () => { + const [students, osocs] = await Promise.all([ + prisma.student.findMany(), + prisma.osoc.findMany(), + ]); + + const expected = { + student_id: students[0].student_id, + student_volunteer_info: "I'd like to volunteer", + responsibilities: "no responsibilities2", + fun_fact: "this is a fun fact too", + student_coach: true, + osoc_id: osocs[0].osoc_id, + edus: ["higher education"], + edu_level: "MaNaMa", + edu_duration: 8, + edu_institute: "Ugent", + edu_year: "7", + email_status: email_status_enum.SENT, + created_at: new Date("December 20, 2021 03:24:00"), + }; + + const found = await getLatestJobApplicationOfStudent(expected.student_id); + + const [attachments, applied_roles, job_application_skill] = + await getDataAssociatedWithApplication(found?.job_application_id); + + if (found) { + job_application_check(expected, found); + } + expect(found).toHaveProperty("email_status", expected.email_status); + expect(found).toHaveProperty("attachment", attachments); + expect(found).toHaveProperty( + "job_application_skill", + job_application_skill + ); + expect(found).toHaveProperty("applied_role", applied_roles); +}); + +it("should return the job application", async () => { + const applications = await prisma.job_application.findMany(); + + const expected = applications[0]; + + const found = await getJobApplication(applications[0].job_application_id); + + const [attachments, applied_roles, job_application_skill] = + await getDataAssociatedWithApplication(found?.job_application_id); + + if (found) { + job_application_check(expected, found); + } + expect(found).toHaveProperty("email_status", expected.email_status); + expect(found).toHaveProperty("attachment", attachments); + expect(found).toHaveProperty( + "job_application_skill", + job_application_skill + ); + expect(found).toHaveProperty("applied_role", applied_roles); +}); + +it("should return all job applications of a year", async () => { + const found = await getJobApplicationByYear(2022); + + // just check if all "extra" fields are there + found.forEach((app) => { + expect(app).toHaveProperty("attachment"); + expect(app.attachment).toBeTruthy(); + expect(app).toHaveProperty("job_application_skill"); + expect(app.job_application_skill).toBeTruthy(); + expect(app).toHaveProperty("applied_role"); + expect(app.applied_role).toBeTruthy(); + }); + + // only 3 applications for the given osoc edition + expect(found.length).toEqual(3); +}); diff --git a/backend/tests/orm_integration/job_application_skill.test.ts b/backend/tests/orm_integration/job_application_skill.test.ts new file mode 100644 index 00000000..7ddabcce --- /dev/null +++ b/backend/tests/orm_integration/job_application_skill.test.ts @@ -0,0 +1,282 @@ +import prisma from "../../prisma/prisma"; +import { + createJobApplicationSkill, + getAllJobApplicationSkill, + getAllJobApplicationSkillByJobApplication, + getJobApplicationSkill, + updateJobApplicationSkill, + deleteJobApplicationSkill, + deleteSkillsByJobApplicationId, +} from "../../orm_functions/job_application_skill"; +import { + CreateJobApplicationSkill, + UpdateJobApplicationSkill, +} from "../../orm_functions/orm_types"; + +const jobApplicationSkill1: UpdateJobApplicationSkill = { + JobApplicationSkillId: 1, + JobApplicationId: 0, + skill: "C++", + languageId: 0, + level: 2, + isPreferred: true, + is_best: true, +}; + +const jobApplicationSkill2: UpdateJobApplicationSkill = { + JobApplicationSkillId: 1, + JobApplicationId: 0, + skill: "C++", + languageId: 0, + level: 2, + isPreferred: true, + is_best: true, +}; + +it("should create 1 new job application skill", async () => { + const job_application = await prisma.job_application.findFirst(); + const language = await prisma.language.findFirst(); + + if (job_application && language) { + const jobApplicationSkill: CreateJobApplicationSkill = { + jobApplicationId: job_application.job_application_id, + skill: "C++", + languageId: language.language_id, + level: 2, + isPreferred: true, + isBest: true, + }; + jobApplicationSkill1.JobApplicationId = + job_application.job_application_id; + jobApplicationSkill2.JobApplicationId = + job_application.job_application_id; + jobApplicationSkill1.languageId = language.language_id; + jobApplicationSkill2.languageId = language.language_id; + + const created_job_application_skill = await createJobApplicationSkill( + jobApplicationSkill + ); + jobApplicationSkill1.JobApplicationSkillId = + created_job_application_skill.job_application_skill_id; + jobApplicationSkill2.JobApplicationSkillId = + created_job_application_skill.job_application_skill_id; + expect(created_job_application_skill).toHaveProperty( + "job_application_id", + jobApplicationSkill1.JobApplicationId + ); + expect(created_job_application_skill).toHaveProperty( + "skill", + jobApplicationSkill1.skill + ); + expect(created_job_application_skill).toHaveProperty( + "language_id", + jobApplicationSkill1.languageId + ); + expect(created_job_application_skill).toHaveProperty( + "level", + jobApplicationSkill1.level + ); + expect(created_job_application_skill).toHaveProperty( + "is_preferred", + jobApplicationSkill1.isPreferred + ); + expect(created_job_application_skill).toHaveProperty( + "is_best", + jobApplicationSkill1.is_best + ); + } +}); + +it("should find all the job_applications skills in the db, 3 in total", async () => { + const searched_job_application_skills = await getAllJobApplicationSkill(); + expect(searched_job_application_skills.length).toEqual(3); + expect(searched_job_application_skills[2]).toHaveProperty( + "job_application_id", + jobApplicationSkill1.JobApplicationId + ); + expect(searched_job_application_skills[2]).toHaveProperty( + "skill", + jobApplicationSkill1.skill + ); + expect(searched_job_application_skills[2]).toHaveProperty( + "language_id", + jobApplicationSkill1.languageId + ); + expect(searched_job_application_skills[2]).toHaveProperty( + "level", + jobApplicationSkill1.level + ); + expect(searched_job_application_skills[2]).toHaveProperty( + "is_preferred", + jobApplicationSkill1.isPreferred + ); + expect(searched_job_application_skills[2]).toHaveProperty( + "is_best", + jobApplicationSkill1.is_best + ); +}); + +it("should find all the job_applications skills linked to the job application", async () => { + const searched_job_application_skills = + await getAllJobApplicationSkillByJobApplication( + jobApplicationSkill1.JobApplicationId + ); + expect(searched_job_application_skills[1]).toHaveProperty( + "job_application_id", + jobApplicationSkill1.JobApplicationId + ); + expect(searched_job_application_skills[1]).toHaveProperty( + "skill", + jobApplicationSkill1.skill + ); + expect(searched_job_application_skills[1]).toHaveProperty( + "language_id", + jobApplicationSkill1.languageId + ); + expect(searched_job_application_skills[1]).toHaveProperty( + "level", + jobApplicationSkill1.level + ); + expect(searched_job_application_skills[1]).toHaveProperty( + "is_preferred", + jobApplicationSkill1.isPreferred + ); + expect(searched_job_application_skills[1]).toHaveProperty( + "is_best", + jobApplicationSkill1.is_best + ); +}); + +it("should find the job_applications skill by its id", async () => { + const searched_job_application_skill = await getJobApplicationSkill( + jobApplicationSkill1.JobApplicationSkillId + ); + expect(searched_job_application_skill).toHaveProperty( + "job_application_id", + jobApplicationSkill1.JobApplicationId + ); + expect(searched_job_application_skill).toHaveProperty( + "skill", + jobApplicationSkill1.skill + ); + expect(searched_job_application_skill).toHaveProperty( + "language_id", + jobApplicationSkill1.languageId + ); + expect(searched_job_application_skill).toHaveProperty( + "level", + jobApplicationSkill1.level + ); + expect(searched_job_application_skill).toHaveProperty( + "is_preferred", + jobApplicationSkill1.isPreferred + ); + expect(searched_job_application_skill).toHaveProperty( + "is_best", + jobApplicationSkill1.is_best + ); +}); + +it("should update job application skill based upon id", async () => { + const updated_job_application_skill = await updateJobApplicationSkill( + jobApplicationSkill2 + ); + expect(updated_job_application_skill).toHaveProperty( + "job_application_id", + jobApplicationSkill2.JobApplicationId + ); + expect(updated_job_application_skill).toHaveProperty( + "skill", + jobApplicationSkill2.skill + ); + expect(updated_job_application_skill).toHaveProperty( + "language_id", + jobApplicationSkill2.languageId + ); + expect(updated_job_application_skill).toHaveProperty( + "level", + jobApplicationSkill2.level + ); + expect(updated_job_application_skill).toHaveProperty( + "is_preferred", + jobApplicationSkill2.isPreferred + ); + expect(updated_job_application_skill).toHaveProperty( + "is_best", + jobApplicationSkill2.is_best + ); +}); + +it("should delete the job application skill based upon id", async () => { + const deleted_job_application_skill = await deleteJobApplicationSkill( + jobApplicationSkill2.JobApplicationSkillId + ); + expect(deleted_job_application_skill).toHaveProperty( + "job_application_id", + jobApplicationSkill2.JobApplicationId + ); + expect(deleted_job_application_skill).toHaveProperty( + "skill", + jobApplicationSkill2.skill + ); + expect(deleted_job_application_skill).toHaveProperty( + "language_id", + jobApplicationSkill2.languageId + ); + expect(deleted_job_application_skill).toHaveProperty( + "level", + jobApplicationSkill2.level + ); + expect(deleted_job_application_skill).toHaveProperty( + "is_preferred", + jobApplicationSkill2.isPreferred + ); + expect(deleted_job_application_skill).toHaveProperty( + "is_best", + jobApplicationSkill2.is_best + ); +}); + +it("should delete all the skills for given jobApplicationId", async () => { + const [job_applications, languages] = await Promise.all([ + prisma.job_application.findMany(), + prisma.language.findMany(), + ]); + await prisma.job_application_skill.createMany({ + data: [ + { + job_application_id: job_applications[1].job_application_id, + skill: "SQL", + language_id: languages[0].language_id, + level: 5, + is_preferred: false, + is_best: true, + }, + { + job_application_id: job_applications[0].job_application_id, + skill: "Python", + language_id: languages[0].language_id, + level: 4, + is_preferred: false, + is_best: false, + }, + ], + }); + + const found = await prisma.job_application_skill.findMany({ + where: { + job_application_id: job_applications[0].job_application_id, + }, + }); + + const deleted = await deleteSkillsByJobApplicationId( + job_applications[0].job_application_id + ); + expect(deleted).toHaveProperty("count", found.length); + const searched = await prisma.job_application_skill.findMany({ + where: { + job_application_id: 0, + }, + }); + expect(searched.length).toEqual(0); +}); diff --git a/backend/tests/orm_integration/language.test.ts b/backend/tests/orm_integration/language.test.ts new file mode 100644 index 00000000..a0983e9a --- /dev/null +++ b/backend/tests/orm_integration/language.test.ts @@ -0,0 +1,58 @@ +import { UpdateLanguage } from "../../orm_functions/orm_types"; +import { + createLanguage, + getAllLanguages, + getLanguage, + getLanguageByName, + updateLanguage, + deleteLanguage, + deleteLanguageByName, +} from "../../orm_functions/language"; + +const language1: UpdateLanguage = { + languageId: 0, + name: "English", +}; + +const language2: UpdateLanguage = { + languageId: 0, + name: "German", +}; + +it("should create 1 new language with", async () => { + const created_language = await createLanguage("English"); + language1.languageId = created_language.language_id; + language2.languageId = created_language.language_id; + expect(created_language).toHaveProperty("name", language1.name); +}); + +it("should find all the languages in the db, 3 in total", async () => { + const searched_languages = await getAllLanguages(); + expect(searched_languages.length).toEqual(3); + expect(searched_languages[2]).toHaveProperty("name", language1.name); +}); + +it("should return the language, by searching for its language id", async () => { + const searched_language = await getLanguage(language1.languageId); + expect(searched_language).toHaveProperty("name", language1.name); +}); + +it("should return the language, by searching for its name", async () => { + const searched_language = await getLanguageByName(language1.name); + expect(searched_language).toHaveProperty("name", language1.name); +}); + +it("should update language based upon language id", async () => { + const updated_language = await updateLanguage(language2); + expect(updated_language).toHaveProperty("name", language2.name); +}); + +it("should delete the language based upon language id", async () => { + const deleted_language = await deleteLanguage(language2.languageId); + expect(deleted_language).toHaveProperty("name", language2.name); +}); + +it("should delete the language based upon language name", async () => { + const deleted_language = await deleteLanguageByName("French"); + expect(deleted_language).toHaveProperty("name", "French"); +}); diff --git a/backend/tests/orm_integration/login_user.test.ts b/backend/tests/orm_integration/login_user.test.ts index 507afec6..eff2edd2 100644 --- a/backend/tests/orm_integration/login_user.test.ts +++ b/backend/tests/orm_integration/login_user.test.ts @@ -1,128 +1,293 @@ -import {CreateLoginUser, UpdateLoginUser, CreatePerson} from "../../orm_functions/orm_types"; -import {createPerson} from "../../orm_functions/person"; -import {createLoginUser, getAllLoginUsers, getPasswordLoginUserByPerson, - getPasswordLoginUser, searchLoginUserByPerson, searchAllAdminLoginUsers, - searchAllCoachLoginUsers, searchAllAdminAndCoachLoginUsers, - updateLoginUser, deleteLoginUserById, deleteLoginUserByPersonId} from "../../orm_functions/login_user"; +import { + CreateLoginUser, + UpdateLoginUser, + CreatePerson, +} from "../../orm_functions/orm_types"; +import { createPerson } from "../../orm_functions/person"; +import { + createLoginUser, + getAllLoginUsers, + getPasswordLoginUserByPerson, + getPasswordLoginUser, + searchLoginUserByPerson, + searchAllAdminLoginUsers, + searchAllCoachLoginUsers, + searchAllAdminAndCoachLoginUsers, + updateLoginUser, + deleteLoginUserById, + deleteLoginUserByPersonId, + setCoach, + setAdmin, +} from "../../orm_functions/login_user"; +import prisma from "../../prisma/prisma"; const login_user: CreateLoginUser = { personId: 0, password: "easy_password", isAdmin: true, isCoach: true, - accountStatus: "ACTIVATED" -} + accountStatus: "ACTIVATED", +}; let login_user_update: UpdateLoginUser = { loginUserId: 0, password: "easy_password", isAdmin: true, isCoach: true, - accountStatus: "ACTIVATED" -} + accountStatus: "ACTIVATED", +}; -it('should create 1 new login user', async () => { +it("should create 1 new login user", async () => { const person0: CreatePerson = { email: "login_user@email.be", firstname: "login_firstname", lastname: "login_lastname", - } + }; const created_person = await createPerson(person0); - login_user.personId = created_person.person_id; - - const created_login_user = await createLoginUser(login_user); - login_user_update.loginUserId = created_login_user.login_user_id; - expect(created_login_user).toHaveProperty("password", login_user.password); - expect(created_login_user).toHaveProperty("is_admin", login_user.isAdmin); - expect(created_login_user).toHaveProperty("is_coach", login_user.isCoach); - expect(created_login_user).toHaveProperty("account_status", login_user.accountStatus); + if (created_person) { + login_user.personId = created_person.person_id; + + const created_login_user = await createLoginUser(login_user); + login_user_update.loginUserId = created_login_user.login_user_id; + expect(created_login_user).toHaveProperty( + "password", + login_user.password + ); + expect(created_login_user).toHaveProperty( + "is_admin", + login_user.isAdmin + ); + expect(created_login_user).toHaveProperty( + "is_coach", + login_user.isCoach + ); + expect(created_login_user).toHaveProperty( + "account_status", + login_user.accountStatus + ); + } }); -it('should find all the login users in the db, 3 in total', async () => { +it("should find all the login users in the db, 4 in total", async () => { const searched_login_users = await getAllLoginUsers(); - expect(searched_login_users.length).toEqual(3); - expect(searched_login_users[2]).toHaveProperty("password", login_user.password); - expect(searched_login_users[2]).toHaveProperty("is_admin", login_user.isAdmin); - expect(searched_login_users[2]).toHaveProperty("is_coach", login_user.isCoach); - expect(searched_login_users[2]).toHaveProperty("account_status", login_user.accountStatus); + expect(searched_login_users.length).toEqual(4); + expect(searched_login_users[3]).toHaveProperty( + "password", + login_user.password + ); + expect(searched_login_users[3]).toHaveProperty( + "is_admin", + login_user.isAdmin + ); + expect(searched_login_users[3]).toHaveProperty( + "is_coach", + login_user.isCoach + ); + expect(searched_login_users[3]).toHaveProperty( + "account_status", + login_user.accountStatus + ); }); -it('should return the password, by searching for its person id', async () => { - const searched_password = await getPasswordLoginUserByPerson(login_user.personId!); +it("should return the password, by searching for its person id", async () => { + const searched_password = await getPasswordLoginUserByPerson( + login_user.personId + ); expect(searched_password).toHaveProperty("password", login_user.password); }); -it('should return the password, by searching for its login user id', async () => { - const searched_password = await getPasswordLoginUser(login_user_update.loginUserId); +it("should return the password, by searching for its login user id", async () => { + const searched_password = await getPasswordLoginUser( + login_user_update.loginUserId + ); expect(searched_password).toHaveProperty("password", login_user.password); }); -it('should return the login user, by searching for its person id', async () => { - const searched_login_user = await searchLoginUserByPerson(login_user.personId!); +it("should return the login user, by searching for its person id", async () => { + const searched_login_user = await searchLoginUserByPerson( + login_user.personId + ); expect(searched_login_user).toHaveProperty("password", login_user.password); expect(searched_login_user).toHaveProperty("is_admin", login_user.isAdmin); expect(searched_login_user).toHaveProperty("is_coach", login_user.isCoach); - expect(searched_login_user).toHaveProperty("account_status", login_user.accountStatus); + expect(searched_login_user).toHaveProperty( + "account_status", + login_user.accountStatus + ); }); -it('should find all the login users in the db that are admin, 3 in total', async () => { +it("should find all the login users in the db that are admin, 4 in total", async () => { const searched_login_users = await searchAllAdminLoginUsers(true); - expect(searched_login_users.length).toEqual(3); - expect(searched_login_users[2]).toHaveProperty("password", login_user.password); - expect(searched_login_users[2]).toHaveProperty("is_admin", login_user.isAdmin); - expect(searched_login_users[2]).toHaveProperty("is_coach", login_user.isCoach); - expect(searched_login_users[2]).toHaveProperty("account_status", login_user.accountStatus); + expect(searched_login_users.length).toEqual(4); + expect(searched_login_users[3]).toHaveProperty( + "password", + login_user.password + ); + expect(searched_login_users[3]).toHaveProperty( + "is_admin", + login_user.isAdmin + ); + expect(searched_login_users[3]).toHaveProperty( + "is_coach", + login_user.isCoach + ); + expect(searched_login_users[3]).toHaveProperty( + "account_status", + login_user.accountStatus + ); }); -it('should find all the login users in the db that are coach, 2 in total', async () => { +it("should find all the login users in the db that are coach, 3 in total", async () => { const searched_login_users = await searchAllCoachLoginUsers(true); - expect(searched_login_users.length).toEqual(2); - expect(searched_login_users[1]).toHaveProperty("password", login_user.password); - expect(searched_login_users[1]).toHaveProperty("is_admin", login_user.isAdmin); - expect(searched_login_users[1]).toHaveProperty("is_coach", login_user.isCoach); - expect(searched_login_users[1]).toHaveProperty("account_status", login_user.accountStatus); + expect(searched_login_users.length).toEqual(3); + expect(searched_login_users[2]).toHaveProperty( + "password", + login_user.password + ); + expect(searched_login_users[2]).toHaveProperty( + "is_admin", + login_user.isAdmin + ); + expect(searched_login_users[2]).toHaveProperty( + "is_coach", + login_user.isCoach + ); + expect(searched_login_users[2]).toHaveProperty( + "account_status", + login_user.accountStatus + ); }); -it('should find all the login users in the db that are admin or coach, 3 in total', async () => { +it("should find all the login users in the db that are admin or coach, 3 in total", async () => { const searched_login_users = await searchAllAdminAndCoachLoginUsers(true); - expect(searched_login_users.length).toEqual(2); - expect(searched_login_users[1]).toHaveProperty("password", login_user.password); - expect(searched_login_users[1]).toHaveProperty("is_admin", login_user.isAdmin); - expect(searched_login_users[1]).toHaveProperty("is_coach", login_user.isCoach); - expect(searched_login_users[1]).toHaveProperty("account_status", login_user.accountStatus); + expect(searched_login_users.length).toEqual(3); + expect(searched_login_users[2]).toHaveProperty( + "password", + login_user.password + ); + expect(searched_login_users[2]).toHaveProperty( + "is_admin", + login_user.isAdmin + ); + expect(searched_login_users[2]).toHaveProperty( + "is_coach", + login_user.isCoach + ); + expect(searched_login_users[2]).toHaveProperty( + "account_status", + login_user.accountStatus + ); +}); + +it("should update the isCoach field and return the updated entry", async () => { + const login_users = await prisma.login_user.findMany({ + include: { + person: true, + }, + }); + const user = login_users[0]; + const updated = await setCoach(user.login_user_id, !user.is_coach); + expect(updated).toHaveProperty("login_user_id", user.login_user_id); + expect(updated).toHaveProperty("is_coach", !user.is_coach); + expect(updated).toHaveProperty("is_admin", user.is_admin); + expect(updated).toHaveProperty("account_status", user.account_status); + expect(updated).toHaveProperty("person", user.person); + // undo the operation (for further tests) + await setCoach(user.login_user_id, user.is_coach); +}); + +it("should update the isAdmin field and return the updated entry", async () => { + const login_users = await prisma.login_user.findMany({ + include: { + person: true, + }, + }); + const user = login_users[0]; + const updated = await setAdmin(user.login_user_id, !user.is_admin); + expect(updated).toHaveProperty("login_user_id", user.login_user_id); + expect(updated).toHaveProperty("is_coach", user.is_coach); + expect(updated).toHaveProperty("is_admin", !user.is_admin); + expect(updated).toHaveProperty("account_status", user.account_status); + expect(updated).toHaveProperty("person", user.person); + // undo the operation (for further tests) + await setAdmin(user.login_user_id, user.is_admin); }); -it('should update login user based upon login user id', async () => { - const searched_login_user = await searchLoginUserByPerson(login_user.personId!); - const loginUserUpdate: UpdateLoginUser = { - loginUserId: searched_login_user!.login_user_id, - password: "last_pass", - isAdmin: false, - isCoach: false, - accountStatus: "DISABLED" +it("should update login user based upon login user id", async () => { + const searched_login_user = await searchLoginUserByPerson( + login_user.personId + ); + if (searched_login_user) { + const loginUserUpdate: UpdateLoginUser = { + loginUserId: searched_login_user.login_user_id, + password: "last_pass", + isAdmin: false, + isCoach: false, + accountStatus: "DISABLED", + }; + login_user_update = loginUserUpdate; + const updated_login_user = await updateLoginUser(loginUserUpdate); + expect(updated_login_user).toHaveProperty( + "password", + loginUserUpdate.password + ); + expect(updated_login_user).toHaveProperty( + "is_admin", + loginUserUpdate.isAdmin + ); + expect(updated_login_user).toHaveProperty( + "is_coach", + loginUserUpdate.isCoach + ); + expect(updated_login_user).toHaveProperty( + "account_status", + loginUserUpdate.accountStatus + ); } - login_user_update = loginUserUpdate; - const updated_login_user = await updateLoginUser(loginUserUpdate); - expect(updated_login_user).toHaveProperty("password", loginUserUpdate.password); - expect(updated_login_user).toHaveProperty("is_admin", loginUserUpdate.isAdmin); - expect(updated_login_user).toHaveProperty("is_coach", loginUserUpdate.isCoach); - expect(updated_login_user).toHaveProperty("account_status", loginUserUpdate.accountStatus); }); -it('should delete the login user based upon login user id', async () => { +it("should delete the login user based upon login user id", async () => { const searched_login_users = await searchAllCoachLoginUsers(false); - const deleted_login_user = await deleteLoginUserById(searched_login_users[0].login_user_id); - expect(deleted_login_user).toHaveProperty("password", searched_login_users[0].password); - expect(deleted_login_user).toHaveProperty("is_admin", searched_login_users[0].is_admin); - expect(deleted_login_user).toHaveProperty("is_coach", searched_login_users[0].is_coach); - expect(deleted_login_user).toHaveProperty("account_status", searched_login_users[0].account_status); + const deleted_login_user = await deleteLoginUserById( + searched_login_users[0].login_user_id + ); + expect(deleted_login_user).toHaveProperty( + "password", + searched_login_users[0].password + ); + expect(deleted_login_user).toHaveProperty( + "is_admin", + searched_login_users[0].is_admin + ); + expect(deleted_login_user).toHaveProperty( + "is_coach", + searched_login_users[0].is_coach + ); + expect(deleted_login_user).toHaveProperty( + "account_status", + searched_login_users[0].account_status + ); }); -it('should delete the login user based upon person id', async () => { - const deleted_login_user = await deleteLoginUserByPersonId(login_user.personId!); - expect(deleted_login_user).toHaveProperty("password", login_user_update.password); - expect(deleted_login_user).toHaveProperty("is_admin", login_user_update.isAdmin); - expect(deleted_login_user).toHaveProperty("is_coach", login_user_update.isCoach); - expect(deleted_login_user).toHaveProperty("account_status", login_user_update.accountStatus); +it("should delete the login user based upon person id", async () => { + const deleted_login_user = await deleteLoginUserByPersonId( + login_user.personId + ); + expect(deleted_login_user).toHaveProperty( + "password", + login_user_update.password + ); + expect(deleted_login_user).toHaveProperty( + "is_admin", + login_user_update.isAdmin + ); + expect(deleted_login_user).toHaveProperty( + "is_coach", + login_user_update.isCoach + ); + expect(deleted_login_user).toHaveProperty( + "account_status", + login_user_update.accountStatus + ); }); diff --git a/backend/tests/orm_integration/osoc.test.ts b/backend/tests/orm_integration/osoc.test.ts index 729b3225..2edc4270 100644 --- a/backend/tests/orm_integration/osoc.test.ts +++ b/backend/tests/orm_integration/osoc.test.ts @@ -1,10 +1,13 @@ import { - createOsoc, deleteOsoc, deleteOsocByYear, + createOsoc, + deleteOsoc, + deleteOsocByYear, getAllOsoc, getOsocAfterYear, getOsocBeforeYear, getOsocByYear, - updateOsoc + updateOsoc, + getLatestOsoc, } from "../../orm_functions/osoc"; import prisma from "../../prisma/prisma"; @@ -12,17 +15,24 @@ it("should create a new osoc edition", async () => { const newOsoc = await createOsoc(2024); expect(newOsoc).toHaveProperty("year", 2024); expect(newOsoc).toHaveProperty("osoc_id"); - }); it("should return all osoc editions", async () => { const osocs = await getAllOsoc(); - [2022, 2023, 2024].forEach((year, index) =>{ - expect(osocs[index]).toHaveProperty("year", year); - expect(osocs[index]).toHaveProperty("osoc_id"); + [2022, 2023, 2024].forEach((year, index) => { + if (year in osocs.map((osoc) => osoc.year)) { + expect(osocs[index]).toHaveProperty("year", year); + expect(osocs[index]).toHaveProperty("osoc_id"); + } }); }); +it("should return the latest osoc edition", async () => { + const osoc = await getLatestOsoc(); + expect(osoc).toHaveProperty("year", 2024); + expect(osoc).toHaveProperty("osoc_id"); +}); + it("should return the osoc edition by year", async () => { const osoc = await getOsocByYear(2022); expect(osoc).toBeTruthy(); @@ -51,37 +61,47 @@ it("should return all osoc editions after 2022", async () => { }); it("should update the selected osoc", async () => { - const osoc = await getOsocByYear(2024); + const osoc = await getOsocByYear(2024); - const updatedOsoc = await updateOsoc({osocId: osoc!.osoc_id, year: 2025}); - expect(updatedOsoc).toHaveProperty("year", 2025); + if (osoc) { + const updatedOsoc = await updateOsoc({ + osocId: osoc.osoc_id, + year: 2025, + }); + expect(updatedOsoc).toHaveProperty("year", 2025); - const osocUpdatedFound = await prisma.osoc.findUnique({ - where: { - osoc_id: osoc!.osoc_id - } - }); - expect(osocUpdatedFound).toHaveProperty("year", 2025); + const osocUpdatedFound = await prisma.osoc.findUnique({ + where: { + osoc_id: osoc.osoc_id, + }, + }); + expect(osocUpdatedFound).toHaveProperty("year", 2025); + } }); it("should delete the osoc", async () => { const osoc = await getOsocByYear(2025); - const removedOsoc = await deleteOsoc(osoc!.osoc_id); - expect(removedOsoc).toHaveProperty("year", osoc!.year); - expect(removedOsoc).toHaveProperty("osoc_id", osoc!.osoc_id); + if (osoc) { + const removedOsoc = await deleteOsoc(osoc.osoc_id); + expect(removedOsoc).toHaveProperty("year", osoc.year); + expect(removedOsoc).toHaveProperty("osoc_id", osoc.osoc_id); - const searchDeleted = await getOsocByYear(osoc!.year); - expect(searchDeleted).toBeNull(); + const searchDeleted = await getOsocByYear(osoc.year); + expect(searchDeleted).toBeNull(); + } }); it("should delete the osoc by year", async () => { const year = 2024; const osoc = await createOsoc(year); - const removedOsoc = await deleteOsocByYear(year); - expect(removedOsoc).toHaveProperty("osoc_id", osoc!.osoc_id); - expect(removedOsoc).toHaveProperty("year", osoc!.year); - const searchDeleted = await getOsocByYear(year); - expect(searchDeleted).toBeNull(); -}); \ No newline at end of file + if (osoc) { + const removedOsoc = await deleteOsocByYear(year); + expect(removedOsoc).toHaveProperty("osoc_id", osoc.osoc_id); + expect(removedOsoc).toHaveProperty("year", osoc.year); + + const searchDeleted = await getOsocByYear(year); + expect(searchDeleted).toBeNull(); + } +}); diff --git a/backend/tests/orm_integration/password_reset.test.ts b/backend/tests/orm_integration/password_reset.test.ts new file mode 100644 index 00000000..735b9233 --- /dev/null +++ b/backend/tests/orm_integration/password_reset.test.ts @@ -0,0 +1,60 @@ +import prisma from "../../prisma/prisma"; +import { + createOrUpdateReset, + findResetByCode, + deleteResetWithLoginUser, + deleteResetWithResetId, +} from "../../orm_functions/password_reset"; + +const newReset = "5444024619724212170969914212450321"; +const date = new Date("2022-07-13"); + +it("should create a new password resety for the given login user", async () => { + const loginUser = await prisma.login_user.findFirst(); + + if (loginUser) { + const created_password_reset = await createOrUpdateReset( + loginUser.login_user_id, + newReset, + date + ); + + expect(created_password_reset).toHaveProperty( + "login_user_id", + loginUser.login_user_id + ); + expect(created_password_reset).toHaveProperty("reset_id", newReset); + expect(created_password_reset).toHaveProperty("valid_until", date); + } +}); + +it("should return the password reset by searching for its the key", async () => { + const found_password_reset = await findResetByCode(newReset); + expect(found_password_reset).toHaveProperty("reset_id", newReset); + expect(found_password_reset).toHaveProperty("valid_until", date); +}); + +it("should delete the password reset based upon its id", async () => { + const deleted_password_reset = await deleteResetWithResetId(newReset); + expect(deleted_password_reset).toHaveProperty("reset_id", newReset); + expect(deleted_password_reset).toHaveProperty("valid_until", date); +}); + +it("should delete the password reset based upon login user id", async () => { + const loginUsers = await prisma.login_user.findMany(); + const password_reset = await prisma.password_reset.findFirst(); + + if (loginUsers[2] && password_reset) { + const deleted_password_reset = await deleteResetWithLoginUser( + loginUsers[2].login_user_id + ); + expect(deleted_password_reset).toHaveProperty( + "reset_id", + password_reset.reset_id + ); + expect(deleted_password_reset).toHaveProperty( + "valid_until", + password_reset.valid_until + ); + } +}); diff --git a/backend/tests/orm_integration/person.test.ts b/backend/tests/orm_integration/person.test.ts index 416953b6..3765d26c 100644 --- a/backend/tests/orm_integration/person.test.ts +++ b/backend/tests/orm_integration/person.test.ts @@ -1,30 +1,31 @@ -import {CreatePerson, UpdatePerson} from "../../orm_functions/orm_types"; -import {createPerson, getAllPersons, - searchPersonByName, searchPersonByLogin, updatePerson, deletePersonById} - from "../../orm_functions/person"; +import { CreatePerson, UpdatePerson } from "../../orm_functions/orm_types"; +import { + createPerson, + getAllPersons, + searchPersonByName, + searchPersonByLogin, + updatePerson, + deletePersonById, +} from "../../orm_functions/person"; -const person2 : UpdatePerson = { - personId: 2, +const person4: CreatePerson = { email: "test@email.be", - github: null, firstname: "first_name", lastname: "last_name", -} +}; -const person3: UpdatePerson = { - personId: 3, +const person5: CreatePerson = { github: "testhub.com", - email: null, - firstname: "person3", + firstname: "person5", lastname: "second name", -} +}; -it('should create 1 new person where github is null', async () => { +it("should create 1 new person where github is null", async () => { const person0: CreatePerson = { email: "test@email.be", firstname: "first_name", lastname: "last_name", - } + }; const created_person = await createPerson(person0); expect(created_person).toHaveProperty("github", null); @@ -33,12 +34,12 @@ it('should create 1 new person where github is null', async () => { expect(created_person).toHaveProperty("email", person0.email); }); -it('should create 1 new person where email is null', async () => { +it("should create 1 new person where email is null", async () => { const person1: CreatePerson = { github: "testhub.com", - firstname: "person3", + firstname: "person5", lastname: "second name", - } + }; const created_person = await createPerson(person1); expect(created_person).toHaveProperty("github", person1.github); @@ -47,68 +48,84 @@ it('should create 1 new person where email is null', async () => { expect(created_person).toHaveProperty("email", null); }); -it('should find all the persons in the db, 2 in total', async () => { +it("should find all the persons in the db, 2 in total", async () => { const searched_persons = await getAllPersons(); - expect(searched_persons[2]).toHaveProperty("github", person2.github); - expect(searched_persons[2]).toHaveProperty("firstname", person2.firstname); - expect(searched_persons[2]).toHaveProperty("lastname", person2.lastname); - expect(searched_persons[2]).toHaveProperty("email", person2.email); + expect(searched_persons[5]).toHaveProperty("github", null); + expect(searched_persons[5]).toHaveProperty("firstname", person4.firstname); + expect(searched_persons[5]).toHaveProperty("lastname", person4.lastname); + expect(searched_persons[5]).toHaveProperty("email", person4.email); - expect(searched_persons[3]).toHaveProperty("github", person3.github); - expect(searched_persons[3]).toHaveProperty("firstname", person3.firstname); - expect(searched_persons[3]).toHaveProperty("lastname", person3.lastname); - expect(searched_persons[3]).toHaveProperty("email", person3.email); + expect(searched_persons[6]).toHaveProperty("github", person5.github); + expect(searched_persons[6]).toHaveProperty("firstname", person5.firstname); + expect(searched_persons[6]).toHaveProperty("lastname", person5.lastname); + expect(searched_persons[6]).toHaveProperty("email", null); }); // Can only be tested with a login user, should therefore be tested in the login user tests? /*it('should find person 1 in the db, by searching for its email', async () => { - const searched_person = await getPasswordPersonByEmail(person2.email!); - expect(searched_person).toHaveProperty("github", person2.github); - expect(searched_person).toHaveProperty("firstname", person2.firstname); - expect(searched_person).toHaveProperty("lastname", person2.lastname); - expect(searched_person).toHaveProperty("email", person2.email); + const searched_person = await getPasswordPersonByEmail(person4.email!); + expect(searched_person).toHaveProperty("github", person4.github); + expect(searched_person).toHaveProperty("firstname", person4.firstname); + expect(searched_person).toHaveProperty("lastname", person4.lastname); + expect(searched_person).toHaveProperty("email", person4.email); });*/ -it('should find person 1 in the db, by searching for its firstname', async () => { - const searched_person = await searchPersonByName(person2.firstname!); - expect(searched_person[0]).toHaveProperty("github", person2.github); - expect(searched_person[0]).toHaveProperty("firstname", person2.firstname); - expect(searched_person[0]).toHaveProperty("lastname", person2.lastname); - expect(searched_person[0]).toHaveProperty("email", person2.email); +it("should find person 1 in the db, by searching for its firstname", async () => { + const searched_person = await searchPersonByName(person4.firstname); + expect(searched_person[0]).toHaveProperty("github", null); + expect(searched_person[0]).toHaveProperty("firstname", person4.firstname); + expect(searched_person[0]).toHaveProperty("lastname", person4.lastname); + expect(searched_person[0]).toHaveProperty("email", person4.email); }); -it('should find person 2 in the db, by searching for its lastname', async () => { - const searched_person3 = await searchPersonByName(person3.lastname!); - expect(searched_person3[0]).toHaveProperty("github", person3.github); - expect(searched_person3[0]).toHaveProperty("firstname", person3.firstname); - expect(searched_person3[0]).toHaveProperty("lastname", person3.lastname); - expect(searched_person3[0]).toHaveProperty("email", person3.email); +it("should find person 2 in the db, by searching for its lastname", async () => { + const searched_person4 = await searchPersonByName(person5.lastname); + expect(searched_person4[0]).toHaveProperty("github", person5.github); + expect(searched_person4[0]).toHaveProperty("firstname", person5.firstname); + expect(searched_person4[0]).toHaveProperty("lastname", person5.lastname); + expect(searched_person4[0]).toHaveProperty("email", null); }); -it('should find all the persons in the db with given email, 1 in total', async () => { - const searched_persons = await searchPersonByLogin(person2.email!); - expect(searched_persons[0]).toHaveProperty("github", person2.github); - expect(searched_persons[0]).toHaveProperty("firstname", person2.firstname); - expect(searched_persons[0]).toHaveProperty("lastname", person2.lastname); - expect(searched_persons[0]).toHaveProperty("email", person2.email); +it("should find all the persons in the db with given email, 1 in total", async () => { + if (person4.email != undefined) { + const searched_persons = await searchPersonByLogin(person4.email); + expect(searched_persons[0]).toHaveProperty("github", null); + expect(searched_persons[0]).toHaveProperty( + "firstname", + person4.firstname + ); + expect(searched_persons[0]).toHaveProperty( + "lastname", + person4.lastname + ); + expect(searched_persons[0]).toHaveProperty("email", person4.email); + } }); -it('should find all the persons in the db with given github, 1 in total', async () => { - const searched_persons = await searchPersonByLogin(person3.github!); - expect(searched_persons[0]).toHaveProperty("github", person3.github); - expect(searched_persons[0]).toHaveProperty("firstname", person3.firstname); - expect(searched_persons[0]).toHaveProperty("lastname", person3.lastname); - expect(searched_persons[0]).toHaveProperty("email", person3.email); +it("should find all the persons in the db with given github, 1 in total", async () => { + if (person5.github) { + const searched_persons = await searchPersonByLogin(person5.github); + expect(searched_persons[0]).toHaveProperty("github", person5.github); + expect(searched_persons[0]).toHaveProperty( + "firstname", + person5.firstname + ); + expect(searched_persons[0]).toHaveProperty( + "lastname", + person5.lastname + ); + expect(searched_persons[0]).toHaveProperty("email", null); + } }); -it('should update person based upon personid', async () => { - const searched_person2 = await searchPersonByName(person2.firstname!); +it("should update person based upon personid", async () => { + const searched_person3 = await searchPersonByName(person4.firstname); const personUpdate: UpdatePerson = { - personId: searched_person2[0].person_id, + personId: searched_person3[0].person_id, email: "new@email.be", firstname: "new_name", lastname: "different_name", - } + }; const updated_person = await updatePerson(personUpdate); expect(updated_person).toHaveProperty("github", null); expect(updated_person).toHaveProperty("firstname", personUpdate.firstname); @@ -116,12 +133,20 @@ it('should update person based upon personid', async () => { expect(updated_person).toHaveProperty("email", personUpdate.email); }); -it('should delete the person based upon personid', async () => { - const searched_person3 = await searchPersonByName(person3.lastname!); - const deleted_person = await deletePersonById(searched_person3[0].person_id); - expect(deleted_person).toHaveProperty("person_id", searched_person3[0].person_id); +it("should delete the person based upon personid", async () => { + const searched_person5 = await searchPersonByName(person5.lastname); + const deleted_person = await deletePersonById( + searched_person5[0].person_id + ); + expect(deleted_person).toHaveProperty( + "person_id", + searched_person5[0].person_id + ); expect(deleted_person).toHaveProperty("github", deleted_person.github); - expect(deleted_person).toHaveProperty("firstname", deleted_person.firstname); + expect(deleted_person).toHaveProperty( + "firstname", + deleted_person.firstname + ); expect(deleted_person).toHaveProperty("lastname", deleted_person.lastname); expect(deleted_person).toHaveProperty("email", deleted_person.email); }); diff --git a/backend/tests/orm_integration/project.test.ts b/backend/tests/orm_integration/project.test.ts new file mode 100644 index 00000000..f29d8fbb --- /dev/null +++ b/backend/tests/orm_integration/project.test.ts @@ -0,0 +1,328 @@ +import { CreateProject, UpdateProject } from "../../orm_functions/orm_types"; +import { getOsocByYear } from "../../orm_functions/osoc"; +import { + createProject, + getAllProjects, + getProjectByName, + getProjectsByOsocEdition, + getProjectsByPartner, + getProjectsByStartDate, + getProjectsByEndDate, + getProjectsStartedBeforeDate, + getProjectsStartedAfterDate, + getProjectsEndedBeforeDate, + getProjectsByNumberPositions, + updateProject, + deleteProject, + deleteProjectByOsocEdition, + deleteProjectByPartner, + getProjectsLessPositions, + getProjectsMorePositions, +} from "../../orm_functions/project"; + +const project1: CreateProject = { + name: "test-project", + osocId: 0, + partner: "test-partner", + startDate: new Date("2022-07-13"), + endDate: new Date("2022-07-15"), + positions: 7, +}; + +const project2: UpdateProject = { + projectId: 2, + name: "different-test", + osocId: 0, + partner: "different-partner", + startDate: new Date("2022-08-13"), + endDate: new Date("2022-08-15"), + positions: 8, +}; + +it("should create 1 new project where osoc is 2022", async () => { + const osoc = await getOsocByYear(2022); + if (osoc) { + project1.osocId = osoc.osoc_id; + const project0: CreateProject = { + name: "test-project", + osocId: osoc.osoc_id, + partner: "test-partner", + startDate: new Date("2022-07-13"), + endDate: new Date("2022-07-15"), + positions: 7, + }; + + const created_project = await createProject(project0); + expect(created_project).toHaveProperty("name", project0.name); + expect(created_project).toHaveProperty("osoc_id", project0.osocId); + expect(created_project).toHaveProperty("partner", project0.partner); + expect(created_project).toHaveProperty( + "start_date", + project0.startDate + ); + expect(created_project).toHaveProperty("end_date", project0.endDate); + expect(created_project).toHaveProperty("positions", project0.positions); + } +}); + +it("should find all the projects in the db, 3 in total", async () => { + const searched_projects = await getAllProjects(); + expect(searched_projects.length).toEqual(4); + expect(searched_projects[3]).toHaveProperty("name", project1.name); + expect(searched_projects[3]).toHaveProperty("osoc_id", project1.osocId); + expect(searched_projects[3]).toHaveProperty("partner", project1.partner); + expect(searched_projects[3]).toHaveProperty( + "start_date", + project1.startDate + ); + expect(searched_projects[3]).toHaveProperty("end_date", project1.endDate); + expect(searched_projects[3]).toHaveProperty( + "positions", + project1.positions + ); +}); + +it("should return the project, by searching for its name", async () => { + const searched_project = await getProjectByName(project1.name); + expect(searched_project[0]).toHaveProperty("name", project1.name); + expect(searched_project[0]).toHaveProperty("osoc_id", project1.osocId); + expect(searched_project[0]).toHaveProperty("partner", project1.partner); + expect(searched_project[0]).toHaveProperty( + "start_date", + project1.startDate + ); + expect(searched_project[0]).toHaveProperty("end_date", project1.endDate); + expect(searched_project[0]).toHaveProperty("positions", project1.positions); +}); + +it("should return the project, by searching for its osoc edition", async () => { + const osoc = await getOsocByYear(2022); + if (osoc) { + const searched_project = await getProjectsByOsocEdition(osoc.osoc_id); + expect(searched_project[1]).toHaveProperty("name", project1.name); + expect(searched_project[1]).toHaveProperty("osoc_id", project1.osocId); + expect(searched_project[1]).toHaveProperty("partner", project1.partner); + expect(searched_project[1]).toHaveProperty( + "start_date", + project1.startDate + ); + expect(searched_project[1]).toHaveProperty( + "end_date", + project1.endDate + ); + expect(searched_project[1]).toHaveProperty( + "positions", + project1.positions + ); + } +}); + +it("should return the projects, by searching for its partner name", async () => { + const searched_projects = await getProjectsByPartner(project1.partner); + expect(searched_projects[0]).toHaveProperty("name", project1.name); + expect(searched_projects[0]).toHaveProperty("osoc_id", project1.osocId); + expect(searched_projects[0]).toHaveProperty("partner", project1.partner); + expect(searched_projects[0]).toHaveProperty( + "start_date", + project1.startDate + ); + expect(searched_projects[0]).toHaveProperty("end_date", project1.endDate); + expect(searched_projects[0]).toHaveProperty( + "positions", + project1.positions + ); +}); + +it("should return the projects, by searching for its start date", async () => { + const searched_projects = await getProjectsByStartDate(project1.startDate); + expect(searched_projects[0]).toHaveProperty("name", project1.name); + expect(searched_projects[0]).toHaveProperty("osoc_id", project1.osocId); + expect(searched_projects[0]).toHaveProperty("partner", project1.partner); + expect(searched_projects[0]).toHaveProperty( + "start_date", + project1.startDate + ); + expect(searched_projects[0]).toHaveProperty("end_date", project1.endDate); + expect(searched_projects[0]).toHaveProperty( + "positions", + project1.positions + ); +}); + +it("should return the projects, by searching for its end date", async () => { + const searched_projects = await getProjectsByEndDate(project1.endDate); + expect(searched_projects[0]).toHaveProperty("name", project1.name); + expect(searched_projects[0]).toHaveProperty("osoc_id", project1.osocId); + expect(searched_projects[0]).toHaveProperty("partner", project1.partner); + expect(searched_projects[0]).toHaveProperty( + "start_date", + project1.startDate + ); + expect(searched_projects[0]).toHaveProperty("end_date", project1.endDate); + expect(searched_projects[0]).toHaveProperty( + "positions", + project1.positions + ); +}); + +it("should return the projects, by searching for all projects starting before date", async () => { + const searched_projects = await getProjectsStartedBeforeDate( + new Date("2022-07-31") + ); + expect(searched_projects[1]).toHaveProperty("name", project1.name); + expect(searched_projects[1]).toHaveProperty("osoc_id", project1.osocId); + expect(searched_projects[1]).toHaveProperty("partner", project1.partner); + expect(searched_projects[1]).toHaveProperty( + "start_date", + project1.startDate + ); + expect(searched_projects[1]).toHaveProperty("end_date", project1.endDate); + expect(searched_projects[1]).toHaveProperty( + "positions", + project1.positions + ); +}); + +it("should return the projects, by searching for all projects starting after date", async () => { + const searched_projects = await getProjectsStartedAfterDate( + new Date("2022-07-01") + ); + expect(searched_projects[2]).toHaveProperty("name", project1.name); + expect(searched_projects[2]).toHaveProperty("osoc_id", project1.osocId); + expect(searched_projects[2]).toHaveProperty("partner", project1.partner); + expect(searched_projects[2]).toHaveProperty( + "start_date", + project1.startDate + ); + expect(searched_projects[2]).toHaveProperty("end_date", project1.endDate); + expect(searched_projects[2]).toHaveProperty( + "positions", + project1.positions + ); +}); + +it("should return the projects, by searching for all projects ending before date", async () => { + const searched_projects = await getProjectsEndedBeforeDate( + new Date("2022-07-31") + ); + expect(searched_projects[1]).toHaveProperty("name", project1.name); + expect(searched_projects[1]).toHaveProperty("osoc_id", project1.osocId); + expect(searched_projects[1]).toHaveProperty("partner", project1.partner); + expect(searched_projects[1]).toHaveProperty( + "start_date", + project1.startDate + ); + expect(searched_projects[1]).toHaveProperty("end_date", project1.endDate); + expect(searched_projects[1]).toHaveProperty( + "positions", + project1.positions + ); +}); + +it("should return the projects, by searching for all projects ending after date", async () => { + const searched_projects = await getProjectsEndedBeforeDate( + new Date("2022-07-31") + ); + expect(searched_projects[1]).toHaveProperty("name", project1.name); + expect(searched_projects[1]).toHaveProperty("osoc_id", project1.osocId); + expect(searched_projects[1]).toHaveProperty("partner", project1.partner); + expect(searched_projects[1]).toHaveProperty( + "start_date", + project1.startDate + ); + expect(searched_projects[1]).toHaveProperty("end_date", project1.endDate); + expect(searched_projects[1]).toHaveProperty( + "positions", + project1.positions + ); +}); + +it("should return the projects, by searching for its number of positions", async () => { + const searched_projects = await getProjectsByNumberPositions( + project1.positions + ); + expect(searched_projects[0]).toHaveProperty("name", project1.name); + expect(searched_projects[0]).toHaveProperty("osoc_id", project1.osocId); + expect(searched_projects[0]).toHaveProperty("partner", project1.partner); + expect(searched_projects[0]).toHaveProperty( + "start_date", + project1.startDate + ); + expect(searched_projects[0]).toHaveProperty("end_date", project1.endDate); + expect(searched_projects[0]).toHaveProperty( + "positions", + project1.positions + ); +}); + +it("should return the projects, by searching for all projects with less positions", async () => { + const searched_projects = await getProjectsLessPositions( + project1.positions + 1 + ); + expect(searched_projects[0]).toHaveProperty("name", project1.name); + expect(searched_projects[0]).toHaveProperty("osoc_id", project1.osocId); + expect(searched_projects[0]).toHaveProperty("partner", project1.partner); + expect(searched_projects[0]).toHaveProperty( + "start_date", + project1.startDate + ); + expect(searched_projects[0]).toHaveProperty("end_date", project1.endDate); + expect(searched_projects[0]).toHaveProperty( + "positions", + project1.positions + ); +}); + +it("should return the projects, by searching for all projects with more positions", async () => { + const searched_projects = await getProjectsMorePositions( + project1.positions - 1 + ); + expect(searched_projects[3]).toHaveProperty("name", project1.name); + expect(searched_projects[3]).toHaveProperty("osoc_id", project1.osocId); + expect(searched_projects[3]).toHaveProperty("partner", project1.partner); + expect(searched_projects[3]).toHaveProperty( + "start_date", + project1.startDate + ); + expect(searched_projects[3]).toHaveProperty("end_date", project1.endDate); + expect(searched_projects[3]).toHaveProperty( + "positions", + project1.positions + ); +}); + +it("should update project based upon project id", async () => { + const searched_project = await getProjectByName(project1.name); + project2.projectId = searched_project[0].project_id; + project2.osocId = project1.osocId; + const updated_project = await updateProject(project2); + expect(updated_project).toHaveProperty("name", project2.name); + expect(updated_project).toHaveProperty("osoc_id", project2.osocId); + expect(updated_project).toHaveProperty("partner", project2.partner); + expect(updated_project).toHaveProperty("start_date", project2.startDate); + expect(updated_project).toHaveProperty("end_date", project2.endDate); + expect(updated_project).toHaveProperty("positions", project2.positions); +}); + +it("should delete the project based upon project id", async () => { + const deleted_project = await deleteProject(project2.projectId); + expect(deleted_project).toHaveProperty("name", project2.name); + expect(deleted_project).toHaveProperty("osoc_id", project2.osocId); + expect(deleted_project).toHaveProperty("partner", project2.partner); + expect(deleted_project).toHaveProperty("start_date", project2.startDate); + expect(deleted_project).toHaveProperty("end_date", project2.endDate); + expect(deleted_project).toHaveProperty("positions", project2.positions); +}); + +it("should delete the project based upon project partner", async () => { + const deleted_project = await deleteProjectByPartner("partner-test-2"); + expect(deleted_project).toHaveProperty("count", 1); +}); + +it("should delete the project based upon osoc id", async () => { + const osoc = await getOsocByYear(2023); + if (osoc) { + const deleted_project = await deleteProjectByOsocEdition(osoc.osoc_id); + expect(deleted_project).toHaveProperty("count", 1); + } +}); diff --git a/backend/tests/orm_integration/project_role.test.ts b/backend/tests/orm_integration/project_role.test.ts new file mode 100644 index 00000000..9870beb4 --- /dev/null +++ b/backend/tests/orm_integration/project_role.test.ts @@ -0,0 +1,144 @@ +import { + CreateProjectRole, + UpdateProjectRole, +} from "../../orm_functions/orm_types"; +import { + createProjectRole, + getProjectRolesByProject, + getNumberOfRolesByProjectAndRole, + getProjectRoleNamesByProject, + updateProjectRole, + deleteProjectRole, + getNumberOfFreePositions, +} from "../../orm_functions/project_role"; +import { getAllProjects } from "../../orm_functions/project"; +import { getRolesByName } from "../../orm_functions/role"; + +const projectRole1: UpdateProjectRole = { + projectRoleId: 0, + projectId: 1, + roleId: 1, + positions: 2, +}; + +const projectRole2: UpdateProjectRole = { + projectRoleId: 0, + projectId: 1, + roleId: 1, + positions: 3, +}; + +it("should create 1 new project role with role developer", async () => { + const projects = await getAllProjects(); + const role = await getRolesByName("Developer"); + + if (role) { + const projectRole: CreateProjectRole = { + projectId: projects[0].project_id, + roleId: role.role_id, + positions: 2, + }; + projectRole1.projectId = projects[0].project_id; + projectRole1.roleId = role.role_id; + projectRole2.projectId = projects[0].project_id; + projectRole2.roleId = role.role_id; + + const created_project_role = await createProjectRole(projectRole); + projectRole1.projectRoleId = created_project_role.project_role_id; + projectRole2.projectRoleId = created_project_role.project_role_id; + expect(created_project_role).toHaveProperty( + "project_id", + projectRole1.projectId + ); + expect(created_project_role).toHaveProperty( + "role_id", + projectRole1.roleId + ); + expect(created_project_role).toHaveProperty( + "positions", + projectRole1.positions + ); + } +}); + +it("should return the project role, by searching for its project", async () => { + const searched_project_role = await getProjectRolesByProject( + projectRole1.projectId + ); + expect(searched_project_role[2]).toHaveProperty( + "project_id", + projectRole1.projectId + ); + expect(searched_project_role[2]).toHaveProperty( + "role_id", + projectRole1.roleId + ); + expect(searched_project_role[2]).toHaveProperty( + "positions", + projectRole1.positions + ); +}); + +it("should return the project role, by searching for its project and projectrole", async () => { + const searched_project_role = await getNumberOfRolesByProjectAndRole( + projectRole1.projectId, + projectRole1.projectRoleId + ); + expect(searched_project_role[0]).toHaveProperty( + "project_id", + projectRole1.projectId + ); + expect(searched_project_role[0]).toHaveProperty( + "role_id", + projectRole1.roleId + ); + expect(searched_project_role[0]).toHaveProperty( + "positions", + projectRole1.positions + ); +}); + +it("should return the project role and role, by searching for its project role id", async () => { + const searched_project_role = await getProjectRoleNamesByProject( + projectRole1.projectId + ); + expect(searched_project_role[2]).toHaveProperty( + "project_id", + projectRole1.projectId + ); + expect(searched_project_role[2]).toHaveProperty( + "role_id", + projectRole1.roleId + ); + expect(searched_project_role[2]).toHaveProperty( + "positions", + projectRole1.positions + ); +}); + +it("should return the the number of free positions, by searching for its project role id", async () => { + const number_of_positions = await getNumberOfFreePositions( + projectRole1.projectRoleId + ); + expect(number_of_positions).toEqual(2); +}); + +it("should update projectrole based upon project role id", async () => { + const updated_project = await updateProjectRole(projectRole2); + expect(updated_project).toHaveProperty( + "project_id", + projectRole2.projectId + ); + expect(updated_project).toHaveProperty("role_id", projectRole2.roleId); + expect(updated_project).toHaveProperty("positions", projectRole2.positions); +}); + +it("should delete the project role based upon project role id", async () => { + const deleted_project = await deleteProjectRole(projectRole2.projectRoleId); + expect(deleted_project).toHaveProperty( + "project_id", + projectRole2.projectId + ); + expect(deleted_project).toHaveProperty("role_id", projectRole2.roleId); + expect(deleted_project).toHaveProperty("positions", projectRole2.positions); +}); diff --git a/backend/tests/orm_integration/role.test.ts b/backend/tests/orm_integration/role.test.ts new file mode 100644 index 00000000..c82650f7 --- /dev/null +++ b/backend/tests/orm_integration/role.test.ts @@ -0,0 +1,70 @@ +import { UpdateRole } from "../../orm_functions/orm_types"; +import { + createRole, + getAllRoles, + getRolesByName, + getRole, + getProjectRoleWithRoleName, + updateRole, + deleteRole, + deleteRoleByName, +} from "../../orm_functions/role"; +import prisma_project from "../../prisma/prisma"; + +const role1: UpdateRole = { + roleId: 0, + name: "Data Scientist", +}; + +const role2: UpdateRole = { + roleId: 0, + name: "Web Designer", +}; + +it("should create 1 new role where", async () => { + const created__role = await createRole("Data Scientist"); + role1.roleId = created__role.role_id; + role2.roleId = created__role.role_id; + expect(created__role).toHaveProperty("role_id", role1.roleId); + expect(created__role).toHaveProperty("name", role1.name); +}); + +it("should find all the roles in the db, 3 in total", async () => { + const searched_roles = await getAllRoles(); + expect(searched_roles.length).toEqual(5); + expect(searched_roles[4]).toHaveProperty("name", role1.name); +}); + +it("should return the role, by searching for its role id", async () => { + const searched_role = await getRole(role1.roleId); + expect(searched_role).toHaveProperty("name", role1.name); +}); + +it("should return the role, by searching for its role name", async () => { + const searched_role = await getRolesByName(role1.name); + expect(searched_role).toHaveProperty("name", role1.name); +}); + +it("should return the project role, by searching for its role name and project id", async () => { + const project = await prisma_project.project.findMany(); + const searched_role = await getProjectRoleWithRoleName( + "Developer", + project[0].project_id + ); + expect(searched_role).toHaveProperty("positions", 3); +}); + +it("should update role based upon role id", async () => { + const updated_role = await updateRole(role2); + expect(updated_role).toHaveProperty("name", role2.name); +}); + +it("should delete the role based upon role id", async () => { + const deleted_role = await deleteRole(role2.roleId); + expect(deleted_role).toHaveProperty("name", role2.name); +}); + +it("should delete the role based upon role name", async () => { + const deleted_role = await deleteRoleByName("Marketeer"); + expect(deleted_role).toHaveProperty("name", "Marketeer"); +}); diff --git a/backend/tests/orm_integration/session_key.test.ts b/backend/tests/orm_integration/session_key.test.ts new file mode 100644 index 00000000..fed256cc --- /dev/null +++ b/backend/tests/orm_integration/session_key.test.ts @@ -0,0 +1,103 @@ +import prisma from "../../prisma/prisma"; +import { + addSessionKey, + refreshKey, + checkSessionKey, + removeAllKeysForUser, +} from "../../orm_functions/session_key"; + +it("should create a new session key for the given login user", async () => { + const loginUsers = await prisma.login_user.findMany(); + + const newKey = "newKey"; + const futureDate = new Date(); + futureDate.setDate(futureDate.getDate() + 10); + + const created = await addSessionKey( + loginUsers[0].login_user_id, + newKey, + futureDate + ); + + expect(created).toHaveProperty( + "login_user_id", + loginUsers[0].login_user_id + ); + expect(created).toHaveProperty("session_key", newKey); + expect(created).toHaveProperty("valid_until", futureDate); +}); + +it("should return the loginUser associated with the key", async () => { + const loginUsers = await prisma.login_user.findMany(); + const found_users = await checkSessionKey("newKey"); + expect(found_users).toHaveProperty( + "login_user_id", + loginUsers[0].login_user_id + ); +}); + +it("should return an error because the key doesn't exist", async () => { + try { + await checkSessionKey("doesn't exist"); + // should not get executed because checkSessionKey should throw error because key doesn't exist + expect(false).toBeTruthy(); + } catch (e) { + expect(e).toBeInstanceOf(Error); + } +}); + +it("should overwrite the old key with the new key", async () => { + const existing_keys = await prisma.session_keys.findMany(); + + const futureDate = new Date(); + futureDate.setDate(futureDate.getDate() + 20); + const updated = await refreshKey(existing_keys[0].session_key, futureDate); + expect(updated).toHaveProperty( + "login_user_id", + existing_keys[0].login_user_id + ); + expect(updated).toHaveProperty("session_key", existing_keys[0].session_key); + expect(updated).toHaveProperty("valid_until", futureDate); + + const updated_keys = await prisma.session_keys.findMany({ + where: { + login_user_id: existing_keys[0].login_user_id, + }, + }); + + let updated_found = false; + updated_keys.forEach((record) => { + if (record.session_key === existing_keys[0].session_key) { + updated_found = true; + } + }); + if (!updated_found) { + // should never get executed if the updated succeeded! + expect(false).toBeTruthy(); + } +}); + +it("should delete all session keys of the user with given key", async () => { + const login_users = await prisma.login_user.findMany(); + const futureDate = new Date(); + futureDate.setDate(futureDate.getDate() + 15); + + await prisma.session_keys.create({ + data: { + login_user_id: login_users[1].login_user_id, + session_key: "key_user1", + valid_until: futureDate, + }, + }); + + const deleted = await removeAllKeysForUser("key"); + expect(deleted).toHaveProperty("count", 3); + const remaining_keys = await prisma.session_keys.findMany(); + expect(remaining_keys.length).toEqual(1); + expect(remaining_keys[0]).toHaveProperty( + "login_user_id", + login_users[1].login_user_id + ); + + await removeAllKeysForUser("key_user1"); +}); diff --git a/backend/tests/orm_integration/student.test.ts b/backend/tests/orm_integration/student.test.ts new file mode 100644 index 00000000..d0f9b2ed --- /dev/null +++ b/backend/tests/orm_integration/student.test.ts @@ -0,0 +1,143 @@ +import { CreateStudent, UpdateStudent } from "../../orm_functions/orm_types"; +import prisma from "../../prisma/prisma"; +import { + createStudent, + getAllStudents, + getStudent, + updateStudent, + searchStudentByGender, + deleteStudent, +} from "../../orm_functions/student"; + +const student1: UpdateStudent = { + studentId: 0, + gender: "Male", + pronouns: "He/ Him", + phoneNumber: "013456789", + nickname: "Superman", + alumni: true, +}; + +const student2: UpdateStudent = { + studentId: 0, + gender: "Female", + pronouns: "She/ Her", + phoneNumber: "9876543210", + nickname: "Superwoman", + alumni: true, +}; + +it("should create 1 new student", async () => { + const person = await prisma.person.findMany(); + if (person) { + const student: CreateStudent = { + personId: person[0].person_id, + gender: "Male", + pronouns: "He/ Him", + phoneNumber: "013456789", + nickname: "Superman", + alumni: true, + }; + + const created_student = await createStudent(student); + student1.studentId = created_student.student_id; + student2.studentId = created_student.student_id; + expect(created_student).toHaveProperty( + "student_id", + created_student.student_id + ); + expect(created_student).toHaveProperty( + "gender", + created_student.gender + ); + expect(created_student).toHaveProperty( + "pronouns", + created_student.pronouns + ); + expect(created_student).toHaveProperty( + "phone_number", + created_student.phone_number + ); + expect(created_student).toHaveProperty( + "nickname", + created_student.nickname + ); + expect(created_student).toHaveProperty( + "alumni", + created_student.alumni + ); + } +}); + +it("should find all the students in the db, 3 in total", async () => { + const searched_students = await getAllStudents(); + expect(searched_students.length).toEqual(4); + expect(searched_students[3]).toHaveProperty( + "student_id", + student1.studentId + ); + expect(searched_students[3]).toHaveProperty("gender", student1.gender); + expect(searched_students[3]).toHaveProperty("pronouns", student1.pronouns); + expect(searched_students[3]).toHaveProperty( + "phone_number", + student1.phoneNumber + ); + expect(searched_students[3]).toHaveProperty("nickname", student1.nickname); + expect(searched_students[3]).toHaveProperty("alumni", student1.alumni); +}); + +it("should return the student, by searching for its id", async () => { + const searched_student = await getStudent(student1.studentId); + expect(searched_student).toHaveProperty("student_id", student1.studentId); + expect(searched_student).toHaveProperty("gender", student1.gender); + expect(searched_student).toHaveProperty("pronouns", student1.pronouns); + expect(searched_student).toHaveProperty( + "phone_number", + student1.phoneNumber + ); + expect(searched_student).toHaveProperty("nickname", student1.nickname); + expect(searched_student).toHaveProperty("alumni", student1.alumni); +}); + +it("should return the students, by searching for gender", async () => { + const searched_student = await searchStudentByGender("Male"); + expect(searched_student.length).toEqual(2); + expect(searched_student[1]).toHaveProperty( + "student_id", + student1.studentId + ); + expect(searched_student[1]).toHaveProperty("gender", student1.gender); + expect(searched_student[1]).toHaveProperty("pronouns", student1.pronouns); + expect(searched_student[1]).toHaveProperty( + "phone_number", + student1.phoneNumber + ); + expect(searched_student[1]).toHaveProperty("nickname", student1.nickname); + expect(searched_student[1]).toHaveProperty("alumni", student1.alumni); +}); + +it("should update student based upon student id", async () => { + const updated_student = await updateStudent(student2); + expect(updated_student).toHaveProperty("student_id", student2.studentId); + expect(updated_student).toHaveProperty("gender", student2.gender); + expect(updated_student).toHaveProperty("pronouns", student2.pronouns); + expect(updated_student).toHaveProperty( + "phone_number", + student2.phoneNumber + ); + expect(updated_student).toHaveProperty("nickname", student2.nickname); + expect(updated_student).toHaveProperty("alumni", student2.alumni); +}); + +it("should delete the student based upon student id", async () => { + const deleted_student = await deleteStudent(student2.studentId); + expect(deleted_student).toHaveProperty("student_id", student2.studentId); + expect(deleted_student).toHaveProperty("gender", student2.gender); + expect(deleted_student).toHaveProperty("pronouns", student2.pronouns); + expect(deleted_student).toHaveProperty( + "phone_number", + student2.phoneNumber + ); + expect(deleted_student).toHaveProperty("nickname", student2.nickname); + expect(deleted_student).toHaveProperty("alumni", student2.alumni); +}); diff --git a/backend/tests/orm_tests/contract.test.ts b/backend/tests/orm_tests/contract.test.ts deleted file mode 100644 index f6c580ca..00000000 --- a/backend/tests/orm_tests/contract.test.ts +++ /dev/null @@ -1,78 +0,0 @@ -import {prismaMock} from "./singleton"; -import {contract_status_enum} from "@prisma/client"; -import {createContract, removeContract, removeContractsFromStudent, updateContract} from "../../orm_functions/contract"; - -test("should create a contract", async () => { - - const contract = { - studentId: 1, - projectRoleId: 1, - information: "fake information", - loginUserId: 5, - contractStatus: contract_status_enum.WAIT_APPROVAL -} - - const returnContract = { - contract_id: 5, - student_id: 1, - project_role_id: 1, - information: "fake information", - created_by_login_user_id: 5, - contract_status: contract_status_enum.WAIT_APPROVAL - } - - prismaMock.contract.create.mockResolvedValue(returnContract); - - await expect(createContract(contract)).resolves.toEqual(returnContract); -}); - -test("should update a contract", async () => { - const contract = { - contractId: 1, - information: "fake information", - loginUserId: 5, - contractStatus: contract_status_enum.WAIT_APPROVAL - } - - const returnContract = { - contract_id: 1, - student_id: 2, - project_role_id: 1, - information: "fake information", - created_by_login_user_id: 5, - contract_status: contract_status_enum.WAIT_APPROVAL - } - - prismaMock.contract.update.mockResolvedValue(returnContract); - - await expect(updateContract(contract)).resolves.toEqual(returnContract); -}); - -test("should remove contracts associated with the student", async () => { - const count = { count: 4}; - - const studentId = 2; - - prismaMock.contract.deleteMany.mockResolvedValue(count); - - await expect(removeContractsFromStudent(studentId)).resolves.toEqual(count); -}); - -test("should remove the give contract", async () => { - const contractId = 2; - - const returnContract = { - contract_id: 1, - student_id: 2, - project_role_id: 1, - information: "fake information", - created_by_login_user_id: 5, - contract_status: contract_status_enum.WAIT_APPROVAL - } - - prismaMock.contract.delete.mockResolvedValue(returnContract); - - await expect(removeContract(contractId)).resolves.toEqual(returnContract); -}); - - diff --git a/backend/tests/orm_tests/language.test.ts b/backend/tests/orm_tests/language.test.ts deleted file mode 100644 index fb9e1da8..00000000 --- a/backend/tests/orm_tests/language.test.ts +++ /dev/null @@ -1,54 +0,0 @@ -import {prismaMock} from "./singleton"; -import { - createLanguage, deleteLanguage, deleteLanguageByName, - getAllLanguages, - getLanguage, - getLanguageByName, - updateLanguage -} from "../../orm_functions/language"; - -const returnValue = { - language_id: 0, - name: "languageName" -} - -test("should create a new language record and return it", async () => { - - prismaMock.language.create.mockResolvedValue(returnValue); - await expect(createLanguage("languageName")).resolves.toEqual(returnValue); -}); - -test("should return a list of all languages registered", async () => { - const returnValue = [{ - language_id: 0, - name: "languageName" - }]; - - prismaMock.language.findMany.mockResolvedValue(returnValue); - await expect(getAllLanguages()).resolves.toEqual(returnValue); -}); - -test("should return the language with the given id", async () => { - prismaMock.language.findUnique.mockResolvedValue(returnValue); - await expect(getLanguage(0)).resolves.toEqual(returnValue); -}); - -test("should return the asked language by name", async () => { - prismaMock.language.findUnique.mockResolvedValue(returnValue); - await expect(getLanguageByName("languageName")).resolves.toEqual(returnValue); -}); - -test("should update the given language", async () => { - prismaMock.language.update.mockResolvedValue(returnValue); - await expect(updateLanguage({languageId: 0, name: "updated"})).resolves.toEqual(returnValue); -}); - -test("should delete the language with the given id and return the deleted record", async () => { - prismaMock.language.delete.mockResolvedValue(returnValue); - await expect(deleteLanguage(0)).resolves.toEqual(returnValue); -}); - -test("should delete the given language name and its record and return the record", async () => { - prismaMock.language.delete.mockResolvedValue(returnValue); - await expect(deleteLanguageByName("languageName")).resolves.toEqual(returnValue); -}) \ No newline at end of file diff --git a/backend/tests/orm_tests/session_key.test.ts b/backend/tests/orm_tests/session_key.test.ts deleted file mode 100644 index 230c27d8..00000000 --- a/backend/tests/orm_tests/session_key.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -import {prismaMock} from "./singleton"; -import {addSessionKey, changeSessionKey, checkSessionKey, removeAllKeysForUser} from "../../orm_functions/session_key"; - -test("should create a new session key for the user", async () => { - - const response = {session_key_id: 1, login_user_id: 0, session_key: "key"} - - prismaMock.session_keys.create.mockResolvedValue(response); - await expect(addSessionKey(0, "key")).resolves.toEqual(response); - -}); - -test("should return the found record", async () => { - const response = {session_key_id: 1, login_user_id: 0, session_key: "key"} - - prismaMock.session_keys.findUnique.mockResolvedValue(response); - await expect(checkSessionKey("key")).resolves.toEqual(response); -}); - -test("should update to the new sesion key", async () => { - const response = {session_key_id: 1, login_user_id: 0, session_key: "key"} - - prismaMock.session_keys.update.mockResolvedValue(response); - await expect(changeSessionKey("oldkey", "newkey")).resolves.toEqual(response); - -}); - -test("should remove all keys from the user with the given key", async () => { - - // needed for the check session key - const valid = {session_key_id: 1, login_user_id: 0, session_key: "key"} - prismaMock.session_keys.findUnique.mockResolvedValue(valid); - - // for the removal - const count = {count: 2} - prismaMock.session_keys.deleteMany.mockResolvedValue(count); - await expect(removeAllKeysForUser("key")).resolves.toEqual(count); - - -}); \ No newline at end of file diff --git a/backend/tests/orm_tests/singleton.ts b/backend/tests/orm_tests/singleton.ts deleted file mode 100644 index 1f51447b..00000000 --- a/backend/tests/orm_tests/singleton.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { PrismaClient } from '@prisma/client' -import { mockDeep, mockReset, DeepMockProxy } from 'jest-mock-extended' - -/* -* this file creates a mock of the prisma client, this mock is used in all the unit orm_tests -*/ - -import prisma from '../../prisma/prisma' - -jest.mock('../../prisma/prisma', () => ({ - __esModule: true, - default: mockDeep(), -})); - -beforeEach(() => { - mockReset(prismaMock) -}); - -export const prismaMock = prisma as unknown as DeepMockProxy - diff --git a/backend/tests/orm_tests/applied_role.test.ts b/backend/tests/orm_unit/applied_role.test.ts similarity index 58% rename from backend/tests/orm_tests/applied_role.test.ts rename to backend/tests/orm_unit/applied_role.test.ts index a0f45a4b..1e8850d7 100644 --- a/backend/tests/orm_tests/applied_role.test.ts +++ b/backend/tests/orm_unit/applied_role.test.ts @@ -1,24 +1,29 @@ -import {prismaMock} from "./singleton"; +import { prismaMock } from "./singleton"; import { CreateAppliedRole } from "../../orm_functions/orm_types"; -import {createAppliedRole, getAppliedRolesByJobApplication } from "../../orm_functions/applied_role"; +import { + createAppliedRole, + getAppliedRolesByJobApplication, +} from "../../orm_functions/applied_role"; const returnValue = { applied_role_id: 0, role_id: 0, - job_application_id: 0 -} + job_application_id: 0, +}; test("should create an applied role in the db with the given object, returns the new record", async () => { const appliedRole: CreateAppliedRole = { jobApplicationId: 0, - roleId: 0 + roleId: 0, }; - - prismaMock.applied_role.create.mockResolvedValue(returnValue) - await expect(createAppliedRole(appliedRole)).resolves.toEqual(returnValue); - }); + + prismaMock.applied_role.create.mockResolvedValue(returnValue); + await expect(createAppliedRole(appliedRole)).resolves.toEqual(returnValue); +}); test("should return all the appliedroles given the application in the db", async () => { prismaMock.applied_role.findMany.mockResolvedValue([returnValue]); - await expect(getAppliedRolesByJobApplication(0)).resolves.toEqual([returnValue]); + await expect(getAppliedRolesByJobApplication(0)).resolves.toEqual([ + returnValue, + ]); }); diff --git a/backend/tests/orm_tests/attachment.test.ts b/backend/tests/orm_unit/attachment.test.ts similarity index 67% rename from backend/tests/orm_tests/attachment.test.ts rename to backend/tests/orm_unit/attachment.test.ts index 3284f922..e7fad258 100644 --- a/backend/tests/orm_tests/attachment.test.ts +++ b/backend/tests/orm_unit/attachment.test.ts @@ -1,25 +1,29 @@ -import {prismaMock} from "./singleton"; -import {type_enum} from "@prisma/client"; +import { prismaMock } from "./singleton"; +import { type_enum } from "@prisma/client"; import { createAttachment, deleteAllAttachmentsForApplication, deleteAttachment, - getAttachmentById + getAttachmentById, } from "../../orm_functions/attachment"; test("should create a new attachment", async () => { const attachment = { attachment_id: 1, job_application_id: 1, - data: "www.testurl.com", - type: type_enum.FILE_URL - } + data: ["www.testurl.com"], + type: [type_enum.FILE_URL], + }; prismaMock.attachment.create.mockResolvedValue(attachment); - await expect(createAttachment(attachment.job_application_id, attachment.data, attachment.type)) - .resolves - .toEqual(attachment); + await expect( + createAttachment( + attachment.job_application_id, + attachment.data, + attachment.type + ) + ).resolves.toEqual(attachment); }); test("should delete the attachment with the given id", async () => { @@ -28,9 +32,9 @@ test("should delete the attachment with the given id", async () => { const attachment = { attachment_id: 2, job_application_id: 1, - data: "www.testurl.com", - type: type_enum.FILE_URL - } + data: ["www.testurl.com"], + type: [type_enum.FILE_URL], + }; prismaMock.attachment.delete.mockResolvedValue(attachment); @@ -38,8 +42,7 @@ test("should delete the attachment with the given id", async () => { }); test("should delete all attachments of a job application", async () => { - - const count = { count: 5} + const count = { count: 5 }; prismaMock.attachment.deleteMany.mockResolvedValue(count); await expect(deleteAllAttachmentsForApplication(1)).resolves.toEqual(count); @@ -49,11 +52,11 @@ test("should return the found attachment", async () => { const attachment = { attachment_id: 2, job_application_id: 1, - data: "www.testurl.com", - type: type_enum.FILE_URL - } + data: ["www.testurl.com"], + type: [type_enum.FILE_URL], + }; prismaMock.attachment.findUnique.mockResolvedValue(attachment); await expect(getAttachmentById(0)).resolves.toEqual(attachment); -}); \ No newline at end of file +}); diff --git a/backend/tests/orm_unit/contract.test.ts b/backend/tests/orm_unit/contract.test.ts new file mode 100644 index 00000000..5b39f8b5 --- /dev/null +++ b/backend/tests/orm_unit/contract.test.ts @@ -0,0 +1,204 @@ +import { prismaMock } from "./singleton"; +import { contract_status_enum } from "@prisma/client"; +import { + createContract, + removeContract, + removeContractsFromStudent, + updateContract, + contractsForStudent, + contractsByProject, + sortedContractsByOsocEdition, +} from "../../orm_functions/contract"; + +test("should create a contract", async () => { + const contract = { + studentId: 1, + projectRoleId: 1, + information: "fake information", + loginUserId: 5, + contractStatus: contract_status_enum.WAIT_APPROVAL, + }; + + const returnContract = { + contract_id: 5, + student_id: 1, + project_role_id: 1, + information: "fake information", + created_by_login_user_id: 5, + contract_status: contract_status_enum.WAIT_APPROVAL, + }; + + prismaMock.contract.create.mockResolvedValue(returnContract); + + await expect(createContract(contract)).resolves.toEqual(returnContract); +}); + +test("should update a contract", async () => { + const contract = { + contractId: 1, + information: "fake information", + loginUserId: 5, + contractStatus: contract_status_enum.WAIT_APPROVAL, + }; + + const returnContract = { + contract_id: 1, + student_id: 2, + project_role_id: 1, + information: "fake information", + created_by_login_user_id: 5, + contract_status: contract_status_enum.WAIT_APPROVAL, + }; + + prismaMock.contract.update.mockResolvedValue(returnContract); + + await expect(updateContract(contract)).resolves.toEqual(returnContract); +}); + +test("should remove contracts associated with the student", async () => { + const count = { count: 4 }; + + const studentId = 2; + + prismaMock.contract.deleteMany.mockResolvedValue(count); + + await expect(removeContractsFromStudent(studentId)).resolves.toEqual(count); +}); + +test("should remove the give contract", async () => { + const contractId = 2; + + const returnContract = { + contract_id: 1, + student_id: 2, + project_role_id: 1, + information: "fake information", + created_by_login_user_id: 5, + contract_status: contract_status_enum.WAIT_APPROVAL, + }; + + prismaMock.contract.delete.mockResolvedValue(returnContract); + + await expect(removeContract(contractId)).resolves.toEqual(returnContract); +}); + +test("should list all contracts of a student", async () => { + const returnContract = [ + { + student_id: 1, + project_role_id: 1, + information: "fake information", + created_by_login_user_id: 5, + contract_status: contract_status_enum.WAIT_APPROVAL, + contract_id: 5, + project_role: { + project: { + osoc_id: 1, + }, + project_id: 1, + }, + student: { + student_id: 1, + pronouns: "he", + nickname: "nick", + alumni: true, + gender: "M", + person_id: 1, + phone_number: "0452986104", + }, + }, + ]; + + prismaMock.contract.findMany.mockResolvedValue(returnContract); + + await expect(contractsForStudent(1)).resolves.toEqual(returnContract); +}); + +test("should list all contracts linked to a project", async () => { + const returnContract = [ + { + student_id: 1, + project_role_id: 1, + information: "fake information", + created_by_login_user_id: 5, + contract_status: contract_status_enum.WAIT_APPROVAL, + contract_id: 5, + project_role: { + project: { + osoc_id: 1, + }, + project_id: 1, + }, + student: { + student_id: 1, + pronouns: "he", + nickname: "nick", + alumni: true, + gender: "M", + person_id: 1, + phone_number: "0452986104", + }, + }, + ]; + + prismaMock.contract.findMany.mockResolvedValue(returnContract); + + await expect(contractsByProject(1)).resolves.toEqual(returnContract); +}); + +test("should list all contracts linked to an osoc edition", async () => { + const returnContract = [ + { + student_id: 2, + project_role_id: 2, + information: "fake information", + created_by_login_user_id: 5, + contract_status: contract_status_enum.WAIT_APPROVAL, + contract_id: 4, + project_role: { + project: { + osoc_id: 1, + }, + project_id: 1, + }, + student: { + student_id: 2, + pronouns: "he", + nickname: "nick", + alumni: true, + gender: "M", + person_id: 1, + phone_number: "0452986104", + }, + }, + { + student_id: 1, + project_role_id: 1, + information: "fake information", + created_by_login_user_id: 5, + contract_status: contract_status_enum.WAIT_APPROVAL, + contract_id: 5, + project_role: { + project: { + osoc_id: 1, + }, + project_id: 1, + }, + student: { + student_id: 1, + pronouns: "he", + nickname: "nick", + alumni: true, + gender: "M", + person_id: 1, + phone_number: "0452986104", + }, + }, + ]; + + prismaMock.contract.findMany.mockResolvedValue(returnContract); + + await expect(sortedContractsByOsocEdition(1)).resolves.toEqual( + returnContract + ); +}); diff --git a/backend/tests/orm_tests/evaluation.test.ts b/backend/tests/orm_unit/evaluation.test.ts similarity index 70% rename from backend/tests/orm_tests/evaluation.test.ts rename to backend/tests/orm_unit/evaluation.test.ts index 31773a3c..3aef60d2 100644 --- a/backend/tests/orm_tests/evaluation.test.ts +++ b/backend/tests/orm_unit/evaluation.test.ts @@ -1,13 +1,15 @@ -import {prismaMock} from "./singleton"; -import {decision_enum} from "@prisma/client"; +import { prismaMock } from "./singleton"; +import { decision_enum } from "@prisma/client"; import { checkIfFinalEvaluationExists, createEvaluationForStudent, getLoginUserByEvaluationId, - updateEvaluationForStudent + updateEvaluationForStudent, } from "../../orm_functions/evaluation"; -import {CreateEvaluationForStudent, UpdateEvaluationForStudent} from "../../orm_functions/orm_types"; - +import { + CreateEvaluationForStudent, + UpdateEvaluationForStudent, +} from "../../orm_functions/orm_types"; test("should return a final evaluation if it exists", async () => { const jobApplicationId = 5; @@ -17,21 +19,23 @@ test("should return a final evaluation if it exists", async () => { job_application_id: 0, decision: decision_enum.MAYBE, motivation: null, - is_final: true - } + is_final: true, + }; prismaMock.evaluation.findFirst.mockResolvedValue(result); - await expect(checkIfFinalEvaluationExists(jobApplicationId)).resolves.toEqual(result) + await expect( + checkIfFinalEvaluationExists(jobApplicationId) + ).resolves.toEqual(result); }); test("should create an evaluation for a student", async () => { - const evaluation: CreateEvaluationForStudent = { - decision: decision_enum.MAYBE, - isFinal: false, - jobApplicationId: 0, - loginUserId: 0, - motivation: undefined - } + const evaluation: CreateEvaluationForStudent = { + decision: decision_enum.MAYBE, + isFinal: false, + jobApplicationId: 0, + loginUserId: 0, + motivation: undefined, + }; const response = { evaluation_id: 0, @@ -39,11 +43,13 @@ test("should create an evaluation for a student", async () => { job_application_id: evaluation.jobApplicationId, decision: evaluation.decision, motivation: "test", - is_final: evaluation.isFinal - } + is_final: evaluation.isFinal, + }; - prismaMock.evaluation.create.mockResolvedValue(response); - await expect(createEvaluationForStudent(evaluation)).resolves.toEqual(response); + prismaMock.evaluation.create.mockResolvedValue(response); + await expect(createEvaluationForStudent(evaluation)).resolves.toEqual( + response + ); }); test("should update the final evaluation that already exists", async () => { @@ -52,8 +58,8 @@ test("should update the final evaluation that already exists", async () => { isFinal: true, jobApplicationId: 0, loginUserId: 0, - motivation: undefined - } + motivation: undefined, + }; const response = { evaluation_id: 0, @@ -61,8 +67,8 @@ test("should update the final evaluation that already exists", async () => { job_application_id: evaluationFinal.jobApplicationId, decision: evaluationFinal.decision, motivation: "test", - is_final: evaluationFinal.isFinal - } + is_final: evaluationFinal.isFinal, + }; const existingEvaluation = { evaluation_id: 0, @@ -70,12 +76,14 @@ test("should update the final evaluation that already exists", async () => { job_application_id: 1, decision: decision_enum.NO, motivation: "test", - is_final: true - } + is_final: true, + }; prismaMock.evaluation.findFirst.mockResolvedValue(existingEvaluation); prismaMock.evaluation.create.mockResolvedValue(response); prismaMock.evaluation.update.mockResolvedValue(response); - await expect(createEvaluationForStudent(evaluationFinal)).resolves.toEqual(response) + await expect(createEvaluationForStudent(evaluationFinal)).resolves.toEqual( + response + ); }); test("should update the evaluation", async () => { @@ -83,8 +91,8 @@ test("should update the evaluation", async () => { decision: decision_enum.NO, evaluation_id: 0, loginUserId: 0, - motivation: "undefined" - } + motivation: "undefined", + }; const response = { evaluation_id: 0, @@ -92,24 +100,25 @@ test("should update the evaluation", async () => { job_application_id: 1, decision: decision_enum.NO, motivation: "test", - is_final: true - } + is_final: true, + }; prismaMock.evaluation.update.mockResolvedValue(response); - await expect(updateEvaluationForStudent(evaluation)).resolves.toEqual(response); -}) + await expect(updateEvaluationForStudent(evaluation)).resolves.toEqual( + response + ); +}); test("should return the loginUser with his info that made this evaluation", async () => { - const result = { evaluation_id: 7, login_user_id: 0, job_application_id: 0, decision: decision_enum.MAYBE, motivation: null, - is_final: true - } + is_final: true, + }; prismaMock.evaluation.findUnique.mockResolvedValue(result); await expect(getLoginUserByEvaluationId(7)).resolves.toEqual(result); -}); \ No newline at end of file +}); diff --git a/backend/tests/orm_tests/job_application.test.ts b/backend/tests/orm_unit/job_application.test.ts similarity index 79% rename from backend/tests/orm_tests/job_application.test.ts rename to backend/tests/orm_unit/job_application.test.ts index c545426d..8c5b6c25 100644 --- a/backend/tests/orm_tests/job_application.test.ts +++ b/backend/tests/orm_unit/job_application.test.ts @@ -1,4 +1,4 @@ -import {prismaMock} from "./singleton"; +import { prismaMock } from "./singleton"; import { changeEmailStatusOfJobApplication, createJobApplication, @@ -10,16 +10,17 @@ import { getLatestJobApplicationOfStudent, getStudentEvaluationsFinal, getStudentEvaluationsTemp, - getStudentEvaluationsTotal + getStudentEvaluationsTotal, } from "../../orm_functions/job_application"; -import {email_status_enum} from "@prisma/client"; -import {CreateJobApplication} from "../../orm_functions/orm_types"; +import { email_status_enum } from "@prisma/client"; +import { CreateJobApplication } from "../../orm_functions/orm_types"; const response = { job_application_id: 0, student_id: 0, responsibilities: "", - student_volunteer_info: "Yes, I can work with a student employment agreement in Belgium", + student_volunteer_info: + "Yes, I can work with a student employment agreement in Belgium", motivation: "", fun_fact: "", is_volunteer: false, @@ -27,12 +28,12 @@ const response = { osoc_id: 0, edus: ["test"], edu_level: "slecht", - edu_duration : 5, - edu_year: 2025, + edu_duration: 5, + edu_year: "2025", edu_institute: "ugent", email_status: email_status_enum.FAILED, - created_at: new Date() -} + created_at: new Date(), +}; test("should return all student evaluations", async () => { prismaMock.job_application.findMany.mockResolvedValue([response]); @@ -50,7 +51,7 @@ test("should return all temp evaluations", async () => { }); test("should delete job applications of student", async () => { - const count = { count: 5}; + const count = { count: 5 }; prismaMock.job_application.deleteMany.mockResolvedValue(count); await expect(deleteJobApplicationsFromStudent(0)).resolves.toEqual(count); @@ -58,7 +59,9 @@ test("should delete job applications of student", async () => { test("should change the email status", async () => { prismaMock.job_application.update.mockResolvedValue(response); - await expect(changeEmailStatusOfJobApplication(0, email_status_enum.FAILED)).resolves.toEqual(response); + await expect( + changeEmailStatusOfJobApplication(0, email_status_enum.FAILED) + ).resolves.toEqual(response); }); test("should delete job application", async () => { @@ -67,30 +70,34 @@ test("should delete job application", async () => { }); test("should create a job application", async () => { - const jobApplicaton: CreateJobApplication = { - created_at: "", + createdAt: "", eduDuration: 5, eduInstitute: "ugent", eduLevel: "good", - eduYear: 2025, + eduYear: "2025", edus: ["good"], emailStatus: email_status_enum.DRAFT, funFact: "cool", - studentVolunteerInfo: "Yes, I can work with a student employment agreement in Belgium", + studentVolunteerInfo: + "Yes, I can work with a student employment agreement in Belgium", osocId: 0, responsibilities: undefined, studentCoach: false, - studentId: 0 - } + studentId: 0, + }; prismaMock.job_application.create.mockResolvedValue(response); - await expect(createJobApplication(jobApplicaton)).resolves.toEqual(response); + await expect(createJobApplication(jobApplicaton)).resolves.toEqual( + response + ); }); test("should return latest job application of a student", async () => { prismaMock.job_application.findFirst.mockResolvedValue(response); - await expect(getLatestJobApplicationOfStudent(0)).resolves.toEqual(response); + await expect(getLatestJobApplicationOfStudent(0)).resolves.toEqual( + response + ); }); test("should return a job application", async () => { @@ -105,5 +112,7 @@ test("should return all job applications of the given year", async () => { test("should return the latest roles this student has applied for", async () => { prismaMock.job_application.findFirst.mockResolvedValue(response); - await expect(getLatestApplicationRolesForStudent(0)).resolves.toEqual(response); -}) \ No newline at end of file + await expect(getLatestApplicationRolesForStudent(0)).resolves.toEqual( + response + ); +}); diff --git a/backend/tests/orm_tests/job_application_skill.test.ts b/backend/tests/orm_unit/job_application_skill.test.ts similarity index 55% rename from backend/tests/orm_tests/job_application_skill.test.ts rename to backend/tests/orm_unit/job_application_skill.test.ts index e7f28db9..1369b018 100644 --- a/backend/tests/orm_tests/job_application_skill.test.ts +++ b/backend/tests/orm_unit/job_application_skill.test.ts @@ -1,9 +1,16 @@ -import {prismaMock} from "./singleton"; -import {CreateJobApplicationSkill, UpdateJobApplicationSkill} from "../../orm_functions/orm_types"; +import { prismaMock } from "./singleton"; import { - createJobApplicationSkill, deleteJobApplicationSkill, + CreateJobApplicationSkill, + UpdateJobApplicationSkill, +} from "../../orm_functions/orm_types"; +import { + createJobApplicationSkill, + deleteJobApplicationSkill, + deleteSkillsByJobApplicationId, getAllJobApplicationSkill, - getAllJobApplicationSkillByJobApplication, getJobApplicationSkill, updateJobApplicationSkill + getAllJobApplicationSkillByJobApplication, + getJobApplicationSkill, + updateJobApplicationSkill, } from "../../orm_functions/job_application_skill"; const returnValue = { @@ -13,56 +20,62 @@ const returnValue = { is_preferred: true, language_id: 1, level: 4, - skill: "skill" -} + skill: "skill", +}; -test ("should create a new job application and return the entry in the database", async () => { +test("should create a new job application and return the entry in the database", async () => { const jobApplicationSkill: CreateJobApplicationSkill = { jobApplicationId: 0, isBest: false, isPreferred: true, languageId: 1, level: 4, - skill: "skill" - } + skill: "skill", + }; prismaMock.job_application_skill.create.mockResolvedValue(returnValue); - await expect(createJobApplicationSkill(jobApplicationSkill)).resolves.toEqual(returnValue); + await expect( + createJobApplicationSkill(jobApplicationSkill) + ).resolves.toEqual(returnValue); }); test("should return all job application skills", async () => { - const returnValue = [{ - job_application_skill_id: 0, - job_application_id: 0, - is_best: false, - is_preferred: true, - language_id: 1, - level: 4, - skill: "skill" - }]; + const returnValue = [ + { + job_application_skill_id: 0, + job_application_id: 0, + is_best: false, + is_preferred: true, + language_id: 1, + level: 4, + skill: "skill", + }, + ]; prismaMock.job_application_skill.findMany.mockResolvedValue(returnValue); await expect(getAllJobApplicationSkill()).resolves.toEqual(returnValue); }); test("should return all skills for the given jobApplicationId", async () => { - const returnValue = [{ - job_application_skill_id: 0, - job_application_id: 0, - is_best: false, - is_preferred: true, - language_id: 1, - level: 4, - skill: "skill" - }]; + const returnValue = [ + { + job_application_skill_id: 0, + job_application_id: 0, + is_best: false, + is_preferred: true, + language_id: 1, + level: 4, + skill: "skill", + }, + ]; prismaMock.job_application_skill.findMany.mockResolvedValue(returnValue); - await expect(getAllJobApplicationSkillByJobApplication(0)).resolves.toEqual(returnValue); + await expect(getAllJobApplicationSkillByJobApplication(0)).resolves.toEqual( + returnValue + ); }); test("should return the job application skill with the given id", async () => { - - prismaMock.job_application_skill.findUnique.mockResolvedValue(returnValue); await expect(getJobApplicationSkill(0)).resolves.toEqual(returnValue); }); @@ -75,15 +88,22 @@ test("should update the job application skill and return the updated record", as is_best: false, languageId: 0, level: 4, - skill: "newSkill" + skill: "newSkill", }; prismaMock.job_application_skill.update.mockResolvedValue(returnValue); - await expect(updateJobApplicationSkill(updatedJobSkill)).resolves.toEqual(returnValue); + await expect(updateJobApplicationSkill(updatedJobSkill)).resolves.toEqual( + returnValue + ); }); test("should delete the jobApplication skill with the given ID and return the deleted record", async () => { - prismaMock.job_application_skill.delete.mockResolvedValue(returnValue); await expect(deleteJobApplicationSkill(0)).resolves.toEqual(returnValue); -}); \ No newline at end of file +}); + +test("should delete all jobApplications with given jobApplicationId", async () => { + const returnVal = { count: 3 }; + prismaMock.job_application_skill.deleteMany.mockResolvedValue(returnVal); + await expect(deleteSkillsByJobApplicationId(9)).resolves.toEqual(returnVal); +}); diff --git a/backend/tests/orm_unit/language.test.ts b/backend/tests/orm_unit/language.test.ts new file mode 100644 index 00000000..d02ae213 --- /dev/null +++ b/backend/tests/orm_unit/language.test.ts @@ -0,0 +1,63 @@ +import { prismaMock } from "./singleton"; +import { + createLanguage, + deleteLanguage, + deleteLanguageByName, + getAllLanguages, + getLanguage, + getLanguageByName, + updateLanguage, +} from "../../orm_functions/language"; + +const returnValue = { + language_id: 0, + name: "languageName", +}; + +test("should create a new language record and return it", async () => { + prismaMock.language.create.mockResolvedValue(returnValue); + await expect(createLanguage("languageName")).resolves.toEqual(returnValue); +}); + +test("should return a list of all languages registered", async () => { + const returnValue = [ + { + language_id: 0, + name: "languageName", + }, + ]; + + prismaMock.language.findMany.mockResolvedValue(returnValue); + await expect(getAllLanguages()).resolves.toEqual(returnValue); +}); + +test("should return the language with the given id", async () => { + prismaMock.language.findUnique.mockResolvedValue(returnValue); + await expect(getLanguage(0)).resolves.toEqual(returnValue); +}); + +test("should return the asked language by name", async () => { + prismaMock.language.findUnique.mockResolvedValue(returnValue); + await expect(getLanguageByName("languageName")).resolves.toEqual( + returnValue + ); +}); + +test("should update the given language", async () => { + prismaMock.language.update.mockResolvedValue(returnValue); + await expect( + updateLanguage({ languageId: 0, name: "updated" }) + ).resolves.toEqual(returnValue); +}); + +test("should delete the language with the given id and return the deleted record", async () => { + prismaMock.language.delete.mockResolvedValue(returnValue); + await expect(deleteLanguage(0)).resolves.toEqual(returnValue); +}); + +test("should delete the given language name and its record and return the record", async () => { + prismaMock.language.delete.mockResolvedValue(returnValue); + await expect(deleteLanguageByName("languageName")).resolves.toEqual( + returnValue + ); +}); diff --git a/backend/tests/orm_tests/login_user.test.ts b/backend/tests/orm_unit/login_user.test.ts similarity index 56% rename from backend/tests/orm_tests/login_user.test.ts rename to backend/tests/orm_unit/login_user.test.ts index 259741d1..82f790e8 100644 --- a/backend/tests/orm_tests/login_user.test.ts +++ b/backend/tests/orm_unit/login_user.test.ts @@ -1,14 +1,26 @@ -import {prismaMock} from "./singleton"; -import {account_status_enum} from "@prisma/client"; -import { CreateLoginUser, UpdateLoginUser } from "../../orm_functions/orm_types"; +import { prismaMock } from "./singleton"; +import { account_status_enum } from "@prisma/client"; import { - createLoginUser, getAllLoginUsers, getPasswordLoginUserByPerson, - getPasswordLoginUser, searchLoginUserByPerson, searchAllAdminLoginUsers, - searchAllCoachLoginUsers, searchAllAdminAndCoachLoginUsers, updateLoginUser, - deleteLoginUserById, deleteLoginUserByPersonId, getLoginUserById -} - from "../../orm_functions/login_user"; - + CreateLoginUser, + UpdateLoginUser, +} from "../../orm_functions/orm_types"; +import { + createLoginUser, + getAllLoginUsers, + getPasswordLoginUserByPerson, + getPasswordLoginUser, + searchLoginUserByPerson, + searchAllAdminLoginUsers, + searchAllCoachLoginUsers, + searchAllAdminAndCoachLoginUsers, + updateLoginUser, + deleteLoginUserById, + deleteLoginUserByPersonId, + getLoginUserById, + setCoach, + setAdmin, + filterLoginUsers, +} from "../../orm_functions/login_user"; const response = { session_id: "50", @@ -18,8 +30,8 @@ const response = { is_admin: false, is_coach: false, session_keys: ["key1", "key2"], - account_status: account_status_enum.DISABLED -} + account_status: account_status_enum.DISABLED, +}; const returnValue = { login_user_id: 0, @@ -28,8 +40,8 @@ const returnValue = { is_admin: true, is_coach: true, account_status: account_status_enum.ACTIVATED, - session_keys: [] -} + session_keys: [], +}; test("should create a login user in the db with the given object, returns the new record", async () => { const loginUser: CreateLoginUser = { @@ -37,14 +49,14 @@ test("should create a login user in the db with the given object, returns the ne password: "Password", isAdmin: true, isCoach: true, - accountStatus: account_status_enum.ACTIVATED + accountStatus: account_status_enum.ACTIVATED, }; - - prismaMock.login_user.create.mockResolvedValue(returnValue) - await expect(createLoginUser(loginUser)).resolves.toEqual(returnValue); - }); - test("should return all login users in the db", async () => { + prismaMock.login_user.create.mockResolvedValue(returnValue); + await expect(createLoginUser(loginUser)).resolves.toEqual(returnValue); +}); + +test("should return all login users in the db", async () => { prismaMock.login_user.findMany.mockResolvedValue([returnValue]); await expect(getAllLoginUsers()).resolves.toEqual([returnValue]); }); @@ -66,26 +78,32 @@ test("should return the searched login user record with the given person id", as test("should return all the login user records that are admin", async () => { prismaMock.login_user.findMany.mockResolvedValue([returnValue]); - await expect(searchAllAdminLoginUsers(true)).resolves.toEqual([returnValue]); + await expect(searchAllAdminLoginUsers(true)).resolves.toEqual([ + returnValue, + ]); }); test("should return all the login user records that are coach", async () => { prismaMock.login_user.findMany.mockResolvedValue([returnValue]); - await expect(searchAllCoachLoginUsers(true)).resolves.toEqual([returnValue]); + await expect(searchAllCoachLoginUsers(true)).resolves.toEqual([ + returnValue, + ]); }); test("should return all the login user records that are coach aswell as admin", async () => { prismaMock.login_user.findMany.mockResolvedValue([returnValue]); - await expect(searchAllAdminAndCoachLoginUsers(true)).resolves.toEqual([returnValue]); + await expect(searchAllAdminAndCoachLoginUsers(true)).resolves.toEqual([ + returnValue, + ]); }); test("should update the login user with the new data and return the updated record", async () => { - const loginUser : UpdateLoginUser = { + const loginUser: UpdateLoginUser = { loginUserId: 0, password: "New_pass", isAdmin: false, isCoach: false, - accountStatus: "ACTIVATED" + accountStatus: "ACTIVATED", }; prismaMock.login_user.update.mockResolvedValue(returnValue); @@ -105,4 +123,48 @@ test("should delete the login user with the given person id and return the delet test("should return the login_user with given id", async () => { prismaMock.login_user.findUnique.mockResolvedValue(response); await expect(getLoginUserById(0)).resolves.toEqual(response); -}) \ No newline at end of file +}); + +test("should return the filtered list of users", async () => { + prismaMock.login_user.findMany.mockResolvedValue([returnValue]); + await expect( + filterLoginUsers( + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined + ) + ).resolves.toEqual([returnValue]); +}); + +test("should reject and throw an error because only sorting on 1 field is allowed", async () => { + try { + await filterLoginUsers( + undefined, + undefined, + "asc", + "desc", + undefined, + undefined, + undefined + ); + // this should never get executed because filterLoginUser should reject + expect(true).toBeFalsy(); + } catch (e) { + // This should get executed! + expect(true).toBeTruthy(); + } +}); + +test("should return the updated login_user", async () => { + prismaMock.login_user.update.mockResolvedValue(response); + await expect(setCoach(0, true)).resolves.toEqual(response); +}); + +test("should return the updated login_user", async () => { + prismaMock.login_user.update.mockResolvedValue(response); + await expect(setAdmin(0, true)).resolves.toEqual(response); +}); diff --git a/backend/tests/orm_tests/osoc.test.ts b/backend/tests/orm_unit/osoc.test.ts similarity index 63% rename from backend/tests/orm_tests/osoc.test.ts rename to backend/tests/orm_unit/osoc.test.ts index b6f0db30..152580c2 100644 --- a/backend/tests/orm_tests/osoc.test.ts +++ b/backend/tests/orm_unit/osoc.test.ts @@ -1,18 +1,21 @@ -import {prismaMock} from "./singleton"; +import { prismaMock } from "./singleton"; import { - createOsoc, deleteOsoc, deleteOsocByYear, + createOsoc, + deleteOsoc, + deleteOsocByYear, getAllOsoc, getOsocAfterYear, getOsocBeforeYear, getOsocByYear, - updateOsoc + updateOsoc, + getLatestOsoc, } from "../../orm_functions/osoc"; -import {UpdateOsoc} from "../../orm_functions/orm_types"; +import { UpdateOsoc } from "../../orm_functions/orm_types"; const returnValue = { osoc_id: 0, - year: 2022 -} + year: 2022, +}; test("should create a new OSOC edition and return it", async () => { prismaMock.osoc.create.mockResolvedValue(returnValue); @@ -20,35 +23,51 @@ test("should create a new OSOC edition and return it", async () => { }); test("should return all osoc editions", async () => { - const returnValue = [{ - osoc_id: 0, - year: 2022 - }]; + const returnValue = [ + { + osoc_id: 0, + year: 2022, + }, + ]; prismaMock.osoc.findMany.mockResolvedValue(returnValue); await expect(getAllOsoc()).resolves.toEqual(returnValue); }); +test("should return the latest osoc editions", async () => { + const returnValue = { + osoc_id: 0, + year: 2022, + }; + + prismaMock.osoc.findFirst.mockResolvedValue(returnValue); + await expect(getLatestOsoc()).resolves.toEqual(returnValue); +}); + test("should return the osoc edition of the given year", async () => { prismaMock.osoc.findUnique.mockResolvedValue(returnValue); await expect(getOsocByYear(2022)).resolves.toEqual(returnValue); }); test("should return every osoc edition BEFORE (exclusive) the given year", async () => { - const returnValue = [{ - osoc_id: 0, - year: 2022 - }]; + const returnValue = [ + { + osoc_id: 0, + year: 2022, + }, + ]; prismaMock.osoc.findMany.mockResolvedValue(returnValue); await expect(getOsocBeforeYear(2023)).resolves.toEqual(returnValue); }); test("should return every osoc edition AFTER (exclusive) the given year", async () => { - const returnValue = [{ - osoc_id: 0, - year: 2022 - }]; + const returnValue = [ + { + osoc_id: 0, + year: 2022, + }, + ]; prismaMock.osoc.findMany.mockResolvedValue(returnValue); await expect(getOsocAfterYear(2020)).resolves.toEqual(returnValue); @@ -57,19 +76,19 @@ test("should return every osoc edition AFTER (exclusive) the given year", async test("should update the osoc edition", async () => { const updatedOsoc: UpdateOsoc = { osocId: 0, - year: 2023 - } + year: 2023, + }; - prismaMock.osoc.update.mockResolvedValue(returnValue) + prismaMock.osoc.update.mockResolvedValue(returnValue); await expect(updateOsoc(updatedOsoc)).resolves.toEqual(returnValue); }); test("should delete the given osoc by id and return the deleted record", async () => { - prismaMock.osoc.delete.mockResolvedValue(returnValue); - await expect(deleteOsoc(0)).resolves.toEqual(returnValue); + prismaMock.osoc.delete.mockResolvedValue(returnValue); + await expect(deleteOsoc(0)).resolves.toEqual(returnValue); }); test("should delete the given osoc by year and return the deleted record", async () => { prismaMock.osoc.delete.mockResolvedValue(returnValue); await expect(deleteOsocByYear(2022)).resolves.toEqual(returnValue); -}); \ No newline at end of file +}); diff --git a/backend/tests/orm_unit/password_reset.test.ts b/backend/tests/orm_unit/password_reset.test.ts new file mode 100644 index 00000000..40476651 --- /dev/null +++ b/backend/tests/orm_unit/password_reset.test.ts @@ -0,0 +1,34 @@ +import { prismaMock } from "./singleton"; +import { + createOrUpdateReset, + deleteResetWithLoginUser, + deleteResetWithResetId, +} from "../../orm_functions/password_reset"; + +const response = { + password_reset_id: 1, + login_user_id: 1, + reset_id: "reset_id", + valid_until: new Date(), +}; + +test("should return the inserted/updated entry", async () => { + prismaMock.password_reset.upsert.mockResolvedValue(response); + await expect( + createOrUpdateReset( + response.login_user_id, + response.reset_id, + response.valid_until + ) + ).resolves.toEqual(response); +}); + +test("should return the deleted entry", async () => { + prismaMock.password_reset.delete.mockResolvedValue(response); + await expect(deleteResetWithLoginUser(1)).resolves.toEqual(response); +}); + +test("should return the deleted entry", async () => { + prismaMock.password_reset.delete.mockResolvedValue(response); + await expect(deleteResetWithResetId("reset_id")).resolves.toEqual(response); +}); diff --git a/backend/tests/orm_tests/person.test.ts b/backend/tests/orm_unit/person.test.ts similarity index 76% rename from backend/tests/orm_tests/person.test.ts rename to backend/tests/orm_unit/person.test.ts index 21c5eace..66216707 100644 --- a/backend/tests/orm_tests/person.test.ts +++ b/backend/tests/orm_unit/person.test.ts @@ -1,11 +1,13 @@ -import {prismaMock} from "./singleton"; -import {CreatePerson, UpdatePerson} from "../../orm_functions/orm_types"; +import { prismaMock } from "./singleton"; +import { CreatePerson, UpdatePerson } from "../../orm_functions/orm_types"; import { - createPerson, deletePersonById, + createPerson, + deletePersonById, getAllPersons, getPasswordPersonByEmail, searchPersonByLogin, - searchPersonByName, updatePerson + searchPersonByName, + updatePerson, } from "../../orm_functions/person"; const returnValue = { @@ -13,17 +15,18 @@ const returnValue = { email: "email@mail.com", firstname: "FirstName", lastname: "LastName", - github: null -} + github: null, + github_id: "666", +}; test("should create a person in the db with the given object, returns the new record", async () => { - const person: CreatePerson = { - email: "email@mail.com", - firstname: "FirstName", - lastname: "LastName" - }; + const person: CreatePerson = { + email: "email@mail.com", + firstname: "FirstName", + lastname: "LastName", + }; - prismaMock.person.create.mockResolvedValue(returnValue) + prismaMock.person.create.mockResolvedValue(returnValue); await expect(createPerson(person)).resolves.toEqual(returnValue); }); @@ -34,7 +37,9 @@ test("should return all people in the db", async () => { test("should return the HASHED password of the given email", async () => { prismaMock.person.findUnique.mockResolvedValue(returnValue); - await expect(getPasswordPersonByEmail("email@mail.com")).resolves.toEqual(returnValue); + await expect(getPasswordPersonByEmail("email@mail.com")).resolves.toEqual( + returnValue + ); }); test("should return the searched person record with the given name", async () => { @@ -44,16 +49,18 @@ test("should return the searched person record with the given name", async () => test("should return all the people with the given login (email or github)", async () => { prismaMock.person.findMany.mockResolvedValue([returnValue]); - await expect(searchPersonByLogin("dvl@github.com")).resolves.toEqual([returnValue]); + await expect(searchPersonByLogin("dvl@github.com")).resolves.toEqual([ + returnValue, + ]); }); test("should update the person with the new data and return the updated record", async () => { - const person : UpdatePerson = { + const person: UpdatePerson = { email: "email@mail.com", firstname: "newFirst", github: null, lastname: "", - personId: 0 + personId: 0, }; prismaMock.person.update.mockResolvedValue(returnValue); @@ -64,4 +71,3 @@ test("should delete the person with the given id and return the deleted record", prismaMock.person.delete.mockResolvedValue(returnValue); await expect(deletePersonById(0)).resolves.toEqual(returnValue); }); - diff --git a/backend/tests/orm_tests/project.test.ts b/backend/tests/orm_unit/project.test.ts similarity index 70% rename from backend/tests/orm_tests/project.test.ts rename to backend/tests/orm_unit/project.test.ts index 97b96783..d6725b7e 100644 --- a/backend/tests/orm_tests/project.test.ts +++ b/backend/tests/orm_unit/project.test.ts @@ -1,12 +1,26 @@ -import {prismaMock} from "./singleton"; +import { prismaMock } from "./singleton"; import { CreateProject, UpdateProject } from "../../orm_functions/orm_types"; -import {createProject, getProjectByName, getAllProjects, - getProjectsByOsocEdition, getProjectsByPartner, getProjectsStartedAfterDate, - getProjectsByStartDate, getProjectsStartedBeforeDate, getProjectsByEndDate, - getProjectsEndedBeforeDate, getProjectsEndedAfterDate, getProjectsByNumberPositions, - getProjectsLessPositions, getProjectsMorePositions, deleteProject, updateProject, - deleteProjectByOsocEdition, deleteProjectByPartner} - from "../../orm_functions/project"; +import { + createProject, + getProjectByName, + getAllProjects, + getProjectsByOsocEdition, + getProjectsByPartner, + getProjectsStartedAfterDate, + getProjectsByStartDate, + getProjectsStartedBeforeDate, + getProjectsByEndDate, + getProjectsEndedBeforeDate, + getProjectsEndedAfterDate, + getProjectsByNumberPositions, + getProjectsLessPositions, + getProjectsMorePositions, + deleteProject, + updateProject, + deleteProjectByOsocEdition, + deleteProjectByPartner, + getProjectById, +} from "../../orm_functions/project"; const returnValue = { project_id: 0, @@ -16,8 +30,8 @@ const returnValue = { start_date: new Date("2022-07-13"), end_date: new Date("2022-08-31"), positions: 10, - description: "" -} + description: "", +}; test("should create a project in the db with the given object, returns the new record", async () => { const project: CreateProject = { @@ -26,11 +40,11 @@ test("should create a project in the db with the given object, returns the new r partner: "Best partner", startDate: new Date("2022-07-13"), endDate: new Date("2022-08-31"), - positions: 10 + positions: 10, }; - - prismaMock.project.create.mockResolvedValue(returnValue) - await expect(createProject(project)).resolves.toEqual(returnValue); + + prismaMock.project.create.mockResolvedValue(returnValue); + await expect(createProject(project)).resolves.toEqual(returnValue); }); test("should return all projects in the db", async () => { @@ -38,9 +52,16 @@ test("should return all projects in the db", async () => { await expect(getAllProjects()).resolves.toEqual([returnValue]); }); +test("should return the project with the given project id", async () => { + prismaMock.project.findUnique.mockResolvedValue(returnValue); + await expect(getProjectById(0)).resolves.toEqual(returnValue); +}); + test("should return all the projects with the given name", async () => { prismaMock.project.findMany.mockResolvedValue([returnValue]); - await expect(getProjectByName("Test project")).resolves.toEqual([returnValue]); + await expect(getProjectByName("Test project")).resolves.toEqual([ + returnValue, + ]); }); test("should return all the projects with the given osoc edition", async () => { @@ -50,42 +71,58 @@ test("should return all the projects with the given osoc edition", async () => { test("should return all the project with the given partner name", async () => { prismaMock.project.findMany.mockResolvedValue([returnValue]); - await expect(getProjectsByPartner("Best partner")).resolves.toEqual([returnValue]); + await expect(getProjectsByPartner("Best partner")).resolves.toEqual([ + returnValue, + ]); }); test("should return all the project with the given start date", async () => { prismaMock.project.findMany.mockResolvedValue([returnValue]); - await expect(getProjectsByStartDate(new Date("2022-07-13"))).resolves.toEqual([returnValue]); + await expect( + getProjectsByStartDate(new Date("2022-07-13")) + ).resolves.toEqual([returnValue]); }); test("should return all the project that started after the given start date", async () => { prismaMock.project.findMany.mockResolvedValue([returnValue]); - await expect(getProjectsStartedAfterDate(new Date("2022-01-01"))).resolves.toEqual([returnValue]); + await expect( + getProjectsStartedAfterDate(new Date("2022-01-01")) + ).resolves.toEqual([returnValue]); }); test("should return all the project that started before the given start date", async () => { prismaMock.project.findMany.mockResolvedValue([returnValue]); - await expect(getProjectsStartedBeforeDate(new Date("2023-01-01"))).resolves.toEqual([returnValue]); + await expect( + getProjectsStartedBeforeDate(new Date("2023-01-01")) + ).resolves.toEqual([returnValue]); }); test("should return all the project with the given end date", async () => { prismaMock.project.findMany.mockResolvedValue([returnValue]); - await expect(getProjectsByEndDate(new Date("2022-08-31"))).resolves.toEqual([returnValue]); + await expect(getProjectsByEndDate(new Date("2022-08-31"))).resolves.toEqual( + [returnValue] + ); }); test("should return all the project that ended after the given end date", async () => { prismaMock.project.findMany.mockResolvedValue([returnValue]); - await expect(getProjectsEndedAfterDate(new Date("2022-01-01"))).resolves.toEqual([returnValue]); + await expect( + getProjectsEndedAfterDate(new Date("2022-01-01")) + ).resolves.toEqual([returnValue]); }); test("should return all the project that ended before the given end date", async () => { prismaMock.project.findMany.mockResolvedValue([returnValue]); - await expect(getProjectsEndedBeforeDate(new Date("2023-01-01"))).resolves.toEqual([returnValue]); + await expect( + getProjectsEndedBeforeDate(new Date("2023-01-01")) + ).resolves.toEqual([returnValue]); }); test("should return all the projects with the given number of positions", async () => { prismaMock.project.findMany.mockResolvedValue([returnValue]); - await expect(getProjectsByNumberPositions(10)).resolves.toEqual([returnValue]); + await expect(getProjectsByNumberPositions(10)).resolves.toEqual([ + returnValue, + ]); }); test("should return all the projects with less positions", async () => { @@ -99,14 +136,14 @@ test("should return all the projects with more positions", async () => { }); test("should update the project with the new data and return the updated record", async () => { - const project : UpdateProject = { + const project: UpdateProject = { projectId: 0, name: "Different project", osocId: 0, partner: "UGent", startDate: new Date("2022-06-05"), endDate: new Date("2022-09-16"), - positions: 7 + positions: 7, }; prismaMock.project.update.mockResolvedValue(returnValue); @@ -119,13 +156,13 @@ test("should delete the project with the given id and return the deleted record" }); test("should delete all the projects with the given osoc id and return number of deleted records", async () => { - const count = {count: 2} + const count = { count: 2 }; prismaMock.project.deleteMany.mockResolvedValue(count); await expect(deleteProjectByOsocEdition(0)).resolves.toEqual(count); }); test("should delete all the projects with the given partner name and return the number of deleted records", async () => { - const count = {count: 2} + const count = { count: 2 }; prismaMock.project.deleteMany.mockResolvedValue(count); await expect(deleteProjectByPartner("UGent")).resolves.toEqual(count); }); diff --git a/backend/tests/orm_tests/project_role.test.ts b/backend/tests/orm_unit/project_role.test.ts similarity index 74% rename from backend/tests/orm_tests/project_role.test.ts rename to backend/tests/orm_unit/project_role.test.ts index 83c7b72f..11d23627 100644 --- a/backend/tests/orm_tests/project_role.test.ts +++ b/backend/tests/orm_unit/project_role.test.ts @@ -1,28 +1,35 @@ -import {prismaMock} from "./singleton"; -import { CreateProjectRole, UpdateProjectRole } from "../../orm_functions/orm_types"; -import {getNumberOfFreePositions, createProjectRole, getProjectRolesByProject, - getNumberOfRolesByProjectAndRole, getProjectRoleNamesByProject, - updateProjectRole, deleteProjectRole} - from "../../orm_functions/project_role"; - +import { prismaMock } from "./singleton"; +import { + CreateProjectRole, + UpdateProjectRole, +} from "../../orm_functions/orm_types"; +import { + getNumberOfFreePositions, + createProjectRole, + getProjectRolesByProject, + getNumberOfRolesByProjectAndRole, + getProjectRoleNamesByProject, + updateProjectRole, + deleteProjectRole, +} from "../../orm_functions/project_role"; const returnValue = { project_role_id: 0, project_id: 0, role_id: 0, - positions: 3 -} + positions: 3, +}; test("should create a project role in the db with the given object, returns the new record", async () => { const projectRole: CreateProjectRole = { projectId: 0, roleId: 0, - positions: 3 + positions: 3, }; - - prismaMock.project_role.create.mockResolvedValue(returnValue) - await expect(createProjectRole(projectRole)).resolves.toEqual(returnValue); - }); + + prismaMock.project_role.create.mockResolvedValue(returnValue); + await expect(createProjectRole(projectRole)).resolves.toEqual(returnValue); +}); test("should return all project roles assigned to given project", async () => { prismaMock.project_role.findMany.mockResolvedValue([returnValue]); @@ -31,20 +38,24 @@ test("should return all project roles assigned to given project", async () => { test("should return the number of roles for the given project and project role", async () => { prismaMock.project_role.findMany.mockResolvedValue([returnValue]); - await expect(getNumberOfRolesByProjectAndRole(0, 0)).resolves.toEqual([returnValue]); + await expect(getNumberOfRolesByProjectAndRole(0, 0)).resolves.toEqual([ + returnValue, + ]); }); test("should return all the names of the project roles for the given project", async () => { prismaMock.project_role.findMany.mockResolvedValue([returnValue]); - await expect(getProjectRoleNamesByProject(0)).resolves.toEqual([returnValue]); + await expect(getProjectRoleNamesByProject(0)).resolves.toEqual([ + returnValue, + ]); }); test("should update the project role with the new data and return the updated record", async () => { - const projectRole : UpdateProjectRole = { + const projectRole: UpdateProjectRole = { projectRoleId: 0, positions: 5, projectId: 0, - roleId: 1 + roleId: 1, }; prismaMock.project_role.update.mockResolvedValue(returnValue); @@ -63,15 +74,15 @@ test("should return the number of free positions", async () => { project_id: 1, role_id: 0, positions: 5, - contract: ['', ''] - } + contract: ["", ""], + }; prismaMock.project_role.findUnique.mockResolvedValue(projectRoleRes); // expected length is 3, because positions == 5 and contract == ["", ""] (array with length 2) - await expect(getNumberOfFreePositions(0)).resolves.toEqual(3) + await expect(getNumberOfFreePositions(0)).resolves.toEqual(3); }); test("should return null because the project_role with given ID was not found", async () => { prismaMock.project_role.findUnique.mockResolvedValue(null); await expect(getNumberOfFreePositions(0)).resolves.toEqual(null); -}); \ No newline at end of file +}); diff --git a/backend/tests/orm_tests/project_user.test.ts b/backend/tests/orm_unit/project_user.test.ts similarity index 51% rename from backend/tests/orm_tests/project_user.test.ts rename to backend/tests/orm_unit/project_user.test.ts index c4f10697..9e7d5266 100644 --- a/backend/tests/orm_tests/project_user.test.ts +++ b/backend/tests/orm_unit/project_user.test.ts @@ -1,19 +1,19 @@ -import {prismaMock} from "./singleton"; +import { prismaMock } from "./singleton"; import { CreateProjectUser } from "../../orm_functions/orm_types"; -import {createProjectUser } from "../../orm_functions/project_user"; +import { createProjectUser } from "../../orm_functions/project_user"; const returnValue = { project_user_id: 0, login_user_id: 0, - project_id: 0 -} + project_id: 0, +}; test("should create an project user in the db with the given object, returns the new record", async () => { const projectUser: CreateProjectUser = { loginUserId: 0, - projectId: 0 + projectId: 0, }; - - prismaMock.project_user.create.mockResolvedValue(returnValue) - await expect(createProjectUser(projectUser)).resolves.toEqual(returnValue); - }); + + prismaMock.project_user.create.mockResolvedValue(returnValue); + await expect(createProjectUser(projectUser)).resolves.toEqual(returnValue); +}); diff --git a/backend/tests/orm_tests/role.test.ts b/backend/tests/orm_unit/role.test.ts similarity index 76% rename from backend/tests/orm_tests/role.test.ts rename to backend/tests/orm_unit/role.test.ts index 5663d169..7002cd4a 100644 --- a/backend/tests/orm_tests/role.test.ts +++ b/backend/tests/orm_unit/role.test.ts @@ -1,21 +1,27 @@ -import {prismaMock} from "./singleton"; +import { prismaMock } from "./singleton"; import { UpdateRole } from "../../orm_functions/orm_types"; -import {getProjectRoleWithRoleName, createProjectRole, getAllRoles, - getRole, getRolesByName, updateRole, deleteRole, deleteRoleByName} - from "../../orm_functions/role"; +import { + getProjectRoleWithRoleName, + createRole, + getAllRoles, + getRole, + getRolesByName, + updateRole, + deleteRole, + deleteRoleByName, +} from "../../orm_functions/role"; const returnValue = { role_id: 0, - name: "Developer" -} + name: "Developer", +}; test("should create a role in the db with the given name, returns the new record", async () => { - prismaMock.role.create.mockResolvedValue(returnValue) - await expect(createProjectRole("Developer")).resolves.toEqual(returnValue); - }); - + prismaMock.role.create.mockResolvedValue(returnValue); + await expect(createRole("Developer")).resolves.toEqual(returnValue); +}); - test("should return all roles in the db", async () => { +test("should return all roles in the db", async () => { prismaMock.role.findMany.mockResolvedValue([returnValue]); await expect(getAllRoles()).resolves.toEqual([returnValue]); }); @@ -31,9 +37,9 @@ test("should return the role object for the given name", async () => { }); test("should update the role with the new data and return the updated record", async () => { - const role : UpdateRole = { + const role: UpdateRole = { roleId: 0, - name: "Data scientist" + name: "Data scientist", }; prismaMock.role.update.mockResolvedValue(returnValue); @@ -47,32 +53,36 @@ test("should delete the role with the given id and return the deleted record", a test("should delete the role with the given name and return the deleted record", async () => { prismaMock.role.delete.mockResolvedValue(returnValue); - await expect(deleteRoleByName("Data scientist")).resolves.toEqual(returnValue); + await expect(deleteRoleByName("Data scientist")).resolves.toEqual( + returnValue + ); }); - test("should return the id of the project_role with the given role name", async () => { - const roleRes = { role_id: 0, - name: "name" - } + name: "name", + }; const projectRoleRes = { project_role_id: 0, information: "", project_id: 1, role_id: 0, - positions: 5 - } + positions: 5, + }; // case when the role_id was found for given name prismaMock.role.findUnique.mockResolvedValue(roleRes); prismaMock.project_role.findFirst.mockResolvedValue(projectRoleRes); - await expect(getProjectRoleWithRoleName("name", 1)).resolves.toEqual(projectRoleRes) + await expect(getProjectRoleWithRoleName("name", 1)).resolves.toEqual( + projectRoleRes + ); // case when no role_id found for given name const idNotFound = null; prismaMock.role.findUnique.mockResolvedValue(idNotFound); prismaMock.project_role.findFirst.mockResolvedValue(projectRoleRes); - await expect(getProjectRoleWithRoleName("name", 1)).resolves.toEqual(idNotFound) -}); \ No newline at end of file + await expect(getProjectRoleWithRoleName("name", 1)).resolves.toEqual( + idNotFound + ); +}); diff --git a/backend/tests/orm_unit/session_key.test.ts b/backend/tests/orm_unit/session_key.test.ts new file mode 100644 index 00000000..8e6c1323 --- /dev/null +++ b/backend/tests/orm_unit/session_key.test.ts @@ -0,0 +1,68 @@ +import { prismaMock } from "./singleton"; +import { + addSessionKey, + refreshKey, + checkSessionKey, + removeAllKeysForUser, +} from "../../orm_functions/session_key"; + +const futureDate = new Date(); +futureDate.setDate(futureDate.getDate() + 15); + +test("should create a new session key for the user", async () => { + const response = { + session_key_id: 1, + login_user_id: 0, + session_key: "key", + valid_until: futureDate, + }; + + prismaMock.session_keys.create.mockResolvedValue(response); + await expect(addSessionKey(0, "key", new Date())).resolves.toEqual( + response + ); +}); + +test("should return the found record", async () => { + const response = { + session_key_id: 1, + login_user_id: 0, + session_key: "key", + valid_until: futureDate, + }; + + prismaMock.session_keys.findFirst.mockResolvedValue(response); + await expect(checkSessionKey("key")).resolves.toEqual(response); +}); + +test("should update to the new sesion key", async () => { + const response = { + session_key_id: 1, + login_user_id: 0, + session_key: "key", + valid_until: futureDate, + }; + + prismaMock.session_keys.update.mockResolvedValue(response); + await expect(refreshKey("oldkey", futureDate)).resolves.toEqual(response); +}); + +test("should remove all keys from the user with the given key", async () => { + // needed for the check session key + const futureDate = new Date(); + futureDate.setDate(futureDate.getDate() + 15); + const valid = [ + { + session_key_id: 1, + login_user_id: 0, + session_key: "key", + valid_until: futureDate, + }, + ]; + prismaMock.session_keys.findMany.mockResolvedValue(valid); + + // for the removal + const count = { count: 0 }; + prismaMock.session_keys.deleteMany.mockResolvedValue(count); + await expect(removeAllKeysForUser("key")).resolves.toEqual(count); +}); diff --git a/backend/tests/orm_unit/singleton.ts b/backend/tests/orm_unit/singleton.ts new file mode 100644 index 00000000..0704289b --- /dev/null +++ b/backend/tests/orm_unit/singleton.ts @@ -0,0 +1,19 @@ +import { PrismaClient } from "@prisma/client"; +import { mockDeep, mockReset, DeepMockProxy } from "jest-mock-extended"; + +/* + * this file creates a mock of the prisma client, this mock is used in all the unit orm_tests + */ + +import prisma from "../../prisma/prisma"; + +jest.mock("../../prisma/prisma", () => ({ + __esModule: true, + default: mockDeep(), +})); + +beforeEach(() => { + mockReset(prismaMock); +}); + +export const prismaMock = prisma as unknown as DeepMockProxy; diff --git a/backend/tests/orm_tests/student.test.ts b/backend/tests/orm_unit/student.test.ts similarity index 86% rename from backend/tests/orm_tests/student.test.ts rename to backend/tests/orm_unit/student.test.ts index 84b6bbb1..9832aace 100644 --- a/backend/tests/orm_tests/student.test.ts +++ b/backend/tests/orm_unit/student.test.ts @@ -1,34 +1,33 @@ -import {prismaMock} from "./singleton"; -import {CreateStudent, UpdateStudent} from "../../orm_functions/orm_types"; +import { prismaMock } from "./singleton"; +import { CreateStudent, UpdateStudent } from "../../orm_functions/orm_types"; import { createStudent, deleteStudent, getAllStudents, getStudent, searchStudentByGender, - updateStudent + updateStudent, } from "../../orm_functions/student"; const response = { student_id: 0, alumni: false, nickname: "Mozz", - gender: 'Male', + gender: "Male", person_id: 0, phone_number: "0118 999 881 999 119 725 3", - pronouns: ["fire"] -} + pronouns: "fire", +}; test("should create a student", async () => { - const student: CreateStudent = { alumni: false, nickname: "Mozz", personId: 0, - gender: 'Male', + gender: "Male", phoneNumber: "0118 999 881 999 119 725 3", - pronouns: ["fire"] - } + pronouns: "fire", + }; prismaMock.student.create.mockResolvedValue(response); await expect(createStudent(student)).resolves.toEqual(response); @@ -50,8 +49,8 @@ test("should update the student", async () => { alumni: false, nickname: "Mozz", phoneNumber: "0118 999 881 999 119 725 3", - pronouns: ["fire"] - } + pronouns: "fire", + }; prismaMock.student.update.mockResolvedValue(response); await expect(updateStudent(student)).resolves.toEqual(response); @@ -65,4 +64,4 @@ test("should delete the student", async () => { test("should return all the people with the selected gender", async () => { prismaMock.student.findMany.mockResolvedValue([response]); await expect(searchStudentByGender("female")).resolves.toEqual([response]); -}); \ No newline at end of file +}); diff --git a/backend/tests/request.test.ts b/backend/tests/request.test.ts index 0f6d3308..788c52d2 100644 --- a/backend/tests/request.test.ts +++ b/backend/tests/request.test.ts @@ -1,752 +1,838 @@ -import {getMockReq} from '@jest-mock/express'; -import express from 'express'; +import { getMockReq } from "@jest-mock/express"; +import express from "express"; -import * as Rq from '../request' -import * as T from '../types'; -import {errors} from '../utility'; +import * as config from "../config.json"; +import * as Rq from "../request"; +import * as T from "../types"; +import { errors } from "../utility"; + +function setSessionKey(req: express.Request, key: string): void { + req.headers.authorization = config.global.authScheme + " " + key; +} test("Can parse Key-only requests", () => { - const valid: express.Request = getMockReq(); - const invalid: express.Request = getMockReq(); - const wrongprop: express.Request = getMockReq(); - - valid.body.sessionkey = "hello I am a key"; - wrongprop.body.key = "hello I am a key as well"; - - const calls = [ - Rq.parseLogoutRequest, Rq.parseStudentAllRequest, Rq.parseCoachAllRequest, - Rq.parseGetAllCoachRequestsRequest, Rq.parseAdminAllRequest, - Rq.parseProjectAllRequest, Rq.parseConflictAllRequest, - Rq.parseFollowupAllRequest, Rq.parseTemplateListRequest - ]; - - const successes = - calls.map(call => expect(call(valid)).resolves.toStrictEqual({ - sessionkey : "hello I am a key" - })); - - const failures = calls.flatMap(call => [call(invalid), call(wrongprop)]) - .map(sub => expect(sub).rejects.toStrictEqual( - errors.cookUnauthenticated())); - - return Promise.all([ successes, failures ].flat()); + const valid: express.Request = getMockReq(); + const invalid: express.Request = getMockReq(); + const wrongprop: express.Request = getMockReq(); + + // valid.body.sessionkey = "hello I am a key"; + // valid.headers.authorization = config.global.authScheme + " hello I am a + // key"; + setSessionKey(valid, "hello I am a key"); + wrongprop.body.key = "hello I am a key as well"; + + const calls = [ + Rq.parseLogoutRequest, + Rq.parseStudentAllRequest, + Rq.parseCoachAllRequest, + Rq.parseGetAllCoachRequestsRequest, + Rq.parseAdminAllRequest, + Rq.parseProjectAllRequest, + Rq.parseConflictAllRequest, + Rq.parseFollowupAllRequest, + Rq.parseTemplateListRequest, + Rq.parseProjectConflictsRequest, + ]; + + const successes = calls.map((call) => + expect(call(valid)).resolves.toStrictEqual({ + sessionkey: "hello I am a key", + }) + ); + + const failures = calls + .flatMap((call) => [call(invalid), call(wrongprop)]) + .map((sub) => + expect(sub).rejects.toStrictEqual(errors.cookUnauthenticated()) + ); + + return Promise.all([successes, failures].flat()); }); test("Can parse Key-ID requests", () => { - const res: - T.Requests.IdRequest = {sessionkey : "Hello I am a key", id : 20123}; - const valid: express.Request = getMockReq(); - const neither: express.Request = getMockReq(); - const onlyKey: express.Request = getMockReq(); - const onlyid: express.Request = getMockReq(); - - valid.body.sessionkey = res.sessionkey; - valid.params.id = res.id.toString(); - onlyKey.body.sessionkey = res.sessionkey; - onlyid.params.id = res.id.toString(); - - const calls = [ - Rq.parseSingleStudentRequest, Rq.parseDeleteStudentRequest, - Rq.parseStudentGetSuggestsRequest, Rq.parseSingleCoachRequest, - Rq.parseDeleteCoachRequest, Rq.parseGetCoachRequestRequest, - Rq.parseAcceptNewCoachRequest, Rq.parseDenyNewCoachRequest, - Rq.parseSingleAdminRequest, Rq.parseDeleteAdminRequest, - Rq.parseSingleProjectRequest, Rq.parseDeleteProjectRequest, - Rq.parseGetDraftedStudentsRequest, Rq.parseGetFollowupStudentRequest, - Rq.parseGetTemplateRequest, Rq.parseDeleteTemplateRequest - ]; - - const successes = - calls.map(call => expect(call(valid)).resolves.toStrictEqual(res)); - - const failures = calls.flatMap(call => [call(neither), call(onlyid)]) - .map(sub => expect(sub).rejects.toStrictEqual( - errors.cookUnauthenticated())); - - const otherfails = calls.map(call => call(onlyKey)) - .map(sub => expect(sub).rejects.toStrictEqual( - errors.cookArgumentError())); - - return Promise.all([ successes, failures, otherfails ].flat()); -}) + const res: T.Requests.IdRequest = { + sessionkey: "Hello I am a key", + id: 20123, + }; + const valid: express.Request = getMockReq(); + const neither: express.Request = getMockReq(); + const onlyKey: express.Request = getMockReq(); + const onlyid: express.Request = getMockReq(); + + // valid.body.sessionkey = res.sessionkey; + setSessionKey(valid, res.sessionkey); + valid.params.id = res.id.toString(); + // onlyKey.body.sessionkey = res.sessionkey; + setSessionKey(onlyKey, res.sessionkey); + onlyid.params.id = res.id.toString(); + + const calls = [ + Rq.parseSingleStudentRequest, + Rq.parseDeleteStudentRequest, + Rq.parseSingleCoachRequest, + Rq.parseDeleteCoachRequest, + Rq.parseGetCoachRequestRequest, + Rq.parseDenyNewCoachRequest, + Rq.parseSingleAdminRequest, + Rq.parseDeleteAdminRequest, + Rq.parseSingleProjectRequest, + Rq.parseDeleteProjectRequest, + Rq.parseGetDraftedStudentsRequest, + Rq.parseGetFollowupStudentRequest, + Rq.parseGetTemplateRequest, + Rq.parseDeleteTemplateRequest, + ]; + + const successes = calls.map((call) => + expect(call(valid)).resolves.toStrictEqual(res) + ); + + const failures = calls + .flatMap((call) => [call(neither), call(onlyid)]) + .map((sub) => + expect(sub).rejects.toStrictEqual(errors.cookUnauthenticated()) + ); + + const otherfails = calls + .map((call) => call(onlyKey)) + .map((sub) => + expect(sub).rejects.toStrictEqual(errors.cookArgumentError()) + ); + + return Promise.all([successes, failures, otherfails].flat()); +}); test("Can parse update login user requests", () => { - const id = 1234; - const valid1: T.Anything = { - isAdmin : false, - isCoach : false, - accountStatus : 'PENDING', - sessionkey : 'abc' - }; - const valid2: T.Anything = { - isAdmin : false, - isCoach : false, - accountStatus : 'PENDING', - pass : 'mypass', - sessionkey : 'abc' - }; - - const invalid1: T.Anything = { - isCoach : false, - accountStatus : 'PENDING', - sessionkey : 'abc' - }; - const invalid2: T.Anything = { - isCoach : false, - accountStatus : 'PENDING', - pass : 'mypass', - sessionkey : 'abc' - }; - const invalid_sk: T.Anything = { - isAdmin : false, - isCoach : false, - accountStatus : 'PENDING', - pass : 'mypass' - }; - - const s = [ valid1, valid2 ].map(x => { - const r: express.Request = getMockReq(); - r.body = {...x}; - r.params.id = id.toString(); - x.id = id; - if (!("pass" in x)) - x.pass = undefined; - return expect(Rq.parseUpdateCoachRequest(r)).resolves.toStrictEqual(x); - }); - - const f = [ invalid1, invalid2 ].map(x => { - const r: express.Request = getMockReq(); - r.body = {...x}; - r.params.id = id.toString(); - x.id = id; - return expect(Rq.parseUpdateCoachRequest(r)) - .rejects.toBe(errors.cookArgumentError()) - }); - - const s_ = [ valid1, valid2 ].map(x => { - const r: express.Request = getMockReq(); - r.body = {...x}; - if (!("pass" in x)) - x.pass = undefined; - r.params.id = id.toString(); - x.id = id; - return expect(Rq.parseUpdateAdminRequest(r)).resolves.toStrictEqual(x); - }); - - const f_ = [ invalid1, invalid2 ].map(x => { - const r: express.Request = getMockReq(); - r.body = {...x}; - return expect(Rq.parseUpdateAdminRequest(r)) - .rejects.toBe(errors.cookArgumentError()) - }); - - const f__ = - [ Rq.parseUpdateAdminRequest, Rq.parseUpdateCoachRequest ].map(f => { + const id = 1234; + const key = "abc"; + const valid1: T.Anything = { + isAdmin: false, + isCoach: false, + accountStatus: "PENDING", + }; + + const invalid1: T.Anything = { isCoach: false, accountStatus: "PENDING" }; + const invalid2: T.Anything = { + isCoach: false, + accountStatus: "PENDING", + }; + const invalid_sk: T.Anything = { + isAdmin: false, + isCoach: false, + accountStatus: "PENDING", + }; + + const s = [valid1].map((x) => { const r: express.Request = getMockReq(); - r.body = {...invalid_sk}; + r.body = { ...x }; r.params.id = id.toString(); - return expect(f(r)).rejects.toBe(errors.cookUnauthenticated()) - }) + setSessionKey(r, key); + x.id = id; + x.sessionkey = key; + return expect(Rq.parseUpdateCoachRequest(r)).resolves.toStrictEqual(x); + }); + + const s_ = [valid1].map((x) => { + const r: express.Request = getMockReq(); + r.body = { ...x }; + setSessionKey(r, key); + r.params.id = id.toString(); + x.id = id; + x.sessionkey = key; + return expect(Rq.parseUpdateAdminRequest(r)).resolves.toStrictEqual(x); + }); + + const f_ = [invalid1, invalid2].map((x) => { + const r: express.Request = getMockReq(); + r.body = { ...x }; + setSessionKey(r, key); + return expect(Rq.parseUpdateAdminRequest(r)).rejects.toBe( + errors.cookArgumentError() + ); + }); + + const f__ = [Rq.parseUpdateAdminRequest, Rq.parseUpdateCoachRequest].map( + (f) => { + const r: express.Request = getMockReq(); + r.body = { ...invalid_sk }; + r.params.id = id.toString(); + return expect(f(r)).rejects.toBe(errors.cookUnauthenticated()); + } + ); - return Promise.all([ s, f, s_, f_, f__ ].flat()); + return Promise.all([s, s_, f_, f__].flat()); }); test("Can parse login request", () => { - const valid: express.Request = getMockReq(); - const noname: express.Request = getMockReq(); - const nopass: express.Request = getMockReq(); - - valid.body.name = "Name #1"; - valid.body.pass = "Pass #1"; - noname.body.pass = "Pass #2"; - nopass.body.name = "Name #2"; - - return Promise.all([ - expect(Rq.parseLoginRequest(valid)) - .resolves.toStrictEqual({name : "Name #1", pass : "Pass #1"}), - expect(Rq.parseLoginRequest(noname)) - .rejects.toBe(errors.cookArgumentError()), - expect(Rq.parseLoginRequest(nopass)) - .rejects.toBe(errors.cookArgumentError()) - ]); + const valid: express.Request = getMockReq(); + const noname: express.Request = getMockReq(); + const nopass: express.Request = getMockReq(); + + valid.body.name = "Alice.STUDENT@hotmail.be"; + valid.body.pass = "Pass #1"; + noname.body.pass = "Pass #2"; + nopass.body.name = "Name.2@email.be"; + + // TODO + return Promise.all([ + expect(Rq.parseLoginRequest(valid)).resolves.toStrictEqual({ + name: "alice.student@hotmail.be", + pass: "Pass #1", + }), + expect(Rq.parseLoginRequest(noname)).rejects.toBe( + errors.cookArgumentError() + ), + expect(Rq.parseLoginRequest(nopass)).rejects.toBe( + errors.cookArgumentError() + ), + ]); }); test("Can parse update student request", () => { - const dataV: T.Anything = { - sessionkey : "abcdef", - emailOrGithub : "ab@c.de", - alumni : false, - firstName : "ab", - lastName : "c", - gender : "Apache Attack Helicopter", - pronouns : "vroom/vroom", - phone : "+32420 696969", - nickname : "jefke", - education : { - level : 7, - duration : 9, - year : "something?!?!?", - institute : "KULeuven" - } - }; - - const failure1: T.Anything = { - sessionkey : "abcdef", - emailOrGithub : "ab@c.de", - firstName : "ab", // no last name - gender : "Apache Attack Helicopter", - pronouns : "vroom/vroom", - phone : "+32420 696969", - education : { - level : 7, - duration : 9, - year : "something?!?!?", - institute : "KULeuven" - } - }; - - const failure2: T.Anything = { - sessionkey : "abcdef", - emailOrGithub : "ab@c.de", - firstName : "ab", - lastName : "c", - gender : "Apache Attack Helicopter", - pronouns : "vroom/vroom", - phone : "+32420 696969", - education : { - level : 7, // missing duration - year : "something?!?!?", - institute : "KULeuven" - } - }; - - const failure3: T.Anything = {sessionkey : "abcdef"}; - - const id = 60011223369; - - const valid: express.Request = getMockReq(); - const ival1: express.Request = getMockReq(); - const ival2: express.Request = getMockReq(); - const ival3: express.Request = getMockReq(); - - valid.body = {...dataV}; - valid.params.id = id.toString(); - ival1.body = {...failure1}; - ival1.params.id = id.toString(); - ival2.body = {...failure2}; - ival2.params.id = id.toString(); - ival3.body = {...failure3}; - ival3.params.id = id.toString(); - - dataV.id = id; - failure1.id = id; - failure1.lastName = undefined; - failure1.alumni = undefined; - failure1.nickname = undefined; - failure2.id = id; - (failure2.education as T.Anything).duration = undefined; - failure2.alumni = undefined; - failure2.nickname = undefined; - - const error = errors.cookArgumentError(); - - return Promise.all([ - expect(Rq.parseUpdateStudentRequest(valid)).resolves.toStrictEqual(dataV), - expect(Rq.parseUpdateStudentRequest(ival1)) - .resolves.toStrictEqual(failure1), - expect(Rq.parseUpdateStudentRequest(ival2)) - .resolves.toStrictEqual(failure2), - expect(Rq.parseUpdateStudentRequest(ival3)).rejects.toStrictEqual(error) - ]); + const sessionkey = "abcdef"; + const dataV: T.Anything = { + emailOrGithub: "ab@c.de", + alumni: false, + firstName: "ab c", + lastName: "", + gender: "Apache Attack Helicopter", + pronouns: "vroom/vroom", + phone: "+32420 696969", + nickname: "jefke", + education: { + level: 7, + duration: 9, + year: "something?!?!?", + institute: "KULeuven", + }, + }; + + const failure1: T.Anything = { + emailOrGithub: "ab@c.de", + firstName: "ab", // no last name + gender: "Apache Attack Helicopter", + pronouns: "vroom/vroom", + phone: "+32420 696969", + education: { + level: 7, + duration: 9, + year: "something?!?!?", + institute: "KULeuven", + }, + }; + + const failure2: T.Anything = { + emailOrGithub: "ab@c.de", + firstName: "ab c", + lastName: "", + gender: "Apache Attack Helicopter", + pronouns: "vroom/vroom", + phone: "+32420 696969", + education: { + level: 7, // missing duration + year: "something?!?!?", + institute: "KULeuven", + }, + }; + + const failure3: T.Anything = {}; + + const id = 60011223369; + + const valid: express.Request = getMockReq(); + const ival1: express.Request = getMockReq(); + const ival2: express.Request = getMockReq(); + const ival3: express.Request = getMockReq(); + + valid.body = { ...dataV }; + valid.params.id = id.toString(); + setSessionKey(valid, sessionkey); + ival1.body = { ...failure1 }; + ival1.params.id = id.toString(); + setSessionKey(ival1, sessionkey); + ival2.body = { ...failure2 }; + ival2.params.id = id.toString(); + setSessionKey(ival2, sessionkey); + ival3.body = { ...failure3 }; + ival3.params.id = id.toString(); + setSessionKey(ival3, sessionkey); + + dataV.id = id; + dataV.sessionkey = sessionkey; + failure1.id = id; + failure1.lastName = undefined; + failure1.alumni = undefined; + failure1.nickname = undefined; + failure1.sessionkey = sessionkey; + failure2.id = id; + (failure2.education as T.Anything).duration = undefined; + failure2.alumni = undefined; + failure2.nickname = undefined; + failure2.sessionkey = sessionkey; + + const error = errors.cookArgumentError(); + + return Promise.all([ + expect(Rq.parseUpdateStudentRequest(valid)).resolves.toStrictEqual( + dataV + ), + expect(Rq.parseUpdateStudentRequest(ival1)).resolves.toStrictEqual( + failure1 + ), + expect(Rq.parseUpdateStudentRequest(ival2)).resolves.toStrictEqual( + failure2 + ), + expect(Rq.parseUpdateStudentRequest(ival3)).rejects.toStrictEqual( + error + ), + ]); }); test("Can parse suggest student request", () => { - const key = "my-session-key"; - const id = 9845; - const ys: T.Anything = {suggestion : "YES", sessionkey : key, senderId : 0}; - const mb: T.Anything = {suggestion : "MAYBE", sessionkey : key, senderId : 0}; - const no: T.Anything = {suggestion : "NO", sessionkey : key, senderId : 0}; - const nr: T.Anything = { - suggestion : "NO", - reason : "I just don't like you", - sessionkey : key, - senderId : 0 - }; - const i1: - T.Anything = {suggestion : "TOMORROW", sessionkey : key, senderId : 0}; - const i2: T.Anything = { - suggestion : "no", - sessionkey : key, - senderId : 0 - }; // no caps - const i3: T.Anything = {sessionkey : key, senderId : 0}; - - const okays = [ ys, mb, no, nr ].map(x => { - const copy: T.Anything = {...x}; - copy.id = id; - const req: express.Request = getMockReq(); - req.params.id = id.toString(); - req.body = x; - ["reason"].forEach(x => { - if (!(x in req.body)) { - copy[x] = undefined - } + const key = "my-session-key"; + const id = 9845; + const ys: T.Anything = { suggestion: "YES" }; + const mb: T.Anything = { suggestion: "MAYBE" }; + const no: T.Anything = { suggestion: "NO" }; + const nr: T.Anything = { + suggestion: "NO", + reason: "I just don't like you", + }; + const i1: T.Anything = { suggestion: "TOMORROW" }; + const i2: T.Anything = { suggestion: "no" }; // no caps + + const okays = [ys, mb, no, nr].map((x) => { + const copy: T.Anything = { ...x }; + copy.id = id; + const req: express.Request = getMockReq(); + req.params.id = id.toString(); + req.body = x; + setSessionKey(req, key); + ["reason"].forEach((x) => { + if (!(x in req.body)) { + copy[x] = undefined; + } + }); + copy.sessionkey = key; + return expect( + Rq.parseSuggestStudentRequest(req) + ).resolves.toStrictEqual(copy); }); - return expect(Rq.parseSuggestStudentRequest(req)) - .resolves.toStrictEqual(copy); - }); - - const fails = [ i1, i2, i3 ].map(x => { - const req: express.Request = getMockReq(); - req.params.id = id.toString(); - req.body = {...x}; - return expect(Rq.parseSuggestStudentRequest(req)) - .rejects.toBe(errors.cookArgumentError()); - }); - - return Promise.all([ okays, fails ].flat()); + + const fails = [i1, i2].map((x) => { + const req: express.Request = getMockReq(); + req.params.id = id.toString(); + req.body = { ...x }; + setSessionKey(req, key); + return expect(Rq.parseSuggestStudentRequest(req)).rejects.toBe( + errors.cookArgumentError() + ); + }); + + return Promise.all([okays, fails].flat()); }); +// TODO test Rq.parseAcceptNewUserRequest +// TODO test Rq.parseGetSuggestionsStudentRequest + test("Can parse final decision request", () => { - const key = "key"; - const id = 6969420420; - const data: T.Anything = {sessionkey : key}; - const dat2: T.Anything = {reply : "YES", sessionkey : key}; - const dat3: T.Anything = {reply : "NO", sessionkey : key}; - const dat4: T.Anything = {reply : "MAYBE", sessionkey : key}; - const dat5: T.Anything = {reply : "something", sessionkey : key}; - const dat6: T.Anything = {reply : "maybe", sessionkey : key}; - const dat7: T.Anything = {reply : "YES"}; - - const p = [ data, dat2, dat3, dat4 ].map(x => { - const r: express.Request = getMockReq(); - r.body = {...x}; - r.params.id = id.toString(); - x.id = id; - if (!("reply" in x)) - x.reply = undefined; - - return expect(Rq.parseFinalizeDecisionRequest(r)).resolves.toStrictEqual(x); - }); - - const q = [ dat5, dat6 ].map(x => { - const r: express.Request = getMockReq(); - r.body = {...x}; - r.params.id = id.toString(); - x.id = id; - return expect(Rq.parseFinalizeDecisionRequest(r)) - .rejects.toBe(errors.cookArgumentError()); - }); - - const r = [ dat7 ].map(x => { - const r: express.Request = getMockReq(); - r.body = {...x}; - r.params.id = id.toString(); - x.id = id; - return expect(Rq.parseFinalizeDecisionRequest(r)) - .rejects.toBe(errors.cookUnauthenticated()); - }); - - return Promise.all([ p, q, r ].flat()); + const key = "key"; + const id = 6969420420; + const data: T.Anything = {}; + const dat2: T.Anything = { reply: "YES" }; + const dat3: T.Anything = { reply: "NO" }; + const dat4: T.Anything = { reply: "MAYBE" }; + const dat5: T.Anything = { reply: "something" }; + const dat6: T.Anything = { reply: "maybe" }; + const dat7: T.Anything = { reply: "YES" }; + + const p = [data, dat2, dat3, dat4].map((x) => { + const r: express.Request = getMockReq(); + r.body = { ...x }; + r.params.id = id.toString(); + setSessionKey(r, key); + x.id = id; + x.sessionkey = key; + if (!("reply" in x)) x.reply = undefined; + if (!("reason" in x)) x.reason = undefined; + + return expect( + Rq.parseFinalizeDecisionRequest(r) + ).resolves.toStrictEqual(x); + }); + + const q = [dat5, dat6].map((x) => { + const r: express.Request = getMockReq(); + r.body = { ...x }; + r.params.id = id.toString(); + setSessionKey(r, key); + x.id = id; + return expect(Rq.parseFinalizeDecisionRequest(r)).rejects.toBe( + errors.cookArgumentError() + ); + }); + + const r = [dat7].map((x) => { + const r: express.Request = getMockReq(); + r.body = { ...x }; + r.params.id = id.toString(); + x.id = id; + return expect(Rq.parseFinalizeDecisionRequest(r)).rejects.toBe( + errors.cookUnauthenticated() + ); + }); + + return Promise.all([p, q, r].flat()); }); test("Can parse coach access request", () => { - const r1: T.Anything = { - firstName : "Jeff", - lastName : "Georgette", - emailOrGithub : "idonthavegithub@git.hub", - pass : "thisismypassword" - }; - - const r2: T.Anything = { - firstName : "Jeff", - lastName : "Georgette", - emailOrGithub : "idonthavegithub@git.hub" - }; - - const req1: express.Request = getMockReq(); - req1.body = {...r1}; - - const req2: express.Request = getMockReq(); - req2.body = {...r2}; - r2.pass = undefined; - - const req3: express.Request = getMockReq(); - req3.body = {}; - - const prom1: Promise = - expect(Rq.parseRequestCoachRequest(req1)).resolves.toStrictEqual(r1); - const prom2: Promise = - expect(Rq.parseRequestCoachRequest(req2)).resolves.toStrictEqual(r2); - const prom3: Promise = expect(Rq.parseRequestCoachRequest(req3)) - .rejects.toBe(errors.cookArgumentError()); - - return Promise.all([ prom1, prom2, prom3 ]); + const r1: T.Anything = { + firstName: "Jeff Georgette", + lastName: "", + email: "idonthavegithub@git.hub", + pass: "thisismypassword", + }; + + const r2: T.Anything = { + firstName: "Jeff Georgette", + lastName: "", + email: "idonthavegithub@git.hub", + }; + + const req1: express.Request = getMockReq(); + req1.body = { ...r1 }; + + const req2: express.Request = getMockReq(); + r2.pass = undefined; + req2.body = { ...r2 }; + + const req3: express.Request = getMockReq(); + req3.body = {}; + + console.log(Rq.parseRequestUserRequest(req1)); + console.log(r1); + + const prom1: Promise = expect( + Rq.parseRequestUserRequest(req1) + ).resolves.toStrictEqual(r1); + const prom2: Promise = expect( + Rq.parseRequestUserRequest(req2) + ).resolves.toStrictEqual(r2); + const prom3: Promise = expect( + Rq.parseRequestUserRequest(req3) + ).rejects.toBe(errors.cookArgumentError()); + + return Promise.all([prom1, prom2, prom3]); }); test("Can parse new project request", () => { - const key = "abcdefghijklmnopqrstuvwxyz"; - const d1: T.Anything = { - sessionkey : key, - name : "Experiment One", - partner : "Simic Combine", - start : Date.now(), - end : Date.now(), - positions : 69 - }; - const d2: T.Anything = {sessionkey : key}; - const d3: T.Anything = { - name : "Experiment One", - partner : "Simic Combine", - start : Date.now(), - end : Date.now(), - positions : 420 - }; - - const req1: express.Request = getMockReq(); - const req2: express.Request = getMockReq(); - const req3: express.Request = getMockReq(); - - req1.body = {...d1}; - req2.body = {...d2}; - req3.body = {...d3}; - - const p1: Promise = - expect(Rq.parseNewProjectRequest(req1)).resolves.toStrictEqual(d1); - const p2: Promise = expect(Rq.parseNewProjectRequest(req2)) - .rejects.toBe(errors.cookArgumentError()); - const p3: Promise = expect(Rq.parseNewProjectRequest(req3)) - .rejects.toBe(errors.cookUnauthenticated()); - - return Promise.all([ p1, p2, p3 ]); + const key = "abcdefghijklmnopqrstuvwxyz"; + const d1: T.Anything = { + name: "Experiment One", + partner: "Simic Combine", + start: Date.now(), + end: Date.now(), + positions: 69, + osocId: 17, + }; + const d2: T.Anything = {}; + const d3: T.Anything = { + name: "Experiment One", + partner: "Simic Combine", + start: Date.now(), + end: Date.now(), + positions: 420, + }; + + const req1: express.Request = getMockReq(); + const req2: express.Request = getMockReq(); + const req3: express.Request = getMockReq(); + + req1.body = { ...d1 }; + setSessionKey(req1, key); + req2.body = { ...d2 }; + setSessionKey(req2, key); + req3.body = { ...d3 }; + + d1.sessionkey = key; + + const p1: Promise = expect( + Rq.parseNewProjectRequest(req1) + ).resolves.toStrictEqual(d1); + const p2: Promise = expect( + Rq.parseNewProjectRequest(req2) + ).rejects.toBe(errors.cookArgumentError()); + const p3: Promise = expect( + Rq.parseNewProjectRequest(req3) + ).rejects.toBe(errors.cookUnauthenticated()); + + return Promise.all([p1, p2, p3]); }); test("Can parse update project request", () => { - const key = "abcdefghijklmnopqrstuvwxyz"; - const id = 6845684; - const d1: T.Anything = { - sessionkey : key, - name : "Experiment One", - partner : "Simic Combine", - start : Date.now(), - end : Date.now(), - positions : 69 - }; - const d2: T.Anything = {sessionkey : key}; - const d3: T.Anything = { - sessionkey : key, - name : "Experiment One", - partner : "Simic Combine", - start : Date.now(), - positions : 420 - }; - const d4: T.Anything = { - name : "Experiment One", - partner : "Simic Combine", - start : Date.now(), - end : Date.now(), - positions : 69 - }; - - const req1: express.Request = getMockReq(); - const req2: express.Request = getMockReq(); - const req3: express.Request = getMockReq(); - const req4: express.Request = getMockReq(); - const req5: express.Request = getMockReq(); - - req1.body = {...d1}; - req1.params.id = id.toString(); - req2.body = {...d2}; - req2.params.id = id.toString(); - req3.body = {...d3}; - req3.params.id = id.toString(); - req4.body = {...d4}; - req4.params.id = id.toString(); - req5.body = {...d1}; - - d1.id = id; - d2.id = id; - d3.id = id; - d3.end = undefined; - d4.id = id; - - const p1: Promise = - expect(Rq.parseUpdateProjectRequest(req1)).resolves.toStrictEqual(d1); - const p2: Promise = expect(Rq.parseUpdateProjectRequest(req2)) - .rejects.toBe(errors.cookArgumentError()); - const p3: Promise = - expect(Rq.parseUpdateProjectRequest(req3)).resolves.toStrictEqual(d3); - const p4: Promise = expect(Rq.parseUpdateProjectRequest(req4)) - .rejects.toBe(errors.cookUnauthenticated()); - const p5: Promise = expect(Rq.parseUpdateProjectRequest(req5)) - .rejects.toBe(errors.cookArgumentError()); - - return Promise.all([ p1, p2, p3, p4, p5 ]); + const key = "abcdefghijklmnopqrstuvwxyz"; + const id = 6845684; + const d1: T.Anything = { + name: "Experiment One", + partner: "Simic Combine", + start: Date.now(), + end: Date.now(), + positions: 69, + }; + const d2: T.Anything = {}; + const d3: T.Anything = { + name: "Experiment One", + partner: "Simic Combine", + start: Date.now(), + positions: 420, + }; + const d4: T.Anything = { + name: "Experiment One", + partner: "Simic Combine", + start: Date.now(), + end: Date.now(), + positions: 69, + }; + + const req1: express.Request = getMockReq(); + const req2: express.Request = getMockReq(); + const req3: express.Request = getMockReq(); + const req4: express.Request = getMockReq(); + const req5: express.Request = getMockReq(); + + req1.body = { ...d1 }; + req1.params.id = id.toString(); + setSessionKey(req1, key); + req2.body = { ...d2 }; + req2.params.id = id.toString(); + setSessionKey(req2, key); + req3.body = { ...d3 }; + req3.params.id = id.toString(); + setSessionKey(req3, key); + req4.body = { ...d4 }; + req4.params.id = id.toString(); + req5.body = { ...d1 }; + setSessionKey(req5, key); + + d1.id = id; + d1.sessionkey = key; + d2.id = id; + d3.id = id; + d3.sessionkey = key; + d3.end = undefined; + d4.id = id; + + const p1: Promise = expect( + Rq.parseUpdateProjectRequest(req1) + ).resolves.toStrictEqual(d1); + const p2: Promise = expect( + Rq.parseUpdateProjectRequest(req2) + ).rejects.toBe(errors.cookArgumentError()); + const p3: Promise = expect( + Rq.parseUpdateProjectRequest(req3) + ).resolves.toStrictEqual(d3); + const p4: Promise = expect( + Rq.parseUpdateProjectRequest(req4) + ).rejects.toBe(errors.cookUnauthenticated()); + const p5: Promise = expect( + Rq.parseUpdateProjectRequest(req5) + ).rejects.toBe(errors.cookArgumentError()); + + return Promise.all([p1, p2, p3, p4, p5]); }); test("Can parse draft student request", () => { - const key = "keyyyyy"; - const id = 89846; - - const d1: T.Anything = { - sessionkey : key, - studentId : "im-a-student", - roles : [ "the", "one", "that", "does", "nothing" ] - }; - const d2: T.Anything = {sessionkey : key, studentId : "im-a-student"}; - const d3: T.Anything = { - studentId : "im-a-student", - roles : [ "the", "one", "that", "does", "nothing" ] - }; - - const r1: express.Request = getMockReq(); - const r2: express.Request = getMockReq(); - const r3: express.Request = getMockReq(); - const r4: express.Request = getMockReq(); - - r1.body = {...d1}; - r2.body = {...d2}; - r3.body = {...d3}; - r4.body = {...d1}; - - r1.params.id = id.toString(); - r2.params.id = id.toString(); - r3.params.id = id.toString(); - - d1.id = id; - d2.id = id; - d3.id = id; - - const p1: Promise = - expect(Rq.parseDraftStudentRequest(r1)).resolves.toStrictEqual(d1); - const p2: Promise = expect(Rq.parseDraftStudentRequest(r2)) - .rejects.toBe(errors.cookArgumentError()); - const p3: Promise = expect(Rq.parseDraftStudentRequest(r3)) - .rejects.toBe(errors.cookUnauthenticated()); - const p4: Promise = expect(Rq.parseDraftStudentRequest(r4)) - .rejects.toBe(errors.cookArgumentError()); - - return Promise.all([ p1, p2, p3, p4 ]); + const key = "keyyyyy"; + const id = 89846; + + const d1: T.Anything = { + studentId: "im-a-student", + role: "the useless one", + }; + const d2: T.Anything = { studentId: "im-a-student" }; + const d3: T.Anything = { studentId: "im-a-student", role: "the lazy one" }; + + const r1: express.Request = getMockReq(); + const r2: express.Request = getMockReq(); + const r3: express.Request = getMockReq(); + const r4: express.Request = getMockReq(); + + r1.body = { ...d1 }; + setSessionKey(r1, key); + r2.body = { ...d2 }; + setSessionKey(r2, key); + r3.body = { ...d3 }; + r4.body = { ...d1 }; + setSessionKey(r4, key); + + r1.params.id = id.toString(); + r2.params.id = id.toString(); + r3.params.id = id.toString(); + + d1.id = id; + d1.sessionkey = key; + d2.id = id; + d3.id = id; + + const p1: Promise = expect( + Rq.parseDraftStudentRequest(r1) + ).resolves.toStrictEqual(d1); + const p2: Promise = expect( + Rq.parseDraftStudentRequest(r2) + ).rejects.toBe(errors.cookArgumentError()); + const p3: Promise = expect( + Rq.parseDraftStudentRequest(r3) + ).rejects.toBe(errors.cookUnauthenticated()); + const p4: Promise = expect( + Rq.parseDraftStudentRequest(r4) + ).rejects.toBe(errors.cookArgumentError()); + + return Promise.all([p1, p2, p3, p4]); }); test("Can parse mark as followed up request", () => { - const key = "my-key-arrived-but"; - const id = 78945312; - - const ht: T.Anything = {sessionkey : key, type : "hold-tight"}; - const cf: T.Anything = {sessionkey : key, type : "confirmed"}; - const cd: T.Anything = {sessionkey : key, type : "cancelled"}; - const i1: T.Anything = {sessionkey : key, type : "invalid"}; - const i2: T.Anything = {type : "hold-tight"}; - const i3: T.Anything = {sessionkey : key}; - - const okays = [ ht, cf, cd ].map(x => { - const r: express.Request = getMockReq(); - r.body = {...x}; - r.params.id = id.toString(); - x.id = id; - return expect(Rq.parseSetFollowupStudentRequest(r)) - .resolves.toStrictEqual(x); - }); - - const fails1 = [ i1, i3 ].map(x => { - const r: express.Request = getMockReq(); - r.body = {...x}; - r.params.id = id.toString(); - x.id = id; - return expect(Rq.parseSetFollowupStudentRequest(r)) - .rejects.toBe(errors.cookArgumentError()); - }); - - const fails2 = [ ht ].map(x => { - const r: express.Request = getMockReq(); - r.body = {...x}; - return expect(Rq.parseSetFollowupStudentRequest(r)) - .rejects.toBe(errors.cookArgumentError()); - }); - - const fails3 = [ i2 ].map(x => { - const r: express.Request = getMockReq(); - r.body = {...x}; - r.params.id = id.toString(); - x.id = id; - return expect(Rq.parseSetFollowupStudentRequest(r)) - .rejects.toBe(errors.cookUnauthenticated()); - }); - - return Promise.all([ okays, fails1, fails2, fails3 ].flat()); + const key = "my-key-arrived-but"; + const id = 78945312; + + const sc: T.Anything = { type: "SCHEDULED" }; + const st: T.Anything = { type: "SENT" }; + const fl: T.Anything = { type: "FAILED" }; + const no: T.Anything = { type: "NONE" }; + const dr: T.Anything = { type: "DRAFT" }; + const i1: T.Anything = { type: "invalid" }; + const i3: T.Anything = {}; + + const okays = [sc, st, fl, no, dr].map((x) => { + const r: express.Request = getMockReq(); + r.body = { ...x }; + setSessionKey(r, key); + r.params.id = id.toString(); + x.id = id; + x.sessionkey = key; + return expect( + Rq.parseSetFollowupStudentRequest(r) + ).resolves.toStrictEqual(x); + }); + + const fails1 = [i1, i3].map((x) => { + const r: express.Request = getMockReq(); + r.body = { ...x }; + setSessionKey(r, key); + r.params.id = id.toString(); + x.id = id; + return expect(Rq.parseSetFollowupStudentRequest(r)).rejects.toBe( + errors.cookArgumentError() + ); + }); + + const fails2 = [fl].map((x) => { + const r: express.Request = getMockReq(); + r.body = { ...x }; + setSessionKey(r, key); + return expect(Rq.parseSetFollowupStudentRequest(r)).rejects.toBe( + errors.cookArgumentError() + ); + }); + + const fails3 = [no].map((x) => { + const r: express.Request = getMockReq(); + r.body = { ...x }; + r.params.id = id.toString(); + x.id = id; + return expect(Rq.parseSetFollowupStudentRequest(r)).rejects.toBe( + errors.cookUnauthenticated() + ); + }); + + return Promise.all([okays, fails1, fails2, fails3].flat()); }); test("Can parse new template request", () => { - const key = "yet-another-session-key"; - - const ok1: T.Anything = { - name : "my-template", - content : "hello-there", - sessionkey : key - }; - const ok2: T.Anything = { - name : "my-template", - content : "hello-there", - sessionkey : key, - desc : "a description did you know that orcas have culture?", - }; - const ok3: T.Anything = { - name : "my-template", - content : "hello-there", - sessionkey : key, - cc : "cc@gmail.com" - }; - const ok4: T.Anything = { - name : "my-template", - content : "hello-there", - sessionkey : key, - desc : "a description did you know that orcas have culture?", - cc : "cc@gmail.com" - }; - const ok5: T.Anything = { - name : "my-template", - content : "hello-there", - subject : "I like C++", - sessionkey : key, - desc : "a description did you know that orcas have culture?", - cc : "cc@gmail.com" - }; - - const f1: T.Anything = { - content : "hello-there", - sessionkey : key, - desc : "a description did you know that orcas have culture?", - cc : "cc@gmail.com" - }; - const f2: T.Anything = { - name : "my-template", - sessionkey : key, - desc : "a description did you know that orcas have culture?", - cc : "cc@gmail.com" - }; - const f3: T.Anything = { - name : "my-template", - content : "hello-there", - desc : "a description did you know that orcas have culture?", - cc : "cc@gmail.com" - }; - - const okays = [ ok1, ok2, ok3, ok4, ok5 ].map(x => { - const r: express.Request = getMockReq(); - r.body = {...x}; - ["desc", "cc", "subject"].forEach(v => { - if (!(v in x)) - x[v] = undefined; + const key = "yet-another-session-key"; + + const ok1: T.Anything = { name: "my-template", content: "hello-there" }; + const ok2: T.Anything = { name: "my-template", content: "hello-there" }; + const ok3: T.Anything = { + name: "my-template", + content: "hello-there", + cc: "cc@gmail.com", + }; + const ok4: T.Anything = { + name: "my-template", + content: "hello-there", + cc: "cc@gmail.com", + }; + const ok5: T.Anything = { + name: "my-template", + content: "hello-there", + subject: "I like C++", + cc: "cc@gmail.com", + }; + + const f1: T.Anything = { content: "hello-there", cc: "cc@gmail.com" }; + const f2: T.Anything = { name: "my-template", cc: "cc@gmail.com" }; + const f3: T.Anything = { + name: "my-template", + content: "hello-there", + cc: "cc@gmail.com", + }; + + const okays = [ok1, ok2, ok3, ok4, ok5].map((x) => { + const r: express.Request = getMockReq(); + r.body = { ...x }; + setSessionKey(r, key); + ["cc", "subject"].forEach((v) => { + if (!(v in x)) x[v] = undefined; + }); + x.sessionkey = key; + + return expect(Rq.parseNewTemplateRequest(r)).resolves.toStrictEqual(x); }); - return expect(Rq.parseNewTemplateRequest(r)).resolves.toStrictEqual(x); - }); - - const fails1 = [ f1, f2 ].map(x => { - const r: express.Request = getMockReq(); - r.body = {...x}; - return expect(Rq.parseNewTemplateRequest(r)) - .rejects.toBe(errors.cookArgumentError()); - }); + const fails1 = [f1, f2].map((x) => { + const r: express.Request = getMockReq(); + r.body = { ...x }; + setSessionKey(r, key); + return expect(Rq.parseNewTemplateRequest(r)).rejects.toBe( + errors.cookArgumentError() + ); + }); - const fails2 = [ f3 ].map(x => { - const r: express.Request = getMockReq(); - r.body = {...x}; - return expect(Rq.parseNewTemplateRequest(r)) - .rejects.toBe(errors.cookUnauthenticated()); - }); + const fails2 = [f3].map((x) => { + const r: express.Request = getMockReq(); + r.body = { ...x }; + return expect(Rq.parseNewTemplateRequest(r)).rejects.toBe( + errors.cookUnauthenticated() + ); + }); - return Promise.all([ okays, fails1, fails2 ].flat()); + return Promise.all([okays, fails1, fails2].flat()); }); test("Can parse update template request", () => { - const key = "yet-another-session-key"; - const id = 987465327465; - - const ok1: T.Anything = { - name : "my-template", - content : "hello-there", - sessionkey : key - }; - const ok2: T.Anything = { - name : "my-template", - content : "hello-there", - sessionkey : key, - desc : "a description did you know that orcas have culture?" - }; - const ok3: T.Anything = { - name : "my-template", - content : "hello-there", - sessionkey : key, - cc : "cc@gmail.com" - }; - const ok4: T.Anything = { - name : "my-template", - content : "hello-there", - sessionkey : key, - desc : "a description did you know that orcas have culture?", - cc : "cc@gmail.com" - }; - const ok5: T.Anything = { - content : "hello-there", - sessionkey : key, - desc : "a description did you know that orcas have culture?", - cc : "cc@gmail.com" - }; - const ok6: T.Anything = { - name : "my-template", - sessionkey : key, - desc : "a description did you know that orcas have culture?", - cc : "cc@gmail.com" - }; - const ok7: T.Anything = { - name : "my-template", - content : "hello-there", - subject : "I like C++", - sessionkey : key, - desc : "a description did you know that orcas have culture?", - cc : "cc@gmail.com" - }; - - const f1: T.Anything = {sessionkey : key}; - const f2: T.Anything = {name : "my-template", content : "hello-there"}; - - const okays = [ ok1, ok2, ok3, ok4, ok5, ok6, ok7 ].map(x => { - const r: express.Request = getMockReq(); - r.body = {...x}; - r.params.id = id.toString(); - x.id = id; - ["name", "content", "subject", "desc", "cc"].forEach(v => { - if (!(v in x)) - x[v] = undefined; + const key = "yet-another-session-key"; + const id = 987465327465; + + const ok1: T.Anything = { name: "my-template", content: "hello-there" }; + const ok2: T.Anything = { + name: "my-template", + content: "hello-there", + }; + const ok3: T.Anything = { + name: "my-template", + content: "hello-there", + cc: "cc@gmail.com", + }; + const ok4: T.Anything = { + name: "my-template", + content: "hello-there", + cc: "cc@gmail.com", + }; + const ok5: T.Anything = { + content: "hello-there", + cc: "cc@gmail.com", + }; + const ok6: T.Anything = { + name: "my-template", + cc: "cc@gmail.com", + }; + const ok7: T.Anything = { + name: "my-template", + content: "hello-there", + subject: "I like C++", + cc: "cc@gmail.com", + }; + + const f1: T.Anything = {}; + const f2: T.Anything = { name: "my-template", content: "hello-there" }; + + const okays = [ok1, ok2, ok3, ok4, ok5, ok6, ok7].map((x) => { + const r: express.Request = getMockReq(); + r.body = { ...x }; + r.params.id = id.toString(); + setSessionKey(r, key); + x.id = id; + x.sessionkey = key; + ["name", "content", "subject", "cc"].forEach((v) => { + if (!(v in x)) x[v] = undefined; + }); + + return expect(Rq.parseUpdateTemplateRequest(r)).resolves.toStrictEqual( + x + ); + }); + + const fails1 = [f1].map((x) => { + const r: express.Request = getMockReq(); + r.body = { ...x }; + r.params.id = id.toString(); + setSessionKey(r, key); + return expect(Rq.parseUpdateTemplateRequest(r)).rejects.toBe( + errors.cookArgumentError() + ); + }); + + const fails2 = [ok1].map((x) => { + const r: express.Request = getMockReq(); + r.body = { ...x }; + setSessionKey(r, key); + return expect(Rq.parseUpdateTemplateRequest(r)).rejects.toBe( + errors.cookArgumentError() + ); + }); + + const fails3 = [f2].map((x) => { + const r: express.Request = getMockReq(); + r.body = { ...x }; + r.params.id = id.toString(); + return expect(Rq.parseUpdateTemplateRequest(r)).rejects.toBe( + errors.cookUnauthenticated() + ); + }); + + return Promise.all([okays, fails1, fails2, fails3].flat()); +}); + +test("Can parse self-modify requests", () => { + const key = "Im bobs secret key, dont tell anyone"; + const v1: T.Anything = {}; + const v2: T.Anything = { name: "BOBv2" }; + const v3: T.Anything = { pass: { oldpass: "PASSv1", newpass: "PASSv2" } }; + + const i1: T.Anything = { name: "BOBv2", pass: "Nothing" }; + const i2: T.Anything = { name: "BOBv2", pass: { oldpass: "PASSv1" } }; + const i3: T.Anything = { name: "BOBv2", pass: { newpass: "PASSv2" } }; + + const valids = [v1, v2, v3].map((r) => { + const req: express.Request = getMockReq(); + req.body = { ...r }; + setSessionKey(req, key); + r.sessionkey = key; + ["name", "pass"].forEach((x) => { + if (!(x in r)) r[x] = undefined; + }); + + expect(Rq.parseUserModSelfRequest(req)).resolves.toStrictEqual(r); + }); + + const invalids = [i1, i2, i3].map((r) => { + const req: express.Request = getMockReq(); + req.body = { ...r }; + setSessionKey(req, key); + + expect(Rq.parseUserModSelfRequest(req)).rejects.toBe( + errors.cookArgumentError() + ); + }); + + const unauths = [v1, v2, v3].map((r) => { + const req: express.Request = getMockReq(); + req.body = { ...r }; + + expect(Rq.parseUserModSelfRequest(req)).rejects.toBe( + errors.cookUnauthenticated() + ); }); - return expect(Rq.parseUpdateTemplateRequest(r)).resolves.toStrictEqual(x); - }); - - const fails1 = [ f1 ].map(x => { - const r: express.Request = getMockReq(); - r.body = {...x}; - r.params.id = id.toString(); - return expect(Rq.parseUpdateTemplateRequest(r)) - .rejects.toBe(errors.cookArgumentError()); - }); - - const fails2 = [ ok1 ].map(x => { - const r: express.Request = getMockReq(); - r.body = {...x}; - return expect(Rq.parseUpdateTemplateRequest(r)) - .rejects.toBe(errors.cookArgumentError()); - }); - - const fails3 = [ f2 ].map(x => { - const r: express.Request = getMockReq(); - r.body = {...x}; - r.params.id = id.toString(); - return expect(Rq.parseUpdateTemplateRequest(r)) - .rejects.toBe(errors.cookUnauthenticated()); - }); - - return Promise.all([ okays, fails1, fails2, fails3 ].flat()); + return Promise.all([valids, invalids, unauths].flat()); }); diff --git a/backend/tests/routes_unit/admin.test.ts b/backend/tests/routes_unit/admin.test.ts new file mode 100644 index 00000000..f088fc56 --- /dev/null +++ b/backend/tests/routes_unit/admin.test.ts @@ -0,0 +1,236 @@ +// only test successes because +// 1: checkSessionKey/isAdmin has been tested +// 2: orm functions are fully tested +// 3: respOrError has been tested +// 4: all parsers have been tested +// -> failures use those stacks and aren't usually caught in the routes +// -> if those stacks work, all failures work as well (because Promises). + +import { getMockReq } from "@jest-mock/express"; +import { account_status_enum, login_user, person } from "@prisma/client"; + +// setup mock for request +import * as req from "../../request"; +jest.mock("../../request"); +const reqMock = req as jest.Mocked; + +// setup mock for utility +import * as util from "../../utility"; +jest.mock("../../utility"); +const utilMock = util as jest.Mocked; + +// setup mocks for orm +import * as ormL from "../../orm_functions/login_user"; +jest.mock("../../orm_functions/login_user"); +const ormLMock = ormL as jest.Mocked; +import * as ormP from "../../orm_functions/person"; +jest.mock("../../orm_functions/person"); +const ormPMock = ormP as jest.Mocked; + +import * as admin from "../../routes/admin"; + +const people: (login_user & { person: person })[] = [ + { + person: { + person_id: 1, + firstname: "Jeffrey", + lastname: "Jan", + email: "jeffrey@jan.be", + github: null, + github_id: null, + }, + is_coach: false, + is_admin: true, + login_user_id: 7, + person_id: 1, + password: "jeffreyForEver", + account_status: "PENDING", + }, + { + person: { + person_id: 2, + firstname: "Jan", + lastname: "Jeffrey", + email: null, + github: "@jan_jeffrey", + github_id: "9846516845", + }, + is_coach: false, + is_admin: true, + login_user_id: 8, + person_id: 2, + password: null, + account_status: "ACTIVATED", + }, +]; + +// setup +beforeEach(() => { + // mocks for request + reqMock.parseAdminAllRequest.mockResolvedValue({ sessionkey: "abcd" }); + reqMock.parseSingleAdminRequest.mockResolvedValue({ + sessionkey: "abcd", + id: 0, + }); + reqMock.parseUpdateAdminRequest.mockImplementation((req) => + Promise.resolve(req.body) + ); + reqMock.parseDeleteAdminRequest.mockResolvedValue({ + sessionkey: "abcd", + id: 1, + }); + + // mocks for utility + utilMock.checkSessionKey.mockImplementation((v) => + Promise.resolve({ + userId: 0, + data: v, + accountStatus: "ACTIVATED", + is_admin: true, + is_coach: true, + }) + ); + utilMock.isAdmin.mockImplementation((v) => + Promise.resolve({ + userId: 0, + data: v, + accountStatus: "ACTIVATED", + is_admin: true, + is_coach: true, + }) + ); + utilMock.mutable.mockImplementation((v) => Promise.resolve(v)); + + // mocks for orm + ormLMock.searchAllAdminLoginUsers.mockResolvedValue(people); + ormLMock.updateLoginUser.mockImplementation((v) => { + if (v.loginUserId != 7 && v.loginUserId != 8) return Promise.reject({}); + return Promise.resolve(v.loginUserId == 7 ? people[0] : people[1]); + }); + ormLMock.deleteLoginUserByPersonId.mockImplementation((v) => { + if (v != 1 && v != 2) return Promise.reject(); + return Promise.resolve(v == 1 ? people[0] : people[1]); + }); + ormPMock.deletePersonById.mockImplementation((v) => { + if (v != 1 && v != 2) return Promise.reject(); + return Promise.resolve(v == 1 ? people[0].person : people[1].person); + }); + ormLMock.searchLoginUserByPerson.mockResolvedValue({ + login_user_id: 1, + person_id: 1, + password: "test", + is_admin: true, + is_coach: true, + account_status: account_status_enum.ACTIVATED, + person: { + person_id: 1, + email: "test@email.com", + github: null, + firstname: "test", + lastname: "test", + github_id: null, + }, + }); +}); + +// reset +afterEach(() => { + reqMock.parseAdminAllRequest.mockReset(); + reqMock.parseSingleAdminRequest.mockReset(); + reqMock.parseUpdateAdminRequest.mockReset(); + reqMock.parseDeleteAdminRequest.mockReset(); + + utilMock.checkSessionKey.mockReset(); + utilMock.isAdmin.mockReset(); + utilMock.mutable.mockReset(); + + ormLMock.searchAllAdminLoginUsers.mockReset(); + ormLMock.deleteLoginUserByPersonId.mockReset(); + ormLMock.updateLoginUser.mockReset(); + ormPMock.deletePersonById.mockReset(); +}); + +function expectCall(func: T, val: U) { + expect(func).toHaveBeenCalledTimes(1); + expect(func).toHaveBeenCalledWith(val); +} + +function expectNoCall(func: T) { + expect(func).toHaveBeenCalledTimes(0); +} + +test("Can list all admins.", async () => { + const req = getMockReq(); + + const res = people.map((val) => ({ + person_data: { + id: val.person.person_id, + name: val.person.firstname + " " + val.person.lastname, + email: val.person.email, + github: val.person.github, + }, + coach: val.is_coach, + admin: val.is_admin, + activated: val.account_status as string, + })); + + await expect(admin.listAdmins(req)).resolves.toStrictEqual({ + data: res, + }); + expect(utilMock.checkSessionKey).toHaveBeenCalledTimes(0); + expectCall(reqMock.parseAdminAllRequest, req); + expectCall(ormL.searchAllAdminLoginUsers, true); + expect(utilMock.isAdmin).toHaveBeenCalledTimes(1); +}); + +test("Can modify a single admin (1).", async () => { + const req = getMockReq(); + req.body = { id: 7, sessionkey: "abcd" }; + const res = { id: 7, name: "Jeffrey Jan" }; + await expect(admin.modAdmin(req)).resolves.toStrictEqual(res); + expectCall(utilMock.isAdmin, { id: 7, sessionkey: "abcd" }); + expectCall(reqMock.parseUpdateAdminRequest, req); + expectCall(ormLMock.updateLoginUser, { + loginUserId: 7, + isAdmin: undefined, + isCoach: undefined, + accountStatus: undefined, + }); + expectNoCall(util.checkSessionKey); + expect(utilMock.mutable).toHaveBeenCalledTimes(1); +}); + +test("Can modify a single admin (2).", async () => { + const req = getMockReq(); + req.body = { + id: 7, + isAdmin: true, + isCoach: false, + sessionkey: "abcd", + }; + const res = { id: 7, name: "Jeffrey Jan" }; + await expect(admin.modAdmin(req)).resolves.toStrictEqual(res); + expectCall(utilMock.isAdmin, req.body); + expectCall(reqMock.parseUpdateAdminRequest, req); + expectCall(ormLMock.updateLoginUser, { + loginUserId: 7, + isAdmin: true, + isCoach: false, + accountStatus: undefined, + }); + expectNoCall(util.checkSessionKey); + expect(utilMock.mutable).toHaveBeenCalledTimes(1); +}); + +test("Can delete admins", async () => { + const req = getMockReq(); + req.body = { id: 1, sessionkey: "abcd" }; + const res = {}; + + await expect(admin.deleteAdmin(req)).resolves.toStrictEqual(res); + expectCall(utilMock.isAdmin, req.body); + expectCall(reqMock.parseDeleteAdminRequest, req); + expectCall(ormLMock.deleteLoginUserByPersonId, req.body.id); + expectCall(ormPMock.deletePersonById, req.body.id); + expect(utilMock.mutable).toHaveBeenCalledTimes(1); +}); diff --git a/backend/tests/routes_unit/coach.test.ts b/backend/tests/routes_unit/coach.test.ts new file mode 100644 index 00000000..897804af --- /dev/null +++ b/backend/tests/routes_unit/coach.test.ts @@ -0,0 +1,250 @@ +// only test successes because +// 1: checkSessionKey/isAdmin has been tested +// 2: orm functions are fully tested +// 3: respOrError has been tested +// 4: all parsers have been tested +// -> failures use those stacks and aren't usually caught in the routes +// -> if those stacks work, all failures work as well (because Promises). + +import { getMockReq } from "@jest-mock/express"; +import { account_status_enum, login_user, person } from "@prisma/client"; + +// setup mock for request +import * as req from "../../request"; +jest.mock("../../request"); +const reqMock = req as jest.Mocked; + +// setup mock for utility +import * as util from "../../utility"; +jest.mock("../../utility"); +const utilMock = util as jest.Mocked; + +// setup mocks for orm +import * as ormL from "../../orm_functions/login_user"; +jest.mock("../../orm_functions/login_user"); +const ormLMock = ormL as jest.Mocked; +import * as ormP from "../../orm_functions/person"; +jest.mock("../../orm_functions/person"); +const ormPMock = ormP as jest.Mocked; + +import * as coach from "../../routes/coach"; + +const people: (login_user & { person: person })[] = [ + { + person: { + person_id: 1, + firstname: "Jeffrey", + lastname: "Jan", + email: "jeffrey@jan.be", + github: null, + github_id: null, + }, + is_coach: true, + is_admin: true, + login_user_id: 7, + person_id: 1, + password: "jeffreyForEver", + account_status: "PENDING", + }, + { + person: { + person_id: 2, + firstname: "Jan", + lastname: "Jeffrey", + email: null, + github: "@jan_jeffrey", + github_id: "16968456845", + }, + is_coach: true, + is_admin: false, + login_user_id: 8, + person_id: 2, + password: null, + account_status: "ACTIVATED", + }, +]; + +// setup +beforeEach(() => { + // mocks for request + reqMock.parseCoachAllRequest.mockResolvedValue({ sessionkey: "abcd" }); + reqMock.parseSingleCoachRequest.mockResolvedValue({ + sessionkey: "abcd", + id: 0, + }); + reqMock.parseUpdateCoachRequest.mockImplementation((req) => + Promise.resolve(req.body) + ); + reqMock.parseGetCoachRequestRequest.mockResolvedValue({ + sessionkey: "abcd", + id: 17, + }); + reqMock.parseDeleteCoachRequest.mockResolvedValue({ + sessionkey: "abcd", + id: 1, + }); + reqMock.parseGetAllCoachRequestsRequest.mockResolvedValue({ + sessionkey: "abcd", + }); + ormLMock.searchLoginUserByPerson.mockResolvedValue({ + login_user_id: 1, + person_id: 1, + password: "test", + is_admin: true, + is_coach: true, + account_status: account_status_enum.ACTIVATED, + person: { + person_id: 1, + email: "test@email.com", + github: null, + firstname: "test", + lastname: "test", + github_id: null, + }, + }); + + // mocks for utility + utilMock.checkSessionKey.mockImplementation((v) => + Promise.resolve({ + userId: 0, + data: v, + accountStatus: "ACTIVATED", + is_admin: true, + is_coach: true, + }) + ); + utilMock.isAdmin.mockImplementation((v) => + Promise.resolve({ + userId: 0, + data: v, + accountStatus: "ACTIVATED", + is_admin: true, + is_coach: true, + }) + ); + utilMock.mutable.mockImplementation((v) => Promise.resolve(v)); + + // mocks for orm + ormLMock.getAllLoginUsers.mockResolvedValue(people); + ormLMock.searchAllCoachLoginUsers.mockResolvedValue(people); + ormLMock.updateLoginUser.mockImplementation((v) => { + if (v.loginUserId != 7 && v.loginUserId != 8) return Promise.reject({}); + return Promise.resolve(v.loginUserId == 7 ? people[0] : people[1]); + }); + ormLMock.deleteLoginUserByPersonId.mockImplementation((v) => { + if (v != 1 && v != 2) { + console.log("invalid id (delete login user) " + v); + return Promise.reject(); + } + return Promise.resolve(v == 1 ? people[0] : people[1]); + }); + + ormPMock.deletePersonById.mockImplementation((v) => { + if (v != 1 && v != 2) { + console.log("invalid id (delete person) " + v); + return Promise.reject(); + } + return Promise.resolve(v == 1 ? people[0].person : people[1].person); + }); +}); + +// reset +afterEach(() => { + reqMock.parseCoachAllRequest.mockReset(); + reqMock.parseSingleCoachRequest.mockReset(); + reqMock.parseUpdateCoachRequest.mockReset(); + reqMock.parseDeleteCoachRequest.mockReset(); + reqMock.parseGetAllCoachRequestsRequest.mockReset(); + reqMock.parseGetCoachRequestRequest.mockReset(); + + utilMock.checkSessionKey.mockReset(); + utilMock.isAdmin.mockReset(); + utilMock.mutable.mockReset(); + + ormLMock.getAllLoginUsers.mockReset(); + ormLMock.searchAllCoachLoginUsers.mockReset(); + ormLMock.deleteLoginUserByPersonId.mockReset(); + ormLMock.updateLoginUser.mockReset(); + ormPMock.deletePersonById.mockReset(); +}); + +function expectCall(func: T, val: U) { + expect(func).toHaveBeenCalledTimes(1); + expect(func).toHaveBeenCalledWith(val); +} + +test("Can list all coaches", async () => { + const req = getMockReq(); + const res = people.map((val) => ({ + person_data: { + id: val.person.person_id, + name: val.person.firstname, + email: val.person.email, + github: val.person.github, + }, + coach: val.is_coach, + admin: val.is_admin, + activated: val.account_status as string, + })); + + await expect(coach.listCoaches(req)).resolves.toStrictEqual({ + data: res, + }); + expect(utilMock.checkSessionKey).toHaveBeenCalledTimes(0); + expectCall(reqMock.parseCoachAllRequest, req); + expectCall(ormL.searchAllCoachLoginUsers, true); + expect(utilMock.isAdmin).toHaveBeenCalledTimes(1); +}); + +test("Can modify a single coach (1).", async () => { + const req = getMockReq(); + req.body = { id: 7, sessionkey: "abcd" }; + const res = { id: 7, name: "Jeffrey Jan" }; + await expect(coach.modCoach(req)).resolves.toStrictEqual(res); + expect(utilMock.checkSessionKey).toHaveBeenCalledTimes(0); + expectCall(reqMock.parseUpdateCoachRequest, req); + expectCall(ormLMock.updateLoginUser, { + loginUserId: 7, + isAdmin: undefined, + isCoach: undefined, + accountStatus: undefined, + }); + expect(utilMock.isAdmin).toHaveBeenCalledTimes(1); + expect(utilMock.mutable).toHaveBeenCalledTimes(1); +}); + +test("Can modify a single coach (2).", async () => { + const req = getMockReq(); + req.body = { + id: 7, + isAdmin: true, + isCoach: false, + sessionkey: "abcd", + }; + const res = { id: 7, name: "Jeffrey Jan" }; + await expect(coach.modCoach(req)).resolves.toStrictEqual(res); + expect(utilMock.checkSessionKey).toHaveBeenCalledTimes(0); + expectCall(reqMock.parseUpdateCoachRequest, req); + expectCall(ormLMock.updateLoginUser, { + loginUserId: 7, + isAdmin: true, + isCoach: false, + accountStatus: undefined, + }); + expect(utilMock.isAdmin).toHaveBeenCalledTimes(1); + expect(utilMock.mutable).toHaveBeenCalledTimes(1); +}); + +test("Can delete coaches", async () => { + const req = getMockReq(); + req.body = { id: 1, sessionkey: "abcd" }; + const res = {}; + + console.log(req.body); + await expect(coach.deleteCoach(req)).resolves.toStrictEqual(res); + expectCall(utilMock.isAdmin, req.body); + expectCall(reqMock.parseDeleteCoachRequest, req); + expectCall(ormLMock.deleteLoginUserByPersonId, req.body.id); + expectCall(ormPMock.deletePersonById, req.body.id); + expect(utilMock.mutable).toHaveBeenCalledTimes(1); +}); diff --git a/backend/tests/routes_unit/followup.test.ts b/backend/tests/routes_unit/followup.test.ts new file mode 100644 index 00000000..40951ed1 --- /dev/null +++ b/backend/tests/routes_unit/followup.test.ts @@ -0,0 +1,237 @@ +// only test successes because +// 1: checkSessionKey/isAdmin has been tested +// 2: orm functions are fully tested +// 3: respOrError has been tested +// 4: all parsers have been tested +// -> failures use those stacks and aren't usually caught in the routes +// -> if those stacks work, all failures work as well (because Promises). + +import express from "express"; +import { getMockReq } from "@jest-mock/express"; +import { + osoc, + job_application, + attachment, + job_application_skill, + applied_role, +} from "@prisma/client"; + +// setup mock for request +import * as req from "../../request"; +jest.mock("../../request"); +const reqMock = req as jest.Mocked; + +// setup mock for utility +import * as util from "../../utility"; +jest.mock("../../utility", () => { + const og = jest.requireActual("../../utility"); + return { + ...og, + checkSessionKey: jest.fn(), + isAdmin: jest.fn(), + }; // we want to only mock checkSessionKey and isAdmin +}); +export const utilMock = util as jest.Mocked; + +// setup mocks for orm +import * as japp from "../../orm_functions/job_application"; +jest.mock("../../orm_functions/job_application"); +const jappMock = japp as jest.Mocked; + +import * as osoc_ from "../../orm_functions/osoc"; +jest.mock("../../orm_functions/osoc"); +const osocMock = osoc_ as jest.Mocked; + +import * as followup from "../../routes/followup"; + +function expectCall(func: T, val: U) { + expect(func).toHaveBeenCalledTimes(1); + expect(func).toHaveBeenCalledWith(val); +} + +function expectNoCall(func: T) { + expect(func).toHaveBeenCalledTimes(0); +} + +const osocdat: osoc = { + osoc_id: 0, + year: 2022, +}; + +type _appl = job_application & { + attachment: attachment[]; + job_application_skill: job_application_skill[]; + applied_role: applied_role[]; +}; + +const jobapps: _appl[] = [ + { + job_application_id: 1, + student_id: 5, + student_volunteer_info: "no idea", + responsibilities: "none", + fun_fact: "not so fun", + student_coach: false, + osoc_id: 0, + edus: [], + edu_duration: 0, + edu_level: "noob", + edu_year: "pro", + edu_institute: "someones basement", + email_status: "SCHEDULED", + created_at: new Date(Date.now()), + job_application_skill: [ + { + job_application_skill_id: 0, + job_application_id: 1, + skill: "useless", + language_id: 6, + level: 5, + is_preferred: false, + is_best: true, + }, + ], + attachment: [ + { attachment_id: 5, job_application_id: 0, data: [], type: [] }, + ], + applied_role: [ + { applied_role_id: 2, job_application_id: 0, role_id: 6 }, + ], + }, + { + job_application_id: 2, + student_id: 7, + student_volunteer_info: "no idea", + responsibilities: "none", + fun_fact: "not so fun", + student_coach: false, + osoc_id: 0, + edus: [], + edu_duration: 0, + edu_level: "noob", + edu_year: "pro", + edu_institute: "someones basement", + email_status: "SCHEDULED", + created_at: new Date(Date.now()), + job_application_skill: [ + { + job_application_skill_id: 0, + job_application_id: 2, + skill: "useless", + language_id: 6, + level: 5, + is_preferred: false, + is_best: true, + }, + ], + attachment: [ + { attachment_id: 5, job_application_id: 2, data: [], type: [] }, + ], + applied_role: [ + { applied_role_id: 2, job_application_id: 2, role_id: 6 }, + ], + }, +]; + +beforeEach(() => { + reqMock.parseFollowupAllRequest.mockResolvedValue({ sessionkey: "abcd" }); + reqMock.parseGetFollowupStudentRequest.mockResolvedValue({ + sessionkey: "abcd", + id: 5, + }); + reqMock.parseSetFollowupStudentRequest.mockImplementation((r) => + Promise.resolve(r.body) + ); + + utilMock.checkSessionKey.mockImplementation((v) => + Promise.resolve({ + userId: 0, + data: v, + accountStatus: "ACTIVATED", + is_admin: true, + is_coach: true, + }) + ); + utilMock.isAdmin.mockImplementation((v) => + Promise.resolve({ + userId: 0, + data: v, + accountStatus: "ACTIVATED", + is_admin: true, + is_coach: true, + }) + ); + + osocMock.getLatestOsoc.mockResolvedValue(osocdat); + jappMock.getJobApplicationByYear.mockResolvedValue(jobapps); + jappMock.getLatestJobApplicationOfStudent.mockImplementation((v) => + v == 5 + ? Promise.resolve(jobapps[0]) + : v == 7 + ? Promise.resolve(jobapps[1]) + : Promise.resolve(null) + ); + jappMock.changeEmailStatusOfJobApplication.mockImplementation((id, u) => + id == 1 + ? Promise.resolve({ ...jobapps[0], email_status: u }) + : id == 2 + ? Promise.resolve({ ...jobapps[1], email_status: u }) + : Promise.reject() + ); +}); + +afterEach(() => { + reqMock.parseFollowupAllRequest.mockReset(); + reqMock.parseGetFollowupStudentRequest.mockReset(); + reqMock.parseSetFollowupStudentRequest.mockReset(); + + utilMock.checkSessionKey.mockReset(); + utilMock.isAdmin.mockReset(); + + osocMock.getLatestOsoc.mockReset(); + jappMock.getJobApplicationByYear.mockReset(); + jappMock.getLatestJobApplicationOfStudent.mockReset(); + jappMock.changeEmailStatusOfJobApplication.mockReset(); +}); + +test("Can get all followup data", async () => { + const req: express.Request = getMockReq(); + + await expect(followup.listFollowups(req)).resolves.toStrictEqual({ + data: jobapps.map((x) => ({ + student: x.student_id, + application: x.job_application_id, + status: x.email_status, + })), + }); + expectCall(utilMock.checkSessionKey, { sessionkey: "abcd" }); + expectCall(reqMock.parseFollowupAllRequest, req); + expect(osocMock.getLatestOsoc).toHaveBeenCalledTimes(1); + expectCall(jappMock.getJobApplicationByYear, osocdat.year); + expectNoCall(utilMock.isAdmin); +}); + +test("Can get single followup", async () => { + const req: express.Request = getMockReq(); + await expect(followup.getFollowup(req)).resolves.toStrictEqual({ + student: jobapps[0].student_id, + application: jobapps[0].job_application_id, + status: jobapps[0].email_status, + }); + expectCall(utilMock.checkSessionKey, { sessionkey: "abcd", id: 5 }); + expectCall(reqMock.parseGetFollowupStudentRequest, req); + expectCall(jappMock.getLatestJobApplicationOfStudent, 5); + expectNoCall(utilMock.isAdmin); +}); + +test("Can update single followup", async () => { + const req: express.Request = getMockReq(); + req.body.type = "SENT"; + req.body.id = 5; + req.body.sessionkey = "abcd"; + await expect(followup.updateFollowup(req)).resolves.toStrictEqual({ + student: 5, + application: 1, + status: "SENT", + }); +}); diff --git a/backend/tests/routes_unit/form.test.ts b/backend/tests/routes_unit/form.test.ts new file mode 100644 index 00000000..776d2aa7 --- /dev/null +++ b/backend/tests/routes_unit/form.test.ts @@ -0,0 +1,697 @@ +import { getMockReq } from "@jest-mock/express"; + +import * as form_router from "../../routes/form"; + +import * as T from "../../types"; +import express from "express"; +import * as Rq from "../../request"; +import { errors } from "../../utility"; +/*import express from "express"; +import * as Rq from "../../request"; +import { errors } from "../../utility";*/ +// setup mock for form +//jest.mock("../../routes/form"); +//const formMock = form as jest.Mocked; +import * as fs from "fs"; +import * as path from "path"; + +const keys = [ + "question_mK177D", + "question_wLpAAJ", + "question_npOjjP", + "question_31VZZb", + "question_wMRkk8", + "question_mJzaaX", + "question_wgGyyJ", + "question_3yYKKd", + "question_3X0VVz", + "question_w8xvvr", + "question_n0O22A", + "question_wzYppg", + "question_w5x66Q", + "question_wddLLV", + "question_mYaEEv", + "question_mDzrrE", + "question_3lrooX", + "question_mRPXXQ", + "question_woGrrN", + "question_nGlNNO", + "question_mOGppM", + "question_mVJRR6", + "question_nPOMMx", + "question_3EWjj2", + "question_nrPNNX", + "question_w4rWW5", + "question_3jPdd1", + "question_w2PqqM", + "question_3xYkkk", + "question_mZ6555", + "question_3NW55p", + "question_3qAZZ5", + "question_wQ5221", + "question_n9DGGX", + "question_meeZZQ", + "question_nW599R", + "question_waYNN2", +]; + +function readDataTestForms(): T.Requests.Form[] { + return Object.values( + fs.readdirSync(path.join(__dirname, "/../../../testforms")) + ) + .filter((filename) => filename.includes("testform")) + .map((filename) => { + const readFile = (path: string) => fs.readFileSync(path, "utf8"); + const fileData = readFile( + path.join(__dirname, `/../../../testforms/${filename}`) + ); + return JSON.parse(fileData); + }); +} + +const form_obj: T.Requests.Form = { + createdAt: "2022-04-24T10:41:30.976Z", + data: { + fields: [ + { + key: "question_mK177D", + value: "343f74a1-7b3d-481e-94f1-cfd613d78f00", + options: [ + { + id: "343f74a1-7b3d-481e-94f1-cfd613d78f00", + text: "Yes", + }, + { + id: "2cf437cc-fee4-496b-8e46-f6c037dca583", + text: "No", + }, + ], + }, + { + key: "question_wLpAAJ", + value: "46db63af-daaa-42ec-a755-8ff5c6d2b1bf", + options: [ + { + id: "46db63af-daaa-42ec-a755-8ff5c6d2b1bf", + text: "Yes, I can work with a student employment agreement in Belgium", + }, + { + id: "f41e1bef-8a6c-4f9f-b369-cd388aacd9f9", + text: "Yes, I can work as a volunteer in Belgium", + }, + { + id: "230a7690-7e05-43d5-97a4-58c0e37142dc", + text: "No – but I would like to join this experience for free", + }, + { + id: "9a065686-5d60-423f-a343-db8ae9066028", + text: "No, I won’t be able to work as a student, as a volunteer or for free.", + }, + ], + }, + { + key: "question_npOjjP", + value: "115ad290-a86e-42f5-939e-8276ff41e749", + options: [ + { + id: "115ad290-a86e-42f5-939e-8276ff41e749", + text: "Yes", + }, + { + id: "7e42e1a5-a15a-4866-b92a-5fd8041a5cc4", + text: "No, I wouldn't be able to work for the majority of days.", + }, + ], + }, + { + key: "question_31VZZb", + value: "Test responsibility", + }, + { + key: "question_wMRkk8", + value: "Bob", + }, + { + key: "question_mJzaaX", + value: "Dylan", + }, + { + key: "question_wgGyyJ", + value: "35e07206-f052-4826-8965-515ed783a43e", + options: [ + { + id: "35e07206-f052-4826-8965-515ed783a43e", + text: "Yes", + }, + { + id: "e3558697-3a38-4137-b699-1fb8ec93e9ab", + text: "No", + }, + ], + }, + { + key: "question_3yYKKd", + value: "Bobby", + }, + { + key: "question_3X0VVz", + value: "2bcf2809-3efe-407d-87c1-dabcf3db0743", + options: [ + { + id: "d664cd5f-121f-421c-8f2c-e1b8b6505899", + text: "Female", + }, + { + id: "2bcf2809-3efe-407d-87c1-dabcf3db0743", + text: "Male", + }, + { + id: "afd31775-f165-41d0-9dd2-45dd4ae1c90a", + text: "Transgender", + }, + { + id: "fc2cc0be-6688-4ab8-9567-36bedbe829b2", + text: "Rather not say", + }, + ], + }, + { + key: "question_w8xvvr", + value: "8b2065a1-09f1-46dc-a03e-138a20160230", + options: [ + { + id: "8b2065a1-09f1-46dc-a03e-138a20160230", + text: "Yes", + }, + { + id: "e08c0951-9ea4-4781-b5e4-a5252b91855f", + text: "No", + }, + ], + }, + { + key: "question_n0O22A", + value: "c15e9f19-2997-4882-b7ec-1f3fd9c70af0", + options: [ + { + id: "97e74546-7f92-422c-b2be-ffaf7ef3f50b", + text: "she/her/hers", + }, + { + id: "a8a3c71b-2d52-4319-9399-e13eeffb7e99", + text: "he/him/his", + }, + { + id: "57489d99-aa62-4f9a-a9b9-5975570ea7d7", + text: "they/them/theirs", + }, + { + id: "071f603c-e1f6-4212-bc2d-57a173a076b6", + text: "ze/hir/hir ", + }, + { + id: "4e0d7394-23b3-4469-ad2d-2b6482186f8c", + text: "by firstname", + }, + { + id: "a5c6726f-661e-43e1-b913-c33683f20ddd", + text: "by call name", + }, + { + id: "c15e9f19-2997-4882-b7ec-1f3fd9c70af0", + text: "other", + }, + ], + }, + { + key: "question_wzYppg", + value: "he/him/his", + }, + { + key: "question_w5x66Q", + value: "86bb74b6-5dd8-4ce0-b28a-962604d2c630", + options: [ + { + id: "66165a2c-404a-4def-b925-846769a123bd", + text: "Dutch", + }, + { + id: "86bb74b6-5dd8-4ce0-b28a-962604d2c630", + text: "English", + }, + { + id: "551bd941-9c3c-4805-89c0-22ae46ac89d0", + text: "French", + }, + { + id: "1ed59443-b164-4ddf-85ff-c603cd4edaf1", + text: "German", + }, + { + id: "3594c098-46fd-4bf9-98b3-c11e568c3403", + text: "Other", + }, + ], + }, + { + key: "question_wddLLV", + value: null, + }, + { + key: "question_mYaEEv", + value: "1f4fa6d9-b2ba-42a7-9f82-5b1be7598d3e", + options: [ + { + id: "18419bd9-82b4-40f8-980a-10ce9a022db3", + text: "★ I can understand your form, but it is hard for me to reply.", + }, + { + id: "eeba838b-5a34-4c92-a8ca-4cab70d19f93", + text: "★★ I can have simple conversations.", + }, + { + id: "6023fc5b-6ae2-4280-a691-37dc781044d8", + text: "★★★ I can express myself, understand people and get a point across.", + }, + { + id: "4bab946f-ff92-4e32-abd7-ed0c9113cdbf", + text: "★★★★ I can have extensive and complicated conversations.", + }, + { + id: "1f4fa6d9-b2ba-42a7-9f82-5b1be7598d3e", + text: "★★★★★ I am fluent.", + }, + ], + }, + { + key: "question_mDzrrE", + value: "0487294163", + }, + { + key: "question_3lrooX", + value: "bob.dylan@gmail.com", + }, + { + key: "question_mRPXXQ", + value: [ + { + url: "https://storage.googleapis.com/tally-response-assets/repXzN/ee3f362a-f4ab-4e19-9fe4-3886aff77462/attachment.txt", + }, + ], + }, + { + key: "question_woGrrN", + value: null, + }, + { + key: "question_nGlNNO", + value: [ + { + url: "https://storage.googleapis.com/tally-response-assets/repXzN/4b6f238f-cd08-4dfe-be66-9dde191ecb09/attachment.txt", + }, + ], + }, + { + key: "question_mOGppM", + value: null, + }, + { + key: "question_mVJRR6", + value: [ + { + url: "https://storage.googleapis.com/tally-response-assets/repXzN/d3dda569-58db-45e0-a1dc-423ac1d6432e/attachment.txt", + }, + ], + }, + { + key: "question_nPOMMx", + value: null, + }, + { + key: "question_3EWjj2", + value: "I am very motivated", + }, + { + key: "question_nrPNNX", + value: "I am funny", + }, + { + key: "question_w4rWW5", + value: [ + "63669061-f3e1-4b97-addc-f8b37a3989f4", + "dbb51b92-95ac-4e17-ba9b-925dd8feeb0a", + ], + options: [ + { + id: "63669061-f3e1-4b97-addc-f8b37a3989f4", + text: "Backend development", + }, + { + id: "816606cb-4f87-4632-9c27-7f09e1c0d0f4", + text: "Business management", + }, + { + id: "dfedd70f-5780-42b4-bc74-85d3c4e69225", + text: "Communication Sciences", + }, + { + id: "de2a0d9b-2cc1-472f-ad76-954677a8afb1", + text: "Computer Sciences", + }, + { + id: "7f51c707-09f4-48bb-b6ea-1f90f5a7dee2", + text: "Design", + }, + { + id: "ef021761-9972-46d6-960a-0236ba723963", + text: "Frontend development", + }, + { + id: "bdefa065-f120-488b-bd01-244c5a59f20e", + text: "Marketing", + }, + { + id: "c2c15641-dee6-4712-8fdf-0448d2dcaed1", + text: "Photography", + }, + { + id: "e9964b65-12b7-4179-91bc-d63f5f02aee9", + text: "Videography", + }, + { + id: "dbb51b92-95ac-4e17-ba9b-925dd8feeb0a", + text: "Other", + }, + ], + }, + { + key: "question_w4rWW5_63669061-f3e1-4b97-addc-f8b37a3989f4", + value: true, + }, + { + key: "question_w4rWW5_816606cb-4f87-4632-9c27-7f09e1c0d0f4", + value: false, + }, + { + key: "question_w4rWW5_dfedd70f-5780-42b4-bc74-85d3c4e69225", + value: false, + }, + { + key: "question_w4rWW5_de2a0d9b-2cc1-472f-ad76-954677a8afb1", + value: false, + }, + { + key: "question_w4rWW5_7f51c707-09f4-48bb-b6ea-1f90f5a7dee2", + value: false, + }, + { + key: "question_w4rWW5_ef021761-9972-46d6-960a-0236ba723963", + value: false, + }, + { + key: "question_w4rWW5_bdefa065-f120-488b-bd01-244c5a59f20e", + value: false, + }, + { + key: "question_w4rWW5_c2c15641-dee6-4712-8fdf-0448d2dcaed1", + value: false, + }, + { + key: "question_w4rWW5_e9964b65-12b7-4179-91bc-d63f5f02aee9", + value: false, + }, + { + key: "question_w4rWW5_dbb51b92-95ac-4e17-ba9b-925dd8feeb0a", + value: true, + }, + { + key: "question_3jPdd1", + value: "Database design", + }, + { + key: "question_w2PqqM", + value: ["a27ad504-f524-4e5b-9919-2ad70c14814a"], + options: [ + { + id: "ee039478-24af-46f0-8619-d0615512c2b7", + text: "A professional Bachelor", + }, + { + id: "a27ad504-f524-4e5b-9919-2ad70c14814a", + text: "An academic Bachelor", + }, + { + id: "97dbef1b-d707-4585-b61b-45c99db6c54c", + text: "An associate degree", + }, + { + id: "9c28047c-0392-42eb-9e0f-0bd1b03a5e12", + text: "A master's degree", + }, + { + id: "25c435fd-f20a-4e70-bafd-0563089e28ec", + text: "Doctoral degree", + }, + { + id: "e5f9b310-6087-4c44-8422-ccee50b9378b", + text: "No diploma, I am self taught", + }, + { + id: "67e44797-6696-4605-b1bd-bf76859c70f9", + text: "Other", + }, + ], + }, + { + key: "question_w2PqqM_ee039478-24af-46f0-8619-d0615512c2b7", + value: false, + }, + { + key: "question_w2PqqM_a27ad504-f524-4e5b-9919-2ad70c14814a", + value: true, + }, + { + key: "question_w2PqqM_97dbef1b-d707-4585-b61b-45c99db6c54c", + value: false, + }, + { + key: "question_w2PqqM_9c28047c-0392-42eb-9e0f-0bd1b03a5e12", + value: false, + }, + { + key: "question_w2PqqM_25c435fd-f20a-4e70-bafd-0563089e28ec", + value: false, + }, + { + key: "question_w2PqqM_e5f9b310-6087-4c44-8422-ccee50b9378b", + value: false, + }, + { + key: "question_w2PqqM_67e44797-6696-4605-b1bd-bf76859c70f9", + value: false, + }, + { + key: "question_3xYkkk", + value: "No diploma", + }, + { + key: "question_mZ6555", + value: 5, + }, + { + key: "question_3NW55p", + value: "Third bachelor", + }, + { + key: "question_3qAZZ5", + value: "Ghent university", + }, + { + key: "question_wQ5221", + value: [ + "687908c0-7091-429c-9f99-cf9670503892", + "dede10c8-a949-463c-8ae8-73f6fab6a362", + ], + options: [ + { + id: "d2c3e561-28bd-4de9-a7db-00cd22beb3b7", + text: "Front-end developer", + }, + { + id: "687908c0-7091-429c-9f99-cf9670503892", + text: "Back-end developer", + }, + { + id: "def165c9-d40a-4d13-b174-98d480e1dd41", + text: "UX / UI designer", + }, + { + id: "fae1aff3-eb1c-4109-abb5-c91ed8e64cf1", + text: "Graphic designer", + }, + { + id: "37c7da5d-361c-4000-b8ed-fcf8e69971f0", + text: "Business Modeller", + }, + { + id: "07228f94-e28d-41f6-ac6e-ccd039921f58", + text: "Storyteller", + }, + { + id: "4b1c6ba6-e945-465e-9222-de02d44988ce", + text: "Marketer", + }, + { + id: "e993e2bb-6dfe-4ec3-8f36-cf940b30533e", + text: "Copywriter", + }, + { + id: "e7f54c05-e90b-4967-b9a5-7fa997abdabc", + text: "Video editor", + }, + { + id: "7418ba9a-e88b-4a4c-98e7-4f346d61024e", + text: "Photographer", + }, + { + id: "dede10c8-a949-463c-8ae8-73f6fab6a362", + text: "Other", + }, + ], + }, + { + key: "question_wQ5221_d2c3e561-28bd-4de9-a7db-00cd22beb3b7", + value: false, + }, + { + key: "question_wQ5221_687908c0-7091-429c-9f99-cf9670503892", + value: true, + }, + { + key: "question_wQ5221_def165c9-d40a-4d13-b174-98d480e1dd41", + value: false, + }, + { + key: "question_wQ5221_fae1aff3-eb1c-4109-abb5-c91ed8e64cf1", + value: false, + }, + { + key: "question_wQ5221_37c7da5d-361c-4000-b8ed-fcf8e69971f0", + value: false, + }, + { + key: "question_wQ5221_07228f94-e28d-41f6-ac6e-ccd039921f58", + value: false, + }, + { + key: "question_wQ5221_4b1c6ba6-e945-465e-9222-de02d44988ce", + value: false, + }, + { + key: "question_wQ5221_e993e2bb-6dfe-4ec3-8f36-cf940b30533e", + value: false, + }, + { + key: "question_wQ5221_e7f54c05-e90b-4967-b9a5-7fa997abdabc", + value: false, + }, + { + key: "question_wQ5221_7418ba9a-e88b-4a4c-98e7-4f346d61024e", + value: false, + }, + { + key: "question_wQ5221_dede10c8-a949-463c-8ae8-73f6fab6a362", + value: true, + }, + { + key: "question_n9DGGX", + value: "Database designer", + }, + { + key: "question_meeZZQ", + value: "Writing songs", + }, + { + key: "question_nW599R", + value: "127d3ef2-2929-4895-ba52-4d10bcaa1e19", + options: [ + { + id: "e47f258c-dc5e-449e-bf93-5587d5b8f3ee", + text: "No, it's my first time participating in osoc", + }, + { + id: "127d3ef2-2929-4895-ba52-4d10bcaa1e19", + text: "Yes, I have been part of osoc before", + }, + ], + }, + { + key: "question_waYNN2", + value: "6959f608-436d-4da8-a14e-f9cedb99f705", + options: [ + { + id: "ffa7874f-52db-4058-93a1-8a015376f256", + text: "No, I don't want to be a student coach", + }, + { + id: "6959f608-436d-4da8-a14e-f9cedb99f705", + text: "Yes, I'd like to be a student coach", + }, + ], + }, + ], + }, +}; + +test("Can parse form request", () => { + const resultList: Promise[] = []; + const req: express.Request = getMockReq(); + + req.body = {}; + resultList.push( + expect(Rq.parseFormRequest(req)).rejects.toBe( + errors.cookArgumentError() + ) + ); + + const req2: express.Request = getMockReq(); + req2.body = { data: {} }; + resultList.push( + expect(Rq.parseFormRequest(req2)).rejects.toBe( + errors.cookArgumentError() + ) + ); + + const req3: express.Request = getMockReq(); + req3.body = { data: { fields: [{ value: "value" }] } }; + resultList.push( + expect(Rq.parseFormRequest(req3)).rejects.toBe( + errors.cookArgumentError() + ) + ); + + readDataTestForms().forEach((data) => { + const req4: express.Request = getMockReq(); + req4.body = { ...data }; + const v1 = expect(Rq.parseFormRequest(req4)).resolves.toHaveProperty( + "data", + data.data + ); + const v2 = expect(Rq.parseFormRequest(req4)).resolves.toHaveProperty( + "createdAt", + data.createdAt + ); + + resultList.push(v1); + resultList.push(v2); + }); + + return Promise.all(resultList); +}); + +describe.each(keys)("Questions present", (key) => { + it(`Question with key ${key} is present`, () => { + const res = form_router.filterQuestion(form_obj, key); + expect(res.data).not.toBe(null); + }); +}); diff --git a/backend/tests/routes_unit/github.test.ts b/backend/tests/routes_unit/github.test.ts new file mode 100644 index 00000000..6df639e8 --- /dev/null +++ b/backend/tests/routes_unit/github.test.ts @@ -0,0 +1,384 @@ +import { getMockReq, getMockRes } from "@jest-mock/express"; + +import { Anything } from "../../types"; + +import axios from "axios"; +jest.mock("axios"); +const axiosMock = axios as jest.Mocked; + +import * as ormP from "../../orm_functions/person"; +jest.mock("../../orm_functions/person"); +const ormPMock = ormP as jest.Mocked; + +import * as ormLU from "../../orm_functions/login_user"; +jest.mock("../../orm_functions/login_user"); +const ormLUMock = ormLU as jest.Mocked; + +import * as ormSK from "../../orm_functions/session_key"; +jest.mock("../../orm_functions/session_key"); +const ormSKMock = ormSK as jest.Mocked; + +import * as util from "../../utility"; +jest.mock("../../utility"); +const utilMock = util as jest.Mocked; + +import * as session_key from "../../routes/session_key.json"; +import * as config from "../../config.json"; + +import * as github from "../../routes/github"; + +const realDateNow = Date.now.bind(global.Date); + +function orNull(v: T | undefined | null): T | null { + if (v == undefined) return null; + else return v; +} + +beforeEach(() => { + global.Date.now = jest.fn(() => 9784651320); + + ormPMock.getPasswordPersonByGithub.mockImplementation((id) => + Promise.resolve( + id == "69" + ? { + github: "@my_github", + person_id: 69, + firstname: "my", + login_user: { + password: null, + login_user_id: 420, + account_status: "ACTIVATED", + is_admin: true, + is_coach: true, + }, + } + : null + ) + ); + + ormPMock.updatePerson.mockImplementation((u) => + Promise.resolve({ + person_id: u.personId, + firstname: u.firstname != null ? u.firstname : "jeff", + email: u.email != null ? u.email : null, + github: u.github != null ? u.github : "@my_acct", + github_id: "784", + lastname: u.lastname != null ? u.lastname : "", + }) + ); + ormPMock.createPerson.mockImplementation((p) => + Promise.resolve({ + person_id: 1234, + firstname: p.firstname, + lastname: p.lastname, + github: orNull(p.github), + github_id: orNull(p.github_id), + email: orNull(p.email), + }) + ); + + ormLUMock.createLoginUser.mockImplementation((lu) => + Promise.resolve({ + login_user_id: 5678, + person_id: lu.personId, + is_admin: lu.isAdmin, + is_coach: lu.isCoach, + account_status: lu.accountStatus, + password: orNull(lu.password), + }) + ); + + ormSKMock.addSessionKey.mockImplementation((id, key, date) => + Promise.resolve({ + session_key_id: 8465, + session_key: key, + login_user_id: id, + valid_until: date, + }) + ); + + utilMock.generateKey.mockReturnValue("abcd"); + utilMock.getOrReject.mockImplementation((v) => + v == null || v == undefined ? Promise.reject({}) : Promise.resolve(v) + ); +}); + +afterEach(() => { + ormPMock.getPasswordPersonByGithub.mockReset(); + ormPMock.updatePerson.mockReset(); + ormPMock.createPerson.mockReset(); + + ormLUMock.createLoginUser.mockReset(); + + ormSKMock.addSessionKey.mockReset(); + + utilMock.generateKey.mockReset(); + + global.Date.now = realDateNow; +}); + +function expectCall(func: T, val: U) { + expect(func).toHaveBeenCalledTimes(1); + expect(func).toHaveBeenCalledWith(val); +} + +function expectNoCall(func: T) { + expect(func).toHaveBeenCalledTimes(0); +} + +test("Can generate and validate states", () => { + expect(github.checkState(github.genState())).toBe(true); +}); + +test("A state is valid only once", () => { + const state = github.genState(); + expect(github.checkState(state)).toBe(true); + expect(github.checkState(state)).toBe(false); +}); + +test("ghIdentity correctly redirects", async () => { + const exp: string = + "https://github.com/login/oauth/authorize?" + + "client_id=undefined&allow_signup=true&" + + "redirect_uri=undefined%2Fapi-osoc%2Fgithub%2Fchallenge&" + + "state="; + utilMock.redirect.mockReset(); + utilMock.redirect.mockImplementation(async (_, url) => { + await expect(url).toBe(exp + github.states[0]); + }); + + await github.ghIdentity(getMockRes().res); +}); + +test("Can parse GH login", async () => { + const v1: Anything = { login: "jeffrey", name: "jan", id: 9845312 }; + const v2: Anything = { login: "jan", name: null, id: 98465132 }; + + const i1: Anything = {}; + const i2: Anything = { id: 7984615320 }; + const i3: Anything = { login: "jan" }; + const i4: Anything = { name: "jeffrey" }; + const i5: Anything = { login: "jan", name: "jeffrey" }; + const i6: Anything = { name: "jeffrey", id: 98465132789 }; + const i7: Anything = { login: "jeffrey", id: 451320 }; + + const valids = [v1, v2].map((v) => { + const res: Anything = { ...v, id: (v.id as number).toString() }; + if (v.name == null) res.name = v.login; + return expect(github.parseGHLogin(v)).resolves.toStrictEqual(res); + }); + + const invalids = [i1, i2, i3, i4, i5, i6, i7].map((v) => { + return expect(github.parseGHLogin(v)).rejects.toStrictEqual({}); + }); + + return Promise.all([valids, invalids].flat()); +}); + +test("Can detect if name changes are required", () => { + const base_login = { login: "", name: "", id: "78451320" }; + const base_person = { + github: "", + person_id: 78645312, + firstname: "", + login_user: null, + }; + + expect( + github.githubNameChange( + { ...base_login, login: "abcd" }, + { ...base_person, github: "cdef" } + ) + ).toBeTruthy(); + expect( + github.githubNameChange( + { ...base_login, name: "abcd" }, + { ...base_person, firstname: "cdef" } + ) + ).toBeTruthy(); + expect( + github.githubNameChange( + { ...base_login, login: "abcd", name: "cdef" }, + { ...base_person, github: "cdef", firstname: "cdef" } + ) + ).toBeTruthy(); + expect( + github.githubNameChange( + { ...base_login, login: "cdef", name: "abcd" }, + { ...base_person, github: "cdef", firstname: "cdef" } + ) + ).toBeTruthy(); + + expect( + github.githubNameChange( + { ...base_login, login: "abcd", name: "cdef" }, + { ...base_person, github: "abcd", firstname: "cdef" } + ) + ).toBeFalsy(); +}); + +test("Can login if the account exists", async () => { + const input = { login: "@my_github", name: "my", id: "69" }; + const output = { sessionkey: "abcd", is_admin: true, is_coach: true }; + const f = new Date(Date.now()); + f.setDate(f.getDate() + session_key.valid_period); + + await expect(github.ghSignupOrLogin(input)).resolves.toStrictEqual(output); + expectCall(ormPMock.getPasswordPersonByGithub, "69"); + expect(util.generateKey).toHaveBeenCalledTimes(1); + expect(ormSK.addSessionKey).toHaveBeenCalledTimes(1); + expect(ormSK.addSessionKey).toHaveBeenCalledWith(420, "abcd", f); + expectNoCall(ormP.updatePerson); + expectNoCall(ormLU.createLoginUser); + expectNoCall(ormP.createPerson); +}); + +test("Can login if the account exists and update username", async () => { + const input = { login: "@my_github", name: "alo", id: "69" }; + const output = { sessionkey: "abcd", is_admin: true, is_coach: true }; + const f = new Date(Date.now()); + f.setDate(f.getDate() + session_key.valid_period); + + await expect(github.ghSignupOrLogin(input)).resolves.toStrictEqual(output); + expectCall(ormPMock.getPasswordPersonByGithub, "69"); + expect(util.generateKey).toHaveBeenCalledTimes(1); + expect(ormSK.addSessionKey).toHaveBeenCalledTimes(1); + expect(ormSK.addSessionKey).toHaveBeenCalledWith(420, "abcd", f); + expect(ormP.updatePerson).toHaveBeenCalledTimes(1); + expect(ormP.updatePerson).toHaveBeenCalledWith({ + personId: 69, + github: "@my_github", + firstname: "alo", + }); + expectNoCall(ormLU.createLoginUser); + expectNoCall(ormP.createPerson); +}); + +test("Can login if the account exists and update github handle", async () => { + const input = { login: "@jefke", name: "my", id: "69" }; + const output = { sessionkey: "abcd", is_admin: true, is_coach: true }; + const f = new Date(Date.now()); + f.setDate(f.getDate() + session_key.valid_period); + + await expect(github.ghSignupOrLogin(input)).resolves.toStrictEqual(output); + expectCall(ormPMock.getPasswordPersonByGithub, "69"); + expect(util.generateKey).toHaveBeenCalledTimes(1); + expect(ormSK.addSessionKey).toHaveBeenCalledTimes(1); + expect(ormSK.addSessionKey).toHaveBeenCalledWith(420, "abcd", f); + expect(ormP.updatePerson).toHaveBeenCalledTimes(1); + expect(ormP.updatePerson).toHaveBeenCalledWith({ + personId: 69, + github: "@jefke", + firstname: "my", + }); + expectNoCall(ormLU.createLoginUser); + expectNoCall(ormP.createPerson); +}); + +test("Can register if account doesn't exist", async () => { + const input = { login: "@jefke", name: "my", id: "-69" }; + const output = { sessionkey: "abcd", is_admin: false, is_coach: true }; + const f = new Date(Date.now()); + f.setDate(f.getDate() + session_key.valid_period); + + await expect(github.ghSignupOrLogin(input)).resolves.toStrictEqual(output); + expectCall(ormPMock.getPasswordPersonByGithub, "-69"); + expectCall(ormP.createPerson, { + github: "@jefke", + firstname: "my", + lastname: "", + github_id: "-69", + }); + expectCall(ormLU.createLoginUser, { + personId: 1234, + isAdmin: false, + isCoach: true, + accountStatus: "PENDING", + }); + expect(util.generateKey).toHaveBeenCalledTimes(1); + expect(ormSK.addSessionKey).toHaveBeenCalledTimes(1); + expect(ormSK.addSessionKey).toHaveBeenCalledWith(5678, "abcd", f); + expectNoCall(ormP.updatePerson); +}); + +test("Login/Register fails if the request is incorrect", async () => { + const req = getMockReq(); + const res = getMockRes().res; + + req.query = { state: "8945" }; + await expect(github.ghExchangeAccessToken(req, res)).rejects.toBe( + config.apiErrors.github.argumentMissing + ); + + req.query = { code: "78946512" }; + await expect(github.ghExchangeAccessToken(req, res)).rejects.toBe( + config.apiErrors.github.argumentMissing + ); + + req.query = { code: "132465798", state: "45" }; + await expect(github.ghExchangeAccessToken(req, res)).rejects.toBe( + config.apiErrors.github.illegalState + ); +}); + +test("Can exchange access token for session key", async () => { + const code = "abcdefghijklmnopqrstuvwxyz"; + + axiosMock.post.mockImplementation((url, body, conf) => { + expect(url).toBe("https://github.com/login/oauth/access_token"); + expect(conf).toHaveProperty("headers.Accept", "application/json"); + expect(body).toStrictEqual({ + client_id: process.env.GITHUB_CLIENT_ID, + client_secret: process.env.GITHUB_SECRET, + code: code as string, + redirect_uri: + github.getHome() + config.global.preferred + "/github/login", + }); + + return Promise.resolve({ data: { access_token: "some_token" } }); + }); + + axiosMock.get.mockImplementation((url, conf) => { + expect(url).toBe("https://api.github.com/user"); + expect(conf).toHaveProperty( + "headers.Authorization", + "token some_token" + ); + + return Promise.resolve({ + data: { login: "@my_github", name: "my", id: 69 }, + }); + }); + + utilMock.redirect.mockImplementation(async (_, url) => { + expect(url).toBe(process.env.FRONTEND + "/login/abcd"); + return Promise.resolve(); + }); + + const req = getMockReq(); + const res = getMockRes().res; + req.query = { code: code, state: github.genState() }; + return expect( + github.ghExchangeAccessToken(req, res) + ).resolves.not.toThrow(); +}); + +test("Can handle internet issues", async () => { + axiosMock.post.mockRejectedValue({ error: "can't connect to server" }); + + utilMock.redirect.mockImplementation(async (_, url) => { + expect(url).toBe( + process.env.FRONTEND + + "/login?loginError=" + + config.apiErrors.github.other.reason + ); + return Promise.resolve(); + }); + + const req = getMockReq(); + const res = getMockRes().res; + req.query = { code: "code", state: github.genState() }; + return expect( + github.ghExchangeAccessToken(req, res) + ).resolves.not.toThrow(); +}); diff --git a/backend/tests/tests-setup.ts b/backend/tests/tests-setup.ts index 35fda959..b167b406 100644 --- a/backend/tests/tests-setup.ts +++ b/backend/tests/tests-setup.ts @@ -1,3 +1,3 @@ -import dotenv from 'dotenv'; +import dotenv from "dotenv"; import path from "path"; -dotenv.config({ path: path.resolve(__dirname + './../prisma/.env.test') }); \ No newline at end of file +dotenv.config({ path: path.resolve(__dirname + "./../prisma/.env.test") }); diff --git a/backend/tests/utility.test.ts b/backend/tests/utility.test.ts index 238dcdfb..f7e06fee 100644 --- a/backend/tests/utility.test.ts +++ b/backend/tests/utility.test.ts @@ -1,416 +1,974 @@ -import {getMockReq, getMockRes} from '@jest-mock/express'; -import express from 'express'; -import {mockDeep} from 'jest-mock-extended'; - -import * as session_key from '../orm_functions/session_key'; - -jest.mock('../orm_functions/session_key'); +import { getMockReq, getMockRes } from "@jest-mock/express"; +import express from "express"; +import { mockDeep } from "jest-mock-extended"; + +import * as session_key from "../orm_functions/session_key"; +import * as sessionKey from "../routes/session_key.json"; + +import { + expectRouter, + expectRouterThrow, + getInvalidEndpointError, + getInvalidVerbEndpointError, + getMockRouter, +} from "./mocking/mocks"; + +jest.mock("../orm_functions/session_key"); const session_keyMock = session_key as jest.Mocked; -import * as login_user from '../orm_functions/login_user'; -jest.mock('../orm_functions/login_user'); +import * as login_user from "../orm_functions/login_user"; +jest.mock("../orm_functions/login_user"); const login_userMock = login_user as jest.Mocked; -import * as crypto from 'crypto'; -jest.mock('crypto'); +import * as student from "../orm_functions/student"; +jest.mock("../orm_functions/student"); +const studentMock = student as jest.Mocked; + +import * as project from "../orm_functions/project"; +jest.mock("../orm_functions/project"); +const projectMock = project as jest.Mocked; + +import * as crypto from "crypto"; +jest.mock("crypto"); const cryptoMock = crypto as jest.Mocked; -import * as config from '../config.json'; -import {ApiError, Anything} from '../types'; -import * as util from '../utility'; +import * as config from "../config.json"; +import { ApiError, Anything } from "../types"; +import * as util from "../utility"; +import { errors } from "../utility"; interface Req { - url: string, verb: string + url: string; + verb: string; } function genPromises(): Promise[] { - const data: unknown[] = [ - 6, "abc", {"key" : "value"}, - {"complex" : {"object" : "with", "array" : [ 1, 2, 3, 4 ]}} - ]; - const promises: Promise[] = - data.map((val) => util.debug(val).then((res) => { - expect(res).toBe(val); - return res; - })); - return promises; + const data: unknown[] = [ + 6, + "abc", + { key: "value" }, + { complex: { object: "with", array: [1, 2, 3, 4] } }, + ]; + const promises: Promise[] = data.map((val) => + util.debug(val).then((res) => { + expect(res).toBe(val); + return res; + }) + ); + return promises; } function obtainResponse() { - const {res} = getMockRes(); - const statSpy = jest.spyOn(res, 'status'); - const sendSpy = jest.spyOn(res, 'send'); - return {res, statSpy, sendSpy}; + const { res } = getMockRes(); + const statSpy = jest.spyOn(res, "status"); + const sendSpy = jest.spyOn(res, "send"); + return { res, statSpy, sendSpy }; } test("utility.errors.cook* work as expected", () => { - // easy ones - expect(util.errors.cookInvalidID()).toBe(config.apiErrors.invalidID); - expect(util.errors.cookArgumentError()).toBe(config.apiErrors.argumentError); - expect(util.errors.cookUnauthenticated()) - .toBe(config.apiErrors.unauthenticated); - expect(util.errors.cookInsufficientRights()) - .toBe(config.apiErrors.insufficientRights); - expect(util.errors.cookServerError()).toBe(config.apiErrors.serverError); - - // annoying ones - // non-existent endpoint - const nonexistData: string[] = - [ "/", "/login", "/student/684648a68498e9/suggest" ]; - const nonexistExpect: {url: string, err: ApiError}[] = nonexistData.map( - url => ({ - url : url, - err : { - http : config.apiErrors.nonExistent.http, - reason : config.apiErrors.nonExistent.reason.replace(/$url/, url) - } - })); - nonexistExpect.forEach( - val => - expect(util.errors.cookNonExistent(val.url)).toStrictEqual(val.err)); - - // invalid verb - const verbs: Req[] = [ - {verb : "GET", url : "/"}, {verb : "POST", url : "/students/all"}, - {verb : "DELETE", url : "/coach/all"}, {verb : "PUT", url : "/login"}, - {verb : "HEAD", url : "/admin/all"}, - {verb : "OPTIONS", url : "/student/abcdeabcde"} - ]; - const verbExpect: {err: ApiError, req: express.Request}[] = verbs.map(v => { - const req: express.Request = getMockReq(); - req.method = v.verb; - req.url = v.url; - const msg = config.apiErrors.invalidVerb.reason.replace(/$url/, v.url) - .replace(/$verb/, v.verb); - return { - err : {http : config.apiErrors.invalidVerb.http, reason : msg}, - req : req - }; - }); - verbExpect.forEach( - v => expect(util.errors.cookInvalidVerb(v.req)).toStrictEqual(v.err)); - - // non json - const mimes: string[] = - [ 'text/html', 'audio/aac', 'image/bmp', 'text/css', 'video/mp4' ]; - const mimeExpect: {err: ApiError, mime: string}[] = mimes.map( - mime => ({ - mime : mime, - err : { - http : config.apiErrors.nonJSONRequest.http, - reason : config.apiErrors.nonJSONRequest.reason.replace(/$mime/, mime) + // easy ones + expect(util.errors.cookInvalidID()).toBe(config.apiErrors.invalidID); + expect(util.errors.cookArgumentError()).toBe( + config.apiErrors.argumentError + ); + expect(util.errors.cookUnauthenticated()).toBe( + config.apiErrors.unauthenticated + ); + expect(util.errors.cookInsufficientRights()).toBe( + config.apiErrors.insufficientRights + ); + expect(util.errors.cookServerError()).toBe(config.apiErrors.serverError); + expect(util.errors.cookLockedRequest()).toBe( + config.apiErrors.lockedRequest + ); + expect(util.errors.cookPendingAccount()).toBe( + config.apiErrors.pendingAccount + ); + + // annoying ones + // non-existent endpoint + const nonexistData: string[] = [ + "/", + "/login", + "/student/684648a68498e9/suggest", + ]; + const nonexistExpect: { url: string; err: ApiError }[] = nonexistData.map( + (url) => ({ + url: url, + err: { + http: config.apiErrors.nonExistent.http, + reason: config.apiErrors.nonExistent.reason.replace( + /~url/, + url + ), + }, + }) + ); + nonexistExpect.forEach((val) => + expect(util.errors.cookNonExistent(val.url)).toStrictEqual(val.err) + ); + + // invalid verb + const verbs: Req[] = [ + { verb: "GET", url: "/" }, + { verb: "POST", url: "/students/all" }, + { verb: "DELETE", url: "/coach/all" }, + { verb: "PUT", url: "/login" }, + { verb: "HEAD", url: "/admin/all" }, + { verb: "OPTIONS", url: "/student/abcdeabcde" }, + ]; + const verbExpect: { err: ApiError; req: express.Request }[] = verbs.map( + (v) => { + const req: express.Request = getMockReq(); + req.method = v.verb; + req.url = v.url; + const msg = config.apiErrors.invalidVerb.reason + .replace(/~url/, v.url) + .replace(/~verb/, v.verb); + return { + err: { http: config.apiErrors.invalidVerb.http, reason: msg }, + req: req, + }; } - })); - mimeExpect.forEach( - m => expect(util.errors.cookNonJSON(m.mime)).toStrictEqual(m.err)); + ); + verbExpect.forEach((v) => + expect(util.errors.cookInvalidVerb(v.req)).toStrictEqual(v.err) + ); + + // non json + const mimes: string[] = [ + "text/html", + "audio/aac", + "image/bmp", + "text/css", + "video/mp4", + ]; + const mimeExpect: { err: ApiError; mime: string }[] = mimes.map((mime) => ({ + mime: mime, + err: { + http: config.apiErrors.nonJSONRequest.http, + reason: config.apiErrors.nonJSONRequest.reason.replace( + /~mime/, + mime + ), + }, + })); + mimeExpect.forEach((m) => + expect(util.errors.cookNonJSON(m.mime)).toStrictEqual(m.err) + ); }); -test("utility.debug returns identical", - () => { return Promise.all(genPromises()); }); +test("utility.debug returns identical", () => { + return Promise.all(genPromises()); +}); test("utility.debug logs their data", () => { - const logSpy = jest.spyOn(console, 'log'); - return Promise.all(genPromises().map( - it => it.then((val) => expect(logSpy).toHaveBeenCalledWith(val)))); + const logSpy = jest.spyOn(console, "log"); + return Promise.all( + genPromises().map((it) => + it.then((val) => expect(logSpy).toHaveBeenCalledWith(val)) + ) + ); }); test("utility.reply writes to a response", () => { - const data: {status: number, data: unknown}[] = [ - {status : 200, data : "success!"}, {status : 405, data : "invalid"}, - {status : 200, data : {using : "some", kind : {of : "data"}}} - ]; - - const {res, statSpy, sendSpy} = obtainResponse(); - - const promises: Promise[] = - data.map(data => util.reply(res, data.status, data.data).then(() => { - expect(sendSpy).toBeCalledWith(data.data); - expect(statSpy).toBeCalledWith(data.status); - })); - return Promise.all(promises); + const data: { status: number; data: unknown }[] = [ + { status: 200, data: "success!" }, + { status: 405, data: "invalid" }, + { status: 200, data: { using: "some", kind: { of: "data" } } }, + ]; + + const { res, statSpy, sendSpy } = obtainResponse(); + + const promises: Promise[] = data.map((data) => + util.reply(res, data.status, data.data).then(() => { + expect(sendSpy).toBeCalledWith(data.data); + expect(statSpy).toBeCalledWith(data.status); + }) + ); + return Promise.all(promises); }); test("utility.replyError writes to a response", () => { - // load api errors - const someUrls: string[] = [ "/", "/logout", "/signup" ]; - const someVerbs: Req[] = [ - {verb : "PUT", url : "/"}, {verb : "OPTIONS", url : "/login"}, - {verb : "DELETE", url : "/student/all"} - ]; - const someReqs: express.Request[] = someVerbs.map(req => { - const request: express.Request = getMockReq(); - request.method = req.verb; - request.url = req.url; - return request; - }); - const someMimes: string[] = - [ 'text/html', 'audio/aac', 'image/bmp', 'text/css', 'video/mp4' ]; - - const errors: ApiError[] = [ - util.errors.cookInvalidID(), util.errors.cookArgumentError(), - util.errors.cookUnauthenticated(), util.errors.cookInsufficientRights(), - util.errors.cookServerError() - ]; - someUrls.forEach(url => errors.push(util.errors.cookNonExistent(url))); - someReqs.forEach(req => errors.push(util.errors.cookInvalidVerb(req))); - someMimes.forEach(mime => errors.push(util.errors.cookNonJSON(mime))); - - // run tests - errors.forEach(err => { - const {res, statSpy, sendSpy} = obtainResponse(); - const datafield: unknown = {success : false, reason : err.reason}; - util.replyError(res, err).then(() => { - expect(statSpy).toBeCalledWith(err.http); - expect(sendSpy).toBeCalledWith(datafield); + // load api errors + const someUrls: string[] = ["/", "/logout", "/signup"]; + const someVerbs: Req[] = [ + { verb: "PUT", url: "/" }, + { verb: "OPTIONS", url: "/login" }, + { verb: "DELETE", url: "/student/all" }, + ]; + const someReqs: express.Request[] = someVerbs.map((req) => { + const request: express.Request = getMockReq(); + request.method = req.verb; + request.url = req.url; + return request; + }); + const someMimes: string[] = [ + "text/html", + "audio/aac", + "image/bmp", + "text/css", + "video/mp4", + ]; + + const errors: ApiError[] = [ + util.errors.cookInvalidID(), + util.errors.cookArgumentError(), + util.errors.cookUnauthenticated(), + util.errors.cookInsufficientRights(), + util.errors.cookServerError(), + ]; + someUrls.forEach((url) => errors.push(util.errors.cookNonExistent(url))); + someReqs.forEach((req) => errors.push(util.errors.cookInvalidVerb(req))); + someMimes.forEach((mime) => errors.push(util.errors.cookNonJSON(mime))); + + // run tests + errors.forEach((err) => { + const { res, statSpy, sendSpy } = obtainResponse(); + const datafield: unknown = { success: false, reason: err.reason }; + util.replyError(res, err).then(() => { + expect(statSpy).toBeCalledWith(err.http); + expect(sendSpy).toBeCalledWith(datafield); + }); }); - }); }); test("utility.replySuccess writes to a response", () => { - const successes: Anything[] = [ {} ]; - - successes.forEach(succ => { - const {res, statSpy, sendSpy} = obtainResponse(); - util.replySuccess(res, succ).then(() => { - succ.success = true; - expect(statSpy).toBeCalledWith(200); - expect(sendSpy).toBeCalledWith(succ); + const successes: Anything[] = [{}]; + + successes.forEach((succ) => { + const { res, statSpy, sendSpy } = obtainResponse(); + util.replySuccess(res, succ).then(() => { + succ.success = true; + expect(statSpy).toBeCalledWith(200); + expect(sendSpy).toBeCalledWith(succ); + }); }); - }); }); test("utility.addInvalidVerbs adds verbs", () => { - const router: express.Router = express.Router(); - const routerSpy = jest.spyOn(router, 'all'); - util.addInvalidVerbs(router, '/'); - // we seem unable to trigger the route with jest? - // please someone check this? - expect(routerSpy).toHaveBeenCalledTimes(1); + const router: express.Router = express.Router(); + const routerSpy = jest.spyOn(router, "all"); + util.addInvalidVerbs(router, "/"); + // we seem unable to trigger the route with jest? + // please someone check this? + expect(routerSpy).toHaveBeenCalledTimes(1); }); // we need to check line_#117 but I have no idea on how to do that... test("utility.logRequest logs request and passes control", () => { - const writeSpy = jest.spyOn(console, "log"); - const request: express.Request = getMockReq(); - const callback = jest.fn( - () => { expect(writeSpy).toHaveBeenCalledWith("GET /student/all"); }); - request.method = "GET"; - request.url = "/student/all"; - util.logRequest(request, callback); - expect(callback).toHaveReturnedTimes(1); + const writeSpy = jest.spyOn(console, "log"); + const request: express.Request = getMockReq(); + const callback = jest.fn(() => { + expect(writeSpy).toHaveBeenCalledWith("GET /student/all"); + }); + request.method = "GET"; + request.url = "/student/all"; + util.logRequest(request, callback); + expect(callback).toHaveReturnedTimes(1); }); test("utility.respOrErrorNoReinject sends successes", async () => { - // positive case - const {res, statSpy, sendSpy} = obtainResponse(); - const obj: Anything = { - data : {"some" : "data"}, - "sessionkey" : "updated-session-key" - }; - const exp: unknown = { - data : {"some" : "data"}, - "sessionkey" : "updated-session-key", - success : true - }; - - await expect(util.respOrErrorNoReinject(res, Promise.resolve(obj))) - .resolves.not.toThrow(); - expect(sendSpy).toHaveBeenCalledWith(exp); - expect(statSpy).toHaveBeenCalledWith(200); + // positive case + const { res, statSpy, sendSpy } = obtainResponse(); + const obj: Anything = { + data: { some: "data" }, + sessionkey: "updated-session-key", + }; + const exp: unknown = { + data: { some: "data" }, + sessionkey: "updated-session-key", + success: true, + }; + + await expect( + util.respOrErrorNoReinject(res, Promise.resolve(obj)) + ).resolves.not.toThrow(); + expect(sendSpy).toHaveBeenCalledWith(exp); + expect(statSpy).toHaveBeenCalledWith(200); }); test("utility.respOrErrorNoReinject sends api errors", () => { - // api error case - const {res, statSpy, sendSpy} = obtainResponse(); - const err: ApiError = util.errors.cookArgumentError(); - const errObj: unknown = {success : false, reason : err.reason}; - util.respOrErrorNoReinject(res, Promise.reject(err)).then(() => { - expect(statSpy).toHaveBeenCalledWith(err.http); - expect(sendSpy).toHaveBeenCalledWith(errObj); - }); + // api error case + const { res, statSpy, sendSpy } = obtainResponse(); + const err: ApiError = util.errors.cookArgumentError(); + const errObj: unknown = { success: false, reason: err.reason }; + util.respOrErrorNoReinject(res, Promise.reject(err)).then(() => { + expect(statSpy).toHaveBeenCalledWith(err.http); + expect(sendSpy).toHaveBeenCalledWith(errObj); + }); }); -test("utility.respOrErrorNoReinject sends generic errors as server errors", - () => { - const {res, statSpy, sendSpy} = obtainResponse(); - const writeSpy = jest.spyOn(console, "log"); - const error = {msg : "some", error : {"generic" : true}}; - const send = { - success : false, - reason : config.apiErrors.serverError.reason - }; - util.respOrErrorNoReinject(res, Promise.reject(error)).then(() => { - expect(statSpy).toHaveBeenCalledWith( - config.apiErrors.serverError.http); - expect(sendSpy).toHaveBeenCalledWith(send); - expect(writeSpy).toHaveBeenCalledWith("UNCAUGHT ERROR " + - JSON.stringify(error)); - }); - }); - -// test respOrError has to wait -> ORM connection has to be mocked +test("utility.respOrErrorNoReinject sends generic errors as server errors", () => { + const { res, statSpy, sendSpy } = obtainResponse(); + const writeSpy = jest.spyOn(console, "log"); + const error = { msg: "some", error: { generic: true } }; + const send = { + success: false, + reason: config.apiErrors.serverError.reason, + }; + util.respOrErrorNoReinject(res, Promise.reject(error)).then(() => { + expect(statSpy).toHaveBeenCalledWith(config.apiErrors.serverError.http); + expect(sendSpy).toHaveBeenCalledWith(send); + expect(writeSpy).toHaveBeenCalledWith( + "UNCAUGHT ERROR " + JSON.stringify(error) + ); + }); +}); + +function setSessionKey(req: express.Request, key: string): void { + req.headers.authorization = config.global.authScheme + " " + key; +} + test("utility.respOrError sends responses with updated keys", async () => { - const {res, statSpy, sendSpy} = obtainResponse(); - const req = getMockReq(); - req.body.sessionkey = "key"; - - session_keyMock.changeSessionKey.mockReset(); - session_keyMock.changeSessionKey.mockImplementation((_, nw) => { - return Promise.resolve( - {session_key_id : 0, login_user_id : 0, session_key : nw}); - }); - - cryptoMock.createHash.mockReset(); - cryptoMock.createHash.mockImplementation(() => { - const h = mockDeep(); - h.update.mockImplementation(() => h); - h.digest.mockImplementation(() => 'abcd'); - return h; - }); - - const obj = {data : {"some" : "data"}, "sessionkey" : ""}; - const exp = {data : {"some" : "data"}, "sessionkey" : "abcd", success : true}; - - await expect(util.respOrError(req, res, Promise.resolve(obj))) - .resolves.not.toThrow(); - expect(sendSpy).toHaveBeenCalledWith(exp); - expect(statSpy).toHaveBeenCalledWith(200); + const { res, statSpy, sendSpy } = obtainResponse(); + const req = getMockReq(); + // req.body.sessionkey = "key"; + setSessionKey(req, "key"); + + session_keyMock.refreshKey.mockReset(); + session_keyMock.refreshKey.mockImplementation((k, nd) => { + return Promise.resolve({ + session_key_id: 0, + login_user_id: 0, + session_key: k, + valid_until: nd, + }); + }); + + cryptoMock.createHash.mockReset(); + cryptoMock.createHash.mockImplementation(() => { + const h = mockDeep(); + h.update.mockImplementation(() => h); + h.digest.mockImplementation(() => "abcd"); + return h; + }); + + const obj = { data: { some: "data" }, sessionkey: "" }; + const exp = { data: { some: "data" }, sessionkey: "", success: true }; + + await expect( + util.respOrError(req, res, Promise.resolve(obj)) + ).resolves.not.toThrow(); + expect(sendSpy).toHaveBeenCalledWith(exp); + expect(statSpy).toHaveBeenCalledWith(200); }); test("utility.redirect sends an HTTP 303", () => { - const {res, statSpy, sendSpy} = obtainResponse(); - const headerSpy = jest.spyOn(res, 'header'); - util.redirect(res, '/some/other/url').then(() => { - expect(statSpy).toHaveBeenCalledWith(303); - expect(sendSpy).toHaveBeenCalledTimes(1); // no args - expect(headerSpy).toHaveBeenCalledWith({Location : '/some/other/url'}); - }); + const { res, statSpy, sendSpy } = obtainResponse(); + const headerSpy = jest.spyOn(res, "header"); + util.redirect(res, "/some/other/url").then(() => { + expect(statSpy).toHaveBeenCalledWith(303); + expect(sendSpy).toHaveBeenCalledTimes(1); // no args + expect(headerSpy).toHaveBeenCalledWith({ Location: "/some/other/url" }); + }); }); test("utility.checkSessionKey works on valid session key", async () => { - session_keyMock.checkSessionKey.mockReset(); - session_keyMock.checkSessionKey.mockResolvedValue( - {login_user_id : 123456789}); - const obj = {sessionkey : "key"}; - const res = {data : {sessionkey : "key"}, userId : 123456789}; - - await expect(util.checkSessionKey(obj)).resolves.toStrictEqual(res); - expect(session_keyMock.checkSessionKey).toHaveBeenCalledTimes(1); - expect(session_keyMock.checkSessionKey).toHaveBeenCalledWith('key'); + login_userMock.getLoginUserById.mockReset(); + session_keyMock.checkSessionKey.mockReset(); + login_userMock.getLoginUserById.mockResolvedValue({ + login_user_id: 123456789, + person_id: 987654321, + password: "pass", + is_admin: true, + is_coach: false, + account_status: "ACTIVATED", + person: { + firstname: "Bob", + lastname: "Test", + email: "bob.test@mail.com", + github: "bob.test@github.com", + person_id: 987654321, + github_id: "46845", + }, + }); + session_keyMock.checkSessionKey.mockResolvedValue({ + login_user_id: 123456789, + }); + const obj = { sessionkey: "key" }; + const res = { + data: { sessionkey: "key" }, + userId: 123456789, + accountStatus: "ACTIVATED", + is_admin: true, + is_coach: false, + }; + + await expect(util.checkSessionKey(obj)).resolves.toStrictEqual(res); + expect(session_keyMock.checkSessionKey).toHaveBeenCalledTimes(1); + expect(session_keyMock.checkSessionKey).toHaveBeenCalledWith("key"); +}); + +test("utility.checkSessionKey fails on valid session key (pending)", async () => { + login_userMock.getLoginUserById.mockReset(); + session_keyMock.checkSessionKey.mockReset(); + login_userMock.getLoginUserById.mockResolvedValue({ + login_user_id: 123456789, + person_id: 987654321, + password: "pass", + is_admin: true, + is_coach: false, + account_status: "PENDING", + person: { + firstname: "Bob", + lastname: "Test", + email: "bob.test@mail.com", + github: "bob.test@github.com", + person_id: 987654321, + github_id: "46845", + }, + }); + session_keyMock.checkSessionKey.mockResolvedValue({ + login_user_id: 123456789, + }); + const obj = { sessionkey: "key" }; + + await expect(util.checkSessionKey(obj)).rejects.toStrictEqual( + errors.cookPendingAccount() + ); + expect(session_keyMock.checkSessionKey).toHaveBeenCalledTimes(1); + expect(session_keyMock.checkSessionKey).toHaveBeenCalledWith("key"); +}); + +test("utility.checkSessionKey works on valid session key (pending,false)", async () => { + login_userMock.getLoginUserById.mockReset(); + session_keyMock.checkSessionKey.mockReset(); + login_userMock.getLoginUserById.mockResolvedValue({ + login_user_id: 123456789, + person_id: 987654321, + password: "pass", + is_admin: true, + is_coach: false, + account_status: "PENDING", + person: { + firstname: "Bob", + lastname: "Test", + email: "bob.test@mail.com", + github: "bob.test@github.com", + person_id: 987654321, + github_id: "46845", + }, + }); + session_keyMock.checkSessionKey.mockResolvedValue({ + login_user_id: 123456789, + }); + const obj = { sessionkey: "key" }; + const res = { + data: { sessionkey: "key" }, + userId: 123456789, + accountStatus: "PENDING", + is_admin: true, + is_coach: false, + }; + + await expect(util.checkSessionKey(obj, false)).resolves.toStrictEqual(res); + expect(session_keyMock.checkSessionKey).toHaveBeenCalledTimes(1); + expect(session_keyMock.checkSessionKey).toHaveBeenCalledWith("key"); }); test("utility.checkSessionKey fails on invalid session key", async () => { - session_keyMock.checkSessionKey.mockReset(); - session_keyMock.checkSessionKey.mockRejectedValue(new Error()); + session_keyMock.checkSessionKey.mockReset(); + session_keyMock.checkSessionKey.mockRejectedValue(new Error()); - await expect(util.checkSessionKey({ - sessionkey : "key" - })).rejects.toStrictEqual(util.errors.cookUnauthenticated()); + await expect( + util.checkSessionKey({ + sessionkey: "key", + }) + ).rejects.toStrictEqual(util.errors.cookUnauthenticated()); - expect(session_keyMock.checkSessionKey).toHaveBeenCalledTimes(1); - expect(session_keyMock.checkSessionKey).toHaveBeenCalledWith("key"); + expect(session_keyMock.checkSessionKey).toHaveBeenCalledTimes(1); + expect(session_keyMock.checkSessionKey).toHaveBeenCalledWith("key"); }); -test("utility.isAdmin should succeed on valid keys, fail on invalid keys" + - "and fail on non-admin keys", - async () => { - session_keyMock.checkSessionKey.mockReset(); - session_keyMock.checkSessionKey.mockImplementation((key: string) => { - if (key == "key_1") - return Promise.resolve({login_user_id : 1}); - if (key == "key_2") - return Promise.resolve({login_user_id : 2}); - return Promise.reject(new Error()); - }); - - login_userMock.searchAllAdminLoginUsers.mockReset(); - login_userMock.searchAllAdminLoginUsers.mockImplementation( - (isAdmin: boolean) => { - if (!isAdmin) - return Promise.resolve([]); - return Promise.resolve([ { - login_user_id : 1, - person_id : -1, - password : "imapassword", - is_admin : true, - is_coach : false, - account_status : 'ACTIVATED', - person : { - lastname : "lastname", - firstname : "firstname", - github : "hiethub", - person_id : 0, - email : "email@mail.com" - }, - session_keys : [] - } ]); - }); - - // test 1: succesfull - await expect(util.isAdmin({ - sessionkey : "key_1" - })).resolves.toStrictEqual({data : {sessionkey : "key_1"}, userId : 1}); - // test 2: not an admin - await expect(util.isAdmin({ - sessionkey : "key_2" - })).rejects.toStrictEqual(util.errors.cookInsufficientRights()); - // test 3: invalid key - await expect(util.isAdmin({ - sessionkey : "key_3" - })).rejects.toStrictEqual(util.errors.cookUnauthenticated()); - - expect(session_keyMock.checkSessionKey).toHaveBeenCalledTimes(3); - expect(login_userMock.searchAllAdminLoginUsers).toHaveBeenCalledTimes(2); - }); +test( + "utility.isAdmin should succeed on valid keys, fail on invalid keys" + + "and fail on non-admin keys", + async () => { + session_keyMock.checkSessionKey.mockReset(); + session_keyMock.checkSessionKey.mockImplementation((key: string) => { + if (key == "key_1") return Promise.resolve({ login_user_id: 1 }); + if (key == "key_2") return Promise.resolve({ login_user_id: 2 }); + return Promise.reject(new Error()); + }); + + login_userMock.getLoginUserById.mockReset(); + login_userMock.getLoginUserById.mockImplementation( + (loginUserId: number) => { + if (loginUserId == 3) + return Promise.reject(errors.cookLockedRequest()); + if (loginUserId == 2) + return Promise.resolve({ + login_user_id: 2, + person_id: -2, + password: "pass", + is_admin: false, + is_coach: false, + account_status: "ACTIVATED", + person: { + firstname: "firstname", + lastname: "lastname", + email: "email@hotmail.com", + github: "hiethub", + person_id: 1, + github_id: "123", + }, + }); + return Promise.resolve({ + login_user_id: 1, + person_id: -1, + password: "imapassword", + is_admin: true, + is_coach: false, + account_status: "ACTIVATED", + person: { + firstname: "firstname", + lastname: "lastname", + email: "email@mail.com", + github: "hiethub", + person_id: 0, + github_id: "123456", + }, + }); + } + ); + + login_userMock.searchAllAdminLoginUsers.mockReset(); + login_userMock.searchAllAdminLoginUsers.mockImplementation( + (isAdmin: boolean) => { + if (!isAdmin) return Promise.resolve([]); + return Promise.resolve([ + { + login_user_id: 1, + person_id: -1, + password: "imapassword", + is_admin: true, + is_coach: false, + account_status: "ACTIVATED", + person: { + lastname: "lastname", + firstname: "firstname", + github: "hiethub", + person_id: 0, + email: "email@mail.com", + github_id: "123456", + }, + session_keys: [], + }, + ]); + } + ); + + // test 1: succesfull + await expect( + util.isAdmin({ + sessionkey: "key_1", + }) + ).resolves.toStrictEqual({ + data: { sessionkey: "key_1" }, + userId: 1, + accountStatus: "ACTIVATED", + is_admin: true, + is_coach: false, + }); + // test 2: not an admin + await expect( + util.isAdmin({ + sessionkey: "key_2", + }) + ).rejects.toStrictEqual(util.errors.cookInsufficientRights()); + // test 3: invalid key + await expect( + util.isAdmin({ + sessionkey: "key_3", + }) + ).rejects.toStrictEqual(util.errors.cookUnauthenticated()); + + expect(session_keyMock.checkSessionKey).toHaveBeenCalledTimes(3); + expect(login_userMock.searchAllAdminLoginUsers).toHaveBeenCalledTimes( + 2 + ); + } +); + +test("utility.isAdmin can catch errors from the DB", async () => { + session_keyMock.checkSessionKey.mockReset(); + session_keyMock.checkSessionKey.mockImplementation(() => + Promise.resolve({ login_user_id: 1 }) + ); + + login_userMock.searchAllAdminLoginUsers.mockReset(); + login_userMock.searchAllAdminLoginUsers.mockImplementation(() => + Promise.reject({}) + ); + + expect( + util.isAdmin({ + sessionkey: "key", + }) + ).rejects.toStrictEqual(util.errors.cookInsufficientRights()); +}); test("utility.refreshKey removes a key and replaces it", async () => { - session_keyMock.changeSessionKey.mockReset(); - session_keyMock.changeSessionKey.mockImplementation((_, nw) => { - return Promise.resolve( - {session_key_id : 0, login_user_id : 0, session_key : nw}); - }); - - cryptoMock.createHash.mockReset(); - cryptoMock.createHash.mockImplementation(() => { - const h = mockDeep(); - h.update.mockImplementation(() => h); - h.digest.mockImplementation(() => 'abcd'); - return h; - }); - - await expect(util.refreshKey('ab')).resolves.toBe('abcd'); - expect(session_keyMock.changeSessionKey).toHaveBeenCalledTimes(1); - expect(session_keyMock.changeSessionKey).toHaveBeenCalledWith('ab', 'abcd'); + session_keyMock.refreshKey.mockReset(); + + // mock Date.now() + const realDateNow = Date.now.bind(global.Date); + const dateNowStub = jest.fn(() => 1530518207007); + global.Date.now = dateNowStub; + + const futureDate = new Date(Date.now()); + futureDate.setDate(futureDate.getDate() + sessionKey.valid_period); + session_keyMock.refreshKey.mockImplementation((old) => { + return Promise.resolve({ + session_key_id: 0, + login_user_id: 0, + session_key: old, + valid_until: futureDate, + }); + }); + + cryptoMock.createHash.mockReset(); + cryptoMock.createHash.mockImplementation(() => { + const h = mockDeep(); + h.update.mockImplementation(() => h); + h.digest.mockImplementation(() => "abcd"); + return h; + }); + + await expect(util.refreshKey("ab")).resolves.toBe("ab"); + expect(session_keyMock.refreshKey).toHaveBeenCalledTimes(1); + expect(session_keyMock.refreshKey).toHaveBeenCalledWith("ab", futureDate); + global.Date.now = realDateNow; }); test("utility.refreshAndInjectKey refreshes a key and injects it", async () => { - session_keyMock.changeSessionKey.mockReset(); - session_keyMock.changeSessionKey.mockImplementation((_, nw) => { - return Promise.resolve( - {session_key_id : 0, login_user_id : 0, session_key : nw}); - }); - - cryptoMock.createHash.mockReset(); - cryptoMock.createHash.mockImplementation(() => { - const h = mockDeep(); - h.update.mockImplementation(() => h); - h.digest.mockImplementation(() => 'abcd'); - return h; - }); - - const initial = { - sessionkey : '', - data : {id : 5, name : 'jef', email : 'jef@jef.com'} - }; - - const result = { - sessionkey : 'abcd', - data : {id : 5, name : 'jef', email : 'jef@jef.com'} - }; - - expect(util.refreshAndInjectKey('ab', initial)) - .resolves.toStrictEqual(result); - expect(session_keyMock.changeSessionKey).toHaveBeenCalledTimes(1); - expect(session_keyMock.changeSessionKey).toHaveBeenCalledWith('ab', 'abcd'); + session_keyMock.refreshKey.mockReset(); + + // mock Date.now() + const realDateNow = Date.now.bind(global.Date); + const dateNowStub = jest.fn(() => 1530518207007); + global.Date.now = dateNowStub; + + const futureDate = new Date(Date.now()); + futureDate.setDate(futureDate.getDate() + sessionKey.valid_period); + session_keyMock.refreshKey.mockImplementation((k) => { + return Promise.resolve({ + session_key_id: 0, + login_user_id: 0, + session_key: k, + valid_until: futureDate, + }); + }); + + cryptoMock.createHash.mockReset(); + cryptoMock.createHash.mockImplementation(() => { + const h = mockDeep(); + h.update.mockImplementation(() => h); + h.digest.mockImplementation(() => "abcd"); + return h; + }); + + const initial = { + sessionkey: "", + data: { id: 5, name: "jef", email: "jef@jef.com" }, + }; + + const result = { + sessionkey: "", + data: { id: 5, name: "jef", email: "jef@jef.com" }, + }; + + expect(util.refreshAndInjectKey("ab", initial)).resolves.toStrictEqual( + result + ); + expect(session_keyMock.refreshKey).toHaveBeenCalledTimes(1); + expect(session_keyMock.refreshKey).toHaveBeenCalledWith("ab", futureDate); + + global.Date.now = realDateNow; +}); + +test("utility.getSessionKey fetches session key or crashes", () => { + const r = getMockReq(); + const err = "No session key - you should check for the session key first."; + setSessionKey(r, "some_key"); + expect(util.getSessionKey(r)).toBe("some_key"); + + const f1 = getMockReq(); + f1.headers.authorization = "some_key"; + expect(() => util.getSessionKey(f1)).toThrow(err); + + const f2 = getMockReq(); + expect(() => util.getSessionKey(f2)).toThrow(err); +}); + +test("utility.getOrDefault returns the correct values", () => { + expect(util.getOrDefault(null, 1)).toBe(1); + expect(util.getOrDefault(undefined, "hello")).toBe("hello"); + expect(util.getOrDefault(27, 59)).toBe(27); +}); + +test("utility.getOrReject behaves correctly", () => { + return Promise.all([ + expect(util.getOrReject("hello")).resolves.toBe("hello"), + expect(util.getOrReject(null)).rejects.toBe( + util.errors.cookNoDataError() + ), + expect(util.getOrReject(undefined)).rejects.toBe( + util.errors.cookNoDataError() + ), + ]); +}); + +test("utility.addInvalidVerbs adds callbacks", () => { + const router = getMockRouter(); + util.addInvalidVerbs(router, "/test"); + + const prm = ["get", "delete", "post"].map((x) => { + const req = getMockReq(); + req.method = x; + req.path = "/test"; + const res = getMockRes(); + Promise.resolve() + .then(() => router(req, res.res)) + .then(() => { + const err = util.errors.cookInvalidVerb(req); + expect(res.res.status).toHaveBeenCalledWith(err.http); + expect(res.res.send).toHaveBeenCalledWith({ + success: false, + reason: err.reason, + }); + }); + }); + + return Promise.all([prm]); +}); + +test("utility.addAllInvalidVerbs adds multiple callbacks", () => { + const router = getMockRouter(); + const eps = ["/", "/test", "/test2"]; + util.addAllInvalidVerbs(router, eps); + + const prm = ["get", "delete", "post"] + .map((v) => + eps.map((e) => { + const req = getMockReq(); + const res = getMockRes().res; + req.method = v; + req.path = e; + + const err = util.errors.cookInvalidVerb(req); + Promise.resolve() + .then(() => router(req, res)) + .then(() => { + expect(res.status).toHaveBeenCalledWith(err.http); + expect(res.send).toHaveBeenCalledWith({ + success: false, + reason: err.reason, + }); + }); + }) + ) + .flat(); + return Promise.all(prm); +}); + +test("utility.route installs exactly one route", async () => { + const router = getMockRouter(); + const cb = () => { + console.log("RETURNING"); + return Promise.resolve({ sessionkey: "abcd", data: {} }); + }; + const path = "/"; + const verb = "get"; + + util.route(router, verb, path, cb); + + // correct + const req1 = getMockReq(); + const res1 = getMockRes().res; + req1.path = path; + req1.method = verb; + + // shouldn't throw + await Promise.resolve() + .then(() => router(req1, res1)) + .then(() => { + expect(res1.status).toHaveBeenCalledWith(500); + }); + + // incorrect ep + const req2 = getMockReq(); + const res2 = getMockRes().res; + req2.path = "/test"; + req2.method = "get"; + expect( + Promise.resolve().then(() => router(req2, res2)) + ).rejects.toStrictEqual(getInvalidEndpointError("/test")); + + // incorrect verb/ep + const req3 = getMockReq(); + const res3 = getMockRes().res; + req3.path = path; + req3.method = "post"; + expect( + Promise.resolve().then(() => router(req3, res3)) + ).rejects.toStrictEqual(getInvalidVerbEndpointError("post", "/")); +}); + +test("utility.routeKeyOnly installs exactly one route", async () => { + const router = getMockRouter(); + const cb = () => { + console.log("RETURNING"); + return Promise.resolve({ sessionkey: "abcd", data: {} }); + }; + const path = "/"; + const verb = "get"; + + util.routeKeyOnly(router, verb, path, cb); + + // correct + const req1 = getMockReq(); + const res1 = getMockRes().res; + req1.path = path; + req1.method = verb; + + // shouldn't throw + await Promise.resolve() + .then(() => router(req1, res1)) + .then(() => { + expect(res1.status).toHaveBeenCalledWith(200); + }); + + // incorrect ep + const req2 = getMockReq(); + const res2 = getMockRes().res; + req2.path = "/test"; + req2.method = "get"; + expect( + Promise.resolve().then(() => router(req2, res2)) + ).rejects.toStrictEqual(getInvalidEndpointError("/test")); + + // incorrect verb/ep + const req3 = getMockReq(); + const res3 = getMockRes().res; + req3.path = path; + req3.method = "post"; + expect( + Promise.resolve().then(() => router(req3, res3)) + ).rejects.toStrictEqual(getInvalidVerbEndpointError("post", "/")); +}); + +test("utility.isValidID checks IDs", async () => { + studentMock.getStudent.mockReset(); + projectMock.getProjectById.mockReset(); + + studentMock.getStudent.mockImplementation((id) => { + if (id == 1) { + return Promise.resolve({ + student_id: 1, + person_id: 5, + gender: "Apache Attack Helicopter", + pronouns: "vroom/vroom/vroom", + phone_number: "0469 420 420", + nickname: "jeff", + alumni: false, + person: { + person_id: 5, + email: null, + github: null, + github_id: null, + firstname: "jeffrey", + lastname: "jan", + }, + }); + } + return Promise.resolve(null); + }); + + projectMock.getProjectById.mockImplementation((id) => { + if (id == 1) { + return Promise.resolve({ + project_id: 5, + name: "Jeff's Battle Bots", + osoc_id: 516645164126546, + partner: "Jeff Himself", + description: "Nothing special and definitely nothing illegal", + start_date: new Date(Date.now()), + end_date: new Date(Date.now()), + positions: 42069, + }); + } + return Promise.resolve(null); + }); + + const err = util.errors.cookInvalidID(); + + return Promise.all([ + expect( + util.isValidID({ id: 1, sessionkey: "" }, "student") + ).resolves.toStrictEqual({ id: 1, sessionkey: "" }), + expect( + util.isValidID({ id: 1, sessionkey: "" }, "project") + ).resolves.toStrictEqual({ id: 1, sessionkey: "" }), + expect( + util.isValidID({ id: 2, sessionkey: "" }, "student") + ).rejects.toStrictEqual(err), + expect( + util.isValidID({ id: 2, sessionkey: "" }, "project") + ).rejects.toStrictEqual(err), + ]); +}); + +test("utility.setupRedirect sets up a single redirect", async () => { + const router = getMockRouter(); + util.setupRedirect(router, ""); + + // valid + const req1 = getMockReq(); + const res1 = getMockRes().res; + req1.path = "/"; + req1.method = "get"; + // Promise.resolve().then(() => router(req1, res1)).then(() => { + await expectRouter(router, "/", "get", req1, res1); + expect(res1.status).toHaveBeenCalledWith(303); + expect(res1.header).toHaveBeenCalledWith({ Location: "/api-osoc/all" }); + expect(res1.send).toHaveBeenCalled(); + // }); + + // incorrect ep + const req2 = getMockReq(); + const res2 = getMockRes().res; + req2.path = "/test"; + req2.method = "get"; + await expectRouterThrow( + router, + "/test", + "get", + req2, + res2, + getInvalidEndpointError("/test") + ); + + // incorrect verb/ep + const req3 = getMockReq(); + const res3 = getMockRes().res; + req3.path = "/"; + req3.method = "post"; + await expectRouterThrow( + router, + "/", + "post", + req3, + res3, + getInvalidVerbEndpointError("post", "/") + ); +}); + +test("utility.mutable should check if a user is mutable", async () => { + expect(util.mutable("str", config.global.defaultUserId)).rejects.toBe( + errors.cookInvalidID() + ); + expect(util.mutable("str", 845321)).resolves.toBe("str"); }); diff --git a/backend/tools/curly.sh b/backend/tools/curly.sh index 7da45433..d5ba9c90 100755 --- a/backend/tools/curly.sh +++ b/backend/tools/curly.sh @@ -22,6 +22,15 @@ function main() { printf 'HTTP Verb? ' read verb + printf 'Session key (leave blank to pass no session key)? ' + read skey + skey=`printf "$skey" | xargs` + if [ -z ${skey//[:blank:]} ]; then + skey='' + else + skey="-H \"Authorization: auth/osoc2 $skey\"" + fi + echo 'You can now pass arguments. Use CTRL-D after you entered the last key-value pair.' args='' printf 'Key? ' @@ -30,6 +39,10 @@ function main() { read value if [[ $args = '' ]]; then args="\"$key\": \"$value\"" + elif [[ ${value:0:1} = '{' ]]; then + # send JSON objects correctly + args="$args, \"$key\": $value" + echo "Will send '$value' as JSON-object" else args="$args, \"$key\": \"$value\"" fi @@ -38,7 +51,7 @@ function main() { echo '' args="'{ $args }'" - echo "Curl command: \`curl -X \"$verb\" \"http://localhost:4096$api$ep\" -i -d $args -H \"Content-Type: application/json\"\`" + echo "Curl command: \`curl -X \"$verb\" \"http://localhost:4096$api$ep\" -i -d $args $skey -H \"Content-Type: application/json\"\`" printf 'Send this curl command (yes/y/no/n/maybe)? ' read ans while [ $ans != 'yes' ] && [ $ans != 'no' ] && [ $ans != 'maybe' ] && [ $ans != 'y' ] && [ $ans != 'n' ]; do @@ -47,12 +60,12 @@ function main() { done if [ $ans = 'y' ] || [ $ans = 'yes' ]; then - /bin/sh -c "curl -X \"$verb\" \"http://localhost:4096$api$ep\" -i -d $args -H \"Content-Type: application/json\"" + /bin/sh -c "curl -X \"$verb\" \"http://localhost:4096$api$ep\" -i -d $args $skey -H \"Content-Type: application/json\"" elif [ $ans = 'maybe' ]; then printf 'Output file? ' read f echo "#!/bin/sh" >f - echo curl -X "$verb" "http://localhost:4096$api$ep" -i -d $args -H "Content-Type: application/json" >>f + echo curl -X "$verb" "http://localhost:4096$api$ep" -i -d $args $skey -H "Content-Type: application/json" >>f fi } diff --git a/backend/tsconfig.json b/backend/tsconfig.json index 663d8ce8..590c99a6 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -6,7 +6,7 @@ "resolveJsonModule": true }, "typedocOptions": { - "entryPoints": ["routes/admin.ts", "routes/coach.ts", "routes/form.ts", "routes/login.ts", "routes/project.ts", "routes/student.ts","orm_functions/applied_role.ts", "orm_functions/job_application.ts", "orm_functions/osoc.ts", "orm_functions/role.ts", "orm_functions/attachment.ts", "orm_functions/job_application_skill.ts", "orm_functions/person.ts", "orm_functions/session_key.ts", "orm_functions/contract.ts", "orm_functions/language.ts", "orm_functions/project.ts", "orm_functions/student.ts", "orm_functions/evaluation.ts", "orm_functions/login_user.ts", "orm_functions/project_role.ts", "orm_functions/general_purpose.ts", "orm_functions/orm_types.ts", "orm_functions/project_user.ts"], + "entryPoints": ["orm_functions/applied_role.ts","orm_functions/attachment.ts","orm_functions/contract.ts","orm_functions/evaluation.ts","orm_functions/files","orm_functions/general_purpose.ts","orm_functions/job_application.ts","orm_functions/job_application_skill.ts","orm_functions/language.ts","orm_functions/login_user.ts","orm_functions/orm_types.ts","orm_functions/osoc.ts","orm_functions/password_reset.ts","orm_functions/person.ts","orm_functions/project.ts","orm_functions/project_role.ts","orm_functions/project_user.ts","orm_functions/role.ts","orm_functions/session_key.ts","orm_functions/student.ts","orm_functions/template.ts","routes/admin.ts","routes/coach.ts","routes/files","routes/followup.ts","routes/form.ts","routes/form_keys.json","routes/github.ts","routes/login.ts","routes/project.ts","routes/reset.ts","routes/role.ts","routes/session_key.json","routes/student.ts","routes/template.ts","routes/user.ts","routes/verify.ts"], "out": "docs" } } diff --git a/backend/types.ts b/backend/types.ts index a22d59f2..191c4924 100644 --- a/backend/types.ts +++ b/backend/types.ts @@ -1,18 +1,23 @@ -import {account_status_enum} from "@prisma/client"; -import express from 'express'; +import { + account_status_enum, + email_status_enum, + type_enum, +} from "@prisma/client"; +import express from "express"; +import { FilterSort } from "./orm_functions/orm_types"; /** * Interface for API errors. Specifies an HTTP status code and error reason. */ export interface ApiError { - /** - * The HTTP status code. - */ - http: number; - /** - * The error reason. - */ - reason: string; + /** + * The HTTP status code. + */ + http: number; + /** + * The error reason. + */ + reason: string; } /** @@ -21,41 +26,53 @@ export interface ApiError { * file. */ export interface Errors { - /** - * Cooks up an Invalid ID response. - */ - cookInvalidID: () => ApiError; - /** - * Cooks up an Argument Error response. - */ - cookArgumentError: () => ApiError; - /** - * Cooks up an Unauthenticated Request response. - */ - cookUnauthenticated: () => ApiError; - /** - * Cooks up an Insufficient Rights response. - */ - cookInsufficientRights: () => ApiError; - /** - * Cooks up a Non-existent Endpoint response. - * @param url The requested endpoint URL. - */ - cookNonExistent: (_: string) => ApiError; - /** - * Cooks up an Invalid HTTP-verb response - * @param req The request with the invalid verb. - */ - cookInvalidVerb: (_: express.Request) => ApiError; - /** - * Cooks up an Invalid MIME Response Type Requested response. - * @param mime The requested invalid MIME type. - */ - cookNonJSON: (_: string) => ApiError; - /** - * Cooks up an Internal Server Error response. - */ - cookServerError: () => ApiError; + /** + * Cooks up an Invalid ID response. + */ + cookInvalidID: () => ApiError; + /** + * Cooks up an Argument Error response. + */ + cookArgumentError: () => ApiError; + /** + * Cooks up an Unauthenticated Request response. + */ + cookUnauthenticated: () => ApiError; + /** + * Cooks up an Insufficient Rights response. + */ + cookInsufficientRights: () => ApiError; + /** + * Cooks up a locked request response. + */ + cookLockedRequest: () => ApiError; + /** + * Cooks up a Non-existent Endpoint response. + * @param url The requested endpoint URL. + */ + cookNonExistent: (_: string) => ApiError; + /** + * Cooks up an Invalid HTTP-verb response + * @param req The request with the invalid verb. + */ + cookInvalidVerb: (_: express.Request) => ApiError; + /** + * Cooks up an Invalid MIME Response Type Requested response. + * @param mime The requested invalid MIME type. + */ + cookNonJSON: (_: string) => ApiError; + /** + * Cooks up an Internal Server Error response. + */ + cookServerError: () => ApiError; + /** + * Cooks up a No Data Error response. + */ + cookNoDataError: () => ApiError; + /** + * Cooks up a Pending Account Error response. + */ + cookPendingAccount: () => ApiError; } /** @@ -63,446 +80,910 @@ export interface Errors { * {@link Responses}. */ export namespace InternalTypes { -/** - * A session key is a string. - */ -export type SessionKey = string; - -/** - * Either yes, maybe or no. This is the enumeration type for student suggests. - */ -export type Suggestion = "YES"|"MAYBE"|"NO"; - -/** - * Represents a partial type response. Usually these will only contain a - * suggestion type, the name and id of the sender and the reason why this - * suggestion exists. - */ -export interface SuggestionInfo { - /** - * The suggestion. - */ - suggestion: Suggestion; - /** - * The sender of the suggestion. - */ - sender: IdName; - /** - * The reason why this suggestion exists. - */ - reason: string; + import FormAttachmentResponse = Responses.FormAttachmentResponse; + /** + * A session key is a string. + */ + export type SessionKey = string; + + /** + * Either yes, maybe or no. This is the enumeration type for student suggests. + */ + export type Suggestion = "YES" | "MAYBE" | "NO"; + + export interface SuggestionInfo {} + + /** + * Represents a response that only contains an ID. + */ + export interface IdOnly { + /** + * The ID. + */ + id: number; + } + + /** + * Represents a partial type response. Usually these will only contain a name + * and an ID. + */ + export interface IdName extends IdOnly { + /** + * The name. + */ + name: string; + } + + /** + * Represents a person, with all associated data. + */ + export interface Person { + /** + * The person id. + */ + person_id: number; + /** + * The firstname of this person. + */ + firstname: string; + /** + * The lastname of this person. + */ + lastname: string; + /** + * The email of this person. + */ + email?: string; + } + + /** + * Represents a form-person, with all associated data. + */ + export interface FormPerson { + /** + * The firstname of this person. + */ + birthName: string; + /** + * The lastname of this person. + */ + lastName: string; + /** + * The email of this person. + */ + email: string; + } + + /** + * Represents a student, with all associated data. Does not correspond to a + * student in the database. + */ + export interface Student {} + + /** + * Represents a form-student, with all associated data. + */ + export interface FormStudent { + /** + * The firstname of this person. + */ + pronouns: string | null; + /** + * The lastname of this person. + */ + gender: string; + /** + * The email of this person. + */ + phoneNumber: string; + /** + * The email of this person. + */ + nickname: string | null; + /** + * The email of this person. + */ + alumni: boolean; + } + + /** + * Represents a form-jobApplication, with all associated data. + */ + export interface FormJobApplication { + /** + * The responsibilities of this person. + */ + responsibilities: string | null; + /** + * The fun fact of this person. + */ + funFact: string; + /** + * The volunteer info of this person. + */ + volunteerInfo: string; + /** + * The check if this person wants to be a studentCoach + */ + studentCoach: boolean | null; + /** + * The email of this person. + */ + osocId: number; + /** + * The email of this person. + */ + educations: string[]; + /** + * The email of this person. + */ + educationLevel: string; + /** + * The email of this person. + */ + educationDuration: number | null; + /** + * The email of this person. + */ + educationYear: string | null; + /** + * The email of this person. + */ + educationInstitute: string | null; + /** + * The email of this person. + */ + emailStatus: email_status_enum; + /** + * The email of this person. + */ + createdAt: string; + } + + /** + * Represents a form-attachment, with all associated data. + */ + export interface FormAttachment { + /** + * The responsibilities of this person. + */ + cv_links: FormAttachmentResponse; + /** + * The fun fact of this person. + */ + portfolio_links: FormAttachmentResponse; + /** + * The volunteer info of this person. + */ + motivations: FormAttachmentResponse; + } + + /** + * Represents a form-jobApplicationSkill, with all associated data. + */ + export interface FormJobApplicationSkill { + /** + * The responsibilities of this person. + */ + most_fluent_language: string; + /** + * The fun fact of this person. + */ + english_level: number; + /** + * The volunteer info of this person. + */ + best_skill: string; + } + + /** + * Represents form-roles, with all associated data. + */ + export interface FormRoles { + /** + * The roles of this person. + */ + roles: string[]; + } + + /** + * Represents a user, with all associated data. + */ + export interface User {} + + /** + * Represents an osoc edition, with all associated data. + */ + export interface OsocEdition {} + + /** + * Represents a check of the key, holds the key aswell as boolean value. + */ + export interface CheckKey {} + + /** + * Represents a coach, with all associated data. + */ + export interface Coach { + person_data: { id: number; name: string }; + coach: boolean; + admin: boolean; + activated: string; + } + + /** + * Represents a coach request response. Usually these will only contain an id, + * name and email. + */ + export interface CoachRequest { + /** + * The id. + */ + id: number; + /** + * The name of the coach. + */ + name: string; + /** + * The email. + */ + email: string; + } + + /** + * Represents an admin, with all associated data. + */ + export interface Admin {} + + /** + * Represents a project, with all associated data. + */ + export interface Project { + id: number; + name: string; + partner: string; + start_date: string; + end_date: string; + positions: number; + } + + /** + * Represents a project, with all associated data. + */ + export interface ProjectFilter {} + + /** + * Represents the drafted students of a project. Usually these will only + * contain an id, name and list of students. + */ + export interface ProjectDraftedStudents { + /** + * The id of the project. + */ + id: number; + /** + * The name of the project. + */ + name: string; + /** + * The students. + */ + students: Student[]; + } + + /** + * Represents the drafted students of a project. Usually these will only + * contain an id, name and list of students. + */ + export interface ModProjectStudent { + /** + * Was the student drafted or not. + */ + drafted: boolean; + /** + * The roles of the student. + */ + role: string; + } + + /** + * Represents a conflict. These contain the student and a list of projects they + * are in. + */ + export interface Conflict { + /** + * The student's ID. + */ + student: number; + /** + * The projects the student is in. + */ + projects: { + /** + * The project's ID. + */ + id: number; + /** + * The project's name. + */ + name: string; + }[]; + } + + export interface ShortTemplate { + id: number; + owner: number; + name: string; + } + + export interface Template extends ShortTemplate { + content: string; + } + + export interface FollowupStatus { + student: number; + status: email_status_enum; + application: number; + } } -/** - * Represents a partial type response. Usually these will only contain a - * suggestion type and a number of occurrences. - */ -export interface SuggestionCount { - /** - * The suggestion. - */ - suggestion: Suggestion; - /** - * The number of occurrences for this kind of suggestion. - */ - occurrences: number; -} - -/** - * Represents a response that only contains an ID. - */ -export interface IdOnly { - /** - * The ID. - */ - id: number; -} - -/** - * Represents a partial type response. Usually these will only contain a name - * and an ID. - */ -export interface IdName extends IdOnly { - /** - * The name. - */ - name: string; +export interface WithUserID { + userId: number; + data: T; + accountStatus: account_status_enum; + is_admin: boolean; + is_coach: boolean; } /** - * Represents a person, with all associated data. + * Namespace for response types. Most of the data types come from the namespace + * {@link InternalTypes}. The success boolean is added when sending. */ -export interface Person { - /** - * The person id. - */ - person_id: number; - /** - * The firstname of this person. - */ - firstname: string; - /** - * The lastname of this person. - */ - lastname: string; - /** - * The email of this person. - */ - email?: string; +export namespace Responses { + /** + * A response consisting of only a session key. + */ + export interface Key { + sessionkey: InternalTypes.SessionKey; + } + + /** + * A response consisting of only an id. + */ + export interface Id { + id: number; + } + + /** + * A response consisting of and id and boolean. + */ + export interface Id_alumni { + id: number; + hasAlreadyTakenPart: boolean; + } + + /** + * A login response contains of a key and a boolean determining whether a user + * is an admin. + */ + export interface Login extends Key { + is_admin: boolean; + is_coach: boolean; + } + + /** + * A partial student response is the keyed combination of their id and name. + */ + export interface PartialStudent extends InternalTypes.IdName {} + + /** + * A partial user response is the combination of their id and name. + */ + export interface PartialUser extends InternalTypes.IdName {} + + /** + * A partial user response is the combination of their id and name. + */ + export interface PartialCoach extends PartialUser {} + + /** + * A student response is the keyed version of the student and their associated + * data. + */ + export interface Student extends InternalTypes.Student {} + + /** + * A form-student. + */ + export interface FormStudent extends InternalTypes.FormStudent {} + + /** + * A form-jobApplication. + */ + export interface FormJobApplication + extends InternalTypes.FormJobApplication {} + + /** + * A form-attachment. + */ + export interface FormAttachment extends InternalTypes.FormAttachment {} + + /** + * A user response is the keyed version of the user and their associated + * data. + */ + export interface User extends InternalTypes.User {} + + /** + * A studentList response is the keyed version of a list of students and their + * associated data. + */ + export interface StudentList { + data: InternalTypes.Student[]; + } + + /** + * + */ + export interface UserList { + data: InternalTypes.User[]; + } + + /** + * + */ + export interface OsocEditionList { + data: InternalTypes.OsocEdition[]; + } + + /** + * A osoc edition response is the keyed version of the osoc edition and their associated + * data. + */ + export interface OsocEdition extends InternalTypes.OsocEdition {} + + /** + * + */ + export interface FormAttachmentResponse { + data: string[]; + types: type_enum[]; + } + + /** + * + */ + export interface VerifyKey extends InternalTypes.CheckKey {} + + /** + * A student list response is the keyed version of an array of partial + * students. + */ + export interface IdNameList { + data: InternalTypes.IdName[]; + } + + /** + * A student response is the keyed version of the student and their associated + * data. + */ + export interface SuggestionInfo { + data: InternalTypes.SuggestionInfo[]; + } + + /** + * An empty response. Doesn't hold a session key. + */ + export interface Empty {} + + /** + * A coach response is the keyed version of the coach and their associated + * data. + */ + export interface Coach extends InternalTypes.Coach {} + + /** + * A coach list response is the keyed version of a list of coaches + */ + export interface CoachList { + data: InternalTypes.Coach[]; + } + + /** + * A person response is the keyed version of the person and their associated + * data. + */ + export interface Person extends InternalTypes.Person {} + + /** + * A form-person. + */ + export interface FormPerson extends InternalTypes.FormPerson {} + + /** + * A form-jobApplicationSkill. + */ + export interface FormJobApplicationSkill + extends InternalTypes.FormJobApplicationSkill {} + + /** + * form roles. + */ + export interface FormRoles extends InternalTypes.FormRoles {} + + /** + * An admin response is the keyed version of the admin and their associated + * data. + */ + export interface Admin extends InternalTypes.Admin {} + + /** + * An adminList response is the keyed version of a list of admins and their + * associated data. + */ + export interface AdminList { + data: InternalTypes.Admin[]; + } + + /** + * A project response is the keyed version of the project and their associated + * data. + */ + export interface Project extends InternalTypes.Project {} + + /** + * A project list response is the keyed version of a list of projects + */ + export interface ProjectList { + data: InternalTypes.Project[]; + } + + /** + * A project filter list is a list of projects + */ + export interface ProjectFilterList { + data: InternalTypes.ProjectFilter; + } + + /** + * An admin list response is the keyed version of the list of admins. + */ + export interface AdminList { + data: InternalTypes.Admin[]; + } + + /** + * A project drafted students response is the keyed version of the students and + * the associated data of the project. + */ + export interface ProjectDraftedStudents + extends InternalTypes.ProjectDraftedStudents {} + + /** + * A project drafted students response is the keyed version of the students and + * the associated data of the project. + */ + export interface ModProjectStudent + extends InternalTypes.ModProjectStudent {} + + /** + * A studentList response is the keyed version of a list of students and their + * associated data. + */ + export interface StudentList { + data: InternalTypes.Student[]; + } + + /** + * An EvaluationCoach response. + */ + export interface EvaluationCoach { + evaluation_id: number; + senderFirstname: string; + senderLastname: string; + reason: string | null; + decision: InternalTypes.Suggestion; + isFinal: boolean; + } + + /** + * A conflictList response is the keyed version of a list of conflicts. + */ + export interface ConflictList { + data: InternalTypes.Conflict[]; + } + + export interface TemplateList { + data: InternalTypes.ShortTemplate[]; + } + + export interface Template extends InternalTypes.Template {} + + export interface SingleFollowup extends InternalTypes.FollowupStatus {} + + export interface FollowupList { + data: InternalTypes.FollowupStatus[]; + } + + /** + * @deprecated Either an API Error or a data value. Is deprecated in favor of + * rejecting with API error or resolving with data ({@link Promise}). + */ + export type OrError = ApiError | T; + + /** + * An API response is one of the previous response types. + * @deprecated Not up to date + */ + export type ApiResponse = + | Empty + | Key + | PartialStudent + | IdNameList + | ConflictList; + + /** + * Either an error while parsing the form or a data value. + */ + export interface FormResponse { + /** + * The data. + */ + data: T | null; + } } -/** - * Represents a student, with all associated data. Does not correspond to a - * student in the database. - */ -export interface Student {} - -/** - * Represents a coach, with all associated data. - */ -export interface Coach { - person_data: {id: number, name: string}; - coach: boolean; - admin: boolean; - activated: string; +export namespace Requests { + export interface Login { + name: string; + pass: string; + } + + /** + * To log in with GitHub, we require your GitHub login and username/alias. + */ + export interface GHLogin { + login: string; + name: string; + id: string; + } + + export interface KeyRequest { + sessionkey: InternalTypes.SessionKey; + } + + export interface IdRequest extends KeyRequest { + id: number; + } + + export interface YearId extends IdRequest { + year?: number; + } + + export interface AccountAcceptance extends IdRequest { + is_admin: boolean; + is_coach: boolean; + } + + export interface UserPwd extends KeyRequest { + pass?: { newpass: string; oldpass: string }; + name?: string; + } + + export interface StudentFilter extends KeyRequest {} + + export interface UserFilter extends KeyRequest { + sessionkey: string; + nameFilter?: string; + emailFilter?: string; + statusFilter?: account_status_enum; + nameSort?: FilterSort; + emailSort?: FilterSort; + isCoachFilter?: boolean; + isAdminFilter?: boolean; + } + + export interface OsocFilter extends KeyRequest {} + + export interface OsocEdition extends KeyRequest { + year: number; + } + + export interface UpdateStudent extends IdRequest { + emailOrGithub?: string; + firstName?: string; + lastName?: string; + gender?: string; + pronouns?: string; + nickname?: string; + alumni?: boolean; + phone?: string; + education?: { + level?: string; + duration?: number; + year?: number; + institute?: string; + }; + } + + export interface Suggest extends IdRequest { + suggestion: InternalTypes.Suggestion; + reason?: string; + } + + export interface Confirm extends IdRequest { + reply?: InternalTypes.Suggestion; + } + + export interface UpdateLoginUser extends IdRequest { + isAdmin: boolean; + isCoach: boolean; + pass?: string; + accountStatus: account_status_enum; + } + + export interface UserRequest { + firstName: string; + lastName: string; + email: string; + pass: string; + } + + export interface Project extends KeyRequest { + osocId: number; + name: string; + partner: string; + start: Date; + end: Date; + positions: number; + } + + export interface ModProject extends IdRequest { + name?: string; + partner?: string; + start?: Date; + end?: Date; + positions?: number; + osocId?: number; + } + + export interface ProjectFilter extends KeyRequest {} + + export interface Draft extends IdRequest { + studentId: number; + role: string; + } + + export interface Followup extends IdRequest { + type: FollowupType; + } + + export interface Template extends KeyRequest { + name: string; + subject?: string; + cc?: string; + content: string; + } + + export interface ModTemplate extends IdRequest { + name?: string; + desc?: string; + subject?: string; + cc?: string; + content?: string; + } + + export interface Form { + createdAt?: string; + data: DataForm; + } + + export interface Role extends KeyRequest { + name: string; + } + + export interface DataForm { + fields: Array; + } + + export interface Question { + key: string; + value: string | string[] | boolean | number | FormValues[] | null; + options?: Array