1
0
cvsa/packages/tracker/app/projects/projectPageAction.ts

256 lines
6.5 KiB
TypeScript

import type { Route } from "./+types/projectPage";
import { db } from "@lib/db";
import { projects, columns, tasks } from "@lib/db/schema";
import { eq, asc, desc } from "drizzle-orm";
import { generate as generateId } from "@alikia/random-key";
import { getCurrentUser } from "@lib/auth-utils";
import { canUserEditProject } from "@lib/auth";
export const projectPageAction = async ({ request, params }: Route.ActionArgs) => {
const user = await getCurrentUser(request);
if (!user) {
throw new Response("Unauthorized", { status: 401 });
}
const formData = await request.formData();
const intent = formData.get("intent");
const projectId = params.id;
// Check if user can edit this project for write operations
const canEdit = await canUserEditProject(user.id, projectId);
if (!canEdit && intent !== "getColumns") {
throw new Response("You do not have permission to edit this project", { status: 403 });
}
if (intent === "getColumns") {
const projectId = formData.get("projectId") as string;
const projectColumns = await db
.select()
.from(columns)
.where(eq(columns.projectId, projectId))
.orderBy(asc(columns.position));
const columnsWithTasks = await Promise.all(
projectColumns.map(async (column) => {
const columnTasks = await db
.select()
.from(tasks)
.where(eq(tasks.columnId, column.id))
.orderBy(desc(tasks.priority), asc(tasks.dueDate));
return {
...column,
tasks: columnTasks.sort((a, b) => {
if (a.dueDate === null && b.dueDate === null) return 0;
if (a.dueDate === null) return 1;
if (b.dueDate === null) return -1;
return a.dueDate.getTime() - b.dueDate.getTime();
})
};
})
);
return { columnsWithTasks };
}
if (intent === "createTask") {
const title = formData.get("title") as string;
const description = formData.get("description") as string;
const columnId = formData.get("columnId") as string;
const priority = formData.get("priority") as "low" | "medium" | "high";
const dueDate = formData.get("dueDate") as string;
if (!title || !columnId) {
return { error: "Title and column are required" };
}
const taskId = await generateId(7);
await db.insert(tasks).values({
id: taskId,
projectId: projectId,
columnId: columnId,
title: title,
description: description,
priority: priority,
dueDate: dueDate ? new Date(dueDate) : null,
createdAt: new Date(),
updatedAt: new Date()
});
return { success: true, taskId };
}
if (intent === "updateTask") {
const taskId = formData.get("taskId") as string;
const title = formData.get("title") as string;
const description = formData.get("description") as string;
const columnId = formData.get("columnId") as string;
const priority = formData.get("priority") as "low" | "medium" | "high";
const dueDate = formData.get("dueDate") as string;
if (!title || !columnId || !taskId) {
return { error: "Title, column, and task ID are required" };
}
await db
.update(tasks)
.set({
title: title,
description: description,
columnId: columnId,
priority: priority,
dueDate: dueDate ? new Date(dueDate) : null,
updatedAt: new Date()
})
.where(eq(tasks.id, taskId));
return { success: true, taskId };
}
if (intent === "deleteTask") {
const taskId = formData.get("taskId") as string;
if (!taskId) {
return { error: "Task ID is required" };
}
await db.delete(tasks).where(eq(tasks.id, taskId));
return { success: true, taskId };
}
if (intent === "createColumn") {
const name = formData.get("name") as string;
if (!name) {
return { error: "Column name is required" };
}
const columnId = await generateId(7);
// Get the highest position for this project
const existingColumns = await db
.select()
.from(columns)
.where(eq(columns.projectId, projectId))
.orderBy(asc(columns.position));
const newPosition =
existingColumns.length > 0
? existingColumns[existingColumns.length - 1].position + 1
: 0;
await db.insert(columns).values({
id: columnId,
projectId: projectId,
name: name,
position: newPosition,
createdAt: new Date(),
updatedAt: new Date()
});
return { success: true, columnId };
}
if (intent === "updateColumn") {
const columnId = formData.get("columnId") as string;
const name = formData.get("name") as string;
const position = formData.get("position") as string;
if (!name || !columnId) {
return { error: "Column name and ID are required" };
}
await db
.update(columns)
.set({
name: name,
position: parseInt(position) || 0,
updatedAt: new Date()
})
.where(eq(columns.id, columnId));
return { success: true, columnId };
}
if (intent === "deleteColumn") {
const columnId = formData.get("columnId") as string;
if (!columnId) {
return { error: "Column ID is required" };
}
// Check if column has tasks
const columnTasks = await db.select().from(tasks).where(eq(tasks.columnId, columnId));
if (columnTasks.length > 0) {
return { error: "Cannot delete column with tasks. Please move or delete tasks first." };
}
await db.delete(columns).where(eq(columns.id, columnId));
return { success: true, columnId };
}
if (intent === "reorderColumns") {
const columnOrder = JSON.parse(formData.get("columnOrder") as string) as string[];
// Update positions for all columns
for (let i = 0; i < columnOrder.length; i++) {
const columnId = columnOrder[i];
await db
.update(columns)
.set({
position: i,
updatedAt: new Date()
})
.where(eq(columns.id, columnId));
}
return { success: true };
}
if (intent === "updateProject") {
const name = formData.get("name") as string;
const description = formData.get("description") as string;
const isPublic = formData.get("isPublic") === "true";
if (!name) {
return { error: "Project name is required" };
}
await db
.update(projects)
.set({
name: name,
description: description,
updatedAt: new Date(),
isPublic: isPublic
})
.where(eq(projects.id, projectId));
return { success: true };
}
if (intent === "deleteProject") {
// Check if project has columns
const projectColumns = await db
.select()
.from(columns)
.where(eq(columns.projectId, projectId));
if (projectColumns.length > 0) {
return {
error: "Cannot delete project with columns. Please delete all columns first."
};
}
await db.delete(projects).where(eq(projects.id, projectId));
return { success: true, redirect: "/" };
}
return { error: "Unknown action" };
};