tiqued/src/lib/server/services/tasks.ts
themodrnhakr eddaf02824 Add method to intelligently insert/update.
The `upsert()` method can both create and update tasks. The method
checks for an `id` propery to determine whether to `INSERT` or `UPDATE`.

A successful operation returns a `ServiceResponse` object with the
inserted task `id`, which can be used to fetch updated information if
needed.

The `NewTask` type is exported for use in form actions, etc.
2025-10-02 00:01:52 -05:00

125 lines
4.1 KiB
TypeScript

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<typeof tasks> & {
type: InferSelectModel<typeof taskTypes>;
};
export type TaskOrNull = InferSelectModel<typeof tasks> & {
type: InferSelectModel<typeof taskTypes> | 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<TaskOrNull[]>,
): Promise<ServiceResponse<Task[], "INTERNAL_ERROR" | "DATA_INTEGRITY_VIOLATION">> {
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<string>) {
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<number>) {
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<Task["id"]>) {
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<ServiceResponse<{ id: number }, "INTERNAL_ERROR" | "VALIDATION_ERROR">> {
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;