diff --git a/server/migrations/0000_initial.ts b/server/migrations/0000_initial.ts
index 583107b61c019fdb4ce60bb4f1ff7c917a267f03..5bbfc84585e6fe110969d91daf1733e0d036aeef 100644
--- a/server/migrations/0000_initial.ts
+++ b/server/migrations/0000_initial.ts
@@ -29,6 +29,7 @@ export async function up(database: Knex): Promise<void> {
         .createTable('projects', table => {
             table.uuid('id').notNullable().primary();
             table.text('name').notNullable();
+            table.text('text').notNullable();
             table.string('color').notNullable();
             table.enum('status', [ 'open', 'closed', 'suspended' ]).notNullable();
         })
diff --git a/server/src/v1/project.ts b/server/src/v1/project.ts
index 3766830e92f1f8b30ebd1040ae53c26b84cb68d2..370eef80ba61f7c44fdf6b95349fee1417e5971f 100644
--- a/server/src/v1/project.ts
+++ b/server/src/v1/project.ts
@@ -48,6 +48,8 @@ project.get('/:uuid', async (req, res) => {
                 .select({
                     id: 'projects.id',
                     name: 'projects.name',
+                    text: 'projects.text',
+                    color: 'projects.color',
                     status: 'projects.status',
                     team_id: 'tms.team_id',
                 })
@@ -134,15 +136,96 @@ project.get('/:uuid/tasks', async (req, res) => {
     }
 });
 
+project.get('/:uuid/assigned', async (req, res) => {
+    try {
+        const id = req.params.uuid;
+        if (validate(id)) {
+            const users = await database('team_members')
+                .innerJoin('team_projects', 'team_members.team_id', 'team_projects.team_id')
+                .innerJoin('tasks', 'team_projects.project_id', 'tasks.project_id')
+                .innerJoin('task_assignees', 'tasks.id', 'task_assignees.task_id')
+                .innerJoin('users', 'task_assignees.user_id', 'users.id')
+                .select({
+                    id: 'users.id',
+                    username: 'users.user_name',
+                    email: 'users.email',
+                    realname: 'users.real_name',
+                })
+                .sum({ time: 'task_assignees.time' })
+                .where({
+                    'team_members.user_id': req.body.token.id,
+                    'team_projects.project_id': id,
+                })
+                .groupBy('users.id');
+            res.status(200).json({
+                status: 'success',
+                tasks: users,
+            });
+        } else {
+            res.status(400).json({
+                status: 'error',
+                message: 'malformed uuid',
+            });
+        }
+    } catch (e) {
+        console.log(e);
+        res.status(400).json({
+            status: 'error',
+            message: 'failed to get assignees',
+        });
+    }
+});
+
+project.get('/:uuid/work', async (req, res) => {
+    try {
+        const id = req.params.uuid;
+        if (validate(id)) {
+            const since = (req.query.since ?? 0) as number;
+            const work = await database({ ut: 'team_members' })
+                .innerJoin('team_projects', 'ut.team_id', 'team_projects.team_id')
+                .innerJoin('tasks', 'team_projects.project_id', 'tasks.project_id')
+                .innerJoin('workhours', 'tasks.id', 'workhours.task_id')
+                .select({
+                    id: 'workhours.id',
+                    task: 'workhours.task_id',
+                    user: 'workhours.user_id',
+                    started: 'workhours.started',
+                    finished: 'workhours.finished',
+                })
+                .where({
+                    'ut.user_id': req.body.token.id,
+                    'team_projects.project_id': id,
+                })
+                .andWhere('workhours.started', '>=', since)
+                .groupBy('workhours.id');
+            res.status(200).json({
+                status: 'success',
+                work: work,
+            });
+        } else {
+            res.status(400).json({
+                status: 'error',
+                message: 'malformed uuid',
+            });
+        }
+    } catch (e) {
+        res.status(400).json({
+            status: 'error',
+            message: 'failed get work',
+        });
+    }
+});
+
 interface AddProjectBody {
     teams: Array<string>;
     name: string;
+    text: string;
     color: string;
     token: Token;
 }
 
 project.post('/', async (req, res) => {
-    if (isOfType<AddProjectBody>(req.body, [['teams', 'object'], ['name', 'string'], ['color', 'string']])) {
+    if (isOfType<AddProjectBody>(req.body, [['teams', 'object'], ['name', 'string'], ['text', 'string'], ['color', 'string']])) {
         try {
             const team_ids = req.body.teams;
             for (const team_id of team_ids) {
@@ -173,6 +256,8 @@ project.post('/', async (req, res) => {
                         await transaction('projects').insert({
                             id: project_id,
                             name: req.body.name,
+                            text: req.body.text,
+                            color: req.body.color,
                             status: 'open',
                         });
                         await transaction('team_projects').insert(
@@ -211,6 +296,7 @@ interface UpdateProjectBody {
     remove_teams?: Array<string>;
     add_teams?: Array<string>;
     name?: string;
+    text?: string;
     color?: string;
     status?: string;
     token: Token;
@@ -245,6 +331,7 @@ project.put('/:uuid', async (req, res) => {
                         await transaction('projects')
                             .update({
                                 name: req.body.name,
+                                text: req.body.text,
                                 color: req.body.color,
                                 status: req.body.status,
                             }).where({
diff --git a/server/src/v1/task.ts b/server/src/v1/task.ts
index 0779a282a722cdec2a5f65d11870e30974edc041..89d3f948cafb5577b08a48b05f0b31109c1f433b 100644
--- a/server/src/v1/task.ts
+++ b/server/src/v1/task.ts
@@ -257,6 +257,46 @@ task.get('/:uuid/comments', async (req, res) => {
     }
 });
 
+task.get('/:uuid/work', async (req, res) => {
+    try {
+        const id = req.params.uuid;
+        if (validate(id)) {
+            const since = (req.query.since ?? 0) as number;
+            const work = await database({ ut: 'team_members' })
+                .innerJoin('team_projects', 'ut.team_id', 'team_projects.team_id')
+                .innerJoin('tasks', 'team_projects.project_id', 'tasks.project_id')
+                .innerJoin('workhours', 'tasks.id', 'workhours.task_id')
+                .select({
+                    id: 'workhours.id',
+                    task: 'workhours.task_id',
+                    user: 'workhours.user_id',
+                    started: 'workhours.started',
+                    finished: 'workhours.finished',
+                })
+                .where({
+                    'ut.user_id': req.body.token.id,
+                    'tasks.id': id,
+                })
+                .andWhere('workhours.started', '>=', since)
+                .groupBy('workhours.id');
+            res.status(200).json({
+                status: 'success',
+                work: work,
+            });
+        } else {
+            res.status(400).json({
+                status: 'error',
+                message: 'malformed uuid',
+            });
+        }
+    } catch (e) {
+        res.status(400).json({
+            status: 'error',
+            message: 'failed get work',
+        });
+    }
+});
+
 task.get('/:uuid', async (req, res) => {
     try {
         const id = req.params.uuid;
diff --git a/server/src/v1/team.ts b/server/src/v1/team.ts
index c97fe686942ef44d54841e7bfe3f290e5c71c897..4fa91366611ebca56c96bfaf5b38618ba96cd06a 100644
--- a/server/src/v1/team.ts
+++ b/server/src/v1/team.ts
@@ -260,6 +260,45 @@ team.get('/:uuid/projects', async (req, res) => {
     }
 });
 
+team.get('/:uuid/work', async (req, res) => {
+    try {
+        const id = req.params.uuid;
+        if (validate(id)) {
+            const since = (req.query.since ?? 0) as number;
+            const work = await database({ ut: 'team_members' })
+                .innerJoin('team_members', 'ut.team_id', 'team_members.team_id')
+                .innerJoin('workhours', 'team_members.user_id', 'workhours.user_id')
+                .select({
+                    id: 'workhours.id',
+                    task: 'workhours.task_id',
+                    user: 'workhours.user_id',
+                    started: 'workhours.started',
+                    finished: 'workhours.finished',
+                })
+                .where({
+                    'ut.user_id': req.body.token.id,
+                    'ut.team_id': id,
+                })
+                .andWhere('workhours.started', '>=', since)
+                .groupBy('workhours.id');
+            res.status(200).json({
+                status: 'success',
+                work: work,
+            });
+        } else {
+            res.status(400).json({
+                status: 'error',
+                message: 'malformed uuid',
+            });
+        }
+    } catch (e) {
+        res.status(400).json({
+            status: 'error',
+            message: 'failed get work',
+        });
+    }
+});
+
 interface AddRoleBody {
     name: string;
     token: Token;
diff --git a/server/src/v1/user.ts b/server/src/v1/user.ts
index 7fb8c66f921945608df0c35d29c1d31f4c8ad298..4136116ed5cf1253a6b1169ba42ae362a6b9472c 100644
--- a/server/src/v1/user.ts
+++ b/server/src/v1/user.ts
@@ -108,6 +108,34 @@ user.get('/tasks', async (req, res) => {
     }
 });
 
+user.get('/work', async (req, res) => {
+    try {
+        const since = (req.query.since ?? 0) as number;
+        const work = await database('workhours')
+            .select({
+                id: 'workhours.id',
+                task: 'workhours.task_id',
+                user: 'workhours.user_id',
+                started: 'workhours.started',
+                finished: 'workhours.finished',
+            })
+            .where({
+                'workhours.user_id': req.body.token.id,
+            })
+            .andWhere('workhours.started', '>=', since)
+            .groupBy('workhours.id');
+        res.status(200).json({
+            status: 'success',
+            work: work,
+        });
+    } catch (e) {
+        res.status(400).json({
+            status: 'error',
+            message: 'failed get work',
+        });
+    }
+});
+
 interface UserUpdateBody {
     token: Token;
     realname?: string;
diff --git a/server/src/v1/work.ts b/server/src/v1/work.ts
index 9868e7c2b4dd823f81d8c93b2d785a33540d2f2b..e12e49047dca30a4660b5b50ea94ed1a1dc92dcb 100644
--- a/server/src/v1/work.ts
+++ b/server/src/v1/work.ts
@@ -1,13 +1,110 @@
 
 import express from 'express';
+import { v4 as uuid, validate } from 'uuid';
 
-import { requireVerification } from './auth';
+import database from '../database';
+import { requireVerification, Token } from './auth';
+import { isOfType } from '../util';
 
 const work = express();
 
 work.use(requireVerification);
 
+interface AddWorkBody {
+    task: string;
+    token: Token;
+}
 
+work.post('/start', async (req, res) => {
+    if (isOfType<AddWorkBody>(req.body, [['task', 'string']])) {
+        try {
+            const task_id = req.body.task;
+            if (validate(task_id)) {
+                const task = await 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' })
+                    .where({
+                        'team_members.user_id': req.body.token.id,
+                        'tasks.id': task_id,
+                    });
+                if (task.length >= 1) {
+                    const work_id = uuid();
+                    await database.transaction(async transaction => {
+                        await transaction('workhours')
+                            .update({
+                                finished: new Date(),
+                            })
+                            .where({
+                                user_id: req.body.token.id,
+                                finished: null,
+                            });
+                        await transaction('workhours')
+                            .insert({
+                                id: work_id,
+                                task_id: task_id,
+                                user_id: req.body.token.id,
+                                started: new Date(),
+                                finished: null,
+                            });
+                    });
+                    res.status(200).json({
+                        status: 'success',
+                        id: work_id,
+                    });
+                } else {
+                    res.status(404).json({
+                        status: 'error',
+                        message: 'task not found',
+                    });
+                }
+            } else {
+                res.status(400).json({
+                    status: 'error',
+                    message: 'malformed uuid',
+                });
+            }
+        } catch (e) {
+            res.status(400).json({
+                status: 'error',
+                message: 'failed to create work',
+            });
+        }
+    } else {
+        res.status(400).json({
+            status: 'error',
+            message: 'missing request fields',
+        });
+    }
+});
+
+work.put('/finish', async (req, res) => {
+    try {
+        const work = await database('workhours')
+            .update({
+                finished: new Date(),
+            })
+            .where({
+                user_id: req.body.token.id,
+                finished: null,
+            });
+        if (work >= 1) {
+            res.status(200).json({
+                status: 'success',
+            });
+        } else {
+            res.status(200).json({
+                status: 'error',
+                message: 'no work to finish',
+            });
+        }
+    } catch (e) {
+        res.status(400).json({
+            status: 'error',
+            message: 'failed to finish work',
+        });
+    }
+});
 
 export default work;