set up for deployment
This commit is contained in:
194
backend/internal/store/version.go
Normal file
194
backend/internal/store/version.go
Normal file
@@ -0,0 +1,194 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/M1ngdaXie/realtime-collab/internal/models"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// CreateDocumentVersion creates a new version snapshot
|
||||
func (s *PostgresStore) CreateDocumentVersion(
|
||||
ctx context.Context,
|
||||
documentID, userID uuid.UUID,
|
||||
snapshot []byte,
|
||||
textPreview *string,
|
||||
label *string,
|
||||
isAuto bool,
|
||||
) (*models.DocumentVersion, error) {
|
||||
tx, err := s.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to begin transaction: %w", err)
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
// Get next version number
|
||||
var versionNumber int
|
||||
err = tx.QueryRowContext(ctx,
|
||||
`SELECT get_next_version_number($1)`,
|
||||
documentID,
|
||||
).Scan(&versionNumber)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get version number: %w", err)
|
||||
}
|
||||
|
||||
// Insert version
|
||||
query := `
|
||||
INSERT INTO document_versions
|
||||
(document_id, yjs_snapshot, text_preview, version_number, created_by, version_label, is_auto_generated)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
RETURNING id, document_id, text_preview, version_number, created_by, version_label, is_auto_generated, created_at
|
||||
`
|
||||
|
||||
var version models.DocumentVersion
|
||||
err = tx.QueryRowContext(ctx, query,
|
||||
documentID, snapshot, textPreview, versionNumber, userID, label, isAuto,
|
||||
).Scan(
|
||||
&version.ID, &version.DocumentID, &version.TextPreview, &version.VersionNumber,
|
||||
&version.CreatedBy, &version.VersionLabel, &version.IsAutoGenerated, &version.CreatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create version: %w", err)
|
||||
}
|
||||
|
||||
// Update document metadata
|
||||
_, err = tx.ExecContext(ctx, `
|
||||
UPDATE documents
|
||||
SET version_count = version_count + 1,
|
||||
last_snapshot_at = NOW()
|
||||
WHERE id = $1
|
||||
`, documentID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to update document metadata: %w", err)
|
||||
}
|
||||
|
||||
if err := tx.Commit(); err != nil {
|
||||
return nil, fmt.Errorf("failed to commit transaction: %w", err)
|
||||
}
|
||||
|
||||
return &version, nil
|
||||
}
|
||||
|
||||
// ListDocumentVersions returns paginated version list with author info
|
||||
func (s *PostgresStore) ListDocumentVersions(
|
||||
ctx context.Context,
|
||||
documentID uuid.UUID,
|
||||
limit int,
|
||||
offset int,
|
||||
) ([]models.DocumentVersionWithAuthor, int, error) {
|
||||
// Get total count
|
||||
var total int
|
||||
err := s.db.QueryRowContext(ctx,
|
||||
`SELECT COUNT(*) FROM document_versions WHERE document_id = $1`,
|
||||
documentID,
|
||||
).Scan(&total)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("failed to count versions: %w", err)
|
||||
}
|
||||
|
||||
// Get paginated versions with author (LEFT JOIN to handle deleted users)
|
||||
query := `
|
||||
SELECT
|
||||
v.id, v.document_id, v.text_preview, v.version_number,
|
||||
v.created_by, v.version_label, v.is_auto_generated, v.created_at,
|
||||
u.id, u.email, u.name, u.avatar_url
|
||||
FROM document_versions v
|
||||
LEFT JOIN users u ON v.created_by = u.id
|
||||
WHERE v.document_id = $1
|
||||
ORDER BY v.version_number DESC
|
||||
LIMIT $2 OFFSET $3
|
||||
`
|
||||
|
||||
rows, err := s.db.QueryContext(ctx, query, documentID, limit, offset)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("failed to list versions: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var versions []models.DocumentVersionWithAuthor
|
||||
for rows.Next() {
|
||||
var v models.DocumentVersionWithAuthor
|
||||
var authorID, authorEmail, authorName sql.NullString
|
||||
var authorAvatar *string
|
||||
|
||||
err := rows.Scan(
|
||||
&v.ID, &v.DocumentID, &v.TextPreview, &v.VersionNumber,
|
||||
&v.CreatedBy, &v.VersionLabel, &v.IsAutoGenerated, &v.CreatedAt,
|
||||
&authorID, &authorEmail, &authorName, &authorAvatar,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, 0, fmt.Errorf("failed to scan version: %w", err)
|
||||
}
|
||||
|
||||
// Populate author if exists
|
||||
if authorID.Valid {
|
||||
authorUUID, _ := uuid.Parse(authorID.String)
|
||||
v.Author = &models.User{
|
||||
ID: authorUUID,
|
||||
Email: authorEmail.String,
|
||||
Name: authorName.String,
|
||||
AvatarURL: authorAvatar,
|
||||
}
|
||||
}
|
||||
|
||||
versions = append(versions, v)
|
||||
}
|
||||
|
||||
return versions, total, nil
|
||||
}
|
||||
|
||||
// GetDocumentVersion retrieves a specific version with full snapshot
|
||||
func (s *PostgresStore) GetDocumentVersion(ctx context.Context, versionID uuid.UUID) (*models.DocumentVersion, error) {
|
||||
query := `
|
||||
SELECT id, document_id, yjs_snapshot, text_preview, ve rsion_number,
|
||||
created_by, version_label, is_auto_generated, created_at
|
||||
FROM document_versions
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
var version models.DocumentVersion
|
||||
err := s.db.QueryRowContext(ctx, query, versionID).Scan(
|
||||
&version.ID, &version.DocumentID, &version.YjsSnapshot, &version.TextPreview,
|
||||
&version.VersionNumber, &version.CreatedBy, &version.VersionLabel,
|
||||
&version.IsAutoGenerated, &version.CreatedAt,
|
||||
)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, fmt.Errorf("version not found")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get version: %w", err)
|
||||
}
|
||||
|
||||
return &version, nil
|
||||
}
|
||||
|
||||
// GetLatestDocumentVersion gets the most recent version for auto-snapshot comparison
|
||||
func (s *PostgresStore) GetLatestDocumentVersion(ctx context.Context, documentID uuid.UUID) (*models.DocumentVersion, error) {
|
||||
query := `
|
||||
SELECT id, document_id, yjs_snapshot, text_preview, version_number,
|
||||
created_by, version_label, is_auto_generated, created_at
|
||||
FROM document_versions
|
||||
WHERE document_id = $1
|
||||
ORDER BY version_number DESC
|
||||
LIMIT 1
|
||||
`
|
||||
|
||||
var version models.DocumentVersion
|
||||
err := s.db.QueryRowContext(ctx, query, documentID).Scan(
|
||||
&version.ID, &version.DocumentID, &version.YjsSnapshot, &version.TextPreview,
|
||||
&version.VersionNumber, &version.CreatedBy, &version.VersionLabel,
|
||||
&version.IsAutoGenerated, &version.CreatedAt,
|
||||
)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil // No versions yet
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get latest version: %w", err)
|
||||
}
|
||||
|
||||
return &version, nil
|
||||
}
|
||||
Reference in New Issue
Block a user