3 Commits

3 changed files with 37 additions and 2 deletions

View File

@@ -31,6 +31,10 @@ dokku storage:mount teafolio /srv/teafolio-dokku/config.toml:/app/config.toml
## CHANGELOG ## CHANGELOG
2024-03-19 v1.4.1
- Add cooldown for failed markdown API calls
- Fix missing extra html elements when using fallback markdown renderer
2024-03-19 v1.4.0 2024-03-19 v1.4.0
- Support using application token for Gitea API - Support using application token for Gitea API
- Add fallback to internal markdown renderer if Gitea's API is unavailable - Add fallback to internal markdown renderer if Gitea's API is unavailable

View File

@@ -4,21 +4,27 @@ import (
"bytes" "bytes"
"context" "context"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"strings" "strings"
"sync/atomic"
"time" "time"
"golang.org/x/sync/semaphore" "golang.org/x/sync/semaphore"
) )
const MarkdownFailureCooldownSeconds = 3600 // 1 hour
type APIClient struct { type APIClient struct {
urlBase string urlBase string
orgName string orgName string
token string token string
apiSem *semaphore.Weighted apiSem *semaphore.Weighted
lastMarkdownFailure atomic.Int64
} }
// NewAPIClient creates a new Gitea API client for a single Gitea organization. // NewAPIClient creates a new Gitea API client for a single Gitea organization.
@@ -297,8 +303,22 @@ func (ac *APIClient) topicsForRepo(ctx context.Context, repo string) ([]string,
return tr.Topics, nil return tr.Topics, nil
} }
var ErrMarkdownCooldown = errors.New("Markdown API was recently not functional")
func (ac *APIClient) checkMarkdownCooldown() error {
if ac.lastMarkdownFailure.Load()+MarkdownFailureCooldownSeconds > time.Now().Unix() {
return ErrMarkdownCooldown
}
return nil
}
// renderMarkdown calls the remote Gitea server's own markdown renderer. // renderMarkdown calls the remote Gitea server's own markdown renderer.
func (ac *APIClient) RenderMarkdown(ctx context.Context, repoName string, body string) ([]byte, error) { func (ac *APIClient) RenderMarkdown(ctx context.Context, repoName string, body string) ([]byte, error) {
if err := ac.checkMarkdownCooldown(); err != nil {
return nil, err
}
err := ac.apiSem.Acquire(ctx, 1) err := ac.apiSem.Acquire(ctx, 1)
if err != nil { if err != nil {
return nil, err // e.g. ctx closed return nil, err // e.g. ctx closed
@@ -328,6 +348,7 @@ func (ac *APIClient) RenderMarkdown(ctx context.Context, repoName string, body s
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode != 200 { if resp.StatusCode != 200 {
ac.lastMarkdownFailure.Store(time.Now().Unix())
return nil, fmt.Errorf("HTTP %d", resp.StatusCode) return nil, fmt.Errorf("HTTP %d", resp.StatusCode)
} }
@@ -336,6 +357,10 @@ func (ac *APIClient) RenderMarkdown(ctx context.Context, repoName string, body s
// renderMarkdownRaw calls the remote Gitea server's own markdown renderer. // renderMarkdownRaw calls the remote Gitea server's own markdown renderer.
func (ac *APIClient) renderMarkdownRaw(ctx context.Context, body []byte) ([]byte, error) { func (ac *APIClient) renderMarkdownRaw(ctx context.Context, body []byte) ([]byte, error) {
if err := ac.checkMarkdownCooldown(); err != nil {
return nil, err
}
err := ac.apiSem.Acquire(ctx, 1) err := ac.apiSem.Acquire(ctx, 1)
if err != nil { if err != nil {
return nil, err // e.g. ctx closed return nil, err // e.g. ctx closed
@@ -356,6 +381,7 @@ func (ac *APIClient) renderMarkdownRaw(ctx context.Context, body []byte) ([]byte
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode != 200 { if resp.StatusCode != 200 {
ac.lastMarkdownFailure.Store(time.Now().Unix())
return nil, fmt.Errorf("HTTP %d", resp.StatusCode) return nil, fmt.Errorf("HTTP %d", resp.StatusCode)
} }

View File

@@ -2,6 +2,7 @@ package main
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"html" "html"
"log" "log"
@@ -9,6 +10,8 @@ import (
"net/url" "net/url"
"strings" "strings"
"teafolio/gitea"
"github.com/yuin/goldmark" "github.com/yuin/goldmark"
) )
@@ -59,7 +62,9 @@ func (this *Application) Repopage(w http.ResponseWriter, r *http.Request, repoNa
readmeHtml, err := this.gitea.RenderMarkdown(ctx, repoName, strings.Join(lines, "\n")) readmeHtml, err := this.gitea.RenderMarkdown(ctx, repoName, strings.Join(lines, "\n"))
if err != nil { if err != nil {
if !errors.Is(err, gitea.ErrMarkdownCooldown) {
log.Printf("%s %s: %s", r.Method, r.URL.Path, fmt.Errorf("rendering markdown: %w", err)) log.Printf("%s %s: %s", r.Method, r.URL.Path, fmt.Errorf("rendering markdown: %w", err))
}
// Failed to use Gitea's markdown renderer // Failed to use Gitea's markdown renderer
// HTTP 401 (Unauthorized) started happening with this API sometime // HTTP 401 (Unauthorized) started happening with this API sometime
@@ -67,7 +72,7 @@ func (this *Application) Repopage(w http.ResponseWriter, r *http.Request, repoNa
// sufficient to make it work again // sufficient to make it work again
// Use our own one instead as a fallback // Use our own one instead as a fallback
buff := bytes.Buffer{} buff := bytes.Buffer{}
err = goldmark.Convert(readme, &buff) err = goldmark.Convert([]byte(strings.Join(lines, "\n")), &buff)
if err != nil { if err != nil {
// Built-in markdown renderer didn't work either // Built-in markdown renderer didn't work either