250 lines
8.8 KiB
Go
250 lines
8.8 KiB
Go
package requests
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"fmt"
|
||
|
|
"strconv"
|
||
|
|
|
||
|
|
pluginv1 "github.com/Silo-Server/silo-plugin-sdk/pkg/pluginproto/silo/plugin/v1"
|
||
|
|
"google.golang.org/protobuf/types/known/structpb"
|
||
|
|
)
|
||
|
|
|
||
|
|
// RequestRouterProvider fulfills a whole request and checks target status via a
|
||
|
|
// request_router.v1 plugin. The host owns governance (which qualities) and the
|
||
|
|
// target records; the provider is the plugin boundary.
|
||
|
|
type RequestRouterProvider interface {
|
||
|
|
Fulfill(ctx context.Context, installationID int, capabilityID string, req Request, qualities []Quality, conns []ResolvedRouterConnection) ([]RouterTarget, string, error)
|
||
|
|
CheckStatus(ctx context.Context, installationID int, capabilityID string, req Request, targets []RouterTargetRef, conns []ResolvedRouterConnection) ([]RouterTargetStatus, error)
|
||
|
|
ListConfigOptions(ctx context.Context, installationID int, capabilityID string, conn ResolvedRouterConnection) (map[string][]RouterOption, error)
|
||
|
|
TestConnection(ctx context.Context, installationID int, capabilityID string, conn ResolvedRouterConnection) (bool, string, error)
|
||
|
|
Validate(ctx context.Context, installationID int, capabilityID string, conn ResolvedRouterConnection, siblings []ResolvedRouterConnection) (fieldErrors map[string]string, formError string, err error)
|
||
|
|
}
|
||
|
|
|
||
|
|
// ResolvedRouterConnection is a connection with plaintext credentials + parsed config.
|
||
|
|
type ResolvedRouterConnection struct {
|
||
|
|
ID string
|
||
|
|
BaseURL string
|
||
|
|
APIKey string
|
||
|
|
Config map[string]any
|
||
|
|
}
|
||
|
|
|
||
|
|
type RouterTarget struct {
|
||
|
|
Quality Quality
|
||
|
|
ConnectionID string
|
||
|
|
ExternalID string
|
||
|
|
ExternalStatus string
|
||
|
|
Status Status
|
||
|
|
Message string
|
||
|
|
}
|
||
|
|
|
||
|
|
type RouterTargetRef struct {
|
||
|
|
Quality Quality
|
||
|
|
ConnectionID string
|
||
|
|
ExternalID string
|
||
|
|
}
|
||
|
|
|
||
|
|
type RouterTargetStatus struct {
|
||
|
|
Quality Quality
|
||
|
|
ConnectionID string
|
||
|
|
Status Status
|
||
|
|
ExternalStatus string
|
||
|
|
Message string
|
||
|
|
}
|
||
|
|
|
||
|
|
type RouterOption struct {
|
||
|
|
Value string `json:"value"`
|
||
|
|
Label string `json:"label"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// RouterClientResolver yields a per-(installation, capability) router client.
|
||
|
|
type RouterClientResolver interface {
|
||
|
|
RequestRouterClient(ctx context.Context, installationID int, capabilityID string) (RouterClient, error)
|
||
|
|
}
|
||
|
|
|
||
|
|
// RouterClient mirrors *pluginhost.RequestRouterClient (exported so the api
|
||
|
|
// package can adapt the concrete type across packages without an import cycle).
|
||
|
|
type RouterClient interface {
|
||
|
|
Fulfill(ctx context.Context, req *pluginv1.FulfillRequest) (*pluginv1.FulfillResponse, error)
|
||
|
|
CheckStatus(ctx context.Context, req *pluginv1.CheckStatusRequest) (*pluginv1.CheckStatusResponse, error)
|
||
|
|
ListConfigOptions(ctx context.Context, req *pluginv1.ListConfigOptionsRequest) (*pluginv1.ListConfigOptionsResponse, error)
|
||
|
|
TestConnection(ctx context.Context, req *pluginv1.TestConnectionRequest) (*pluginv1.TestConnectionResponse, error)
|
||
|
|
Validate(ctx context.Context, req *pluginv1.ValidateRequest) (*pluginv1.ValidateResponse, error)
|
||
|
|
}
|
||
|
|
|
||
|
|
type pluginRouterProvider struct{ resolver RouterClientResolver }
|
||
|
|
|
||
|
|
func NewPluginRouterProvider(r RouterClientResolver) RequestRouterProvider {
|
||
|
|
return &pluginRouterProvider{resolver: r}
|
||
|
|
}
|
||
|
|
|
||
|
|
func routerDescriptor(req Request) *pluginv1.RequestDescriptor {
|
||
|
|
ids := map[string]string{}
|
||
|
|
if req.TMDBID != 0 {
|
||
|
|
ids["tmdb"] = strconv.Itoa(req.TMDBID)
|
||
|
|
}
|
||
|
|
if req.TVDBID != nil {
|
||
|
|
ids["tvdb"] = strconv.Itoa(*req.TVDBID)
|
||
|
|
}
|
||
|
|
if req.IMDbID != "" {
|
||
|
|
ids["imdb"] = req.IMDbID
|
||
|
|
}
|
||
|
|
year := 0
|
||
|
|
if req.Year != nil {
|
||
|
|
year = *req.Year
|
||
|
|
}
|
||
|
|
return &pluginv1.RequestDescriptor{
|
||
|
|
MediaType: string(req.MediaType),
|
||
|
|
Title: req.Title,
|
||
|
|
Year: int32(year),
|
||
|
|
ExternalIds: ids,
|
||
|
|
IsAnime: req.IsAnime,
|
||
|
|
RequesterUserId: int64(req.RequestedByUserID),
|
||
|
|
RequesterProfileId: req.RequestedByProfileID,
|
||
|
|
RequesterEmail: req.RequesterEmail,
|
||
|
|
RequesterUsername: req.RequesterUsername,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func routerProtoConn(c ResolvedRouterConnection) (*pluginv1.RouterConnection, error) {
|
||
|
|
cfg, err := structpb.NewStruct(c.Config)
|
||
|
|
if err != nil {
|
||
|
|
return nil, fmt.Errorf("router: encode connection %s config: %w", c.ID, err)
|
||
|
|
}
|
||
|
|
return &pluginv1.RouterConnection{Id: c.ID, BaseUrl: c.BaseURL, ApiKey: c.APIKey, Config: cfg}, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func routerProtoConns(conns []ResolvedRouterConnection) ([]*pluginv1.RouterConnection, error) {
|
||
|
|
out := make([]*pluginv1.RouterConnection, 0, len(conns))
|
||
|
|
for _, c := range conns {
|
||
|
|
pc, err := routerProtoConn(c)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
out = append(out, pc)
|
||
|
|
}
|
||
|
|
return out, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (p *pluginRouterProvider) Fulfill(ctx context.Context, installationID int, capabilityID string, req Request, qualities []Quality, conns []ResolvedRouterConnection) ([]RouterTarget, string, error) {
|
||
|
|
client, err := p.resolver.RequestRouterClient(ctx, installationID, capabilityID)
|
||
|
|
if err != nil {
|
||
|
|
return nil, "", err
|
||
|
|
}
|
||
|
|
qs := make([]*pluginv1.RequestedQuality, 0, len(qualities))
|
||
|
|
for _, q := range qualities {
|
||
|
|
qs = append(qs, &pluginv1.RequestedQuality{Id: string(q), Is4K: q == Quality2160p})
|
||
|
|
}
|
||
|
|
pconns, err := routerProtoConns(conns)
|
||
|
|
if err != nil {
|
||
|
|
return nil, "", err
|
||
|
|
}
|
||
|
|
resp, err := client.Fulfill(ctx, &pluginv1.FulfillRequest{
|
||
|
|
CapabilityId: capabilityID, Request: routerDescriptor(req), Qualities: qs, Connections: pconns,
|
||
|
|
})
|
||
|
|
if err != nil {
|
||
|
|
return nil, "", err
|
||
|
|
}
|
||
|
|
targets := make([]RouterTarget, 0, len(resp.GetTargets()))
|
||
|
|
for _, t := range resp.GetTargets() {
|
||
|
|
targets = append(targets, RouterTarget{
|
||
|
|
Quality: Quality(t.GetQuality()), ConnectionID: t.GetConnectionId(),
|
||
|
|
ExternalID: t.GetExternalId(), ExternalStatus: t.GetExternalStatus(),
|
||
|
|
Status: Status(t.GetStatus()), Message: t.GetMessage(),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
return targets, resp.GetMessage(), nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (p *pluginRouterProvider) CheckStatus(ctx context.Context, installationID int, capabilityID string, req Request, targets []RouterTargetRef, conns []ResolvedRouterConnection) ([]RouterTargetStatus, error) {
|
||
|
|
client, err := p.resolver.RequestRouterClient(ctx, installationID, capabilityID)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
refs := make([]*pluginv1.TargetRef, 0, len(targets))
|
||
|
|
for _, t := range targets {
|
||
|
|
refs = append(refs, &pluginv1.TargetRef{Quality: string(t.Quality), ConnectionId: t.ConnectionID, ExternalId: t.ExternalID})
|
||
|
|
}
|
||
|
|
pconns, err := routerProtoConns(conns)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
resp, err := client.CheckStatus(ctx, &pluginv1.CheckStatusRequest{
|
||
|
|
CapabilityId: capabilityID, Request: routerDescriptor(req), Targets: refs, Connections: pconns,
|
||
|
|
})
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
out := make([]RouterTargetStatus, 0, len(resp.GetStatuses()))
|
||
|
|
for _, st := range resp.GetStatuses() {
|
||
|
|
out = append(out, RouterTargetStatus{
|
||
|
|
Quality: Quality(st.GetQuality()), ConnectionID: st.GetConnectionId(),
|
||
|
|
Status: Status(st.GetStatus()), ExternalStatus: st.GetExternalStatus(), Message: st.GetMessage(),
|
||
|
|
})
|
||
|
|
}
|
||
|
|
return out, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (p *pluginRouterProvider) ListConfigOptions(ctx context.Context, installationID int, capabilityID string, conn ResolvedRouterConnection) (map[string][]RouterOption, error) {
|
||
|
|
client, err := p.resolver.RequestRouterClient(ctx, installationID, capabilityID)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
pconn, err := routerProtoConn(conn)
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
resp, err := client.ListConfigOptions(ctx, &pluginv1.ListConfigOptionsRequest{
|
||
|
|
CapabilityId: capabilityID, Connection: pconn,
|
||
|
|
})
|
||
|
|
if err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
out := map[string][]RouterOption{}
|
||
|
|
for field, list := range resp.GetOptionsByField() {
|
||
|
|
opts := make([]RouterOption, 0, len(list.GetOptions()))
|
||
|
|
for _, o := range list.GetOptions() {
|
||
|
|
opts = append(opts, RouterOption{Value: o.GetValue(), Label: o.GetLabel()})
|
||
|
|
}
|
||
|
|
out[field] = opts
|
||
|
|
}
|
||
|
|
return out, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (p *pluginRouterProvider) TestConnection(ctx context.Context, installationID int, capabilityID string, conn ResolvedRouterConnection) (bool, string, error) {
|
||
|
|
client, err := p.resolver.RequestRouterClient(ctx, installationID, capabilityID)
|
||
|
|
if err != nil {
|
||
|
|
return false, "", err
|
||
|
|
}
|
||
|
|
pconn, err := routerProtoConn(conn)
|
||
|
|
if err != nil {
|
||
|
|
return false, "", err
|
||
|
|
}
|
||
|
|
resp, err := client.TestConnection(ctx, &pluginv1.TestConnectionRequest{
|
||
|
|
CapabilityId: capabilityID, Connection: pconn,
|
||
|
|
})
|
||
|
|
if err != nil {
|
||
|
|
return false, "", err
|
||
|
|
}
|
||
|
|
return resp.GetOk(), resp.GetMessage(), nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (p *pluginRouterProvider) Validate(ctx context.Context, installationID int, capabilityID string, conn ResolvedRouterConnection, siblings []ResolvedRouterConnection) (map[string]string, string, error) {
|
||
|
|
client, err := p.resolver.RequestRouterClient(ctx, installationID, capabilityID)
|
||
|
|
if err != nil {
|
||
|
|
return nil, "", err
|
||
|
|
}
|
||
|
|
pc, err := routerProtoConn(conn)
|
||
|
|
if err != nil {
|
||
|
|
return nil, "", err
|
||
|
|
}
|
||
|
|
sibs, err := routerProtoConns(siblings)
|
||
|
|
if err != nil {
|
||
|
|
return nil, "", err
|
||
|
|
}
|
||
|
|
resp, err := client.Validate(ctx, &pluginv1.ValidateRequest{CapabilityId: capabilityID, Connection: pc, Siblings: sibs})
|
||
|
|
if err != nil {
|
||
|
|
return nil, "", err
|
||
|
|
}
|
||
|
|
return resp.GetFieldErrors(), resp.GetFormError(), nil
|
||
|
|
}
|