a tool for shared writing and social publishing
1"use server";
2
3import { createServerClient } from "@supabase/ssr";
4import { drizzle } from "drizzle-orm/node-postgres";
5import type { Fact } from "src/replicache";
6import type { Attribute } from "src/replicache/attributes";
7import { Database } from "supabase/database.types";
8import { v7 } from "uuid";
9
10import {
11 entities,
12 permission_tokens,
13 permission_token_rights,
14 entity_sets,
15 facts,
16} from "drizzle/schema";
17import { sql } from "drizzle-orm";
18import { redirect } from "next/navigation";
19import { cookies } from "next/headers";
20import { pool } from "supabase/pool";
21
22let supabase = createServerClient<Database>(
23 process.env.NEXT_PUBLIC_SUPABASE_API_URL as string,
24 process.env.SUPABASE_SERVICE_ROLE_KEY as string,
25 { cookies: {} },
26);
27
28export async function createNewLeafletFromTemplate(
29 template_id: string,
30 redirectUser?: boolean,
31) {
32 let auth_token = (await cookies()).get("auth_token")?.value;
33 let res = await supabase
34 .from("permission_tokens")
35 .select("*, permission_token_rights(*)")
36 .eq("id", template_id)
37 .single();
38 let rootEntity = res.data?.root_entity;
39 if (!rootEntity || !res.data) return { error: "Leaflet not found" } as const;
40 let { data } = await supabase.rpc("get_facts", {
41 root: rootEntity,
42 });
43 let initialFacts = (data as unknown as Fact<Attribute>[]) || [];
44
45 let oldEntityIDToNewID = {} as { [k: string]: string };
46 let oldEntities = initialFacts.reduce((acc, f) => {
47 if (!acc.includes(f.entity)) acc.push(f.entity);
48 return acc;
49 }, [] as string[]);
50 let newEntities = [] as string[];
51
52 for (let oldEntity of oldEntities) {
53 let newEntity = v7();
54 oldEntityIDToNewID[oldEntity] = newEntity;
55 newEntities.push(newEntity);
56 }
57
58 let newFacts = await Promise.all(
59 initialFacts.map(async (fact) => {
60 let entity = oldEntityIDToNewID[fact.entity];
61 let data = fact.data;
62 if (
63 data.type === "ordered-reference" ||
64 data.type == "spatial-reference" ||
65 data.type === "reference"
66 ) {
67 data.value = oldEntityIDToNewID[data.value];
68 }
69 if (data.type === "image") {
70 let url = data.src.split("?");
71 let paths = url[0].split("/");
72 let newID = v7();
73 await supabase.storage
74 .from("minilink-user-assets")
75 .copy(paths[paths.length - 1], newID);
76 let newPath = [...paths];
77 newPath[newPath.length - 1] = newID;
78 let newURL = newPath.join("/");
79 if (url[1]) newURL += `?${url[1]}`;
80 data.src = newURL;
81 }
82 return { entity, attribute: fact.attribute, data };
83 }),
84 );
85
86 const client = await pool.connect();
87 const db = drizzle(client);
88
89 let { permissionToken } = await db.transaction(async (tx) => {
90 // Create a new entity set
91 let [entity_set] = await tx.insert(entity_sets).values({}).returning();
92 await tx
93 .insert(entities)
94 .values(newEntities.map((e) => ({ id: e, set: entity_set.id })));
95 await tx.insert(facts).values(
96 newFacts.map((f) => ({
97 id: v7(),
98 entity: f.entity,
99 attribute: f.attribute,
100 data: sql`${f.data}`,
101 })),
102 );
103
104 let [permissionToken] = await tx
105 .insert(permission_tokens)
106 .values({ root_entity: oldEntityIDToNewID[rootEntity] })
107 .returning();
108
109 //and give it all the permission on that entity set
110 let [rights] = await tx
111 .insert(permission_token_rights)
112 .values({
113 token: permissionToken.id,
114 entity_set: entity_set.id,
115 read: true,
116 write: true,
117 create_token: true,
118 change_entity_set: true,
119 })
120 .returning();
121
122 if (auth_token) {
123 await tx.execute(sql`
124 WITH auth_token AS (
125 SELECT identities.id as identity_id
126 FROM email_auth_tokens
127 LEFT JOIN identities ON email_auth_tokens.identity = identities.id
128 WHERE email_auth_tokens.id = ${auth_token}
129 AND email_auth_tokens.confirmed = true
130 AND identities.id IS NOT NULL
131 )
132 INSERT INTO permission_token_on_homepage (token, identity)
133 SELECT ${permissionToken.id}, identity_id
134 FROM auth_token
135 `);
136 }
137 return { permissionToken, rights, entity_set };
138 });
139
140 client.release();
141 if (redirectUser) redirect(`/${permissionToken.id}`);
142 return { id: permissionToken.id, error: null } as const;
143}