// Task Repository Implementation following Hexagonal Architecture
import {
formatDate,
getRangeValue,
removeLocalOffsetFromDate,
} from "@/lib/utils";
import {
and,
gte,
ilike,
eq,
count,
asc,
desc,
ne,
lte,
or,
isNotNull,
sql,
countDistinct,
lt,
isNull,
} from "drizzle-orm";
import moment from "moment";
import { jsonAggBuildObject, jsonBuildObject } from "@/helpers/query";
import { dbGetAll, dbGetOne } from "@/lib/db/drizzle-client";
import { areas, project, task, user } from "@/database/schema";
import { db } from "@/database/db";
import {
FilterTasks,
MonthlyActivity,
ProjectTaskQueryResult,
Task,
TaskId,
TasksByDate,
WeeklyActivity,
} from "../domain/models";
import { TaskRepository } from "../domain/repositories";
import { createLogRecord } from "@/server/log";
import { UserId } from "@/server/user/domain/models";
import { Area, AreaId, AreaStatusEnum } from "@/server/project/domain/models";
class TaskRepositoryImpl implements TaskRepository {
async getProjectTasks(
filter: FilterTasks
): Promise<ProjectTaskQueryResult[]> {
const { searchQuery, status, range } = filter;
const rangeValue = range ? getRangeValue(range!) : undefined;
const results = await dbGetAll("project", {
where: and(
searchQuery ? ilike(project.name, `%${searchQuery}%`) : undefined,
!searchQuery ? ne(project.status, "completada"):undefined,
!searchQuery ? ne(project.status, "cancelada"):undefined,
rangeValue
? and(
gte(project.updatedAt, rangeValue.start),
lte(project.updatedAt, rangeValue.end)
)
: undefined
),
columns: {
id: true,
name: true,
status: true,
},
with: {
task: {
columns: {
id: true,
status: true,
priority: true,
startDate: true,
floorName: true,
areaId: true,
userId: true,
projectName: true,
projectId: true,
areaName: true,
startedTime: true,
endedTime: true,
expireDate: true,
comments: true,
createdAT: true,
updatedAt: true,
},
with: {
user: {
columns: {
id: true,
firstName: true,
lastName: true,
},
},
},
},
},
orderBy: [asc(project.name), task.id],
});
const filteredResults = !searchQuery ? results.filter(
(project) => project.task.length > 0
):results
return filteredResults as ProjectTaskQueryResult[];
}
async getRecentModified(): Promise<Task[]> {
const date = moment().startOf("day").toDate();
const todayDate = removeLocalOffsetFromDate(date);
const data = await db
.select({
id: task.id,
startDate: task.startDate,
expireDate: task.expireDate,
startedTime: task.startedTime,
endedTime: task.endedTime,
status: task.status,
createdAt: task.createdAT,
comments: task.comments,
projectName: task.projectName,
areaName: task.areaName,
floorName: task.floorName,
projectId: task.projectId,
priority: task.priority,
updatedAt: task.updatedAt,
user: jsonBuildObject({
id: user.id,
firstName: user.firstName,
lastName: user.lastName,
}),
})
.from(task)
.limit(5)
.leftJoin(user, eq(task.userId, user.id))
.orderBy(desc(task.updatedAt))
.where(and(gte(task.updatedAt, todayDate)));
return data as Task[];
}
async getAll(filter: FilterTasks, userId?: UserId): Promise<TasksByDate[]> {
const {
status,
searchQuery,
limit,
onlyUserTask,
onlyRecentModifiedTask,
range,
} = filter;
const date = moment().startOf("day").toDate();
const todayDate = removeLocalOffsetFromDate(date);
const rangeValue = range ? getRangeValue(range!) : undefined;
const tasksItems = await db
.select({
startDate: task.startDate,
tasks: jsonAggBuildObject({
id: task.id,
areaId: task.areaId,
userId: task.userId,
startDate: task.startDate,
expireDate: task.expireDate,
startedTime: task.startedTime,
endedTime: task.endedTime,
status: task.status,
createdAt: task.createdAT,
comments: task.comments,
projectName: task.projectName,
areaName: task.areaName,
floorName: task.floorName,
projectId: task.projectId,
priority: task.priority,
updatedAt: task.updatedAt,
user: jsonBuildObject({
id: user.id,
firstName: user.firstName,
lastName: user.lastName,
}),
}),
})
.from(task)
.limit(limit!)
.innerJoin(user, eq(task.userId, user.id))
.groupBy(task.startDate)
.where(
and(
onlyUserTask && userId?.value ? eq(user.id, userId.value) : undefined,
rangeValue
? and(
gte(task.startDate, formatDate(rangeValue.start)),
lte(task.startDate, formatDate(rangeValue.end))
)
: undefined,
searchQuery ? ilike(task.projectName, `%${searchQuery}%`) : undefined,
onlyRecentModifiedTask ? gte(task.updatedAt, todayDate) : undefined,
status === "pendiente"
? and(
gte(task.startDate, formatDate(todayDate)),
or(
eq(task.status, "en proceso"),
eq(task.status, "no iniciada"),
eq(task.status, "reabierta")
),
or(
isNull(task.expireDate),
gte(task.expireDate, formatDate(todayDate))
)
)
: undefined,
status === "expirada"
? and(
or(
and(
isNotNull(task.expireDate),
lt(task.expireDate, formatDate(todayDate))
),
lt(task.startDate, formatDate(todayDate))
),
ne(task.status, "cancelada"),
ne(task.status, "completada")
)
: undefined,
status === "completada"
? or(eq(task.status, "revisada"), eq(task.status, "completada"))
: undefined
)
);
return tasksItems as TasksByDate[];
}
async create(data: Task, userId: UserId): Promise<Task> {
await db.transaction(async (tx) => {
const createPromise = tx.insert(task).values(data as any);
const logRecordPromise = createLogRecord(tx, {
userId: userId.value,
modifiedItem: `Tarea-${data.areaName}/${data.floorName}/${data.projectName}`,
eventType: "crear",
tableName: "tool",
item: data,
});
await Promise.all([createPromise, logRecordPromise]);
});
return data;
}
async updateAreaStatus(
status: AreaStatusEnum,
areaId: AreaId
): Promise<Area[]> {
return db.update(areas).set({ status }).where(eq(areas.id, areaId.value)).returning();
}
async update(data: Partial<Task>, userId: UserId): Promise<Partial<Task>> {
const {
projectId,
projectName,
areaId,
areaName,
id,
floorName,
userId: assignedUserId,
user,
...rest
} = data;
const logValues = user
? { ...rest, assignedUser: `${user.firstName} ${user.lastName}` }
: rest;
const todayDate= removeLocalOffsetFromDate(new Date())
await db.transaction(async (tx) => {
const createPromise = tx
.update(task)
.set({
userId: assignedUserId,
...(rest as any),
...(data?.status === 'en proceso' && {startedTime:todayDate,endedTime:null} ),
...(data?.status === 'completada' || data?.status === 'revisada' ? {endedTime:todayDate}:null ),
})
.where(eq(task.id, data.id!));
const logRecordPromise = createLogRecord(tx as any, {
userId: userId.value,
modifiedItem: `Tarea = area:${areaName},piso:${floorName},projecto:${projectName} modificada`,
eventType: "modificar",
tableName: "task",
item: logValues,
});
const promises: any = [createPromise, logRecordPromise];
if (data?.status && data?.areaId) {
const updateArePromise = this.updateAreaStatus(data.status as any, {
value: data.areaId,
});
promises.push(updateArePromise);
}
await Promise.all(promises);
});
return data;
}
async isTaskAreaAssigned(areaId: AreaId): Promise<boolean> {
const result = await dbGetOne("task", {
where: and(eq(task.areaId, areaId.value), ne(task.status, "cancelada")),
});
return result ? true : false;
}
async delete(taskId: TaskId, userId: UserId): Promise<Task> {
return await db.transaction(async (tx) => {
const [deletedTask] = await tx
.delete(task)
.where(eq(task.id, taskId.value))
.returning();
if (deletedTask) {
await createLogRecord(tx, {
userId: userId.value,
modifiedItem: `Tarea-${deletedTask.areaName}/${deletedTask.floorName}/${deletedTask.projectName}`,
eventType: "eliminar",
tableName: "task",
item: deletedTask,
});
}
return deletedTask as Task;
});
}
async getUserWeeklyActivity(userId: UserId): Promise<WeeklyActivity[]> {
const data = await db
.select({
count: count(task.id),
day: sql`EXTRACT(DOW FROM ${task.updatedAt})`,
})
.from(task)
.where(
and(
or(eq(task.status, "completada"), eq(task.status, "revisada")),
eq(task.userId, userId.value)
)
)
.groupBy(sql`EXTRACT(DOW FROM ${task.updatedAt})`);
const daysOfWeek: string[] = [
"domingo",
"lunes",
"martes",
"miercoles",
"jueves",
"viernes",
"sabado",
];
const daysList = daysOfWeek.map((day) => {
return { day, count: 0 };
});
data.forEach(({ count, day }: any) => {
if (day >= 0 && day < daysList.length) {
daysList[day].count = count;
}
});
return daysList;
}
async userMonthActivity(userId: UserId): Promise<MonthlyActivity> {
const [data] = await db
.select({
compleatedTasks: countDistinct(task.id),
floors: countDistinct(task.floorName),
projects: countDistinct(task.projectName),
})
.from(task)
.where(
and(
or(eq(task.status, "completada"), eq(task.status, "revisada")),
eq(task.userId, userId.value)
)
)
.groupBy(task.userId);
return data;
}
}
export const defaultTaskRepository = new TaskRepositoryImpl();