Skip to content

Commit

Permalink
Extract business logic to services, add tests and run tests during CI/CD
Browse files Browse the repository at this point in the history
  • Loading branch information
vguleaev committed Oct 6, 2024
1 parent cd68e47 commit 633a4e0
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 53 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/build-and-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ jobs:
- name: Run ESLint
run: bun run lint

- name: Run tests
run: bun test

- name: Build Docker image
run: docker build . -t docker.io/${{ env.DOCKER_USER }}/${{ env.APP_NAME }}:${{ github.sha }}

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"scripts": {
"start": "bun server/index.ts",
"dev": "bun --watch server/index.ts",
"test": "bun test",
"lint": "eslint ./server ./frontend/src",
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate",
Expand Down
12 changes: 12 additions & 0 deletions server/repos/group.repo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { asc, eq } from 'drizzle-orm';
import type { UserType } from '@kinde-oss/kinde-typescript-sdk';
import { db } from '../db/db';
import { backlogTaskGroupTable } from '../db/schema';

export async function getGroups(user: UserType) {
return await db
.select()
.from(backlogTaskGroupTable)
.where(eq(backlogTaskGroupTable.userId, user.id))
.orderBy(asc(backlogTaskGroupTable.createdAt));
}
35 changes: 35 additions & 0 deletions server/repos/tasks.repo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { and, desc, eq, gte } from 'drizzle-orm';
import type { ValueOf } from 'ts-essentials';
import type { UserType } from '@kinde-oss/kinde-typescript-sdk';
import { db } from '../db/db';
import { backlogTasksTable } from '../db/schema';
import { BACKLOG_TASK_CREATED_FILTER } from '../constants/backlog-task-created-filter';
import dayjs from 'dayjs';

export async function getTasksByCreatedDate(
user: UserType,
createdFilter: ValueOf<typeof BACKLOG_TASK_CREATED_FILTER>
) {
return await db
.select()
.from(backlogTasksTable)
.where(and(eq(backlogTasksTable.userId, user.id), getCreatedDateFilter(createdFilter)))
.orderBy(desc(backlogTasksTable.createdAt));
}

function getCreatedDateFilter(createdFilter: ValueOf<typeof BACKLOG_TASK_CREATED_FILTER>) {
switch (createdFilter) {
case BACKLOG_TASK_CREATED_FILTER.TODAY:
return gte(backlogTasksTable.createdAt, dayjs().startOf('day').toISOString());
case BACKLOG_TASK_CREATED_FILTER.LAST_7_DAYS:
return gte(backlogTasksTable.createdAt, dayjs().subtract(7, 'day').toISOString());
case BACKLOG_TASK_CREATED_FILTER.LAST_MONTH:
return gte(backlogTasksTable.createdAt, dayjs().subtract(1, 'month').toISOString());
case BACKLOG_TASK_CREATED_FILTER.LAST_3_MONTHS:
return gte(backlogTasksTable.createdAt, dayjs().subtract(3, 'month').toISOString());
case BACKLOG_TASK_CREATED_FILTER.LAST_6_MONTHS:
return gte(backlogTasksTable.createdAt, dayjs().subtract(6, 'month').toISOString());
default:
throw new Error('Invalid created filter');
}
}
55 changes: 2 additions & 53 deletions server/routes/backlog.route.ts
Original file line number Diff line number Diff line change
@@ -1,70 +1,19 @@
import { Hono } from 'hono';
import { authMiddleware } from '../middlewares/auth.middleware';
import { db } from '../db/db';
import { backlogTaskGroupTable, backlogTasksTable, type BacklogTask, type BacklogTaskGroup } from '../db/schema';
import { and, asc, desc, eq, gte } from 'drizzle-orm';
import { BACKLOG_TASK_PRIORITY } from '../constants/backlog-task-priority.const';
import type { ValueOf } from 'ts-essentials';
import { BACKLOG_TASK_CREATED_FILTER } from '../constants/backlog-task-created-filter';
import dayjs from 'dayjs';
import { getBacklogTasks, type BacklogTaskGroupWithTasks } from '../services/backlog.service';

export type GetBacklogResponse = {
groups: BacklogTaskGroupWithTasks[];
};

export type BacklogTaskGroupWithTasks = BacklogTaskGroup & {
tasks: BacklogTask[];
};

const priorityMap = {
[BACKLOG_TASK_PRIORITY.HIGH]: 1,
[BACKLOG_TASK_PRIORITY.MEDIUM]: 2,
[BACKLOG_TASK_PRIORITY.LOW]: 3,
};

const getCreatedDateFilter = (createdFilter: ValueOf<typeof BACKLOG_TASK_CREATED_FILTER>) => {
switch (createdFilter) {
case BACKLOG_TASK_CREATED_FILTER.TODAY:
return gte(backlogTasksTable.createdAt, dayjs().startOf('day').toISOString());
case BACKLOG_TASK_CREATED_FILTER.LAST_7_DAYS:
return gte(backlogTasksTable.createdAt, dayjs().subtract(7, 'day').toISOString());
case BACKLOG_TASK_CREATED_FILTER.LAST_MONTH:
return gte(backlogTasksTable.createdAt, dayjs().subtract(1, 'month').toISOString());
case BACKLOG_TASK_CREATED_FILTER.LAST_3_MONTHS:
return gte(backlogTasksTable.createdAt, dayjs().subtract(3, 'month').toISOString());
case BACKLOG_TASK_CREATED_FILTER.LAST_6_MONTHS:
return gte(backlogTasksTable.createdAt, dayjs().subtract(6, 'month').toISOString());
default:
throw new Error('Invalid created filter');
}
};

const backlogRoute = new Hono().get('/', authMiddleware, async (ctx) => {
const user = ctx.get('user');
const createdFilter = ctx.req.query('created') as ValueOf<typeof BACKLOG_TASK_CREATED_FILTER>;

const groups = await db
.select()
.from(backlogTaskGroupTable)
.where(eq(backlogTaskGroupTable.userId, user.id))
.orderBy(asc(backlogTaskGroupTable.createdAt));

const tasks = await db
.select()
.from(backlogTasksTable)
.where(and(eq(backlogTasksTable.userId, user.id), getCreatedDateFilter(createdFilter)))
.orderBy(desc(backlogTasksTable.createdAt));
const groupsWithTasks = await getBacklogTasks(user, createdFilter);

const sortedTasks = tasks.sort((a, b) => {
return priorityMap[a.priority] - priorityMap[b.priority];
});

const groupsWithTasks = groups.map((group) => {
return {
...group,
tasks: sortedTasks.filter((task) => task.groupId === group.id),
};
});
return ctx.json<GetBacklogResponse>({
groups: groupsWithTasks,
});
Expand Down
76 changes: 76 additions & 0 deletions server/services/backlog.service.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { describe, it, expect, mock } from 'bun:test';
import { getBacklogTasks } from './backlog.service';
import { BACKLOG_TASK_CREATED_FILTER } from '../constants/backlog-task-created-filter';
import type { UserType } from '@kinde-oss/kinde-typescript-sdk';
import type { BacklogTask, BacklogTaskGroup } from '../db/schema';
import { BACKLOG_TASK_PRIORITY } from '../constants/backlog-task-priority.const';

describe('getBacklogTasks', () => {
const user = { id: 'user1' } as UserType;
const createdFilter = BACKLOG_TASK_CREATED_FILTER.TODAY;

it('should fetch backlog task groups and tasks', async () => {
const mockGroups = [
{ id: 'group1', name: 'test', userId: 'user1', createdAt: '2023-01-01T00:00:00Z' },
] as BacklogTaskGroup[];

const mockTasks = [
{
id: 'task1',
userId: 'user1',
groupId: 'group1',
priority: BACKLOG_TASK_PRIORITY.HIGH,
createdAt: '2023-01-01T00:00:00Z',
},
] as BacklogTask[];

mock.module('../repos/group.repo', () => ({
getGroups: () => Promise.resolve(mockGroups),
}));

mock.module('../repos/tasks.repo', () => ({
getTasksByCreatedDate: () => Promise.resolve(mockTasks),
}));

const result = await getBacklogTasks(user, createdFilter);

expect(result).toEqual([
{
...mockGroups[0],
tasks: mockTasks,
},
]);
});

it('should sort tasks by priority', async () => {
const mockGroups = [{ id: 'group1', userId: 'user1', createdAt: '2023-01-01T00:00:00Z' }];
const mockTasks = [
{
id: 'task1',
userId: 'user1',
groupId: 'group1',
priority: BACKLOG_TASK_PRIORITY.LOW,
createdAt: '2023-01-01T00:00:00Z',
},
{
id: 'task2',
userId: 'user1',
groupId: 'group1',
priority: BACKLOG_TASK_PRIORITY.HIGH,
createdAt: '2023-01-01T00:00:00Z',
},
];

mock.module('../repos/group.repo', () => ({
getGroups: () => Promise.resolve(mockGroups),
}));

mock.module('../repos/tasks.repo', () => ({
getTasksByCreatedDate: () => Promise.resolve(mockTasks),
}));

const result = await getBacklogTasks(user, createdFilter);
console.log('res', result);
expect(result[0].tasks).toEqual([mockTasks[1], mockTasks[0]]);
});
});
35 changes: 35 additions & 0 deletions server/services/backlog.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { ValueOf } from 'ts-essentials';
import type { UserType } from '@kinde-oss/kinde-typescript-sdk';
import { type BacklogTask, type BacklogTaskGroup } from '../db/schema';
import { BACKLOG_TASK_PRIORITY } from '../constants/backlog-task-priority.const';
import { BACKLOG_TASK_CREATED_FILTER } from '../constants/backlog-task-created-filter';
import { getGroups } from '../repos/group.repo';
import { getTasksByCreatedDate } from '../repos/tasks.repo';

export type BacklogTaskGroupWithTasks = BacklogTaskGroup & {
tasks: BacklogTask[];
};

const priorityMap = {
[BACKLOG_TASK_PRIORITY.HIGH]: 1,
[BACKLOG_TASK_PRIORITY.MEDIUM]: 2,
[BACKLOG_TASK_PRIORITY.LOW]: 3,
};

export async function getBacklogTasks(user: UserType, createdFilter: ValueOf<typeof BACKLOG_TASK_CREATED_FILTER>) {
const groups = await getGroups(user);
const tasks = await getTasksByCreatedDate(user, createdFilter);

const groupsWithTasks = groups.map((group) => {
return {
...group,
tasks: tasks
.filter((task) => task.groupId === group.id)
.sort((a, b) => {
return priorityMap[a.priority] - priorityMap[b.priority];
}),
};
});

return groupsWithTasks;
}

0 comments on commit 633a4e0

Please sign in to comment.