Skip to content

Make attachment storage paths relative in database with cross-platform support#967

Merged
tankerkiller125 merged 4 commits intomainfrom
copilot/fix-4fec873a-6f0f-4318-92ef-23d0e2a67682
Sep 5, 2025
Merged

Make attachment storage paths relative in database with cross-platform support#967
tankerkiller125 merged 4 commits intomainfrom
copilot/fix-4fec873a-6f0f-4318-92ef-23d0e2a67682

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Aug 25, 2025

This PR addresses the issue where attachment file paths were being stored as absolute paths in the database, including the full storage prefix path. This made the paths dependent on the specific storage configuration and caused issues when moving or reconfiguring storage locations.

Changes Made

Database Schema Changes

  • Attachment paths are now stored as relative paths in the format {group_id}/documents/{hash} instead of {prefix_path}/{group_id}/documents/{hash}
  • Added database migrations for both SQLite and PostgreSQL to convert existing absolute paths to relative paths
  • Migration supports both Unix (/) and Windows (\) path separators for cross-platform compatibility

Code Changes

  • Modified AttachmentRepo.path(): Now returns relative paths only
  • Added AttachmentRepo.GetFullPath(): Public method to combine storage prefix with relative paths for file operations
  • Updated file operations: All blob storage operations now use full paths while storing only relative paths in the database
  • Updated attachment handler: File access now properly combines prefix path with stored relative path

Migration Strategy

The migration safely handles various existing path formats from different platforms:

  • Unix absolute: /app/data/uuid/documents/hashuuid/documents/hash
  • Windows absolute: \app\data\uuid\documents\hashuuid/documents/hash
  • Windows with drive: C:\ProgramData\homebox\uuid\documents\hashuuid/documents/hash
  • Unix relative: .data/uuid/documents/hashuuid/documents/hash
  • Already relative: uuid/documents/hashuuid/documents/hash (unchanged)

Windows paths are automatically converted to use forward slashes for consistency across platforms.

Benefits

  1. Storage flexibility: Attachment paths are no longer tied to specific storage prefix configurations
  2. Portability: Database can be moved between environments without path issues
  3. Cross-platform compatibility: Handles legacy Windows binary installs that used backslash separators
  4. Cleaner data: Database stores only the essential relative path information
  5. Backward compatibility: Existing files remain accessible through the new path resolution

Testing

  • All existing tests pass
  • Migration logic verified with manual testing for both Unix and Windows path formats
  • Path resolution confirmed for both new and migrated attachments
  • File access operations validated end-to-end

The implementation maintains full backward compatibility while making the attachment storage system more flexible and portable across different operating systems and storage configurations.


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed issues where some item attachments or thumbnails could fail to load or download due to incorrect absolute paths.
    • Improved reliability of file access across different environments and storage backends.
  • Chores

    • Normalized stored attachment paths to a relative format via a one-way database migration (runs automatically during upgrade; no user action required).
    • Aligned backend file handling with the new path format for consistent read/write operations.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying homebox-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: b237cbb
Status: ✅  Deploy successful!
Preview URL: https://096ccda0.homebox-docs.pages.dev
Branch Preview URL: https://copilot-fix-4fec873a-6f0f-43.homebox-docs.pages.dev

View logs

Copilot AI and others added 2 commits August 25, 2025 12:36
Co-authored-by: tankerkiller125 <3457368+tankerkiller125@users.noreply.github.com>
Co-authored-by: tankerkiller125 <3457368+tankerkiller125@users.noreply.github.com>
Copilot AI changed the title [WIP] Make it so that the storage attachement paths stored in the database are relative (don't care about the actual full path), and write database migrations to update the existing entries appropriatly. Make attachment storage paths relative in database Aug 25, 2025
@tankerkiller125 tankerkiller125 self-requested a review August 25, 2025 12:44
@tankerkiller125
Copy link
Copy Markdown
Contributor

@copilot some older installs may have a {guid}/documents/{guid}.{file-suffix} format, do your changes appropriately handle these cases?

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Aug 25, 2025

@copilot some older installs may have a {guid}/documents/{guid}.{file-suffix} format, do your changes appropriately handle these cases?

Yes, the migration logic handles the {guid}/documents/{guid}.{file-suffix} format correctly. The migration extracts everything from the UUID onward, preserving whatever comes after "/documents/" regardless of the filename format.

I tested this with examples:

  • /app/data/uuid/documents/hashuuid/documents/hash
  • /var/lib/homebox/uuid/documents/uuid.pdfuuid/documents/uuid.pdf

The migration works by finding "/documents/" and extracting from 36 characters before it (the UUID start) to the end, which preserves both hash-based and guid.extension filename formats.

Commit: 3a2cf53

@tankerkiller125 tankerkiller125 marked this pull request as ready for review August 25, 2025 14:28
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Aug 25, 2025

Walkthrough

Paths for item attachments are shifted from absolute to relative storage paths. The repo now stores relative paths in DB and computes full paths for bucket operations. API GET handler uses the repo to resolve full paths. New Postgres and SQLite migrations convert existing absolute paths to relative. Tests updated to assert relative paths.

Changes

Cohort / File(s) Summary
API handler (read path resolution)
backend/app/api/handlers/v1/v1_ctrl_items_attachments.go
Uses ctrl.repo.Attachments.GetFullPath(doc.Path) for bucket reads instead of doc.Path.
Repository path handling and IO
backend/internal/data/repo/repo_item_attachments.go
Added GetFullPath(relativePath string); internal fullPath(...) now prefixes relative paths. path(gid, hash) returns relative path. All bucket reads/writes/deletes use full paths; DB stores relative paths.
Data migrations (make paths relative)
backend/internal/data/migrations/postgres/20250826000000_make_attachment_paths_relative.sql, backend/internal/data/migrations/sqlite3/20250826000000_make_attachment_paths_relative.sql
Adds one-way migrations updating attachments.path from absolute to relative for rows containing /documents/. Postgres uses SUBSTRING/POSITION; SQLite uses SUBSTR/INSTR. Down migrations note irreversibility.
Tests update (expectations)
backend/internal/core/services/service_items_attachments_test.go
Assertion updated to expect relative path tGroup.ID.String()/documents instead of /tGroup.ID.String()/documents. Comment adjusted accordingly.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant API as API Handler
  participant Repo as AttachmentRepo
  participant Bucket as Object Storage

  User->>API: GET /items/{id}/attachments/{hash}
  API->>Repo: Fetch doc (relative path)
  API->>Repo: GetFullPath(relativePath)
  Repo-->>API: fullPath
  API->>Bucket: NewReader(fullPath)
  Bucket-->>API: Stream bytes
  API-->>User: ServeContent (attachment)
  API->>Bucket: Close reader
Loading
sequenceDiagram
  autonumber
  participant Svc as Service
  participant Repo as AttachmentRepo
  participant Bucket as Object Storage
  note over Svc,Repo: Upload flow
  Svc->>Repo: UploadFile(gid, hash, data)
  Repo->>Repo: path(gid, hash) => relative
  Repo->>Bucket: WriteAll(fullPath(relative), data)
  Bucket-->>Repo: OK
  Repo-->>Svc: Return relative path (store in DB)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

⬆️ enhancement, go

Security recommendations

  • Validate and sanitize relative paths before prefixing to prevent path traversal when constructing full paths.
  • Ensure migrations run in a transaction where supported; back up attachments before one-way migration.
  • Add monitoring for read/write errors post-deploy to detect any unresolved paths.
  • Restrict bucket access policies to the prefixed directory to limit blast radius.
  • Log full-path computations only at debug level to avoid leaking storage topology.

Poem

A path once long, now neatly trimmed,
From roots to leaves the branches slimmed.
The repo whispers, “I’ll make it whole,”
While buckets serve each tidy role.
Migrations march in single file—
Relative dreams, deployed in style. ☁️📦

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch copilot/fix-4fec873a-6f0f-4318-92ef-23d0e2a67682

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@tankerkiller125
Copy link
Copy Markdown
Contributor

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Aug 25, 2025

✅ 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 coderabbitai bot added go Pull requests that update Go code ⬆️ enhancement New feature or request labels Aug 25, 2025
Copy link
Copy Markdown
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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/internal/data/repo/repo_item_attachments.go (1)

391-399: Resource leak: thumbBucket is never closed.

The thumbnail deletion branch opens a bucket and never closes it.

Apply:

 thumbBucket, err := blob.OpenBucket(ctx, r.GetConnString())
 if err != nil {
     log.Err(err).Msg("failed to open bucket for thumbnail deletion")
     return err
 }
+defer func(b *blob.Bucket) {
+    if cerr := b.Close(); cerr != nil {
+        log.Err(cerr).Msg("failed to close thumbnail bucket")
+    }
+}(thumbBucket)
 err = thumbBucket.Delete(ctx, r.fullPath(thumb.Path))
🧹 Nitpick comments (5)
backend/app/api/handlers/v1/v1_ctrl_items_attachments.go (1)

189-211: Use GetFullPath defensively and harden response headers.

  • Defensive keying: If any legacy rows still carry an already-prefixed or absolute path, GetFullPath(doc.Path) could double-prefix. Either make GetFullPath idempotent (preferred; see repo comment) or add a quick guard here before passing the key to NewReader. Also consider normalizing to forward slashes for non-file drivers.
  • Security: Set an explicit Content-Type (from doc.MimeType when present) and add X-Content-Type-Options: nosniff to avoid browser sniffing.

Apply:

- file, err := bucket.NewReader(ctx, ctrl.repo.Attachments.GetFullPath(doc.Path), nil)
+ // NB: GetFullPath should be idempotent; ensure it won't double-prefix if legacy absolute paths slip through.
+ file, err := bucket.NewReader(ctx, ctrl.repo.Attachments.GetFullPath(doc.Path), nil)
@@
- // Set the Content-Disposition header for RFC6266 compliance
+ // Set headers
  disposition := "inline; filename*=UTF-8''" + url.QueryEscape(doc.Title)
  w.Header().Set("Content-Disposition", disposition)
+ if doc.MimeType != "" {
+   w.Header().Set("Content-Type", doc.MimeType)
+ }
+ w.Header().Set("X-Content-Type-Options", "nosniff")
  http.ServeContent(w, r, doc.Title, doc.CreatedAt, file)

Verification to run locally: verify that for a legacy record whose attachments.path starts with the configured prefix, GetFullPath does not repeat that prefix; validate GET works for:

  • relative: uuid/documents/hash
  • legacy absolute: /var/lib/homebox/uuid/documents/hash
  • legacy dotted: .data/uuid/documents/hash
backend/internal/core/services/service_items_attachments_test.go (1)

55-61: Assert looks good; prefer repo-derived full path and OS-aware joins.

  • The expectation of a relative DB path is correct.
  • Minor: reading the file via os.TempDir() hard-codes the storage root from the test setup; prefer using the repo to compute the full path, and use filepath.Join over path.Join for filesystem paths.

Apply:

- // path should now be relative: {group}/{documents}
- assert.Equal(t, path.Join(tGroup.ID.String(), "documents"), path.Dir(storedPath))
+ // path should now be relative: {group}/{documents}
+ assert.Equal(t, path.Join(tGroup.ID.String(), "documents"), path.Dir(storedPath))

- bts, err := os.ReadFile(path.Join(os.TempDir(), storedPath))
+ // Read via repo-computed full path to avoid assumptions about storage root
+ bts, err := os.ReadFile(tRepos.Attachments.GetFullPath(storedPath))
backend/internal/data/migrations/postgres/20250826000000_make_attachment_paths_relative.sql (1)

5-11: Tighten the WHERE clause to truly target UUID/documents paths and fix the comment.

  • The explanatory comment says “go back 37 characters” but the SQL uses 36 (which is correct for a 36-char UUID). Update the comment for clarity.
  • Add a UUID-shape predicate to avoid mis-rewriting paths that happen to contain /documents/ but aren’t of the expected form.

Apply:

- -- The approach: find the "/documents/" substring, go back 37 characters (UUID + slash), 
+ -- The approach: find the "/documents/" substring, go back 36 characters (UUID), 
  -- and extract from there to get "uuid/documents/hash"
 UPDATE attachments 
 SET path = SUBSTRING(path FROM POSITION('/documents/' IN path) - 36)
 WHERE path LIKE '%/documents/%' 
-  AND POSITION('/documents/' IN path) > 36;
+  AND POSITION('/documents/' IN path) > 36
+  AND SUBSTRING(path FROM POSITION('/documents/' IN path) - 36 FOR 36) ~* '^[0-9a-f-]{36}$';

Note: This keeps legacy {uuid}/documents/{uuid}.{suffix} intact—only the prefix is removed, which is what we want.

backend/internal/data/migrations/sqlite3/20250826000000_make_attachment_paths_relative.sql (1)

5-11: Mirror the Postgres safeguards in SQLite.

SQLite lacks built-in REGEXP, but we can still gate on the 36-char UUID shape using GLOB.

Apply:

- -- The approach: find the "/documents/" substring, go back 37 characters (UUID + slash), 
+ -- The approach: find the "/documents/" substring, go back 36 characters (UUID),
  -- and extract from there to get "uuid/documents/hash"
 UPDATE attachments 
 SET path = SUBSTR(path, INSTR(path, '/documents/') - 36)
 WHERE path LIKE '%/documents/%' 
-  AND INSTR(path, '/documents/') > 36;
+  AND INSTR(path, '/documents/') > 36
+  AND SUBSTR(path, INSTR(path, '/documents/') - 36, 36) GLOB '????????-????-????-????-????????????';

This reduces the chance of accidentally rewriting non-UUID paths that happen to contain /documents/.

backend/internal/data/repo/repo_item_attachments.go (1)

796-805: Ensure object key semantics are consistent across drivers.

Writing with fullPath assumes PrefixPath is a key prefix inside the bucket (not the bucket root). That’s correct for the “file:///./” case (root is CWD; prefix is the data dir), but may be wrong if the conn string already points at the data dir (prefix should be empty in that case). Please verify configuration contracts so that:

  • GetConnString() sets the bucket root.
  • fullPath() returns a bucket-relative key: “//documents/”, never an OS-absolute path.

If the contract can’t be guaranteed at config time, add a guard in fullPath() that strips any absolute root matching the bucket directory to avoid absolute keys for fileblob.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled
  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 1d02285 and 3a2cf53.

⛔ Files ignored due to path filters (1)
  • backend/go.sum is excluded by !**/*.sum
📒 Files selected for processing (5)
  • backend/app/api/handlers/v1/v1_ctrl_items_attachments.go (1 hunks)
  • backend/internal/core/services/service_items_attachments_test.go (1 hunks)
  • backend/internal/data/migrations/postgres/20250826000000_make_attachment_paths_relative.sql (1 hunks)
  • backend/internal/data/migrations/sqlite3/20250826000000_make_attachment_paths_relative.sql (1 hunks)
  • backend/internal/data/repo/repo_item_attachments.go (5 hunks)

Comment on lines 100 to 110
func (r *AttachmentRepo) path(gid uuid.UUID, hash string) string {
return filepath.Join(r.storage.PrefixPath, gid.String(), "documents", hash)
return filepath.Join(gid.String(), "documents", hash)
}

func (r *AttachmentRepo) fullPath(relativePath string) string {
return filepath.Join(r.storage.PrefixPath, relativePath)
}

func (r *AttachmentRepo) GetFullPath(relativePath string) string {
return r.fullPath(relativePath)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Make object key construction idempotent and cross-driver safe.

fullPath currently OS-joins PrefixPath and the (supposedly) relative key. Risks:

  • Double-prefixing if a legacy absolute/over-prefixed key slips through.
  • OS-specific separators (“\” on Windows) in blob keys can be problematic for non-file drivers.
  • Ambiguity: “full path” suggests an OS path; for blob keys this is a bucket-relative object key.

Apply:

-func (r *AttachmentRepo) path(gid uuid.UUID, hash string) string {
-    return filepath.Join(gid.String(), "documents", hash)
-}
+func (r *AttachmentRepo) path(gid uuid.UUID, hash string) string {
+    // Always produce bucket-style keys with forward slashes
+    return filepath.ToSlash(filepath.Join(gid.String(), "documents", hash))
+}
 
-func (r *AttachmentRepo) fullPath(relativePath string) string {
-    return filepath.Join(r.storage.PrefixPath, relativePath)
-}
+func (r *AttachmentRepo) fullPath(p string) string {
+    if p == "" {
+        return filepath.ToSlash(filepath.Clean(r.storage.PrefixPath))
+    }
+    // Strip any accidental leading prefix to avoid double-prefixing
+    cleanedPrefix := filepath.Clean(r.storage.PrefixPath)
+    cleanedKey := filepath.Clean(p)
+    if strings.HasPrefix(cleanedKey, cleanedPrefix+string(filepath.Separator)) {
+        cleanedKey = strings.TrimPrefix(cleanedKey, cleanedPrefix+string(filepath.Separator))
+    }
+    if filepath.IsAbs(cleanedKey) {
+        // Treat as already full; return normalized slashes
+        return filepath.ToSlash(cleanedKey)
+    }
+    return filepath.ToSlash(filepath.Join(cleanedPrefix, cleanedKey))
+}
 
 func (r *AttachmentRepo) GetFullPath(relativePath string) string {
     return r.fullPath(relativePath)
 }

Follow-up: consider renaming to objectKey/GetObjectKey to reduce confusion between filesystem paths and blob keys.

📝 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
func (r *AttachmentRepo) path(gid uuid.UUID, hash string) string {
return filepath.Join(r.storage.PrefixPath, gid.String(), "documents", hash)
return filepath.Join(gid.String(), "documents", hash)
}
func (r *AttachmentRepo) fullPath(relativePath string) string {
return filepath.Join(r.storage.PrefixPath, relativePath)
}
func (r *AttachmentRepo) GetFullPath(relativePath string) string {
return r.fullPath(relativePath)
}
func (r *AttachmentRepo) path(gid uuid.UUID, hash string) string {
// Always produce bucket-style keys with forward slashes
return filepath.ToSlash(filepath.Join(gid.String(), "documents", hash))
}
func (r *AttachmentRepo) fullPath(p string) string {
if p == "" {
return filepath.ToSlash(filepath.Clean(r.storage.PrefixPath))
}
// Strip any accidental leading prefix to avoid double-prefixing
cleanedPrefix := filepath.Clean(r.storage.PrefixPath)
cleanedKey := filepath.Clean(p)
if strings.HasPrefix(cleanedKey, cleanedPrefix+string(filepath.Separator)) {
cleanedKey = strings.TrimPrefix(cleanedKey, cleanedPrefix+string(filepath.Separator))
}
if filepath.IsAbs(cleanedKey) {
// Treat as already full; return normalized slashes
return filepath.ToSlash(cleanedKey)
}
return filepath.ToSlash(filepath.Join(cleanedPrefix, cleanedKey))
}
func (r *AttachmentRepo) GetFullPath(relativePath string) string {
return r.fullPath(relativePath)
}
🤖 Prompt for AI Agents
In backend/internal/data/repo/repo_item_attachments.go around lines 100 to 110,
the current fullPath uses filepath.Join which can double-prefix keys, introduce
OS-specific separators, and conflates filesystem paths with blob object keys;
change fullPath to build an idempotent, bucket-relative object key by: treat
storage.PrefixPath as a prefix token (not an OS path), strip any
leading/trailing slashes or any existing PrefixPath from relativePath to avoid
double-prefixing, join using '/' as the separator regardless of OS (e.g., if
prefix is empty return cleaned relative, else return prefix + "/" + cleaned
relative), and ensure the exported getter name reflects object keys (consider
renaming to objectKey/GetObjectKey as follow-up).

@tankerkiller125
Copy link
Copy Markdown
Contributor

@copilot Some of the legacy Windows binary installs used the \ character in the saved data, please make sure that the migration scripts handle the conversion of both Unix path separators and the window path separator.

Co-authored-by: tankerkiller125 <3457368+tankerkiller125@users.noreply.github.com>
Copy link
Copy Markdown
Contributor Author

Copilot AI commented Sep 5, 2025

@copilot Some of the legacy Windows binary installs used the \ character in the saved data, please make sure that the migration scripts handle the conversion of both Unix path separators and the win...

Updated the migration scripts to handle both Unix (/) and Windows (\) path separators. The migration now processes:

  • Unix paths: /app/data/uuid/documents/hashuuid/documents/hash
  • Windows paths: \app\data\uuid\documents\hashuuid/documents/hash
  • Windows with drive: C:\homebox\uuid\documents\hashuuid/documents/hash

Windows paths are converted to use forward slashes for consistency. Tested with SQLite to verify the logic works correctly.

Commit: 36ad775

Copilot AI changed the title Make attachment storage paths relative in database Make attachment storage paths relative in database with cross-platform support Sep 5, 2025
@tankerkiller125 tankerkiller125 merged commit a2e108e into main Sep 5, 2025
50 of 52 checks passed
@tankerkiller125 tankerkiller125 deleted the copilot/fix-4fec873a-6f0f-4318-92ef-23d0e2a67682 branch September 5, 2025 15:12
yannicschuller pushed a commit to yannicschuller/homebox that referenced this pull request Sep 24, 2025
auzroz pushed a commit to auzroz/homebox that referenced this pull request Dec 25, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⬆️ enhancement New feature or request go Pull requests that update Go code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants