Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/render/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@ class Render {
};
return log(titleObj);
}
_buildPrefix(item: { _id: string; [key: string]: any }) {
_buildPrefix(item: { externalId: number; [key: string]: any }) {
const prefix = [];

const { _id: id } = item;
const { externalId: id } = item;
prefix.push(" ".repeat(4 - String(id).length));
prefix.push(grey(`${id}.`));

Expand Down Expand Up @@ -194,7 +194,7 @@ class Render {
success({ prefix, message, suffix });
}
successGet(task: ITask) {
const [prefix, suffix] = ["\n", `${grey(task._id)}\n`];
const [prefix, suffix] = ["\n", `${grey(task.externalId)}\n`];
const message = "Get task:";
log({ prefix, message, suffix });
log(grey(JSON.stringify(task, null, 4)));
Expand Down
50 changes: 40 additions & 10 deletions src/tasks/taskApi.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,44 @@ import {

export class TaskApi {
db: any;

constructor(db) {
this.db = db;
this.ensureExternalIds();
}
_getById(tasks: ITask[], id: string): ITask {
const task = tasks.find((task) => task._id === id);
ensureExternalIds() {
const tasks = this.getTasks();
if (tasks.length === 0) return;
let nextId: number = tasks[0].externalId || 0;
tasks.forEach((task: ITask) => {
if (task.externalId === undefined || task.externalId === null) {
nextId += 1;
task.externalId = nextId;
this.db.data.lastTaskId = nextId;
} else {
nextId = task.externalId;
}
});
}
getTasks(): ITask[] {
return this.db.data.tasks;
}
_getByExternalId(tasks: ITask[], externalId: number): ITask {
const task = tasks.find((task) => task.externalId === externalId);
if (task === undefined) throw new Error("Task not found");
return task;
}
_getById(tasks: ITask[], id: string): ITask {
const externalId = this.parseExternalId(id);
return this._getByExternalId(tasks, externalId);
}
parseExternalId(id: string): number {
const parsed = parseInt(id, 10);
if (isNaN(parsed) || parsed <= 0) {
throw new Error("Invalid task ID: must be a positive integer");
}
return parsed;
}
async get(id: string): Promise<ITask> {
const tasks = await this.db.data.tasks;
return this._getById(tasks, id);
Expand Down Expand Up @@ -60,14 +90,13 @@ export class TaskApi {
};
}
async create(data: ICreateTask): Promise<ITask> {
const tasks = await this.db.data.tasks;
const tasks: ITask[] = await this.getTasks();
const now = new Date();
const id = `${
this.db.data.lastTaskId !== undefined ? this.db.data.lastTaskId + 1 : 1
}`;
const externalId = this.db.data.lastTaskId + 1;
const task: ITask = {
...data,
_id: id,
_id: crypto.randomUUID(),
externalId,
priority: calculatePriority(data),
createdAt: now,
updatedAt: now,
Expand All @@ -76,7 +105,7 @@ export class TaskApi {
timeSpent: 0,
};
tasks.push(task);
this.db.data.lastTaskId = parseInt(id);
this.db.data.lastTaskId = externalId;
await this.db.write();
return task;
}
Expand Down Expand Up @@ -118,7 +147,7 @@ export class TaskApi {
// Recalculate priority
task = { ...task, priority: calculatePriority(task as any) };
// Update the task in the array
const index = tasks.findIndex((t: ITask) => t._id === id);
const index = tasks.findIndex((t: ITask) => t.externalId === task.externalId);
if (index !== -1) {
tasks[index] = task;
}
Expand All @@ -127,8 +156,9 @@ export class TaskApi {
return task;
}
async delete(id: string): Promise<void> {
const externalId = this.parseExternalId(id);
const tasks = await this.db.data.tasks;
const index = tasks.findIndex((task: ITask) => task._id === id);
const index = tasks.findIndex((task: ITask) => task.externalId === externalId);
if (index === -1) {
throw new Error("Task not found");
}
Expand Down
18 changes: 8 additions & 10 deletions src/tasks/taskCommands.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export default class TaskCommands {
async createTask(task: ICreateTask): Promise<void> {
const taskCreated = await this.taskAPi.create(task);

render.successCreate(taskCreated._id);
render.successCreate(String(taskCreated.externalId));
}
async getTask(id: string): Promise<void> {
const task = await this.taskAPi.get(id);
Expand All @@ -56,20 +56,20 @@ export default class TaskCommands {
async updateTask(id: string, data: IUpdateTask): Promise<void> {
const task = await this.taskAPi.update(id, data);

render.successEdit(task._id);
render.successEdit(String(task.externalId));
}
async setStatusInProgress(id: string) {
const task = await this.taskAPi.update(id, {
status: TaskStatus.InProgress,
startedAt: new Date(),
});
render.successEdit(task._id);
render.successEdit(String(task.externalId));
}
async setStatusBlocked(id: string) {
const task = await this.taskAPi.update(id, {
status: TaskStatus.Blocked,
});
render.successEdit(task._id);
render.successEdit(String(task.externalId));
}
/**
*
Expand All @@ -86,19 +86,18 @@ export default class TaskCommands {
const task = await this.taskAPi.update(id, {
status,
});
render.successEdit(task._id);
render.successEdit(String(task.externalId));
}
/**
* Archive all tasks that have been completed
*/
async clearCompletedTasks() {
// Get all tasks
const tasks = await this.taskAPi.getAll();
tasks.data.forEach(async (task: ITask) => {
if (task.status === TaskStatus.Done) {
this.archivedb.data.tasks.push(task);
this.db.data.tasks.splice(
this.db.data.tasks.findIndex((t: ITask) => t._id === task._id),
this.db.data.tasks.findIndex((t: ITask) => t.externalId === task.externalId),
1
);
}
Expand All @@ -114,11 +113,10 @@ export default class TaskCommands {
const tasks = await this.taskAPi.getAll();
let newId = 1;
tasks.data.forEach(async (task: ITask) => {
task._id = `${newId}`;
task.externalId = newId;
newId += 1;
});
// Update the lastTaskId so new tasks use lastTaskId +1 ...
this.db.data.lastTaskId = newId;
this.db.data.lastTaskId = newId - 1;
await this.db.write();
}

Expand Down
1 change: 1 addition & 0 deletions src/types/task.interface.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export interface ITask {
_id: string;
externalId: number;
title: string;
description: string;
status: TaskStatus;
Expand Down
60 changes: 51 additions & 9 deletions tests/tasks/taskApi.class.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ describe('TaskApi', () => {
data: {
tasks: [
{
_id: '1',
_id: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
externalId: 1,
title: 'Task 1',
importance: 3,
urgency: 3,
Expand All @@ -22,7 +23,8 @@ describe('TaskApi', () => {
labels: []
},
{
_id: '2',
_id: 'b2c3d4e5-f6a7-8901-bcde-f12345678901',
externalId: 2,
title: 'Task 2',
importance: 1,
urgency: 1,
Expand All @@ -41,9 +43,10 @@ describe('TaskApi', () => {
});

describe('get', () => {
it('should return a task by id', async () => {
it('should return a task by externalId', async () => {
const task = await taskApi.get('1');
expect(task._id).toBe('1');
expect(task._id).toBe('a1b2c3d4-e5f6-7890-abcd-ef1234567890');
expect(task.externalId).toBe(1);
expect(task.title).toBe('Task 1');
});

Expand All @@ -60,18 +63,56 @@ describe('TaskApi', () => {

it('should apply limit and offset', async () => {
const result = await taskApi.getAll({ limit: 1, offset: 1 });
expect(result.data[0]._id).toBe('2');
expect(result.data[0].externalId).toBe(2);
});

it('should apply sorting', async () => {
const result = await taskApi.getAll({ sort: 'importance' as any, order: 'asc' });
expect(result.data[0]._id).toBe('2');
expect(result.data[0].externalId).toBe(2);
});

it('should apply filtering by statusExcludeFilter', async () => {
const result = await taskApi.getAll({ statusExcludeFilter: [TaskStatus.Done] });
expect(result.data.length).toBe(1);
expect(result.data[0]._id).toBe('1');
expect(result.data[0].externalId).toBe(1);
});

it('should exclude blocked tasks when hideBlockedTasks is true', async () => {
mockDb.data = {
tasks: [
{
_id: 'c3d4e5f6-a7b8-9012-cdef-123456789012',
externalId: 1,
title: 'Task 1',
importance: 5,
urgency: 5,
estimatedTime: 1,
status: TaskStatus.Pending,
createdAt: new Date(),
updatedAt: new Date(),
labels: [],
timeSpent: 0,
},
{
_id: 'd4e5f6a7-b8c9-0123-defa-234567890123',
externalId: 2,
title: 'Task 2',
importance: 1,
urgency: 1,
estimatedTime: 1,
status: TaskStatus.Blocked,
createdAt: new Date(),
updatedAt: new Date(),
labels: [],
timeSpent: 0,
},
],
lastTaskId: 2,
};
taskApi = new TaskApi(mockDb);
const result = await taskApi.getAll({ hideBlockedTasks: true });
expect(result.data.length).toBe(1);
expect(result.data[0].externalId).toBe(1);
});
});

Expand All @@ -85,7 +126,7 @@ describe('TaskApi', () => {
labels: ['label1']
};
const task = await taskApi.create(newTaskData as any);
expect(task._id).toBe('3');
expect(task.externalId).toBe(3);
expect(task.title).toBe('New Task');
expect(task.status).toBe(TaskStatus.Pending);
expect(mockDb.data.lastTaskId).toBe(3);
Expand Down Expand Up @@ -128,7 +169,8 @@ describe('TaskApi', () => {
mockDb.data = {
tasks: [
{
_id: '1',
_id: '12345678-1234-5678-1234-567812345678',
externalId: 1,
title: 'Task with time tracking',
importance: 3,
urgency: 3,
Expand Down
Loading