hmn/src/templates/mapping.go

348 lines
9.7 KiB
Go
Raw Normal View History

package templates
2021-04-22 23:02:50 +00:00
import (
"html/template"
"net"
2021-06-22 09:50:40 +00:00
"regexp"
2021-06-22 17:08:05 +00:00
"strconv"
"strings"
2021-04-22 23:02:50 +00:00
"git.handmade.network/hmn/hmn/src/hmnurl"
"git.handmade.network/hmn/hmn/src/models"
)
func PostToTemplate(p *models.Post, author *models.User, currentTheme string) Post {
return Post{
ID: p.ID,
2021-07-30 19:59:48 +00:00
// Urls not set here. They vary per thread type. Set 'em yourself!
Preview: p.Preview,
ReadOnly: p.ReadOnly,
2021-04-23 04:07:44 +00:00
Author: UserToTemplate(author, currentTheme),
// No content. A lot of the time we don't have this handy and don't need it. See AddContentVersion.
PostDate: p.PostDate,
}
}
2021-07-04 21:24:48 +00:00
func (p *Post) AddContentVersion(ver models.PostVersion, editor *models.User) {
p.Content = template.HTML(ver.TextParsed)
2021-07-04 21:24:48 +00:00
p.IP = maybeIp(ver.IP)
2021-04-23 04:07:44 +00:00
if editor != nil {
2021-07-04 21:24:48 +00:00
editorTmpl := UserToTemplate(editor, "theme not required here")
p.Editor = &editorTmpl
2021-07-04 21:24:48 +00:00
p.EditDate = ver.Date
p.EditReason = ver.EditReason
}
}
2021-06-06 23:48:43 +00:00
var LifecycleBadgeClasses = map[models.ProjectLifecycle]string{
models.ProjectLifecycleUnapproved: "",
models.ProjectLifecycleApprovalRequired: "",
models.ProjectLifecycleActive: "",
models.ProjectLifecycleHiatus: "notice-hiatus",
models.ProjectLifecycleDead: "notice-dead",
models.ProjectLifecycleLTSRequired: "",
models.ProjectLifecycleLTS: "notice-lts",
}
var LifecycleBadgeStrings = map[models.ProjectLifecycle]string{
models.ProjectLifecycleUnapproved: "",
models.ProjectLifecycleApprovalRequired: "",
models.ProjectLifecycleActive: "",
models.ProjectLifecycleHiatus: "On Hiatus",
models.ProjectLifecycleDead: "Dead",
models.ProjectLifecycleLTSRequired: "",
models.ProjectLifecycleLTS: "Complete",
}
2021-06-22 09:50:40 +00:00
func ProjectUrl(p *models.Project) string {
2021-06-06 23:48:43 +00:00
var url string
if p.Lifecycle == models.ProjectLifecycleUnapproved || p.Lifecycle == models.ProjectLifecycleApprovalRequired {
url = hmnurl.BuildProjectNotApproved(p.Slug)
} else {
url = hmnurl.BuildProjectHomepage(p.Slug)
}
2021-06-22 09:50:40 +00:00
return url
}
func ProjectToTemplate(p *models.Project, theme string) Project {
logo := p.LogoLight
if theme == "dark" {
logo = p.LogoDark
}
url := ProjectUrl(p)
return Project{
2021-06-06 23:48:43 +00:00
Name: p.Name,
Subdomain: p.Subdomain(),
Color1: p.Color1,
Color2: p.Color2,
Url: url,
Blurb: p.Blurb,
ParsedDescription: template.HTML(p.ParsedDescription),
Logo: hmnurl.BuildUserFile(logo),
LifecycleBadgeClass: LifecycleBadgeClasses[p.Lifecycle],
LifecycleString: LifecycleBadgeStrings[p.Lifecycle],
IsHMN: p.IsHMN(),
HasBlog: true, // TODO: Check flag sets or whatever
HasForum: true,
HasLibrary: true,
DateApproved: p.DateApproved,
}
}
func SessionToTemplate(s *models.Session) Session {
return Session{
CSRFToken: s.CSRFToken,
}
}
func ThreadToTemplate(t *models.Thread) Thread {
return Thread{
Title: t.Title,
Locked: t.Locked,
Sticky: t.Sticky,
}
}
2021-06-22 09:50:40 +00:00
func UserAvatarUrl(u *models.User, currentTheme string) string {
if currentTheme == "" {
currentTheme = "light"
}
2021-04-22 23:02:50 +00:00
avatar := ""
if u != nil && u.Avatar != nil && len(*u.Avatar) > 0 {
avatar = hmnurl.BuildUserFile(*u.Avatar)
} else {
avatar = hmnurl.BuildTheme("empty-avatar.svg", currentTheme, true)
2021-04-22 23:02:50 +00:00
}
2021-06-22 09:50:40 +00:00
return avatar
}
2021-04-22 23:02:50 +00:00
2021-06-22 09:50:40 +00:00
func UserDisplayName(u *models.User) string {
2021-04-22 23:02:50 +00:00
name := u.Name
if u.Name == "" {
name = u.Username
}
2021-06-22 09:50:40 +00:00
return name
}
func UserToTemplate(u *models.User, currentTheme string) User {
if u == nil {
return User{
Name: "Deleted user",
AvatarUrl: UserAvatarUrl(u, currentTheme),
}
}
2021-06-22 09:50:40 +00:00
email := ""
if u.ShowEmail {
// TODO(asaf): Always show email to admins
email = u.Email
}
2021-04-22 23:02:50 +00:00
return User{
2021-07-22 02:16:10 +00:00
ID: u.ID,
Username: u.Username,
Email: email,
IsStaff: u.IsStaff,
2021-04-17 00:01:13 +00:00
2021-06-22 09:50:40 +00:00
Name: UserDisplayName(u),
2021-04-22 23:02:50 +00:00
Blurb: u.Blurb,
Signature: u.Signature,
2021-06-22 09:50:40 +00:00
DateJoined: u.DateJoined,
AvatarUrl: UserAvatarUrl(u, currentTheme),
ProfileUrl: hmnurl.BuildUserProfile(u.Username),
2021-04-17 00:01:13 +00:00
2021-08-08 20:05:52 +00:00
DarkTheme: u.DarkTheme,
Timezone: u.Timezone,
2021-04-17 00:01:13 +00:00
CanEditLibrary: u.CanEditLibrary,
DiscordSaveShowcase: u.DiscordSaveShowcase,
DiscordDeleteSnippetOnMessageDelete: u.DiscordDeleteSnippetOnMessageDelete,
}
}
2021-06-22 09:50:40 +00:00
var RegexServiceYoutube = regexp.MustCompile(`youtube\.com/(c/)?(?P<userdata>[\w/-]+)$`)
var RegexServiceTwitter = regexp.MustCompile(`twitter\.com/(?P<userdata>\w+)$`)
var RegexServiceGithub = regexp.MustCompile(`github\.com/(?P<userdata>[\w/-]+)$`)
var RegexServiceTwitch = regexp.MustCompile(`twitch\.tv/(?P<userdata>[\w/-]+)$`)
var RegexServiceHitbox = regexp.MustCompile(`hitbox\.tv/(?P<userdata>[\w/-]+)$`)
var RegexServicePatreon = regexp.MustCompile(`patreon\.com/(?P<userdata>[\w/-]+)$`)
var RegexServiceSoundcloud = regexp.MustCompile(`soundcloud\.com/(?P<userdata>[\w/-]+)$`)
var RegexServiceItch = regexp.MustCompile(`(?P<userdata>[\w/-]+)\.itch\.io/?$`)
var LinkServiceMap = map[string]*regexp.Regexp{
"youtube": RegexServiceYoutube,
"twitter": RegexServiceTwitter,
"github": RegexServiceGithub,
"twitch": RegexServiceTwitch,
"hitbox": RegexServiceHitbox,
"patreon": RegexServicePatreon,
"soundcloud": RegexServiceSoundcloud,
"itch": RegexServiceItch,
}
func ParseKnownServicesForLink(link *models.Link) (serviceName string, userData string) {
for name, re := range LinkServiceMap {
match := re.FindStringSubmatch(link.Value)
if match != nil {
serviceName = name
userData = match[re.SubexpIndex("userdata")]
return
}
}
return "", ""
}
func LinkToTemplate(link *models.Link) Link {
name := ""
2021-07-08 07:40:30 +00:00
/*
// NOTE(asaf): While Name and Key are separate things, Name is almost always the same as Key in the db, which looks weird.
// So we're just going to ignore Name until we decide it's worth reusing.
if link.Name != nil {
name = *link.Name
}
*/
2021-06-22 09:50:40 +00:00
serviceName, serviceUserData := ParseKnownServicesForLink(link)
2021-07-08 07:40:30 +00:00
if serviceUserData != "" {
name = serviceUserData
}
if name == "" {
name = link.Value
}
2021-06-22 09:50:40 +00:00
return Link{
2021-07-08 07:40:30 +00:00
Key: link.Key,
Name: name,
Icon: serviceName,
Url: link.Value,
2021-06-22 09:50:40 +00:00
}
}
2021-06-22 17:08:05 +00:00
func TimelineItemsToJSON(items []TimelineItem) string {
// NOTE(asaf): As of 2021-06-22: This only serializes the data necessary for snippet showcase.
builder := strings.Builder{}
builder.WriteRune('[')
for i, item := range items {
if i > 0 {
builder.WriteRune(',')
}
builder.WriteRune('{')
builder.WriteString(`"type":`)
builder.WriteString(strconv.Itoa(int(item.Type)))
builder.WriteRune(',')
builder.WriteString(`"date":`)
builder.WriteString(strconv.FormatInt(item.Date.UTC().Unix(), 10))
builder.WriteRune(',')
builder.WriteString(`"description":"`)
jsonString := string(item.Description)
jsonString = strings.ReplaceAll(jsonString, `\`, `\\`)
jsonString = strings.ReplaceAll(jsonString, `"`, `\"`)
jsonString = strings.ReplaceAll(jsonString, "\n", "\\n")
jsonString = strings.ReplaceAll(jsonString, "\r", "\\r")
jsonString = strings.ReplaceAll(jsonString, "\t", "\\t")
builder.WriteString(jsonString)
builder.WriteString(`",`)
builder.WriteString(`"owner_name":"`)
builder.WriteString(item.OwnerName) // TODO: Do we need to do escaping on these other string fields too? Feels like someone could use this for XSS.
2021-06-22 17:08:05 +00:00
builder.WriteString(`",`)
builder.WriteString(`"owner_avatar":"`)
builder.WriteString(item.OwnerAvatarUrl)
builder.WriteString(`",`)
builder.WriteString(`"owner_url":"`)
builder.WriteString(item.OwnerUrl)
builder.WriteString(`",`)
builder.WriteString(`"snippet_url":"`)
builder.WriteString(item.Url)
builder.WriteString(`",`)
builder.WriteString(`"width":`)
builder.WriteString(strconv.Itoa(item.Width))
builder.WriteRune(',')
builder.WriteString(`"height":`)
builder.WriteString(strconv.Itoa(item.Height))
builder.WriteRune(',')
builder.WriteString(`"asset_url":"`)
builder.WriteString(item.AssetUrl)
builder.WriteString(`",`)
builder.WriteString(`"discord_message_url":"`)
builder.WriteString(item.DiscordMessageUrl)
builder.WriteString(`"`)
builder.WriteRune('}')
}
builder.WriteRune(']')
return builder.String()
}
2021-07-23 03:09:46 +00:00
func PodcastToTemplate(projectSlug string, podcast *models.Podcast, imageFilename string) Podcast {
imageUrl := ""
if imageFilename != "" {
imageUrl = hmnurl.BuildUserFile(imageFilename)
}
return Podcast{
Title: podcast.Title,
Description: podcast.Description,
Language: podcast.Language,
ImageUrl: imageUrl,
Url: hmnurl.BuildPodcast(projectSlug),
RSSUrl: hmnurl.BuildPodcastRSS(projectSlug),
// TODO(asaf): Move this to the db if we want to support user podcasts
AppleUrl: "https://podcasts.apple.com/us/podcast/the-handmade-network-podcast/id1507790631",
GoogleUrl: "https://www.google.com/podcasts?feed=aHR0cHM6Ly9oYW5kbWFkZS5uZXR3b3JrL3BvZGNhc3QvcG9kY2FzdC54bWw%3D",
SpotifyUrl: "https://open.spotify.com/show/2Nd9NjXscrBbQwYULiYKiU",
}
}
func PodcastEpisodeToTemplate(projectSlug string, episode *models.PodcastEpisode, audioFileSize int64, imageFilename string) PodcastEpisode {
imageUrl := ""
if imageFilename != "" {
imageUrl = hmnurl.BuildUserFile(imageFilename)
}
return PodcastEpisode{
GUID: episode.GUID.String(),
Title: episode.Title,
Description: episode.Description,
DescriptionHtml: template.HTML(episode.DescriptionHtml),
EpisodeNumber: episode.EpisodeNumber,
Url: hmnurl.BuildPodcastEpisode(projectSlug, episode.GUID.String()),
ImageUrl: imageUrl,
FileUrl: hmnurl.BuildPodcastEpisodeFile(projectSlug, episode.AudioFile),
FileSize: audioFileSize,
PublicationDate: episode.PublicationDate,
Duration: episode.Duration,
}
}
func maybeString(s *string) string {
if s == nil {
return ""
}
return *s
}
func maybeIp(ip *net.IPNet) string {
if ip == nil {
return ""
}
return ip.String()
}