feat(compliance): add content compliance detection across tenant, ops, and clients
Implements the Revision 8/9 compliance design: tenant + ops backends, ops-web content-safety pages, admin-web editor/publish gate integration, desktop publish block surfacing, and supporting migrations / shared types / config plumbing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,9 @@ package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/geo-platform/tenant-api/internal/shared/contentstats"
|
||||
"github.com/geo-platform/tenant-api/internal/tenant/repository/generated"
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
@@ -69,9 +71,10 @@ func (r *articleRepository) CreateArticleVersion(ctx context.Context, input Crea
|
||||
html_content,
|
||||
markdown_content,
|
||||
word_count,
|
||||
source_label
|
||||
source_label,
|
||||
plaintext_snapshot
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
RETURNING id
|
||||
`,
|
||||
input.ArticleID,
|
||||
@@ -81,6 +84,7 @@ func (r *articleRepository) CreateArticleVersion(ctx context.Context, input Crea
|
||||
pgText(storedMarkdown),
|
||||
input.WordCount,
|
||||
pgText(&input.SourceLabel),
|
||||
strings.TrimSpace(input.Title+"\n"+ExtractArticlePlaintext(input.MarkdownContent)),
|
||||
).Scan(&versionID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@@ -88,6 +92,60 @@ func (r *articleRepository) CreateArticleVersion(ctx context.Context, input Crea
|
||||
return versionID, nil
|
||||
}
|
||||
|
||||
func ExtractArticlePlaintext(markdown string) string {
|
||||
markdown = strings.TrimSpace(markdown)
|
||||
if markdown == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
var builder strings.Builder
|
||||
builder.Grow(len(markdown))
|
||||
inFence := false
|
||||
lines := strings.Split(markdown, "\n")
|
||||
for _, line := range lines {
|
||||
trimmed := strings.TrimSpace(line)
|
||||
if strings.HasPrefix(trimmed, "```") || strings.HasPrefix(trimmed, "~~~") {
|
||||
inFence = !inFence
|
||||
continue
|
||||
}
|
||||
if inFence {
|
||||
builder.WriteString(trimmed)
|
||||
builder.WriteByte('\n')
|
||||
continue
|
||||
}
|
||||
line = stripMarkdownLine(line)
|
||||
if strings.TrimSpace(line) == "" {
|
||||
continue
|
||||
}
|
||||
builder.WriteString(line)
|
||||
builder.WriteByte('\n')
|
||||
}
|
||||
return strings.TrimSpace(builder.String())
|
||||
}
|
||||
|
||||
func stripMarkdownLine(line string) string {
|
||||
replacer := strings.NewReplacer(
|
||||
"#", " ",
|
||||
"*", " ",
|
||||
"_", " ",
|
||||
"`", " ",
|
||||
">", " ",
|
||||
"[", " ",
|
||||
"]", " ",
|
||||
"(", " ",
|
||||
")", " ",
|
||||
"!", " ",
|
||||
"|", " ",
|
||||
)
|
||||
line = replacer.Replace(line)
|
||||
line = strings.TrimLeft(line, "-+0123456789. \t")
|
||||
return strings.Join(strings.Fields(line), " ")
|
||||
}
|
||||
|
||||
func CountArticlePlaintextWords(markdown string) int {
|
||||
return contentstats.CountWords(ExtractArticlePlaintext(markdown))
|
||||
}
|
||||
|
||||
func (r *articleRepository) UpdateArticleCurrentVersion(ctx context.Context, articleID, tenantID, versionID int64) error {
|
||||
tag, err := r.db.Exec(ctx, `
|
||||
UPDATE articles
|
||||
|
||||
Reference in New Issue
Block a user