task-managment / src / server / task / infrastructure / repositories-impl.ts
repositories-impl.ts
Raw
// 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();