diff --git a/admin/blog/blog.go b/admin/blog/blog.go
index 806f34c..294ac66 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(collectionPosts, &authoredPost)
+ collectionPosts = append([]*blogPost{&authoredPost}, collectionPosts...)
}
collections = append(collections, &blogPostGroup{
Year: collectionYear,
diff --git a/admin/http.go b/admin/http.go
index 4bbc9b6..55265cb 100644
--- a/admin/http.go
+++ b/admin/http.go
@@ -62,39 +62,69 @@ 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: %s\n", err)
+ fmt.Fprintf(os.Stderr, "WARN: Failed to pull releases: %v\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: %s\n", err)
+ fmt.Fprintf(os.Stderr, "WARN: Failed to pull releases count: %v\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: %s\n", err)
+ fmt.Fprintf(os.Stderr, "WARN: Failed to pull artists: %v\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: %s\n", err)
+ fmt.Fprintf(os.Stderr, "WARN: Failed to pull artist count: %v\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: %s\n", err)
+ fmt.Fprintf(os.Stderr, "WARN: Failed to pull orphan tracks: %v\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: %s\n", err)
+ 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)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return
}
@@ -107,6 +137,8 @@ func adminIndexHandler(app *model.AppState) http.Handler {
ArtistCount int
Tracks []*model.Track
TrackCount int
+ BlogPost *BlogPost
+ BlogCount int
}
err = templates.IndexTemplate.Execute(w, IndexData{
@@ -117,9 +149,11 @@ 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: %s\n", err)
+ fmt.Fprintf(os.Stderr, "WARN: Failed to render admin index: %v\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 a7f281b..72d3cef 100644
--- a/admin/static/blog.css
+++ b/admin/static/blog.css
@@ -1,5 +1,11 @@
+.blog-collection {
+ display: flex;
+ flex-direction: column;
+ gap: .5em;
+}
+
.blog-collection h2 {
- margin: .5em 1em;
+ margin: 0 0 0 1em;
font-size: 1em;
text-transform: uppercase;
font-weight: 600;
diff --git a/admin/static/blog.js b/admin/static/blog.js
index e69de29..3ce9111 100644
--- a/admin/static/blog.js
+++ b/admin/static/blog.js
@@ -0,0 +1,25 @@
+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 af08c09..90214f1 100644
--- a/admin/templates/html/index.html
+++ b/admin/templates/html/index.html
@@ -4,6 +4,7 @@
+
{{end}}
{{define "content"}}
@@ -52,6 +53,18 @@
{{block "track" .}}{{end}}
{{end}}
+
+
+
+ {{if .BlogPost}}
+ {{block "blogpost" .BlogPost}}{{end}}
+ {{else}}
+
There are no blog posts.
+ {{end}}
+
@@ -59,4 +72,5 @@
+
{{end}}
diff --git a/admin/templates/templates.go b/admin/templates/templates.go
index 6244540..924188b 100644
--- a/admin/templates/templates.go
+++ b/admin/templates/templates.go
@@ -43,6 +43,7 @@ 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 5312a55..68a08a9 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)
}
}))
- mux.Handle("/v1/artist", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ artistIndexHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
// GET /api/v1/artist
@@ -56,7 +56,9 @@ func Handler(app *model.AppState) http.Handler {
default:
http.NotFound(w, r)
}
- }))
+ })
+ mux.Handle("/v1/artist/", artistIndexHandler)
+ mux.Handle("/v1/artist", artistIndexHandler)
// RELEASE ENDPOINTS
@@ -87,7 +89,7 @@ func Handler(app *model.AppState) http.Handler {
http.NotFound(w, r)
}
}))
- mux.Handle("/v1/music", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ musicIndexHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
// GET /api/v1/music
@@ -98,7 +100,9 @@ func Handler(app *model.AppState) http.Handler {
default:
http.NotFound(w, r)
}
- }))
+ })
+ mux.Handle("/v1/music/", musicIndexHandler)
+ mux.Handle("/v1/music", musicIndexHandler)
// TRACK ENDPOINTS
@@ -129,7 +133,7 @@ func Handler(app *model.AppState) http.Handler {
http.NotFound(w, r)
}
}))
- mux.Handle("/v1/track", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ trackIndexHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
// GET /api/v1/track (admin)
@@ -140,7 +144,17 @@ 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 01899a6..322bc5d 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\n", http.StatusBadRequest)
+ http.Error(w, "Artist ID cannot be blank", 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\n", artist.ID), http.StatusBadRequest)
+ http.Error(w, fmt.Sprintf("Artist %s already exists", 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
new file mode 100644
index 0000000..f935b95
--- /dev/null
+++ b/api/blog.go
@@ -0,0 +1,217 @@
+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 69b7f12..f2bf479 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\n", http.StatusBadRequest)
+ http.Error(w, "Release ID cannot be empty", 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\n", release.ID), http.StatusBadRequest)
+ http.Error(w, fmt.Sprintf("Release %s already exists", 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 4e48418..ac5b83b 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\n", http.StatusBadRequest)
+ http.Error(w, "Track title cannot be empty", 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\n", http.StatusBadRequest)
+ http.Error(w, "Track title cannot be empty", http.StatusBadRequest)
return
}
diff --git a/controller/blog.go b/controller/blog.go
index 7fb201e..f0b5742 100644
--- a/controller/blog.go
+++ b/controller/blog.go
@@ -44,6 +44,18 @@ 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) " +
@@ -81,3 +93,18 @@ 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 4a2d03a..c1d44a9 100644
--- a/model/blog.go
+++ b/model/blog.go
@@ -11,17 +11,17 @@ import (
type (
BlogPost struct {
- 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"`
+ 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"`
}
)
@@ -40,12 +40,12 @@ func (b *BlogPost) GetMonth() string {
}
func (b *BlogPost) PrintDate() string {
- return b.CreatedAt.Format("2 January 2006, 03:04")
+ return b.CreatedAt.Format("2 January 2006, 15:04")
}
func (b *BlogPost) PrintModifiedDate() string {
if !b.ModifiedAt.Valid {
return ""
}
- return b.ModifiedAt.Time.Format("2 January 2006, 03:04")
+ return b.ModifiedAt.Time.Format("2 January 2006, 15:04")
}