import { PgTransaction } from "drizzle-orm/pg-core"; import * as driz from "drizzle-orm"; import * as base64 from "base64-js"; import * as Y from "yjs"; import { MutationContext } from "./mutations"; import { entities, facts } from "drizzle/schema"; import { Attribute, Attributes, FilterAttributes } from "./attributes"; import { Fact, PermissionToken } from "."; import { DeepReadonly } from "replicache"; import { createClient } from "@supabase/supabase-js"; import { Database } from "supabase/database.types"; import { v7 } from "uuid"; export function serverMutationContext( tx: PgTransaction, permission_token_id: string, token_rights: PermissionToken["permission_token_rights"], ) { let ctx: MutationContext & { checkPermission: (entity: string) => Promise; } = { permission_token_id, async runOnServer(cb) { let supabase = createClient( process.env.NEXT_PUBLIC_SUPABASE_API_URL as string, process.env.SUPABASE_SERVICE_ROLE_KEY as string, ); return cb({ supabase }); }, async checkPermission(entity: string) { let [permission_set] = await tx .select({ entity_set: entities.set }) .from(entities) .where(driz.eq(entities.id, entity)); return ( !!permission_set && !!token_rights.find( (r) => r.entity_set === permission_set.entity_set && r.write == true, ) ); }, async runOnClient(_cb) {}, async createEntity({ entityID, permission_set }) { if ( !token_rights.find( (r) => r.entity_set === permission_set && r.write === true, ) ) { return false; } await tx.transaction( async (tx2) => await tx2 .insert(entities) .values({ set: permission_set, id: entityID, }) .catch(console.log), ); return true; }, scanIndex: { async eav(entity, attribute) { return (await tx .select({ id: facts.id, data: facts.data, entity: facts.entity, attribute: facts.attribute, }) .from(facts) .where( driz.and( driz.eq(facts.attribute, attribute), driz.eq(facts.entity, entity), ), )) as DeepReadonly>[]; }, }, async assertFact(f) { if (!f.entity) return; let attribute = Attributes[f.attribute as Attribute]; if (!attribute) return; let id = f.id || v7(); let data = { ...f.data }; let [permission_set] = await tx .select({ entity_set: entities.set }) .from(entities) .where(driz.eq(entities.id, f.entity)); if (!(await this.checkPermission(f.entity))) return; if (attribute.cardinality === "one") { let existingFact = await tx .select({ id: facts.id, data: facts.data }) .from(facts) .where( driz.and( driz.eq(facts.attribute, f.attribute), driz.eq(facts.entity, f.entity), ), ); if (existingFact[0]) { id = existingFact[0].id; if (attribute.type === "text") { const oldUpdate = base64.toByteArray( ( existingFact[0]?.data as Fact< keyof FilterAttributes<{ type: "text" }> >["data"] ).value, ); let textData = data as Fact< keyof FilterAttributes<{ type: "text" }> >["data"]; const newUpdate = base64.toByteArray(textData.value); const updateBytes = Y.mergeUpdates([oldUpdate, newUpdate]); textData.value = base64.fromByteArray(updateBytes); } } } await tx.transaction( async (tx2) => await tx2 .insert(facts) .values({ id: id, entity: f.entity, data: driz.sql`${data}::jsonb`, attribute: f.attribute, }) .onConflictDoUpdate({ target: facts.id, set: { data: driz.sql`${f.data}::jsonb` }, }) .catch((e) => { console.log(`error on inserting fact: `, JSON.stringify(e)); }), ); }, async retractFact(id) { let [f] = await tx .select() .from(facts) .rightJoin(entities, driz.eq(entities.id, facts.entity)) .where(driz.eq(facts.id, id)); if (!f || !(await this.checkPermission(f.entities.id))) return; await tx.delete(facts).where(driz.eq(facts.id, id)); }, async deleteEntity(entity) { if (!(await this.checkPermission(entity))) return; await Promise.all([ tx.delete(entities).where(driz.eq(entities.id, entity)), tx .delete(facts) .where( driz.sql`(data->>'type' = 'ordered-reference' or data ->>'type' = 'reference' or data ->>'type' = 'spatial-reference') and data->>'value' = ${entity}`, ), ]); }, }; return ctx; }