Skip to content

Commit eeb7ffc

Browse files
committed
initial pass, restruturing
1 parent 3490367 commit eeb7ffc

135 files changed

Lines changed: 2508 additions & 2759 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
"use server";
2+
3+
import { Task, TaskDraft } from "@/types/home/codev";
4+
import { createClientServerComponent } from "@/utils/supabase/server";
5+
import { requireUser } from "@/lib/server/auth-guard";
6+
import { createNotificationAction } from "@/actions/notifications/notification.actions";
7+
import {
8+
fetchTaskDetail,
9+
type KanbanTaskDetail,
10+
} from "@/lib/server/kanban-task-query";
11+
import { fetchMemberBoardTasks as fetchMemberBoardTasksQuery } from "@/lib/server/kanban-member-tasks-query";
12+
import { fetchColumnTasksPage as fetchColumnTasksPageQuery } from "@/lib/server/kanban-column-tasks-query";
13+
import { KANBAN_COLUMN_TASK_PAGE_SIZE } from "@/lib/kanban/board-pagination";
14+
import type { BoardSnapshotInput } from "@/lib/kanban/board-snapshot-types";
15+
import { persistBoardSnapshot } from "@/lib/server/kanban-board-snapshot-sync";
16+
import { updateDeveloperLevels } from "@/lib/kanban/update-developer-levels";
17+
import {
18+
type CodevMember,
19+
type GuardSupabase,
20+
assertProjectMembership,
21+
buildPositionUpdates,
22+
fetchColumnTasks,
23+
getProjectIdForBoard,
24+
getProjectIdForColumn,
25+
getProjectIdForTask,
26+
persistTaskPositions,
27+
revalidateKanbanBoardLists,
28+
} from "@/lib/kanban/action-helpers";
29+
30+
export type { BoardSnapshotInput } from "@/lib/kanban/board-snapshot-types";
31+
32+
export async function moveTask({
33+
taskId,
34+
columnId,
35+
position,
36+
opId: _opId,
37+
}: {
38+
taskId: string;
39+
columnId: string;
40+
position: number;
41+
opId?: string;
42+
}): Promise<{ success: boolean; error?: string }> {
43+
let supabase: GuardSupabase;
44+
let user;
45+
try {
46+
({ supabase, user } = await requireUser());
47+
} catch {
48+
return { success: false, error: "Unauthorized" };
49+
}
50+
51+
const projectResolution = await getProjectIdForTask(supabase, taskId);
52+
try {
53+
await assertProjectMembership(supabase, user.id, projectResolution);
54+
} catch {
55+
return { success: false, error: "Forbidden" };
56+
}
57+
58+
try {
59+
const { data: task, error: taskError } = await supabase
60+
.from("tasks")
61+
.select("id, kanban_column_id, position")
62+
.eq("id", taskId)
63+
.single();
64+
65+
if (taskError || !task) {
66+
return { success: false, error: "Task not found" };
67+
}
68+
69+
const sourceColumnId = task.kanban_column_id;
70+
const [sourceTasks, targetTasks] = await Promise.all([
71+
sourceColumnId ? fetchColumnTasks(supabase, sourceColumnId) : Promise.resolve([]),
72+
fetchColumnTasks(supabase, columnId),
73+
]);
74+
75+
const updates = buildPositionUpdates(
76+
sourceColumnId,
77+
sourceTasks,
78+
columnId,
79+
targetTasks,
80+
taskId,
81+
position,
82+
);
83+
84+
await persistTaskPositions(supabase, updates);
85+
86+
return { success: true };
87+
} catch (error) {
88+
console.error("moveTask error:", error);
89+
return {
90+
success: false,
91+
error: error instanceof Error ? error.message : "Failed to move task",
92+
};
93+
}
94+
}
95+
96+
export async function syncBoardSnapshot(
97+
snapshot: BoardSnapshotInput,
98+
): Promise<{ success: boolean; error?: string }> {
99+
return persistBoardSnapshot(snapshot);
100+
}
101+
102+
export async function batchUpdateTasks(
103+
updates: Array<{ taskId: string; newColumnId: string }>,
104+
): Promise<{ success: boolean; error?: string }> {
105+
if (updates.length === 0) {
106+
return { success: true };
107+
}
108+
109+
const last = updates[updates.length - 1]!;
110+
return moveTask({
111+
taskId: last.taskId,
112+
columnId: last.newColumnId,
113+
position: 0,
114+
});
115+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
"use server";
2+
3+
import { requireRole } from "@/lib/server/auth-guard";
4+
import { revalidatePath } from "next/cache";
5+
6+
7+
8+
interface CreateBoardResult {
9+
success: boolean;
10+
error?: string;
11+
data?: any;
12+
}
13+
14+
export const createNewBoard = async (
15+
formData: FormData,
16+
): Promise<CreateBoardResult> => {
17+
let supabase;
18+
try {
19+
({ supabase } = await requireRole("kanban"));
20+
} catch {
21+
return { success: false, error: "Forbidden" };
22+
}
23+
24+
const name = formData.get("name")?.toString();
25+
const description = formData.get("description")?.toString();
26+
27+
if (!name) {
28+
return { success: false, error: "Board name is required" };
29+
}
30+
31+
const { data, error } = await supabase
32+
.from("kanban_boards")
33+
.insert({
34+
name,
35+
description,
36+
})
37+
.select()
38+
.single();
39+
40+
if (error) {
41+
console.error("Error creating board:", error.message);
42+
return { success: false, error: error.message };
43+
}
44+
45+
revalidatePath("/home/kanban");
46+
return { success: true, data };
47+
};
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
"use server";
2+
3+
import { Task, TaskDraft } from "@/types/home/codev";
4+
import { createClientServerComponent } from "@/utils/supabase/server";
5+
import { requireUser } from "@/lib/server/auth-guard";
6+
import { createNotificationAction } from "@/actions/notifications/notification.actions";
7+
import {
8+
fetchTaskDetail,
9+
type KanbanTaskDetail,
10+
} from "@/lib/server/kanban-task-query";
11+
import { fetchMemberBoardTasks as fetchMemberBoardTasksQuery } from "@/lib/server/kanban-member-tasks-query";
12+
import { fetchColumnTasksPage as fetchColumnTasksPageQuery } from "@/lib/server/kanban-column-tasks-query";
13+
import { KANBAN_COLUMN_TASK_PAGE_SIZE } from "@/lib/kanban/board-pagination";
14+
import type { BoardSnapshotInput } from "@/lib/kanban/board-snapshot-types";
15+
import { persistBoardSnapshot } from "@/lib/server/kanban-board-snapshot-sync";
16+
import { updateDeveloperLevels } from "@/lib/kanban/update-developer-levels";
17+
import {
18+
type CodevMember,
19+
type GuardSupabase,
20+
assertProjectMembership,
21+
buildPositionUpdates,
22+
fetchColumnTasks,
23+
getProjectIdForBoard,
24+
getProjectIdForColumn,
25+
getProjectIdForTask,
26+
persistTaskPositions,
27+
revalidateKanbanBoardLists,
28+
} from "@/lib/kanban/action-helpers";
29+
30+
export const createNewColumn = async (
31+
columnName: string,
32+
boardId: string,
33+
): Promise<{ success: boolean; error?: string }> => {
34+
let supabase: GuardSupabase;
35+
let user;
36+
try {
37+
({ supabase, user } = await requireUser());
38+
} catch {
39+
return { success: false, error: "Unauthorized" };
40+
}
41+
42+
const projectId = await getProjectIdForBoard(supabase, boardId);
43+
try {
44+
await assertProjectMembership(supabase, user.id, projectId);
45+
} catch {
46+
return { success: false, error: "Forbidden" };
47+
}
48+
49+
try {
50+
const { data: existingColumns, error: queryError } = await supabase
51+
.from("kanban_columns")
52+
.select("position")
53+
.eq("board_id", boardId)
54+
.order("position", { ascending: false })
55+
.limit(1);
56+
57+
if (queryError) {
58+
return { success: false, error: queryError.message };
59+
}
60+
61+
const nextPosition = (existingColumns?.[0]?.position ?? -1) + 1;
62+
63+
const { error: insertError } = await supabase
64+
.from("kanban_columns")
65+
.insert({
66+
name: columnName.trim(),
67+
board_id: boardId,
68+
position: nextPosition,
69+
created_at: new Date().toISOString(),
70+
updated_at: new Date().toISOString(),
71+
});
72+
73+
if (insertError) {
74+
console.error("Database error:", insertError);
75+
return { success: false, error: insertError.message };
76+
}
77+
78+
return { success: true };
79+
} catch (error) {
80+
console.error("Unexpected error:", error);
81+
return {
82+
success: false,
83+
error: error instanceof Error ? error.message : "Failed to create column",
84+
};
85+
}
86+
};
87+
88+
export const updateColumnPosition = async (
89+
columnId: string,
90+
newPosition: number,
91+
): Promise<{ success: boolean; error?: string }> => {
92+
let supabase: GuardSupabase;
93+
let user;
94+
try {
95+
({ supabase, user } = await requireUser());
96+
} catch {
97+
return { success: false, error: "Unauthorized" };
98+
}
99+
100+
const projectId = await getProjectIdForColumn(supabase, columnId);
101+
try {
102+
await assertProjectMembership(supabase, user.id, projectId);
103+
} catch {
104+
return { success: false, error: "Forbidden" };
105+
}
106+
107+
try {
108+
const { error } = await supabase
109+
.from("kanban_columns")
110+
.update({
111+
position: newPosition,
112+
updated_at: new Date().toISOString(),
113+
})
114+
.eq("id", columnId);
115+
116+
if (error) {
117+
throw error;
118+
}
119+
120+
return { success: true };
121+
} catch (error) {
122+
console.error("Error updating column position:", error);
123+
return {
124+
success: false,
125+
error: error instanceof Error ? error.message : "Failed to update column position",
126+
};
127+
}
128+
};
129+
130+
export const deleteColumn = async (
131+
columnId: string,
132+
): Promise<{ success: boolean; error?: string }> => {
133+
let supabase: GuardSupabase;
134+
let user;
135+
try {
136+
({ supabase, user } = await requireUser());
137+
} catch {
138+
return { success: false, error: "Unauthorized" };
139+
}
140+
141+
const projectId = await getProjectIdForColumn(supabase, columnId);
142+
try {
143+
await assertProjectMembership(supabase, user.id, projectId);
144+
} catch {
145+
return { success: false, error: "Forbidden" };
146+
}
147+
148+
try {
149+
const { error } = await supabase
150+
.from("kanban_columns")
151+
.delete()
152+
.eq("id", columnId);
153+
154+
if (error) {
155+
return { success: false, error: error.message };
156+
}
157+
158+
return { success: true };
159+
} catch (error) {
160+
return {
161+
success: false,
162+
error: error instanceof Error ? error.message : "Failed to delete column",
163+
};
164+
}
165+
};
166+
167+
export const updateColumnName = async (
168+
columnId: string,
169+
newName: string,
170+
): Promise<{ success: boolean; error?: string }> => {
171+
let supabase: GuardSupabase;
172+
let user;
173+
try {
174+
({ supabase, user } = await requireUser());
175+
} catch {
176+
return { success: false, error: "Unauthorized" };
177+
}
178+
179+
const projectId = await getProjectIdForColumn(supabase, columnId);
180+
try {
181+
await assertProjectMembership(supabase, user.id, projectId);
182+
} catch {
183+
return { success: false, error: "Forbidden" };
184+
}
185+
186+
try {
187+
const { error } = await supabase
188+
.from("kanban_columns")
189+
.update({
190+
name: newName.trim(),
191+
updated_at: new Date().toISOString(),
192+
})
193+
.eq("id", columnId);
194+
195+
if (error) {
196+
return { success: false, error: error.message };
197+
}
198+
199+
return { success: true };
200+
} catch (error) {
201+
return {
202+
success: false,
203+
error: error instanceof Error ? error.message : "Failed to update column name",
204+
};
205+
}
206+
};
207+
208+
// OPTIMIZED: Complete task with parallel operations to prevent timeouts

0 commit comments

Comments
 (0)