Initial commit: knox knowledge index
This commit is contained in:
@@ -0,0 +1,245 @@
|
||||
package ingest
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"time"
|
||||
)
|
||||
|
||||
type GiteaIngester struct {
|
||||
Version string
|
||||
}
|
||||
|
||||
func NewGiteaIngester() *GiteaIngester {
|
||||
return &GiteaIngester{Version: "gitea-tea/v1"}
|
||||
}
|
||||
|
||||
func (g *GiteaIngester) SourceID() string { return "gitea" }
|
||||
|
||||
func (g *GiteaIngester) IngestAll() ([]*IngestResult, error) {
|
||||
var results []*IngestResult
|
||||
|
||||
repos, err := g.fetchRepos()
|
||||
if err == nil {
|
||||
for _, r := range repos {
|
||||
if result := g.repoToResult(r); result != nil {
|
||||
results = append(results, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
issues, err := g.fetchIssues()
|
||||
if err == nil {
|
||||
for _, i := range issues {
|
||||
if result := g.issueToResult(i); result != nil {
|
||||
results = append(results, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pulls, err := g.fetchPulls()
|
||||
if err == nil {
|
||||
for _, p := range pulls {
|
||||
if result := g.pullToResult(p); result != nil {
|
||||
results = append(results, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
type teaRepo struct {
|
||||
Owner string `json:"owner"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Updated string `json:"updated"`
|
||||
}
|
||||
|
||||
type teaIssue struct {
|
||||
Index string `json:"index"`
|
||||
Title string `json:"title"`
|
||||
State string `json:"state"`
|
||||
Owner string `json:"owner"`
|
||||
Repo string `json:"repo"`
|
||||
Labels string `json:"labels"`
|
||||
Author string `json:"author"`
|
||||
Updated string `json:"updated"`
|
||||
}
|
||||
|
||||
type teaPull struct {
|
||||
Index string `json:"index"`
|
||||
Title string `json:"title"`
|
||||
State string `json:"state"`
|
||||
Owner string `json:"owner"`
|
||||
Repo string `json:"repo"`
|
||||
Updated string `json:"updated"`
|
||||
}
|
||||
|
||||
func (g *GiteaIngester) teaJSON(args ...string) ([][]byte, error) {
|
||||
cmd := exec.Command("tea", append(args, "--output", "json")...)
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("tea %s: %w", args[0], err)
|
||||
}
|
||||
|
||||
var raw json.RawMessage
|
||||
if err := json.Unmarshal(output, &raw); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Handle both array and object responses
|
||||
var arr []json.RawMessage
|
||||
if err := json.Unmarshal(raw, &arr); err == nil {
|
||||
bytes := make([][]byte, len(arr))
|
||||
for i, a := range arr {
|
||||
bytes[i] = a
|
||||
}
|
||||
return bytes, nil
|
||||
}
|
||||
|
||||
// Single object — wrap in array
|
||||
return [][]byte{raw}, nil
|
||||
}
|
||||
|
||||
func (g *GiteaIngester) fetchRepos() ([]teaRepo, error) {
|
||||
items, err := g.teaJSON("repos", "list", "--limit", "100", "--fields", "owner,name,description,updated")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var repos []teaRepo
|
||||
for _, item := range items {
|
||||
var r teaRepo
|
||||
if err := json.Unmarshal(item, &r); err == nil {
|
||||
repos = append(repos, r)
|
||||
}
|
||||
}
|
||||
return repos, nil
|
||||
}
|
||||
|
||||
func (g *GiteaIngester) fetchIssues() ([]teaIssue, error) {
|
||||
items, err := g.teaJSON("issues", "list", "--state", "open", "--limit", "100", "--fields", "index,title,state,author,milestone,labels,owner,repo,updated")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var issues []teaIssue
|
||||
for _, item := range items {
|
||||
var i teaIssue
|
||||
if err := json.Unmarshal(item, &i); err == nil {
|
||||
issues = append(issues, i)
|
||||
}
|
||||
}
|
||||
return issues, nil
|
||||
}
|
||||
|
||||
func (g *GiteaIngester) fetchPulls() ([]teaPull, error) {
|
||||
items, err := g.teaJSON("pulls", "list", "--state", "open", "--limit", "100", "--fields", "index,title,state,owner,repo,updated")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var pulls []teaPull
|
||||
for _, item := range items {
|
||||
var p teaPull
|
||||
if err := json.Unmarshal(item, &p); err == nil {
|
||||
pulls = append(pulls, p)
|
||||
}
|
||||
}
|
||||
return pulls, nil
|
||||
}
|
||||
|
||||
// signalTime returns the item's last-activity time, falling back to now.
|
||||
func signalTime(updated string) string {
|
||||
if updated != "" {
|
||||
if _, err := time.Parse(time.RFC3339, updated); err == nil {
|
||||
return updated
|
||||
}
|
||||
}
|
||||
return time.Now().UTC().Format(time.RFC3339)
|
||||
}
|
||||
|
||||
func (g *GiteaIngester) repoToResult(r teaRepo) *IngestResult {
|
||||
fullName := r.Owner + "/" + r.Name
|
||||
fp := Fingerprint([]byte("gitea:repo:" + fullName))
|
||||
|
||||
return &IngestResult{
|
||||
Fingerprint: fp,
|
||||
SourceID: g.SourceID(),
|
||||
SourcePath: fullName,
|
||||
Project: r.Name,
|
||||
ContentType: "repo",
|
||||
Title: fullName,
|
||||
Summary: r.Description,
|
||||
Confidence: 0.9,
|
||||
IngesterVersion: g.Version,
|
||||
CreatedAt: signalTime(r.Updated),
|
||||
Provenance: map[string]any{
|
||||
"type": "repository",
|
||||
"owner": r.Owner,
|
||||
"name": r.Name,
|
||||
"description": r.Description,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GiteaIngester) issueToResult(i teaIssue) *IngestResult {
|
||||
title := fmt.Sprintf("#%s %s", i.Index, i.Title)
|
||||
fp := Fingerprint([]byte(fmt.Sprintf("gitea:issue:%s/%s:%s", i.Owner, i.Repo, i.Index)))
|
||||
|
||||
summary := fmt.Sprintf("[%s/%s#%s] %s", i.Owner, i.Repo, i.Index, i.Title)
|
||||
if i.Labels != "" {
|
||||
summary += " [" + i.Labels + "]"
|
||||
}
|
||||
|
||||
return &IngestResult{
|
||||
Fingerprint: fp,
|
||||
SourceID: g.SourceID(),
|
||||
SourcePath: fmt.Sprintf("%s/%s#%s", i.Owner, i.Repo, i.Index),
|
||||
Project: i.Repo,
|
||||
ContentType: "issue",
|
||||
Title: title,
|
||||
Summary: summary,
|
||||
Confidence: 0.85,
|
||||
IngesterVersion: g.Version,
|
||||
CreatedAt: signalTime(i.Updated),
|
||||
Provenance: map[string]any{
|
||||
"type": "issue",
|
||||
"owner": i.Owner,
|
||||
"repo": i.Repo,
|
||||
"index": i.Index,
|
||||
"state": i.State,
|
||||
"labels": i.Labels,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GiteaIngester) pullToResult(p teaPull) *IngestResult {
|
||||
title := fmt.Sprintf("!%s %s", p.Index, p.Title)
|
||||
fp := Fingerprint([]byte(fmt.Sprintf("gitea:pull:%s/%s:%s", p.Owner, p.Repo, p.Index)))
|
||||
|
||||
return &IngestResult{
|
||||
Fingerprint: fp,
|
||||
SourceID: g.SourceID(),
|
||||
SourcePath: fmt.Sprintf("%s/%s!%s", p.Owner, p.Repo, p.Index),
|
||||
Project: p.Repo,
|
||||
ContentType: "pull",
|
||||
Title: title,
|
||||
Summary: fmt.Sprintf("[PR %s/%s] %s", p.Owner, p.Repo, p.Title),
|
||||
Confidence: 0.85,
|
||||
IngesterVersion: g.Version,
|
||||
CreatedAt: signalTime(p.Updated),
|
||||
Provenance: map[string]any{
|
||||
"type": "pull_request",
|
||||
"owner": p.Owner,
|
||||
"repo": p.Repo,
|
||||
"index": p.Index,
|
||||
"state": p.State,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GiteaIngester) Ingest(_ string) (*IngestResult, error) {
|
||||
return nil, fmt.Errorf("use IngestAll() for gitea")
|
||||
}
|
||||
|
||||
var _ Ingester = (*GiteaIngester)(nil)
|
||||
Reference in New Issue
Block a user