add config and bruno
This commit is contained in:
parent
8ceb698f03
commit
472c1b36a7
9 changed files with 708 additions and 7 deletions
44
gemlog/config.go
Normal file
44
gemlog/config.go
Normal 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
|
||||
}
|
||||
217
gemlog/db.go
217
gemlog/db.go
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue