package gemlog import ( "bytes" "encoding/base64" "encoding/json" "fmt" "io" "log/slog" "net/http" "time" ) func genBasicAuthHeader(user, password string) string { auth := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", user, password))) return fmt.Sprintf("Basic %s", auth) } func listGemLogs(config *Config) ([]GemlogListEntry, error) { url := fmt.Sprintf("%s:%d/gemlog/_design/gemlog-cli/_view/list", config.CouchDB.Host, config.CouchDB.Port) req, err := http.NewRequest("GET", url, nil) if err != nil { return nil, fmt.Errorf("failed to create request: %w", err) } req.Header.Add("authorization", genBasicAuthHeader(config.CouchDB.User, config.CouchDB.Password)) res, err := http.DefaultClient.Do(req) if err != nil { return nil, fmt.Errorf("failed to send request: %w", err) } defer res.Body.Close() body, err := io.ReadAll(res.Body) if err != nil { return nil, fmt.Errorf("failed to read response body: %w", err) } if res.StatusCode < 200 || res.StatusCode >= 300 { return nil, fmt.Errorf("unexpected status code %d: %s", res.StatusCode, string(body)) } // Parse CouchDB response var couchResponse struct { TotalRows int `json:"total_rows"` Offset int `json:"offset"` Rows []struct { ID string `json:"id"` Key string `json:"key"` Value struct { Title string `json:"title"` Slug string `json:"slug"` Date time.Time `json:"date"` Rev string `json:"rev"` ID string `json:"id"` } `json:"value"` } `json:"rows"` } if err := json.Unmarshal(body, &couchResponse); err != nil { return nil, fmt.Errorf("failed to parse response: %w", err) } // Extract entries from response entries := make([]GemlogListEntry, 0, len(couchResponse.Rows)) for _, row := range couchResponse.Rows { entry := GemlogListEntry{ ID: row.ID, Title: row.Value.Title, Slug: row.Value.Slug, Date: row.Value.Date, Rev: row.Value.Rev, } entries = append(entries, entry) } slog.Info("Found gemlogs", "count", len(entries)) return entries, nil } func ReadGemlogEntry(config *Config, id string) (GemlogEntry, error) { url := fmt.Sprintf("%s:%d/gemlog/%s", config.CouchDB.Host, config.CouchDB.Port, id) req, err := http.NewRequest("GET", url, nil) if err != nil { return GemlogEntry{}, fmt.Errorf("failed to create request: %w", err) } req.Header.Add("authorization", genBasicAuthHeader(config.CouchDB.User, config.CouchDB.Password)) req.Header.Add("content-type", "application/json") res, err := http.DefaultClient.Do(req) if err != nil { return GemlogEntry{}, fmt.Errorf("failed to send request: %w", err) } defer res.Body.Close() body, err := io.ReadAll(res.Body) if err != nil { return GemlogEntry{}, fmt.Errorf("failed to read response body: %w", err) } if res.StatusCode < 200 || res.StatusCode >= 300 { return GemlogEntry{}, fmt.Errorf("unexpected status code %d: %s", res.StatusCode, string(body)) } var rawData struct { Title string `json:"title"` GemText string `json:"gemtxt"` Slug string `json:"slug"` Date time.Time `json:"date"` } if err := json.Unmarshal(body, &rawData); err != nil { return GemlogEntry{}, fmt.Errorf("failed to parse response: %w", err) } return GemlogEntry{ Title: rawData.Title, Slug: rawData.Slug, Date: rawData.Date, Gemtxt: rawData.GemText, Tags: make([]string, 0), }, nil } func DeleteGemlogEntry(config *Config, id string, rev string) error { url := fmt.Sprintf("%s:%d/gemlog/%s?rev=%s", config.CouchDB.Host, config.CouchDB.Port, id, rev) req, err := http.NewRequest("DELETE", url, nil) if err != nil { return fmt.Errorf("failed to create request: %w", err) } req.Header.Add("authorization", genBasicAuthHeader(config.CouchDB.User, config.CouchDB.Password)) req.Header.Add("content-type", "application/json") res, err := http.DefaultClient.Do(req) if err != nil { return fmt.Errorf("failed to send request: %w", err) } defer res.Body.Close() body, err := io.ReadAll(res.Body) if err != nil { return fmt.Errorf("failed to read response body: %w", err) } if res.StatusCode < 200 || res.StatusCode >= 300 { return fmt.Errorf("unexpected status code %d: %s", res.StatusCode, string(body)) } return nil } func SaveGemlogEntry(config *Config, entry *GemlogEntry) error { url := fmt.Sprintf("%s:%d/gemlog/", config.CouchDB.Host, config.CouchDB.Port) // Marshal the entry struct to JSON jsonData, err := json.Marshal(entry) if err != nil { return fmt.Errorf("failed to marshal entry: %w", err) } req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData)) if err != nil { return fmt.Errorf("failed to create request: %w", err) } req.Header.Add("authorization", genBasicAuthHeader(config.CouchDB.User, config.CouchDB.Password)) req.Header.Add("content-type", "application/json") res, err := http.DefaultClient.Do(req) if err != nil { return fmt.Errorf("failed to send request: %w", err) } defer res.Body.Close() body, err := io.ReadAll(res.Body) if err != nil { return fmt.Errorf("failed to read response body: %w", err) } if res.StatusCode < 200 || res.StatusCode >= 300 { return fmt.Errorf("unexpected status code %d: %s", res.StatusCode, string(body)) } return nil } func CheckDBConnection(config *Config) error { url := fmt.Sprintf("%s:%d/gemlog/", config.CouchDB.Host, config.CouchDB.Port) req, err := http.NewRequest("GET", url, nil) if err != nil { return fmt.Errorf("failed to create request: %w", err) } req.Header.Add("authorization", genBasicAuthHeader(config.CouchDB.User, config.CouchDB.Password)) res, err := http.DefaultClient.Do(req) if err != nil { return fmt.Errorf("failed to send request: %w", err) } defer res.Body.Close() if res.StatusCode == 200 { return nil } return fmt.Errorf("unexpected status code %d", res.StatusCode) }