package repository import ( "context" "fmt" "github.com/jackc/pgx/v5" "github.com/sergi/go-diff/diffmatchpatch" "github.com/geo-platform/tenant-api/internal/tenant/repository/generated" ) type ArticleVersionContent struct { HTMLContent *string MarkdownContent *string } type storedArticleVersionContent struct { VersionNo int HTMLContent *string MarkdownContent *string } func LoadArticleVersionContent( ctx context.Context, db generated.DBTX, articleID int64, versionID int64, ) (*ArticleVersionContent, error) { rows, err := db.Query(ctx, ` SELECT version_no, html_content, markdown_content FROM article_versions WHERE article_id = $1 AND version_no <= ( SELECT version_no FROM article_versions WHERE id = $2 AND article_id = $1 ) ORDER BY version_no ASC `, articleID, versionID) if err != nil { return nil, err } defer rows.Close() versions, err := collectStoredArticleVersionContents(rows) if err != nil { return nil, err } if len(versions) == 0 { return nil, pgx.ErrNoRows } return reconstructStoredArticleVersionContents(versions) } func loadLatestArticleVersionContent( ctx context.Context, db generated.DBTX, articleID int64, ) (*ArticleVersionContent, error) { rows, err := db.Query(ctx, ` SELECT version_no, html_content, markdown_content FROM article_versions WHERE article_id = $1 ORDER BY version_no ASC `, articleID) if err != nil { return nil, err } defer rows.Close() versions, err := collectStoredArticleVersionContents(rows) if err != nil { return nil, err } if len(versions) == 0 { return &ArticleVersionContent{}, nil } return reconstructStoredArticleVersionContents(versions) } func collectStoredArticleVersionContents(rows pgx.Rows) ([]storedArticleVersionContent, error) { versions := make([]storedArticleVersionContent, 0) for rows.Next() { var version storedArticleVersionContent if err := rows.Scan(&version.VersionNo, &version.HTMLContent, &version.MarkdownContent); err != nil { return nil, err } versions = append(versions, version) } if err := rows.Err(); err != nil { return nil, err } return versions, nil } func buildArticleVersionDiffStorage(previous, current ArticleVersionContent) (*string, *string) { return stringPtr(articleVersionPatchText(textValue(previous.HTMLContent), textValue(current.HTMLContent))), stringPtr(articleVersionPatchText(textValue(previous.MarkdownContent), textValue(current.MarkdownContent))) } func reconstructStoredArticleVersionContents(versions []storedArticleVersionContent) (*ArticleVersionContent, error) { var html string var markdown string for _, version := range versions { nextHTML, err := applyArticleVersionPatch(html, version.HTMLContent) if err != nil { return nil, fmt.Errorf("apply html patch for version %d: %w", version.VersionNo, err) } nextMarkdown, err := applyArticleVersionPatch(markdown, version.MarkdownContent) if err != nil { return nil, fmt.Errorf("apply markdown patch for version %d: %w", version.VersionNo, err) } html = nextHTML markdown = nextMarkdown } result := &ArticleVersionContent{} result.HTMLContent = stringPtr(html) result.MarkdownContent = stringPtr(markdown) return result, nil } func articleVersionPatchText(previous, current string) string { dmp := diffmatchpatch.New() patches := dmp.PatchMake(previous, current) return dmp.PatchToText(patches) } func applyArticleVersionPatch(base string, patchText *string) (string, error) { if patchText == nil || *patchText == "" { return base, nil } dmp := diffmatchpatch.New() patches, err := dmp.PatchFromText(*patchText) if err != nil { return "", err } next, applied := dmp.PatchApply(patches, base) for _, ok := range applied { if !ok { return "", fmt.Errorf("patch apply failed") } } return next, nil } func textValue(value *string) string { if value == nil { return "" } return *value } func stringPtr(value string) *string { copied := value return &copied }