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.
125 lines
4.1 KiB
TypeScript
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;
|