diff --git a/src/features/grammar-point/loader/errors.ts b/src/features/grammar-point/loader/errors.ts new file mode 100644 index 0000000..904a229 --- /dev/null +++ b/src/features/grammar-point/loader/errors.ts @@ -0,0 +1,16 @@ +export class GrammarPointsLoaderError extends Error { + constructor( + message: string, + public code?: number, + ) { + super(message); + this.name = "GrammarPointsLoaderError"; + } +} +export class GrammarPointNotFoundError extends GrammarPointsLoaderError { + constructor(message: string) { + super(message); + this.name = "GrammarPointNotFoundError"; + this.code = 404; + } +} diff --git a/src/features/grammar-point/loader/index.ts b/src/features/grammar-point/loader/index.ts new file mode 100644 index 0000000..7b9a6f7 --- /dev/null +++ b/src/features/grammar-point/loader/index.ts @@ -0,0 +1,82 @@ +import type { LiveLoader } from "astro/loaders"; +import { + type Context, + type GrammarPoint, + fetchAllGrammarPoints, + fetchGrammarPoint, + fetchGrammarPoints, +} from "grammar-sdk"; +import logger from "libs/logger"; +import { GrammarPointNotFoundError, GrammarPointsLoaderError } from "./errors"; + +const grammarPointsToEntries = (grammarPoints: GrammarPoint[]) => ({ + entries: grammarPoints.map(grammarPointToEntry), +}); + +const grammarPointToEntry = (grammarPoint: GrammarPoint) => ({ + id: grammarPoint.id, + data: grammarPoint, + cacheHint: { + tags: [`grammar-point-${grammarPoint.id}`], + }, +}); + +export function grammarPointsLoader(): LiveLoader< + GrammarPoint, + { id: string; context: Context }, + { ids: string[]; context: Context } | { context: Context } +> { + return { + name: "grammarPoints", + loadCollection: async ({ filter }) => { + try { + if (filter && "ids" in filter) { + return { + ...grammarPointsToEntries( + await fetchGrammarPoints(filter.ids, filter.context), + ), + cacheHint: { + tags: [ + `grammar-points-${filter.context.user.role}-${filter.ids.join(",")}`, + ], + }, + }; + } + + const context = filter?.context ?? { user: { role: "guest" } }; + + return { + ...grammarPointsToEntries(await fetchAllGrammarPoints(context)), + cacheHint: { + tags: [`grammar-points-${context.user.role}`], + }, + }; + } catch (error) { + const message = "Failed to load grammar points"; + logger.error(error, message); + return { + error: new GrammarPointsLoaderError(message, 500), + }; + } + }, + loadEntry: async ({ filter: { id, context } }) => { + try { + const entry = await fetchGrammarPoint(id, context); + if (!entry) { + return { + error: new GrammarPointNotFoundError( + `Grammar point with id ${id} not found`, + ), + }; + } + return grammarPointToEntry(entry); + } catch (error) { + const message = `Failed to load grammar point with id ${id}`; + logger.error(error, message); + return { + error: new GrammarPointsLoaderError(message, 500), + }; + } + }, + }; +} diff --git a/src/live.config.ts b/src/live.config.ts new file mode 100644 index 0000000..8490db4 --- /dev/null +++ b/src/live.config.ts @@ -0,0 +1,8 @@ +import { defineLiveCollection } from "astro:content"; +import { grammarPointsLoader } from "./features/grammar-point/loader"; + +const grammarPoints = defineLiveCollection({ + loader: grammarPointsLoader(), +}); + +export const collections = { grammarPoints }; diff --git a/src/middleware.ts b/src/middleware.ts index db45630..d8939e7 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -4,6 +4,7 @@ import { isUserAdmin } from "libs/auth/admin"; const PATHS_TO_IGNORE = [ "signin", + "signup", "register", "auth", "login", @@ -12,6 +13,7 @@ const PATHS_TO_IGNORE = [ "help", "privacy-policy", "sitemap.xml", + "error", ]; export const onRequest = defineMiddleware( diff --git a/src/pages/error.astro b/src/pages/error.astro new file mode 100644 index 0000000..0896bfe --- /dev/null +++ b/src/pages/error.astro @@ -0,0 +1,14 @@ +--- +import Layout from "~/layouts/Layout.astro"; +--- + + +
+
+

+ We're experiencing technical difficulties. Please try again later. +

+ Go back to home +
+
+
diff --git a/src/pages/grammar/[id]/index.astro b/src/pages/grammar/[id]/index.astro index 2df5449..5ffd9fb 100644 --- a/src/pages/grammar/[id]/index.astro +++ b/src/pages/grammar/[id]/index.astro @@ -2,24 +2,41 @@ import { GET as fetchGpInReview } from "@api/sr/review/list"; import { GrammarPoint } from "@components/solid/grammar-point/GrammarPoint"; import { ReviewToggleButton } from "@components/solid/grammar-point/ReviewToggleButton"; -import { fetchGrammarPoint } from "grammar-sdk"; import { Button } from "packages/ui/button"; import { Anchor } from "ui/anchor"; import Layout from "../../../layouts/Layout.astro"; import { contextFromAstro } from "~/libs/context"; +import { getLiveEntry } from "astro:content"; const { id } = Astro.params; const backTo = Astro.url.searchParams.get("backTo") ?? undefined; -const grammarPoint = id - ? await fetchGrammarPoint(id, contextFromAstro(Astro)) - : undefined; +if (!id || isNaN(Number(id))) { + return Astro.redirect("/grammar"); +} + +const { entry: grammarPointEntry, error: grammarPointError } = + await getLiveEntry("grammarPoints", { + id, + context: contextFromAstro(Astro), + }); + +if (grammarPointError) { + return Astro.redirect("/error"); +} + +if (!grammarPointEntry) { + return Astro.redirect("/grammar"); +} + const inReview = id ? (( (await (await fetchGpInReview(Astro)).json()) ?? ([] as string[]) )?.includes?.(id) ?? false) : false; const user = Astro.locals.user; + +const grammarPoint = grammarPointEntry.data; --- diff --git a/src/pages/grammar/[id]/practice.astro b/src/pages/grammar/[id]/practice.astro index 71facbb..605da0b 100644 --- a/src/pages/grammar/[id]/practice.astro +++ b/src/pages/grammar/[id]/practice.astro @@ -1,14 +1,27 @@ --- -import { fetchGrammarPoints } from "grammar-sdk"; import { simpleShuffle } from "utils/array"; import { Exercises } from "../../../components/solid/Exercise/Exercises"; import Layout from "../../../layouts/Layout.astro"; import { contextFromAstro } from "~/libs/context"; +import { getLiveCollection } from "astro:content"; const ids = Astro.params.id?.split("-") ?? []; -const grammarPoints = await fetchGrammarPoints(ids, contextFromAstro(Astro)); -const exercises = simpleShuffle(grammarPoints.flatMap((g) => g.exercises)); +const { entries: grammarPoints, error } = await getLiveCollection( + "grammarPoints", + { + ids, + context: contextFromAstro(Astro), + }, +); + +if (error) { + return Astro.redirect("/error"); +} + +const exercises = simpleShuffle( + (grammarPoints ?? []).flatMap((g) => g.data.exercises), +); --- diff --git a/src/pages/grammar/index.astro b/src/pages/grammar/index.astro index 9642ab6..34c2ba1 100644 --- a/src/pages/grammar/index.astro +++ b/src/pages/grammar/index.astro @@ -1,16 +1,28 @@ --- import { GET as fetchGpInReview } from "@api/sr/review/list"; -import { fetchAllGrammarPoints } from "grammar-sdk"; import { Grammar } from "../../components/solid/grammar/Grammar"; import Layout from "../../layouts/Layout.astro"; import { contextFromAstro } from "~/libs/context"; +import { getLiveCollection } from "astro:content"; + +const { entries, error } = await getLiveCollection("grammarPoints", { + context: contextFromAstro(Astro), +}); + +if (error) { + return Astro.redirect("/error"); +} -const grammar = await fetchAllGrammarPoints(contextFromAstro(Astro)); const inReview = (await (await fetchGpInReview(Astro)).json()) as string[]; const maybeMode = Astro.url.searchParams.get("mode"); const mode = maybeMode === "cram" ? "cram" : "nav"; --- - + entry.data) ?? []} + inReview={inReview} + mode={mode} + /> diff --git a/src/pages/grammar/practice/result.astro b/src/pages/grammar/practice/result.astro index 9155ee9..44946c3 100644 --- a/src/pages/grammar/practice/result.astro +++ b/src/pages/grammar/practice/result.astro @@ -1,16 +1,22 @@ --- import { SessionResult } from "@components/solid/Exercise/SessionResult"; import Layout from "@layouts/Layout.astro"; -import { fetchAllGrammarPoints } from "grammar-sdk"; +import { getLiveCollection } from "astro:content"; import { contextFromAstro } from "~/libs/context"; -const grammar = await fetchAllGrammarPoints(contextFromAstro(Astro)); +const { entries: grammar, error } = await getLiveCollection("grammarPoints", { + context: contextFromAstro(Astro), +}); + +if (error) { + return Astro.redirect("/error"); +} --- entry.data) ?? []} /> diff --git a/src/pages/sr/review/[sessionId]/result.astro b/src/pages/sr/review/[sessionId]/result.astro index 59e3b8a..f87b3c1 100644 --- a/src/pages/sr/review/[sessionId]/result.astro +++ b/src/pages/sr/review/[sessionId]/result.astro @@ -2,7 +2,7 @@ import { SessionResult } from "@components/solid/Exercise/SessionResult"; import Layout from "@layouts/Layout.astro"; import { actions } from "astro:actions"; -import { fetchAllGrammarPoints } from "grammar-sdk"; +import { getLiveCollection } from "astro:content"; import { contextFromAstro } from "~/libs/context"; const sessionResult = Astro.params.sessionId @@ -10,13 +10,22 @@ const sessionResult = Astro.params.sessionId sessionId: Astro.params.sessionId, }) : undefined; -const grammar = await fetchAllGrammarPoints(contextFromAstro(Astro)); +const { entries: grammar, error } = await getLiveCollection("grammarPoints", { + context: contextFromAstro(Astro), +}); + +if (error) { + return Astro.redirect("/error"); +} --- { sessionResult?.data ? ( - + entry.data) ?? []} + /> ) : (
No session result found, probably it hasn't been calculated yet. Please