Skip to content

Commit 60bf5fb

Browse files
authored
ENG-1634 Re-sync nodes if needed at the time the users publish the node (#951)
1 parent a5ed8d2 commit 60bf5fb

File tree

2 files changed

+121
-68
lines changed

2 files changed

+121
-68
lines changed

apps/obsidian/src/utils/publishNode.ts

Lines changed: 14 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import type { FrontMatterCache, TFile } from "obsidian";
2-
import { Notice } from "obsidian";
32
import type { default as DiscourseGraphPlugin } from "~/index";
43
import { getLoggedInClient, getSupabaseContext } from "./supabaseContext";
5-
import { addFile } from "@repo/database/lib/files";
6-
import mime from "mime-types";
74
import type { DGSupabaseClient } from "@repo/database/lib/client";
85
import {
96
getRelationsForNodeInstanceId,
@@ -15,7 +12,10 @@ import {
1512
} from "./relationsStore";
1613
import type { RelationInstance } from "~/types";
1714
import { getAvailableGroupIds } from "./importNodes";
18-
import { syncAllNodesAndRelations } from "./syncDgNodesToSupabase";
15+
import {
16+
syncAllNodesAndRelations,
17+
syncPublishedNodeAssets,
18+
} from "./syncDgNodesToSupabase";
1919
import type { DiscourseNodeInVault } from "./getDiscourseNodes";
2020
import type { SupabaseContext } from "./supabaseContext";
2121
import type { TablesInsert } from "@repo/database/dbTypes";
@@ -262,6 +262,8 @@ export const publishNode = async ({
262262
if (myGroups.size === 0) throw new Error("Cannot get group");
263263
const existingPublish =
264264
(frontmatter.publishedToGroups as undefined | string[]) || [];
265+
// Hopefully temporary workaround for sync bug
266+
await syncAllNodesAndRelations(plugin);
265267
const commonGroups = existingPublish.filter((g) => myGroups.has(g));
266268
// temporary single-group assumption
267269
const myGroup = (commonGroups.length > 0 ? commonGroups : [...myGroups])[0]!;
@@ -506,65 +508,14 @@ export const publishNodeToGroup = async ({
506508
});
507509
}
508510
}
509-
510-
// Always sync non-text assets when node is published to this group
511-
const existingFiles: string[] = [];
512-
const existingReferencesReq = await client
513-
.from("my_file_references")
514-
.select("*")
515-
.eq("space_id", spaceId)
516-
.eq("source_local_id", nodeId);
517-
if (existingReferencesReq.error) {
518-
console.error(existingReferencesReq.error);
519-
return;
520-
}
521-
const existingReferencesByPath = Object.fromEntries(
522-
existingReferencesReq.data.map((ref) => [ref.filepath, ref]),
523-
);
524-
525-
for (const attachment of attachments) {
526-
const mimetype = mime.lookup(attachment.path) || "application/octet-stream";
527-
if (mimetype.startsWith("text/")) continue;
528-
// Do not use standard upload for large files
529-
if (attachment.stat.size >= 6 * 1024 * 1024) {
530-
new Notice(
531-
`Asset file ${attachment.path} is larger than 6Mb and will not be uploaded`,
532-
);
533-
continue;
534-
}
535-
existingFiles.push(attachment.path);
536-
const existingRef = existingReferencesByPath[attachment.path];
537-
if (
538-
!existingRef ||
539-
new Date(existingRef.last_modified + "Z").valueOf() <
540-
attachment.stat.mtime
541-
) {
542-
const content = await plugin.app.vault.readBinary(attachment);
543-
await addFile({
544-
client,
545-
spaceId,
546-
sourceLocalId: nodeId,
547-
fname: attachment.path,
548-
mimetype,
549-
created: new Date(attachment.stat.ctime),
550-
lastModified: new Date(attachment.stat.mtime),
551-
content,
552-
});
553-
}
554-
}
555-
let cleanupCommand = client
556-
.from("FileReference")
557-
.delete()
558-
.eq("space_id", spaceId)
559-
.eq("source_local_id", nodeId);
560-
if (existingFiles.length)
561-
cleanupCommand = cleanupCommand.notIn("filepath", [
562-
...new Set(existingFiles),
563-
]);
564-
const cleanupResult = await cleanupCommand;
565-
// do not fail on cleanup
566-
if (cleanupResult.error) console.error(cleanupResult.error);
567-
511+
await syncPublishedNodeAssets({
512+
plugin,
513+
client,
514+
nodeId,
515+
spaceId,
516+
file,
517+
attachments,
518+
});
568519
if (!existingPublish.includes(myGroup))
569520
await plugin.app.fileManager.processFrontMatter(
570521
file,

apps/obsidian/src/utils/syncDgNodesToSupabase.ts

Lines changed: 107 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
/* eslint-disable @typescript-eslint/naming-convention */
2-
import { FrontMatterCache, Notice, TFile } from "obsidian";
2+
import { Notice, TFile } from "obsidian";
3+
import { addFile } from "@repo/database/lib/files";
4+
import mime from "mime-types";
35
import { ensureNodeInstanceId } from "~/utils/nodeInstanceId";
46
import type { DGSupabaseClient } from "@repo/database/lib/client";
57
import type { Json } from "@repo/database/dbTypes";
@@ -9,7 +11,7 @@ import {
911
type SupabaseContext,
1012
} from "./supabaseContext";
1113
import { default as DiscourseGraphPlugin } from "~/index";
12-
import { publishNode, ensurePublishedRelationsAccuracy } from "./publishNode";
14+
import { ensurePublishedRelationsAccuracy } from "./publishNode";
1315
import { upsertNodesToSupabaseAsContentWithEmbeddings } from "./upsertNodesAsContentWithEmbeddings";
1416
import {
1517
orderConceptsByDependency,
@@ -579,6 +581,92 @@ const convertDgToSupabaseConcepts = async ({
579581
}
580582
};
581583

584+
export const syncPublishedNodeAssets = async ({
585+
plugin,
586+
client,
587+
nodeId,
588+
spaceId,
589+
file,
590+
attachments,
591+
}: {
592+
plugin: DiscourseGraphPlugin;
593+
client: DGSupabaseClient;
594+
nodeId: string;
595+
spaceId: number;
596+
file: TFile;
597+
attachments?: TFile[];
598+
}): Promise<void> => {
599+
if (attachments === undefined) {
600+
const embeds = plugin.app.metadataCache.getFileCache(file)?.embeds ?? [];
601+
attachments = embeds
602+
.map(({ link }) => {
603+
const attachment = plugin.app.metadataCache.getFirstLinkpathDest(
604+
link,
605+
file.path,
606+
);
607+
return attachment;
608+
})
609+
.filter((a) => !!a);
610+
}
611+
// Always sync non-text assets when node is published to this group
612+
const existingFiles: string[] = [];
613+
const existingReferencesReq = await client
614+
.from("my_file_references")
615+
.select("*")
616+
.eq("space_id", spaceId)
617+
.eq("source_local_id", nodeId);
618+
if (existingReferencesReq.error) {
619+
console.error(existingReferencesReq.error);
620+
return;
621+
}
622+
const existingReferencesByPath = Object.fromEntries(
623+
existingReferencesReq.data.map((ref) => [ref.filepath, ref]),
624+
) as Record<string, (typeof existingReferencesReq.data)[0]>;
625+
626+
for (const attachment of attachments) {
627+
const mimetype = mime.lookup(attachment.path) || "application/octet-stream";
628+
if (mimetype.startsWith("text/")) continue;
629+
// Do not use standard upload for large files
630+
if (attachment.stat.size >= 6 * 1024 * 1024) {
631+
new Notice(
632+
`Asset file ${attachment.path} is larger than 6Mb and will not be uploaded`,
633+
);
634+
continue;
635+
}
636+
existingFiles.push(attachment.path);
637+
const existingRef = existingReferencesByPath[attachment.path];
638+
if (
639+
!existingRef ||
640+
new Date(existingRef.last_modified + "Z").valueOf() <
641+
attachment.stat.mtime
642+
) {
643+
const content = await plugin.app.vault.readBinary(attachment);
644+
await addFile({
645+
client,
646+
spaceId,
647+
sourceLocalId: nodeId,
648+
fname: attachment.path,
649+
mimetype,
650+
created: new Date(attachment.stat.ctime),
651+
lastModified: new Date(attachment.stat.mtime),
652+
content,
653+
});
654+
}
655+
}
656+
let cleanupCommand = client
657+
.from("FileReference")
658+
.delete()
659+
.eq("space_id", spaceId)
660+
.eq("source_local_id", nodeId);
661+
if (existingFiles.length)
662+
cleanupCommand = cleanupCommand.notIn("filepath", [
663+
...new Set(existingFiles),
664+
]);
665+
const cleanupResult = await cleanupCommand;
666+
// do not fail on cleanup
667+
if (cleanupResult.error) console.error(cleanupResult.error);
668+
};
669+
582670
/**
583671
* For nodes that are already published, ensure non-text assets are pushed to
584672
* storage. Called after content sync so new embeds (e.g. images) get uploaded.
@@ -587,17 +675,26 @@ const syncPublishedNodesAssets = async (
587675
plugin: DiscourseGraphPlugin,
588676
nodes: ObsidianDiscourseNodeData[],
589677
): Promise<void> => {
678+
const context = await getSupabaseContext(plugin);
679+
if (!context) throw new Error("Cannot get context");
680+
const spaceId = context.spaceId;
681+
const client = await getLoggedInClient(plugin);
682+
if (!client) throw new Error("Cannot get client");
590683
const published = nodes.filter(
591684
(n) =>
592685
((n.frontmatter.publishedToGroups as string[] | undefined)?.length ?? 0) >
593686
0,
594687
);
595688
for (const node of published) {
596689
try {
597-
await publishNode({
690+
const nodeId = node.frontmatter.nodeInstanceId as string | undefined;
691+
if (!nodeId) throw new Error("Please sync the node first");
692+
await syncPublishedNodeAssets({
598693
plugin,
694+
client,
695+
nodeId,
696+
spaceId,
599697
file: node.file,
600-
frontmatter: node.frontmatter as FrontMatterCache,
601698
});
602699
} catch (error) {
603700
console.error(
@@ -651,7 +748,12 @@ const syncChangedNodesToSupabase = async ({
651748

652749
// When file changes affect an already-published node, ensure new non-text
653750
// assets (e.g. images) are pushed to storage.
654-
await syncPublishedNodesAssets(plugin, changedNodes);
751+
try {
752+
await syncPublishedNodesAssets(plugin, changedNodes);
753+
} catch (error) {
754+
console.error(`Failed to sync published node assets`, error);
755+
new Notice(`Failed to sync published node assets.`);
756+
}
655757
};
656758

657759
/**

0 commit comments

Comments
 (0)