Restructure & refactor codebase
This commit is contained in:
@@ -0,0 +1,156 @@
|
||||
package glance
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html"
|
||||
"html/template"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
var extensionWidgetTemplate = mustParseTemplate("extension.html", "widget-base.html")
|
||||
|
||||
type extensionWidget struct {
|
||||
widgetBase `yaml:",inline"`
|
||||
URL string `yaml:"url"`
|
||||
FallbackContentType string `yaml:"fallback-content-type"`
|
||||
Parameters map[string]string `yaml:"parameters"`
|
||||
AllowHtml bool `yaml:"allow-potentially-dangerous-html"`
|
||||
Extension extension `yaml:"-"`
|
||||
cachedHTML template.HTML `yaml:"-"`
|
||||
}
|
||||
|
||||
func (widget *extensionWidget) initialize() error {
|
||||
widget.withTitle("Extension").withCacheDuration(time.Minute * 30)
|
||||
|
||||
if widget.URL == "" {
|
||||
return errors.New("URL is required")
|
||||
}
|
||||
|
||||
_, err := url.Parse(widget.URL)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (widget *extensionWidget) update(ctx context.Context) {
|
||||
extension, err := fetchExtension(extensionRequestOptions{
|
||||
URL: widget.URL,
|
||||
FallbackContentType: widget.FallbackContentType,
|
||||
Parameters: widget.Parameters,
|
||||
AllowHtml: widget.AllowHtml,
|
||||
})
|
||||
|
||||
widget.canContinueUpdateAfterHandlingErr(err)
|
||||
|
||||
widget.Extension = extension
|
||||
|
||||
if extension.Title != "" {
|
||||
widget.Title = extension.Title
|
||||
}
|
||||
|
||||
widget.cachedHTML = widget.renderTemplate(widget, extensionWidgetTemplate)
|
||||
}
|
||||
|
||||
func (widget *extensionWidget) Render() template.HTML {
|
||||
return widget.cachedHTML
|
||||
}
|
||||
|
||||
type extensionType int
|
||||
|
||||
const (
|
||||
extensionContentHTML extensionType = iota
|
||||
extensionContentUnknown = iota
|
||||
)
|
||||
|
||||
var extensionStringToType = map[string]extensionType{
|
||||
"html": extensionContentHTML,
|
||||
}
|
||||
|
||||
const (
|
||||
extensionHeaderTitle = "Widget-Title"
|
||||
extensionHeaderContentType = "Widget-Content-Type"
|
||||
)
|
||||
|
||||
type extensionRequestOptions struct {
|
||||
URL string `yaml:"url"`
|
||||
FallbackContentType string `yaml:"fallback-content-type"`
|
||||
Parameters map[string]string `yaml:"parameters"`
|
||||
AllowHtml bool `yaml:"allow-potentially-dangerous-html"`
|
||||
}
|
||||
|
||||
type extension struct {
|
||||
Title string
|
||||
Content template.HTML
|
||||
}
|
||||
|
||||
func convertExtensionContent(options extensionRequestOptions, content []byte, contentType extensionType) template.HTML {
|
||||
switch contentType {
|
||||
case extensionContentHTML:
|
||||
if options.AllowHtml {
|
||||
return template.HTML(content)
|
||||
}
|
||||
|
||||
fallthrough
|
||||
default:
|
||||
return template.HTML(html.EscapeString(string(content)))
|
||||
}
|
||||
}
|
||||
|
||||
func fetchExtension(options extensionRequestOptions) (extension, error) {
|
||||
request, _ := http.NewRequest("GET", options.URL, nil)
|
||||
|
||||
query := url.Values{}
|
||||
|
||||
for key, value := range options.Parameters {
|
||||
query.Set(key, value)
|
||||
}
|
||||
|
||||
request.URL.RawQuery = query.Encode()
|
||||
|
||||
response, err := http.DefaultClient.Do(request)
|
||||
|
||||
if err != nil {
|
||||
slog.Error("Failed fetching extension", "url", options.URL, "error", err)
|
||||
return extension{}, fmt.Errorf("%w: request failed: %w", errNoContent, err)
|
||||
}
|
||||
|
||||
defer response.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(response.Body)
|
||||
|
||||
if err != nil {
|
||||
slog.Error("Failed reading response body of extension", "url", options.URL, "error", err)
|
||||
return extension{}, fmt.Errorf("%w: could not read body: %w", errNoContent, err)
|
||||
}
|
||||
|
||||
extension := extension{}
|
||||
|
||||
if response.Header.Get(extensionHeaderTitle) == "" {
|
||||
extension.Title = "Extension"
|
||||
} else {
|
||||
extension.Title = response.Header.Get(extensionHeaderTitle)
|
||||
}
|
||||
|
||||
contentType, ok := extensionStringToType[response.Header.Get(extensionHeaderContentType)]
|
||||
|
||||
if !ok {
|
||||
contentType, ok = extensionStringToType[options.FallbackContentType]
|
||||
|
||||
if !ok {
|
||||
contentType = extensionContentUnknown
|
||||
}
|
||||
}
|
||||
|
||||
extension.Content = convertExtensionContent(options, body, contentType)
|
||||
|
||||
return extension, nil
|
||||
}
|
||||
Reference in New Issue
Block a user