Compare commits
No commits in common. "7a70be98393acb56da102ebebfffd5ea28724b0c" and "41c2286311c80ad8b48ffed6c5c047a2d756918e" have entirely different histories.
7a70be9839
...
41c2286311
18
README.md
18
README.md
|
@ -23,8 +23,9 @@ Each directory in the `/users/` directory of a repository is considered a user.
|
||||||
-- This will be rendered as the blog post.
|
-- This will be rendered as the blog post.
|
||||||
-- Must be present for the blog post to be valid.
|
-- Must be present for the blog post to be valid.
|
||||||
-- All other folders will be ignored
|
-- All other folders will be ignored
|
||||||
\- webmentions.yml
|
\- status.yml
|
||||||
-- Used to track incoming and outgoing webmentions
|
-- Used to track various process status related to the post,
|
||||||
|
-- such as if a webmention was sent.
|
||||||
\- media/
|
\- media/
|
||||||
-- Contains all media files used in the blog post.
|
-- Contains all media files used in the blog post.
|
||||||
-- All files in this folder will be publicly available
|
-- All files in this folder will be publicly available
|
||||||
|
@ -37,10 +38,6 @@ Each directory in the `/users/` directory of a repository is considered a user.
|
||||||
\- VERSION
|
\- VERSION
|
||||||
-- Contains the version string.
|
-- Contains the version string.
|
||||||
-- Used to determine compatibility in the future
|
-- Used to determine compatibility in the future
|
||||||
\- media/
|
|
||||||
-- All this files will be publicly available. To be used for general files
|
|
||||||
\- avatar.{png, jpg, jpeg, gif}
|
|
||||||
-- The avatar for the user
|
|
||||||
\- config.yml
|
\- config.yml
|
||||||
-- Contains settings global to the user.
|
-- Contains settings global to the user.
|
||||||
-- For example: page title and style options
|
-- For example: page title and style options
|
||||||
|
@ -67,15 +64,10 @@ Actual post
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
#### webmentions.yml
|
#### status.yml
|
||||||
|
|
||||||
```
|
```
|
||||||
incoming:
|
webmentions:
|
||||||
- source: https://example.com/post
|
|
||||||
title: Example Post
|
|
||||||
ApprovalStatus: ["", "approved", "rejected"]
|
|
||||||
retrieved_at: 2021-08-13T17:07:00Z
|
|
||||||
outgoing:
|
|
||||||
- target: https://example.com/post
|
- target: https://example.com/post
|
||||||
supported: true
|
supported: true
|
||||||
scanned_at: 2021-08-13T17:07:00Z
|
scanned_at: 2021-08-13T17:07:00Z
|
||||||
|
|
|
@ -237,26 +237,6 @@ func postMediaHandler(repo *owl.Repository) func(http.ResponseWriter, *http.Requ
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func userMediaHandler(repo *owl.Repository) func(http.ResponseWriter, *http.Request, httprouter.Params) {
|
|
||||||
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
|
|
||||||
filepath := ps.ByName("filepath")
|
|
||||||
|
|
||||||
user, err := getUserFromRepo(repo, ps)
|
|
||||||
if err != nil {
|
|
||||||
println("Error getting user: ", err.Error())
|
|
||||||
notFoundHandler(repo)(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
filepath = path.Join(user.MediaDir(), filepath)
|
|
||||||
if _, err := os.Stat(filepath); err != nil {
|
|
||||||
println("Error getting file: ", err.Error())
|
|
||||||
notFoundHandler(repo)(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
http.ServeFile(w, r, filepath)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func notFoundHandler(repo *owl.Repository) func(http.ResponseWriter, *http.Request) {
|
func notFoundHandler(repo *owl.Repository) func(http.ResponseWriter, *http.Request) {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
path := r.URL.Path
|
path := r.URL.Path
|
||||||
|
|
|
@ -14,11 +14,10 @@ func Router(repo *owl.Repository) http.Handler {
|
||||||
router.ServeFiles("/static/*filepath", http.Dir(repo.StaticDir()))
|
router.ServeFiles("/static/*filepath", http.Dir(repo.StaticDir()))
|
||||||
router.GET("/", repoIndexHandler(repo))
|
router.GET("/", repoIndexHandler(repo))
|
||||||
router.GET("/user/:user/", userIndexHandler(repo))
|
router.GET("/user/:user/", userIndexHandler(repo))
|
||||||
router.GET("/user/:user/media/*filepath", userMediaHandler(repo))
|
router.POST("/user/:user/webmention/", userWebmentionHandler(repo))
|
||||||
router.GET("/user/:user/index.xml", userRSSHandler(repo))
|
router.GET("/user/:user/index.xml", userRSSHandler(repo))
|
||||||
router.GET("/user/:user/posts/:post/", postHandler(repo))
|
router.GET("/user/:user/posts/:post/", postHandler(repo))
|
||||||
router.GET("/user/:user/posts/:post/media/*filepath", postMediaHandler(repo))
|
router.GET("/user/:user/posts/:post/media/*filepath", postMediaHandler(repo))
|
||||||
router.POST("/user/:user/webmention/", userWebmentionHandler(repo))
|
|
||||||
router.NotFound = http.HandlerFunc(notFoundHandler(repo))
|
router.NotFound = http.HandlerFunc(notFoundHandler(repo))
|
||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
|
@ -27,11 +26,10 @@ func SingleUserRouter(repo *owl.Repository) http.Handler {
|
||||||
router := httprouter.New()
|
router := httprouter.New()
|
||||||
router.ServeFiles("/static/*filepath", http.Dir(repo.StaticDir()))
|
router.ServeFiles("/static/*filepath", http.Dir(repo.StaticDir()))
|
||||||
router.GET("/", userIndexHandler(repo))
|
router.GET("/", userIndexHandler(repo))
|
||||||
router.GET("/media/*filepath", userMediaHandler(repo))
|
router.POST("/webmention/", userWebmentionHandler(repo))
|
||||||
router.GET("/index.xml", userRSSHandler(repo))
|
router.GET("/index.xml", userRSSHandler(repo))
|
||||||
router.GET("/posts/:post/", postHandler(repo))
|
router.GET("/posts/:post/", postHandler(repo))
|
||||||
router.GET("/posts/:post/media/*filepath", postMediaHandler(repo))
|
router.GET("/posts/:post/media/*filepath", postMediaHandler(repo))
|
||||||
router.POST("/webmention/", userWebmentionHandler(repo))
|
|
||||||
router.NotFound = http.HandlerFunc(notFoundHandler(repo))
|
router.NotFound = http.HandlerFunc(notFoundHandler(repo))
|
||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,9 +62,9 @@ var webmentionCmd = &cobra.Command{
|
||||||
for _, webmention := range webmentions {
|
for _, webmention := range webmentions {
|
||||||
go func(webmention owl.WebmentionOut) {
|
go func(webmention owl.WebmentionOut) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
sendErr := post.SendWebmention(webmention)
|
err = post.SendWebmention(webmention)
|
||||||
if sendErr != nil {
|
if err != nil {
|
||||||
println("Error sending webmentions: ", sendErr.Error())
|
println("Error sending webmentions: ", err.Error())
|
||||||
} else {
|
} else {
|
||||||
println("Webmention sent to ", webmention.Target)
|
println("Webmention sent to ", webmention.Target)
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,26 +18,6 @@
|
||||||
border-color: #ccc;
|
border-color: #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar {
|
|
||||||
float: left;
|
|
||||||
margin-right: 1rem;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
display: flex;
|
|
||||||
flex-flow: row wrap;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-title {
|
|
||||||
order: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-profile {
|
|
||||||
order: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
hgroup h2 a { color: inherit; }
|
hgroup h2 a { color: inherit; }
|
||||||
</style>
|
</style>
|
||||||
|
@ -45,17 +25,24 @@
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<header>
|
<header>
|
||||||
<div class="container header h-card">
|
<nav class="container">
|
||||||
<hgroup class="header-title">
|
<ul>
|
||||||
<h2><a class="p-name u-url" href="{{ .User.UrlPath }}">{{ .UserConfig.Title }}</a></h2>
|
<li>
|
||||||
<h3 class="p-note">{{ .UserConfig.SubTitle }}</h3>
|
<hgroup>
|
||||||
|
<h2><a href="{{ .User.UrlPath }}">{{ .UserConfig.Title }}</a></h2>
|
||||||
|
<h3>{{ .UserConfig.SubTitle }}</h3>
|
||||||
</hgroup>
|
</hgroup>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
<div class="header-profile">
|
</nav>
|
||||||
{{ if .User.AvatarUrl }}
|
</header>
|
||||||
<img class="u-logo avatar" src="{{ .User.AvatarUrl }}" alt="{{ .UserConfig.Title }}" width="100" height="100" />
|
<main class="container">
|
||||||
{{ end }}
|
{{ .Content }}
|
||||||
<div style="float: right; list-style: none;">
|
</main>
|
||||||
|
<footer class="container">
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
{{ if .UserConfig.TwitterHandle}}
|
{{ if .UserConfig.TwitterHandle}}
|
||||||
<li><a href="https://twitter.com/{{.UserConfig.TwitterHandle}}" rel="me">@{{.UserConfig.TwitterHandle}} on Twitter</a>
|
<li><a href="https://twitter.com/{{.UserConfig.TwitterHandle}}" rel="me">@{{.UserConfig.TwitterHandle}} on Twitter</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -64,15 +51,8 @@
|
||||||
<li><a href="https://github.com/{{.UserConfig.GitHubHandle}}" rel="me">@{{.UserConfig.GitHubHandle}} on GitHub</a>
|
<li><a href="https://github.com/{{.UserConfig.GitHubHandle}}" rel="me">@{{.UserConfig.GitHubHandle}} on GitHub</a>
|
||||||
</li>
|
</li>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</ul>
|
||||||
</div>
|
</nav>
|
||||||
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<main class="container">
|
|
||||||
{{ .Content }}
|
|
||||||
</main>
|
|
||||||
<footer class="container">
|
|
||||||
</footer>
|
</footer>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<small>
|
<small>
|
||||||
Published:
|
Published:
|
||||||
<time class="dt-published" datetime="{{.Meta.Date}}">
|
<time class="dt-published" datetime="{{.Meta.Date}}">
|
||||||
{{.Meta.FormattedDate}}
|
{{.Meta.Date}}
|
||||||
</time>
|
</time>
|
||||||
</small>
|
</small>
|
||||||
</hgroup>
|
</hgroup>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<small>
|
<small>
|
||||||
Published:
|
Published:
|
||||||
<time class="dt-published" datetime="{{.Post.Meta.Date}}">
|
<time class="dt-published" datetime="{{.Post.Meta.Date}}">
|
||||||
{{.Post.Meta.FormattedDate}}
|
{{.Post.Meta.Date}}
|
||||||
</time>
|
</time>
|
||||||
<a class="u-url" href="{{.Post.FullUrl}}">#</a>
|
<a class="u-url" href="{{.Post.FullUrl}}">#</a>
|
||||||
</small>
|
</small>
|
||||||
|
|
61
post.go
61
post.go
|
@ -2,7 +2,6 @@ package owl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
@ -30,64 +29,8 @@ type Post struct {
|
||||||
type PostMeta struct {
|
type PostMeta struct {
|
||||||
Title string `yaml:"title"`
|
Title string `yaml:"title"`
|
||||||
Aliases []string `yaml:"aliases"`
|
Aliases []string `yaml:"aliases"`
|
||||||
Date time.Time `yaml:"date"`
|
|
||||||
Draft bool `yaml:"draft"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pm PostMeta) FormattedDate() string {
|
|
||||||
return pm.Date.Format("02-01-2006 15:04:05")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pm *PostMeta) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|
||||||
type T struct {
|
|
||||||
Title string `yaml:"title"`
|
|
||||||
Aliases []string `yaml:"aliases"`
|
|
||||||
Draft bool `yaml:"draft"`
|
|
||||||
}
|
|
||||||
type S struct {
|
|
||||||
Date string `yaml:"date"`
|
Date string `yaml:"date"`
|
||||||
}
|
Draft bool `yaml:"draft"`
|
||||||
|
|
||||||
var t T
|
|
||||||
var s S
|
|
||||||
if err := unmarshal(&t); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := unmarshal(&s); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
pm.Title = t.Title
|
|
||||||
pm.Aliases = t.Aliases
|
|
||||||
pm.Draft = t.Draft
|
|
||||||
|
|
||||||
possibleFormats := []string{
|
|
||||||
"2006-01-02",
|
|
||||||
time.Layout,
|
|
||||||
time.ANSIC,
|
|
||||||
time.UnixDate,
|
|
||||||
time.RubyDate,
|
|
||||||
time.RFC822,
|
|
||||||
time.RFC822Z,
|
|
||||||
time.RFC850,
|
|
||||||
time.RFC1123,
|
|
||||||
time.RFC1123Z,
|
|
||||||
time.RFC3339,
|
|
||||||
time.RFC3339Nano,
|
|
||||||
time.Stamp,
|
|
||||||
time.StampMilli,
|
|
||||||
time.StampMicro,
|
|
||||||
time.StampNano,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, format := range possibleFormats {
|
|
||||||
if t, err := time.Parse(format, s.Date); err == nil {
|
|
||||||
pm.Date = t
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type PostWebmetions struct {
|
type PostWebmetions struct {
|
||||||
|
@ -365,7 +308,7 @@ func (post *Post) SendWebmention(webmention WebmentionOut) error {
|
||||||
|
|
||||||
// if last scan is less than 7 days ago, don't send webmention
|
// if last scan is less than 7 days ago, don't send webmention
|
||||||
if webmention.ScannedAt.After(time.Now().Add(-7*24*time.Hour)) && !webmention.Supported {
|
if webmention.ScannedAt.After(time.Now().Add(-7*24*time.Hour)) && !webmention.Supported {
|
||||||
return errors.New("did not scan. Last scan was less than 7 days ago")
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
webmention.ScannedAt = time.Now()
|
webmention.ScannedAt = time.Now()
|
||||||
|
|
65
post_test.go
65
post_test.go
|
@ -156,7 +156,7 @@ func TestLoadMeta(t *testing.T) {
|
||||||
t.Errorf("Expected title: %v, got %v", []string{"foo/bar/"}, post.Meta().Aliases)
|
t.Errorf("Expected title: %v, got %v", []string{"foo/bar/"}, post.Meta().Aliases)
|
||||||
}
|
}
|
||||||
|
|
||||||
if post.Meta().Date.Format(time.RFC1123Z) != "Wed, 17 Aug 2022 10:50:02 +0000" {
|
if post.Meta().Date != "Wed, 17 Aug 2022 10:50:02 +0000" {
|
||||||
t.Errorf("Expected title: %s, got %s", "Wed, 17 Aug 2022 10:50:02 +0000", post.Meta().Title)
|
t.Errorf("Expected title: %s, got %s", "Wed, 17 Aug 2022 10:50:02 +0000", post.Meta().Title)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -392,8 +392,8 @@ func TestSendWebmentionOnlyScansOncePerWeek(t *testing.T) {
|
||||||
webmention = webmentions[0]
|
webmention = webmentions[0]
|
||||||
|
|
||||||
err := post.SendWebmention(webmention)
|
err := post.SendWebmention(webmention)
|
||||||
if err == nil {
|
if err != nil {
|
||||||
t.Errorf("Expected error, got nil")
|
t.Errorf("Error sending webmention: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
webmentions = post.OutgoingWebmentions()
|
webmentions = post.OutgoingWebmentions()
|
||||||
|
@ -550,62 +550,3 @@ func TestComplexParallelWebmentions(t *testing.T) {
|
||||||
t.Errorf("Expected 20 webmentions, got %d", len(outs))
|
t.Errorf("Expected 20 webmentions, got %d", len(outs))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func TestComplexParallelSimulatedProcessesWebmentions(t *testing.T) {
|
|
||||||
repoName := testRepoName()
|
|
||||||
repo, _ := owl.CreateRepository(repoName, owl.RepoConfig{})
|
|
||||||
repo.HttpClient = &MockHttpClient{}
|
|
||||||
repo.Parser = &MockParseLinksHtmlParser{
|
|
||||||
Links: []string{
|
|
||||||
"http://example.com/1",
|
|
||||||
"http://example.com/2",
|
|
||||||
"http://example.com/3",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
user, _ := repo.CreateUser("testuser")
|
|
||||||
post, _ := user.CreateNewPost("testpost")
|
|
||||||
|
|
||||||
wg := sync.WaitGroup{}
|
|
||||||
wg.Add(40)
|
|
||||||
|
|
||||||
for i := 0; i < 20; i++ {
|
|
||||||
go func(k int) {
|
|
||||||
defer wg.Done()
|
|
||||||
fRepo, _ := owl.OpenRepository(repoName)
|
|
||||||
fUser, _ := fRepo.GetUser("testuser")
|
|
||||||
fPost, err := fUser.GetPost(post.Id())
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
fPost.AddIncomingWebmention("http://example.com/" + strconv.Itoa(k))
|
|
||||||
}(i)
|
|
||||||
go func(k int) {
|
|
||||||
defer wg.Done()
|
|
||||||
fRepo, _ := owl.OpenRepository(repoName)
|
|
||||||
fUser, _ := fRepo.GetUser("testuser")
|
|
||||||
fPost, err := fUser.GetPost(post.Id())
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
webmention := owl.WebmentionOut{
|
|
||||||
Target: "http://example.com/" + strconv.Itoa(k),
|
|
||||||
}
|
|
||||||
fPost.SendWebmention(webmention)
|
|
||||||
}(i)
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
ins := post.IncomingWebmentions()
|
|
||||||
|
|
||||||
if len(ins) != 20 {
|
|
||||||
t.Errorf("Expected 20 webmentions, got %d", len(ins))
|
|
||||||
}
|
|
||||||
|
|
||||||
outs := post.OutgoingWebmentions()
|
|
||||||
|
|
||||||
if len(outs) != 20 {
|
|
||||||
t.Errorf("Expected 20 webmentions, got %d", len(outs))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -251,13 +251,3 @@ func TestRenderIncludesFullUrl(t *testing.T) {
|
||||||
t.Error("Expected: " + post.FullUrl())
|
t.Error("Expected: " + post.FullUrl())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddAvatarIfExist(t *testing.T) {
|
|
||||||
user := getTestUser()
|
|
||||||
os.WriteFile(path.Join(user.MediaDir(), "avatar.png"), []byte("test"), 0644)
|
|
||||||
|
|
||||||
result, _ := owl.RenderIndexPage(user)
|
|
||||||
if !strings.Contains(result, "avatar.png") {
|
|
||||||
t.Error("Avatar not rendered. Got: " + result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -136,10 +136,9 @@ func (repo *Repository) CreateUser(name string) (User, error) {
|
||||||
// creates repo/name folder if it doesn't exist
|
// creates repo/name folder if it doesn't exist
|
||||||
user_dir := new_user.Dir()
|
user_dir := new_user.Dir()
|
||||||
os.Mkdir(user_dir, 0755)
|
os.Mkdir(user_dir, 0755)
|
||||||
// create folders
|
|
||||||
os.Mkdir(path.Join(user_dir, "meta"), 0755)
|
os.Mkdir(path.Join(user_dir, "meta"), 0755)
|
||||||
|
// create public folder
|
||||||
os.Mkdir(path.Join(user_dir, "public"), 0755)
|
os.Mkdir(path.Join(user_dir, "public"), 0755)
|
||||||
os.Mkdir(path.Join(user_dir, "media"), 0755)
|
|
||||||
|
|
||||||
// create Meta files
|
// create Meta files
|
||||||
os.WriteFile(path.Join(user_dir, "meta", "VERSION"), []byte(VERSION), 0644)
|
os.WriteFile(path.Join(user_dir, "meta", "VERSION"), []byte(VERSION), 0644)
|
||||||
|
|
3
rss.go
3
rss.go
|
@ -3,7 +3,6 @@ package owl
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type RSS struct {
|
type RSS struct {
|
||||||
|
@ -47,7 +46,7 @@ func RenderRSSFeed(user User) (string, error) {
|
||||||
Guid: post.FullUrl(),
|
Guid: post.FullUrl(),
|
||||||
Title: post.Title(),
|
Title: post.Title(),
|
||||||
Link: post.FullUrl(),
|
Link: post.FullUrl(),
|
||||||
PubDate: meta.Date.Format(time.RFC1123Z),
|
PubDate: meta.Date,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -71,7 +71,7 @@ func TestRenderRSSFeedPostData(t *testing.T) {
|
||||||
if !strings.Contains(res, post.FullUrl()) {
|
if !strings.Contains(res, post.FullUrl()) {
|
||||||
t.Error("SubTitle not rendered. Got: " + res)
|
t.Error("SubTitle not rendered. Got: " + res)
|
||||||
}
|
}
|
||||||
if !strings.Contains(res, "Thu, 01 Jan 2015 00:00:00 +0000") {
|
if !strings.Contains(res, "2015-01-01") {
|
||||||
t.Error("Date not rendered. Got: " + res)
|
t.Error("Date not rendered. Got: " + res)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
40
user.go
40
user.go
|
@ -43,11 +43,6 @@ func (user User) WebmentionUrl() string {
|
||||||
return url
|
return url
|
||||||
}
|
}
|
||||||
|
|
||||||
func (user User) MediaUrl() string {
|
|
||||||
url, _ := url.JoinPath(user.UrlPath(), "media")
|
|
||||||
return url
|
|
||||||
}
|
|
||||||
|
|
||||||
func (user User) PostDir() string {
|
func (user User) PostDir() string {
|
||||||
return path.Join(user.Dir(), "public")
|
return path.Join(user.Dir(), "public")
|
||||||
}
|
}
|
||||||
|
@ -56,10 +51,6 @@ func (user User) MetaDir() string {
|
||||||
return path.Join(user.Dir(), "meta")
|
return path.Join(user.Dir(), "meta")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (user User) MediaDir() string {
|
|
||||||
return path.Join(user.Dir(), "media")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (user User) ConfigFile() string {
|
func (user User) ConfigFile() string {
|
||||||
return path.Join(user.MetaDir(), "config.yml")
|
return path.Join(user.MetaDir(), "config.yml")
|
||||||
}
|
}
|
||||||
|
@ -68,16 +59,6 @@ func (user User) Name() string {
|
||||||
return user.name
|
return user.name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (user User) AvatarUrl() string {
|
|
||||||
for _, ext := range []string{".jpg", ".jpeg", ".png", ".gif"} {
|
|
||||||
if fileExists(path.Join(user.MediaDir(), "avatar"+ext)) {
|
|
||||||
url, _ := url.JoinPath(user.MediaUrl(), "avatar"+ext)
|
|
||||||
return url
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (user User) Posts() ([]*Post, error) {
|
func (user User) Posts() ([]*Post, error) {
|
||||||
postFiles := listDir(path.Join(user.Dir(), "public"))
|
postFiles := listDir(path.Join(user.Dir(), "public"))
|
||||||
posts := make([]*Post, 0)
|
posts := make([]*Post, 0)
|
||||||
|
@ -110,7 +91,12 @@ func (user User) Posts() ([]*Post, error) {
|
||||||
postDates := make([]PostWithDate, len(posts))
|
postDates := make([]PostWithDate, len(posts))
|
||||||
for i, post := range posts {
|
for i, post := range posts {
|
||||||
meta := post.Meta()
|
meta := post.Meta()
|
||||||
postDates[i] = PostWithDate{post: post, date: meta.Date}
|
date, err := time.Parse(time.RFC1123Z, meta.Date)
|
||||||
|
if err != nil {
|
||||||
|
// invalid date -> use 1970-01-01
|
||||||
|
date = time.Time{}
|
||||||
|
}
|
||||||
|
postDates[i] = PostWithDate{post: post, date: date}
|
||||||
}
|
}
|
||||||
|
|
||||||
// sort posts by date
|
// sort posts by date
|
||||||
|
@ -157,21 +143,11 @@ func (user User) CreateNewPost(title string) (Post, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
post := Post{user: &user, id: folder_name, title: title}
|
post := Post{user: &user, id: folder_name, title: title}
|
||||||
meta := PostMeta{
|
|
||||||
Title: title,
|
|
||||||
Date: time.Now(),
|
|
||||||
Aliases: []string{},
|
|
||||||
Draft: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
initial_content := ""
|
initial_content := ""
|
||||||
initial_content += "---\n"
|
initial_content += "---\n"
|
||||||
// write meta
|
initial_content += "title: " + title + "\n"
|
||||||
meta_bytes, err := yaml.Marshal(meta)
|
initial_content += "date: " + time.Now().UTC().Format(time.RFC1123Z) + "\n"
|
||||||
if err != nil {
|
|
||||||
return Post{}, err
|
|
||||||
}
|
|
||||||
initial_content += string(meta_bytes)
|
|
||||||
initial_content += "---\n"
|
initial_content += "---\n"
|
||||||
initial_content += "\n"
|
initial_content += "\n"
|
||||||
initial_content += "Write your post here.\n"
|
initial_content += "Write your post here.\n"
|
||||||
|
|
19
user_test.go
19
user_test.go
|
@ -42,8 +42,8 @@ func TestCreateNewPostAddsDateToMetaBlock(t *testing.T) {
|
||||||
posts, _ := user.Posts()
|
posts, _ := user.Posts()
|
||||||
post, _ := user.GetPost(posts[0].Id())
|
post, _ := user.GetPost(posts[0].Id())
|
||||||
meta := post.Meta()
|
meta := post.Meta()
|
||||||
if meta.Date.IsZero() {
|
if meta.Date == "" {
|
||||||
t.Errorf("Found no date. Got: %v", meta.Date)
|
t.Error("Found no date. Got: " + meta.Date)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -302,18 +302,3 @@ func TestPostsSortedByPublishingDateBrokenAtBottom(t *testing.T) {
|
||||||
t.Error("Wrong Id, Got: " + posts[1].Id())
|
t.Error("Wrong Id, Got: " + posts[1].Id())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAvatarEmptyIfNotExist(t *testing.T) {
|
|
||||||
user := getTestUser()
|
|
||||||
if user.AvatarUrl() != "" {
|
|
||||||
t.Error("Avatar should be empty")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAvatarSetIfFileExist(t *testing.T) {
|
|
||||||
user := getTestUser()
|
|
||||||
os.WriteFile(path.Join(user.MediaDir(), "avatar.png"), []byte("test"), 0644)
|
|
||||||
if user.AvatarUrl() == "" {
|
|
||||||
t.Error("Avatar should not be empty")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue