Skip to content

Commit 700a7ab

Browse files
authored
ENG-1485 Re-scan unpublished relations at load or sync (#918)
1 parent c949f4b commit 700a7ab

File tree

2 files changed

+173
-1
lines changed

2 files changed

+173
-1
lines changed

apps/obsidian/src/utils/publishNode.ts

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,14 @@ import {
1111
getFileForNodeInstanceIds,
1212
loadRelations,
1313
saveRelations,
14+
type RelationsFile,
1415
} from "./relationsStore";
1516
import type { RelationInstance } from "~/types";
1617
import { getAvailableGroupIds } from "./importNodes";
1718
import { syncAllNodesAndRelations } from "./syncDgNodesToSupabase";
19+
import type { DiscourseNodeInVault } from "./getDiscourseNodes";
20+
import type { SupabaseContext } from "./supabaseContext";
21+
import type { TablesInsert } from "@repo/database/dbTypes";
1822

1923
const publishSchema = async ({
2024
client,
@@ -74,6 +78,21 @@ const intersection = <T>(set1: Set<T>, set2: Set<T>): Set<T> => {
7478
return r;
7579
};
7680

81+
const difference = <T>(set1: Set<T>, set2: Set<T>): Set<T> => {
82+
// @ts-expect-error - Set.difference is ES2025 feature
83+
if (set1.difference) return set1.difference(set2); // eslint-disable-line
84+
const result = new Set(set1);
85+
if (set1.size <= set2.size)
86+
for (const e of set1) {
87+
if (set2.has(e)) result.delete(e);
88+
}
89+
else
90+
for (const e of set2) {
91+
if (result.has(e)) result.delete(e);
92+
}
93+
return result;
94+
};
95+
7796
export const publishNewRelation = async (
7897
plugin: DiscourseGraphPlugin,
7998
relation: RelationInstance,
@@ -249,6 +268,146 @@ export const publishNode = async ({
249268
return await publishNodeToGroup({ plugin, file, frontmatter, myGroup });
250269
};
251270

271+
export const ensurePublishedRelationsAccuracy = async ({
272+
client,
273+
context,
274+
plugin,
275+
allNodesById,
276+
relationInstancesData,
277+
}: {
278+
client: DGSupabaseClient;
279+
context: SupabaseContext;
280+
plugin: DiscourseGraphPlugin;
281+
allNodesById: Record<string, DiscourseNodeInVault>;
282+
relationInstancesData: RelationsFile;
283+
}): Promise<void> => {
284+
const myGroups = await getAvailableGroupIds(client);
285+
const relationInstances = Object.values(relationInstancesData.relations);
286+
const syncedRelationIdsResult = await client
287+
.from("Concept")
288+
.select("source_local_id")
289+
.eq("space_id", context.spaceId)
290+
.eq("is_schema", false)
291+
.gt("arity", 0);
292+
if (syncedRelationIdsResult.error) {
293+
console.error(
294+
"Could not get synced relation ids",
295+
syncedRelationIdsResult.error,
296+
);
297+
return;
298+
}
299+
const syncedRelationIds = new Set(
300+
(syncedRelationIdsResult.data || []).map((x) => x.source_local_id!),
301+
);
302+
// Also a good time to look at orphan relations
303+
const existingRelationIds = new Set(relationInstances.map((r) => r.id));
304+
const orphanRelationIds = difference(syncedRelationIds, existingRelationIds);
305+
if (orphanRelationIds.size) {
306+
const r = await client
307+
.from("Concept")
308+
.delete()
309+
.eq("space_id", context.spaceId)
310+
.in("source_local_id", [...orphanRelationIds]);
311+
if (!r.error) {
312+
for (const id of orphanRelationIds) {
313+
syncedRelationIds.delete(id);
314+
}
315+
}
316+
}
317+
let changed = false;
318+
const missingPublishRecords: TablesInsert<"ResourceAccess">[] = [];
319+
for (const group of myGroups) {
320+
const publishableRelations = relationInstances.filter(
321+
(r) =>
322+
!r.importedFromRid &&
323+
syncedRelationIds.has(r.id) &&
324+
(
325+
(allNodesById[r.source]?.frontmatter?.publishedToGroups as
326+
| string[]
327+
| undefined) || []
328+
).indexOf(group) >= 0 &&
329+
(
330+
(allNodesById[r.destination]?.frontmatter?.publishedToGroups as
331+
| string[]
332+
| undefined) || []
333+
).indexOf(group) >= 0,
334+
);
335+
const publishableRelationIds = new Set(
336+
publishableRelations.map((x) => x.id),
337+
);
338+
const publishedIds = await client
339+
.from("ResourceAccess")
340+
.select("source_local_id")
341+
.eq("account_uid", group)
342+
.eq("space_id", context.spaceId);
343+
if (publishedIds.error) {
344+
console.error("Could not get synced relation ids", publishedIds.error);
345+
continue;
346+
}
347+
const publishedRelationIds = intersection(
348+
syncedRelationIds,
349+
new Set((publishedIds.data || []).map((x) => x.source_local_id)),
350+
);
351+
const missingPublishableIds = difference(
352+
publishableRelationIds,
353+
publishedRelationIds,
354+
);
355+
if (missingPublishableIds.size > 0) {
356+
missingPublishRecords.push(
357+
/* eslint-disable @typescript-eslint/naming-convention */
358+
...[...missingPublishableIds].map((source_local_id) => ({
359+
source_local_id,
360+
space_id: context.spaceId,
361+
account_uid: group,
362+
})),
363+
/* eslint-enable @typescript-eslint/naming-convention */
364+
);
365+
}
366+
const extraPublishableIds = difference(
367+
publishedRelationIds,
368+
publishableRelationIds,
369+
);
370+
if (extraPublishableIds.size > 0) {
371+
const r = await client
372+
.from("ResourceAccess")
373+
.delete()
374+
.eq("account_uid", group)
375+
.eq("space_id", context.spaceId)
376+
.in("source_local_id", [...extraPublishableIds]);
377+
if (r.error) console.error(r.error);
378+
else {
379+
for (const id of extraPublishableIds) {
380+
const rel = relationInstancesData.relations[id];
381+
const pos = (rel?.publishedToGroupId || []).indexOf(group);
382+
if (pos >= 0) {
383+
rel!.publishedToGroupId!.splice(pos, 1);
384+
changed = true;
385+
}
386+
}
387+
}
388+
}
389+
}
390+
if (missingPublishRecords.length > 0) {
391+
const r = await client.from("ResourceAccess").upsert(missingPublishRecords);
392+
if (r.error) console.error(r.error);
393+
else {
394+
for (const record of missingPublishRecords) {
395+
const rel = relationInstancesData.relations[record.source_local_id];
396+
const group = record.account_uid;
397+
const pos = (rel?.publishedToGroupId || []).indexOf(group);
398+
if (rel && pos < 0) {
399+
if (rel.publishedToGroupId === undefined) rel.publishedToGroupId = [];
400+
rel.publishedToGroupId.push(group);
401+
changed = true;
402+
}
403+
}
404+
}
405+
}
406+
if (changed) {
407+
await saveRelations(plugin, relationInstancesData);
408+
}
409+
};
410+
252411
export const publishNodeToGroup = async ({
253412
plugin,
254413
file,

apps/obsidian/src/utils/syncDgNodesToSupabase.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
type SupabaseContext,
1010
} from "./supabaseContext";
1111
import { default as DiscourseGraphPlugin } from "~/index";
12-
import { publishNode } from "./publishNode";
12+
import { publishNode, ensurePublishedRelationsAccuracy } from "./publishNode";
1313
import { upsertNodesToSupabaseAsContentWithEmbeddings } from "./upsertNodesAsContentWithEmbeddings";
1414
import {
1515
orderConceptsByDependency,
@@ -416,6 +416,7 @@ export const syncAllNodesAndRelations = async (
416416
accountLocalId,
417417
plugin,
418418
allNodes,
419+
fullSync: true,
419420
});
420421

421422
// When synced nodes are already published, ensure non-text assets are in storage.
@@ -433,13 +434,15 @@ const convertDgToSupabaseConcepts = async ({
433434
accountLocalId,
434435
plugin,
435436
allNodes,
437+
fullSync,
436438
}: {
437439
nodesSince: ObsidianDiscourseNodeData[];
438440
supabaseClient: DGSupabaseClient;
439441
context: SupabaseContext;
440442
accountLocalId: string;
441443
plugin: DiscourseGraphPlugin;
442444
allNodes?: DiscourseNodeInVault[];
445+
fullSync?: boolean;
443446
}): Promise<void> => {
444447
const lastNodeSchemaSync = (
445448
await getLastNodeSchemaSyncTime(supabaseClient, context.spaceId)
@@ -564,6 +567,16 @@ const convertDgToSupabaseConcepts = async ({
564567
throw new Error(`upsert_concepts failed: ${errorMessage}`);
565568
}
566569
}
570+
if (fullSync === true) {
571+
// occasional extra work: Make sure relations that should be published are.
572+
await ensurePublishedRelationsAccuracy({
573+
client: supabaseClient,
574+
context,
575+
plugin,
576+
allNodesById,
577+
relationInstancesData,
578+
});
579+
}
567580
};
568581

569582
/**

0 commit comments

Comments
 (0)