feat: Implement error handling and response structure for API

- Added standardized error response structure in `errors.go` for consistent error handling across the API.
- Implemented specific response functions for various HTTP status codes (400, 401, 403, 404, 500) to enhance error reporting.
- Introduced validation error handling to provide detailed feedback on input validation issues.

test: Add comprehensive tests for share handler functionality

- Created a suite of tests for share handler endpoints, covering scenarios for creating, listing, deleting shares, and managing share links.
- Included tests for permission checks, validation errors, and edge cases such as unauthorized access and invalid document IDs.

chore: Set up test utilities and database for integration tests

- Established a base handler suite for common setup tasks in tests, including database initialization and teardown.
- Implemented test data seeding to facilitate consistent testing across different scenarios.

migration: Add public sharing support in the database schema

- Modified the `documents` table to include `share_token` and `is_public` columns for managing public document sharing.
- Added constraints to ensure data integrity, preventing public documents from lacking a share token.
This commit is contained in:
M1ngdaXie
2026-01-05 15:25:46 -08:00
parent 7f5f32179b
commit 8ae7fd96e8
15 changed files with 1870 additions and 118 deletions

View File

@@ -34,7 +34,7 @@ type Store interface {
CleanupExpiredSessions(ctx context.Context) error
// Share operations
CreateDocumentShare(ctx context.Context, documentID, userID uuid.UUID, permission string, createdBy *uuid.UUID) (*models.DocumentShare, error)
CreateDocumentShare(ctx context.Context, documentID, userID uuid.UUID, permission string, createdBy *uuid.UUID) (*models.DocumentShare, bool, error)
ListDocumentShares(ctx context.Context, documentID uuid.UUID) ([]models.DocumentShareWithUser, error)
DeleteDocumentShare(ctx context.Context, documentID, userID uuid.UUID) error
CanViewDocument(ctx context.Context, documentID, userID uuid.UUID) (bool, error)
@@ -105,7 +105,7 @@ func (s *PostgresStore) CreateDocument(name string, docType models.DocumentType)
doc := &models.Document{}
query := `
SELECT id, name, type, yjs_state, created_at, updated_at
SELECT id, name, type, yjs_state, owner_id, is_public, created_at, updated_at
FROM documents
WHERE id = $1
`
@@ -115,6 +115,8 @@ func (s *PostgresStore) CreateDocument(name string, docType models.DocumentType)
&doc.Name,
&doc.Type,
&doc.YjsState,
&doc.OwnerID,
&doc.Is_Public,
&doc.CreatedAt,
&doc.UpdatedAt,
)
@@ -261,7 +263,7 @@ func (s *PostgresStore) CreateDocumentWithOwner(name string, docType models.Docu
// ListUserDocuments lists documents owned by or shared with a user
func (s *PostgresStore) ListUserDocuments(ctx context.Context, userID uuid.UUID) ([]models.Document, error) {
query := `
SELECT DISTINCT d.id, d.name, d.type, d.owner_id, d.created_at, d.updated_at
SELECT DISTINCT d.id, d.name, d.type, d.owner_id, d.is_public, d.created_at, d.updated_at
FROM documents d
LEFT JOIN document_shares ds ON d.id = ds.document_id
WHERE d.owner_id = $1 OR ds.user_id = $1
@@ -277,7 +279,8 @@ func (s *PostgresStore) ListUserDocuments(ctx context.Context, userID uuid.UUID)
var documents []models.Document
for rows.Next() {
var doc models.Document
err := rows.Scan(&doc.ID, &doc.Name, &doc.Type, &doc.OwnerID, &doc.CreatedAt, &doc.UpdatedAt)
// Note: yjs_state is NOT included in list to avoid performance issues
err := rows.Scan(&doc.ID, &doc.Name, &doc.Type, &doc.OwnerID, &doc.Is_Public, &doc.CreatedAt, &doc.UpdatedAt)
if err != nil {
return nil, fmt.Errorf("failed to scan document: %w", err)
}