164 lines
3.9 KiB
Go
164 lines
3.9 KiB
Go
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
|
|
}
|