Files
godoxy-yusing/internal/api/v1/favicon.go
Yuzerion 31b4fedf72 refactor(entrypoint): move route registry into entrypoint context (#200)
- Introduced `NewTestRoute` function to simplify route creation in benchmark tests.
- Replaced direct route validation and starting with error handling using `require.NoError`.
- Updated server retrieval to use `common.ProxyHTTPAddr` for consistency.
- Improved logging for HTTP route addition errors in `AddRoute` method.

* fix(tcp): wrap proxy proto listener before acl

* refactor(entrypoint): propagate errors from route registration and stream serving

* fix(docs): correct swagger and package README
2026-02-08 09:17:46 +08:00

111 lines
3.5 KiB
Go

package v1
import (
"context"
"net/http"
"github.com/gin-gonic/gin"
entrypoint "github.com/yusing/godoxy/internal/entrypoint/types"
"github.com/yusing/godoxy/internal/homepage/icons"
iconfetch "github.com/yusing/godoxy/internal/homepage/icons/fetch"
apitypes "github.com/yusing/goutils/apitypes"
_ "unsafe"
)
type GetFavIconRequest struct {
URL string `form:"url" binding:"required_without=Alias"`
Alias string `form:"alias" binding:"required_without=URL"`
Variant icons.Variant `form:"variant" binding:"omitempty,oneof=light dark"`
} // @name GetFavIconRequest
// @x-id "favicon"
// @BasePath /api/v1
// @Summary Get favicon
// @Description Get favicon
// @Tags v1
// @Accept json
// @Produce image/svg+xml,image/x-icon,image/png,image/webp
// @Param url query string false "URL of the route"
// @Param alias query string false "Alias of the route"
// @Success 200 {array} iconfetch.Result
// @Failure 400 {object} apitypes.ErrorResponse "Bad Request: alias is empty or route is not HTTPRoute"
// @Failure 403 {object} apitypes.ErrorResponse "Forbidden: unauthorized"
// @Failure 404 {object} apitypes.ErrorResponse "Not Found: route or icon not found"
// @Failure 500 {object} apitypes.ErrorResponse "Internal Server Error: internal error"
// @Router /favicon [get]
func FavIcon(c *gin.Context) {
var request GetFavIconRequest
if err := c.ShouldBindQuery(&request); err != nil {
c.JSON(http.StatusBadRequest, apitypes.Error("invalid request", err))
return
}
// try with url
if request.URL != "" {
var iconURL icons.URL
if err := iconURL.Parse(request.URL); err != nil {
c.JSON(http.StatusBadRequest, apitypes.Error("invalid url", err))
return
}
icon := &iconURL
if request.Variant != icons.VariantNone {
icon = icon.WithVariant(request.Variant)
}
fetchResult, err := iconfetch.FetchFavIconFromURL(c.Request.Context(), icon)
if err != nil {
iconfetch.GinError(c, fetchResult.StatusCode, err)
return
}
c.Data(fetchResult.StatusCode, fetchResult.ContentType(), fetchResult.Icon)
return
}
// try with alias
result, err := GetFavIconFromAlias(c.Request.Context(), request.Alias, request.Variant)
if err != nil {
iconfetch.GinError(c, result.StatusCode, err)
return
}
c.Data(result.StatusCode, result.ContentType(), result.Icon)
}
//go:linkname GetFavIconFromAlias v1.GetFavIconFromAlias
func GetFavIconFromAlias(ctx context.Context, alias string, variant icons.Variant) (iconfetch.Result, error) {
// try with route.Icon
ep := entrypoint.FromCtx(ctx)
if ep == nil { // impossible, but just in case
return iconfetch.FetchResultWithErrorf(http.StatusInternalServerError, "entrypoint not initialized")
}
r, ok := ep.HTTPRoutes().Get(alias)
if !ok {
return iconfetch.FetchResultWithErrorf(http.StatusNotFound, "route not found")
}
var (
result iconfetch.Result
err error
)
hp := r.HomepageItem()
if hp.Icon != nil {
if hp.Icon.Source == icons.SourceRelative {
result, err = iconfetch.FindIcon(ctx, r, *hp.Icon.FullURL, variant)
} else if variant != icons.VariantNone {
result, err = iconfetch.FetchFavIconFromURL(ctx, hp.Icon.WithVariant(variant))
if err != nil {
// fallback to no variant
result, err = iconfetch.FetchFavIconFromURL(ctx, hp.Icon.WithVariant(icons.VariantNone))
}
} else {
result, err = iconfetch.FetchFavIconFromURL(ctx, hp.Icon)
}
} else {
// try extract from "link[rel=icon]"
result, err = iconfetch.FindIcon(ctx, r, "/", variant)
}
if result.StatusCode == 0 {
result.StatusCode = http.StatusOK
}
return result, err
}