Skip to content

Commit

Permalink
refactor: table and column names (#144)
Browse files Browse the repository at this point in the history
* deps: bump drizzle-orm and drizzle-kit

* refactor: recurring_task_group -> task_group, tables should include mapping if association table

* refactor: all group id should be user group id

* fix: rename task groups and user group correctly in frontend too

* feat: show user email next to logout button
  • Loading branch information
invertedEcho authored Dec 7, 2024
1 parent 96aac1a commit 7175392
Show file tree
Hide file tree
Showing 52 changed files with 2,780 additions and 1,306 deletions.
1 change: 1 addition & 0 deletions backend/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@ module.exports = {
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/switch-exhaustiveness-check': 'error',
'object-shorthand': ['error', 'always'],
},
};
4 changes: 2 additions & 2 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
"@nestjs/schedule": "^4.0.2",
"@nestjs/serve-static": "^4.0.2",
"bcrypt": "^5.1.1",
"drizzle-orm": "^0.30.9",
"drizzle-orm": "0.37.0",
"pg": "^8.11.5",
"postgres": "^3.4.4",
"reflect-metadata": "^0.2.0",
Expand All @@ -54,7 +54,7 @@
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"dotenv-cli": "^7.4.2",
"drizzle-kit": "^0.21.1",
"drizzle-kit": "0.29.1",
"eslint": "^8.42.0",
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-prettier": "^5.0.0",
Expand Down
269 changes: 35 additions & 234 deletions backend/pnpm-lock.yaml

Large diffs are not rendered by default.

17 changes: 7 additions & 10 deletions backend/src/assignment/assignment-scheduler.service.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,23 @@
import { Injectable } from '@nestjs/common';
import { Cron, CronExpression } from '@nestjs/schedule';
import { dbInsertAssignments } from 'src/db/functions/assignment';
import { hydrateRecurringTaskGroupsToAssignToAssignments } from './util';
import { dbGetRecurringTaskGroupsToAssignForCurrentInterval } from 'src/db/functions/recurring-task-group';
import { hydrateTaskGroupsToAssignToAssignments } from './util';
import { dbGetTaskGroupsToAssignForCurrentInterval } from 'src/db/functions/task-group';

@Injectable()
export class AssignmentSchedulerService {
@Cron(CronExpression.EVERY_10_SECONDS)
async handleCreateAssignmentsCron() {
const recurringTaskGroupsToAssign =
await dbGetRecurringTaskGroupsToAssignForCurrentInterval({
overrideNow: undefined,
});
const taskGroupsToAssign = await dbGetTaskGroupsToAssignForCurrentInterval({
overrideNow: undefined,
});

if (recurringTaskGroupsToAssign.length === 0) {
if (taskGroupsToAssign.length === 0) {
return;
}

const assignmentsToCreate =
await hydrateRecurringTaskGroupsToAssignToAssignments(
recurringTaskGroupsToAssign,
);
await hydrateTaskGroupsToAssignToAssignments(taskGroupsToAssign);

if (assignmentsToCreate.length > 0) {
await dbInsertAssignments({ assignments: assignmentsToCreate });
Expand Down
61 changes: 29 additions & 32 deletions backend/src/assignment/assignment-scheduler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ import { client, db } from 'src/db';
import {
assignmentTable,
InsertAssignment,
InsertRecurringTaskGroup,
InsertTaskGroup,
InsertTask,
recurringTaskGroupTable,
recurringTaskGroupUserTable,
taskGroupTable,
taskGroupUserMappingTable,
taskTable,
taskUserGroupTable,
taskUserGroupMappingTable,
} from 'src/db/schema';
import {
mockRecurringTaskGroupUserValues,
mockTaskGroupUserValues,
taskVacuuming,
userGroupWG1,
userJulian,
Expand Down Expand Up @@ -40,27 +40,27 @@ describe('Assignment scheduler', () => {
});

it('sets assignment creation date to start of interval when task is created in middle of interval', async () => {
const recurringTaskGroupWeekly = {
const taskGroupWeekly = {
id: 1,
initialStartDate: new Date('2024-08-25 22:00:00Z'),
interval: '1 week',
title: 'Every week',
userGroupId: 1,
} satisfies InsertRecurringTaskGroup;
} satisfies InsertTaskGroup;

// Create task group with initialstartdate Monday in week 1
await db.insert(recurringTaskGroupTable).values(recurringTaskGroupWeekly);
await db.insert(recurringTaskGroupUserTable).values({
recurringTaskGroupId: recurringTaskGroupWeekly.id,
await db.insert(taskGroupTable).values(taskGroupWeekly);
await db.insert(taskGroupUserMappingTable).values({
taskGroupId: taskGroupWeekly.id,
userId: userJulian.id,
assignmentOrdinal: 2,
});

// Create new Task
await db.insert(taskTable).values(taskVacuuming);
await db
.insert(taskUserGroupTable)
.values({ groupId: userGroupWG1.id, taskId: taskVacuuming.id });
.insert(taskUserGroupMappingTable)
.values({ userGroupId: userGroupWG1.id, taskId: taskVacuuming.id });

// Run scheduler function at Wednesday in week 2
jest.setSystemTime(new Date('2024-09-03 22:00:00Z'));
Expand All @@ -77,24 +77,21 @@ describe('Assignment scheduler', () => {
it('correctly inserts assignments to user in right order after after everyone was assigned once', async () => {
jest.setSystemTime(new Date('2024-09-10 08:00:00Z'));
const intervalStr = '7 days';
const mockRecurringTaskGroup = {
const mockTaskGroup = {
title: 'Weekly Tasks',
interval: intervalStr,
userGroupId: userGroupWG1.id,
initialStartDate: getStartOfInterval(intervalStr),
} satisfies InsertRecurringTaskGroup;
const insertedRecurringTaskGroup = (
await db
.insert(recurringTaskGroupTable)
.values(mockRecurringTaskGroup)
.returning()
} satisfies InsertTaskGroup;
const insertedTaskGroup = (
await db.insert(taskGroupTable).values(mockTaskGroup).returning()
)[0];
if (insertedRecurringTaskGroup === undefined) {
throw new Error('Did not insert mock recurring task group');
if (insertedTaskGroup === undefined) {
throw new Error('Did not insert mock task group');
}
const mockTask = {
title: 'Staubsaugen',
recurringTaskGroupId: insertedRecurringTaskGroup.id,
taskGroupId: insertedTaskGroup.id,
} satisfies InsertTask;
const insertedTask = (
await db.insert(taskTable).values(mockTask).returning()
Expand All @@ -104,10 +101,10 @@ describe('Assignment scheduler', () => {
throw new Error('Did not insert mock task');
}

const firstUserId = mockRecurringTaskGroupUserValues[0]?.userId;
const firstUserId = mockTaskGroupUserValues[0]?.userId;
if (firstUserId === undefined) {
throw new Error(
'mock recurring task group user values did not contain expected data.',
'mock task group user values did not contain expected data.',
);
}
const firstAssignment = {
Expand All @@ -117,10 +114,10 @@ describe('Assignment scheduler', () => {
state: 'completed',
} satisfies InsertAssignment;

const secondUserId = mockRecurringTaskGroupUserValues[1]?.userId;
const secondUserId = mockTaskGroupUserValues[1]?.userId;
if (secondUserId === undefined) {
throw new Error(
'mock recurring task group user values did not contain expected data.',
'mock task group user values did not contain expected data.',
);
}
const secondAssignment = {
Expand All @@ -130,10 +127,10 @@ describe('Assignment scheduler', () => {
state: 'completed',
} satisfies InsertAssignment;

const thirdUserId = mockRecurringTaskGroupUserValues[2]?.userId;
const thirdUserId = mockTaskGroupUserValues[2]?.userId;
if (thirdUserId === undefined) {
throw new Error(
'mock recurring task group user values did not contain expected data.',
'mock task group user values did not contain expected data.',
);
}
const thirdAssignment = {
Expand All @@ -143,19 +140,19 @@ describe('Assignment scheduler', () => {
state: 'completed',
} satisfies InsertAssignment;

await db.insert(recurringTaskGroupUserTable).values([
await db.insert(taskGroupUserMappingTable).values([
{
recurringTaskGroupId: insertedRecurringTaskGroup.id,
taskGroupId: insertedTaskGroup.id,
userId: firstUserId,
assignmentOrdinal: 0,
},
{
recurringTaskGroupId: insertedRecurringTaskGroup.id,
taskGroupId: insertedTaskGroup.id,
userId: secondUserId,
assignmentOrdinal: 1,
},
{
recurringTaskGroupId: insertedRecurringTaskGroup.id,
taskGroupId: insertedTaskGroup.id,
userId: thirdUserId,
assignmentOrdinal: 2,
},
Expand Down
4 changes: 2 additions & 2 deletions backend/src/assignment/assignment.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import { AssignmentResponse } from './types';
export class AssignmentController {
@Get()
async getAllAssignments(
@Query('groupId') groupId: number,
@Query('userGroupId') userGroupId: number,
): Promise<AssignmentResponse[]> {
return await dbGetAssignmentsForUserGroupFromCurrentInterval(groupId);
return await dbGetAssignmentsForUserGroupFromCurrentInterval(userGroupId);
}

@Patch('/:id/:state')
Expand Down
88 changes: 42 additions & 46 deletions backend/src/assignment/util.ts
Original file line number Diff line number Diff line change
@@ -1,80 +1,76 @@
import { dbGetAssignmentsForTaskGroup } from 'src/db/functions/assignment';
import { RecurringTaskGroupToAssign } from 'src/db/functions/recurring-task-group';
import { TaskGroupToAssign } from 'src/db/functions/task-group';
import { InsertAssignment } from 'src/db/schema';
import { getStartOfInterval } from 'src/utils/date';

/**
* Hydrates recurringTaskGroupsToAssign to "ready-to-insert" assignments.
* Hydrates taskGroupsToAssign to "ready-to-insert" assignments.
*
* This is done by finding the next responsible user id for each recurring task group.
* This is done by finding the next responsible user id for each task group.
* Note that this function queries the database to retrieve the latest assignment for each `recurringTaskGroup`.
* Note that this function queries the database to retrieve the latest assignment for each `taskGroup`.
*
* @param recurringTaskGroupsToAssign The recurring task groups where assignments need to be created.
* @param taskGroupsToAssign The task groups where assignments need to be created.
*/
export async function hydrateRecurringTaskGroupsToAssignToAssignments(
recurringTaskGroupsToAssign: RecurringTaskGroupToAssign[],
export async function hydrateTaskGroupsToAssignToAssignments(
taskGroupsToAssign: TaskGroupToAssign[],
): Promise<InsertAssignment[]> {
const latestAssignmentUserIdForEachRecurringTaskGroup: {
recTaskGroupId: number;
const latestAssignmentUserIdForEachTaskGroup: {
taskGroupId: number;
userId: number | undefined;
}[] = await Promise.all(
recurringTaskGroupsToAssign.map(async (rec) => {
taskGroupsToAssign.map(async (taskGroupToAssign) => {
const latestAssignment = (
await dbGetAssignmentsForTaskGroup({
taskGroupId: rec.recurringTaskGroupId,
taskGroupId: taskGroupToAssign.taskGroupId,
limit: 1,
})
)[0];
return {
recTaskGroupId: rec.recurringTaskGroupId,
taskGroupId: taskGroupToAssign.taskGroupId,
userId: latestAssignment?.userId,
};
}),
);

const assignmentsToCreate: InsertAssignment[] = [];
for (const recurringTaskGroup of recurringTaskGroupsToAssign) {
const { recurringTaskGroupId, userIdsOfRecurringTaskGroup } =
recurringTaskGroup;
for (const taskGroupToAssign of taskGroupsToAssign) {
const { taskGroupId, userIdsOfTaskGroup } = taskGroupToAssign;

const usersOfTaskGroup = userIdsOfRecurringTaskGroup.map(
(userId, index) => {
const assignmentOrdinal = recurringTaskGroup.assignmentOrdinals[index];
if (assignmentOrdinal === undefined) {
throw new Error('No assignment ordinal for userId');
}
return {
userId,
assignmentOrdinal,
};
},
);
const usersOfTaskGroup = userIdsOfTaskGroup.map((userId, index) => {
const assignmentOrdinal = taskGroupToAssign.assignmentOrdinals[index];
if (assignmentOrdinal === undefined) {
throw new Error('No assignment ordinal for userId');
}
return {
userId,
assignmentOrdinal,
};
});

const userIdOfLatestAssignment =
latestAssignmentUserIdForEachRecurringTaskGroup.find(
(item) => item.recTaskGroupId === recurringTaskGroupId,
latestAssignmentUserIdForEachTaskGroup.find(
(item) => item.taskGroupId === taskGroupId,
)?.userId;

const nextResponsibleUserId =
findNextResponsibleUserIdForRecurringTaskGroup(
userIdOfLatestAssignment,
usersOfTaskGroup,
);
const nextResponsibleUserId = findNextResponsibleUserIdForTaskGroup(
userIdOfLatestAssignment,
usersOfTaskGroup,
);

if (nextResponsibleUserId === undefined) {
throw new Error(
`Failed to find the next responsible user for the task group ${recurringTaskGroupId}`,
`Failed to find the next responsible user for the task group ${taskGroupId}`,
);
}

const taskIds = recurringTaskGroup.taskIds;
const taskIds = taskGroupToAssign.taskIds;
const hydratedAssignments = taskIds.map((taskId) => ({
taskId: taskId,
taskId,
userId: nextResponsibleUserId,
createdAt: recurringTaskGroup.isInFirstInterval
? recurringTaskGroup.taskGroupInitialStartDate
: getStartOfInterval(recurringTaskGroup.interval),
createdAt: taskGroupToAssign.isInFirstInterval
? taskGroupToAssign.taskGroupInitialStartDate
: getStartOfInterval(taskGroupToAssign.interval),
}));

assignmentsToCreate.push(...hydratedAssignments);
Expand All @@ -83,16 +79,16 @@ export async function hydrateRecurringTaskGroupsToAssignToAssignments(
}

/**
* Finds the next responsible user id for a recurring task group.
* Finds the next responsible user id for a task group.
*
* @param userIdOfLatestAssignment The userId of the latest assignment from a recurring task group.
* @param usersOfRecurringTaskGroup All users (with their `assignmentOrdinal`) belonging to the recurring task group.
* @param userIdOfLatestAssignment The userId of the latest assignment from a task group.
* @param usersOfTaskGroup All users (with their `assignmentOrdinal`) belonging to the task group.
*/
export function findNextResponsibleUserIdForRecurringTaskGroup(
export function findNextResponsibleUserIdForTaskGroup(
userIdOfLatestAssignment: number | undefined,
usersOfRecurringTaskGroup: { userId: number; assignmentOrdinal: number }[],
usersOfTaskGroup: { userId: number; assignmentOrdinal: number }[],
) {
const usersSortedByAssignmentOrdinal = usersOfRecurringTaskGroup.sort(
const usersSortedByAssignmentOrdinal = usersOfTaskGroup.sort(
(a, b) => a.assignmentOrdinal - b.assignmentOrdinal,
);

Expand Down
6 changes: 3 additions & 3 deletions backend/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export class AuthController {
} else {
await dbAddUserToUserGroup({
userId: newUser.id,
groupId: maybeGroup.groupId,
userGroupId: maybeGroup.userGroupId,
});
}
} catch (error) {
Expand All @@ -119,8 +119,8 @@ export class AuthController {
return {
userId: req.user.sub,
userGroup: {
id: userGroup?.user_group.name,
name: userGroup?.user_group.name,
id: userGroup?.userGroup.name,
name: userGroup?.userGroup.name,
},
email: user.email,
username: user.username,
Expand Down
Loading

0 comments on commit 7175392

Please sign in to comment.