get write action working and entries saving to db
This commit is contained in:
parent
472c1b36a7
commit
09fb974bd2
5 changed files with 136 additions and 257 deletions
218
gemlog/db.go
218
gemlog/db.go
|
|
@ -1,218 +1,50 @@
|
|||
package gemlog
|
||||
|
||||
import (
|
||||
"context"
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
_ "github.com/go-kivik/couchdb/v3" // CouchDB driver
|
||||
"github.com/go-kivik/kivik/v3"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Post represents a blog post document in CouchDB
|
||||
type Post struct {
|
||||
ID string `json:"_id,omitempty"`
|
||||
Rev string `json:"_rev,omitempty"`
|
||||
Title string `json:"title"`
|
||||
Date time.Time `json:"date"`
|
||||
Slug string `json:"slug"`
|
||||
Tags []string `json:"tags"`
|
||||
Content string `json:"content"`
|
||||
Created time.Time `json:"created"`
|
||||
Modified time.Time `json:"modified"`
|
||||
}
|
||||
func SaveGemlogEntry(config *Config, entry *GemlogEntry) error {
|
||||
url := fmt.Sprintf("%s:%d/gemlog/", config.CouchDB.Host, config.CouchDB.Port)
|
||||
|
||||
// CouchDBClient wraps the CouchDB connection and operations
|
||||
type CouchDBClient struct {
|
||||
client *kivik.Client
|
||||
db *kivik.DB
|
||||
}
|
||||
|
||||
// NewCouchDBClient creates a new CouchDB client connection
|
||||
func NewCouchDBClient(url, dbName string) (*CouchDBClient, error) {
|
||||
client, err := kivik.New("couch", url)
|
||||
// Marshal the entry struct to JSON
|
||||
jsonData, err := json.Marshal(entry)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create CouchDB client: %w", err)
|
||||
return fmt.Errorf("failed to marshal entry: %w", err)
|
||||
}
|
||||
|
||||
// Check if database exists, create if it doesn't
|
||||
exists, err := client.DBExists(context.TODO(), dbName)
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to check if database exists: %w", err)
|
||||
return fmt.Errorf("failed to create request: %w", err)
|
||||
}
|
||||
|
||||
if !exists {
|
||||
err = client.CreateDB(context.TODO(), dbName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create database: %w", err)
|
||||
}
|
||||
}
|
||||
// Encode username:password for Basic Auth
|
||||
auth := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", config.CouchDB.User, config.CouchDB.Password)))
|
||||
req.Header.Add("authorization", fmt.Sprintf("Basic %s", auth))
|
||||
req.Header.Add("content-type", "application/json")
|
||||
|
||||
db := client.DB(context.TODO(), dbName)
|
||||
|
||||
return &CouchDBClient{
|
||||
client: client,
|
||||
db: db,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SavePost saves a post to CouchDB
|
||||
func (c *CouchDBClient) SavePost(post *Post) error {
|
||||
ctx := context.TODO()
|
||||
|
||||
// Set timestamps
|
||||
now := time.Now()
|
||||
if post.Created.IsZero() {
|
||||
post.Created = now
|
||||
}
|
||||
post.Modified = now
|
||||
|
||||
// Generate ID if not set (use slug-timestamp)
|
||||
if post.ID == "" {
|
||||
post.ID = fmt.Sprintf("%s-%d", post.Slug, now.Unix())
|
||||
}
|
||||
|
||||
// Save the document
|
||||
rev, err := c.db.Put(ctx, post.ID, post)
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to save post: %w", err)
|
||||
return fmt.Errorf("failed to send request: %w", err)
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
// Update the revision
|
||||
post.Rev = rev
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPost retrieves a post by ID
|
||||
func (c *CouchDBClient) GetPost(id string) (*Post, error) {
|
||||
ctx := context.TODO()
|
||||
|
||||
row := c.db.Get(ctx, id)
|
||||
if row.Err != nil {
|
||||
return nil, fmt.Errorf("failed to get post: %w", row.Err)
|
||||
}
|
||||
|
||||
var post Post
|
||||
if err := row.ScanDoc(&post); err != nil {
|
||||
return nil, fmt.Errorf("failed to scan post document: %w", err)
|
||||
}
|
||||
|
||||
return &post, nil
|
||||
}
|
||||
|
||||
// GetAllPosts retrieves all posts from the database
|
||||
func (c *CouchDBClient) GetAllPosts() ([]*Post, error) {
|
||||
ctx := context.TODO()
|
||||
|
||||
rows, err := c.db.AllDocs(ctx, kivik.Options{
|
||||
"include_docs": true,
|
||||
})
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get all posts: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var posts []*Post
|
||||
for rows.Next() {
|
||||
var post Post
|
||||
if err := rows.ScanDoc(&post); err != nil {
|
||||
continue // Skip invalid documents
|
||||
}
|
||||
posts = append(posts, &post)
|
||||
return fmt.Errorf("failed to read response body: %w", err)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("error iterating posts: %w", err)
|
||||
if res.StatusCode < 200 || res.StatusCode >= 300 {
|
||||
return fmt.Errorf("unexpected status code %d: %s", res.StatusCode, string(body))
|
||||
}
|
||||
|
||||
return posts, nil
|
||||
}
|
||||
|
||||
// UpdatePost updates an existing post
|
||||
func (c *CouchDBClient) UpdatePost(post *Post) error {
|
||||
if post.ID == "" || post.Rev == "" {
|
||||
return fmt.Errorf("post ID and revision are required for updates")
|
||||
}
|
||||
|
||||
post.Modified = time.Now()
|
||||
return c.SavePost(post)
|
||||
}
|
||||
|
||||
// DeletePost deletes a post by ID and revision
|
||||
func (c *CouchDBClient) DeletePost(id, rev string) error {
|
||||
ctx := context.TODO()
|
||||
|
||||
_, err := c.db.Delete(ctx, id, rev)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete post: %w", err)
|
||||
}
|
||||
// fmt.Println(res)
|
||||
// fmt.Println(string(body))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseGeminiPost parses a gemini post content and returns a Post struct
|
||||
func ParseGeminiPost(content string) (*Post, error) {
|
||||
lines := strings.Split(content, "\n")
|
||||
|
||||
var post Post
|
||||
var contentStart int
|
||||
inFrontmatter := false
|
||||
|
||||
for i, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
|
||||
// Check for frontmatter separator
|
||||
if line == "---" {
|
||||
if !inFrontmatter {
|
||||
inFrontmatter = true
|
||||
continue
|
||||
} else {
|
||||
// End of frontmatter
|
||||
contentStart = i + 1
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !inFrontmatter {
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse frontmatter
|
||||
if strings.Contains(line, ":") {
|
||||
parts := strings.SplitN(line, ":", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
|
||||
key := strings.TrimSpace(parts[0])
|
||||
value := strings.TrimSpace(parts[1])
|
||||
|
||||
switch key {
|
||||
case "title":
|
||||
post.Title = value
|
||||
case "date":
|
||||
if date, err := time.Parse("2006-01-02", value); err == nil {
|
||||
post.Date = date
|
||||
}
|
||||
case "slug":
|
||||
post.Slug = value
|
||||
case "tags":
|
||||
// Parse comma-separated tags
|
||||
if value != "" {
|
||||
tagList := strings.Split(value, ",")
|
||||
for _, tag := range tagList {
|
||||
post.Tags = append(post.Tags, strings.TrimSpace(tag))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extract content after frontmatter
|
||||
if contentStart < len(lines) {
|
||||
post.Content = strings.Join(lines[contentStart:], "\n")
|
||||
}
|
||||
|
||||
return &post, nil
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue