feature: move docs
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user