Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ADD Deno Kv #18

Merged
merged 3 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions components/Collapse.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Course } from "../utils/types.ts";
import CourseCard from "./CourseCard.tsx";

export default function Collapse(
{ title, courses }: { title: string; courses: Course[] },
{ title, courses, completed }: { title: string; courses: Course[]; completed: string[] },
) {
return (
<div class="collapse collapse-arrow bg-base-300">
Expand All @@ -12,7 +12,7 @@ export default function Collapse(
</div>
<div class="collapse-content flex flex-col">
{courses.map((course) => (
<CourseCard key={course.slug} course={course} />
<CourseCard isDone={completed ? completed.includes(course.slug) : false} key={course.slug} course={course} />
))}
</div>
</div>
Expand Down
6 changes: 3 additions & 3 deletions components/CourseCard.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Course } from "../utils/types.ts";

import ProgressCheck from "../islands/ProgressCheck.tsx";
import ProgressCheck from "./ProgressCheck.tsx";

export default function CourseCard(props: { course: Course }) {
export default function CourseCard(props: { course: Course; isDone: boolean }) {
const { course } = props;
return (
<a
Expand All @@ -12,7 +12,7 @@ export default function CourseCard(props: { course: Course }) {
style={{ order: course.order }}
>
<h3 class="text-gray-500 font-bold flex gap-1 items-center">
<ProgressCheck slug={course.slug} />
<ProgressCheck isDone={props.isDone} />
{course.title}
</h3>
</a>
Expand Down
6 changes: 4 additions & 2 deletions components/Courses.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import Collapse from "./Collapse.tsx";
import CourseCard from "./CourseCard.tsx";

export default function Courses(
{ courses }: { courses: (Course | CourseGroup)[] },
{ courses, completed }: { courses: (Course | CourseGroup)[], completed: string[] },
) {

return (
<>
<h1 class="text-5xl font-bold z-10 mb-2">الاساسيات</h1>
Expand All @@ -16,6 +17,7 @@ export default function Courses(
return (
<div key={index} class="mt-1">
<Collapse
completed={completed}
title={course.label}
courses={course.courses}
/>
Expand All @@ -25,7 +27,7 @@ export default function Courses(
// Single course
return (
<div key={course.slug}>
<CourseCard course={course} />
<CourseCard course={course} isDone={completed ? completed.includes(course.slug) : false} />
</div>
);
}
Expand Down
8 changes: 8 additions & 0 deletions components/ProgressCheck.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import IconCircleCheckFilled from "https://deno.land/x/tabler_icons_tsx@0.0.5/tsx/circle-check-filled.tsx";
import IconCircle from "https://deno.land/x/tabler_icons_tsx@0.0.5/tsx/circle.tsx";

export default function ProgressCheck(props: {isDone : boolean}) {
return props.isDone
? <IconCircleCheckFilled aria-hidden="true" class="h-4 w-4" />
: <IconCircle aria-hidden="true" class="h-4 w-4" />;
}
37 changes: 20 additions & 17 deletions components/ProgressPageSplit.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
import Progress from "../islands/Progress.tsx";
import Icon from "./Icon.tsx";
import ProgressBar from "./ProgressBar.tsx";

export default function ProgressPageSplit() {
return (
<>
<div className="flex gap-2">
<h1 className="text-2xl font-bold">مرحباً بك في</h1>
<Icon />
</div>
<p className="text-lg">
وجهتك الأمثل لاكتساب مهارات جافاسكربت بسهولة وفعالية. رحلة تعليمية شيقة
تمتد من الأساسيات إلى المستويات المتقدمة
</p>
<h2 className="text-xl font-bold">تقدمك في إنجاز الدروس:</h2>
{/* TODO: Make this a component */}
<Progress />
</>
);
}
export default function ProgressPageSplit(props: { completed: number, total: number }) {
return (
<>
<div className="flex gap-2">
<h1 className="text-2xl font-bold">مرحباً بك في</h1>
<Icon />
</div>
<p className="text-lg">
وجهتك الأمثل لاكتساب مهارات جافاسكربت بسهولة وفعالية. رحلة تعليمية شيقة
تمتد من الأساسيات إلى المستويات المتقدمة
</p>
<h2 className="text-xl font-bold">تقدمك في إنجاز الدروس:</h2>
<div className="flex flex-col gap-2">
<h1 className="text-sm">لقد تعلمت {props.completed} من أصل {props.total} درس</h1>
<ProgressBar progress={Math.floor((props.completed / props.total) * 100)} />
</div>
</>
);
}
5 changes: 3 additions & 2 deletions deno.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"check": "deno fmt --check && deno lint && deno check **/*.ts && deno check **/*.tsx",
"cli": "echo \"import '\\$fresh/src/dev/cli.ts'\" | deno run --unstable -A -",
"manifest": "deno task cli manifest $(pwd)",
"start": "deno run -A --watch=static/,routes/ dev.ts",
"start": "deno run -A --watch=static/,routes/ --unstable dev.ts",
"build": "deno run -A dev.ts build",
"preview": "deno run -A main.ts",
"update": "deno run -A -r https://fresh.deno.dev/update ."
Expand All @@ -31,7 +31,8 @@
"tailwindcss/plugin": "npm:/tailwindcss@3.3.5/plugin.js",
"$std/": "https://deno.land/std@0.208.0/",
"$gfm": "https://deno.land/x/gfm@0.1.26/mod.ts",
"daisyui": "npm:daisyui@latest"
"daisyui": "npm:daisyui@latest",
"zod": "https://esm.sh/zod@3.22.4"
},
"compilerOptions": {
"jsx": "react-jsx",
Expand Down
6 changes: 4 additions & 2 deletions fresh.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
import * as $_slug_ from "./routes/[...slug].tsx";
import * as $_404 from "./routes/_404.tsx";
import * as $_app from "./routes/_app.tsx";
import * as $_middleware from "./routes/_middleware.ts";
import * as $about from "./routes/about.tsx";
import * as $api_test from "./routes/api/test.ts";
import * as $api_test_finsh from "./routes/api/test/finsh.ts";
import * as $group_slug_ from "./routes/group/[slug].tsx";
import * as $index from "./routes/index.tsx";
import * as $Editor from "./islands/Editor.tsx";
Expand All @@ -24,8 +25,9 @@ const manifest = {
"./routes/[...slug].tsx": $_slug_,
"./routes/_404.tsx": $_404,
"./routes/_app.tsx": $_app,
"./routes/_middleware.ts": $_middleware,
"./routes/about.tsx": $about,
"./routes/api/test.ts": $api_test,
"./routes/api/test/finsh.ts": $api_test_finsh,
"./routes/group/[slug].tsx": $group_slug_,
"./routes/index.tsx": $index,
},
Expand Down
16 changes: 12 additions & 4 deletions islands/Editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,12 @@ export default function Editor(props: EditorProps) {
{
msg: "لا يوجد اختبارات لهذا السؤال",
type: "info",
}
)
},
);
setTesting(false);
return;
}

// deno-lint-ignore prefer-const
let isPass = false;
// deno-lint-ignore prefer-const
Expand All @@ -61,7 +61,15 @@ export default function Editor(props: EditorProps) {
msg: "تم تجاوز الاختبارات بنجاح",
type: "success",
});
localStorage.setItem(props.slug, "done");
fetch("/api/test/finsh", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
courseslug: props.slug,
}),
});
setTesting(false);
return;
} else {
Expand Down
3 changes: 1 addition & 2 deletions routes/_app.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { type PageProps } from "$fresh/server.ts";
import { Handlers, type PageProps, FreshContext } from "$fresh/server.ts";
import NavBar from "../components/Nav.tsx";
import Toast from "../islands/Toast.tsx";
import { populateCache } from "../utils/course-cache.ts";

populateCache();

export default function App({ Component }: PageProps) {
return (
<html dir="rtl" lang="ar">
Expand Down
24 changes: 24 additions & 0 deletions routes/_middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { FreshContext } from "$fresh/server.ts";
import {getCookies, Cookie, setCookie} from "$std/http/mod.ts";
import { createStudent } from "../utils/KV.ts";
export async function handler(
req: Request,
ctx: FreshContext,
) {
const resp = await ctx.next();
const cookies = getCookies(req.headers);
const sessionId = cookies["sessionId"] || "";
if (!sessionId) {
try {
const student = await createStudent();
const cookie: Cookie = {
name: "sessionId",
value: student.sessionId,
}
setCookie(resp.headers, cookie);
} catch {
return ctx.renderNotFound();
}
}
return resp;
}
5 changes: 0 additions & 5 deletions routes/api/test.ts

This file was deleted.

19 changes: 19 additions & 0 deletions routes/api/test/finsh.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Handlers } from "$fresh/server.ts";
import { getCookies } from "$std/http/mod.ts";
import { addCompletedCourse } from "../../../utils/KV.ts";
interface FinshTest {
courseslug: string;
}
export const handler: Handlers<FinshTest> = {
async POST(req, _ctx) {
try {
const FinshTest = (await req.json() as FinshTest)
const courseslug = FinshTest.courseslug
const sessionId = getCookies(req.headers)?.sessionId;
addCompletedCourse(sessionId, courseslug)
} catch (error) {
return new Response(error.message, { status: 500 });
}
return new Response("ok");
},
};
33 changes: 23 additions & 10 deletions routes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,39 @@ import { Head } from "$fresh/runtime.ts";
import { Handlers } from "$fresh/server.ts";
import { PageProps } from "$fresh/server.ts";

import { getCourses } from "../utils/course.ts";
import { getCourses, getNumberOfCourses } from "../utils/course.ts";
import { Course, CourseGroup } from "../utils/types.ts";
import { cache } from "../utils/course-cache.ts";

import Footer from "../components/Footer.tsx";
import Courses from "../components/Courses.tsx";
import ProgressPageSplit from "../components/ProgressPageSplit.tsx";
import { getCookies } from "$std/http/mod.ts";
import { getStudent } from "../utils/KV.ts";


export const handler: Handlers<{ courses: (Course | CourseGroup)[] }> = {
interface Props {
courses: (Course | CourseGroup)[];
completed: string[];
total: number;
}
export const handler: Handlers<Props> = {
async GET(_req, ctx) {
const courses = await getCourses(cache);
return ctx.render(courses);
const session = getCookies(_req.headers)["sessionId"];
const completed = (await getStudent(session)).completedCourses;
const total = getNumberOfCourses(courses.courses);
return ctx.render({
completed,
total,
courses: courses.courses,
});
},
};

export default function BlogIndexPage(
props: PageProps<{ courses: (Course | CourseGroup)[] }>,
) {
const { courses } = props.data;
props: PageProps<Props>,
) {
const { courses, completed, total } = props.data;
return (
<>
<Head>
Expand All @@ -39,12 +52,12 @@ export default function BlogIndexPage(
content="وجهتك الأمثل لاكتساب مهارات جافاسكربت بسهولة وفعالية. رحلة تعليمية شيقة تمتد من الأساسيات إلى المستويات المتقدمة"
/>
</Head>
<main className="flex min-w-screen-md px-4 pt-12 mx-auto mb-6 max-sm:flex-col-reverse">
<main className="flex min-w-screen-md w-[75%] pt-12 mx-auto mb-6 max-sm:flex-col-reverse">
<div className="max-sm:w-full w-1/2 p-4">
<Courses courses={courses} />
<Courses completed={completed} courses={courses} />
</div>
<div className="max-sm:w-full w-1/2 p-4 flex flex-col gap-2">
<ProgressPageSplit />
<ProgressPageSplit completed={completed.length} total={total} />
</div>
</main>
<Footer />
Expand Down
1 change: 1 addition & 0 deletions tailwind.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export default {
],
plugins: [daisyui],
daisyui: {
log: false,
themes: ["dracula", "nord"],
},
} satisfies Config;
46 changes: 46 additions & 0 deletions utils/KV.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { student, Result } from "./types.ts";

const kv = await Deno.openKv();
export const createStudent = async (): Promise<student> => {
const student: student = {
sessionId: crypto.randomUUID(),
completedCourses: [],
}
const res = await kv.set(["student", student.sessionId], student)
if (res.ok) {
return student
} else {
throw new Error("Failed to create student")
}
}
export const getStudent = async (sessionId: string): Promise<student> => {
const student = await kv.get(["student", sessionId])
if (student) {
return student.value as student
} else {
throw new Error("Failed to get student")
}
}
export const updateStudent = async (sessionId: string, student: student): Promise<Result> => {
const res = await kv.set(["student", sessionId], student)
if (res.ok) {
return res
} else {
throw new Error("Failed to update student")
}
}
export const addCompletedCourse = async (sessionId: string, course: string) : Promise<Result> => {
const student = await getStudent(sessionId)
if (student.completedCourses.includes(course)) {
throw new Error("Course already completed")
}
const completedCourses = student.completedCourses
completedCourses.push(course)
student.completedCourses = completedCourses
try {
const res = await updateStudent(sessionId, student)
return res
} catch {
throw new Error("Failed to add completed course")
}
}
12 changes: 12 additions & 0 deletions utils/course.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,3 +103,15 @@ export async function getCourses(
console.log(`Caching data took ${(endTime - startTime) / 1000} seconds`);
return cache;
}

export function getNumberOfCourses(courses: (Course | CourseGroup)[]) {
let count = 0;
for (const course of courses) {
if ("courses" in course) {
count += course.courses.length;
} else {
count++;
}
}
return count;
}
Loading
Loading