Skip to content
Draft
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ erl_crash.dump
/blob-report/
/playwright/.cache/
/playwright/.auth/
.wrangler
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
},
"scripts": {
"dev": "pnpm run --filter=web dev",
"deploy": "pnpm run build && wrangler deploy",
"setup-db": "pnpm run migrate && pnpm run seed",
"reset-db": "supabase db reset && pnpm run setup-db",
"test": "vitest run --exclude '**/tests/integration/**'",
Expand Down
6 changes: 5 additions & 1 deletion packages/db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,15 @@ const connectionString = process.env.DATABASE_URL ?? "";
// Disable prefetch as it is not supported for "Transaction" pool mode
const client = postgres(connectionString, { prepare: false });
export const db = drizzle(client, { schema: { ...schema, ...tmpSchema } });
export const defaultDb = () => {
// @ts-expect-error: env is defined in wrangler.toml
const client = postgres(env.HYPERDRIVE.connectionString, { prepare: false });
return drizzle(client, { schema: { ...schema, ...tmpSchema } });
};
export type DbClient = typeof db;

export const makeDb = (connectionString: string) =>
drizzle(postgres(connectionString, { prepare: false }), {
schema: { ...schema, ...tmpSchema },
});

export type Transaction = Parameters<Parameters<DbClient["transaction"]>[0]>[0];
18 changes: 10 additions & 8 deletions packages/grammar-sdk/src/db.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { eq, inArray, sql } from "drizzle-orm";
import { db, type DbClient, type Transaction } from "db";
import { type DbClient, type Transaction, defaultDb } from "db";
import {
GrammarPoints,
type CreateGrammarPoint,
Expand Down Expand Up @@ -29,7 +29,7 @@ import {

export const getGrammarPoint = async (
id: number,
dbClient: DbClient = db,
dbClient: DbClient = defaultDb(),
): Promise<GrammarPoint | undefined> => {
const grammarDto = await dbClient.query.grammarPointsTmp.findFirst({
where: eq(grammarPointsTmp.id, id),
Expand All @@ -43,7 +43,7 @@ export const getGrammarPoint = async (

export const getGrammarPoints = async (
ids?: number[],
dbClient: DbClient = db,
dbClient: DbClient = defaultDb(),
): Promise<GrammarPoint[]> => {
const mainQuery = {
where: ids ? inArray(grammarPointsTmp.id, ids) : undefined,
Expand All @@ -59,7 +59,7 @@ export const getGrammarPoints = async (

export const getExercisesByGrammarPointIds = async (
grammarPointIds: number[],
dbClient: DbClient = db,
dbClient: DbClient = defaultDb(),
): Promise<Exercise[]> => {
const exercisesDto = await dbClient.query.exercisesTmp.findMany({
where: inArray(exercisesTmp.grammarPointId, grammarPointIds),
Expand All @@ -81,7 +81,7 @@ export const getExercisesByGrammarPointIds = async (

export const getExercisesByGrammarPointId = async (
grammarPointId: number,
dbClient: DbClient = db,
dbClient: DbClient = defaultDb(),
): Promise<Exercise[]> => {
const exercisesDto = await dbClient.query.exercisesTmp.findMany({
where: eq(exercisesTmp.grammarPointId, grammarPointId),
Expand Down Expand Up @@ -132,7 +132,7 @@ export const createGrammarPoint = async (
}
const maxOrder = GrammarPoints.maxOrder(grammarPoints);

const created = await db
const created = await defaultDb()
.insert(grammarPointsTmp)
.values({
...data,
Expand Down Expand Up @@ -218,6 +218,8 @@ export const updateGrammarPoint = async (
return err("At least one field is required to update a grammar point.");
}

const db = defaultDb();

await db
.update(grammarPointsTmp)
.set(updateData)
Expand Down Expand Up @@ -261,7 +263,7 @@ export const updateGrammarPointsOrder = async (
) AS v(id, new_order)
WHERE o.id = v.id
`;
await db.execute(sqlUpdate);
await defaultDb().execute(sqlUpdate);

return ok(true);
};
Expand Down Expand Up @@ -402,7 +404,7 @@ export const putExercises = async (

export const getLabels = async (
context: Context,
dbClient: DbClient = db,
dbClient: DbClient = defaultDb(),
): Promise<Result<Label[], string | AuthorizationError>> => {
if (!Context.isAdmin(context)) {
return err(
Expand Down
16 changes: 8 additions & 8 deletions packages/space-repetition/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
type Exercise,
} from "grammar-sdk";
import { Map as IMap, Seq } from "immutable";
import { db } from "db";
import { defaultDb } from "db";
import { Context, type User } from "auth";
import { NaiveAlgorithm } from "./src/NaiveAlgorithm";
import { Session } from "./src/session";
Expand Down Expand Up @@ -41,7 +41,7 @@ export const getLessons = async (
amount: number,
user: User,
): Promise<Lesson[]> => {
const attempts = await getAttempts(db, user);
const attempts = await getAttempts(defaultDb(), user);

const grammarPoints = await fetchAllGrammarPoints(Context.fromUser(user));

Expand All @@ -65,11 +65,11 @@ export const addAttempt = async (
attempt: Attempt,
user: User,
): Promise<void> => {
await saveAttempt(db, attempt, user);
await saveAttempt(defaultDb(), attempt, user);
};

export const getNextRound = async (user: User): Promise<Round[]> => {
const attempts = await getAttempts(db, user);
const attempts = await getAttempts(defaultDb(), user);

const spaceRepetition = SpaceRepetition(attempts);
const nextRound = spaceRepetition.nextRound(algorithm, settings);
Expand Down Expand Up @@ -130,7 +130,7 @@ export const countStreak = async (
timezone: string,
user: User,
): Promise<number> => {
const attempts = await getAttempts(db, user);
const attempts = await getAttempts(defaultDb(), user);
return countStreakUtils(
today,
timezone,
Expand Down Expand Up @@ -161,21 +161,21 @@ export const getInReviewByTorfl = async (user: User) => {
};

export const getSchedule = async (user: User): Promise<Schedule> => {
const attempts = await getAttempts(db, user);
const attempts = await getAttempts(defaultDb(), user);
const spaceRepetition = SpaceRepetition(attempts);
return spaceRepetition.getSchedule(algorithm, settings);
};

export const listGrammarPointsInReview = async (
user: User,
): Promise<string[]> => {
const attempts = await getAttempts(db, user);
const attempts = await getAttempts(defaultDb(), user);
const spaceRepetition = SpaceRepetition(attempts);
return spaceRepetition.repeatingGrammarPoints();
};

export const getSessionResult = async (user: User, sessionId: string) => {
const session = await getSession(db, user, sessionId);
const session = await getSession(defaultDb(), user, sessionId);
return Session.calculateResult(session);
};

Expand Down
10 changes: 6 additions & 4 deletions packages/web/astro.config.mjs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
// @ts-check
import node from "@astrojs/node";
import solid from "@astrojs/solid-js";
import tailwindcss from "@tailwindcss/vite";
import { defineConfig, envField, logHandlers, memoryCache } from "astro/config";

import cloudflare from "@astrojs/cloudflare";

export default defineConfig({
site: "https://grumma.org",
security: {
Expand All @@ -22,10 +23,11 @@ export default defineConfig({
integrations: [solid()],
vite: {
plugins: [tailwindcss()],
resolve: {
conditions: ["workerd"],
},
},
cache: { provider: memoryCache() },
output: "server",
adapter: node({
mode: "standalone",
}),
adapter: cloudflare(),
});
10 changes: 7 additions & 3 deletions packages/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,19 @@
"name": "web",
"type": "module",
"version": "0.0.1",
"packageManager": "pnpm@11.21.0",
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"type-check": "astro check",
"build": "astro check && astro build",
"preview": "astro preview",
"astro": "astro"
"preview": "pnpm run build && wrangler dev",
"astro": "astro",
"generate-types": "wrangler types",
"deploy": "pnpm run build && wrangler deploy"
},
"dependencies": {
"@astrojs/cloudflare": "^14.2.1",
"@astrojs/node": "11.1.1",
"@astrojs/solid-js": "7.0.2",
"@astrojs/ts-plugin": "1.10.10",
Expand Down Expand Up @@ -52,7 +56,7 @@
"@astrojs/check": "0.9.10",
"@types/node": "^26.2.0",
"tailwindcss": "^4.2.1",
"vitest": "^4.1.10"
"wrangler": "^4.121.0"
},
"peerDependencies": {
"typescript": "catalog:"
Expand Down
2 changes: 2 additions & 0 deletions packages/web/public/.assetsignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
_worker.js
_routes.json
12 changes: 8 additions & 4 deletions packages/web/src/actions/gp-management.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
updateGrammarPoint,
updateGrammarPointsOrder,
} from "grammar-sdk";
import { db } from "db";
import { defaultDb } from "db";

const handleError = (error: string | AuthorizationError) => {
if (isAuthorizationError(error)) {
Expand Down Expand Up @@ -112,7 +112,11 @@ export const gpManagement = {
accept: "json",
input: exerciseSchema.array().min(1),
handler: async (input, context) => {
const result = await putExercises(db, input, contextFromAstro(context));
const result = await putExercises(
defaultDb(),
input,
contextFromAstro(context),
);
if (result.isErr()) {
handleError(result.error);
}
Expand All @@ -136,7 +140,7 @@ export const gpManagement = {
}),
handler: async (input, context) => {
const result = await createLabel(
db,
defaultDb(),
{
color: `#${Math.floor(Math.random() * 0x1000000)
.toString(16)
Expand All @@ -156,7 +160,7 @@ export const gpManagement = {
getLabels: defineAction({
accept: "json",
handler: async (_input, context) => {
const result = await getLabels(contextFromAstro(context), db);
const result = await getLabels(contextFromAstro(context), defaultDb());
if (result.isErr()) {
handleError(result.error);
}
Expand Down
8 changes: 4 additions & 4 deletions packages/web/src/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ActionError, defineAction } from "astro:actions";
import { PUBLIC_URL } from "astro:env/server";
import { z } from "astro/zod";
import { fetchExamplesByGrammarPointId, fetchGrammarPoint } from "grammar-sdk";
import { db } from "db";
import { defaultDb } from "db";
import { createSupabaseServerInstance } from "~/libs/supabase";
import { saveFeedback } from "feedback";
import type { Stage } from "space-repetition";
Expand Down Expand Up @@ -238,7 +238,7 @@ export const server = {
}),
handler: async (feedback, context) => {
const user = extractUser(context);
await saveFeedback(db, {
await saveFeedback(defaultDb(), {
...feedback,
userId: user.id,
createdAt: new Date(),
Expand All @@ -252,7 +252,7 @@ export const server = {
}),
handler: async ({ grammarPointId }, context) => {
const user = extractUser(context);
await addToRepetitions(db, user, grammarPointId, new Date());
await addToRepetitions(defaultDb(), user, grammarPointId, new Date());
return { success: true };
},
}),
Expand All @@ -263,7 +263,7 @@ export const server = {
}),
handler: async ({ grammarPointId }, context) => {
const user = extractUser(context);
await removeFromRepetitions(db, user, grammarPointId);
await removeFromRepetitions(defaultDb(), user, grammarPointId);
return { success: true };
},
}),
Expand Down
8 changes: 4 additions & 4 deletions packages/web/src/actions/tour.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { defineAction } from "astro:actions";
import { db } from "db";
import { defaultDb } from "db";
import { tour as tourSchema } from "db/schema";
import { extractUser } from "./utils";
import { z } from "astro/zod";
Expand All @@ -13,7 +13,7 @@ export const tour = {
}),
handler: async ({ type }, context) => {
const user = extractUser(context);
await db.insert(tourSchema).values({
await defaultDb().insert(tourSchema).values({
userId: user.id,
type,
completed: true,
Expand All @@ -27,7 +27,7 @@ export const tour = {
}),
handler: async ({ type }, context) => {
const user = extractUser(context);
await db
await defaultDb()
.delete(tourSchema)
.where(and(eq(tourSchema.userId, user.id), eq(tourSchema.type, type)));
},
Expand All @@ -39,7 +39,7 @@ export const tour = {
}),
handler: async ({ type }, context) => {
const user = extractUser(context);
const tourEntry = await db
const tourEntry = await defaultDb()
.select()
.from(tourSchema)
.where(and(eq(tourSchema.userId, user.id), eq(tourSchema.type, type)))
Expand Down
10 changes: 6 additions & 4 deletions packages/web/src/features/latest-topics/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const cacheKey = "discourse_latest_topics";
const cacheDuration = 10 * 60 * 1000; // 10 minutes

const getLatestTopicsWithCache = async () => {
const cachedTopics = await cache.get<LatestTopic[]>(
const cachedTopics = await cache().get<LatestTopic[]>(
cacheKey,
(v: unknown): LatestTopic[] => {
if (!Array.isArray(v)) {
Expand All @@ -51,8 +51,10 @@ const getLatestTopicsWithCache = async () => {
);
if (cachedTopics) return cachedTopics;
const topics = (await getLatestTopics()).slice(0, 6);
cache.set(cacheKey, topics, cacheDuration).catch((e) => {
logger.error(e, "Failed to cache latest topics");
});
cache()
.set(cacheKey, topics, cacheDuration)
.catch((e) => {
logger.error(e, "Failed to cache latest topics");
});
return topics;
};
Loading