Skip to content

Commit a08fb98

Browse files
committed
working unit and lesson but need to work on displaying the data
1 parent dba8596 commit a08fb98

File tree

21 files changed

+483
-150
lines changed

21 files changed

+483
-150
lines changed

drizzle.config.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ export default defineConfig({
66
dialect: "turso",
77
out: "./src/db/drizzle",
88
dbCredentials: {
9-
url: process.env.TURSO_DATABASE_URL!,
10-
authToken: process.env.TURSO_AUTH_TOKEN!,
9+
url: "http://127.0.0.1:8080"
10+
// url: process.env.TURSO_DATABASE_URL!,
11+
// authToken: process.env.TURSO_AUTH_TOKEN!,
1112
},
1213
});

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"dependencies": {
2121
"@hookform/resolvers": "^5.2.2",
2222
"@libsql/client": "^0.15.15",
23+
"@radix-ui/react-accordion": "^1.2.12",
2324
"@radix-ui/react-alert-dialog": "^1.1.15",
2425
"@radix-ui/react-aspect-ratio": "^1.1.7",
2526
"@radix-ui/react-avatar": "^1.1.11",

pnpm-lock.yaml

Lines changed: 62 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/actions/admin/course.ts

Lines changed: 68 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,48 @@
11
'use server';
2+
import { adminClient } from "@/lib/safe-action";
23
import { coursesTags, courses, users, tags } from "@/db/schema";
3-
import { type CourseFormValues } from "@/lib/validations/course";
4+
import { courseSchema, type CourseFormValues } from "@/lib/validations/course";
45
import { db } from "@/db";
5-
import { auth } from "@/lib/auth";
6-
import { headers } from "next/headers";
76
import { eq } from "drizzle-orm";
8-
import { CreateResponse, CourseWithData } from "@/lib/types";
7+
import { CourseWithData } from "@/lib/types";
98

10-
export async function createCourse(data: CourseFormValues): Promise<CreateResponse> {
11-
const user = await auth.api.getSession({
12-
headers: await headers()
13-
});
14-
if (!user) throw new Error("Unauthorized");
15-
try {
16-
const existingCourse = await db.select().from(courses).where(eq(courses.title, data.title));
17-
if (existingCourse.length > 0) {
18-
return { success: false, message: "Course with title already made."}
19-
}
20-
const [newCourse] = await db.insert(courses).values({
21-
title: data.title,
22-
description: data.description,
23-
difficulty: data.difficulty,
24-
createdBy: user.user.id,
25-
}).returning({ id: courses.id});
26-
const courseId = newCourse.id;
27-
if (data.tags && data.tags.length > 0) {
28-
for (const tagName of data.tags) {
29-
const tag = await db.select({ id: tags.id }).from(tags).where(eq(tags.tagName, tagName)).limit(1);
30-
if (tag.length > 0) {
31-
await db.insert(coursesTags).values({
32-
courseId: courseId,
33-
tagId: tag[0].id,
34-
});
35-
}
36-
}
9+
export const createCourseAction = adminClient
10+
.schema(courseSchema)
11+
.action(async ({ parsedInput, ctx }) => {
12+
const { title, description, difficulty, tags: tagNames } = parsedInput;
13+
const existing = await db
14+
.select()
15+
.from(courses)
16+
.where(eq(courses.title, title));
17+
if (existing.length > 0) {
18+
return { success: false, message: "Course with this title already exists.",}
19+
}
20+
const [created] = await db
21+
.insert(courses)
22+
.values({
23+
title,
24+
description,
25+
difficulty,
26+
createdBy: ctx.userId,
27+
})
28+
.returning();
29+
if (tagNames?.length) {
30+
for (const tagName of tagNames) {
31+
const [tag] = await db
32+
.select()
33+
.from(tags)
34+
.where(eq(tags.tagName, tagName));
35+
36+
if (tag) {
37+
await db.insert(coursesTags).values({
38+
courseId: created.id,
39+
tagId: tag.id,
40+
});
3741
}
38-
return { success: true, message: "Course created successfully!" };
39-
} catch (error) {
40-
console.error(error);
41-
return { success: false, message: "Failed to create course." };
42+
}
4243
}
43-
}
44+
return { success: true, message: "Course created!",};
45+
});
4446

4547
export async function getAllCourses(): Promise<CourseWithData[]> {
4648
const rows = await db
@@ -77,4 +79,34 @@ export async function getAllCourses(): Promise<CourseWithData[]> {
7779
}
7880

7981
return Object.values(coursesMap);
82+
}
83+
84+
export async function getCourseById(id: number) {
85+
const rows = await db
86+
.select({
87+
id: courses.id,
88+
title: courses.title,
89+
description: courses.description,
90+
difficulty: courses.difficulty,
91+
createdBy: users.name,
92+
tagName: tags.tagName,
93+
})
94+
.from(courses)
95+
.leftJoin(users, eq(users.id, courses.createdBy))
96+
.leftJoin(coursesTags, eq(coursesTags.courseId, courses.id))
97+
.leftJoin(tags, eq(tags.id, coursesTags.tagId))
98+
.where(eq(courses.id, id));
99+
100+
if (rows.length === 0) return null;
101+
102+
const course = {
103+
id: rows[0].id,
104+
title: rows[0].title,
105+
description: rows[0].description,
106+
difficulty: rows[0].difficulty,
107+
createdBy: rows[0].createdBy,
108+
tags: rows.map((r) => r.tagName).filter(Boolean),
109+
};
110+
111+
return course;
80112
}

src/actions/admin/lesson.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
'use server';
22
import { revalidatePath } from "next/cache";
33
import { db } from "@/db/index";
4-
import { lessons } from "@/db/schema";
4+
import { lessons, units } from "@/db/schema";
55
import { lessonFormSchema } from "@/lib/validations/lesson";
66
import { actionClient } from "@/lib/safe-action";
7+
import { eq, asc } from "drizzle-orm";
78

89
export const createLessonAction = actionClient
910
.schema(lessonFormSchema)
@@ -33,4 +34,22 @@ export const createLessonAction = actionClient
3334
revalidatePath(`/admin/courses/${courseId}`);
3435

3536
return { success: true };
36-
});
37+
});
38+
39+
export async function getLessonsForCourse(courseId: number) {
40+
return await db
41+
.select({
42+
lessonId: lessons.id,
43+
unitId: lessons.unitId,
44+
mediaType: lessons.mediaType,
45+
contentUrl: lessons.contentUrl,
46+
contentBlobId: lessons.contentBlobId,
47+
metadata: lessons.metadata,
48+
lessonPosition: lessons.position,
49+
unitPosition: units.position,
50+
})
51+
.from(units)
52+
.innerJoin(lessons, eq(units.id, lessons.unitId))
53+
.where(eq(units.courseId, courseId))
54+
.orderBy(asc(units.position), asc(lessons.position));
55+
}

src/actions/admin/units.ts

Lines changed: 36 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,50 @@
1-
// src/app/admin/courses/[courseId]/lesson/create/actions.ts
21
'use server';
3-
4-
import { revalidatePath } from "next/cache";
52
import { db } from "@/db/index";
63
import { units } from "@/db/schema";
7-
import { createUnitSchema } from "@/lib/validations/unit";
8-
import { actionClient } from "@/lib/safe-action";
4+
import { unitFormSchema } from "@/lib/validations/unit";
5+
import { adminClient } from "@/lib/safe-action";
6+
import { eq, asc, sql, and } from "drizzle-orm";
97

10-
// Create unit (for the modal)
11-
export const createUnitAction = actionClient
12-
.schema(createUnitSchema)
8+
export const createUnitAction = adminClient
9+
.schema(unitFormSchema)
1310
.action(async ({ parsedInput }) => {
1411
const { title, courseId } = parsedInput;
15-
// Minimal insert: courseId (text) + title; position defaults to 1
16-
const [unit] = await db
12+
const [maxPositionRow] = await db
13+
.select({ maxPosition: sql<number>`MAX(${units.position})` })
14+
.from(units)
15+
.where(eq(units.courseId, courseId));
16+
17+
const newPosition = (maxPositionRow?.maxPosition ?? 0) + 1;
18+
19+
const existing = await db
20+
.select()
21+
.from(units)
22+
.where(and(eq(units.title, title), eq(units.courseId, courseId)));
23+
if (existing.length > 0) {
24+
return { success: false, message: "Unit with this title already exists.", }
25+
}
26+
const [newUnit] = await db
1727
.insert(units)
1828
.values({
1929
courseId,
2030
title,
31+
position: newPosition,
2132
})
22-
.returning();
23-
24-
revalidatePath(`/admin/courses/${courseId}`);
33+
.returning({ id: units.id, title: units.title});
2534

26-
return {
27-
success: true,
28-
unitId: unit.id,
29-
unitTitle: unit.title,
35+
return {
36+
success: true,
37+
message: "Success",
38+
unitId: newUnit.id,
39+
unitTitle: newUnit.title,
3040
};
3141
});
42+
43+
44+
export async function getUnitsForCourse(courseId: number) {
45+
return await db
46+
.select()
47+
.from(units)
48+
.where(eq(units.courseId, courseId))
49+
.orderBy(asc(units.position));
50+
}

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { db } from "@/db/index";
22
import { units } from "@/db/schema";
33
import { eq } from "drizzle-orm";
4-
import { CreateLessonForm } from "@/components/admin/CreateLessonForm";
4+
import CreateLessonForm from "@/components/admin/CreateLessonForm";
55
import {
66
Card,
77
CardHeader,
@@ -16,11 +16,12 @@ type PageProps = {
1616

1717
export default async function CreateLessonPage({ params }: PageProps) {
1818
const { courseId } = await params;
19+
const id = Number(courseId);
1920

2021
const courseUnits = await db
2122
.select()
2223
.from(units)
23-
.where(eq(units.courseId, courseId));
24+
.where(eq(units.courseId, id));
2425

2526
return (
2627
<div className="mx-auto flex max-w-3xl flex-col gap-6">
@@ -44,7 +45,7 @@ export default async function CreateLessonPage({ params }: PageProps) {
4445
</CardHeader>
4546
<CardContent className="pt-2">
4647
<CreateLessonForm
47-
courseId={courseId}
48+
courseId={id}
4849
initialUnits={courseUnits.map((u) => ({
4950
id: u.id,
5051
title: u.title ?? `Unit ${u.id}`,

0 commit comments

Comments
 (0)