@@ -11,10 +11,14 @@ import {
1111 getFileForNodeInstanceIds ,
1212 loadRelations ,
1313 saveRelations ,
14+ type RelationsFile ,
1415} from "./relationsStore" ;
1516import type { RelationInstance } from "~/types" ;
1617import { getAvailableGroupIds } from "./importNodes" ;
1718import { syncAllNodesAndRelations } from "./syncDgNodesToSupabase" ;
19+ import type { DiscourseNodeInVault } from "./getDiscourseNodes" ;
20+ import type { SupabaseContext } from "./supabaseContext" ;
21+ import type { TablesInsert } from "@repo/database/dbTypes" ;
1822
1923const 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+
7796export 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+
252411export const publishNodeToGroup = async ( {
253412 plugin,
254413 file,
0 commit comments