add page for microblog header content
This commit is contained in:
parent
ef5f5e33ab
commit
ee4dfdad18
5 changed files with 110 additions and 57 deletions
4
dev.sh
4
dev.sh
|
|
@ -2,4 +2,6 @@
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
go run main.go -cert=./keys/localhost.crt.pem -key=./keys/localhost_key.pem -host=localhost:8080
|
source .env
|
||||||
|
|
||||||
|
fd | entr -r go run main.go -cert=./keys/localhost.crt.pem -key=./keys/localhost_key.pem -host=localhost:8080
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"gemini_site/internal/pocketbase"
|
"gemini_site/internal/pocketbase"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
gemini "github.com/kulak/gemini"
|
gemini "github.com/kulak/gemini"
|
||||||
|
|
@ -20,14 +22,6 @@ const (
|
||||||
sourceNostr source = "nostr"
|
sourceNostr source = "nostr"
|
||||||
)
|
)
|
||||||
|
|
||||||
var supportedSources = map[source]bool{
|
|
||||||
sourceNostr: true,
|
|
||||||
sourcePleroma: false,
|
|
||||||
sourceBlueSky: false,
|
|
||||||
sourceMastodon: false,
|
|
||||||
sourcePixelfed: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Post represents a single blog post
|
// Post represents a single blog post
|
||||||
type post struct {
|
type post struct {
|
||||||
ID string
|
ID string
|
||||||
|
|
@ -36,6 +30,7 @@ type post struct {
|
||||||
// TODO: add support for images, must extend the pocketbase query
|
// TODO: add support for images, must extend the pocketbase query
|
||||||
// Images []string
|
// Images []string
|
||||||
Timestamp time.Time
|
Timestamp time.Time
|
||||||
|
Source source
|
||||||
}
|
}
|
||||||
|
|
||||||
// PBPost represents a microblog post from PocketBase
|
// PBPost represents a microblog post from PocketBase
|
||||||
|
|
@ -108,14 +103,32 @@ func NewMicroBlog(pbClient *pocketbase.PocketBaseClient) *MicroBlog {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// GetPost retrieves a post by ID
|
// GetPost retrieves a post by ID
|
||||||
// func (mb *MicroBlog) GetPost(id string) (*Post, bool) {
|
//
|
||||||
// for _, post := range mb.posts {
|
// func (mb *MicroBlog) GetPost(id string) (*Post, bool) {
|
||||||
// if post.ID == id {
|
// for _, post := range mb.posts {
|
||||||
// return &post, true
|
// if post.ID == id {
|
||||||
// }
|
// return &post, true
|
||||||
// }
|
// }
|
||||||
// return nil, false
|
// }
|
||||||
// }
|
// return nil, false
|
||||||
|
// }
|
||||||
|
func transformNostrPost(p pbPost) post {
|
||||||
|
var nostrPost nostrPost
|
||||||
|
if err := json.Unmarshal(p.FullPost, &nostrPost); err != nil {
|
||||||
|
slog.Error("Problem unmarshalling nostr post", "error", err)
|
||||||
|
return post{}
|
||||||
|
}
|
||||||
|
content := nostrPost.Content
|
||||||
|
content = strings.ReplaceAll(content, "#", "\\#")
|
||||||
|
// content = strings.ReplaceAll(content, "\t", "\\t")
|
||||||
|
return post{
|
||||||
|
ID: p.ID,
|
||||||
|
RemoteID: p.RemoteID,
|
||||||
|
Content: content,
|
||||||
|
Timestamp: time.Now(),
|
||||||
|
Source: source(p.Source),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GetRecentPosts returns the most recent posts
|
// GetRecentPosts returns the most recent posts
|
||||||
func (mb *MicroBlog) GetRecentPosts(limit int) ([]post, error) {
|
func (mb *MicroBlog) GetRecentPosts(limit int) ([]post, error) {
|
||||||
|
|
@ -133,17 +146,19 @@ func (mb *MicroBlog) GetRecentPosts(limit int) ([]post, error) {
|
||||||
var filteredPosts []post
|
var filteredPosts []post
|
||||||
for _, p := range rawPosts {
|
for _, p := range rawPosts {
|
||||||
if p.Source == SourceNostr {
|
if p.Source == SourceNostr {
|
||||||
var nostrPost nostrPost
|
filteredPosts = append(filteredPosts, transformNostrPost(p))
|
||||||
if err := json.Unmarshal(p.FullPost, &nostrPost); err != nil {
|
|
||||||
slog.Error("Problem unmarshalling nostr post", "error", err)
|
// var nostrPost nostrPost
|
||||||
continue
|
// if err := json.Unmarshal(p.FullPost, &nostrPost); err != nil {
|
||||||
}
|
// slog.Error("Problem unmarshalling nostr post", "error", err)
|
||||||
filteredPosts = append(filteredPosts, post{
|
// continue
|
||||||
ID: p.ID,
|
// }
|
||||||
RemoteID: p.RemoteID,
|
// filteredPosts = append(filteredPosts, post{
|
||||||
Content: nostrPost.Content,
|
// ID: p.ID,
|
||||||
Timestamp: time.Now(),
|
// RemoteID: p.RemoteID,
|
||||||
})
|
// Content: nostrPost.Content,
|
||||||
|
// Timestamp: time.Now(),
|
||||||
|
// })
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -152,47 +167,68 @@ func (mb *MicroBlog) GetRecentPosts(limit int) ([]post, error) {
|
||||||
|
|
||||||
// HandleBlogRequest handles Gemini requests for the microblog
|
// HandleBlogRequest handles Gemini requests for the microblog
|
||||||
func (mb *MicroBlog) HandleBlogRequest(w gemini.ResponseWriter, req *gemini.Request) {
|
func (mb *MicroBlog) HandleBlogRequest(w gemini.ResponseWriter, req *gemini.Request) {
|
||||||
// path := req.URL.Path
|
path := req.URL.Path
|
||||||
|
|
||||||
// switch {
|
switch {
|
||||||
// case path == "/microblog" || path == "/microblog/":
|
case path == "/microblog" || path == "/microblog/":
|
||||||
// mb.serveBlogIndex(w, req)
|
mb.serveIndex(w, req)
|
||||||
// case strings.HasPrefix(path, "/microblog/post/"):
|
// case strings.HasPrefix(path, "/microblog/post/"):
|
||||||
// postID := strings.TrimPrefix(path, "/microblog/post/")
|
// postID := strings.TrimPrefix(path, "/microblog/post/")
|
||||||
// mb.servePost(w, req, postID)
|
// mb.servePost(w, req, postID)
|
||||||
// default:
|
default:
|
||||||
// w.WriteStatusMsg(gemini.StatusNotFound, "Blog page not found")
|
w.WriteStatusMsg(gemini.StatusNotFound, "Page not found")
|
||||||
// }
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawPost(builder *strings.Builder, p post) {
|
||||||
|
builder.WriteString("+------------------------------------------+\n")
|
||||||
|
builder.WriteString(p.Content)
|
||||||
|
builder.WriteString("\n")
|
||||||
|
builder.WriteString(fmt.Sprintf("source: %s, id: %s...\n", p.Source, p.RemoteID[:10]))
|
||||||
|
builder.WriteString("+------------------------------------------+\n\n\n")
|
||||||
|
|
||||||
|
// builder.WriteString(fmt.Sprintf("=> /blog/post/%s %s\n", p.ID, p.Title))
|
||||||
|
// builder.WriteString(fmt.Sprintf(" By %s on %s\n\n",
|
||||||
|
// p.Timestamp.Format("2006-01-02 15:04")))
|
||||||
}
|
}
|
||||||
|
|
||||||
// serveBlogIndex serves the main blog page with recent posts
|
// serveBlogIndex serves the main blog page with recent posts
|
||||||
// func (mb *MicroBlog) serveBlogIndex(w gemini.ResponseWriter, req *gemini.Request) {
|
func (mb *MicroBlog) serveIndex(w gemini.ResponseWriter, req *gemini.Request) {
|
||||||
// w.WriteStatusMsg(gemini.StatusSuccess, "text/gemini")
|
w.WriteStatusMsg(gemini.StatusSuccess, "text/gemini")
|
||||||
|
|
||||||
// var content strings.Builder
|
var content strings.Builder
|
||||||
// content.WriteString("# Gemini Microblog\n\n")
|
// content.WriteString("# Gemini Microblog\n\n")
|
||||||
|
// content.WriteString("Here are my microblog posts from various plantforms\n")
|
||||||
|
// Read and include the contents of ../../pages/microblog.gmi
|
||||||
|
page, err := os.ReadFile("./pages/microblog.gmi")
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Problem reading microblog page", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
content.Write(page)
|
||||||
|
content.WriteString("\n")
|
||||||
|
|
||||||
// posts := mb.GetRecentPosts(10)
|
posts, err := mb.GetRecentPosts(20)
|
||||||
|
if err != nil {
|
||||||
|
content.WriteString("Error fetching posts: " + err.Error() + "\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
// if len(posts) == 0 {
|
if len(posts) == 0 {
|
||||||
// content.WriteString("No posts yet. Be the first to write something!\n\n")
|
content.WriteString("No posts found.\n\n")
|
||||||
// } else {
|
} else {
|
||||||
// content.WriteString("## Recent Posts\n\n")
|
content.WriteString("## Recent Posts\n\n")
|
||||||
|
|
||||||
// for _, post := range posts {
|
for _, post := range posts {
|
||||||
// content.WriteString(fmt.Sprintf("=> /blog/post/%s %s\n", post.ID, post.Title))
|
drawPost(&content, post)
|
||||||
// content.WriteString(fmt.Sprintf(" By %s on %s\n\n",
|
}
|
||||||
// post.Author,
|
}
|
||||||
// post.Timestamp.Format("2006-01-02 15:04")))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// content.WriteString("## Actions\n\n")
|
content.WriteString("## Nav\n\n")
|
||||||
// content.WriteString("=> /blog/new Write a new post\n")
|
// content.WriteString("=> /blog/new Write a new post\n")
|
||||||
// content.WriteString("=> / Back to home\n")
|
content.WriteString("=> / Back to home\n")
|
||||||
|
|
||||||
// w.WriteBody([]byte(content.String()))
|
w.WriteBody([]byte(content.String()))
|
||||||
// }
|
}
|
||||||
|
|
||||||
// servePost serves a single blog post
|
// servePost serves a single blog post
|
||||||
// func (mb *MicroBlog) servePost(w gemini.ResponseWriter, req *gemini.Request, postID string) {
|
// func (mb *MicroBlog) servePost(w gemini.ResponseWriter, req *gemini.Request, postID string) {
|
||||||
|
|
|
||||||
|
|
@ -176,6 +176,7 @@ func (c *PocketBaseClient) GetList(
|
||||||
"perPage": {strconv.Itoa(pageSize)},
|
"perPage": {strconv.Itoa(pageSize)},
|
||||||
"sort": {sort},
|
"sort": {sort},
|
||||||
"skipTotal": {"true"},
|
"skipTotal": {"true"},
|
||||||
|
"filter": {"source = \"nostr\""},
|
||||||
// TODO: add additional fields like image and tag?
|
// TODO: add additional fields like image and tag?
|
||||||
}
|
}
|
||||||
apiURL := fmt.Sprintf("%s/api/collections/%s/records", c.host, collection)
|
apiURL := fmt.Sprintf("%s/api/collections/%s/records", c.host, collection)
|
||||||
|
|
|
||||||
4
main.go
4
main.go
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gemini_site/internal/microblog"
|
"gemini_site/internal/microblog"
|
||||||
|
"gemini_site/internal/pocketbase"
|
||||||
|
|
||||||
gemini "github.com/kulak/gemini"
|
gemini "github.com/kulak/gemini"
|
||||||
)
|
)
|
||||||
|
|
@ -102,8 +103,9 @@ func main() {
|
||||||
flag.StringVar(&key, "key", "server.key.pem", "private key associated with certificate file")
|
flag.StringVar(&key, "key", "server.key.pem", "private key associated with certificate file")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
|
pbClient := pocketbase.NewPocketBaseClient()
|
||||||
handler := MainHandler{
|
handler := MainHandler{
|
||||||
blog: microblog.NewHandler(),
|
blog: microblog.NewHandler(pbClient),
|
||||||
}
|
}
|
||||||
|
|
||||||
err := gemini.ListenAndServe(host, cert, key, gemini.TrapPanic(handler.ServeGemini))
|
err := gemini.ListenAndServe(host, cert, key, gemini.TrapPanic(handler.ServeGemini))
|
||||||
|
|
|
||||||
12
pages/microblog.gmi
Normal file
12
pages/microblog.gmi
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
# Microblog
|
||||||
|
|
||||||
|
An aggregation of tweet like posts from my various social media platforms.
|
||||||
|
|
||||||
|
clearnet version:
|
||||||
|
|
||||||
|
=> https://travisshears.com/micro-blog
|
||||||
|
|
||||||
|
So for it renders posts from:
|
||||||
|
* nostr, id: nprofile1qyxhwumn8ghj7mn0wvhxcmmvqqs9udcv9uhqggjz87js9rtaph4lajlxnxsvwvm7zwdjt6etzyk52rgeg4wrz
|
||||||
|
* mastodon, id: dice.camp/@travisshears
|
||||||
|
* bluesky, id: @travisshears.bsky.social
|
||||||
Loading…
Add table
Add a link
Reference in a new issue