Skip to content

Conversation

@maparent
Copy link
Collaborator

@maparent maparent commented Jan 15, 2026

https://linear.app/discourse-graphs/issue/ENG-1180/f7-publish-non-text-assets-referenced-by-a-dg-node

Create a bucket for assets, indexed by content hash. Create a file reference table that associates files with content-local paths. Upload image assets on file publish.

https://www.loom.com/share/00a010ce3b4740cfbb3dff2febb7beb2

The loom video had a glitch: I would not re-publish a published page. Now I check if the file was modified since the sync, but I did not re-do the loom video.

Summary by CodeRabbit

  • New Features

    • File attachment support for content publishing, including upload, duplicate detection, and automatic cleanup of orphaned files.
    • Publishing now skips unchanged content based on file timestamps to avoid redundant updates.
  • Chores

    • Added storage/runtime dependencies and database schema/migrations to enable asset storage and file reference management.

✏️ Tip: You can customize this high-level summary in your review settings.

@linear
Copy link

linear bot commented Jan 15, 2026

@supabase
Copy link

supabase bot commented Jan 15, 2026

Updates to Preview Branch (eng-1180-f7-publish-non-text-assets-referenced-by-a-dg-node) ↗︎

Deployments Status Updated
Database Mon, 19 Jan 2026 14:43:52 UTC
Services Mon, 19 Jan 2026 14:43:52 UTC
APIs Mon, 19 Jan 2026 14:43:52 UTC

Tasks are run on every commit but only new migration files are pushed.
Close and reopen this PR if you want to apply changes from existing seed or migration files.

Tasks Status Updated
Configurations Mon, 19 Jan 2026 14:43:53 UTC
Migrations Mon, 19 Jan 2026 14:43:53 UTC
Seeding Mon, 19 Jan 2026 14:43:53 UTC
Edge Functions Mon, 19 Jan 2026 14:43:54 UTC

View logs for this Workflow Run ↗︎.
Learn more about Supabase for Git ↗︎.

@maparent maparent changed the base branch from main to eng-1285-base-user-affordance-to-trigger-node-sharing January 15, 2026 18:49
@maparent
Copy link
Collaborator Author

@CodeRabbit review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 15, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 15, 2026

📝 Walkthrough

Walkthrough

Adds file-attachment support: DB schema and migrations for FileReference and file_gc, RLS and storage policies, trigger-based GC functions, a server-side addFile utility to upload assets, and changes in the Obsidian publisher to detect, upload, and manage attached files when publishing content.

Changes

Cohort / File(s) Summary
Package deps & workspace
apps/obsidian/package.json, packages/database/package.json, pnpm-workspace.yaml
Added mime-types and @types/mime-types to the Obsidian app; added @supabase/storage-js catalog entry (v2.88.0) to workspace/catalog and packages/database deps.
Database schemas & migration
packages/database/supabase/schemas/assets.sql, packages/database/supabase/migrations/20260118210851_fileref.sql, packages/database/supabase/config.toml
New SQL surface: FileReference table, file_gc table, my_file_references view, file_exists and file_access functions, trigger functions and triggers for GC, storage bucket seed, and storage.object policies; added assets.sql to migration paths.
DB types & API
packages/database/src/dbTypes.ts
Exported new table/view types (FileReference, file_gc, my_file_references) and new RPC functions (file_exists, file_access).
Asset upload library
packages/database/src/lib/files.ts
Added addFile function: computes SHA-256 hash, calls RPC file_exists, uploads to assets bucket if new, inserts/updates FileReference record; handles conflict (23505) and upload 409.
Obsidian publishing flow
apps/obsidian/src/utils/publishNode.ts
Added pre-publish Content last_modified check to skip unchanged files; resolved embedded links, filtered by MIME, read binaries, uploaded attachments via addFile, collected current attachments and deleted stale FileReference entries; adjusted front-matter publishedToGroups update.

Sequence Diagram

sequenceDiagram
    participant App as Obsidian App
    participant DB as Supabase Client
    participant RPC as DB RPC (file_exists)
    participant Storage as Supabase Storage (assets)
    participant FRef as FileReference Table

    App->>DB: publishNode(content, attachments)
    DB->>DB: SELECT Content by (space_id, source_local_id)
    DB-->>App: existing last_modified
    alt content up-to-date
        App-->>DB: abort publish
    else
        App->>App: resolve embedded links, filter mime-types
        loop per attachment
            App->>RPC: file_exists(hash)
            alt not exists
                App->>Storage: upload blob (key = hash)
            end
            App->>FRef: INSERT FileReference (space_id, source_local_id, filepath, filehash,...)
            alt conflict (23505)
                FRef->>FRef: UPDATE last_modified,filehash,created
            end
        end
        App->>FRef: DELETE FileReference rows not in current attachment set
        DB-->>App: publish complete
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main change: implementing publishing of non-text assets referenced by DG nodes, which aligns with the comprehensive file handling, storage bucket, and asset upload functionality across all modified files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🤖 Fix all issues with AI agents
In `@apps/obsidian/src/utils/publishNode.ts`:
- Around line 101-108: The cleanup query in the publishNode function is using
the wrong column name: change the .eq("contentId", contentId) filter used on
client.from("FileReference") to use the database column name "content_id" (i.e.
.eq("content_id", contentId)) so the delete actually matches rows; keep the rest
of the logic (the .notIn("filepath", [...new Set(existingFiles)]) and the
non-failing error handling) intact and ensure you reference the same
FileReference/table and existingFiles set when updating the filter.
- Around line 75-76: The current asset extraction misses wikilink-style embeds;
update the logic around assetsRe and fileRefs in publishNode.ts to match both
markdown and wikilink image syntaxes by either replacing assetsRe with a
combined regex that captures paths from both ![alt](path) and ![[path]] patterns
or adding a second regex/pass that finds ![[...]] and extracts the inner path;
ensure the extracted capture group(s) are normalized the same way as existing
markdown captures so downstream code consuming fileContent/fileRefs continues to
work (look for symbols: assetsRe, fileRefs, fileContent).
- Around line 81-83: Replace the URL-based path resolution that constructs a
file URL and substrings it (using new URL(...), asUrl.href.substring, and
plugin.app.vault.getFileByPath) with Obsidian's proper link resolution: call
plugin.app.metadataCache.getFirstLinkpathDest(fname, file.path) to obtain the
attachment; update any logic that expects a Vault file object from getFileByPath
to use the result of getFirstLinkpathDest (which correctly handles relative
links, URL-reserved characters, aliases and fragments) and remove the asUrl/path
substring manipulation.

In `@packages/database/src/lib/files.ts`:
- Around line 30-38: The insert into FileReference via
client.from("FileReference").insert(...) can fail with a duplicate key on the
composite primary key (content_id, filepath); change this to an
upsert/conflict-handling call so re-publishes update existing rows instead of
erroring. Replace the plain insert used where frefResult is assigned with an
insert...onConflict (or upsert) that targets the (content_id, filepath) conflict
and updates fields like filehash, last_modified (and created if needed), and
keep the existing error check (frefResult.error) after the upsert call.

In `@packages/database/supabase/migrations/20260109035003_fileref.sql`:
- Line 62: The OWNER statement in the migration targets the wrong table
("FileReference") — do not edit this committed migration; instead create a new
migration that runs ALTER TABLE public."file_gc" OWNER TO "postgres"; or update
the schema generation source so subsequent migrations include the correct owner
for file_gc, and ensure the new migration references the table name file_gc (not
FileReference) to correct the owner assignment.
- Around line 90-102: Fix the trigger function after_delete_update_fref by: 1)
qualifying the GC table as public.file_gc instead of file_gc to avoid
search_path issues; 2) storing the file identifier used by storage (use
OLD.filehash rather than OLD.filepath, or map filepath→filehash before insert)
into public.file_gc so the garbage collector can locate assets; 3) change the
return to RETURN OLD (instead of RETURN NEW) for DELETE triggers (even though
AFTER triggers ignore it); and 4) apply these fixes in the proper
migration/schema (assets.sql or a new migration) rather than only editing this
generated file. Ensure references to public."FileReference" and
after_delete_update_fref are updated accordingly.

In `@packages/database/supabase/schemas/assets.sql`:
- Line 62: The ALTER TABLE OWNER statement is pointing to the wrong table:
change the ALTER TABLE public."FileReference" OWNER TO "postgres"; line so it
sets ownership for the file_gc table instead; locate the ALTER TABLE statement
in the schemas file (symbol: ALTER TABLE public."FileReference" OWNER TO
"postgres";) and update it to reference file_gc (symbol: file_gc) so the schema
assigns the correct owner.
- Around line 90-102: The trigger function after_delete_update_fref should be
fixed to reference the file_gc table with full schema qualification
(public.file_gc) and return OLD instead of NEW; update the INSERT to target
public.file_gc and change the function's final RETURN to RETURN OLD. Also
address the design mismatch: either change the stored GC key to filehash
(populate public.file_gc with OLD.filehash) or ensure the GC consumer resolves
filepath→filehash, and update the INSERT column/value accordingly to keep GC
consumers consistent with storage keys.
🧹 Nitpick comments (2)
apps/obsidian/src/utils/publishNode.ts (1)

6-6: Remove unused import.

The pl locale from date-fns is imported but never used in this file.

Proposed fix
-import { pl } from "date-fns/locale/pl";
packages/database/supabase/migrations/20260109035003_fileref.sql (1)

107-110: Consider idempotent bucket creation.

The INSERT INTO storage.buckets will fail if the assets bucket already exists (e.g., if migration is run multiple times in development). Consider using ON CONFLICT DO NOTHING:

Proposed fix
 INSERT INTO storage.buckets
 (id, name, public)
 VALUES
-('assets', 'assets', true);
+('assets', 'assets', true)
+ON CONFLICT (id) DO NOTHING;
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d34f8dd and a161d07.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (9)
  • apps/obsidian/package.json
  • apps/obsidian/src/utils/publishNode.ts
  • packages/database/package.json
  • packages/database/src/dbTypes.ts
  • packages/database/src/lib/files.ts
  • packages/database/supabase/config.toml
  • packages/database/supabase/migrations/20260109035003_fileref.sql
  • packages/database/supabase/schemas/assets.sql
  • pnpm-workspace.yaml
🧰 Additional context used
📓 Path-based instructions (4)
apps/obsidian/**/*.{ts,tsx,js,jsx,json}

📄 CodeRabbit inference engine (apps/obsidian/AGENTS.md)

Prefer existing dependencies from package.json for the Obsidian plugin

Files:

  • apps/obsidian/package.json
  • apps/obsidian/src/utils/publishNode.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use Tailwind CSS for styling where possible
When refactoring inline styles, use tailwind classes
Follow responsive design principles
Prefer type over interface for TypeScript
Use explicit return types for functions in TypeScript
Avoid any types in TypeScript when possible
Prefer arrow functions over regular function declarations
Use named parameters (object destructuring) when a function has more than 2 parameters
Use PascalCase for components and types
Use camelCase for variables and functions
Use UPPERCASE for constants
Prefer small, focused functions over inline code
Extract complex logic into well-named functions
Function names should describe their purpose clearly
Choose descriptive function names that make comments unnecessary
Break down complex operations into smaller, meaningful functions
Prefer early returns over nested conditionals for better readability
Prefer util functions for reusable logic and common operations
Add comments only when necessary; descriptive names should minimize the need for comments
Explain the why, not the what in comments, focusing on reasoning, trade-offs, and approaches
Document limitations, known bugs, or edge cases where behavior may not align with expectations

Files:

  • packages/database/src/lib/files.ts
  • apps/obsidian/src/utils/publishNode.ts
  • packages/database/src/dbTypes.ts
{packages/ui/**,apps/**}/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use platform-native UI components first with shadcn/ui as a fallback

Files:

  • apps/obsidian/src/utils/publishNode.ts
apps/obsidian/**/*.{md,ts,tsx,js,jsx}

📄 CodeRabbit inference engine (apps/obsidian/AGENTS.md)

apps/obsidian/**/*.{md,ts,tsx,js,jsx}: Utilize the icon anchor in embedded images to tweak spacing around icons for proper alignment with adjacent text
Icons should be surrounded by parenthesis in references (e.g., ( lucide-cog.svg > icon ))

Files:

  • apps/obsidian/src/utils/publishNode.ts
🧠 Learnings (18)
📚 Learning: 2026-01-15T03:03:39.934Z
Learnt from: CR
Repo: DiscourseGraphs/discourse-graph PR: 0
File: apps/obsidian/AGENTS.md:0-0
Timestamp: 2026-01-15T03:03:39.934Z
Learning: Applies to apps/obsidian/**/*.{ts,tsx,js,jsx,json} : Prefer existing dependencies from package.json for the Obsidian plugin

Applied to files:

  • apps/obsidian/package.json
  • packages/database/package.json
📚 Learning: 2026-01-15T03:03:39.934Z
Learnt from: CR
Repo: DiscourseGraphs/discourse-graph PR: 0
File: apps/obsidian/AGENTS.md:0-0
Timestamp: 2026-01-15T03:03:39.934Z
Learning: Applies to apps/obsidian/**/*.{md,ts,tsx,js,jsx} : Utilize the icon anchor in embedded images to tweak spacing around icons for proper alignment with adjacent text

Applied to files:

  • apps/obsidian/package.json
📚 Learning: 2025-09-11T23:54:41.861Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 431
File: packages/database/package.json:18-22
Timestamp: 2025-09-11T23:54:41.861Z
Learning: In the discourse-graph repository's packages/database/package.json, the typesVersions mapping for "./dbDotEnv" must retain the leading "./" prefix. Removing it (as suggested by standard TypeScript documentation) fails in practice in their specific build environment/tooling setup.

Applied to files:

  • apps/obsidian/package.json
  • packages/database/package.json
📚 Learning: 2026-01-15T03:03:44.167Z
Learnt from: CR
Repo: DiscourseGraphs/discourse-graph PR: 0
File: apps/roam/AGENTS.md:0-0
Timestamp: 2026-01-15T03:03:44.167Z
Learning: Applies to apps/roam/**/*.{js,ts,tsx,jsx} : Prefer existing dependencies from package.json

Applied to files:

  • apps/obsidian/package.json
  • packages/database/package.json
📚 Learning: 2026-01-15T03:03:47.971Z
Learnt from: CR
Repo: DiscourseGraphs/discourse-graph PR: 0
File: apps/website/AGENTS.md:0-0
Timestamp: 2026-01-15T03:03:47.971Z
Learning: Prefer existing dependencies from package.json

Applied to files:

  • apps/obsidian/package.json
📚 Learning: 2025-06-25T18:03:52.669Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 241
File: packages/database/tsconfig.json:3-7
Timestamp: 2025-06-25T18:03:52.669Z
Learning: The packages/database directory in the discourse-graph repository has a unique structure as a database schema/migration package. It contains doc/, scripts/, supabase/ directories and TypeScript files at the root level, but no typical src/, test/, dist/, or node_modules directories. The current tsconfig.json with "include": ["."] and "exclude": ["supabase"] is appropriate for this structure.

Applied to files:

  • packages/database/src/lib/files.ts
  • pnpm-workspace.yaml
  • packages/database/package.json
  • packages/database/supabase/config.toml
  • packages/database/src/dbTypes.ts
📚 Learning: 2026-01-14T22:16:15.442Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 698
File: apps/obsidian/src/utils/publishNode.ts:14-19
Timestamp: 2026-01-14T22:16:15.442Z
Learning: In apps/obsidian/src/utils/publishNode.ts, the function intentionally publishes to only the first group (data[0]?.group_id) from a user's group memberships. This single-group limitation is a deliberate design choice for the MVP phase, even though the system supports multi-group memberships via the group_membership table and the publishedToGroups frontmatter array.

Applied to files:

  • apps/obsidian/src/utils/publishNode.ts
📚 Learning: 2025-06-09T16:57:14.681Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 198
File: packages/database/supabase/migrations/20250605083319_alpha_upload.sql:157-337
Timestamp: 2025-06-09T16:57:14.681Z
Learning: Migration files in packages/database/supabase/migrations/ are historical snapshots that preserve database schema and functions as they existed at the time of creation. These files should not be updated to reflect current schema changes, even if they reference tables or columns that have since been modified or removed. Schema incompatibilities in migration files are expected and acceptable as they represent the valid state at the time of migration.

Applied to files:

  • packages/database/supabase/config.toml
  • packages/database/supabase/migrations/20260109035003_fileref.sql
  • packages/database/supabase/schemas/assets.sql
📚 Learning: 2025-05-22T23:50:23.771Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 165
File: packages/database/supabase/migrations/20250513173724_content_concept_key.sql:37-60
Timestamp: 2025-05-22T23:50:23.771Z
Learning: For the discourse-graph project, database schema management focuses on the final state in the supabase/schemas directory, not on the individual migration files. When reviewing database changes, consider only the schema definitions in this directory, not potential duplications or conflicts across migration files.

Applied to files:

  • packages/database/supabase/config.toml
  • packages/database/supabase/migrations/20260109035003_fileref.sql
  • packages/database/supabase/schemas/assets.sql
📚 Learning: 2025-05-20T03:04:21.602Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 165
File: packages/database/supabase/migrations/20250512142307_sync_table.sql:20-21
Timestamp: 2025-05-20T03:04:21.602Z
Learning: Database migration files (like the SQL files in packages/database/supabase/migrations/) should not be modified after they're created, even to fix minor issues like unused variables. Issues should be addressed in newer schema files instead to maintain migration history integrity.

Applied to files:

  • packages/database/supabase/config.toml
  • packages/database/supabase/migrations/20260109035003_fileref.sql
  • packages/database/supabase/schemas/assets.sql
📚 Learning: 2025-05-20T03:01:11.220Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 165
File: packages/database/supabase/migrations/20250504195841_remote_schema.sql:1-7
Timestamp: 2025-05-20T03:01:11.220Z
Learning: Supabase automatically creates necessary schemas like the "extensions" schema before migrations run, so there's no need to explicitly create these schemas in migration files.

Applied to files:

  • packages/database/supabase/config.toml
  • packages/database/supabase/migrations/20260109035003_fileref.sql
  • packages/database/supabase/schemas/assets.sql
📚 Learning: 2025-08-23T17:30:00.244Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 0
File: :0-0
Timestamp: 2025-08-23T17:30:00.244Z
Learning: For PRs involving both `packages/database/supabase/schemas` and `packages/database/supabase/migrations` files, only report errors in the schema files to avoid duplication, as long as the schema and migration files are consistent.

Applied to files:

  • packages/database/supabase/config.toml
📚 Learning: 2025-05-20T03:02:36.027Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 165
File: packages/database/supabase/schemas/extensions.sql:1-11
Timestamp: 2025-05-20T03:02:36.027Z
Learning: The necessary schemas for Supabase extensions (like 'extensions', 'graphql', and 'vault') are automatically created by Supabase before extensions are installed, so they don't need to be explicitly created in SQL migration files.

Applied to files:

  • packages/database/supabase/config.toml
  • packages/database/supabase/migrations/20260109035003_fileref.sql
  • packages/database/supabase/schemas/assets.sql
📚 Learning: 2025-05-20T03:11:07.917Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 165
File: packages/database/supabase/migrations/20250504202930_content_tables.sql:37-45
Timestamp: 2025-05-20T03:11:07.917Z
Learning: When reviewing SQL migrations in this codebase, understand that historical migration files should not be modified once they're applied. Instead, issues should be fixed in subsequent migrations or schema definition files. This is why maparent indicated "migration, not changing. corrected in schema."

Applied to files:

  • packages/database/supabase/config.toml
  • packages/database/supabase/migrations/20260109035003_fileref.sql
📚 Learning: 2025-05-30T14:49:24.016Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 182
File: apps/website/app/utils/supabase/dbUtils.ts:22-28
Timestamp: 2025-05-30T14:49:24.016Z
Learning: In apps/website/app/utils/supabase/dbUtils.ts, expanding the KNOWN_EMBEDDINGS and DEFAULT_DIMENSIONS mappings to support additional embedding models requires corresponding database model changes (creating new embedding tables), which should be scoped as separate work from API route implementations.

Applied to files:

  • packages/database/supabase/config.toml
  • packages/database/src/dbTypes.ts
📚 Learning: 2025-05-20T03:11:07.917Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 165
File: packages/database/supabase/migrations/20250504202930_content_tables.sql:37-45
Timestamp: 2025-05-20T03:11:07.917Z
Learning: When reviewing SQL migrations in the discourse-graph codebase, understand that historical migration files should not be modified once they're applied. Instead, issues should be fixed in subsequent migrations or schema definition files. This is why maparent indicated "migration, not changing. corrected in schema."

Applied to files:

  • packages/database/supabase/config.toml
  • packages/database/supabase/migrations/20260109035003_fileref.sql
📚 Learning: 2026-01-01T18:13:07.400Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 660
File: packages/database/supabase/migrations/20251231203152_migrate_space_access_to_user.sql:182-192
Timestamp: 2026-01-01T18:13:07.400Z
Learning: In PostgreSQL migrations, if you rename a table with ALTER TABLE ... RENAME TO, all properties including Row Level Security (RLS) settings remain on the table. If RLS was enabled before the rename, it will still be enabled afterward and does not need to be re-enabled via ALTER TABLE ... ENABLE ROW LEVEL SECURITY. Apply this guidance to any migration that uses RENAME TO in order to avoid unnecessary SQL for RLS.

Applied to files:

  • packages/database/supabase/migrations/20260109035003_fileref.sql
📚 Learning: 2025-07-13T16:47:14.352Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 0
File: :0-0
Timestamp: 2025-07-13T16:47:14.352Z
Learning: In the discourse-graph codebase, types.gen.ts contains automatically generated database function type definitions that may have reordered signatures between versions. This reordering is expected behavior from the code generation process and should not be flagged as an issue requiring fixes.

Applied to files:

  • packages/database/src/dbTypes.ts
🧬 Code graph analysis (2)
packages/database/src/lib/files.ts (1)
packages/database/src/lib/client.ts (1)
  • DGSupabaseClient (9-9)
apps/obsidian/src/utils/publishNode.ts (1)
packages/database/src/lib/files.ts (1)
  • addFile (5-39)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Supabase Preview
  • GitHub Check: Supabase Preview
  • GitHub Check: Supabase Preview
  • GitHub Check: validate
🔇 Additional comments (15)
packages/database/package.json (1)

48-48: LGTM - @supabase/storage-js dependency follows existing patterns.

The addition uses catalog: reference, consistent with other @supabase/* packages in this file (lines 46-49). This enables the storage operations needed for file reference management.

pnpm-workspace.yaml (1)

34-34: LGTM - Consistent Supabase package versioning.

The @supabase/storage-js version 2.88.0 matches the other Supabase packages in the catalog (@supabase/supabase-js, @supabase/functions-js, @supabase/auth-js), ensuring compatibility across the suite.

packages/database/src/dbTypes.ts (3)

602-676: LGTM - Auto-generated types for new file reference entities.

The generated types for file_gc and FileReference tables correctly reflect the database schema additions. The FileReference table properly includes foreign key relationships to Content and Space tables.


1322-1384: LGTM - View types correctly generated with nullable fields.

The my_file_references view type appropriately has all fields as nullable, which is the standard pattern for auto-generated view types in Supabase. The relationships mirror those of the underlying FileReference table.


1664-1664: LGTM - file_exists function signature.

The function type { Args: { hashvalue: string }; Returns: boolean } correctly represents the RPC for checking file existence by content hash, supporting the deduplication logic in the file upload flow.

packages/database/supabase/config.toml (1)

58-58: LGTM - Schema path ordering looks correct.

The assets.sql schema is positioned after content.sql (which defines Content table that FileReference references) and before concept.sql. This ordering ensures dependencies are satisfied during schema application.

apps/obsidian/package.json (1)

25-25: Reconsider this approval against the stated Obsidian coding guideline.

The coding guideline requires preferring existing dependencies from package.json, but mime-types and @types/mime-types are new dependencies being added. While Obsidian's API does not provide built-in MIME type detection (necessitating a library), the original approval did not address or justify this exception to the stated guideline. Either justify why this new dependency is necessary despite the constraint, or investigate whether an existing transitive dependency or alternative approach can provide the same functionality.

Likely an incorrect or invalid review comment.

packages/database/src/lib/files.ts (2)

5-6: LGTM on function signature.

Good use of named parameters with object destructuring per coding guidelines.


17-21: LGTM on hash computation.

Correct SHA-256 implementation using Web Crypto API.

apps/obsidian/src/utils/publishNode.ts (1)

21-23: LGTM on context retrieval.

Proper null checking and error handling for the Supabase context.

packages/database/supabase/migrations/20260109035003_fileref.sql (2)

104-105: Update trigger may queue files for GC prematurely.

The UPDATE trigger fires on any column change, but the GC check uses OLD.filepath. If a user updates last_modified without changing filepath, this could incorrectly evaluate the filepath count (though it should still find the row). More critically, if filepath is updated, the old filepath gets queued for GC even though it may still be referenced by other content.


1-24: LGTM on FileReference table structure.

The table design with composite PK, foreign keys with cascade, and appropriate indexes is well-structured.

packages/database/supabase/schemas/assets.sql (3)

112-115: INSERT policy allows any authenticated user to upload to assets bucket.

The policy storage_insert_assets_authenticated only checks bucket_id = 'assets' without verifying the user has rights to associate this file with any content. An authenticated user could potentially upload arbitrary files to the assets bucket.

Consider adding a check that the user has already created a valid FileReference for this hash, or accept this as a trade-off for simpler upload flow (with orphan cleanup via GC).


1-8: LGTM on table structure and RLS policies.

The FileReference table design and row-level security policies are well-structured with appropriate access controls.

Also applies to: 40-54


26-38: Inconsistent access check between view and RLS policy.

The view my_file_references uses my_space_ids() (Line 36) while the RLS policy uses in_space() (Line 48). Verify these are semantically equivalent for your access control model.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@maparent
Copy link
Collaborator Author

@CodeRabbit review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 15, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@apps/obsidian/src/utils/publishNode.ts`:
- Around line 110-117: The cleanup currently only runs when existingFiles.length
is truthy, so when a node has no embeds old FileReference rows are left behind;
change publish cleanup to always call
client.from("FileReference").delete().eq("content_id", contentId) and
conditionally add .notIn("filepath", [...new Set(existingFiles)]) only when
existingFiles.length > 0 so that if there are zero current attachments all
FileReference rows for that content_id are deleted; update the block that
references existingFiles and cleanupResult accordingly (keep the non-failing
error log behavior).

In `@packages/database/src/lib/files.ts`:
- Around line 1-2: The import upsertNodesToSupabaseAsContentWithEmbeddings at
the top of the file is unresolved and unused; remove the import statement
referencing "~/utils/upsertNodesAsContentWithEmbeddings" (or replace it with the
correct path only if the symbol is actually used elsewhere in this module) and
ensure there are no remaining references to
upsertNodesToSupabaseAsContentWithEmbeddings in this file (check functions in
this module that might have intended to call it and either wire the correct
import path or remove those calls).
- Around line 23-29: The file existence check using client.rpc("file_exists") is
non-atomic with the subsequent
client.storage.from(ASSETS_BUCKET_NAME).upload(...) call and can cause 409
conflicts under concurrent publishes; modify the upload invocation (where
uploadResult is assigned) to pass the upload option upsert: true so the storage
upload becomes idempotent and concurrent uploads overwrite rather than fail,
leaving the rest of the logic (lookForDup, exists handling, and throwing
uploadResult.error) unchanged.
♻️ Duplicate comments (1)
packages/database/supabase/schemas/assets.sql (1)

59-101: GC mismatch: column/trigger should use filehash, not filepath.

The GC condition checks filepath and the table column is filepath, but storage keys are filehash. This can leave orphaned blobs when different contents share a filename. Consider storing filehash in file_gc and checking fr.filehash.

🛠️ Proposed fix
-CREATE TABLE IF NOT EXISTS public.file_gc (
-    filepath character varying NOT NULL PRIMARY KEY
-);
+CREATE TABLE IF NOT EXISTS public.file_gc (
+    filehash character varying NOT NULL PRIMARY KEY
+);

 ...

-    IF (SELECT count(content_id) FROM public."FileReference" AS fr WHERE fr.filepath=OLD.filepath) = 0 THEN
+    IF (SELECT count(content_id) FROM public."FileReference" AS fr WHERE fr.filehash=OLD.filehash) = 0 THEN
         INSERT INTO public.file_gc VALUES (OLD.filehash);
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a161d07 and 9bea3ba.

📒 Files selected for processing (4)
  • apps/obsidian/src/utils/publishNode.ts
  • packages/database/src/lib/files.ts
  • packages/database/supabase/migrations/20260109035003_fileref.sql
  • packages/database/supabase/schemas/assets.sql
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use Tailwind CSS for styling where possible
When refactoring inline styles, use tailwind classes
Follow responsive design principles
Prefer type over interface for TypeScript
Use explicit return types for functions in TypeScript
Avoid any types in TypeScript when possible
Prefer arrow functions over regular function declarations
Use named parameters (object destructuring) when a function has more than 2 parameters
Use PascalCase for components and types
Use camelCase for variables and functions
Use UPPERCASE for constants
Prefer small, focused functions over inline code
Extract complex logic into well-named functions
Function names should describe their purpose clearly
Choose descriptive function names that make comments unnecessary
Break down complex operations into smaller, meaningful functions
Prefer early returns over nested conditionals for better readability
Prefer util functions for reusable logic and common operations
Add comments only when necessary; descriptive names should minimize the need for comments
Explain the why, not the what in comments, focusing on reasoning, trade-offs, and approaches
Document limitations, known bugs, or edge cases where behavior may not align with expectations

Files:

  • apps/obsidian/src/utils/publishNode.ts
  • packages/database/src/lib/files.ts
{packages/ui/**,apps/**}/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use platform-native UI components first with shadcn/ui as a fallback

Files:

  • apps/obsidian/src/utils/publishNode.ts
apps/obsidian/**/*.{ts,tsx,js,jsx,json}

📄 CodeRabbit inference engine (apps/obsidian/AGENTS.md)

Prefer existing dependencies from package.json for the Obsidian plugin

Files:

  • apps/obsidian/src/utils/publishNode.ts
apps/obsidian/**/*.{md,ts,tsx,js,jsx}

📄 CodeRabbit inference engine (apps/obsidian/AGENTS.md)

apps/obsidian/**/*.{md,ts,tsx,js,jsx}: Utilize the icon anchor in embedded images to tweak spacing around icons for proper alignment with adjacent text
Icons should be surrounded by parenthesis in references (e.g., ( lucide-cog.svg > icon ))

Files:

  • apps/obsidian/src/utils/publishNode.ts
🧠 Learnings (19)
📚 Learning: 2026-01-14T22:16:15.442Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 698
File: apps/obsidian/src/utils/publishNode.ts:14-19
Timestamp: 2026-01-14T22:16:15.442Z
Learning: In apps/obsidian/src/utils/publishNode.ts, the function intentionally publishes to only the first group (data[0]?.group_id) from a user's group memberships. This single-group limitation is a deliberate design choice for the MVP phase, even though the system supports multi-group memberships via the group_membership table and the publishedToGroups frontmatter array.

Applied to files:

  • apps/obsidian/src/utils/publishNode.ts
📚 Learning: 2026-01-15T03:03:39.934Z
Learnt from: CR
Repo: DiscourseGraphs/discourse-graph PR: 0
File: apps/obsidian/AGENTS.md:0-0
Timestamp: 2026-01-15T03:03:39.934Z
Learning: Applies to apps/obsidian/**/*.{md,ts,tsx,js,jsx} : Utilize the icon anchor in embedded images to tweak spacing around icons for proper alignment with adjacent text

Applied to files:

  • apps/obsidian/src/utils/publishNode.ts
📚 Learning: 2025-08-25T15:53:21.799Z
Learnt from: sid597
Repo: DiscourseGraphs/discourse-graph PR: 372
File: apps/roam/src/components/DiscourseNodeMenu.tsx:116-116
Timestamp: 2025-08-25T15:53:21.799Z
Learning: In apps/roam/src/components/DiscourseNodeMenu.tsx, when handling tag insertion, multiple leading hashtags (like ##foo) should be preserved as they represent user intent, not normalized to a single hashtag. The current regex /^#/ is correct as it only removes one leading # before adding one back, maintaining any additional hashtags the user intended.

Applied to files:

  • apps/obsidian/src/utils/publishNode.ts
📚 Learning: 2026-01-15T03:03:39.934Z
Learnt from: CR
Repo: DiscourseGraphs/discourse-graph PR: 0
File: apps/obsidian/AGENTS.md:0-0
Timestamp: 2026-01-15T03:03:39.934Z
Learning: Applies to apps/obsidian/**/*.{md,ts,tsx,js,jsx} : Icons should be surrounded by parenthesis in references (e.g., ( lucide-cog.svg > icon ))

Applied to files:

  • apps/obsidian/src/utils/publishNode.ts
📚 Learning: 2026-01-15T03:03:39.934Z
Learnt from: CR
Repo: DiscourseGraphs/discourse-graph PR: 0
File: apps/obsidian/AGENTS.md:0-0
Timestamp: 2026-01-15T03:03:39.934Z
Learning: Applies to apps/obsidian/Attachments/icons/obsidian-icon-*.svg : Add the prefix obsidian-icon- before the Obsidian icon name when naming icon files

Applied to files:

  • apps/obsidian/src/utils/publishNode.ts
📚 Learning: 2025-11-23T23:53:43.094Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 0
File: :0-0
Timestamp: 2025-11-23T23:53:43.094Z
Learning: In `apps/roam/src/utils/supabaseContext.ts`, the Roam URL normalization uses a targeted string replacement `url.replace("/?server-port=3333#/", "/#/")` rather than URL parsing to avoid impacting unforeseen URL patterns. This conservative approach is intentional to handle only the specific known case.

Applied to files:

  • apps/obsidian/src/utils/publishNode.ts
📚 Learning: 2025-06-09T16:57:14.681Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 198
File: packages/database/supabase/migrations/20250605083319_alpha_upload.sql:157-337
Timestamp: 2025-06-09T16:57:14.681Z
Learning: Migration files in packages/database/supabase/migrations/ are historical snapshots that preserve database schema and functions as they existed at the time of creation. These files should not be updated to reflect current schema changes, even if they reference tables or columns that have since been modified or removed. Schema incompatibilities in migration files are expected and acceptable as they represent the valid state at the time of migration.

Applied to files:

  • packages/database/src/lib/files.ts
  • packages/database/supabase/migrations/20260109035003_fileref.sql
  • packages/database/supabase/schemas/assets.sql
📚 Learning: 2025-07-08T14:48:38.048Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 272
File: packages/ui/src/lib/supabase/contextFunctions.ts:50-111
Timestamp: 2025-07-08T14:48:38.048Z
Learning: The Supabase client does not offer transaction support, so idempotent upserts with proper conflict resolution (using onConflict with ignoreDuplicates: false) are the preferred approach for multi-step database operations in packages/ui/src/lib/supabase/contextFunctions.ts. This pattern prevents orphaned records when retrying operations.

Applied to files:

  • packages/database/src/lib/files.ts
📚 Learning: 2025-10-18T18:58:16.100Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 504
File: apps/roam/src/utils/syncDgNodesToSupabase.ts:523-531
Timestamp: 2025-10-18T18:58:16.100Z
Learning: In `apps/roam/src/utils/syncDgNodesToSupabase.ts`, partial successes from `upsertNodesToSupabaseAsContent` and `addMissingEmbeddings` (indicated by numeric return values showing the count of successful operations) should NOT trigger backoff. Only complete failures (false) should trigger the exponential backoff mechanism. This design allows the sync process to continue making progress even when some items fail.

Applied to files:

  • packages/database/src/lib/files.ts
📚 Learning: 2025-05-20T03:04:21.602Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 165
File: packages/database/supabase/migrations/20250512142307_sync_table.sql:20-21
Timestamp: 2025-05-20T03:04:21.602Z
Learning: Database migration files (like the SQL files in packages/database/supabase/migrations/) should not be modified after they're created, even to fix minor issues like unused variables. Issues should be addressed in newer schema files instead to maintain migration history integrity.

Applied to files:

  • packages/database/supabase/migrations/20260109035003_fileref.sql
  • packages/database/supabase/schemas/assets.sql
📚 Learning: 2025-05-20T03:11:07.917Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 165
File: packages/database/supabase/migrations/20250504202930_content_tables.sql:37-45
Timestamp: 2025-05-20T03:11:07.917Z
Learning: When reviewing SQL migrations in this codebase, understand that historical migration files should not be modified once they're applied. Instead, issues should be fixed in subsequent migrations or schema definition files. This is why maparent indicated "migration, not changing. corrected in schema."

Applied to files:

  • packages/database/supabase/migrations/20260109035003_fileref.sql
  • packages/database/supabase/schemas/assets.sql
📚 Learning: 2025-05-22T23:50:23.771Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 165
File: packages/database/supabase/migrations/20250513173724_content_concept_key.sql:37-60
Timestamp: 2025-05-22T23:50:23.771Z
Learning: For the discourse-graph project, database schema management focuses on the final state in the supabase/schemas directory, not on the individual migration files. When reviewing database changes, consider only the schema definitions in this directory, not potential duplications or conflicts across migration files.

Applied to files:

  • packages/database/supabase/migrations/20260109035003_fileref.sql
  • packages/database/supabase/schemas/assets.sql
📚 Learning: 2025-05-20T03:01:11.220Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 165
File: packages/database/supabase/migrations/20250504195841_remote_schema.sql:1-7
Timestamp: 2025-05-20T03:01:11.220Z
Learning: Supabase automatically creates necessary schemas like the "extensions" schema before migrations run, so there's no need to explicitly create these schemas in migration files.

Applied to files:

  • packages/database/supabase/migrations/20260109035003_fileref.sql
  • packages/database/supabase/schemas/assets.sql
📚 Learning: 2025-05-20T03:02:36.027Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 165
File: packages/database/supabase/schemas/extensions.sql:1-11
Timestamp: 2025-05-20T03:02:36.027Z
Learning: The necessary schemas for Supabase extensions (like 'extensions', 'graphql', and 'vault') are automatically created by Supabase before extensions are installed, so they don't need to be explicitly created in SQL migration files.

Applied to files:

  • packages/database/supabase/migrations/20260109035003_fileref.sql
  • packages/database/supabase/schemas/assets.sql
📚 Learning: 2025-05-20T03:11:07.917Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 165
File: packages/database/supabase/migrations/20250504202930_content_tables.sql:37-45
Timestamp: 2025-05-20T03:11:07.917Z
Learning: When reviewing SQL migrations in the discourse-graph codebase, understand that historical migration files should not be modified once they're applied. Instead, issues should be fixed in subsequent migrations or schema definition files. This is why maparent indicated "migration, not changing. corrected in schema."

Applied to files:

  • packages/database/supabase/migrations/20260109035003_fileref.sql
  • packages/database/supabase/schemas/assets.sql
📚 Learning: 2025-08-23T17:30:00.244Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 0
File: :0-0
Timestamp: 2025-08-23T17:30:00.244Z
Learning: For PRs involving both `packages/database/supabase/schemas` and `packages/database/supabase/migrations` files, only report errors in the schema files to avoid duplication, as long as the schema and migration files are consistent.

Applied to files:

  • packages/database/supabase/migrations/20260109035003_fileref.sql
  • packages/database/supabase/schemas/assets.sql
📚 Learning: 2025-07-13T16:47:14.352Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 0
File: :0-0
Timestamp: 2025-07-13T16:47:14.352Z
Learning: When reviewing SQL functions in the discourse-graph codebase, be mindful that suggesting additional explicit error handling may introduce unnecessary database queries. Functions that use appropriate ON CONFLICT clauses may already handle errors sufficiently without needing extra validation queries.

Applied to files:

  • packages/database/supabase/migrations/20260109035003_fileref.sql
  • packages/database/supabase/schemas/assets.sql
📚 Learning: 2026-01-01T18:13:07.400Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 660
File: packages/database/supabase/migrations/20251231203152_migrate_space_access_to_user.sql:182-192
Timestamp: 2026-01-01T18:13:07.400Z
Learning: In PostgreSQL migrations, if you rename a table with ALTER TABLE ... RENAME TO, all properties including Row Level Security (RLS) settings remain on the table. If RLS was enabled before the rename, it will still be enabled afterward and does not need to be re-enabled via ALTER TABLE ... ENABLE ROW LEVEL SECURITY. Apply this guidance to any migration that uses RENAME TO in order to avoid unnecessary SQL for RLS.

Applied to files:

  • packages/database/supabase/migrations/20260109035003_fileref.sql
📚 Learning: 2025-06-29T14:06:04.789Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 254
File: packages/database/supabase/schemas/space.sql:37-45
Timestamp: 2025-06-29T14:06:04.789Z
Learning: For PostgreSQL triggers, return value requirements differ between BEFORE and AFTER triggers. BEFORE triggers must return appropriate values (OLD for DELETE, NEW for INSERT/UPDATE) because they can affect the operation. AFTER triggers can return any value (NEW, OLD, or NULL) because the return value is ignored - the operation has already completed.

Applied to files:

  • packages/database/supabase/schemas/assets.sql
🧬 Code graph analysis (2)
apps/obsidian/src/utils/publishNode.ts (1)
packages/database/src/lib/files.ts (1)
  • addFile (6-57)
packages/database/src/lib/files.ts (1)
packages/database/src/lib/client.ts (1)
  • DGSupabaseClient (9-9)
🪛 GitHub Actions: CI
packages/database/src/lib/files.ts

[error] 1-1: Cannot find module '~/utils/upsertNodesAsContentWithEmbeddings' or its corresponding type declarations (src/lib/files.ts:1,62).

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Supabase Preview
  • GitHub Check: Supabase Preview
  • GitHub Check: Supabase Preview
🔇 Additional comments (6)
packages/database/src/lib/files.ts (1)

31-55: Insert + conflict-update flow looks solid.

The duplicate-key handling keeps FileReference metadata fresh without failing re-publishes.

apps/obsidian/src/utils/publishNode.ts (2)

7-8: Attachment regexes now cover markdown and wikilink embeds.

Good coverage for both ![alt](path) and ![[path]].


82-109: Attachment resolution + upload pipeline is coherent.

Resolved paths, binary reads, and addFile integration fit the publish flow well.

packages/database/supabase/migrations/20260109035003_fileref.sql (1)

1-131: Skipping migration review in favor of schema files.

Per project guidance, issues should be addressed in supabase/schemas (not historical migrations). Based on learnings, focusing review on schema sources instead.

packages/database/supabase/schemas/assets.sql (2)

1-55: Core FileReference + RLS setup looks consistent.

Table structure, indexes, and policies align with the access model.


107-131: Storage bucket seed + policies look good.

Policies align with the file_access/file_exists functions and the assets bucket.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@maparent
Copy link
Collaborator Author

@CodeRabbit review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 15, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@apps/obsidian/src/utils/publishNode.ts`:
- Around line 82-109: Filter out markdown note links before uploading: when
iterating matches from MARKDOWN_FILE_LINK_RE and WIKILINK_RE, resolve the
attachment via plugin.app.metadataCache.getFirstLinkpathDest as before but skip
processing if the resolved attachment is a markdown/text file (e.g.,
attachment.path endsWith('.md') or mime.lookup(attachment.path) ===
'text/markdown' / startsWith('text/')). Only push non-markdown paths into
existingFiles and only call addFile for those non-text assets; leave
console.warn and continue for missing attachments.
- Around line 7-8: The current regex-based extraction using
MARKDOWN_FILE_LINK_RE and WIKILINK_RE in publishNode.ts fails for filenames with
parentheses and skips valid embeds; replace the regex parsing with Obsidian's
metadata cache: use plugin.app.metadataCache.getFileCache(file)?.embeds
(defaulting to empty array), iterate embeds and resolve each embed via
plugin.app.metadataCache.getFirstLinkpathDest(link, file.path), then process the
resolved attachment instead of relying on the regex matches; remove or disable
MARKDOWN_FILE_LINK_RE and WIKILINK_RE usage and ensure tests cover filenames
with parentheses.
♻️ Duplicate comments (1)
packages/database/supabase/schemas/assets.sql (1)

56-113: Prevent GC of shared hashes (use filehash consistently).

Line 95 checks remaining references by filepath, but GC entries are keyed by hashes (Line 96) and storage objects are hash-named. If a hash is referenced by multiple filepaths, deleting one path can enqueue GC and delete an asset still in use. Align the GC table and trigger logic to use filehash consistently (and rename the column to avoid ambiguity).

✅ Suggested fix (schema-level)
--- a/packages/database/supabase/schemas/assets.sql
+++ b/packages/database/supabase/schemas/assets.sql
@@
--- We could pass the name to the edge function, but it's safer to accumulate paths in a table
+-- We could pass the name to the edge function, but it's safer to accumulate hashes in a table
 CREATE TABLE IF NOT EXISTS public.file_gc (
-    filepath character varying NOT NULL PRIMARY KEY
+    filehash character varying NOT NULL PRIMARY KEY
 );
@@
-    IF (SELECT count(content_id) FROM public."FileReference" AS fr WHERE fr.filepath=OLD.filepath) = 0 THEN
-        INSERT INTO public.file_gc VALUES (OLD.filehash);
+    IF (SELECT count(content_id) FROM public."FileReference" AS fr WHERE fr.filehash = OLD.filehash) = 0 THEN
+        INSERT INTO public.file_gc (filehash) VALUES (OLD.filehash);
@@
-    IF NEW.filehash IS NOT NULL THEN
-        DELETE FROM public.file_gc WHERE filepath = NEW.filehash;
+    IF NEW.filehash IS NOT NULL THEN
+        DELETE FROM public.file_gc WHERE filehash = NEW.filehash;
     END IF;
@@
-    DELETE FROM public.file_gc WHERE filepath = NEW.filehash;
+    DELETE FROM public.file_gc WHERE filehash = NEW.filehash;
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9bea3ba and a3d572f.

📒 Files selected for processing (4)
  • apps/obsidian/src/utils/publishNode.ts
  • packages/database/src/lib/files.ts
  • packages/database/supabase/migrations/20260109035003_fileref.sql
  • packages/database/supabase/schemas/assets.sql
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/database/src/lib/files.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use Tailwind CSS for styling where possible
When refactoring inline styles, use tailwind classes
Follow responsive design principles
Prefer type over interface for TypeScript
Use explicit return types for functions in TypeScript
Avoid any types in TypeScript when possible
Prefer arrow functions over regular function declarations
Use named parameters (object destructuring) when a function has more than 2 parameters
Use PascalCase for components and types
Use camelCase for variables and functions
Use UPPERCASE for constants
Prefer small, focused functions over inline code
Extract complex logic into well-named functions
Function names should describe their purpose clearly
Choose descriptive function names that make comments unnecessary
Break down complex operations into smaller, meaningful functions
Prefer early returns over nested conditionals for better readability
Prefer util functions for reusable logic and common operations
Add comments only when necessary; descriptive names should minimize the need for comments
Explain the why, not the what in comments, focusing on reasoning, trade-offs, and approaches
Document limitations, known bugs, or edge cases where behavior may not align with expectations

Files:

  • apps/obsidian/src/utils/publishNode.ts
{packages/ui/**,apps/**}/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use platform-native UI components first with shadcn/ui as a fallback

Files:

  • apps/obsidian/src/utils/publishNode.ts
apps/obsidian/**/*.{ts,tsx,js,jsx,json}

📄 CodeRabbit inference engine (apps/obsidian/AGENTS.md)

Prefer existing dependencies from package.json for the Obsidian plugin

Files:

  • apps/obsidian/src/utils/publishNode.ts
apps/obsidian/**/*.{md,ts,tsx,js,jsx}

📄 CodeRabbit inference engine (apps/obsidian/AGENTS.md)

apps/obsidian/**/*.{md,ts,tsx,js,jsx}: Utilize the icon anchor in embedded images to tweak spacing around icons for proper alignment with adjacent text
Icons should be surrounded by parenthesis in references (e.g., ( lucide-cog.svg > icon ))

Files:

  • apps/obsidian/src/utils/publishNode.ts
🧠 Learnings (20)
📓 Common learnings
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 198
File: packages/database/supabase/migrations/20250605083319_alpha_upload.sql:157-337
Timestamp: 2025-06-09T16:57:14.681Z
Learning: Migration files in packages/database/supabase/migrations/ are historical snapshots that preserve database schema and functions as they existed at the time of creation. These files should not be updated to reflect current schema changes, even if they reference tables or columns that have since been modified or removed. Schema incompatibilities in migration files are expected and acceptable as they represent the valid state at the time of migration.
📚 Learning: 2026-01-14T22:16:15.442Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 698
File: apps/obsidian/src/utils/publishNode.ts:14-19
Timestamp: 2026-01-14T22:16:15.442Z
Learning: In apps/obsidian/src/utils/publishNode.ts, the function intentionally publishes to only the first group (data[0]?.group_id) from a user's group memberships. This single-group limitation is a deliberate design choice for the MVP phase, even though the system supports multi-group memberships via the group_membership table and the publishedToGroups frontmatter array.

Applied to files:

  • apps/obsidian/src/utils/publishNode.ts
📚 Learning: 2026-01-15T03:03:39.934Z
Learnt from: CR
Repo: DiscourseGraphs/discourse-graph PR: 0
File: apps/obsidian/AGENTS.md:0-0
Timestamp: 2026-01-15T03:03:39.934Z
Learning: Applies to apps/obsidian/**/*.{md,ts,tsx,js,jsx} : Utilize the icon anchor in embedded images to tweak spacing around icons for proper alignment with adjacent text

Applied to files:

  • apps/obsidian/src/utils/publishNode.ts
📚 Learning: 2025-08-25T15:53:21.799Z
Learnt from: sid597
Repo: DiscourseGraphs/discourse-graph PR: 372
File: apps/roam/src/components/DiscourseNodeMenu.tsx:116-116
Timestamp: 2025-08-25T15:53:21.799Z
Learning: In apps/roam/src/components/DiscourseNodeMenu.tsx, when handling tag insertion, multiple leading hashtags (like ##foo) should be preserved as they represent user intent, not normalized to a single hashtag. The current regex /^#/ is correct as it only removes one leading # before adding one back, maintaining any additional hashtags the user intended.

Applied to files:

  • apps/obsidian/src/utils/publishNode.ts
📚 Learning: 2026-01-15T03:03:39.934Z
Learnt from: CR
Repo: DiscourseGraphs/discourse-graph PR: 0
File: apps/obsidian/AGENTS.md:0-0
Timestamp: 2026-01-15T03:03:39.934Z
Learning: Applies to apps/obsidian/**/*.{md,ts,tsx,js,jsx} : Icons should be surrounded by parenthesis in references (e.g., ( lucide-cog.svg > icon ))

Applied to files:

  • apps/obsidian/src/utils/publishNode.ts
📚 Learning: 2026-01-15T03:03:39.934Z
Learnt from: CR
Repo: DiscourseGraphs/discourse-graph PR: 0
File: apps/obsidian/AGENTS.md:0-0
Timestamp: 2026-01-15T03:03:39.934Z
Learning: Applies to apps/obsidian/Attachments/icons/obsidian-icon-*.svg : Add the prefix obsidian-icon- before the Obsidian icon name when naming icon files

Applied to files:

  • apps/obsidian/src/utils/publishNode.ts
📚 Learning: 2025-11-23T23:53:43.094Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 0
File: :0-0
Timestamp: 2025-11-23T23:53:43.094Z
Learning: In `apps/roam/src/utils/supabaseContext.ts`, the Roam URL normalization uses a targeted string replacement `url.replace("/?server-port=3333#/", "/#/")` rather than URL parsing to avoid impacting unforeseen URL patterns. This conservative approach is intentional to handle only the specific known case.

Applied to files:

  • apps/obsidian/src/utils/publishNode.ts
📚 Learning: 2025-10-18T18:58:16.100Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 504
File: apps/roam/src/utils/syncDgNodesToSupabase.ts:523-531
Timestamp: 2025-10-18T18:58:16.100Z
Learning: In `apps/roam/src/utils/syncDgNodesToSupabase.ts`, partial successes from `upsertNodesToSupabaseAsContent` and `addMissingEmbeddings` (indicated by numeric return values showing the count of successful operations) should NOT trigger backoff. Only complete failures (false) should trigger the exponential backoff mechanism. This design allows the sync process to continue making progress even when some items fail.

Applied to files:

  • apps/obsidian/src/utils/publishNode.ts
📚 Learning: 2025-06-09T16:57:14.681Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 198
File: packages/database/supabase/migrations/20250605083319_alpha_upload.sql:157-337
Timestamp: 2025-06-09T16:57:14.681Z
Learning: Migration files in packages/database/supabase/migrations/ are historical snapshots that preserve database schema and functions as they existed at the time of creation. These files should not be updated to reflect current schema changes, even if they reference tables or columns that have since been modified or removed. Schema incompatibilities in migration files are expected and acceptable as they represent the valid state at the time of migration.

Applied to files:

  • packages/database/supabase/migrations/20260109035003_fileref.sql
  • packages/database/supabase/schemas/assets.sql
📚 Learning: 2025-05-20T03:04:21.602Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 165
File: packages/database/supabase/migrations/20250512142307_sync_table.sql:20-21
Timestamp: 2025-05-20T03:04:21.602Z
Learning: Database migration files (like the SQL files in packages/database/supabase/migrations/) should not be modified after they're created, even to fix minor issues like unused variables. Issues should be addressed in newer schema files instead to maintain migration history integrity.

Applied to files:

  • packages/database/supabase/migrations/20260109035003_fileref.sql
  • packages/database/supabase/schemas/assets.sql
📚 Learning: 2025-05-20T03:11:07.917Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 165
File: packages/database/supabase/migrations/20250504202930_content_tables.sql:37-45
Timestamp: 2025-05-20T03:11:07.917Z
Learning: When reviewing SQL migrations in this codebase, understand that historical migration files should not be modified once they're applied. Instead, issues should be fixed in subsequent migrations or schema definition files. This is why maparent indicated "migration, not changing. corrected in schema."

Applied to files:

  • packages/database/supabase/migrations/20260109035003_fileref.sql
  • packages/database/supabase/schemas/assets.sql
📚 Learning: 2025-05-22T23:50:23.771Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 165
File: packages/database/supabase/migrations/20250513173724_content_concept_key.sql:37-60
Timestamp: 2025-05-22T23:50:23.771Z
Learning: For the discourse-graph project, database schema management focuses on the final state in the supabase/schemas directory, not on the individual migration files. When reviewing database changes, consider only the schema definitions in this directory, not potential duplications or conflicts across migration files.

Applied to files:

  • packages/database/supabase/migrations/20260109035003_fileref.sql
  • packages/database/supabase/schemas/assets.sql
📚 Learning: 2025-05-20T03:11:07.917Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 165
File: packages/database/supabase/migrations/20250504202930_content_tables.sql:37-45
Timestamp: 2025-05-20T03:11:07.917Z
Learning: When reviewing SQL migrations in the discourse-graph codebase, understand that historical migration files should not be modified once they're applied. Instead, issues should be fixed in subsequent migrations or schema definition files. This is why maparent indicated "migration, not changing. corrected in schema."

Applied to files:

  • packages/database/supabase/migrations/20260109035003_fileref.sql
  • packages/database/supabase/schemas/assets.sql
📚 Learning: 2025-05-20T03:01:11.220Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 165
File: packages/database/supabase/migrations/20250504195841_remote_schema.sql:1-7
Timestamp: 2025-05-20T03:01:11.220Z
Learning: Supabase automatically creates necessary schemas like the "extensions" schema before migrations run, so there's no need to explicitly create these schemas in migration files.

Applied to files:

  • packages/database/supabase/migrations/20260109035003_fileref.sql
  • packages/database/supabase/schemas/assets.sql
📚 Learning: 2025-05-20T03:02:36.027Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 165
File: packages/database/supabase/schemas/extensions.sql:1-11
Timestamp: 2025-05-20T03:02:36.027Z
Learning: The necessary schemas for Supabase extensions (like 'extensions', 'graphql', and 'vault') are automatically created by Supabase before extensions are installed, so they don't need to be explicitly created in SQL migration files.

Applied to files:

  • packages/database/supabase/migrations/20260109035003_fileref.sql
  • packages/database/supabase/schemas/assets.sql
📚 Learning: 2026-01-01T18:13:07.400Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 660
File: packages/database/supabase/migrations/20251231203152_migrate_space_access_to_user.sql:182-192
Timestamp: 2026-01-01T18:13:07.400Z
Learning: In PostgreSQL migrations, if you rename a table with ALTER TABLE ... RENAME TO, all properties including Row Level Security (RLS) settings remain on the table. If RLS was enabled before the rename, it will still be enabled afterward and does not need to be re-enabled via ALTER TABLE ... ENABLE ROW LEVEL SECURITY. Apply this guidance to any migration that uses RENAME TO in order to avoid unnecessary SQL for RLS.

Applied to files:

  • packages/database/supabase/migrations/20260109035003_fileref.sql
📚 Learning: 2025-08-23T17:30:00.244Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 0
File: :0-0
Timestamp: 2025-08-23T17:30:00.244Z
Learning: For PRs involving both `packages/database/supabase/schemas` and `packages/database/supabase/migrations` files, only report errors in the schema files to avoid duplication, as long as the schema and migration files are consistent.

Applied to files:

  • packages/database/supabase/migrations/20260109035003_fileref.sql
  • packages/database/supabase/schemas/assets.sql
📚 Learning: 2025-07-13T16:47:14.352Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 0
File: :0-0
Timestamp: 2025-07-13T16:47:14.352Z
Learning: When reviewing SQL functions in the discourse-graph codebase, be mindful that suggesting additional explicit error handling may introduce unnecessary database queries. Functions that use appropriate ON CONFLICT clauses may already handle errors sufficiently without needing extra validation queries.

Applied to files:

  • packages/database/supabase/migrations/20260109035003_fileref.sql
  • packages/database/supabase/schemas/assets.sql
📚 Learning: 2025-10-15T00:12:54.623Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 482
File: packages/database/supabase/schemas/access_token.sql:43-43
Timestamp: 2025-10-15T00:12:54.623Z
Learning: In PostgreSQL RLS policies, you cannot combine multiple operations like `FOR SELECT, UPDATE, DELETE` in a single CREATE POLICY statement. Use `FOR ALL` to cover all operations, or create separate policies for each operation type (FOR SELECT, FOR INSERT, FOR UPDATE, FOR DELETE).

Applied to files:

  • packages/database/supabase/migrations/20260109035003_fileref.sql
  • packages/database/supabase/schemas/assets.sql
📚 Learning: 2025-06-29T14:06:04.789Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 254
File: packages/database/supabase/schemas/space.sql:37-45
Timestamp: 2025-06-29T14:06:04.789Z
Learning: For PostgreSQL triggers, return value requirements differ between BEFORE and AFTER triggers. BEFORE triggers must return appropriate values (OLD for DELETE, NEW for INSERT/UPDATE) because they can affect the operation. AFTER triggers can return any value (NEW, OLD, or NULL) because the return value is ignored - the operation has already completed.

Applied to files:

  • packages/database/supabase/schemas/assets.sql
🧬 Code graph analysis (1)
apps/obsidian/src/utils/publishNode.ts (1)
packages/database/src/lib/files.ts (1)
  • addFile (5-56)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Supabase Preview
  • GitHub Check: Supabase Preview
🔇 Additional comments (4)
apps/obsidian/src/utils/publishNode.ts (4)

3-5: Verify mime-types default import compatibility.

If esModuleInterop isn’t enabled, the default import can be undefined. Consider switching to a namespace import or confirm the TS config.

🛠️ Suggested change (if needed)
-import mime from "mime-types";
+import * as mime from "mime-types";

23-25: Good guard for missing Supabase context.

Fail-fast here keeps publish behavior predictable.


45-53: Idempotent upserts look good — please verify SDK behavior.

ignoreDuplicates is the right intent, but confirm the option is supported and behaves as expected for your supabase-js version.

Also applies to: 65-73


110-120: Cleanup handling is solid.

Nice to see cleanup runs even when there are zero attachments; this avoids stale references.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@maparent maparent requested a review from trangdoan982 January 16, 2026 01:19
@maparent
Copy link
Collaborator Author

@CodeRabbit review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 16, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@apps/obsidian/src/utils/publishNode.ts`:
- Around line 42-46: Remove the stray console.log in publishNode.ts that prints
idResponse.data.last_modified, lastModifiedDb.getTime(), and file.stat.mtime;
either delete this debug statement or replace it with a proper logger call at
debug/trace level (e.g., using the existing project logger) gated by
configuration, so it does not run in production during every publish. Target the
console.log inside the publishNode flow where idResponse, lastModifiedDb, and
file.stat.mtime are available.

In `@packages/database/src/lib/files.ts`:
- Around line 26-29: Replace the fragile string check on
uploadResult.error.message with a check of the error code: after calling
client.storage.from(ASSETS_BUCKET_NAME).upload(hashvalue, content, {contentType:
mimetype}) inspect uploadResult.error.code and treat code === "already_exists"
as the non-fatal case; for any other error code rethrow uploadResult.error. Keep
the existing upload call and variables (ASSETS_BUCKET_NAME, hashvalue, content,
mimetype) and only change the conditional from checking error.message to
checking uploadResult.error.code.
♻️ Duplicate comments (1)
packages/database/supabase/schemas/assets.sql (1)

95-101: GC trigger should check references by filehash, not filepath.

If multiple paths share a hash (or a hash changes on update), counting by filepath can enqueue a hash that’s still referenced, leading to premature GC or orphaned blobs. Switch the reference check to filehash to align with storage keys.

🛠️ Suggested fix
-    IF (SELECT count(content_id) FROM public."FileReference" AS fr WHERE fr.filepath=OLD.filepath) = 0 THEN
+    IF (SELECT count(*) FROM public."FileReference" AS fr WHERE fr.filehash = OLD.filehash) = 0 THEN
         INSERT INTO public.file_gc VALUES (OLD.filehash);
         -- TODO: Invocation with pg_net, following the pattern in
         -- https://supabase.com/docs/guides/functions/schedule-functions
     END IF;
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a3d572f and 9d33307.

📒 Files selected for processing (4)
  • apps/obsidian/src/utils/publishNode.ts
  • packages/database/src/lib/files.ts
  • packages/database/supabase/migrations/20260109035003_fileref.sql
  • packages/database/supabase/schemas/assets.sql
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use Tailwind CSS for styling where possible
When refactoring inline styles, use tailwind classes
Follow responsive design principles
Prefer type over interface for TypeScript
Use explicit return types for functions in TypeScript
Avoid any types in TypeScript when possible
Prefer arrow functions over regular function declarations
Use named parameters (object destructuring) when a function has more than 2 parameters
Use PascalCase for components and types
Use camelCase for variables and functions
Use UPPERCASE for constants
Prefer small, focused functions over inline code
Extract complex logic into well-named functions
Function names should describe their purpose clearly
Choose descriptive function names that make comments unnecessary
Break down complex operations into smaller, meaningful functions
Prefer early returns over nested conditionals for better readability
Prefer util functions for reusable logic and common operations
Add comments only when necessary; descriptive names should minimize the need for comments
Explain the why, not the what in comments, focusing on reasoning, trade-offs, and approaches
Document limitations, known bugs, or edge cases where behavior may not align with expectations

Files:

  • apps/obsidian/src/utils/publishNode.ts
  • packages/database/src/lib/files.ts
{packages/ui/**,apps/**}/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use platform-native UI components first with shadcn/ui as a fallback

Files:

  • apps/obsidian/src/utils/publishNode.ts
apps/obsidian/**/*.{ts,tsx,js,jsx,json}

📄 CodeRabbit inference engine (apps/obsidian/AGENTS.md)

Prefer existing dependencies from package.json for the Obsidian plugin

Files:

  • apps/obsidian/src/utils/publishNode.ts
apps/obsidian/**/*.{md,ts,tsx,js,jsx}

📄 CodeRabbit inference engine (apps/obsidian/AGENTS.md)

apps/obsidian/**/*.{md,ts,tsx,js,jsx}: Utilize the icon anchor in embedded images to tweak spacing around icons for proper alignment with adjacent text
Icons should be surrounded by parenthesis in references (e.g., ( lucide-cog.svg > icon ))

Files:

  • apps/obsidian/src/utils/publishNode.ts
🧠 Learnings (29)
📓 Common learnings
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 198
File: packages/database/supabase/migrations/20250605083319_alpha_upload.sql:157-337
Timestamp: 2025-06-09T16:57:14.681Z
Learning: Migration files in packages/database/supabase/migrations/ are historical snapshots that preserve database schema and functions as they existed at the time of creation. These files should not be updated to reflect current schema changes, even if they reference tables or columns that have since been modified or removed. Schema incompatibilities in migration files are expected and acceptable as they represent the valid state at the time of migration.
📚 Learning: 2026-01-14T22:16:15.442Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 698
File: apps/obsidian/src/utils/publishNode.ts:14-19
Timestamp: 2026-01-14T22:16:15.442Z
Learning: In apps/obsidian/src/utils/publishNode.ts, the function intentionally publishes to only the first group (data[0]?.group_id) from a user's group memberships. This single-group limitation is a deliberate design choice for the MVP phase, even though the system supports multi-group memberships via the group_membership table and the publishedToGroups frontmatter array.

Applied to files:

  • apps/obsidian/src/utils/publishNode.ts
📚 Learning: 2026-01-15T03:03:39.934Z
Learnt from: CR
Repo: DiscourseGraphs/discourse-graph PR: 0
File: apps/obsidian/AGENTS.md:0-0
Timestamp: 2026-01-15T03:03:39.934Z
Learning: Applies to apps/obsidian/**/*.{md,ts,tsx,js,jsx} : Utilize the icon anchor in embedded images to tweak spacing around icons for proper alignment with adjacent text

Applied to files:

  • apps/obsidian/src/utils/publishNode.ts
📚 Learning: 2025-08-25T15:53:21.799Z
Learnt from: sid597
Repo: DiscourseGraphs/discourse-graph PR: 372
File: apps/roam/src/components/DiscourseNodeMenu.tsx:116-116
Timestamp: 2025-08-25T15:53:21.799Z
Learning: In apps/roam/src/components/DiscourseNodeMenu.tsx, when handling tag insertion, multiple leading hashtags (like ##foo) should be preserved as they represent user intent, not normalized to a single hashtag. The current regex /^#/ is correct as it only removes one leading # before adding one back, maintaining any additional hashtags the user intended.

Applied to files:

  • apps/obsidian/src/utils/publishNode.ts
📚 Learning: 2026-01-15T03:03:39.934Z
Learnt from: CR
Repo: DiscourseGraphs/discourse-graph PR: 0
File: apps/obsidian/AGENTS.md:0-0
Timestamp: 2026-01-15T03:03:39.934Z
Learning: Applies to apps/obsidian/**/*.{md,ts,tsx,js,jsx} : Icons should be surrounded by parenthesis in references (e.g., ( lucide-cog.svg > icon ))

Applied to files:

  • apps/obsidian/src/utils/publishNode.ts
📚 Learning: 2026-01-15T03:03:39.934Z
Learnt from: CR
Repo: DiscourseGraphs/discourse-graph PR: 0
File: apps/obsidian/AGENTS.md:0-0
Timestamp: 2026-01-15T03:03:39.934Z
Learning: Applies to apps/obsidian/Attachments/icons/obsidian-icon-*.svg : Add the prefix obsidian-icon- before the Obsidian icon name when naming icon files

Applied to files:

  • apps/obsidian/src/utils/publishNode.ts
📚 Learning: 2025-11-23T23:53:43.094Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 0
File: :0-0
Timestamp: 2025-11-23T23:53:43.094Z
Learning: In `apps/roam/src/utils/supabaseContext.ts`, the Roam URL normalization uses a targeted string replacement `url.replace("/?server-port=3333#/", "/#/")` rather than URL parsing to avoid impacting unforeseen URL patterns. This conservative approach is intentional to handle only the specific known case.

Applied to files:

  • apps/obsidian/src/utils/publishNode.ts
📚 Learning: 2025-10-18T18:58:16.100Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 504
File: apps/roam/src/utils/syncDgNodesToSupabase.ts:523-531
Timestamp: 2025-10-18T18:58:16.100Z
Learning: In `apps/roam/src/utils/syncDgNodesToSupabase.ts`, partial successes from `upsertNodesToSupabaseAsContent` and `addMissingEmbeddings` (indicated by numeric return values showing the count of successful operations) should NOT trigger backoff. Only complete failures (false) should trigger the exponential backoff mechanism. This design allows the sync process to continue making progress even when some items fail.

Applied to files:

  • apps/obsidian/src/utils/publishNode.ts
  • packages/database/src/lib/files.ts
📚 Learning: 2026-01-15T03:03:39.934Z
Learnt from: CR
Repo: DiscourseGraphs/discourse-graph PR: 0
File: apps/obsidian/AGENTS.md:0-0
Timestamp: 2026-01-15T03:03:39.934Z
Learning: Applies to apps/obsidian/**/*.{ts,tsx,js,jsx,json} : Prefer existing dependencies from package.json for the Obsidian plugin

Applied to files:

  • apps/obsidian/src/utils/publishNode.ts
📚 Learning: 2025-07-08T14:48:38.048Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 272
File: packages/ui/src/lib/supabase/contextFunctions.ts:50-111
Timestamp: 2025-07-08T14:48:38.048Z
Learning: The Supabase client does not offer transaction support, so idempotent upserts with proper conflict resolution (using onConflict with ignoreDuplicates: false) are the preferred approach for multi-step database operations in packages/ui/src/lib/supabase/contextFunctions.ts. This pattern prevents orphaned records when retrying operations.

Applied to files:

  • packages/database/src/lib/files.ts
📚 Learning: 2025-08-31T17:37:17.173Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 400
File: packages/database/src/dbDotEnv.js:24-31
Timestamp: 2025-08-31T17:37:17.173Z
Learning: The "all" variant for SUPABASE_USE_DB should only be used in createEnv.mts and not in the dbDotEnv module. It was incorrectly added to dbDotEnv.d.ts but should be removed to maintain proper separation of concerns.

Applied to files:

  • packages/database/src/lib/files.ts
📚 Learning: 2025-07-21T14:22:20.752Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 291
File: packages/database/supabase/functions/create-space/index.ts:0-0
Timestamp: 2025-07-21T14:22:20.752Z
Learning: In the discourse-graph codebase, types.gen.ts is not accessible from Supabase edge functions, requiring duplication of types and utilities when needed in the edge function environment at packages/database/supabase/functions/.

Applied to files:

  • packages/database/src/lib/files.ts
📚 Learning: 2025-09-11T23:54:41.861Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 431
File: packages/database/package.json:18-22
Timestamp: 2025-09-11T23:54:41.861Z
Learning: In the discourse-graph repository's packages/database/package.json, the typesVersions mapping for "./dbDotEnv" must retain the leading "./" prefix. Removing it (as suggested by standard TypeScript documentation) fails in practice in their specific build environment/tooling setup.

Applied to files:

  • packages/database/src/lib/files.ts
📚 Learning: 2025-06-25T18:03:52.669Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 241
File: packages/database/tsconfig.json:3-7
Timestamp: 2025-06-25T18:03:52.669Z
Learning: The packages/database directory in the discourse-graph repository has a unique structure as a database schema/migration package. It contains doc/, scripts/, supabase/ directories and TypeScript files at the root level, but no typical src/, test/, dist/, or node_modules directories. The current tsconfig.json with "include": ["."] and "exclude": ["supabase"] is appropriate for this structure.

Applied to files:

  • packages/database/src/lib/files.ts
📚 Learning: 2025-05-30T14:49:24.016Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 182
File: apps/website/app/utils/supabase/dbUtils.ts:22-28
Timestamp: 2025-05-30T14:49:24.016Z
Learning: In apps/website/app/utils/supabase/dbUtils.ts, expanding the KNOWN_EMBEDDINGS and DEFAULT_DIMENSIONS mappings to support additional embedding models requires corresponding database model changes (creating new embedding tables), which should be scoped as separate work from API route implementations.

Applied to files:

  • packages/database/src/lib/files.ts
📚 Learning: 2025-07-13T16:47:14.352Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 0
File: :0-0
Timestamp: 2025-07-13T16:47:14.352Z
Learning: In the discourse-graph codebase, types.gen.ts contains automatically generated database function type definitions that may have reordered signatures between versions. This reordering is expected behavior from the code generation process and should not be flagged as an issue requiring fixes.

Applied to files:

  • packages/database/src/lib/files.ts
📚 Learning: 2025-09-01T19:09:07.052Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 0
File: :0-0
Timestamp: 2025-09-01T19:09:07.052Z
Learning: User prefers to avoid making types explicit in each require statement due to anti-DRY concerns when working with module imports in TypeScript.

Applied to files:

  • packages/database/src/lib/files.ts
📚 Learning: 2025-08-23T17:30:00.244Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 0
File: :0-0
Timestamp: 2025-08-23T17:30:00.244Z
Learning: For PRs involving both `packages/database/supabase/schemas` and `packages/database/supabase/migrations` files, only report errors in the schema files to avoid duplication, as long as the schema and migration files are consistent.

Applied to files:

  • packages/database/src/lib/files.ts
  • packages/database/supabase/migrations/20260109035003_fileref.sql
  • packages/database/supabase/schemas/assets.sql
📚 Learning: 2025-06-09T16:57:14.681Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 198
File: packages/database/supabase/migrations/20250605083319_alpha_upload.sql:157-337
Timestamp: 2025-06-09T16:57:14.681Z
Learning: Migration files in packages/database/supabase/migrations/ are historical snapshots that preserve database schema and functions as they existed at the time of creation. These files should not be updated to reflect current schema changes, even if they reference tables or columns that have since been modified or removed. Schema incompatibilities in migration files are expected and acceptable as they represent the valid state at the time of migration.

Applied to files:

  • packages/database/supabase/migrations/20260109035003_fileref.sql
  • packages/database/supabase/schemas/assets.sql
📚 Learning: 2025-05-20T03:04:21.602Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 165
File: packages/database/supabase/migrations/20250512142307_sync_table.sql:20-21
Timestamp: 2025-05-20T03:04:21.602Z
Learning: Database migration files (like the SQL files in packages/database/supabase/migrations/) should not be modified after they're created, even to fix minor issues like unused variables. Issues should be addressed in newer schema files instead to maintain migration history integrity.

Applied to files:

  • packages/database/supabase/migrations/20260109035003_fileref.sql
  • packages/database/supabase/schemas/assets.sql
📚 Learning: 2025-05-20T03:11:07.917Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 165
File: packages/database/supabase/migrations/20250504202930_content_tables.sql:37-45
Timestamp: 2025-05-20T03:11:07.917Z
Learning: When reviewing SQL migrations in this codebase, understand that historical migration files should not be modified once they're applied. Instead, issues should be fixed in subsequent migrations or schema definition files. This is why maparent indicated "migration, not changing. corrected in schema."

Applied to files:

  • packages/database/supabase/migrations/20260109035003_fileref.sql
  • packages/database/supabase/schemas/assets.sql
📚 Learning: 2025-05-22T23:50:23.771Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 165
File: packages/database/supabase/migrations/20250513173724_content_concept_key.sql:37-60
Timestamp: 2025-05-22T23:50:23.771Z
Learning: For the discourse-graph project, database schema management focuses on the final state in the supabase/schemas directory, not on the individual migration files. When reviewing database changes, consider only the schema definitions in this directory, not potential duplications or conflicts across migration files.

Applied to files:

  • packages/database/supabase/migrations/20260109035003_fileref.sql
  • packages/database/supabase/schemas/assets.sql
📚 Learning: 2025-05-20T03:11:07.917Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 165
File: packages/database/supabase/migrations/20250504202930_content_tables.sql:37-45
Timestamp: 2025-05-20T03:11:07.917Z
Learning: When reviewing SQL migrations in the discourse-graph codebase, understand that historical migration files should not be modified once they're applied. Instead, issues should be fixed in subsequent migrations or schema definition files. This is why maparent indicated "migration, not changing. corrected in schema."

Applied to files:

  • packages/database/supabase/migrations/20260109035003_fileref.sql
  • packages/database/supabase/schemas/assets.sql
📚 Learning: 2025-05-20T03:01:11.220Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 165
File: packages/database/supabase/migrations/20250504195841_remote_schema.sql:1-7
Timestamp: 2025-05-20T03:01:11.220Z
Learning: Supabase automatically creates necessary schemas like the "extensions" schema before migrations run, so there's no need to explicitly create these schemas in migration files.

Applied to files:

  • packages/database/supabase/migrations/20260109035003_fileref.sql
  • packages/database/supabase/schemas/assets.sql
📚 Learning: 2025-07-13T16:47:14.352Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 0
File: :0-0
Timestamp: 2025-07-13T16:47:14.352Z
Learning: When reviewing SQL functions in the discourse-graph codebase, be mindful that suggesting additional explicit error handling may introduce unnecessary database queries. Functions that use appropriate ON CONFLICT clauses may already handle errors sufficiently without needing extra validation queries.

Applied to files:

  • packages/database/supabase/migrations/20260109035003_fileref.sql
  • packages/database/supabase/schemas/assets.sql
📚 Learning: 2025-10-15T00:12:54.623Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 482
File: packages/database/supabase/schemas/access_token.sql:43-43
Timestamp: 2025-10-15T00:12:54.623Z
Learning: In PostgreSQL RLS policies, you cannot combine multiple operations like `FOR SELECT, UPDATE, DELETE` in a single CREATE POLICY statement. Use `FOR ALL` to cover all operations, or create separate policies for each operation type (FOR SELECT, FOR INSERT, FOR UPDATE, FOR DELETE).

Applied to files:

  • packages/database/supabase/migrations/20260109035003_fileref.sql
  • packages/database/supabase/schemas/assets.sql
📚 Learning: 2026-01-01T18:13:07.400Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 660
File: packages/database/supabase/migrations/20251231203152_migrate_space_access_to_user.sql:182-192
Timestamp: 2026-01-01T18:13:07.400Z
Learning: In PostgreSQL migrations, if you rename a table with ALTER TABLE ... RENAME TO, all properties including Row Level Security (RLS) settings remain on the table. If RLS was enabled before the rename, it will still be enabled afterward and does not need to be re-enabled via ALTER TABLE ... ENABLE ROW LEVEL SECURITY. Apply this guidance to any migration that uses RENAME TO in order to avoid unnecessary SQL for RLS.

Applied to files:

  • packages/database/supabase/migrations/20260109035003_fileref.sql
📚 Learning: 2025-05-20T03:02:36.027Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 165
File: packages/database/supabase/schemas/extensions.sql:1-11
Timestamp: 2025-05-20T03:02:36.027Z
Learning: The necessary schemas for Supabase extensions (like 'extensions', 'graphql', and 'vault') are automatically created by Supabase before extensions are installed, so they don't need to be explicitly created in SQL migration files.

Applied to files:

  • packages/database/supabase/schemas/assets.sql
📚 Learning: 2025-06-29T14:06:04.789Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 254
File: packages/database/supabase/schemas/space.sql:37-45
Timestamp: 2025-06-29T14:06:04.789Z
Learning: For PostgreSQL triggers, return value requirements differ between BEFORE and AFTER triggers. BEFORE triggers must return appropriate values (OLD for DELETE, NEW for INSERT/UPDATE) because they can affect the operation. AFTER triggers can return any value (NEW, OLD, or NULL) because the return value is ignored - the operation has already completed.

Applied to files:

  • packages/database/supabase/schemas/assets.sql
🧬 Code graph analysis (2)
apps/obsidian/src/utils/publishNode.ts (1)
packages/database/src/lib/files.ts (1)
  • addFile (5-57)
packages/database/src/lib/files.ts (1)
packages/database/src/lib/client.ts (1)
  • DGSupabaseClient (9-9)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Supabase Preview
  • GitHub Check: Supabase Preview
🔇 Additional comments (4)
packages/database/supabase/schemas/assets.sql (1)

1-55: FileReference schema + RLS setup looks consistent.

PK/indexes and the visibility view/policies align well with the access model.

packages/database/supabase/migrations/20260109035003_fileref.sql (1)

1-145: No additional comments for the migration mirror.

The schema file is the review source of truth for DB logic in this PR.
Based on learnings, schema files are the focus for DB correctness reviews.

apps/obsidian/src/utils/publishNode.ts (2)

52-80: Access upsert flow looks good.

The upsert + ignoreDuplicates handling keeps publish idempotent.


89-125: Attachment extraction and cleanup look solid.

Using metadata cache embeds + text filtering and unconditional cleanup is a clean, robust flow.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@maparent maparent removed the request for review from trangdoan982 January 16, 2026 04:43
@maparent
Copy link
Collaborator Author

@CodeRabbit review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 16, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@packages/database/src/lib/files.ts`:
- Around line 25-30: The upload error check currently does a strict string
comparison of uploadResult.error.statusCode to "409" which can fail if
statusCode is numeric; update the logic inside the block that calls
client.storage.from(ASSETS_BUCKET_NAME).upload(hashvalue, content, {contentType:
mimetype}) so you coerce the statusCode to a number (e.g.,
Number((uploadResult.error as any).statusCode)) or otherwise normalize types
before comparing to 409, and only throw uploadResult.error when the normalized
statusCode !== 409; keep the rest of the uploadResult.error handling unchanged.
🧹 Nitpick comments (1)
packages/database/src/lib/files.ts (1)

43-56: Insert-then-update pattern is appropriate; consider removing redundant space_id from update.

The manual upsert pattern correctly handles the duplicate key scenario (code "23505"). However, space_id in the update payload (line 47) is redundant since it's matching by content_id and filepath, and space_id shouldn't change for the same reference.

Optional cleanup
     if (frefResult.error.code === "23505") {
       const updateResult = await client.from("FileReference").update({
-        /* eslint-disable `@typescript-eslint/naming-convention` */
-        space_id: spaceId,
         last_modified: lastModified.toISOString(),
-        /* eslint-enable `@typescript-eslint/naming-convention` */
         filehash: hashvalue,
         created: created.toISOString()
       }).eq("content_id", contentId).eq("filepath", fname);
📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9d33307 and ffcf2d5.

📒 Files selected for processing (2)
  • apps/obsidian/src/utils/publishNode.ts
  • packages/database/src/lib/files.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/obsidian/src/utils/publishNode.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use Tailwind CSS for styling where possible
When refactoring inline styles, use tailwind classes
Follow responsive design principles
Prefer type over interface for TypeScript
Use explicit return types for functions in TypeScript
Avoid any types in TypeScript when possible
Prefer arrow functions over regular function declarations
Use named parameters (object destructuring) when a function has more than 2 parameters
Use PascalCase for components and types
Use camelCase for variables and functions
Use UPPERCASE for constants
Prefer small, focused functions over inline code
Extract complex logic into well-named functions
Function names should describe their purpose clearly
Choose descriptive function names that make comments unnecessary
Break down complex operations into smaller, meaningful functions
Prefer early returns over nested conditionals for better readability
Prefer util functions for reusable logic and common operations
Add comments only when necessary; descriptive names should minimize the need for comments
Explain the why, not the what in comments, focusing on reasoning, trade-offs, and approaches
Document limitations, known bugs, or edge cases where behavior may not align with expectations

Files:

  • packages/database/src/lib/files.ts
🧠 Learnings (10)
📚 Learning: 2025-07-08T14:48:38.048Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 272
File: packages/ui/src/lib/supabase/contextFunctions.ts:50-111
Timestamp: 2025-07-08T14:48:38.048Z
Learning: The Supabase client does not offer transaction support, so idempotent upserts with proper conflict resolution (using onConflict with ignoreDuplicates: false) are the preferred approach for multi-step database operations in packages/ui/src/lib/supabase/contextFunctions.ts. This pattern prevents orphaned records when retrying operations.

Applied to files:

  • packages/database/src/lib/files.ts
📚 Learning: 2025-10-18T18:58:16.100Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 504
File: apps/roam/src/utils/syncDgNodesToSupabase.ts:523-531
Timestamp: 2025-10-18T18:58:16.100Z
Learning: In `apps/roam/src/utils/syncDgNodesToSupabase.ts`, partial successes from `upsertNodesToSupabaseAsContent` and `addMissingEmbeddings` (indicated by numeric return values showing the count of successful operations) should NOT trigger backoff. Only complete failures (false) should trigger the exponential backoff mechanism. This design allows the sync process to continue making progress even when some items fail.

Applied to files:

  • packages/database/src/lib/files.ts
📚 Learning: 2025-08-31T17:37:17.173Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 400
File: packages/database/src/dbDotEnv.js:24-31
Timestamp: 2025-08-31T17:37:17.173Z
Learning: The "all" variant for SUPABASE_USE_DB should only be used in createEnv.mts and not in the dbDotEnv module. It was incorrectly added to dbDotEnv.d.ts but should be removed to maintain proper separation of concerns.

Applied to files:

  • packages/database/src/lib/files.ts
📚 Learning: 2025-07-21T14:22:20.752Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 291
File: packages/database/supabase/functions/create-space/index.ts:0-0
Timestamp: 2025-07-21T14:22:20.752Z
Learning: In the discourse-graph codebase, types.gen.ts is not accessible from Supabase edge functions, requiring duplication of types and utilities when needed in the edge function environment at packages/database/supabase/functions/.

Applied to files:

  • packages/database/src/lib/files.ts
📚 Learning: 2025-09-11T23:54:41.861Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 431
File: packages/database/package.json:18-22
Timestamp: 2025-09-11T23:54:41.861Z
Learning: In the discourse-graph repository's packages/database/package.json, the typesVersions mapping for "./dbDotEnv" must retain the leading "./" prefix. Removing it (as suggested by standard TypeScript documentation) fails in practice in their specific build environment/tooling setup.

Applied to files:

  • packages/database/src/lib/files.ts
📚 Learning: 2025-06-25T18:03:52.669Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 241
File: packages/database/tsconfig.json:3-7
Timestamp: 2025-06-25T18:03:52.669Z
Learning: The packages/database directory in the discourse-graph repository has a unique structure as a database schema/migration package. It contains doc/, scripts/, supabase/ directories and TypeScript files at the root level, but no typical src/, test/, dist/, or node_modules directories. The current tsconfig.json with "include": ["."] and "exclude": ["supabase"] is appropriate for this structure.

Applied to files:

  • packages/database/src/lib/files.ts
📚 Learning: 2025-05-30T14:49:24.016Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 182
File: apps/website/app/utils/supabase/dbUtils.ts:22-28
Timestamp: 2025-05-30T14:49:24.016Z
Learning: In apps/website/app/utils/supabase/dbUtils.ts, expanding the KNOWN_EMBEDDINGS and DEFAULT_DIMENSIONS mappings to support additional embedding models requires corresponding database model changes (creating new embedding tables), which should be scoped as separate work from API route implementations.

Applied to files:

  • packages/database/src/lib/files.ts
📚 Learning: 2025-07-13T16:47:14.352Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 0
File: :0-0
Timestamp: 2025-07-13T16:47:14.352Z
Learning: In the discourse-graph codebase, types.gen.ts contains automatically generated database function type definitions that may have reordered signatures between versions. This reordering is expected behavior from the code generation process and should not be flagged as an issue requiring fixes.

Applied to files:

  • packages/database/src/lib/files.ts
📚 Learning: 2025-09-01T19:09:07.052Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 0
File: :0-0
Timestamp: 2025-09-01T19:09:07.052Z
Learning: User prefers to avoid making types explicit in each require statement due to anti-DRY concerns when working with module imports in TypeScript.

Applied to files:

  • packages/database/src/lib/files.ts
📚 Learning: 2025-08-23T17:30:00.244Z
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 0
File: :0-0
Timestamp: 2025-08-23T17:30:00.244Z
Learning: For PRs involving both `packages/database/supabase/schemas` and `packages/database/supabase/migrations` files, only report errors in the schema files to avoid duplication, as long as the schema and migration files are consistent.

Applied to files:

  • packages/database/src/lib/files.ts
🧬 Code graph analysis (1)
packages/database/src/lib/files.ts (1)
packages/database/src/lib/client.ts (1)
  • DGSupabaseClient (9-9)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Supabase Preview
  • GitHub Check: Supabase Preview
🔇 Additional comments (5)
packages/database/src/lib/files.ts (5)

1-4: LGTM!

Clean import setup and constant naming follows guidelines (UPPERCASE for constants).


5-16: LGTM!

Good use of named parameters via object destructuring for a function with many parameters, explicit return type, and arrow function syntax—all per coding guidelines.


17-21: LGTM!

Hash computation is correct. The comment on line 17 appropriately documents the memory assumption.


22-24: LGTM!

RPC call with proper error propagation.


31-41: LGTM!

Insert operation is well-structured with proper field mapping.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@maparent maparent requested a review from trangdoan982 January 16, 2026 15:54
@maparent maparent force-pushed the eng-1285-base-user-affordance-to-trigger-node-sharing branch from d34f8dd to a45aadf Compare January 18, 2026 14:52
@maparent maparent force-pushed the eng-1180-f7-publish-non-text-assets-referenced-by-a-dg-node branch 2 times, most recently from 0cf87a3 to d79feec Compare January 18, 2026 16:11
@maparent maparent marked this pull request as draft January 18, 2026 16:43
@maparent maparent removed the request for review from trangdoan982 January 18, 2026 18:26
@maparent
Copy link
Collaborator Author

@CodeRabbit this was rebased and partly rewritten. Please review.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 18, 2026

@maparent: I'll perform a fresh review of the PR given the rebase and rewrite. The review will cover all the changes comprehensively.


🧠 Learnings used
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 0
File: :0-0
Timestamp: 2025-08-30T17:03:26.927Z
Learning: For maparent: Takes a focused approach to PR scope - acknowledges good suggestions that are out of scope for current PR rather than expanding scope.
✅ Actions performed

Full review triggered.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@apps/obsidian/src/utils/publishNode.ts`:
- Around line 31-57: The timestamp parsing currently always appends "Z" to
idResponse.data.last_modified, producing an invalid Date when the value already
contains a timezone; change the logic around creating lastModifiedDb so you
first inspect idResponse.data.last_modified (from the idResponse returned by
client.from("Content").select(...)), and only append "Z" if the string has no
timezone indicator (e.g., not ending with "Z" and not containing a ±HH:MM
offset); then construct lastModifiedDb = new Date(parsedString) and keep the
existing skip check that uses existingPublish, myGroup, and file.stat.mtime to
decide whether to return early. Ensure you update the block that defines
lastModifiedDb and the subsequent comparison so NaN is no longer possible.

In `@packages/database/supabase/schemas/assets.sql`:
- Around line 89-104: The trigger function after_delete_update_fref has two
issues: change the EXISTS/count check to use count(*) instead of
count(content_id) and compare file hashes (fr.filehash = OLD.filehash) so GC is
decided by hash references rather than filepaths; also update the cleanup DELETE
in the NEW.filehash branch to delete from public.file_gc by the filehash column
(e.g., WHERE filehash = NEW.filehash) instead of matching filepath. Ensure
references to the FileReference table and public.file_gc use the filehash column
consistently.
🧹 Nitpick comments (2)
apps/obsidian/src/utils/publishNode.ts (1)

62-87: Consider filtering text-like MIME types beyond text/*.

mime.lookup() returns application/json for JSON files (and other text-like types) which won’t match text/* and would still be uploaded. If the intent is strictly “non-text assets,” consider using mime.charset() to detect text encodings.

♻️ Possible refinement
-    const mimetype = mime.lookup(attachment.path) || "application/octet-stream";
-    if (mimetype.startsWith("text/")) continue;
+    const mimetype = mime.lookup(attachment.path) || "application/octet-stream";
+    const charset = mime.charset(mimetype);
+    if (mimetype.startsWith("text/") || charset) continue;
packages/database/supabase/schemas/assets.sql (1)

55-65: Consider renaming file_gc.filepath column to filehash.

The file_gc table column is named filepath but stores hash values (see Line 95 where OLD.filehash is inserted). This naming inconsistency could cause confusion during maintenance. Consider renaming to filehash for clarity.

Suggested fix
 CREATE TABLE IF NOT EXISTS public.file_gc (
-    filepath character varying NOT NULL PRIMARY KEY
+    filehash character varying NOT NULL PRIMARY KEY
 );

This would also require updating the DELETE statements in the trigger functions.

@maparent
Copy link
Collaborator Author

@CodeRabbit I think uploading data files such as json or csv is fine.
review last corrections.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 19, 2026

@maparent: Understood! The current implementation supports uploading data files like JSON and CSV alongside images—the addFile function accepts any mimetype and the attachment processing filters based on non-text mime types, so data files are already included in the scope.

I'll review the latest corrections now.


🧠 Learnings used
Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 0
File: :0-0
Timestamp: 2025-08-30T17:03:26.927Z
Learning: For maparent: Takes a focused approach to PR scope - acknowledges good suggestions that are out of scope for current PR rather than expanding scope.

Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 165
File: packages/database/supabase/migrations/20250504202930_content_tables.sql:37-45
Timestamp: 2025-05-20T03:11:07.917Z
Learning: When reviewing SQL migrations in the discourse-graph codebase, understand that historical migration files should not be modified once they're applied. Instead, issues should be fixed in subsequent migrations or schema definition files. This is why maparent indicated "migration, not changing. corrected in schema."

Learnt from: maparent
Repo: DiscourseGraphs/discourse-graph PR: 165
File: packages/database/supabase/migrations/20250504202930_content_tables.sql:37-45
Timestamp: 2025-05-20T03:11:07.917Z
Learning: When reviewing SQL migrations in this codebase, understand that historical migration files should not be modified once they're applied. Instead, issues should be fixed in subsequent migrations or schema definition files. This is why maparent indicated "migration, not changing. corrected in schema."
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@apps/obsidian/src/utils/publishNode.ts`:
- Around line 31-46: The current early-return in publishNode.ts (using
existingPublish, myGroup, file.stat.mtime and lastModifiedDb from the
maybeSingle() query) ignores embedded attachment updates; change the skip logic
to compute the latest modification time across the note and its attachments
(e.g., gather attachment mtimes for the nodeId/spaceId, take the max of
file.stat.mtime and all attachment mtimes) and compare that max to
lastModifiedDb.getTime() before returning; apply the same fix to the analogous
check around lines 61-85 so asset updates won’t be skipped.

In `@packages/database/supabase/schemas/assets.sql`:
- Around line 138-145: The current policy storage_delete_assets_noref on
storage.objects uses a NOT EXISTS subquery against public."FileReference" which
is evaluated under the authenticated user's RLS and can incorrectly return TRUE
for references in other spaces; fix by either (A) restrict the policy to TO
service_role only so only privileged callers can delete assets, or (B) replace
the NOT EXISTS subquery with a call to a new SECURITY DEFINER helper function
(e.g., file_reference_exists(filehash text) RETURNS boolean SECURITY DEFINER)
that performs the existence check bypassing RLS and returns whether a filehash
exists across all spaces, and update the policy's USING clause to call that
function instead of directly querying public."FileReference".
- Around line 89-101: The trigger after_delete_update_fref is vulnerable to a
race where concurrent deletes both see count>0 and skip inserting into file_gc;
fix it by acquiring a per-filehash advisory lock at the start of the trigger
(use pg_advisory_xact_lock with a hash-based key) before running the SELECT
count/INSERT/DELETE logic: convert the relevant OLD.filehash (or NEW.filehash
when checking deletion) to a numeric lock key (e.g., use
hashtext(OLD.filehash)::bigint or combine hashtext parts to a bigint), call
pg_advisory_xact_lock(<key>) inside the function, then perform the count and
conditional INSERT INTO public.file_gc and DELETE FROM public.file_gc as
currently written, and ensure you skip locking when filehash IS NULL to avoid
unnecessary locks.
- Around line 69-88: The SECURITY DEFINER functions public.file_exists(VARCHAR)
and public.file_access(VARCHAR) currently allow PUBLIC EXECUTE which leaks
file-hash existence; revoke EXECUTE from PUBLIC and explicitly grant EXECUTE
only to the minimal service roles that need it (for example your backend service
role or authenticated role). Concretely, add statements such as REVOKE EXECUTE
ON FUNCTION public.file_exists(VARCHAR) FROM PUBLIC; REVOKE EXECUTE ON FUNCTION
public.file_access(VARCHAR) FROM PUBLIC; followed by GRANT EXECUTE ON FUNCTION
public.file_exists(VARCHAR) TO <specific_role(s)>; GRANT EXECUTE ON FUNCTION
public.file_access(VARCHAR) TO <specific_role(s)> (replace <specific_role(s)>
with your actual service/auth roles). Ensure you use the exact function
signatures (VARCHAR) when altering privileges.

Comment on lines +31 to +46
const idResponse = await client
.from("Content")
.select("last_modified")
.eq("source_local_id", nodeId)
.eq("space_id", spaceId)
.eq("variant", "full")
.maybeSingle();
if (idResponse.error || !idResponse.data) {
throw idResponse.error || new Error("no data while fetching node");
}
const lastModifiedDb = new Date(idResponse.data.last_modified + "Z");
if (
existingPublish.includes(myGroup) &&
file.stat.mtime <= lastModifiedDb.getTime()
)
return; // already published
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Skip check ignores attachment updates, leaving stale assets.

The early return only compares the note’s mtime to last_modified. If an embedded file is updated but the note isn’t touched, publishing will return early and never refresh the asset. Consider including attachment mtimes in the skip condition.

🛠️ Suggested adjustment
-  const lastModifiedDb = new Date(idResponse.data.last_modified + "Z");
-  if (
-    existingPublish.includes(myGroup) &&
-    file.stat.mtime <= lastModifiedDb.getTime()
-  )
-    return; // already published
+  const lastModifiedDb = new Date(idResponse.data.last_modified + "Z");
+  const attachments: Array<{ file: TFile; mimetype: string }> = [];
+  let latestAttachmentMtime = 0;
@@
-    existingFiles.push(attachment.path);
-    const content = await plugin.app.vault.readBinary(attachment);
-    await addFile({
-      client,
-      spaceId,
-      sourceLocalId: nodeId,
-      fname: attachment.path,
-      mimetype,
-      created: new Date(attachment.stat.ctime),
-      lastModified: new Date(attachment.stat.mtime),
-      content,
-    });
+    existingFiles.push(attachment.path);
+    latestAttachmentMtime = Math.max(latestAttachmentMtime, attachment.stat.mtime);
+    attachments.push({ file: attachment, mimetype });
   }
+
+  if (
+    existingPublish.includes(myGroup) &&
+    file.stat.mtime <= lastModifiedDb.getTime() &&
+    latestAttachmentMtime <= lastModifiedDb.getTime()
+  )
+    return; // already published (content + attachments)
+
+  for (const { file: attachment, mimetype } of attachments) {
+    const content = await plugin.app.vault.readBinary(attachment);
+    await addFile({
+      client,
+      spaceId,
+      sourceLocalId: nodeId,
+      fname: attachment.path,
+      mimetype,
+      created: new Date(attachment.stat.ctime),
+      lastModified: new Date(attachment.stat.mtime),
+      content,
+    });
+  }

Also applies to: 61-85

🤖 Prompt for AI Agents
In `@apps/obsidian/src/utils/publishNode.ts` around lines 31 - 46, The current
early-return in publishNode.ts (using existingPublish, myGroup, file.stat.mtime
and lastModifiedDb from the maybeSingle() query) ignores embedded attachment
updates; change the skip logic to compute the latest modification time across
the note and its attachments (e.g., gather attachment mtimes for the
nodeId/spaceId, take the max of file.stat.mtime and all attachment mtimes) and
compare that max to lastModifiedDb.getTime() before returning; apply the same
fix to the analogous check around lines 61-85 so asset updates won’t be skipped.

Comment on lines +69 to +88
CREATE OR REPLACE FUNCTION public.file_exists(hashvalue VARCHAR) RETURNS boolean
SET search_path = ''
SECURITY DEFINER
LANGUAGE sql AS $$
SELECT EXISTS (SELECT true FROM public."FileReference" WHERE filehash = hashvalue LIMIT 1);
$$;

CREATE OR REPLACE FUNCTION public.file_access(hashvalue VARCHAR) RETURNS boolean
SET search_path = ''
SECURITY DEFINER
LANGUAGE sql AS $$
SELECT EXISTS (
SELECT true FROM public."FileReference"
WHERE filehash = hashvalue AND (
public.in_space(space_id) OR
public.can_view_specific_content(space_id, source_local_id)
)
LIMIT 1);
$$;

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat packages/database/supabase/schemas/assets.sql

Repository: DiscourseGraphs/discourse-graph

Length of output: 6326


🌐 Web query:

Supabase PostgreSQL default EXECUTE privileges SECURITY DEFINER PUBLIC

💡 Result:

Short answer

  • PostgreSQL (and therefore Supabase) gives PUBLIC EXECUTE on functions by default. Use ALTER DEFAULT PRIVILEGES to change defaults and GRANT/REVOKE to change existing functions. (postgresql.org)

Common commands (run as the role that creates functions, or FOR ROLE ):

  • Revoke future public execute for all functions (global):
    ALTER DEFAULT PRIVILEGES REVOKE EXECUTE ON FUNCTIONS FROM PUBLIC; (postgresql.org)
  • Revoke future public execute in a schema:
    ALTER DEFAULT PRIVILEGES IN SCHEMA public REVOKE EXECUTE ON FUNCTIONS FROM PUBLIC; (postgresql.org)
  • Revoke execute on existing functions in a schema:
    REVOKE EXECUTE ON ALL FUNCTIONS IN SCHEMA public FROM PUBLIC; (supabase.com)
  • Revoke/Grant a single function:
    REVOKE EXECUTE ON FUNCTION public.my_fn(argtypes) FROM PUBLIC;
    GRANT EXECUTE ON FUNCTION public.my_fn(argtypes) TO authenticated; (supabase.com)

Supabase-specific notes

  • Supabase exposes any function with EXECUTE to the REST RPC endpoint; typical roles you may grant to are anon, authenticated, service_role, or supabase_auth_admin (Auth hooks auto-grant supabase_auth_admin). Supabase docs recommend revoking PUBLIC/anon and granting only the roles that need access. (supabase.com)

SECURITY DEFINER caution

  • SECURITY DEFINER functions run with the owner's privileges (Supabase dashboard-created definer functions may run as postgres). They can be dangerous if exposed — set search_path explicitly inside the function and prefer SECURITY INVOKER; if you must use SECURITY DEFINER, revoke public execute and grant only specific roles. Supabase docs recommend avoiding SECURITY DEFINER when possible. (docs-ewup05pxh-supabase.vercel.app)

If you want, I can generate the exact SQL migration tailored to your Supabase roles/schemas.

Citations:


🌐 Web query:

PostgreSQL SECURITY DEFINER function default privileges for PUBLIC role

💡 Result:

Short answer: By default PostgreSQL grants PUBLIC the EXECUTE privilege on newly created functions/procedures (this is independent of SECURITY DEFINER). If you want to prevent PUBLIC from being able to call functions you must revoke that default (or revoke on the specific function after creation) using ALTER DEFAULT PRIVILEGES or a direct REVOKE. [1][2]

How to change it (examples)

  • Revoke the global default for functions created by the current role:
    ALTER DEFAULT PRIVILEGES REVOKE EXECUTE ON FUNCTIONS FROM PUBLIC; [1]
  • Revoke the default for functions created by a specific role:
    ALTER DEFAULT PRIVILEGES FOR ROLE admin REVOKE EXECUTE ON FUNCTIONS FROM PUBLIC; [1]
  • To remove EXECUTE on an already-created function:
    REVOKE EXECUTE ON FUNCTION schema.fn(...) FROM PUBLIC; [2]

References

  • PostgreSQL docs: Privileges (default EXECUTE for functions/procedures to PUBLIC). [1]
  • PostgreSQL docs: ALTER DEFAULT PRIVILEGES (examples showing REVOKE EXECUTE FROM PUBLIC). [2]

Restrict EXECUTE privileges on SECURITY DEFINER functions.

file_exists and file_access currently default to PUBLIC EXECUTE, which leaks file hash existence information to unauthenticated users and creates an information disclosure risk. Revoke PUBLIC and grant only the roles that need access.

Suggested privilege tightening
+REVOKE EXECUTE ON FUNCTION public.file_exists(VARCHAR) FROM PUBLIC;
+GRANT EXECUTE ON FUNCTION public.file_exists(VARCHAR) TO service_role;
+
+REVOKE EXECUTE ON FUNCTION public.file_access(VARCHAR) FROM PUBLIC;
+GRANT EXECUTE ON FUNCTION public.file_access(VARCHAR) TO authenticated, service_role;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
CREATE OR REPLACE FUNCTION public.file_exists(hashvalue VARCHAR) RETURNS boolean
SET search_path = ''
SECURITY DEFINER
LANGUAGE sql AS $$
SELECT EXISTS (SELECT true FROM public."FileReference" WHERE filehash = hashvalue LIMIT 1);
$$;
CREATE OR REPLACE FUNCTION public.file_access(hashvalue VARCHAR) RETURNS boolean
SET search_path = ''
SECURITY DEFINER
LANGUAGE sql AS $$
SELECT EXISTS (
SELECT true FROM public."FileReference"
WHERE filehash = hashvalue AND (
public.in_space(space_id) OR
public.can_view_specific_content(space_id, source_local_id)
)
LIMIT 1);
$$;
CREATE OR REPLACE FUNCTION public.file_exists(hashvalue VARCHAR) RETURNS boolean
SET search_path = ''
SECURITY DEFINER
LANGUAGE sql AS $$
SELECT EXISTS (SELECT true FROM public."FileReference" WHERE filehash = hashvalue LIMIT 1);
$$;
CREATE OR REPLACE FUNCTION public.file_access(hashvalue VARCHAR) RETURNS boolean
SET search_path = ''
SECURITY DEFINER
LANGUAGE sql AS $$
SELECT EXISTS (
SELECT true FROM public."FileReference"
WHERE filehash = hashvalue AND (
public.in_space(space_id) OR
public.can_view_specific_content(space_id, source_local_id)
)
LIMIT 1);
$$;
REVOKE EXECUTE ON FUNCTION public.file_exists(VARCHAR) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION public.file_exists(VARCHAR) TO service_role;
REVOKE EXECUTE ON FUNCTION public.file_access(VARCHAR) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION public.file_access(VARCHAR) TO authenticated, service_role;
🤖 Prompt for AI Agents
In `@packages/database/supabase/schemas/assets.sql` around lines 69 - 88, The
SECURITY DEFINER functions public.file_exists(VARCHAR) and
public.file_access(VARCHAR) currently allow PUBLIC EXECUTE which leaks file-hash
existence; revoke EXECUTE from PUBLIC and explicitly grant EXECUTE only to the
minimal service roles that need it (for example your backend service role or
authenticated role). Concretely, add statements such as REVOKE EXECUTE ON
FUNCTION public.file_exists(VARCHAR) FROM PUBLIC; REVOKE EXECUTE ON FUNCTION
public.file_access(VARCHAR) FROM PUBLIC; followed by GRANT EXECUTE ON FUNCTION
public.file_exists(VARCHAR) TO <specific_role(s)>; GRANT EXECUTE ON FUNCTION
public.file_access(VARCHAR) TO <specific_role(s)> (replace <specific_role(s)>
with your actual service/auth roles). Ensure you use the exact function
signatures (VARCHAR) when altering privileges.

Comment on lines +89 to +101
CREATE OR REPLACE FUNCTION public.after_delete_update_fref() RETURNS TRIGGER
SET search_path = ''
SECURITY DEFINER
LANGUAGE plpgsql AS $$
BEGIN
IF (SELECT count(source_local_id) FROM public."FileReference" AS fr WHERE fr.filehash=OLD.filehash) = 0 THEN
INSERT INTO public.file_gc VALUES (OLD.filehash);
-- TODO: Invocation with pg_net, following the pattern in
-- https://supabase.com/docs/guides/functions/schedule-functions
END IF;
IF NEW.filehash IS NOT NULL THEN
DELETE FROM public.file_gc WHERE filehash = NEW.filehash;
END IF;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

cat -n packages/database/supabase/schemas/assets.sql | head -150 | tail -100

Repository: DiscourseGraphs/discourse-graph

Length of output: 4777


🏁 Script executed:

# Find the complete trigger function and relevant table definitions
rg -A 20 "CREATE OR REPLACE FUNCTION public.after_delete_update_fref" packages/database/supabase/schemas/assets.sql

Repository: DiscourseGraphs/discourse-graph

Length of output: 812


🏁 Script executed:

# Search for file_gc table definition
rg -B 5 -A 10 "CREATE TABLE.*file_gc\|CREATE TABLE.*\"file_gc\"" packages/database/supabase/schemas/

Repository: DiscourseGraphs/discourse-graph

Length of output: 57


🏁 Script executed:

# Search for FileReference table definition
rg -B 5 -A 20 "CREATE TABLE.*FileReference\|CREATE TABLE.*\"FileReference\"" packages/database/supabase/schemas/

Repository: DiscourseGraphs/discourse-graph

Length of output: 57


🏁 Script executed:

# Check for any ON CONFLICT clauses related to file_gc
rg "file_gc" packages/database/supabase/schemas/assets.sql

Repository: DiscourseGraphs/discourse-graph

Length of output: 504


Concurrent deletes can both skip GC insertion due to isolation-level race condition.

In PostgreSQL's default READ COMMITTED isolation, when two FileReference rows with the same filehash are deleted concurrently:

  • Transaction A's trigger counts remaining rows and sees row B still present (B uncommitted) → count > 0 → skips INSERT
  • Transaction B's trigger counts remaining rows and sees row A still present (A uncommitted) → count > 0 → skips INSERT
  • Both transactions commit with no file_gc entry inserted

The PRIMARY KEY on file_gc prevents duplicates but doesn't prevent both triggers from skipping the insert. Serialization per filehash (advisory lock) or statement-level cleanup would eliminate the race.

🧩 One option: per-hash advisory lock
 BEGIN
+    PERFORM pg_advisory_xact_lock(hashtext(OLD.filehash));
     IF (SELECT count(source_local_id) FROM public."FileReference" AS fr WHERE fr.filehash=OLD.filehash) = 0 THEN
         INSERT INTO public.file_gc VALUES (OLD.filehash);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
CREATE OR REPLACE FUNCTION public.after_delete_update_fref() RETURNS TRIGGER
SET search_path = ''
SECURITY DEFINER
LANGUAGE plpgsql AS $$
BEGIN
IF (SELECT count(source_local_id) FROM public."FileReference" AS fr WHERE fr.filehash=OLD.filehash) = 0 THEN
INSERT INTO public.file_gc VALUES (OLD.filehash);
-- TODO: Invocation with pg_net, following the pattern in
-- https://supabase.com/docs/guides/functions/schedule-functions
END IF;
IF NEW.filehash IS NOT NULL THEN
DELETE FROM public.file_gc WHERE filehash = NEW.filehash;
END IF;
CREATE OR REPLACE FUNCTION public.after_delete_update_fref() RETURNS TRIGGER
SET search_path = ''
SECURITY DEFINER
LANGUAGE plpgsql AS $$
BEGIN
PERFORM pg_advisory_xact_lock(hashtext(OLD.filehash));
IF (SELECT count(source_local_id) FROM public."FileReference" AS fr WHERE fr.filehash=OLD.filehash) = 0 THEN
INSERT INTO public.file_gc VALUES (OLD.filehash);
-- TODO: Invocation with pg_net, following the pattern in
-- https://supabase.com/docs/guides/functions/schedule-functions
END IF;
IF NEW.filehash IS NOT NULL THEN
DELETE FROM public.file_gc WHERE filehash = NEW.filehash;
END IF;
🤖 Prompt for AI Agents
In `@packages/database/supabase/schemas/assets.sql` around lines 89 - 101, The
trigger after_delete_update_fref is vulnerable to a race where concurrent
deletes both see count>0 and skip inserting into file_gc; fix it by acquiring a
per-filehash advisory lock at the start of the trigger (use
pg_advisory_xact_lock with a hash-based key) before running the SELECT
count/INSERT/DELETE logic: convert the relevant OLD.filehash (or NEW.filehash
when checking deletion) to a numeric lock key (e.g., use
hashtext(OLD.filehash)::bigint or combine hashtext parts to a bigint), call
pg_advisory_xact_lock(<key>) inside the function, then perform the count and
conditional INSERT INTO public.file_gc and DELETE FROM public.file_gc as
currently written, and ensure you skip locking when filehash IS NULL to avoid
unnecessary locks.

Comment on lines +138 to +145
DROP POLICY IF EXISTS "storage_delete_assets_noref" ON storage.objects;
CREATE POLICY "storage_delete_assets_noref"
ON storage.objects FOR DELETE TO authenticated USING (
bucket_id = 'assets' AND NOT EXISTS (
SELECT true FROM public."FileReference"
WHERE filehash = name LIMIT 1
)
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's find the relevant schema files and understand the structure
fd -e sql | head -20

Repository: DiscourseGraphs/discourse-graph

Length of output: 1584


🏁 Script executed:

# Find the FileReference table definition and related RLS policies
rg -A 10 "CREATE TABLE.*FileReference" packages/database/

Repository: DiscourseGraphs/discourse-graph

Length of output: 2326


🏁 Script executed:

# Search for existing RLS policies on FileReference
rg "CREATE POLICY.*FileReference" packages/database/

Repository: DiscourseGraphs/discourse-graph

Length of output: 1574


🏁 Script executed:

# Check the storage.objects RLS policies
rg -B 2 -A 8 "CREATE POLICY.*storage.*objects" packages/database/

Repository: DiscourseGraphs/discourse-graph

Length of output: 57


🏁 Script executed:

# Look for any SECURITY DEFINER functions related to file/asset operations
rg -B 2 -A 10 "SECURITY DEFINER" packages/database/ | grep -i "asset\|file\|storage"

Repository: DiscourseGraphs/discourse-graph

Length of output: 8890


RLS in the NOT EXISTS subquery allows cross-space asset deletion.

storage_delete_assets_noref checks FileReference under the authenticated user's RLS context. An authenticated user unable to access a space's references will not see them in the subquery, causing NOT EXISTS to return TRUE even if the filehash is referenced in inaccessible spaces. This allows authenticated users to delete assets still in use.

Restrict deletes to service_role (for garbage collection/edge functions) or create a SECURITY DEFINER function to perform the existence check, bypassing RLS to see all references.

🔐 Restrict to service_role
-CREATE POLICY "storage_delete_assets_noref"
-ON storage.objects FOR DELETE TO authenticated USING (
+CREATE POLICY "storage_delete_assets_noref"
+ON storage.objects FOR DELETE TO service_role USING (
     bucket_id = 'assets' AND NOT EXISTS (
         SELECT true FROM public."FileReference"
         WHERE filehash = name LIMIT 1
     )
 );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
DROP POLICY IF EXISTS "storage_delete_assets_noref" ON storage.objects;
CREATE POLICY "storage_delete_assets_noref"
ON storage.objects FOR DELETE TO authenticated USING (
bucket_id = 'assets' AND NOT EXISTS (
SELECT true FROM public."FileReference"
WHERE filehash = name LIMIT 1
)
);
DROP POLICY IF EXISTS "storage_delete_assets_noref" ON storage.objects;
CREATE POLICY "storage_delete_assets_noref"
ON storage.objects FOR DELETE TO service_role USING (
bucket_id = 'assets' AND NOT EXISTS (
SELECT true FROM public."FileReference"
WHERE filehash = name LIMIT 1
)
);
🤖 Prompt for AI Agents
In `@packages/database/supabase/schemas/assets.sql` around lines 138 - 145, The
current policy storage_delete_assets_noref on storage.objects uses a NOT EXISTS
subquery against public."FileReference" which is evaluated under the
authenticated user's RLS and can incorrectly return TRUE for references in other
spaces; fix by either (A) restrict the policy to TO service_role only so only
privileged callers can delete assets, or (B) replace the NOT EXISTS subquery
with a call to a new SECURITY DEFINER helper function (e.g.,
file_reference_exists(filehash text) RETURNS boolean SECURITY DEFINER) that
performs the existence check bypassing RLS and returns whether a filehash exists
across all spaces, and update the policy's USING clause to call that function
instead of directly querying public."FileReference".

@maparent maparent force-pushed the eng-1285-base-user-affordance-to-trigger-node-sharing branch from a45aadf to e2d7c4c Compare January 19, 2026 04:32
@maparent maparent force-pushed the eng-1180-f7-publish-non-text-assets-referenced-by-a-dg-node branch from 4845f84 to 44eb63e Compare January 19, 2026 04:32
@maparent maparent force-pushed the eng-1180-f7-publish-non-text-assets-referenced-by-a-dg-node branch from 44eb63e to f8766bb Compare January 19, 2026 14:43
@maparent maparent force-pushed the eng-1285-base-user-affordance-to-trigger-node-sharing branch from e2d7c4c to 5b024df Compare January 19, 2026 14:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants