Skip to content

Commit e5b8fb9

Browse files
authored
Merge pull request #63 from acmutsa/admin/course-crud
Completed [courseId] Page
2 parents d76d387 + 58ecd04 commit e5b8fb9

File tree

11 files changed

+309
-109
lines changed

11 files changed

+309
-109
lines changed

src/actions/admin/course.ts

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ import { adminClient } from "@/lib/safe-action";
33
import { coursesTags, courses, users, tags } from "@/db/schema";
44
import { courseSchema, type CourseFormValues } from "@/lib/validations/course";
55
import { db } from "@/db";
6-
import { eq } from "drizzle-orm";
6+
import { eq, inArray } from "drizzle-orm";
77
import { CourseWithData } from "@/lib/types";
8+
import { units, lessons } from "@/db/schema";
9+
import { z } from "zod";
810

911
export const createCourseAction = adminClient
1012
.schema(courseSchema)
@@ -109,4 +111,90 @@ export async function getCourseById(id: number) {
109111
};
110112

111113
return course;
112-
}
114+
}
115+
116+
export const updateLessonUnitOrder = adminClient
117+
.schema(
118+
z.object({
119+
updatedUnits: z.array(
120+
z.object({
121+
id: z.number(),
122+
position: z.number(),
123+
})
124+
),
125+
updatedLessons: z.array(
126+
z.object({
127+
id: z.number(),
128+
unitId: z.number(),
129+
position: z.number(),
130+
})
131+
),
132+
})
133+
)
134+
.action(async ({ parsedInput, ctx }) => {
135+
const { updatedUnits, updatedLessons } = parsedInput;
136+
137+
try {
138+
// --- Step 1: Validate unit IDs exist ---
139+
const dbUnits = await db.select().from(units).where(inArray(units.id, updatedUnits.map(u => u.id)));
140+
if (dbUnits.length !== updatedUnits.length) {
141+
return { success: false, message: "One or more units are invalid." };
142+
}
143+
144+
// --- Step 2: Validate lesson IDs exist ---
145+
const dbLessons = await db.select().from(lessons).where(inArray(lessons.id, updatedLessons.map(l => l.id)));
146+
if (dbLessons.length !== updatedLessons.length) {
147+
return { success: false, message: "One or more lessons are invalid." };
148+
}
149+
150+
// --- Step 3: Check unique and sequential positions for units ---
151+
const unitPositions = updatedUnits.map(u => u.position);
152+
const uniqueUnitPositions = new Set(unitPositions);
153+
if (uniqueUnitPositions.size !== updatedUnits.length) {
154+
return { success: false, message: "Duplicate positions found for units." };
155+
}
156+
157+
const sortedUnitPositions = [...unitPositions].sort((a, b) => a - b);
158+
for (let i = 0; i < sortedUnitPositions.length; i++) {
159+
if (sortedUnitPositions[i] !== i + 1) {
160+
return { success: false, message: "Unit positions must be sequential starting from 1." };
161+
}
162+
}
163+
164+
// --- Step 4: Check unique and sequential positions for lessons within each unit ---
165+
const lessonsByUnit = updatedLessons.reduce((acc: Record<number, number[]>, l) => {
166+
if (!acc[l.unitId]) acc[l.unitId] = [];
167+
acc[l.unitId].push(l.position);
168+
return acc;
169+
}, {} as Record<number, number[]>);
170+
171+
for (const unitId in lessonsByUnit) {
172+
const positions = lessonsByUnit[unitId].sort((a, b) => a - b);
173+
for (let i = 0; i < positions.length; i++) {
174+
if (positions[i] !== i + 1) {
175+
return { success: false, message: `Lesson positions in unit ${unitId} must be sequential starting from 1.` };
176+
}
177+
}
178+
}
179+
180+
// --- Step 5: Update within a transaction ---
181+
await db.transaction(async (tx) => {
182+
for (const unit of updatedUnits) {
183+
await tx.update(units)
184+
.set({ position: unit.position })
185+
.where(eq(units.id, unit.id));
186+
}
187+
188+
for (const lesson of updatedLessons) {
189+
await tx.update(lessons)
190+
.set({ position: lesson.position, unitId: lesson.unitId })
191+
.where(eq(lessons.id, lesson.id));
192+
}
193+
});
194+
195+
return { success: true, message: "Units and lessons order updated successfully." };
196+
} catch (error) {
197+
console.error("Failed to update lesson/unit order", error);
198+
return { success: false, message: "Failed to update order." };
199+
}
200+
});

src/actions/admin/units.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ export const createUnitAction = adminClient
4040
};
4141
});
4242

43-
4443
export async function getUnitsForCourse(courseId: number) {
4544
return await db
4645
.select()
Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
export default async function Page({
2-
params,
3-
}: {
4-
params: Promise<{ courseId: string }>;
5-
}) {
1+
import type { CourseIdPageProps } from "@/lib/types";
2+
3+
export default async function Page({ params }: CourseIdPageProps) {
64
const { courseId } = await params;
75
return <div>Edit page for courseID: {courseId}</div>;
86
}

src/app/admin/courses/[courseId]/lesson/create/page.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,9 @@ import {
99
CardDescription,
1010
CardContent,
1111
} from "@/components/ui/card";
12+
import type { CourseIdPageProps } from "@/lib/types";
1213

13-
type PageProps = {
14-
params: Promise<{ courseId: string }>;
15-
};
16-
17-
export default async function CreateLessonPage({ params }: PageProps) {
14+
export default async function CreateLessonPage({ params }: CourseIdPageProps) {
1815
const { courseId } = await params;
1916
const id = Number(courseId);
2017

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import React from 'react'
2+
import type { CourseIdPageProps } from '@/lib/types'
3+
4+
export default async function page({ params }: CourseIdPageProps) {
5+
const { courseId } = await params;
6+
const id = Number(courseId);
7+
return (
8+
<div>{id}</div>
9+
)
10+
}

src/app/admin/courses/[courseId]/page.tsx

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,9 @@ import { getCourseById } from "@/actions/admin/course";
1212
import { getUnitsForCourse } from "@/actions/admin/units";
1313
import { getLessonsForCourse } from "@/actions/admin/lesson";
1414
import Link from "next/link";
15+
import type { CourseIdPageProps } from "@/lib/types";
1516

16-
export default async function CourseIdPage({
17-
params
18-
}: {
19-
params: Promise<{ courseId: string}>
20-
}) {
17+
export default async function CourseIdPage({ params }: CourseIdPageProps) {
2118
const { courseId } = await params;
2219
const id = Number(courseId);
2320
const [course, units, lessons] = await Promise.all([
@@ -34,7 +31,7 @@ export default async function CourseIdPage({
3431
}
3532

3633
return (
37-
<div className="flex flex-col gap-4 min-h-[500px] h-[85vh]">
34+
<div className="flex flex-col gap-4 min-h-[500px] h-full">
3835
<div className="flex justify-end gap-4">
3936
<Link href={`/admin/courses/${id}/lesson/create`}>
4037
<Button variant={"outline"} className="cursor-pointer">Create Lesson</Button>
@@ -50,7 +47,7 @@ export default async function CourseIdPage({
5047
</CardTitle>
5148
<CardDescription className="px-6 text-md">{course.description}</CardDescription>
5249
<CardContent className="h-full">
53-
<CourseController units={units} lessons={lessons} />
50+
<CourseController courseId={id} units={units} lessons={lessons} />
5451
</CardContent>
5552
</Card>
5653
</div>

src/app/admin/courses/[courseId]/units/create/page.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
import UnitForm from "@/components/admin/unit/UnitForm";
2+
import type { CourseIdPageProps } from "@/lib/types";
23

3-
export default async function Page({
4-
params,
5-
}: {
6-
params: Promise<{ courseId: string }>;
7-
}) {
4+
export default async function Page({ params }: CourseIdPageProps) {
85
const { courseId } = await params;
96
const id = Number(courseId);
107
return (
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import type { CourseIdPageProps } from '@/lib/types'
2+
3+
export default async function page({ params }: CourseIdPageProps) {
4+
const { courseId } = await params;
5+
const id = Number(courseId);
6+
return (
7+
<div>{id}</div>
8+
)
9+
}

0 commit comments

Comments
 (0)