import { type DB, db } from "$lib/server/db/db"; import { tasks, type taskTypes } from "$lib/server/db/schema/tasks"; import type { ServiceResponse } from "$lib/server/services/service.types"; import { eq, type InferSelectModel } from "drizzle-orm"; import logger from "../logger"; export type Task = InferSelectModel & { type: InferSelectModel; }; export type TaskOrNull = InferSelectModel & { type: InferSelectModel | null; }; export type NewTask = typeof tasks.$inferInsert; class TasksService { private db: DB; private caller: "internal" | "api"; constructor(caller: "internal" | "api", dbClient: DB = db) { this.db = dbClient; this.caller = caller; } private async _executeQuery( query: () => Promise, ): Promise> { try { const tasks = await query(); if (tasks.some(x => x.type === null)) { const badTaskIds = tasks.filter(t => t.type === null).map(t => t.id); logger.error(`Data integrity issue: The following tasks have invalid type IDs: ${badTaskIds.join(", ")}`); return { status: "failure", error: "One or more tasks are invalid because they are not associated with a type.", code: "DATA_INTEGRITY_VIOLATION", }; } return { data: tasks as Task[], status: "ok" }; } catch (error) { logger.error({ msg: "Error querying the database.", error }); return { status: "failure", error: "An internal server error occurred.", code: "INTERNAL_ERROR" }; } } public async getAll() { logger.info("Fetching all task records..."); return this._executeQuery(() => this.db.query.tasks.findMany({ with: { type: true } })); } public async getByTaskId(taskIds: Array) { const mappedTasks = taskIds.map(x => { const prefix = x.slice(0, 2); const task_id = x.slice(2); return { prefix, task_id }; }); logger.info( `Fetching ${ taskIds.length === 0 ? "0 records" : taskIds.length < 10 ? taskIds.join(", ") : `${taskIds.length} records` }.`, ); return this._executeQuery(() => this.db.query.tasks.findMany({ with: { type: true }, where: (tasks, { inArray }) => inArray(tasks.taskId, mappedTasks.map(x => x.task_id)), }) ); } public async getByDbId(ids: Array) { logger.info(`Fetching ${ids.length} records.`); return this._executeQuery(() => this.db.query.tasks.findMany({ with: { type: true }, where: (tasks, { inArray }) => inArray(tasks.id, ids), }) ); } public async getByParent(id: NonNullable) { logger.info(`Searching for records with parent '${id}'.`); return this._executeQuery(() => this.db.query.tasks.findMany({ with: { type: true }, where: (tasks, { eq }) => eq(tasks.parent, id), }) ); } public async upsert(taskData: NewTask): Promise> { try { if (taskData.id) { const updated = await this.db.update(tasks) .set(taskData) .where(eq(tasks.id, taskData.id)) .returning({ id: tasks.id }); if (updated.length === 0) { return { status: "failure", code: "VALIDATION_ERROR", error: `Task with ID ${taskData.id} not found for update.` }; } return { status: "ok", data: { id: updated[0].id } }; } else { const created = await this.db.insert(tasks) .values(taskData) .returning({ id: tasks.id }); if (created.length === 0) { throw new Error("Insert operation failed to return the new task."); } return { status: "ok", data: { id: created[0].id } }; } } catch (error) { logger.error({ msg: "Error upserting task.", error }); return { status: "failure", error: "An internal server error occurred while saving the task.", code: "INTERNAL_ERROR" }; } } } export default TasksService;