feature: move docs

This commit is contained in:
2026-07-27 11:52:13 -04:00
parent ae0939177c
commit 03c06c0dd9
20 changed files with 607 additions and 35 deletions

View File

@@ -51,6 +51,14 @@ type PersistDocumentInput struct {
Tags []string
}
type MoveDocumentInput struct {
ID string
OldPath string
NewPath string
ExpectedHash string
Title string
}
func NewRepository(db *sql.DB) *Repository {
return &Repository{db: db}
}
@@ -109,6 +117,81 @@ func (r *Repository) GetDocumentByPathIncludingArchived(ctx context.Context, pat
return r.getDocumentByPath(ctx, path, true)
}
func (r *Repository) GetDocumentByPathOrAlias(ctx context.Context, path string) (*DocumentRecord, error) {
record, err := r.GetDocumentByPath(ctx, path)
if err == nil || !errors.Is(err, sql.ErrNoRows) {
return record, err
}
var documentID string
if err := r.db.QueryRowContext(ctx, `
SELECT document_id FROM document_aliases WHERE path = ?
`, path).Scan(&documentID); err != nil {
return nil, err
}
return r.GetDocumentByID(ctx, documentID)
}
func (r *Repository) MoveDocument(ctx context.Context, input MoveDocumentInput) error {
tx, err := r.db.BeginTx(ctx, nil)
if err != nil {
return fmt.Errorf("begin document move: %w", err)
}
defer func() { _ = tx.Rollback() }()
var aliasDocumentID string
err = tx.QueryRowContext(ctx, `SELECT document_id FROM document_aliases WHERE path = ?`, input.NewPath).Scan(&aliasDocumentID)
if err != nil && !errors.Is(err, sql.ErrNoRows) {
return fmt.Errorf("check destination alias: %w", err)
}
if err == nil && aliasDocumentID != input.ID {
return fmt.Errorf("destination path is reserved by another document")
}
if _, err := tx.ExecContext(ctx, `DELETE FROM document_aliases WHERE path = ? AND document_id = ?`, input.NewPath, input.ID); err != nil {
return fmt.Errorf("remove destination alias: %w", err)
}
result, err := tx.ExecContext(ctx, `
UPDATE documents
SET path = ?, title = ?, updated_at = ?
WHERE id = ? AND path = ? AND current_hash = ? AND archived_at IS NULL
`, input.NewPath, input.Title, time.Now().UTC().Format(time.RFC3339), input.ID, input.OldPath, input.ExpectedHash)
if err != nil {
return fmt.Errorf("update document path: %w", err)
}
changed, err := result.RowsAffected()
if err != nil {
return fmt.Errorf("document move rows affected: %w", err)
}
if changed != 1 {
return sql.ErrNoRows
}
if _, err := tx.ExecContext(ctx, `
UPDATE attachments SET document_path = ? WHERE document_path = ?
`, input.NewPath, input.OldPath); err != nil {
return fmt.Errorf("move attachment metadata: %w", err)
}
if _, err := tx.ExecContext(ctx, `
UPDATE permissions SET resource_id = ?
WHERE resource_type = 'document' AND resource_id = ?
`, input.NewPath, input.OldPath); err != nil {
return fmt.Errorf("move document permissions: %w", err)
}
if _, err := tx.ExecContext(ctx, `
INSERT INTO document_aliases (path, document_id, created_at)
VALUES (?, ?, ?)
ON CONFLICT(path) DO UPDATE SET document_id = excluded.document_id, created_at = excluded.created_at
`, input.OldPath, input.ID, time.Now().UTC().Format(time.RFC3339)); err != nil {
return fmt.Errorf("create document alias: %w", err)
}
if err := tx.Commit(); err != nil {
return fmt.Errorf("commit document move: %w", err)
}
return nil
}
func (r *Repository) GetDocumentByID(ctx context.Context, id string) (*DocumentRecord, error) {
var (
record DocumentRecord