personal-gemini-capsule/internal/microblog/microblog.go

196 lines
5.3 KiB
Go

package microblog
import (
"encoding/json"
"fmt"
"gemini_site/internal/pocketbase"
"log/slog"
"time"
gemini "github.com/kulak/gemini"
)
type source string
const (
sourcePleroma source = "pleroma"
sourceBlueSky source = "blue_sky"
sourceMastodon source = "mastodon"
sourcePixelfed source = "pixelfed"
sourceNostr source = "nostr"
)
var supportedSources = []source{
sourceNostr,
// TODO: Add support for BlueSky and Mastodon
// SourceBlueSky,
// SourceMastodon,
}
// Post represents a single blog post
type post struct {
ID string
RemoteID string
Content string
// TODO: add support for images, must extend the pocketbase query
// Images []string
Timestamp time.Time
}
// PBPost represents a microblog post from PocketBase
type pbPost struct {
ID string `json:"id"`
RemoteID string `json:"remoteId"`
Source Source `json:"source"`
FullPost json.RawMessage `json:"fullPost"`
Posted string `json:"posted"`
}
// MicroBlog manages blog posts
type MicroBlog struct {
pbClient *pocketbase.PocketBaseClient
}
// NewMicroBlog creates a new microblog instance
func NewMicroBlog(pbClient *pocketbase.PocketBaseClient) *MicroBlog {
mb := &MicroBlog{
pbClient: pbClient,
}
return mb
}
// // Add some sample posts
// mb.addSamplePosts()
// return mb
// }
// addSamplePosts adds some initial content
// func (mb *MicroBlog) addSamplePosts() {
// samplePosts := []Post{
// {
// ID: "1",
// Title: "Welcome to the Gemini Microblog",
// Content: "This is the first post on our Gemini-powered microblog! It's simple, fast, and distraction-free.",
// Author: "Admin",
// Timestamp: time.Now().Add(-2 * time.Hour),
// },
// {
// ID: "2",
// Title: "The Beauty of Simplicity",
// Content: "Gemini protocol encourages us to focus on content over presentation. This microblog embodies that philosophy.",
// Author: "Admin",
// Timestamp: time.Now().Add(-1 * time.Hour),
// },
// }
// mb.posts = append(mb.posts, samplePosts...)
// }
// AddPost adds a new post to the blog
// func (mb *MicroBlog) AddPost(title, content, author string) string {
// id := fmt.Sprintf("%d", time.Now().Unix())
// post := Post{
// ID: id,
// Title: title,
// Content: content,
// Author: author,
// Timestamp: time.Now(),
// }
// mb.posts = append(mb.posts, post)
// return id
// }
// GetPost retrieves a post by ID
// func (mb *MicroBlog) GetPost(id string) (*Post, bool) {
// for _, post := range mb.posts {
// if post.ID == id {
// return &post, true
// }
// }
// return nil, false
// }
// GetRecentPosts returns the most recent posts
func (mb *MicroBlog) GetRecentPosts(limit int) ([]post, error) {
res, err := mb.pbClient.GetList("micro_blog_posts", 1, limit, "-posted")
if err != nil {
return nil, fmt.Errorf("failed to fetch posts from pocketbase: %w", err)
}
var rawPosts []pbPost
if err := json.Unmarshal(res.Items, &rawPosts); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}
slog.Info("Posts from pocketbase", "rawPosts", rawPosts)
return nil, fmt.Errorf("todo: %w", err)
}
// HandleBlogRequest handles Gemini requests for the microblog
func (mb *MicroBlog) HandleBlogRequest(w gemini.ResponseWriter, req *gemini.Request) {
// path := req.URL.Path
// switch {
// case path == "/microblog" || path == "/microblog/":
// mb.serveBlogIndex(w, req)
// case strings.HasPrefix(path, "/microblog/post/"):
// postID := strings.TrimPrefix(path, "/microblog/post/")
// mb.servePost(w, req, postID)
// default:
// w.WriteStatusMsg(gemini.StatusNotFound, "Blog page not found")
// }
}
// serveBlogIndex serves the main blog page with recent posts
// func (mb *MicroBlog) serveBlogIndex(w gemini.ResponseWriter, req *gemini.Request) {
// w.WriteStatusMsg(gemini.StatusSuccess, "text/gemini")
// var content strings.Builder
// content.WriteString("# Gemini Microblog\n\n")
// posts := mb.GetRecentPosts(10)
// if len(posts) == 0 {
// content.WriteString("No posts yet. Be the first to write something!\n\n")
// } else {
// content.WriteString("## Recent Posts\n\n")
// for _, post := range posts {
// content.WriteString(fmt.Sprintf("=> /blog/post/%s %s\n", post.ID, post.Title))
// 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("=> /blog/new Write a new post\n")
// content.WriteString("=> / Back to home\n")
// w.WriteBody([]byte(content.String()))
// }
// servePost serves a single blog post
// func (mb *MicroBlog) servePost(w gemini.ResponseWriter, req *gemini.Request, postID string) {
// post, found := mb.GetPost(postID)
// if !found {
// w.WriteStatusMsg(gemini.StatusNotFound, "Post not found")
// return
// }
// w.WriteStatusMsg(gemini.StatusSuccess, "text/gemini")
// var content strings.Builder
// content.WriteString(fmt.Sprintf("# %s\n\n", post.Title))
// content.WriteString(fmt.Sprintf("By %s on %s\n\n",
// post.Author,
// post.Timestamp.Format("2006-01-02 15:04")))
// content.WriteString("---\n\n")
// content.WriteString(post.Content)
// content.WriteString("\n\n---\n\n")
// content.WriteString("=> /blog Back to blog\n")
// content.WriteString("=> / Back to home\n")
// w.WriteBody([]byte(content.String()))
// }