diff --git a/server/package.json b/server/package.json index 728f81a6b8e972ed5ea97923dfe1bcba01de5e10..d218ee220830c459d380bac3f0d560bfe57c594c 100644 --- a/server/package.json +++ b/server/package.json @@ -33,6 +33,7 @@ "@types/jest": "^26.0.22", "@types/jsonwebtoken": "^8.5.1", "@types/node": "^14.14.37", + "@types/pg": "^8.6.0", "@types/sharp": "^0.28.0", "@types/supertest": "^2.0.11", "@types/uuid": "^8.3.0", diff --git a/server/src/database.ts b/server/src/database.ts index e4c44713715ffa277a787df4b01a313731ca835c..31ac46a3d694a875d41b66ee7b0701a55c2dd7b1 100644 --- a/server/src/database.ts +++ b/server/src/database.ts @@ -4,10 +4,15 @@ import knex from 'knex'; import { environment } from './config'; import config from './knexconfig'; +import pg from 'pg'; +pg.types.setTypeParser(20, parseInt) +pg.types.setTypeParser(1700, parseInt) + export const database = knex(config[environment]); -// Only after this promise resolves is the migration finished -export const ready = database.migrate.latest(); +export function migrate() { + return database.migrate.latest(); +} export function close() { return database.destroy(); diff --git a/server/src/index.ts b/server/src/index.ts index 8d53d6cecadaa2ba8462d814ad496cc4c8b42479..d9ea92232ddf28b66fcd5a8ce09919442be093a7 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -1,8 +1,12 @@ import express from 'express'; + +import { migrate } from './database'; import { api } from './api'; import { port, web_serve } from './config'; +migrate(); + const app = express(); if (web_serve) { diff --git a/server/src/setupTests.ts b/server/src/setupTests.ts index 52ee95a8bef604bb472ed206790f1fe4bb8f9cd6..6418b7a9c033be3f8742384168d47541ac4bfc70 100644 --- a/server/src/setupTests.ts +++ b/server/src/setupTests.ts @@ -1,7 +1,7 @@ import { env } from 'process'; -import { database, ready, close } from './database'; +import { database, migrate, close } from './database'; async function loadTestData() { await database('users') @@ -83,7 +83,7 @@ async function loadTestData() { text: 'Project0 Text', color: '#00f', status: 'open', - deadline: Date.parse('2020-10-10'), + deadline: '2020-10-10', }, { id: '00000000-0000-4000-8000-000000000001', name: 'Project1', @@ -270,21 +270,41 @@ async function deleteTestData() { await database('comments') .delete() .whereIn('comments.task_id', [ + '00000000-0000-4000-8000-000000000000', + '00000000-0000-4000-8000-000000000001', + '00000000-0000-4000-8000-000000000002', + '00000000-0000-4000-8000-000000000003', + '00000000-0000-4000-8000-000000000004', '00000000-0000-4000-8000-000000000005', ]); await database('workhours') .delete() .whereIn('workhours.task_id', [ + '00000000-0000-4000-8000-000000000000', + '00000000-0000-4000-8000-000000000001', + '00000000-0000-4000-8000-000000000002', + '00000000-0000-4000-8000-000000000003', + '00000000-0000-4000-8000-000000000004', '00000000-0000-4000-8000-000000000005', ]); await database('task_assignees') .delete() .whereIn('task_assignees.task_id', [ + '00000000-0000-4000-8000-000000000000', + '00000000-0000-4000-8000-000000000001', + '00000000-0000-4000-8000-000000000002', + '00000000-0000-4000-8000-000000000003', + '00000000-0000-4000-8000-000000000004', '00000000-0000-4000-8000-000000000005', ]); await database('task_dependencies') .delete() .whereIn('task_dependencies.task_id', [ + '00000000-0000-4000-8000-000000000000', + '00000000-0000-4000-8000-000000000001', + '00000000-0000-4000-8000-000000000002', + '00000000-0000-4000-8000-000000000003', + '00000000-0000-4000-8000-000000000004', '00000000-0000-4000-8000-000000000005', ]); await database('task_requirements') @@ -292,6 +312,9 @@ async function deleteTestData() { .whereIn('task_requirements.task_id', [ '00000000-0000-4000-8000-000000000000', '00000000-0000-4000-8000-000000000001', + '00000000-0000-4000-8000-000000000002', + '00000000-0000-4000-8000-000000000003', + '00000000-0000-4000-8000-000000000004', '00000000-0000-4000-8000-000000000005', ]); await database('tasks') @@ -351,7 +374,7 @@ async function deleteTestData() { } beforeAll(async () => { - await ready; + await migrate(); await loadTestData(); }); diff --git a/server/src/v1/auth.test.ts b/server/src/v1/auth.test.ts index ff3a33d7addb9eadfd6a7e1814724f1285e5746e..cdcace98a8796143497c994536097a075758bbab 100644 --- a/server/src/v1/auth.test.ts +++ b/server/src/v1/auth.test.ts @@ -1,7 +1,7 @@ import supertest, { Response } from 'supertest'; -import { database } from '../database'; +import database from '../database'; import { api } from '../api'; import { generateAuthToken } from './auth'; diff --git a/server/src/v1/project.ts b/server/src/v1/project.ts index 443e725fab53f40b4e1c9e1d26c3bc1fc6dcba06..a9436ad27aff1602ec6aeb1fb4fdb7dce2174b1b 100644 --- a/server/src/v1/project.ts +++ b/server/src/v1/project.ts @@ -245,13 +245,14 @@ project.get('/:uuid/activity', async (req, res) => { 'team_projects.project_id': id, }) .andWhereNot({ 'workhours.finished': null }) - .andWhereBetween('workhours.started', [since.getTime(), to.getTime()]) + .andWhere('workhours.started', '>=', since.getTime()) + .andWhere('workhours.started', '<=', to.getTime()) .groupBy('workhours.id') ) .select({ - day: database.raw('`started` / 1000 / 60 / 60 / 24'), + day: database.raw('(workhours.started / 1000 / 60 / 60 / 24)'), }) - .sum({ time: database.raw('`finished` - `started`') }) + .sum({ time: database.raw('(workhours.finished - workhours.started)') }) .groupBy('day'); res.status(200).json({ status: 'success', @@ -287,14 +288,14 @@ project.get('/:uuid/completion', async (req, res) => { .select({ id: 'tasks.id', status: database.raw( - 'Case When `tasks`.`status` = \'open\' ' - + 'And (Select ' - + 'Sum(`task_requirements`.`time` * 60 * 1000) ' - + 'from `task_requirements` where `task_requirements`.`task_id` = `tasks`.`id`) ' - + '< (Select ' - + 'Sum(`workhours`.`finished` - `workhours`.`started`) ' - + 'from `workhours` where `workhours`.`task_id` = `tasks`.`id`) ' - + 'Then \'overdue\' Else `tasks`.`status` End'), + `Case When tasks.status = 'open' + And (Select + Sum(task_requirements.time * 60 * 1000) + from task_requirements where task_requirements.task_id = tasks.id) + < (Select + Sum(workhours.finished - workhours.started) + from workhours where workhours.task_id = tasks.id) + Then 'overdue' Else tasks.status End`), }) .where({ 'team_members.user_id': req.body.token.id, @@ -303,12 +304,13 @@ project.get('/:uuid/completion', async (req, res) => { .andWhere('tasks.edited', '>=', since.getTime()) .andWhere('tasks.created', '<=', to.getTime()) .groupBy('tasks.id') + .as('task_status') ) .select({ - status: 'status', + status: 'task_status.status', }) - .count({ count: 'id' }) - .groupBy('status'); + .count({ count: 'task_status.id' }) + .groupBy('task_status.status'); res.status(200).json({ status: 'success', completion: completion.reduce((object, { status, count }) => ({ @@ -323,6 +325,7 @@ project.get('/:uuid/completion', async (req, res) => { }); } } catch (e) { + console.log(e); res.status(400).json({ status: 'error', message: 'failed get completion', @@ -366,7 +369,7 @@ project.post('/', async (req, res) => { name: req.body.name, text: req.body.text, color: req.body.color, - deadline: req.body.deadline ? Date.parse(req.body.deadline) : null, + deadline: req.body.deadline ? (new Date(req.body.deadline)).toISOString().substr(0, 10) : null, status: 'open', }) await transaction('team_projects').insert( diff --git a/server/src/v1/team.ts b/server/src/v1/team.ts index f86814dc9e22c7e1eb8a5e5be28e826751fc5ade..9090c106e41d46d693718703199b2f558c11e0a8 100644 --- a/server/src/v1/team.ts +++ b/server/src/v1/team.ts @@ -361,15 +361,16 @@ team.get('/:uuid/activity', async (req, res) => { .innerJoin('team_members', 'ut.team_id', 'team_members.team_id') .innerJoin('workhours', 'team_members.user_id', 'workhours.user_id') .select({ - day: database.raw('`started` / 1000 / 60 / 60 / 24'), + day: database.raw('(workhours.started / 1000 / 60 / 60 / 24)'), }) - .sum({ time: database.raw('`workhours`.`finished` - `workhours`.`started`') }) + .sum({ time: database.raw('(workhours.finished - workhours.started)') }) .where({ 'ut.user_id': req.body.token.id, 'ut.team_id': id, }) .andWhereNot({ 'workhours.finished': null }) - .andWhereBetween('workhours.started', [since.getTime(), to.getTime()]) + .andWhere('workhours.started', '>=', since.getTime()) + .andWhere('workhours.started', '<=', to.getTime()) .groupBy('day'); res.status(200).json({ status: 'success', @@ -398,21 +399,21 @@ team.get('/:uuid/completion', async (req, res) => { if (validate(id)) { const since = new Date(parseInt(req.query.since as string ?? 0)); const to = new Date(parseInt(req.query.to as string ?? Date.now())); - const completion = await database( + const completion: any[] = await database( database('team_members') .innerJoin('team_projects', 'team_members.team_id', 'team_projects.team_id') .innerJoin('tasks', 'team_projects.project_id', 'tasks.project_id') .select({ id: 'tasks.id', status: database.raw( - 'Case When `tasks`.`status` = \'open\' ' - + 'And (Select ' - + 'Sum(`task_requirements`.`time` * 60 * 1000) ' - + 'from `task_requirements` where `task_requirements`.`task_id` = `tasks`.`id`) ' - + '< (Select ' - + 'Sum(`workhours`.`finished` - `workhours`.`started`) ' - + 'from `workhours` where `workhours`.`task_id` = `tasks`.`id`) ' - + 'Then \'overdue\' Else `tasks`.`status` End'), + `Case When tasks.status = 'open' + And (Select + Sum(task_requirements.time * 60 * 1000) + from task_requirements where task_requirements.task_id = tasks.id) + < (Select + Sum(workhours.finished - workhours.started) + from workhours where workhours.task_id = tasks.id) + Then 'overdue' Else tasks.status End`), }) .where({ 'team_members.user_id': req.body.token.id, @@ -421,12 +422,13 @@ team.get('/:uuid/completion', async (req, res) => { .andWhere('tasks.edited', '>=', since.getTime()) .andWhere('tasks.created', '<=', to.getTime()) .groupBy('tasks.id') + .as('task_status') ) .select({ - status: 'status', + status: 'task_status.status', }) - .count({ count: 'id' }) - .groupBy('status') as any[]; + .count({ count: 'task_status.id' }) + .groupBy('task_status.status'); res.status(200).json({ status: 'success', completion: completion.reduce((object, { status, count }) => ({ diff --git a/server/src/v1/user.ts b/server/src/v1/user.ts index 15e04eade4c12ae9a10cbd58a9cd05d0de986653..ebe8b3a0d77a698ea8d4ca56dd1429c0e79a73a8 100644 --- a/server/src/v1/user.ts +++ b/server/src/v1/user.ts @@ -193,14 +193,15 @@ user.get('/activity', async (req, res) => { const to = new Date(parseInt(req.query.to as string ?? Date.now())); const activity = await database('workhours') .select({ - day: database.raw('`started` / 1000 / 60 / 60 / 24'), + day: database.raw('(workhours.started / 1000 / 60 / 60 / 24)'), }) - .sum({ time: database.raw('`finished` - `started`') }) + .sum({ time: database.raw('(workhours.finished - workhours.started)') }) .where({ 'workhours.user_id': req.body.token.id, }) .andWhereNot({ 'workhours.finished': null }) - .andWhereBetween('workhours.started', [since.getTime(), to.getTime()]) + .andWhere('workhours.started', '>=', since.getTime()) + .andWhere('workhours.started', '<=', to.getTime()) .groupBy('day'); res.status(200).json({ status: 'success', @@ -210,7 +211,6 @@ user.get('/activity', async (req, res) => { })), }); } catch (e) { - console.log(e); res.status(400).json({ status: 'error', message: 'failed get activity', @@ -222,20 +222,20 @@ user.get('/completion', async (req, res) => { try { const since = new Date(parseInt(req.query.since as string ?? 0)); const to = new Date(parseInt(req.query.to as string ?? Date.now())); - const completion = await database( + const completion: any[] = await database( database('task_assignees') .innerJoin('tasks', 'task_assignees.task_id', 'tasks.id') .select({ id: 'tasks.id', status: database.raw( - 'Case When `tasks`.`status` = \'open\' ' - + 'And (Select ' - + 'Sum(`task_requirements`.`time` * 60 * 1000) ' - + 'from `task_requirements` where `task_requirements`.`task_id` = `tasks`.`id`) ' - + '< (Select ' - + 'Sum(`workhours`.`finished` - `workhours`.`started`) ' - + 'from `workhours` where `workhours`.`task_id` = `tasks`.`id`) ' - + 'Then \'overdue\' Else `tasks`.`status` End'), + `Case When tasks.status = 'open' + And (Select + Sum(task_requirements.time * 60 * 1000) + from task_requirements where task_requirements.task_id = tasks.id) + < (Select + Sum(workhours.finished - workhours.started) + from workhours where workhours.task_id = tasks.id) + Then 'overdue' Else tasks.status End`), }) .where({ 'task_assignees.user_id': req.body.token.id, @@ -243,12 +243,13 @@ user.get('/completion', async (req, res) => { .andWhere('tasks.edited', '>=', since.getTime()) .andWhere('tasks.created', '<=', to.getTime()) .groupBy('tasks.id') + .as('task_status') ) .select({ - status: 'status', + status: 'task_status.status', }) - .count({ count: 'id' }) - .groupBy('status') as any[]; + .count({ count: 'task_status.id' }) + .groupBy('task_status.status'); res.status(200).json({ status: 'success', completion: completion.reduce((object, { status, count }) => ({ diff --git a/server/yarn.lock b/server/yarn.lock index 44c1bf0674d0700e302bf4bc318acb8504dcf33f..f2d290bcfeb9c61c574139a0974d4e08300525fd 100644 --- a/server/yarn.lock +++ b/server/yarn.lock @@ -673,6 +673,15 @@ resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e" integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA== +"@types/pg@^8.6.0": + version "8.6.0" + resolved "https://registry.yarnpkg.com/@types/pg/-/pg-8.6.0.tgz#34233b891a127d6caaad28e177b1baec1a2958d4" + integrity sha512-3JXFrsl8COoqVB1+2Pqelx6soaiFVXzkT3fkuSNe7GB40ysfT0FHphZFPiqIXpMyTHSFRdLTyZzrFBrJRPAArA== + dependencies: + "@types/node" "*" + pg-protocol "*" + pg-types "^2.2.0" + "@types/prettier@^2.0.0": version "2.2.3" resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.2.3.tgz#ef65165aea2924c9359205bf748865b8881753c0" @@ -4136,12 +4145,12 @@ pg-pool@^3.3.0: resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.3.0.tgz#12d5c7f65ea18a6e99ca9811bd18129071e562fc" integrity sha512-0O5huCql8/D6PIRFAlmccjphLYWC+JIzvUhSzXSpGaf+tjTZc4nn+Lr7mLXBbFJfvwbP0ywDv73EiaBsxn7zdg== -pg-protocol@^1.5.0: +pg-protocol@*, pg-protocol@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.5.0.tgz#b5dd452257314565e2d54ab3c132adc46565a6a0" integrity sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ== -pg-types@^2.1.0: +pg-types@^2.1.0, pg-types@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3" integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==