diff --git a/admin/blog/blog.go b/admin/blog/blog.go
index 294ac66..806f34c 100644
--- a/admin/blog/blog.go
+++ b/admin/blog/blog.go
@@ -66,7 +66,7 @@ func serveBlogIndex(app *model.AppState) http.Handler {
if post.CreatedAt.Year() != collectionYear || i == len(posts) - 1 {
if i == len(posts) - 1 {
- collectionPosts = append([]*blogPost{&authoredPost}, collectionPosts...)
+ collectionPosts = append(collectionPosts, &authoredPost)
}
collections = append(collections, &blogPostGroup{
Year: collectionYear,
diff --git a/admin/http.go b/admin/http.go
index 55265cb..4bbc9b6 100644
--- a/admin/http.go
+++ b/admin/http.go
@@ -62,69 +62,39 @@ func adminIndexHandler(app *model.AppState) http.Handler {
releases, err := controller.GetAllReleases(app.DB, false, 3, true)
if err != nil {
- fmt.Fprintf(os.Stderr, "WARN: Failed to pull releases: %v\n", err)
+ fmt.Fprintf(os.Stderr, "WARN: Failed to pull releases: %s\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
releaseCount, err := controller.GetReleaseCount(app.DB, false)
if err != nil {
- fmt.Fprintf(os.Stderr, "WARN: Failed to pull releases count: %v\n", err)
+ fmt.Fprintf(os.Stderr, "WARN: Failed to pull releases count: %s\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
artists, err := controller.GetAllArtists(app.DB)
if err != nil {
- fmt.Fprintf(os.Stderr, "WARN: Failed to pull artists: %v\n", err)
+ fmt.Fprintf(os.Stderr, "WARN: Failed to pull artists: %s\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
artistCount, err := controller.GetArtistCount(app.DB)
if err != nil {
- fmt.Fprintf(os.Stderr, "WARN: Failed to pull artist count: %v\n", err)
+ fmt.Fprintf(os.Stderr, "WARN: Failed to pull artist count: %s\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
tracks, err := controller.GetOrphanTracks(app.DB)
if err != nil {
- fmt.Fprintf(os.Stderr, "WARN: Failed to pull orphan tracks: %v\n", err)
+ fmt.Fprintf(os.Stderr, "WARN: Failed to pull orphan tracks: %s\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
trackCount, err := controller.GetTrackCount(app.DB)
if err != nil {
- fmt.Fprintf(os.Stderr, "WARN: Failed to pull track count: %v\n", err)
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
- return
- }
-
- type BlogPost struct {
- *model.BlogPost
- Author *model.Account
- }
- blogPosts, err := controller.GetBlogPosts(app.DB, false, 1, 0)
- if err != nil {
- fmt.Fprintf(os.Stderr, "WARN: Failed to pull blog posts: %v\n", err)
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
- return
- }
- var latestBlogPost *BlogPost = nil
- if len(blogPosts) > 0 {
- author, err := controller.GetAccountByID(app.DB, blogPosts[0].AuthorID)
- if err != nil {
- fmt.Fprintf(os.Stderr, "WARN: Failed to pull latest blog post author %s: %v\n", blogPosts[0].AuthorID, err)
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
- return
- }
- latestBlogPost = &BlogPost{
- BlogPost: blogPosts[0],
- Author: author,
- }
- }
- blogCount, err := controller.GetBlogPostCount(app.DB, false)
- if err != nil {
- fmt.Fprintf(os.Stderr, "WARN: Failed to pull blog post count: %v\n", err)
+ fmt.Fprintf(os.Stderr, "WARN: Failed to pull track count: %s\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
@@ -137,8 +107,6 @@ func adminIndexHandler(app *model.AppState) http.Handler {
ArtistCount int
Tracks []*model.Track
TrackCount int
- BlogPost *BlogPost
- BlogCount int
}
err = templates.IndexTemplate.Execute(w, IndexData{
@@ -149,11 +117,9 @@ func adminIndexHandler(app *model.AppState) http.Handler {
ArtistCount: artistCount,
Tracks: tracks,
TrackCount: trackCount,
- BlogPost: latestBlogPost,
- BlogCount: blogCount,
})
if err != nil {
- fmt.Fprintf(os.Stderr, "WARN: Failed to render admin index: %v\n", err)
+ fmt.Fprintf(os.Stderr, "WARN: Failed to render admin index: %s\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
diff --git a/admin/static/blog.css b/admin/static/blog.css
index 72d3cef..a7f281b 100644
--- a/admin/static/blog.css
+++ b/admin/static/blog.css
@@ -1,11 +1,5 @@
-.blog-collection {
- display: flex;
- flex-direction: column;
- gap: .5em;
-}
-
.blog-collection h2 {
- margin: 0 0 0 1em;
+ margin: .5em 1em;
font-size: 1em;
text-transform: uppercase;
font-weight: 600;
diff --git a/admin/static/blog.js b/admin/static/blog.js
index 3ce9111..e69de29 100644
--- a/admin/static/blog.js
+++ b/admin/static/blog.js
@@ -1,25 +0,0 @@
-document.addEventListener('readystatechange', () => {
- const newBlogBtn = document.getElementById("create-post");
- if (newBlogBtn) newBlogBtn.addEventListener("click", event => {
- event.preventDefault();
- const id = prompt("Enter an ID for this blog post:");
- if (id == null || id == "") return;
-
- fetch("/api/v1/blog", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({id})
- }).then(res => {
- if (res.ok) location = "/admin/blogs/" + id;
- else {
- res.text().then(err => {
- alert(err);
- console.error(err);
- });
- }
- }).catch(err => {
- alert("Failed to create release. Check the console for details.");
- console.error(err);
- });
- });
-});
diff --git a/admin/templates/html/index.html b/admin/templates/html/index.html
index 90214f1..af08c09 100644
--- a/admin/templates/html/index.html
+++ b/admin/templates/html/index.html
@@ -4,7 +4,6 @@
-
{{end}}
{{define "content"}}
@@ -53,18 +52,6 @@
{{block "track" .}}{{end}}
{{end}}
-
-
-
- {{if .BlogPost}}
- {{block "blogpost" .BlogPost}}{{end}}
- {{else}}
-
There are no blog posts.
- {{end}}
-
@@ -72,5 +59,4 @@
-
{{end}}
diff --git a/admin/templates/templates.go b/admin/templates/templates.go
index 924188b..6244540 100644
--- a/admin/templates/templates.go
+++ b/admin/templates/templates.go
@@ -43,7 +43,6 @@ var IndexTemplate = template.Must(template.Must(BaseTemplate.Clone()).Parse(
componentReleaseHTML,
componentArtistHTML,
componentTrackHTML,
- componentBlogPostHTML,
}, "\n"),
))
diff --git a/api/api.go b/api/api.go
index 68a08a9..5312a55 100644
--- a/api/api.go
+++ b/api/api.go
@@ -1,14 +1,14 @@
package api
import (
- "context"
- "fmt"
- "net/http"
- "os"
- "strings"
+ "context"
+ "fmt"
+ "net/http"
+ "os"
+ "strings"
- "arimelody-web/controller"
- "arimelody-web/model"
+ "arimelody-web/controller"
+ "arimelody-web/model"
)
func Handler(app *model.AppState) http.Handler {
@@ -45,7 +45,7 @@ func Handler(app *model.AppState) http.Handler {
http.NotFound(w, r)
}
}))
- artistIndexHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ mux.Handle("/v1/artist", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
// GET /api/v1/artist
@@ -56,9 +56,7 @@ func Handler(app *model.AppState) http.Handler {
default:
http.NotFound(w, r)
}
- })
- mux.Handle("/v1/artist/", artistIndexHandler)
- mux.Handle("/v1/artist", artistIndexHandler)
+ }))
// RELEASE ENDPOINTS
@@ -89,7 +87,7 @@ func Handler(app *model.AppState) http.Handler {
http.NotFound(w, r)
}
}))
- musicIndexHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ mux.Handle("/v1/music", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
// GET /api/v1/music
@@ -100,9 +98,7 @@ func Handler(app *model.AppState) http.Handler {
default:
http.NotFound(w, r)
}
- })
- mux.Handle("/v1/music/", musicIndexHandler)
- mux.Handle("/v1/music", musicIndexHandler)
+ }))
// TRACK ENDPOINTS
@@ -133,7 +129,7 @@ func Handler(app *model.AppState) http.Handler {
http.NotFound(w, r)
}
}))
- trackIndexHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ mux.Handle("/v1/track", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
// GET /api/v1/track (admin)
@@ -144,17 +140,7 @@ func Handler(app *model.AppState) http.Handler {
default:
http.NotFound(w, r)
}
- })
- mux.Handle("/v1/track/", trackIndexHandler)
- mux.Handle("/v1/track", trackIndexHandler)
-
- // BLOG ENDPOINTS
-
- mux.Handle("GET /v1/blog/{id}", ServeBlog(app))
- mux.Handle("PUT /v1/blog/{id}", requireAccount(UpdateBlog(app)))
- mux.Handle("DELETE /v1/blog/{id}", requireAccount(DeleteBlog(app)))
- mux.Handle("GET /v1/blog", ServeAllBlogs(app))
- mux.Handle("POST /v1/blog", requireAccount(CreateBlog(app)))
+ }))
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
session, err := getSession(app, r)
diff --git a/api/artist.go b/api/artist.go
index 322bc5d..01899a6 100644
--- a/api/artist.go
+++ b/api/artist.go
@@ -99,7 +99,7 @@ func CreateArtist(app *model.AppState) http.Handler {
}
if artist.ID == "" {
- http.Error(w, "Artist ID cannot be blank", http.StatusBadRequest)
+ http.Error(w, "Artist ID cannot be blank\n", http.StatusBadRequest)
return
}
if artist.Name == "" { artist.Name = artist.ID }
@@ -107,7 +107,7 @@ func CreateArtist(app *model.AppState) http.Handler {
err = controller.CreateArtist(app.DB, &artist)
if err != nil {
if strings.Contains(err.Error(), "duplicate key") {
- http.Error(w, fmt.Sprintf("Artist %s already exists", artist.ID), http.StatusBadRequest)
+ http.Error(w, fmt.Sprintf("Artist %s already exists\n", artist.ID), http.StatusBadRequest)
return
}
fmt.Printf("WARN: Failed to create artist %s: %s\n", artist.ID, err)
diff --git a/api/blog.go b/api/blog.go
deleted file mode 100644
index f935b95..0000000
--- a/api/blog.go
+++ /dev/null
@@ -1,217 +0,0 @@
-package api
-
-import (
- "arimelody-web/controller"
- "arimelody-web/log"
- "arimelody-web/model"
- "encoding/json"
- "fmt"
- "net/http"
- "os"
- "strings"
- "time"
-)
-
-func ServeAllBlogs(app *model.AppState) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- session := r.Context().Value("session").(*model.Session)
-
- onlyVisible := true
- if session != nil && session.Account != nil {
- onlyVisible = false
- }
-
- posts, err := controller.GetBlogPosts(app.DB, onlyVisible, -1, 0)
- if err != nil {
- fmt.Fprintf(os.Stderr, "WARN: Failed to fetch blog posts: %v\n", err)
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
- return
- }
-
- type (
- BlogAuthor struct {
- ID string `json:"id"`
- Avatar string `json:"avatar"`
- }
-
- BlogPost struct {
- ID string `json:"id"`
- Title string `json:"title"`
- Description string `json:"description"`
- Author BlogAuthor `json:"author"`
- CreatedAt time.Time `json:"created_at"`
- }
- )
- resPosts := []*BlogPost{}
-
- for _, post := range posts {
- author, err := controller.GetAccountByID(app.DB, post.AuthorID)
- if err != nil {
- fmt.Fprintf(os.Stderr, "WARN: Failed to fetch author for blog post %s: %v\n", post.ID, err)
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
- return
- }
-
- resPosts = append(resPosts, &BlogPost{
- ID: post.ID,
- Title: post.Title,
- Description: post.Description,
- Author: BlogAuthor{
- ID: author.Username,
- Avatar: author.AvatarURL.String,
- },
- CreatedAt: post.CreatedAt,
- })
- }
-
- err = json.NewEncoder(w).Encode(resPosts)
- if err != nil {
- fmt.Fprintf(os.Stderr, "WARN: Failed to serve blog posts: %v\n", err)
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
- return
- }
- })
-}
-
-func ServeBlog(app *model.AppState) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- session := r.Context().Value("session").(*model.Session)
- privileged := session != nil && session.Account != nil
- blogID := r.PathValue("id")
-
- blog, err := controller.GetBlogPost(app.DB, blogID)
- if err != nil {
- if strings.Contains(err.Error(), "no rows") {
- http.NotFound(w, r)
- return
- }
- fmt.Fprintf(os.Stderr, "WARN: Failed to fetch blog post %s: %v\n", blogID, err)
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
- return
- }
-
- if !blog.Visible && !privileged {
- http.NotFound(w, r)
- return
- }
-
- err = json.NewEncoder(w).Encode(blog)
- if err != nil {
- fmt.Fprintf(os.Stderr, "WARN: Failed to serve blog post %s: %v\n", blogID, err)
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
- return
- }
- })
-}
-
-func CreateBlog(app *model.AppState) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- session := r.Context().Value("session").(*model.Session)
-
- var blog model.BlogPost
- err := json.NewDecoder(r.Body).Decode(&blog)
- if err != nil {
- http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
- return
- }
-
- if blog.ID == "" {
- http.Error(w, "Post ID cannot be empty", http.StatusBadRequest)
- return
- }
-
- if blog.Title == "" { blog.Title = blog.ID }
-
- if !blog.CreatedAt.Equal(time.Unix(0, 0)) {
- blog.CreatedAt = time.Date(time.Now().Year(), time.Now().Month(), time.Now().Day(), 0, 0, 0, 0, time.UTC)
- }
-
- blog.AuthorID = session.Account.ID
-
- err = controller.CreateBlogPost(app.DB, &blog)
- if err != nil {
- if strings.Contains(err.Error(), "duplicate key") {
- http.Error(w, fmt.Sprintf("Post %s already exists", blog.ID), http.StatusBadRequest)
- return
- }
- fmt.Printf("WARN: Failed to create blog %s: %s\n", blog.ID, err)
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
- return
- }
-
- app.Log.Info(log.TYPE_BLOG, "Blog post \"%s\" created by \"%s\".", blog.ID, session.Account.Username)
-
- w.Header().Add("Content-Type", "application/json")
- w.WriteHeader(http.StatusCreated)
- err = json.NewEncoder(w).Encode(blog)
- if err != nil {
- fmt.Printf("WARN: Blog post %s created, but failed to send JSON response: %s\n", blog.ID, err)
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
- }
- })
-}
-
-func UpdateBlog(app *model.AppState) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- session := r.Context().Value("session").(*model.Session)
- blogID := r.PathValue("id")
-
- blog, err := controller.GetBlogPost(app.DB, blogID)
- if err != nil {
- if strings.Contains(err.Error(), "no rows") {
- http.NotFound(w, r)
- return
- }
- fmt.Fprintf(os.Stderr, "WARN: Failed to fetch blog post %s: %v\n", blogID, err)
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
- return
- }
-
- err = json.NewDecoder(r.Body).Decode(blog)
- if err != nil {
- fmt.Printf("WARN: Failed to update blog %s: %s\n", blog.ID, err)
- http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
- return
- }
-
- err = controller.UpdateBlogPost(app.DB, blogID, blog)
- if err != nil {
- if strings.Contains(err.Error(), "no rows") {
- http.NotFound(w, r)
- return
- }
- fmt.Printf("WARN: Failed to update release %s: %s\n", blogID, err)
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
- }
-
- app.Log.Info(log.TYPE_BLOG, "Blog post \"%s\" updated by \"%s\".", blog.ID, session.Account.Username)
-
- w.Header().Add("Content-Type", "application/json")
- w.WriteHeader(http.StatusCreated)
- err = json.NewEncoder(w).Encode(blog)
- if err != nil {
- fmt.Printf("WARN: Blog post %s created, but failed to send JSON response: %s\n", blog.ID, err)
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
- }
- })
-}
-
-func DeleteBlog(app *model.AppState) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- session := r.Context().Value("session").(*model.Session)
- blogID := r.PathValue("id")
-
- rowsAffected, err := controller.DeleteBlogPost(app.DB, blogID)
- if err != nil {
- fmt.Printf("WARN: Failed to delete blog post %s: %s\n", blogID, err)
- http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
- return
- }
- if rowsAffected == 0 {
- http.NotFound(w, r)
- return
- }
-
- app.Log.Info(log.TYPE_BLOG, "Blog post \"%s\" deleted by \"%s\".", blogID, session.Account.Username)
- })
-}
diff --git a/api/release.go b/api/release.go
index f2bf479..69b7f12 100644
--- a/api/release.go
+++ b/api/release.go
@@ -200,7 +200,7 @@ func CreateRelease(app *model.AppState) http.Handler {
}
if release.ID == "" {
- http.Error(w, "Release ID cannot be empty", http.StatusBadRequest)
+ http.Error(w, "Release ID cannot be empty\n", http.StatusBadRequest)
return
}
@@ -216,7 +216,7 @@ func CreateRelease(app *model.AppState) http.Handler {
err = controller.CreateRelease(app.DB, &release)
if err != nil {
if strings.Contains(err.Error(), "duplicate key") {
- http.Error(w, fmt.Sprintf("Release %s already exists", release.ID), http.StatusBadRequest)
+ http.Error(w, fmt.Sprintf("Release %s already exists\n", release.ID), http.StatusBadRequest)
return
}
fmt.Printf("WARN: Failed to create release %s: %s\n", release.ID, err)
diff --git a/api/track.go b/api/track.go
index ac5b83b..4e48418 100644
--- a/api/track.go
+++ b/api/track.go
@@ -86,7 +86,7 @@ func CreateTrack(app *model.AppState) http.Handler {
}
if track.Title == "" {
- http.Error(w, "Track title cannot be empty", http.StatusBadRequest)
+ http.Error(w, "Track title cannot be empty\n", http.StatusBadRequest)
return
}
@@ -121,7 +121,7 @@ func UpdateTrack(app *model.AppState, track *model.Track) http.Handler {
}
if track.Title == "" {
- http.Error(w, "Track title cannot be empty", http.StatusBadRequest)
+ http.Error(w, "Track title cannot be empty\n", http.StatusBadRequest)
return
}
diff --git a/controller/blog.go b/controller/blog.go
index f0b5742..7fb201e 100644
--- a/controller/blog.go
+++ b/controller/blog.go
@@ -44,18 +44,6 @@ func GetBlogPosts(db *sqlx.DB, onlyVisible bool, limit int, offset int) ([]*mode
return blogs, nil
}
-func GetBlogPostCount(db *sqlx.DB, onlyVisible bool) (int, error) {
- query := "SELECT count(*) FROM blogpost"
- if onlyVisible {
- query += " WHERE visible=true"
- }
-
- var count int
- err := db.Get(&count, query)
-
- return count, err
-}
-
func CreateBlogPost(db *sqlx.DB, post *model.BlogPost) error {
_, err := db.Exec(
"INSERT INTO blogpost (id,title,description,visible,author,markdown,html,bluesky_actor,bluesky_post) " +
@@ -93,18 +81,3 @@ func UpdateBlogPost(db *sqlx.DB, postID string, post *model.BlogPost) error {
return err
}
-
-func DeleteBlogPost(db *sqlx.DB, postID string) (int64, error) {
- result, err := db.Exec(
- "DELETE FROM blogpost "+
- "WHERE id=$1",
- postID,
- )
- if err != nil {
- return 0, err
- }
-
- rowsAffected, _ := result.RowsAffected()
-
- return rowsAffected, nil
-}
diff --git a/model/blog.go b/model/blog.go
index c1d44a9..4a2d03a 100644
--- a/model/blog.go
+++ b/model/blog.go
@@ -11,17 +11,17 @@ import (
type (
BlogPost struct {
- ID string `json:"id" db:"id"`
- Title string `json:"title" db:"title"`
- Description string `json:"description" db:"description"`
- Visible bool `json:"visible" db:"visible"`
- CreatedAt time.Time `json:"created_at" db:"created_at"`
- ModifiedAt sql.NullTime `json:"modified_at" db:"modified_at"`
- AuthorID string `json:"author" db:"author"`
- Markdown string `json:"markdown" db:"markdown"`
- HTML template.HTML `json:"html" db:"html"`
- BlueskyActorID *string `json:"bluesky_actor" db:"bluesky_actor"`
- BlueskyPostID *string `json:"bluesky_post" db:"bluesky_post"`
+ ID string `db:"id"`
+ Title string `db:"title"`
+ Description string `db:"description"`
+ Visible bool `db:"visible"`
+ CreatedAt time.Time `db:"created_at"`
+ ModifiedAt sql.NullTime `db:"modified_at"`
+ AuthorID string `db:"author"`
+ Markdown string `db:"markdown"`
+ HTML template.HTML `db:"html"`
+ BlueskyActorID *string `db:"bluesky_actor"`
+ BlueskyPostID *string `db:"bluesky_post"`
}
)
@@ -40,12 +40,12 @@ func (b *BlogPost) GetMonth() string {
}
func (b *BlogPost) PrintDate() string {
- return b.CreatedAt.Format("2 January 2006, 15:04")
+ return b.CreatedAt.Format("2 January 2006, 03:04")
}
func (b *BlogPost) PrintModifiedDate() string {
if !b.ModifiedAt.Valid {
return ""
}
- return b.ModifiedAt.Time.Format("2 January 2006, 15:04")
+ return b.ModifiedAt.Time.Format("2 January 2006, 03:04")
}