Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 48 additions & 2 deletions apps/roam/src/utils/publishNodesToGroups.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ import { CrossAppNode } from "@repo/database/crossAppContracts";
import type { DGSupabaseClient } from "@repo/database/lib/client";
import { getAvailableGroupIds } from "@repo/database/lib/groups";
import { nodeUidsWithTypeToCrossApp } from "./roamToCrossAppConverters";
import { nodeSchemaToCrossApp } from "./roamToCrossAppConverters";
import { crossAppNodeSchemaToDbConcept } from "@repo/database/lib/crossAppConverters";
import { ensurePartialSpaceAccess } from "@repo/database/lib/groups";
import { isIgnorableUpsertError } from "@repo/database/lib/contextFunctions";
import getDiscourseNodes from "./getDiscourseNodes";
import { difference, intersection } from "@repo/utils/setOperations";
import internalError from "./internalError";

Expand All @@ -16,7 +19,10 @@ const onlyStrings = (values: (string | null)[]): string[] =>
values.filter((value): value is string => typeof value === "string");

type PublishNodesResult = {
publishedNodeSchemaUids: string[];
publishedNodeUids: string[];
syncedNodeSchemaUids: string[];
failedSyncedUids: string[];
skippedUnsyncedUids: string[];
okGroupIds: string[];
failedGroupIds: string[];
Expand All @@ -42,7 +48,10 @@ export const publishNodesToGroups = async ({
nodes: CrossAppNode[];
}): Promise<PublishNodesResult> => {
const result: PublishNodesResult = {
publishedNodeSchemaUids: [],
publishedNodeUids: [],
syncedNodeSchemaUids: [],
failedSyncedUids: [],
skippedUnsyncedUids: [],
okGroupIds: [],
failedGroupIds: [],
Expand Down Expand Up @@ -71,12 +80,19 @@ export const publishNodesToGroups = async ({
if (groupIds.length === 0) return result;

let nodeUids = [...new Set(nodes.map((node) => node.localId))];
const nodeSchemaUids = new Set(nodes.map((node) => node.nodeType));
const nodeSchemas = getDiscourseNodes()
.filter((s) => nodeSchemaUids.has(s.type))
.map((s) => nodeSchemaToCrossApp(s))
.filter((s) => s !== null);

const neededUids = [...nodeSchemaUids, ...nodeUids];

const syncedRes = await client
.from("my_concepts")
.select("source_local_id")
.eq("space_id", spaceId)
.in("source_local_id", nodeUids);
.in("source_local_id", neededUids);
if (syncedRes.error) {
internalError({ error: syncedRes.error });
return result;
Expand All @@ -86,18 +102,47 @@ export const publishNodesToGroups = async ({
);
result.skippedUnsyncedUids = nodeUids.filter((uid) => !syncedUids.has(uid));
nodeUids = [...intersection(syncedUids, new Set(nodeUids))];
const missingNodeSchemas = nodeSchemas.filter(
(s) => !syncedUids.has(s.localId),
);
const upsertConcepts = [
...missingNodeSchemas.map((s) => crossAppNodeSchemaToDbConcept(s)),
].filter((r) => r !== undefined);

const resourceAccesses = [];
const resourceIds = [...nodeUids, ...nodeSchemaUids];
for (const groupId of groupIds) {
resourceAccesses.push(
...nodeUids.map((sourceLocalId) => ({
...resourceIds.map((sourceLocalId) => ({
account_uid: groupId,
source_local_id: sourceLocalId,
space_id: spaceId,
})),
);
}

if (upsertConcepts.length > 0) {
const response = await client.rpc("upsert_concepts", {
v_space_id: spaceId,
data: upsertConcepts,
});
if (response.error) {
internalError({ error: response.error });
return result;
}
Comment thread
maparent marked this conversation as resolved.
const syncedSchemaUids = new Set(missingNodeSchemas.map((s) => s.localId));
response.data.forEach((v, i) => {
if (v === -1) {
const localId = upsertConcepts[i].source_local_id;
if (localId) {
if (syncedSchemaUids.has(localId)) syncedSchemaUids.delete(localId);
result.failedSyncedUids.push(localId);
}
}
});
result.syncedNodeSchemaUids = [...syncedSchemaUids];
}

const grantRes = await client
.from("ResourceAccess")
.upsert(resourceAccesses, { ignoreDuplicates: true });
Expand All @@ -108,6 +153,7 @@ export const publishNodesToGroups = async ({
}

result.okGroupIds = groupIds;
result.publishedNodeSchemaUids = [...nodeSchemaUids];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logic Bug: publishedNodeSchemaUids includes failed schemas

This line assigns ALL node schema UIDs to publishedNodeSchemaUids, including ones that failed to sync (tracked in failedSyncedUids). This is inconsistent with how publishedNodeUids works (line 157), which only includes successfully synced nodes.

Fix: Exclude failed schemas:

result.publishedNodeSchemaUids = [...nodeSchemaUids].filter(
  (uid) => !result.failedSyncedUids.includes(uid)
);
Suggested change
result.publishedNodeSchemaUids = [...nodeSchemaUids];
result.publishedNodeSchemaUids = [...nodeSchemaUids].filter(
(uid) => !result.failedSyncedUids.includes(uid)
);

Spotted by Graphite

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

result.publishedNodeUids = nodeUids;

return result;
Expand Down
27 changes: 26 additions & 1 deletion apps/roam/src/utils/roamToCrossAppConverters.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import type { CrossAppNode } from "@repo/database/crossAppContracts";
import type {
CrossAppNode,
CrossAppNodeSchema,
} from "@repo/database/crossAppContracts";
import type { RoamFullContentNode } from "./convertRoamNodeToFullContent";
import type { DiscourseNode } from "./getDiscourseNodes";
import type { TreeNode, ViewType } from "roamjs-components/types";
Expand Down Expand Up @@ -116,3 +119,25 @@ export const nodeUidsWithTypeToCrossApp = async (
});
return results;
};

export const nodeSchemaToCrossApp = (
s: DiscourseNode,
): CrossAppNodeSchema | null => {
const relData = window.roamAlphaAPI.pull(
"[:create/time :edit/time {:create/user [:user/uid]}]",
`[:block/uid "${s.type}"]`,
) as unknown as {
":create/time": number;
":edit/time": number;
":create/user": { ":user/uid": string };
};
if (!relData) return null;
const userUid = (relData[":create/user"] ?? {})[":user/uid"];
if (!userUid) return null;
return {
localId: s.type,
label: s.text,
authorId: userUid,
createdAt: new Date(relData[":create/time"] || Date.now()),
};
};
2 changes: 1 addition & 1 deletion packages/database/src/inputTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export type LocalAccountDataInput = Partial<
export type LocalDocumentDataInput = Partial<
Omit<
Database["public"]["CompositeTypes"]["document_local_input"],
"author_inline"
"author_inline" | "contents"
> & { author_inline: LocalAccountDataInput }
>;
export type LocalContentDataInput = Partial<
Expand Down