add config and bruno

This commit is contained in:
Travis Shears 2025-09-30 19:40:21 +02:00
parent 8ceb698f03
commit 472c1b36a7
9 changed files with 708 additions and 7 deletions

44
gemlog/config.go Normal file
View file

@ -0,0 +1,44 @@
package gemlog
import (
"fmt"
"os"
"path/filepath"
"gopkg.in/yaml.v3"
)
// CouchDBConfig represents the CouchDB configuration section
type CouchDBConfig struct {
Host string `yaml:"host"`
Port int `yaml:"port"`
User string `yaml:"user"`
Password string `yaml:"password"`
}
// Config represents the main configuration structure
type Config struct {
CouchDB CouchDBConfig `yaml:"couchdb"`
}
// LoadConfig reads and parses the YAML configuration file from ~/.config/gemlog-cli
func LoadConfig() (*Config, error) {
homeDir, err := os.UserHomeDir()
if err != nil {
return nil, fmt.Errorf("failed to get user home directory: %w", err)
}
configPath := filepath.Join(homeDir, ".config", "gemlog-cli", "config.yml")
data, err := os.ReadFile(configPath)
if err != nil {
return nil, fmt.Errorf("failed to read config file %s: %w", configPath, err)
}
var config Config
if err := yaml.Unmarshal(data, &config); err != nil {
return nil, fmt.Errorf("failed to parse YAML config: %w", err)
}
return &config, nil
}

View file

@ -1 +1,218 @@
package gemlog
import (
"context"
"fmt"
"strings"
"time"
_ "github.com/go-kivik/couchdb/v3" // CouchDB driver
"github.com/go-kivik/kivik/v3"
)
// 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"`
}
// 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)
if err != nil {
return nil, fmt.Errorf("failed to create CouchDB client: %w", err)
}
// Check if database exists, create if it doesn't
exists, err := client.DBExists(context.TODO(), dbName)
if err != nil {
return nil, fmt.Errorf("failed to check if database exists: %w", err)
}
if !exists {
err = client.CreateDB(context.TODO(), dbName)
if err != nil {
return nil, fmt.Errorf("failed to create database: %w", err)
}
}
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)
if err != nil {
return fmt.Errorf("failed to save post: %w", err)
}
// 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,
})
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)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("error iterating posts: %w", err)
}
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)
}
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
}