256 lines
6.5 KiB
TypeScript
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" };
|
|
};
|