Add structured materials, canvas layouts, tags and content permissions
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
package platform
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/url"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
type Paragraph struct {
|
||||
ID string `json:"id"`
|
||||
Text string `json:"text"`
|
||||
Description string `json:"description,omitempty"`
|
||||
}
|
||||
type Section struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Paragraphs []Paragraph `json:"paragraphs"`
|
||||
}
|
||||
type Article struct {
|
||||
Sections []Section `json:"sections"`
|
||||
}
|
||||
type Image struct {
|
||||
URL string `json:"url"`
|
||||
Alt string `json:"alt,omitempty"`
|
||||
Width int `json:"width,omitempty"`
|
||||
Height int `json:"height,omitempty"`
|
||||
}
|
||||
type CardPart struct {
|
||||
Kind string `json:"kind"`
|
||||
Text *string `json:"text,omitempty"`
|
||||
Article *Article `json:"article,omitempty"`
|
||||
Image *Image `json:"image,omitempty"`
|
||||
}
|
||||
type Content struct {
|
||||
SchemaVersion int `json:"schema_version"`
|
||||
Text *string `json:"text,omitempty"`
|
||||
Article *Article `json:"article,omitempty"`
|
||||
Image *Image `json:"image,omitempty"`
|
||||
Parts []CardPart `json:"parts,omitempty"`
|
||||
}
|
||||
|
||||
var blockID = regexp.MustCompile(`^[A-Za-z0-9_-]{1,64}$`)
|
||||
|
||||
func validArticle(article *Article) bool {
|
||||
if article == nil || len(article.Sections) > 200 {
|
||||
return false
|
||||
}
|
||||
seen := map[string]bool{}
|
||||
total := 0
|
||||
for _, section := range article.Sections {
|
||||
if !blockID.MatchString(section.ID) || seen[section.ID] || len(section.Title) > 300 || len(section.Description) > 10000 {
|
||||
return false
|
||||
}
|
||||
seen[section.ID] = true
|
||||
total += len(section.Paragraphs)
|
||||
if total > 2000 {
|
||||
return false
|
||||
}
|
||||
for _, paragraph := range section.Paragraphs {
|
||||
if !blockID.MatchString(paragraph.ID) || seen[paragraph.ID] || len(paragraph.Text) > 100000 || len(paragraph.Description) > 10000 {
|
||||
return false
|
||||
}
|
||||
seen[paragraph.ID] = true
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
func validImage(image *Image) bool {
|
||||
if image == nil || len(image.URL) > 2048 || len(image.Alt) > 2000 || image.Width < 0 || image.Width > 100000 || image.Height < 0 || image.Height > 100000 {
|
||||
return false
|
||||
}
|
||||
u, err := url.Parse(image.URL)
|
||||
return err == nil && u.Scheme == "https" && u.Hostname() != "" && u.User == nil && u.Fragment == ""
|
||||
}
|
||||
func validPart(part CardPart) bool {
|
||||
switch part.Kind {
|
||||
case "text":
|
||||
return part.Text != nil && len(*part.Text) <= 100000 && part.Article == nil && part.Image == nil
|
||||
case "article":
|
||||
return part.Text == nil && part.Image == nil && validArticle(part.Article)
|
||||
case "image":
|
||||
return part.Text == nil && part.Article == nil && validImage(part.Image)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
func normalizeContent(kind string, raw json.RawMessage) (json.RawMessage, bool) {
|
||||
if raw = bytes.TrimSpace(raw); len(raw) == 0 || raw[0] != '{' {
|
||||
return nil, false
|
||||
}
|
||||
var content Content
|
||||
d := json.NewDecoder(bytes.NewReader(raw))
|
||||
d.DisallowUnknownFields()
|
||||
if d.Decode(&content) != nil || d.Decode(new(any)) != io.EOF {
|
||||
return nil, false
|
||||
}
|
||||
if content.SchemaVersion == 0 {
|
||||
content.SchemaVersion = 1
|
||||
}
|
||||
if content.SchemaVersion != 1 {
|
||||
return nil, false
|
||||
}
|
||||
switch kind {
|
||||
case "text", "article", "image":
|
||||
if len(content.Parts) > 0 || !validPart(CardPart{Kind: kind, Text: content.Text, Article: content.Article, Image: content.Image}) {
|
||||
return nil, false
|
||||
}
|
||||
case "board":
|
||||
if content.Text != nil || content.Article != nil || content.Image != nil || len(content.Parts) > 0 {
|
||||
return nil, false
|
||||
}
|
||||
case "card":
|
||||
if content.Text != nil || content.Article != nil || content.Image != nil || len(content.Parts) < 1 || len(content.Parts) > 20 {
|
||||
return nil, false
|
||||
}
|
||||
for _, part := range content.Parts {
|
||||
if !validPart(part) {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
out, err := json.Marshal(content)
|
||||
return out, err == nil
|
||||
}
|
||||
Reference in New Issue
Block a user