This commit is contained in:
@@ -23,6 +23,19 @@ type postLocalePayload struct {
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
// requestLangCode is the normalized locale from ?lang= or Accept-Language (zh, en, ja, …).
|
||||
func requestLangCode(r *http.Request) string {
|
||||
raw := strings.TrimSpace(r.URL.Query().Get("lang"))
|
||||
if raw == "" {
|
||||
raw = r.Header.Get("Accept-Language")
|
||||
}
|
||||
raw = strings.TrimSpace(strings.Split(raw, ",")[0])
|
||||
if raw == "" {
|
||||
return "zh"
|
||||
}
|
||||
return translateNormalizePostLang(raw)
|
||||
}
|
||||
|
||||
func (t postTextI18n) pick(r *http.Request) string {
|
||||
return pickLangField(r, t.TextZh, t.TextEn, t.TextJa, t.TextKo, t.TextVi, t.TextId, t.TextMs)
|
||||
}
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
package handlers
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Valid post_type values (Browse filter chips + admin selector).
|
||||
// Valid post_type values (Browse filter chips; inferred on admin save).
|
||||
var validPostTypes = map[string]bool{
|
||||
"image": true, "video": true, "music": true, "ppt": true, "pdf": true,
|
||||
"link": true, "text": true, "archive": true,
|
||||
}
|
||||
|
||||
var postTextURLPattern = regexp.MustCompile(`https?://`)
|
||||
|
||||
func normalizePostType(raw string) string {
|
||||
s := strings.ToLower(strings.TrimSpace(raw))
|
||||
switch s {
|
||||
@@ -33,3 +38,84 @@ func normalizePostType(raw string) string {
|
||||
}
|
||||
return "text"
|
||||
}
|
||||
|
||||
// inferPostTypeFromContent picks post_type from attachments and text (admin no longer sends postType).
|
||||
func inferPostTypeFromContent(text string, attachments []attachmentInput) string {
|
||||
hasText := strings.TrimSpace(text) != ""
|
||||
if len(attachments) == 0 {
|
||||
if hasText && postTextURLPattern.MatchString(text) {
|
||||
return "link"
|
||||
}
|
||||
return "text"
|
||||
}
|
||||
|
||||
var image, video, music, ppt, pdf, archive bool
|
||||
for _, a := range attachments {
|
||||
img, vid, mus, p, pd, ar := attachmentPostTypeSignals(a)
|
||||
image = image || img
|
||||
video = video || vid
|
||||
music = music || mus
|
||||
ppt = ppt || p
|
||||
pdf = pdf || pd
|
||||
archive = archive || ar
|
||||
}
|
||||
|
||||
// Specific file types first; image beats video when both exist (e.g. multi-photo posts).
|
||||
switch {
|
||||
case ppt:
|
||||
return "ppt"
|
||||
case pdf && !image && !video:
|
||||
return "pdf"
|
||||
case archive && !image && !video && !music && !ppt && !pdf:
|
||||
return "archive"
|
||||
case music && !image && !video:
|
||||
return "music"
|
||||
case image:
|
||||
return "image"
|
||||
case video:
|
||||
return "video"
|
||||
case pdf:
|
||||
return "pdf"
|
||||
case archive:
|
||||
return "archive"
|
||||
case music:
|
||||
return "music"
|
||||
}
|
||||
if hasText && postTextURLPattern.MatchString(text) {
|
||||
return "link"
|
||||
}
|
||||
return "text"
|
||||
}
|
||||
|
||||
func attachmentPostTypeSignals(a attachmentInput) (image, video, music, ppt, pdf, archive bool) {
|
||||
kind := strings.ToLower(strings.TrimSpace(a.Kind))
|
||||
mime := strings.ToLower(strings.TrimSpace(a.Mime))
|
||||
fn := strings.ToLower(strings.TrimSpace(a.Filename))
|
||||
if kind == "" && mime != "" {
|
||||
kind = classifyAttachmentKind(mime, fn)
|
||||
}
|
||||
|
||||
if kind == "video" || strings.HasPrefix(mime, "video/") {
|
||||
video = true
|
||||
}
|
||||
if kind == "image" || strings.HasPrefix(mime, "image/") {
|
||||
image = true
|
||||
}
|
||||
if strings.HasPrefix(mime, "audio/") ||
|
||||
strings.HasSuffix(fn, ".mp3") || strings.HasSuffix(fn, ".wav") ||
|
||||
strings.HasSuffix(fn, ".m4a") || strings.HasSuffix(fn, ".flac") || strings.HasSuffix(fn, ".aac") {
|
||||
music = true
|
||||
}
|
||||
if strings.Contains(mime, "pdf") || strings.HasSuffix(fn, ".pdf") {
|
||||
pdf = true
|
||||
}
|
||||
if strings.Contains(mime, "presentation") ||
|
||||
strings.HasSuffix(fn, ".ppt") || strings.HasSuffix(fn, ".pptx") {
|
||||
ppt = true
|
||||
}
|
||||
if strings.HasSuffix(fn, ".zip") || strings.HasSuffix(fn, ".rar") ||
|
||||
strings.HasSuffix(fn, ".7z") || strings.Contains(mime, "zip") {
|
||||
archive = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -31,7 +31,8 @@ type PostDTO struct {
|
||||
PostType string `json:"postType"`
|
||||
CategoryID int `json:"categoryId,omitempty"`
|
||||
CategorySlug string `json:"categorySlug,omitempty"`
|
||||
Language string `json:"language"`
|
||||
Language string `json:"language"` // UI locale from ?lang= (matches text selection)
|
||||
SourceLanguage string `json:"sourceLanguage,omitempty"` // DB source metadata (admin input language)
|
||||
Text string `json:"text,omitempty"`
|
||||
Localizations map[string]postLocalePayload `json:"localizations"`
|
||||
Attachments []AttachmentDTO `json:"attachments"`
|
||||
@@ -212,9 +213,9 @@ func translateNormalizePostLang(lang string) string {
|
||||
}
|
||||
|
||||
func validateAdminPost(ap *adminPost) error {
|
||||
ap.PostType = normalizePostType(ap.PostType)
|
||||
if ap.PostType == "" || !validPostTypes[ap.PostType] {
|
||||
return fmt.Errorf("postType required (image, video, music, ppt, pdf, link, text, archive)")
|
||||
ap.PostType = inferPostTypeFromContent(ap.Text, ap.Attachments)
|
||||
if !validPostTypes[ap.PostType] {
|
||||
ap.PostType = "text"
|
||||
}
|
||||
if len(ap.Attachments) > maxPostAttachments {
|
||||
return fmt.Errorf("too many attachments (max %d)", maxPostAttachments)
|
||||
@@ -389,11 +390,11 @@ func scanPostRow(r *http.Request, row pgx.Row) (PostDTO, postTextI18n, error) {
|
||||
var id uuid.UUID
|
||||
var catID *int
|
||||
var slug *string
|
||||
var lang, postType string
|
||||
var sourceLang, postType string
|
||||
var pub, updated, created *time.Time
|
||||
err := row.Scan(
|
||||
&id, &texts.TextZh, &texts.TextEn, &texts.TextJa, &texts.TextKo, &texts.TextVi, &texts.TextId, &texts.TextMs,
|
||||
&lang, &postType, &catID, &slug, &dto.IsRecommended, &pub, &updated, &created,
|
||||
&sourceLang, &postType, &catID, &slug, &dto.IsRecommended, &pub, &updated, &created,
|
||||
)
|
||||
if err != nil {
|
||||
return dto, texts, err
|
||||
@@ -406,7 +407,8 @@ func scanPostRow(r *http.Request, row pgx.Row) (PostDTO, postTextI18n, error) {
|
||||
if slug != nil {
|
||||
dto.CategorySlug = *slug
|
||||
}
|
||||
dto.Language = lang
|
||||
dto.SourceLanguage = sourceLang
|
||||
dto.Language = requestLangCode(r)
|
||||
dto.Text = texts.pick(r)
|
||||
dto.Localizations = texts.toLocalizations()
|
||||
if pub != nil {
|
||||
|
||||
Reference in New Issue
Block a user