diff --git a/app/api/rpc/[command]/pull.ts b/app/api/rpc/[command]/pull.ts new file mode 100644 index 00000000..ce75a003 --- /dev/null +++ b/app/api/rpc/[command]/pull.ts @@ -0,0 +1,103 @@ +import { z } from "zod"; +import { + PullRequest, + PullResponseV1, + VersionNotSupportedResponse, +} from "replicache"; +import { Database } from "supabase/database.types"; +import { Fact } from "src/replicache"; +import postgres from "postgres"; +import { drizzle } from "drizzle-orm/postgres-js"; +import { FactWithIndexes, getClientGroup } from "src/replicache/utils"; +import { Attributes } from "src/replicache/attributes"; +import { permission_tokens } from "drizzle/schema"; +import { eq } from "drizzle-orm"; +import { makeRoute } from "../lib"; +import { Env } from "./route"; + +// First define the sub-types for V0 and V1 requests +const pullRequestV0 = z.object({ + pullVersion: z.literal(0), + schemaVersion: z.string(), + profileID: z.string(), + cookie: z.any(), // ReadonlyJSONValue + clientID: z.string(), + lastMutationID: z.number(), +}); + +// For the Cookie type used in V1 +const cookieType = z.union([ + z.null(), + z.string(), + z.number(), + z + .object({ + order: z.union([z.string(), z.number()]), + }) + .and(z.record(z.string(), z.any())), // ReadonlyJSONValue with order property +]); + +const pullRequestV1 = z.object({ + pullVersion: z.literal(1), + schemaVersion: z.string(), + profileID: z.string(), + cookie: cookieType, + clientGroupID: z.string(), +}); + +// Combined PullRequest type +const PullRequestSchema = z.union([pullRequestV0, pullRequestV1]); + +export const pull = makeRoute({ + route: "pull", + input: z.object({ pullRequest: PullRequestSchema, token_id: z.string() }), + handler: async ({ pullRequest, token_id }, { db, supabase }: Env) => { + let body = pullRequest; + if (body.pullVersion === 0) return versionNotSupported; + let [token] = await db + .select({ root_entity: permission_tokens.root_entity }) + .from(permission_tokens) + .where(eq(permission_tokens.id, token_id)); + let facts: { + attribute: string; + created_at: string; + data: any; + entity: string; + id: string; + updated_at: string | null; + version: number; + }[] = []; + let clientGroup = {}; + if (token) { + let { data } = await supabase.rpc("get_facts", { + root: token.root_entity, + }); + + clientGroup = await getClientGroup(db, body.clientGroupID); + facts = data || []; + } + + return { + cookie: Date.now(), + lastMutationIDChanges: clientGroup, + patch: [ + { op: "clear" }, + { op: "put", key: "initialized", value: true }, + ...facts.map((f) => { + return { + op: "put", + key: f.id, + value: FactWithIndexes( + f as unknown as Fact, + ), + } as const; + }), + ], + } as PullResponseV1; + }, +}); + +const versionNotSupported: VersionNotSupportedResponse = { + error: "VersionNotSupported", + versionType: "pull", +}; diff --git a/app/api/rpc/[command]/push.ts b/app/api/rpc/[command]/push.ts new file mode 100644 index 00000000..257d348c --- /dev/null +++ b/app/api/rpc/[command]/push.ts @@ -0,0 +1,111 @@ +import { PushResponse } from "replicache"; +import { serverMutationContext } from "src/replicache/serverMutationContext"; +import { mutations } from "src/replicache/mutations"; +import { eq } from "drizzle-orm"; +import { permission_token_rights, replicache_clients } from "drizzle/schema"; +import { getClientGroup } from "src/replicache/utils"; +import { makeRoute } from "../lib"; +import { z } from "zod"; +import { Env } from "./route"; + +const mutationV0Schema = z.object({ + id: z.number(), + name: z.string(), + args: z.unknown(), + timestamp: z.number(), +}); + +const mutationV1Schema = mutationV0Schema.extend({ + clientID: z.string(), +}); + +const pushRequestV0Schema = z.object({ + pushVersion: z.literal(0), + schemaVersion: z.string(), + profileID: z.string(), + clientID: z.string(), + mutations: z.array(mutationV0Schema), +}); + +const pushRequestV1Schema = z.object({ + pushVersion: z.literal(1), + schemaVersion: z.string(), + profileID: z.string(), + clientGroupID: z.string(), + mutations: z.array(mutationV1Schema), +}); + +// Combine both versions into final PushRequest schema +const pushRequestSchema = z.discriminatedUnion("pushVersion", [ + pushRequestV0Schema, + pushRequestV1Schema, +]); + +type PushRequestZ = z.infer; + +export const push = makeRoute({ + route: "push", + input: z.object({ + pushRequest: pushRequestSchema, + rootEntity: z.string(), + token: z.object({ id: z.string() }), + }), + handler: async ( + { pushRequest, rootEntity, token }, + { db, supabase }: Env, + ) => { + if (pushRequest.pushVersion !== 1) { + return { + result: { error: "VersionNotSupported", versionType: "push" } as const, + }; + } + let clientGroup = await getClientGroup(db, pushRequest.clientGroupID); + let token_rights = await db + .select() + .from(permission_token_rights) + .where(eq(permission_token_rights.token, token.id)); + for (let mutation of pushRequest.mutations) { + let lastMutationID = clientGroup[mutation.clientID] || 0; + if (mutation.id <= lastMutationID) continue; + clientGroup[mutation.clientID] = mutation.id; + let name = mutation.name as keyof typeof mutations; + if (!mutations[name]) { + continue; + } + await db.transaction(async (tx) => { + try { + await mutations[name]( + mutation.args as any, + serverMutationContext(tx, token_rights), + ); + } catch (e) { + console.log( + `Error occured while running mutation: ${name}`, + JSON.stringify(e), + JSON.stringify(mutation, null, 2), + ); + } + await tx + .insert(replicache_clients) + .values({ + client_group: pushRequest.clientGroupID, + client_id: mutation.clientID, + last_mutation: mutation.id, + }) + .onConflictDoUpdate({ + target: replicache_clients.client_id, + set: { last_mutation: mutation.id }, + }); + }); + } + + let channel = supabase.channel(`rootEntity:${rootEntity}`); + await channel.send({ + type: "broadcast", + event: "poke", + payload: { message: "poke" }, + }); + supabase.removeChannel(channel); + return { result: undefined } as const; + }, +}); diff --git a/app/api/rpc/[command]/route.ts b/app/api/rpc/[command]/route.ts new file mode 100644 index 00000000..74bb38ad --- /dev/null +++ b/app/api/rpc/[command]/route.ts @@ -0,0 +1,29 @@ +import { drizzle } from "drizzle-orm/postgres-js"; +import { makeRouter } from "../lib"; +import { push } from "./push"; +import postgres from "postgres"; +import { createClient } from "@supabase/supabase-js"; +import { Database } from "supabase/database.types"; +import { pull } from "./pull"; + +const client = postgres(process.env.DB_URL as string, { idle_timeout: 5 }); +let supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_API_URL as string, + process.env.SUPABASE_SERVICE_ROLE_KEY as string, +); +const db = drizzle(client); + +const Env = { + supabase, + db, +}; +export type Env = typeof Env; +export type Routes = typeof Routes; +let Routes = [push, pull]; +export async function POST( + req: Request, + { params }: { params: { command: string } }, +) { + let router = makeRouter(Routes); + return router(params.command, req, Env); +} diff --git a/app/api/rpc/client.ts b/app/api/rpc/client.ts new file mode 100644 index 00000000..a403b1c8 --- /dev/null +++ b/app/api/rpc/client.ts @@ -0,0 +1,4 @@ +import { makeAPIClient } from "./lib"; +import type { Routes } from "./[command]/route"; + +export const callRPC = makeAPIClient("/api/rpc"); diff --git a/app/api/rpc/lib.ts b/app/api/rpc/lib.ts new file mode 100644 index 00000000..d9600913 --- /dev/null +++ b/app/api/rpc/lib.ts @@ -0,0 +1,104 @@ +import { ZodObject, ZodRawShape, ZodUnion, z } from "zod"; + +type Route< + Cmd extends string, + Input extends ZodObject | ZodUnion, + Result extends object, + Env extends {}, +> = { + route: Cmd; + input: Input; + handler: (msg: z.infer, env: Env, request: Request) => Promise; +}; + +type Routes = Route[]; + +export function makeAPIClient>(basePath: string) { + return async ( + route: T, + data: z.infer["input"]>, + ) => { + let result = await fetch(`${basePath}/${route}`, { + body: JSON.stringify(data), + method: "POST", + headers: { "Content-type": "application/json" }, + }); + return result.json() as Promise< + Awaited["handler"]>> + >; + }; +} + +export const makeRouter = (routes: Routes) => { + return async (route: string, request: Request, env: Env) => { + let status = 200; + let result; + switch (request.method) { + case "POST": { + let handler = routes.find((f) => f.route === route); + if (!handler) { + status = 404; + result = { error: `route ${route} not Found` }; + break; + } + + let body; + if (handler.input) + try { + body = await request.json(); + } catch (e) { + result = { error: "Request body must be valid JSON" }; + status = 400; + break; + } + + let msg = handler.input.safeParse(body); + if (!msg.success) { + status = 400; + result = msg.error; + break; + } + try { + result = (await handler.handler( + msg.data as any, + env, + request, + )) as object; + break; + } catch (e) { + console.log(e); + status = 500; + result = { + error: "An error occured while handling this request", + errorText: (e as Error).toString(), + }; + break; + } + } + default: + status = 404; + result = { error: "Only POST Supported" }; + } + + let res = new Response(JSON.stringify(result), { + status, + headers: { + "Access-Control-Allow-Credentials": "true", + "Content-type": "application/json;charset=UTF-8", + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET,HEAD,POST,OPTIONS", + }, + }); + //result.headers?.forEach((h) => res.headers.append(h[0], h[1])); + return res; + }; +}; + +export function makeRoute< + Cmd extends string, + Input extends ZodObject | ZodUnion, + Result extends object, + Env extends {}, +>(d: Route) { + return d; +} diff --git a/src/replicache/index.tsx b/src/replicache/index.tsx index 6b60ee07..fcedfeeb 100644 --- a/src/replicache/index.tsx +++ b/src/replicache/index.tsx @@ -8,12 +8,11 @@ import { Replicache, WriteTransaction, } from "replicache"; -import { Pull } from "./pull"; import { mutations } from "./mutations"; import { Attributes, Data, FilterAttributes } from "./attributes"; -import { Push } from "./push"; import { clientMutationContext } from "./clientMutationContext"; import { supabaseBrowserClient } from "supabase/browserClient"; +import { callRPC } from "app/api/rpc/client"; export type Fact = { id: string; @@ -82,13 +81,23 @@ export function ReplicacheProvider(props: { mutations: pushRequest.mutations.slice(0, 250), } as PushRequest; return { - response: await Push(smolpushRequest, props.name, props.token), + response: ( + await callRPC("push", { + pushRequest: smolpushRequest, + token: props.token, + rootEntity: props.name, + }) + ).result, httpRequestInfo: { errorMessage: "", httpStatusCode: 200 }, }; }, puller: async (pullRequest) => { + let res = await callRPC("pull", { + pullRequest, + token_id: props.token.id, + }); return { - response: await Pull(pullRequest, props.token.id), + response: res, httpRequestInfo: { errorMessage: "", httpStatusCode: 200 }, }; }, diff --git a/src/replicache/pull.ts b/src/replicache/pull.ts deleted file mode 100644 index 3d967c61..00000000 --- a/src/replicache/pull.ts +++ /dev/null @@ -1,71 +0,0 @@ -"use server"; - -import { createClient } from "@supabase/supabase-js"; -import { - PullRequest, - PullResponseV1, - VersionNotSupportedResponse, -} from "replicache"; -import { Database } from "supabase/database.types"; -import { Fact } from "."; -import postgres from "postgres"; -import { drizzle } from "drizzle-orm/postgres-js"; -import { FactWithIndexes, getClientGroup } from "./utils"; -import { Attributes } from "./attributes"; -import { permission_tokens } from "drizzle/schema"; -import { eq } from "drizzle-orm"; -let supabase = createClient( - process.env.NEXT_PUBLIC_SUPABASE_API_URL as string, - process.env.SUPABASE_SERVICE_ROLE_KEY as string, -); - -const client = postgres(process.env.DB_URL as string, { idle_timeout: 5 }); -const db = drizzle(client); -export async function Pull( - body: PullRequest, - token_id: string, -): Promise { - console.log("Pull"); - if (body.pullVersion === 0) return versionNotSupported; - let [token] = await db - .select({ root_entity: permission_tokens.root_entity }) - .from(permission_tokens) - .where(eq(permission_tokens.id, token_id)); - let facts: { - attribute: string; - created_at: string; - data: any; - entity: string; - id: string; - updated_at: string | null; - version: number; - }[] = []; - let clientGroup = {}; - if (token) { - let { data } = await supabase.rpc("get_facts", { root: token.root_entity }); - - clientGroup = await getClientGroup(db, body.clientGroupID); - facts = data || []; - } - - return { - cookie: Date.now(), - lastMutationIDChanges: clientGroup, - patch: [ - { op: "clear" }, - { op: "put", key: "initialized", value: true }, - ...facts.map((f) => { - return { - op: "put", - key: f.id, - value: FactWithIndexes(f as unknown as Fact), - } as const; - }), - ], - }; -} - -const versionNotSupported: VersionNotSupportedResponse = { - error: "VersionNotSupported", - versionType: "pull", -}; diff --git a/src/replicache/push.ts b/src/replicache/push.ts deleted file mode 100644 index db82c315..00000000 --- a/src/replicache/push.ts +++ /dev/null @@ -1,76 +0,0 @@ -"use server"; -import { PushRequest, PushResponse } from "replicache"; -import { serverMutationContext } from "./serverMutationContext"; -import { mutations } from "./mutations"; -import { drizzle } from "drizzle-orm/postgres-js"; -import { eq } from "drizzle-orm"; -import postgres from "postgres"; -import { permission_token_rights, replicache_clients } from "drizzle/schema"; -import { getClientGroup } from "./utils"; -import { createClient } from "@supabase/supabase-js"; -import { Database } from "supabase/database.types"; - -const client = postgres(process.env.DB_URL as string, { idle_timeout: 5 }); -let supabase = createClient( - process.env.NEXT_PUBLIC_SUPABASE_API_URL as string, - process.env.SUPABASE_SERVICE_ROLE_KEY as string, -); -const db = drizzle(client); -export async function Push( - pushRequest: PushRequest, - rootEntity: string, - token: { id: string }, -): Promise { - console.log("Push"); - if (pushRequest.pushVersion !== 1) { - return { error: "VersionNotSupported", versionType: "push" }; - } - let clientGroup = await getClientGroup(db, pushRequest.clientGroupID); - let token_rights = await db - .select() - .from(permission_token_rights) - .where(eq(permission_token_rights.token, token.id)); - for (let mutation of pushRequest.mutations) { - let lastMutationID = clientGroup[mutation.clientID] || 0; - if (mutation.id <= lastMutationID) continue; - clientGroup[mutation.clientID] = mutation.id; - let name = mutation.name as keyof typeof mutations; - if (!mutations[name]) { - continue; - } - await db.transaction(async (tx) => { - try { - await mutations[name]( - mutation.args as any, - serverMutationContext(tx, token_rights), - ); - } catch (e) { - console.log( - `Error occured while running mutation: ${name}`, - JSON.stringify(e), - JSON.stringify(mutation, null, 2), - ); - } - await tx - .insert(replicache_clients) - .values({ - client_group: pushRequest.clientGroupID, - client_id: mutation.clientID, - last_mutation: mutation.id, - }) - .onConflictDoUpdate({ - target: replicache_clients.client_id, - set: { last_mutation: mutation.id }, - }); - }); - } - - let channel = supabase.channel(`rootEntity:${rootEntity}`); - await channel.send({ - type: "broadcast", - event: "poke", - payload: { message: "poke" }, - }); - supabase.removeChannel(channel); - return undefined; -} -- 2.51.2 From c500383e1d2fdfffab648951c2be7d2f90944923 Mon Sep 17 00:00:00 2001 From: Jared Pereira Date: Sun, 15 Dec 2024 19:07:30 -0500 Subject: [PATCH 2/7] put all of push/pull in a transaction --- app/api/rpc/[command]/pull.ts | 60 +++++++++++++++++++++-------------- app/api/rpc/[command]/push.ts | 33 +++++++++---------- 2 files changed, 54 insertions(+), 39 deletions(-) diff --git a/app/api/rpc/[command]/pull.ts b/app/api/rpc/[command]/pull.ts index ce75a003..e0411121 100644 --- a/app/api/rpc/[command]/pull.ts +++ b/app/api/rpc/[command]/pull.ts @@ -11,7 +11,7 @@ import { drizzle } from "drizzle-orm/postgres-js"; import { FactWithIndexes, getClientGroup } from "src/replicache/utils"; import { Attributes } from "src/replicache/attributes"; import { permission_tokens } from "drizzle/schema"; -import { eq } from "drizzle-orm"; +import { eq, sql } from "drizzle-orm"; import { makeRoute } from "../lib"; import { Env } from "./route"; @@ -54,28 +54,42 @@ export const pull = makeRoute({ handler: async ({ pullRequest, token_id }, { db, supabase }: Env) => { let body = pullRequest; if (body.pullVersion === 0) return versionNotSupported; - let [token] = await db - .select({ root_entity: permission_tokens.root_entity }) - .from(permission_tokens) - .where(eq(permission_tokens.id, token_id)); - let facts: { - attribute: string; - created_at: string; - data: any; - entity: string; - id: string; - updated_at: string | null; - version: number; - }[] = []; - let clientGroup = {}; - if (token) { - let { data } = await supabase.rpc("get_facts", { - root: token.root_entity, - }); + let [facts, clientGroup] = await db.transaction(async (tx) => { + let [token] = await tx + .select({ root_entity: permission_tokens.root_entity }) + .from(permission_tokens) + .where(eq(permission_tokens.id, token_id)); - clientGroup = await getClientGroup(db, body.clientGroupID); - facts = data || []; - } + let facts: { + attribute: string; + created_at: string; + data: any; + entity: string; + id: string; + updated_at: string | null; + version: number; + }[] = []; + let clientGroup = {}; + + if (token) { + let data = (await tx.execute( + sql`select * from get_facts(${token.root_entity}) as get_facts`, + )) as { + attribute: string; + created_at: string; + data: any; + entity: string; + id: string; + updated_at: string | null; + version: number; + }[]; + + clientGroup = await getClientGroup(tx, body.clientGroupID); + facts = data || []; + return [facts, clientGroup]; + } + return []; + }); return { cookie: Date.now(), @@ -83,7 +97,7 @@ export const pull = makeRoute({ patch: [ { op: "clear" }, { op: "put", key: "initialized", value: true }, - ...facts.map((f) => { + ...(facts || []).map((f) => { return { op: "put", key: f.id, diff --git a/app/api/rpc/[command]/push.ts b/app/api/rpc/[command]/push.ts index 257d348c..d8f761d4 100644 --- a/app/api/rpc/[command]/push.ts +++ b/app/api/rpc/[command]/push.ts @@ -59,20 +59,21 @@ export const push = makeRoute({ result: { error: "VersionNotSupported", versionType: "push" } as const, }; } - let clientGroup = await getClientGroup(db, pushRequest.clientGroupID); - let token_rights = await db - .select() - .from(permission_token_rights) - .where(eq(permission_token_rights.token, token.id)); - for (let mutation of pushRequest.mutations) { - let lastMutationID = clientGroup[mutation.clientID] || 0; - if (mutation.id <= lastMutationID) continue; - clientGroup[mutation.clientID] = mutation.id; - let name = mutation.name as keyof typeof mutations; - if (!mutations[name]) { - continue; - } - await db.transaction(async (tx) => { + + await db.transaction(async (tx) => { + let clientGroup = await getClientGroup(tx, pushRequest.clientGroupID); + let token_rights = await tx + .select() + .from(permission_token_rights) + .where(eq(permission_token_rights.token, token.id)); + for (let mutation of pushRequest.mutations) { + let lastMutationID = clientGroup[mutation.clientID] || 0; + if (mutation.id <= lastMutationID) continue; + clientGroup[mutation.clientID] = mutation.id; + let name = mutation.name as keyof typeof mutations; + if (!mutations[name]) { + continue; + } try { await mutations[name]( mutation.args as any, @@ -96,8 +97,8 @@ export const push = makeRoute({ target: replicache_clients.client_id, set: { last_mutation: mutation.id }, }); - }); - } + } + }); let channel = supabase.channel(`rootEntity:${rootEntity}`); await channel.send({ -- 2.51.2 From aa8d9b268248a9ce5b127b607cedb12ee4d6ae2b Mon Sep 17 00:00:00 2001 From: Jared Pereira Date: Tue, 17 Dec 2024 17:21:16 -0500 Subject: [PATCH 3/7] move getting home leaflets to rpc from server action --- actions/getLeafletData.ts | 31 ------------- .../rpc/[command]/getFactsFromHomeLeaflets.ts | 35 +++++++++++++++ app/api/rpc/[command]/route.ts | 3 +- app/api/rpc/lib.ts | 8 +--- app/home/LeafletList.tsx | 14 +++--- app/home/page.tsx | 45 ++++++++----------- 6 files changed, 66 insertions(+), 70 deletions(-) delete mode 100644 actions/getLeafletData.ts create mode 100644 app/api/rpc/[command]/getFactsFromHomeLeaflets.ts diff --git a/actions/getLeafletData.ts b/actions/getLeafletData.ts deleted file mode 100644 index e2e994ae..00000000 --- a/actions/getLeafletData.ts +++ /dev/null @@ -1,31 +0,0 @@ -"use server"; - -import { createServerClient } from "@supabase/ssr"; -import { Fact } from "src/replicache"; -import { Attributes } from "src/replicache/attributes"; -import { Database } from "supabase/database.types"; - -let supabase = createServerClient( - process.env.NEXT_PUBLIC_SUPABASE_API_URL as string, - process.env.SUPABASE_SERVICE_ROLE_KEY as string, - { cookies: {} }, -); -export async function getLeafletData(tokens: string[]) { - //Eventually check permission tokens in here somehow! - let all_facts = await supabase.rpc("get_facts_for_roots", { - max_depth: 3, - roots: tokens, - }); - if (all_facts.data) - return all_facts.data.reduce( - (acc, fact) => { - if (!acc[fact.root_id]) acc[fact.root_id] = []; - acc[fact.root_id].push( - fact as unknown as Fact, - ); - return acc; - }, - {} as { [key: string]: Fact[] }, - ); - return {}; -} diff --git a/app/api/rpc/[command]/getFactsFromHomeLeaflets.ts b/app/api/rpc/[command]/getFactsFromHomeLeaflets.ts new file mode 100644 index 00000000..4652d450 --- /dev/null +++ b/app/api/rpc/[command]/getFactsFromHomeLeaflets.ts @@ -0,0 +1,35 @@ +import { z } from "zod"; +import { Fact } from "src/replicache"; +import { Attributes } from "src/replicache/attributes"; +import { makeRoute } from "../lib"; +import { Env } from "./route"; + +export const getFactsFromHomeLeaflets = makeRoute({ + route: "getFactsFromHomeLeaflets", + input: z.object({ + tokens: z.array(z.string()), + }), + handler: async ({ tokens }, { supabase }: Pick) => { + let all_facts = await supabase.rpc("get_facts_for_roots", { + max_depth: 3, + roots: tokens, + }); + + if (all_facts.data) { + return { + result: all_facts.data.reduce( + (acc, fact) => { + if (!acc[fact.root_id]) acc[fact.root_id] = []; + acc[fact.root_id].push( + fact as unknown as Fact, + ); + return acc; + }, + {} as { [key: string]: Fact[] }, + ), + }; + } + + return { result: {} }; + }, +}); diff --git a/app/api/rpc/[command]/route.ts b/app/api/rpc/[command]/route.ts index 74bb38ad..1ed772ed 100644 --- a/app/api/rpc/[command]/route.ts +++ b/app/api/rpc/[command]/route.ts @@ -5,6 +5,7 @@ import postgres from "postgres"; import { createClient } from "@supabase/supabase-js"; import { Database } from "supabase/database.types"; import { pull } from "./pull"; +import { getFactsFromHomeLeaflets } from "./getFactsFromHomeLeaflets"; const client = postgres(process.env.DB_URL as string, { idle_timeout: 5 }); let supabase = createClient( @@ -19,7 +20,7 @@ const Env = { }; export type Env = typeof Env; export type Routes = typeof Routes; -let Routes = [push, pull]; +let Routes = [push, pull, getFactsFromHomeLeaflets]; export async function POST( req: Request, { params }: { params: { command: string } }, diff --git a/app/api/rpc/lib.ts b/app/api/rpc/lib.ts index d9600913..ff339c8b 100644 --- a/app/api/rpc/lib.ts +++ b/app/api/rpc/lib.ts @@ -8,7 +8,7 @@ type Route< > = { route: Cmd; input: Input; - handler: (msg: z.infer, env: Env, request: Request) => Promise; + handler: (msg: z.infer, env: Env) => Promise; }; type Routes = Route[]; @@ -59,11 +59,7 @@ export const makeRouter = (routes: Routes) => { break; } try { - result = (await handler.handler( - msg.data as any, - env, - request, - )) as object; + result = (await handler.handler(msg.data as any, env)) as object; break; } catch (e) { console.log(e); diff --git a/app/home/LeafletList.tsx b/app/home/LeafletList.tsx index 8408d893..f7a2150a 100644 --- a/app/home/LeafletList.tsx +++ b/app/home/LeafletList.tsx @@ -7,8 +7,8 @@ import { Fact, ReplicacheProvider } from "src/replicache"; import { LeafletPreview } from "./LeafletPreview"; import { useIdentityData } from "components/IdentityProvider"; import { Attributes } from "src/replicache/attributes"; -import { getLeafletData } from "actions/getLeafletData"; import { getIdentityData } from "actions/getIdentityData"; +import { callRPC } from "app/api/rpc/client"; export function LeafletList(props: { initialFacts: { @@ -21,13 +21,15 @@ export function LeafletList(props: { let { identity } = useIdentityData(); let { data: initialFacts, mutate } = useSWR( "home-leaflet-data", - () => { - if (identity) - return getLeafletData( - identity.permission_token_on_homepage.map( + async () => { + if (identity) { + let { result } = await callRPC("getFactsFromHomeLeaflets", { + tokens: identity.permission_token_on_homepage.map( (ptrh) => ptrh.permission_tokens.root_entity, ), - ); + }); + return result; + } }, { fallbackData: props.initialFacts }, ); diff --git a/app/home/page.tsx b/app/home/page.tsx index 534e0932..bfed985d 100644 --- a/app/home/page.tsx +++ b/app/home/page.tsx @@ -21,6 +21,7 @@ import { LoginButton } from "components/LoginButton"; import { HelpPopover } from "components/HelpPopover"; import { AccountSettings } from "./AccountSettings"; import { LoggedOutWarning } from "./LoggedOutWarning"; +import { getFactsFromHomeLeaflets } from "app/api/rpc/[command]/getFactsFromHomeLeaflets"; let supabase = createServerClient( process.env.NEXT_PUBLIC_SUPABASE_API_URL as string, @@ -66,34 +67,26 @@ export default async function Home() { } if (!permission_token) return
no home page wierdly
; - let { data } = await supabase.rpc("get_facts", { - root: permission_token.root_entity, - }); - let initialFacts = (data as unknown as Fact[]) || []; + let [homeLeafletFacts, allLeafletFacts] = await Promise.all([ + supabase.rpc("get_facts", { + root: permission_token.root_entity, + }), + auth_res + ? getFactsFromHomeLeaflets.handler( + { + tokens: auth_res.permission_token_on_homepage.map( + (r) => r.permission_tokens.root_entity, + ), + }, + { supabase }, + ) + : undefined, + ]); + let initialFacts = + (homeLeafletFacts.data as unknown as Fact[]) || []; let root_entity = permission_token.root_entity; - let home_docs_initialFacts: { - [root_entity: string]: Fact[]; - } = {}; - if (auth_res) { - let all_facts = await supabase.rpc("get_facts_for_roots", { - max_depth: 3, - roots: auth_res.permission_token_on_homepage.map( - (r) => r.permission_tokens.root_entity, - ), - }); - if (all_facts.data) - home_docs_initialFacts = all_facts.data.reduce( - (acc, fact) => { - if (!acc[fact.root_id]) acc[fact.root_id] = []; - acc[fact.root_id].push( - fact as unknown as Fact, - ); - return acc; - }, - {} as { [key: string]: Fact[] }, - ); - } + let home_docs_initialFacts = allLeafletFacts?.result || {}; return ( Date: Tue, 17 Dec 2024 17:37:29 -0500 Subject: [PATCH 4/7] allow rpc to return null --- app/api/rpc/lib.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/api/rpc/lib.ts b/app/api/rpc/lib.ts index ff339c8b..be4bdc3e 100644 --- a/app/api/rpc/lib.ts +++ b/app/api/rpc/lib.ts @@ -3,7 +3,7 @@ import { ZodObject, ZodRawShape, ZodUnion, z } from "zod"; type Route< Cmd extends string, Input extends ZodObject | ZodUnion, - Result extends object, + Result extends object | null, Env extends {}, > = { route: Cmd; @@ -93,7 +93,7 @@ export const makeRouter = (routes: Routes) => { export function makeRoute< Cmd extends string, Input extends ZodObject | ZodUnion, - Result extends object, + Result extends object | null, Env extends {}, >(d: Route) { return d; -- 2.51.2 From ecfd0641c73aa60b0e1c69fbafe4757a7fa753eb Mon Sep 17 00:00:00 2001 From: Jared Pereira Date: Tue, 17 Dec 2024 19:10:02 -0500 Subject: [PATCH 5/7] fix logging in with no home docs --- actions/login.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/actions/login.ts b/actions/login.ts index d1d210da..58853769 100644 --- a/actions/login.ts +++ b/actions/login.ts @@ -114,14 +114,13 @@ export async function loginWithEmailToken( .set({ identity: identity.id }) .where(eq(email_auth_tokens.id, token_id)); - console.log( + if (localLeaflets.length > 0) await tx.insert(permission_token_on_homepage).values( localLeaflets.map((l) => ({ identity: identity.id, token: l.token.id, })), - ), - ); + ); return token; }); -- 2.51.2 From 6e8aaca639523a3449bcd8829a89c2e58a61da38 Mon Sep 17 00:00:00 2001 From: Jared Pereira Date: Tue, 17 Dec 2024 19:21:38 -0500 Subject: [PATCH 6/7] focus first block when creating from button --- app/home/CreateNewButton.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/home/CreateNewButton.tsx b/app/home/CreateNewButton.tsx index 853bad63..57a59131 100644 --- a/app/home/CreateNewButton.tsx +++ b/app/home/CreateNewButton.tsx @@ -59,7 +59,7 @@ export const CreateNewLeafletButton = (props: { { let id = await createNewLeaflet("doc", false); - window.open(`/${id}`, "_blank"); + window.open(`/${id}?focusFirstBlock`, "_blank"); }} > {" "} @@ -73,7 +73,7 @@ export const CreateNewLeafletButton = (props: { { let id = await createNewLeaflet("canvas", false); - window.open(`/${id}`, "_blank"); + window.open(`/${id}?focusFirstBlock`, "_blank"); }} > -- 2.51.2 From 9cb1196434b8f8e41bbf68d346cdd2f9a825e2a8 Mon Sep 17 00:00:00 2001 From: Jared Pereira Date: Tue, 17 Dec 2024 20:59:41 -0500 Subject: [PATCH 7/7] don't prefetch watermark --- components/Watermark.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/components/Watermark.tsx b/components/Watermark.tsx index 19c40c45..db52f16d 100644 --- a/components/Watermark.tsx +++ b/components/Watermark.tsx @@ -8,7 +8,7 @@ export const Watermark = (props: { mobile?: boolean }) => { let showWatermark = useEntity(rootEntity, "theme/page-leaflet-watermark"); if (!showWatermark?.data.value) return null; return ( - +