teafolio/api.go

205 lines
5.1 KiB
Go

package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"strings"
"time"
)
type Repo struct {
Name string `json:"name"`
Description string `json:"description"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type ContentsResponse struct {
Content []byte `json:"content"` // Assume base64 "encoding" parameter in Gitea response, and use Go's auto decode
}
type TopicsResponse struct {
Topics []string `json:"topics"`
}
type ReaddirEntry struct {
Name string `json:"name"`
Path string `json:"path"`
Size int `json:"size"`
RawURL string `json:"download_url"`
}
func (rde ReaddirEntry) isImage() bool {
return strings.HasSuffix(rde.Name, `.png`) || strings.HasSuffix(rde.Name, `.jpg`) || strings.HasSuffix(rde.Name, `.jpeg`)
}
type MarkdownRequest struct {
Context string
Mode string
Text string
Wiki bool
}
// repos gets a list of Git repositories in this organisation.
func (this *Application) repos() ([]Repo, error) {
resp, err := http.Get(this.cfg.Gitea.URL + `api/v1/orgs/` + url.PathEscape(this.cfg.Gitea.Org) + `/repos`)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("HTTP %d", resp.StatusCode)
}
var repos []Repo
err = json.NewDecoder(resp.Body).Decode(&repos)
if err != nil {
return nil, err
}
return repos, nil
}
// repoFile gets a single file from the default branch of the git repository
// Usually the default branch is `master`.
func (this *Application) repoFile(repo, filename string) ([]byte, error) {
resp, err := http.Get(this.cfg.Gitea.URL + `api/v1/repos/` + url.PathEscape(this.cfg.Gitea.Org) + `/` + url.PathEscape(repo) + `/contents/` + url.PathEscape(filename))
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("HTTP %d", resp.StatusCode)
}
var cr ContentsResponse
err = json.NewDecoder(resp.Body).Decode(&cr)
if err != nil {
return nil, err
}
return cr.Content, nil
}
func (this *Application) filesInDirectory(repo, dir string) ([]ReaddirEntry, error) {
resp, err := http.Get(this.cfg.Gitea.URL + `api/v1/repos/` + url.PathEscape(this.cfg.Gitea.Org) + `/` + url.PathEscape(repo) + `/contents/` + dir) // n.b. $dir param not escaped
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
// "No files found" happens with a HTTP 500/404 error depending on Gitea version. Catch this special case
if resp.StatusCode == 500 || resp.StatusCode == 404 {
b, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
if strings.Contains(string(b), `does not exist`) {
return []ReaddirEntry{}, nil // no files found
}
}
return nil, fmt.Errorf("HTTP %d", resp.StatusCode)
}
var ret []ReaddirEntry
err = json.NewDecoder(resp.Body).Decode(&ret)
if err != nil {
return nil, err
}
return ret, nil
}
// imageFilesForRepo finds documentation images for the repository.
// It searches the dist/ and doc/ subdirectories.
func (this *Application) imageFilesForRepo(repo string) ([]ReaddirEntry, error) {
ret := []ReaddirEntry{}
for _, dirName := range []string{`dist`, `doc`} {
files, err := this.filesInDirectory(repo, dirName)
if err != nil {
return nil, fmt.Errorf("readdir(%s): %w", dirName, err)
}
for _, f := range files {
if f.isImage() {
ret = append(ret, f)
}
}
}
return ret, nil
}
func (this *Application) topicsForRepo(repo string) ([]string, error) {
resp, err := http.Get(this.cfg.Gitea.URL + `api/v1/repos/` + url.PathEscape(this.cfg.Gitea.Org) + `/` + url.PathEscape(repo) + `/topics`)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("HTTP %d", resp.StatusCode)
}
var tr TopicsResponse
err = json.NewDecoder(resp.Body).Decode(&tr)
if err != nil {
return nil, err
}
return tr.Topics, nil
}
// renderMarkdown calls the remote Gitea server's own markdown renderer.
func (this *Application) renderMarkdown(repoName string, body string) ([]byte, error) {
req := MarkdownRequest{
Context: this.cfg.Gitea.URL + url.PathEscape(this.cfg.Gitea.Org) + `/` + url.PathEscape(repoName) + `/src/branch/master`,
Mode: "gfm", // magic constant - Github Flavoured Markdown
Text: body,
}
jb, err := json.Marshal(req)
if err != nil {
return nil, err
}
resp, err := http.Post(this.cfg.Gitea.URL+`api/v1/markdown/`, `application/json`, bytes.NewReader(jb))
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("HTTP %d", resp.StatusCode)
}
return ioutil.ReadAll(resp.Body)
}
// renderMarkdownRaw calls the remote Gitea server's own markdown renderer.
func (this *Application) renderMarkdownRaw(body []byte) ([]byte, error) {
resp, err := http.Post(this.cfg.Gitea.URL+`api/v1/markdown/raw`, `text/plain`, bytes.NewReader(body))
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("HTTP %d", resp.StatusCode)
}
return ioutil.ReadAll(resp.Body)
}