Implement basic API scoping
This commit is contained in:
parent
064ae2f564
commit
9db53d39c4
21 changed files with 519 additions and 264 deletions
|
|
@ -2,12 +2,10 @@ package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/config/validators"
|
"github.com/FAU-CDI/wisski-distillery/internal/config/validators"
|
||||||
"github.com/tkw1536/pkglib/httpx"
|
|
||||||
"golang.org/x/net/idna"
|
"golang.org/x/net/idna"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -31,26 +29,6 @@ type HTTPConfig struct {
|
||||||
API validators.NullableBool `yaml:"api" validate:"bool" default:"false"`
|
API validators.NullableBool `yaml:"api" validate:"bool" default:"false"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var apiNotEnabled = httpx.Response{
|
|
||||||
StatusCode: http.StatusForbidden,
|
|
||||||
Body: []byte(`{"message":"API is not enabled"}`),
|
|
||||||
}
|
|
||||||
|
|
||||||
func (hcfg HTTPConfig) APIDecorator(methods ...string) func(http.Handler) http.Handler {
|
|
||||||
methods = append(methods, "OPTIONS") // always permit the options method!
|
|
||||||
|
|
||||||
if !hcfg.API.Value {
|
|
||||||
return func(http.Handler) http.Handler {
|
|
||||||
return httpx.PermitMethods(apiNotEnabled, methods...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// permit only the specified methods
|
|
||||||
return func(h http.Handler) http.Handler {
|
|
||||||
return httpx.PermitMethods(h, methods...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// JoinPath returns the root public url joined with the provided parts.
|
// JoinPath returns the root public url joined with the provided parts.
|
||||||
func (hcfg HTTPConfig) JoinPath(elem ...string) *url.URL {
|
func (hcfg HTTPConfig) JoinPath(elem ...string) *url.URL {
|
||||||
u := url.URL{
|
u := url.URL{
|
||||||
|
|
|
||||||
175
internal/dis/component/auth/api/handler.go
Normal file
175
internal/dis/component/auth/api/handler.go
Normal file
|
|
@ -0,0 +1,175 @@
|
||||||
|
// Package api implements a common handler used by the api routes
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/config"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/scopes"
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
"github.com/tkw1536/pkglib/httpx"
|
||||||
|
"github.com/tkw1536/pkglib/lazy"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Handler represents an API handler that returns a REST response.
|
||||||
|
// The response is automatically marshaled using T.
|
||||||
|
type Handler[T any] struct {
|
||||||
|
Config *config.Config
|
||||||
|
Auth *auth.Auth // Handler to handle Auth
|
||||||
|
|
||||||
|
Methods []string // HTTP methods to allow
|
||||||
|
methods lazy.Lazy[map[string]struct{}]
|
||||||
|
|
||||||
|
Scope scopes.Scope
|
||||||
|
ScopeParam func(*http.Request) string
|
||||||
|
Handler func(string, *http.Request) (T, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiNotEnabled = &Response{
|
||||||
|
Status: http.StatusNotImplemented,
|
||||||
|
Message: "API is not implemented on this server",
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiMethodNotAllowed = &Response{
|
||||||
|
Status: http.StatusMethodNotAllowed,
|
||||||
|
Message: "method not allowed",
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiInternalServerError = &Response{
|
||||||
|
Status: http.StatusInternalServerError,
|
||||||
|
Message: "internal server error",
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiBadRequest = &Response{
|
||||||
|
Status: http.StatusBadRequest,
|
||||||
|
Message: "bad request",
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiNotFound = &Response{
|
||||||
|
Status: http.StatusNotFound,
|
||||||
|
Message: "not found",
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiForbidden = &Response{
|
||||||
|
Status: http.StatusForbidden,
|
||||||
|
Message: "forbidden",
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServeHTTP servers an api call
|
||||||
|
func (handler *Handler[T]) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// check that the api is actually enabled!
|
||||||
|
if !handler.Config.HTTP.API.Value {
|
||||||
|
apiNotEnabled.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the permitted methods
|
||||||
|
methods := handler.methods.Get(func() map[string]struct{} {
|
||||||
|
m := make(map[string]struct{}, len(handler.Methods)+1)
|
||||||
|
for _, method := range handler.Methods {
|
||||||
|
m[method] = struct{}{}
|
||||||
|
}
|
||||||
|
m["OPTIONS"] = struct{}{}
|
||||||
|
return m
|
||||||
|
})
|
||||||
|
|
||||||
|
// check that the method is permitted
|
||||||
|
if _, ok := methods[r.Method]; !ok {
|
||||||
|
apiMethodNotAllowed.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// we now delegate to user-level code;
|
||||||
|
// so we now need to make sure that panic()s are caught.
|
||||||
|
var stage string
|
||||||
|
defer func() {
|
||||||
|
// recover any error
|
||||||
|
rec := recover()
|
||||||
|
if rec == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// log the error, and serve the default internal server error
|
||||||
|
zerolog.Ctx(r.Context()).Error().Str("panic", fmt.Sprint(rec)).Str("stage", stage).Str("route", r.URL.RequestURI()).Msg("api handler caused panic()")
|
||||||
|
apiInternalServerError.ServeHTTP(w, r)
|
||||||
|
}()
|
||||||
|
|
||||||
|
// read the parameter
|
||||||
|
stage = "param"
|
||||||
|
var param string
|
||||||
|
if handler.ScopeParam != nil {
|
||||||
|
param = handler.ScopeParam(r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that the scope is correct
|
||||||
|
stage = "scope"
|
||||||
|
if err := handler.Auth.CheckScope(param, handler.Scope, r); err != nil {
|
||||||
|
(&Response{
|
||||||
|
Status: http.StatusForbidden,
|
||||||
|
Message: err.Error(),
|
||||||
|
}).ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
stage = "handler"
|
||||||
|
|
||||||
|
result, err := handler.Handler(param, r)
|
||||||
|
switch true {
|
||||||
|
case err == nil: /* keep going */
|
||||||
|
|
||||||
|
// handle common httpx errors
|
||||||
|
case errors.Is(err, httpx.ErrInternalServerError):
|
||||||
|
apiInternalServerError.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
case errors.Is(err, httpx.ErrBadRequest):
|
||||||
|
apiBadRequest.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
case errors.Is(err, httpx.ErrNotFound):
|
||||||
|
apiNotFound.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
case errors.Is(err, httpx.ErrForbidden):
|
||||||
|
apiForbidden.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
case errors.Is(err, httpx.ErrMethodNotAllowed):
|
||||||
|
apiMethodNotAllowed.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
|
||||||
|
// generic error
|
||||||
|
default:
|
||||||
|
(&Response{
|
||||||
|
Status: http.StatusInternalServerError,
|
||||||
|
Message: err.Error(),
|
||||||
|
}).ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
stage = "marshal"
|
||||||
|
|
||||||
|
// encode the result into json and send it as the response
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
json.NewEncoder(w).Encode(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Response represents a generic response to any request.
|
||||||
|
// Response objects cache response serialization
|
||||||
|
type Response struct {
|
||||||
|
Status int `json:"status"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
res lazy.Lazy[httpx.Response]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Response) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
g.res.Get(func() httpx.Response {
|
||||||
|
bytes, _ := json.Marshal(g)
|
||||||
|
return httpx.Response{
|
||||||
|
ContentType: "application/json",
|
||||||
|
Body: bytes,
|
||||||
|
StatusCode: g.Status,
|
||||||
|
}
|
||||||
|
}).ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/policy"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/policy"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/scopes"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/wisski"
|
"github.com/FAU-CDI/wisski-distillery/internal/wisski"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/php/users"
|
"github.com/FAU-CDI/wisski-distillery/internal/wisski/ingredient/php/users"
|
||||||
|
|
@ -30,7 +31,7 @@ var (
|
||||||
func (next *Next) Routes() component.Routes {
|
func (next *Next) Routes() component.Routes {
|
||||||
return component.Routes{
|
return component.Routes{
|
||||||
Prefix: "/next/",
|
Prefix: "/next/",
|
||||||
Decorator: next.Dependencies.Auth.Require(component.ScopeUserLoggedIn, nil),
|
Decorator: next.Dependencies.Auth.Require(scopes.ScopeUserLoggedIn, nil),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/next"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/next"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/policy"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/policy"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/scopes"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/ssh2"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/ssh2"
|
||||||
|
|
@ -39,7 +40,7 @@ func (panel *UserPanel) Routes() component.Routes {
|
||||||
return component.Routes{
|
return component.Routes{
|
||||||
Prefix: "/user/",
|
Prefix: "/user/",
|
||||||
CSRF: true,
|
CSRF: true,
|
||||||
Decorator: panel.Dependencies.Auth.Require(component.ScopeUserLoggedIn, nil),
|
Decorator: panel.Dependencies.Auth.Require(scopes.ScopeUserLoggedIn, nil),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -115,7 +116,7 @@ func (panel *UserPanel) HandleRoute(ctx context.Context, route string) (http.Han
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure that the user is logged in!
|
// ensure that the user is logged in!
|
||||||
return panel.Dependencies.Auth.Protect(router, component.ScopeUserLoggedIn, nil), nil
|
return panel.Dependencies.Auth.Protect(router, scopes.ScopeUserLoggedIn, nil), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type userFormContext struct {
|
type userFormContext struct {
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,13 @@ var (
|
||||||
_ component.ScopeProvider = (*UserLoggedIn)(nil)
|
_ component.ScopeProvider = (*UserLoggedIn)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ScopeAdminLoggedIn Scope = "login.admin"
|
||||||
|
)
|
||||||
|
|
||||||
func (*AdminLoggedIn) Scope() component.ScopeInfo {
|
func (*AdminLoggedIn) Scope() component.ScopeInfo {
|
||||||
return component.ScopeInfo{
|
return component.ScopeInfo{
|
||||||
Scope: component.ScopeAdminLoggedIn,
|
Scope: ScopeAdminLoggedIn,
|
||||||
Description: "session has a signed in admin",
|
Description: "session has a signed in admin",
|
||||||
DeniedMessage: "user must be signed into an admin account with TOTP enabled",
|
DeniedMessage: "user must be signed into an admin account with TOTP enabled",
|
||||||
TakesParam: false,
|
TakesParam: false,
|
||||||
|
|
|
||||||
38
internal/dis/component/auth/scopes/instances.go
Normal file
38
internal/dis/component/auth/scopes/instances.go
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
package scopes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ListInstancesScope struct {
|
||||||
|
component.Base
|
||||||
|
Dependencies struct {
|
||||||
|
Auth *auth.Auth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ component.ScopeProvider = (*ListInstancesScope)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ScopeInstanceDirectory Scope = "instances.directory"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (*ListInstancesScope) Scope() component.ScopeInfo {
|
||||||
|
return component.ScopeInfo{
|
||||||
|
Scope: ScopeInstanceDirectory,
|
||||||
|
Description: "get a public directory of instances",
|
||||||
|
DeniedMessage: "",
|
||||||
|
TakesParam: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lis *ListInstancesScope) HasScope(param string, r *http.Request) (bool, error) {
|
||||||
|
// TODO: at the moment everyone has this permission
|
||||||
|
// this should change in the future!
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
37
internal/dis/component/auth/scopes/news.go
Normal file
37
internal/dis/component/auth/scopes/news.go
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
package scopes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ListNewsScope struct {
|
||||||
|
component.Base
|
||||||
|
Dependencies struct {
|
||||||
|
Auth *auth.Auth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ component.ScopeProvider = (*ListNewsScope)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ScopeListNews Scope = "news.list"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (*ListNewsScope) Scope() component.ScopeInfo {
|
||||||
|
return component.ScopeInfo{
|
||||||
|
Scope: ScopeListNews,
|
||||||
|
Description: "list news items",
|
||||||
|
DeniedMessage: "",
|
||||||
|
TakesParam: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lns *ListNewsScope) HasScope(param string, r *http.Request) (bool, error) {
|
||||||
|
// TODO: at the moment everyone has this permission
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
6
internal/dis/component/auth/scopes/scopes.go
Normal file
6
internal/dis/component/auth/scopes/scopes.go
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
// Package scopes implements and provides scopes used by the API
|
||||||
|
package scopes
|
||||||
|
|
||||||
|
import "github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
||||||
|
|
||||||
|
type Scope = component.Scope
|
||||||
|
|
@ -18,9 +18,13 @@ var (
|
||||||
_ component.ScopeProvider = (*UserLoggedIn)(nil)
|
_ component.ScopeProvider = (*UserLoggedIn)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ScopeUserLoggedIn Scope = "login.user"
|
||||||
|
)
|
||||||
|
|
||||||
func (*UserLoggedIn) Scope() component.ScopeInfo {
|
func (*UserLoggedIn) Scope() component.ScopeInfo {
|
||||||
return component.ScopeInfo{
|
return component.ScopeInfo{
|
||||||
Scope: component.ScopeUserLoggedIn,
|
Scope: ScopeUserLoggedIn,
|
||||||
Description: "session has an associated user",
|
Description: "session has an associated user",
|
||||||
TakesParam: false,
|
TakesParam: false,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/FAU-CDI/wdresolve/resolvers"
|
"github.com/FAU-CDI/wdresolve/resolvers"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/scopes"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/assets"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
|
||||||
|
|
@ -102,7 +103,7 @@ func (resolver *Resolver) HandleRoute(ctx context.Context, route string) (http.H
|
||||||
IndexContext: context,
|
IndexContext: context,
|
||||||
}
|
}
|
||||||
|
|
||||||
if resolver.Dependencies.Auth.CheckScope("", component.ScopeUserLoggedIn, r) != nil {
|
if resolver.Dependencies.Auth.CheckScope("", scopes.ScopeUserLoggedIn, r) != nil {
|
||||||
ctx.IndexContext.Prefixes = nil
|
ctx.IndexContext.Prefixes = nil
|
||||||
}
|
}
|
||||||
httpx.WriteHTML(tpl.Context(r, ctx), nil, t, "", w, r)
|
httpx.WriteHTML(tpl.Context(r, ctx), nil, t, "", w, r)
|
||||||
|
|
|
||||||
|
|
@ -51,11 +51,6 @@ func (scope ScopeInfo) CheckError(err error) error {
|
||||||
return CheckError{Scope: scope.Scope, Err: err}
|
return CheckError{Scope: scope.Scope, Err: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
|
||||||
ScopeUserLoggedIn Scope = "login.user"
|
|
||||||
ScopeAdminLoggedIn Scope = "login.admin"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ScopeProvider is a component that can check a specific scope
|
// ScopeProvider is a component that can check a specific scope
|
||||||
type ScopeProvider interface {
|
type ScopeProvider interface {
|
||||||
Component
|
Component
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/policy"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/policy"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/scopes"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/admin/socket"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/admin/socket"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
|
||||||
"github.com/julienschmidt/httprouter"
|
"github.com/julienschmidt/httprouter"
|
||||||
|
|
@ -46,12 +47,12 @@ func (admin *Admin) Routes() component.Routes {
|
||||||
return component.Routes{
|
return component.Routes{
|
||||||
Prefix: "/admin/",
|
Prefix: "/admin/",
|
||||||
CSRF: true,
|
CSRF: true,
|
||||||
Decorator: admin.Dependencies.Auth.Require(component.ScopeAdminLoggedIn, nil),
|
Decorator: admin.Dependencies.Auth.Require(scopes.ScopeAdminLoggedIn, nil),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (admin *Admin) Menu(r *http.Request) []component.MenuItem {
|
func (admin *Admin) Menu(r *http.Request) []component.MenuItem {
|
||||||
if admin.Dependencies.Auth.CheckScope("", component.ScopeAdminLoggedIn, r) != nil {
|
if admin.Dependencies.Auth.CheckScope("", scopes.ScopeAdminLoggedIn, r) != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return []component.MenuItem{
|
return []component.MenuItem{
|
||||||
|
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
package home
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/status"
|
|
||||||
"github.com/tkw1536/pkglib/httpx"
|
|
||||||
)
|
|
||||||
|
|
||||||
type API struct {
|
|
||||||
component.Base
|
|
||||||
|
|
||||||
Dependencies struct {
|
|
||||||
Home *Home
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ component.Routeable = (*API)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
func (api *API) Routes() component.Routes {
|
|
||||||
return component.Routes{
|
|
||||||
Prefix: "/api/v1/systems",
|
|
||||||
Exact: true,
|
|
||||||
Decorator: api.Config.HTTP.APIDecorator("GET"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type APISystemInfo struct {
|
|
||||||
Slug string
|
|
||||||
URL string
|
|
||||||
Tagline string
|
|
||||||
|
|
||||||
EntityCount int
|
|
||||||
BundleCount int
|
|
||||||
LastEdit time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (api *API) HandleRoute(ctx context.Context, path string) (http.Handler, error) {
|
|
||||||
return httpx.JSON(func(r *http.Request) ([]APISystemInfo, error) {
|
|
||||||
var statuses []status.WissKI
|
|
||||||
if api.Dependencies.Home.ShouldShowList(r) {
|
|
||||||
statuses = api.Dependencies.Home.homeInstances.Get(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(statuses) == 0 {
|
|
||||||
return []APISystemInfo{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
infos := make([]APISystemInfo, len(statuses))
|
|
||||||
for i, status := range statuses {
|
|
||||||
infos[i].Slug = status.Slug
|
|
||||||
infos[i].URL = status.URL
|
|
||||||
infos[i].EntityCount = status.Statistics.Bundles.TotalCount()
|
|
||||||
infos[i].BundleCount = status.Statistics.Bundles.TotalBundles
|
|
||||||
infos[i].LastEdit = status.Statistics.Bundles.LastEdit().Time
|
|
||||||
}
|
|
||||||
return infos, nil
|
|
||||||
}), nil
|
|
||||||
}
|
|
||||||
|
|
@ -6,23 +6,16 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/list"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances"
|
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/status"
|
|
||||||
"github.com/tkw1536/pkglib/lazy"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Home struct {
|
type Home struct {
|
||||||
component.Base
|
component.Base
|
||||||
Dependencies struct {
|
Dependencies struct {
|
||||||
|
ListInstances *list.ListInstances
|
||||||
Templating *templating.Templating
|
Templating *templating.Templating
|
||||||
Instances *instances.Instances
|
|
||||||
Auth *auth.Auth
|
|
||||||
}
|
}
|
||||||
|
|
||||||
instanceNames lazy.Lazy[map[string]struct{}] // instance names
|
|
||||||
homeInstances lazy.Lazy[[]status.WissKI] // list of home instances (updated via cron)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -61,21 +54,8 @@ func (home *Home) HandleRoute(ctx context.Context, route string) (http.Handler,
|
||||||
}), nil
|
}), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (home *Home) instanceMap(ctx context.Context) (map[string]struct{}, error) {
|
|
||||||
wissKIs, err := home.Dependencies.Instances.All(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
names := make(map[string]struct{}, len(wissKIs))
|
|
||||||
for _, w := range wissKIs {
|
|
||||||
names[w.Slug] = struct{}{}
|
|
||||||
}
|
|
||||||
return names, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (home *Home) serveWissKI(w http.ResponseWriter, slug string, r *http.Request) {
|
func (home *Home) serveWissKI(w http.ResponseWriter, slug string, r *http.Request) {
|
||||||
if _, ok := home.instanceNames.Get(nil)[slug]; !ok {
|
if _, ok := home.Dependencies.ListInstances.Names()[slug]; !ok {
|
||||||
// Get(nil) guaranteed to work by precondition
|
// Get(nil) guaranteed to work by precondition
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
fmt.Fprintf(w, "WissKI %q not found\n", slug)
|
fmt.Fprintf(w, "WissKI %q not found\n", slug)
|
||||||
|
|
|
||||||
|
|
@ -1,86 +0,0 @@
|
||||||
package home
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/status"
|
|
||||||
"golang.org/x/sync/errgroup"
|
|
||||||
)
|
|
||||||
|
|
||||||
// loadInstances loads all the instances into the home route
|
|
||||||
func (home *Home) loadInstances(ctx context.Context) ([]status.WissKI, error) {
|
|
||||||
// find all the WissKIs
|
|
||||||
wissKIs, err := home.Dependencies.Instances.All(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
instances := make([]status.WissKI, len(wissKIs))
|
|
||||||
|
|
||||||
// determine their infos
|
|
||||||
var eg errgroup.Group
|
|
||||||
for i, instance := range wissKIs {
|
|
||||||
i := i
|
|
||||||
wissKI := instance
|
|
||||||
eg.Go(func() (err error) {
|
|
||||||
instances[i], err = wissKI.Info().Information(ctx, false)
|
|
||||||
return
|
|
||||||
})
|
|
||||||
}
|
|
||||||
eg.Wait()
|
|
||||||
|
|
||||||
// and return the new instances
|
|
||||||
return instances, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateInstanceList updates the instances list of the home struct
|
|
||||||
type UpdateInstanceList struct {
|
|
||||||
component.Base
|
|
||||||
Dependencies struct {
|
|
||||||
Home *Home
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ component.Cronable = (*UpdateInstanceList)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
func (*UpdateInstanceList) TaskName() string {
|
|
||||||
return "instance list"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ul *UpdateInstanceList) Cron(ctx context.Context) error {
|
|
||||||
names, err := ul.Dependencies.Home.instanceMap(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.Dependencies.Home.instanceNames.Set(names)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type UpdateHome struct {
|
|
||||||
component.Base
|
|
||||||
Dependencies struct {
|
|
||||||
Home *Home
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
_ component.Cronable = (*UpdateHome)(nil)
|
|
||||||
)
|
|
||||||
|
|
||||||
func (ur *UpdateHome) TaskName() string {
|
|
||||||
return "home instances fetch"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ur *UpdateHome) Cron(ctx context.Context) error {
|
|
||||||
instances, err := ur.Dependencies.Home.loadInstances(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ur.Dependencies.Home.homeInstances.Set(instances)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
@ -47,23 +47,6 @@ type publicContext struct {
|
||||||
|
|
||||||
const logoHTML = template.HTML(`<img src="/logo.svg" alt="WissKI Distillery Logo" class="biglogo">`)
|
const logoHTML = template.HTML(`<img src="/logo.svg" alt="WissKI Distillery Logo" class="biglogo">`)
|
||||||
|
|
||||||
// ShouldShowList determines if the given request should show a WissKI list
|
|
||||||
func (home *Home) ShouldShowList(r *http.Request) bool {
|
|
||||||
allowPrivate := home.Config.Home.List.Private.Value
|
|
||||||
allowPublic := home.Config.Home.List.Public.Value
|
|
||||||
|
|
||||||
if allowPrivate == allowPublic {
|
|
||||||
return allowPrivate
|
|
||||||
}
|
|
||||||
|
|
||||||
user, _ := home.Dependencies.Auth.UserOf(r)
|
|
||||||
if user == nil {
|
|
||||||
return allowPublic
|
|
||||||
} else {
|
|
||||||
return allowPrivate
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (home *Home) publicHandler(ctx context.Context) http.Handler {
|
func (home *Home) publicHandler(ctx context.Context) http.Handler {
|
||||||
title := home.Config.Home.Title
|
title := home.Config.Home.Title
|
||||||
|
|
||||||
|
|
@ -89,7 +72,7 @@ func (home *Home) publicHandler(ctx context.Context) http.Handler {
|
||||||
|
|
||||||
// prepare about
|
// prepare about
|
||||||
pc.aboutContext.Logo = logoHTML
|
pc.aboutContext.Logo = logoHTML
|
||||||
pc.aboutContext.Instances = home.homeInstances.Get(nil)
|
pc.aboutContext.Instances = home.Dependencies.ListInstances.Infos()
|
||||||
pc.aboutContext.SelfRedirect = home.Config.Home.SelfRedirect.String()
|
pc.aboutContext.SelfRedirect = home.Config.Home.SelfRedirect.String()
|
||||||
|
|
||||||
// render the about template
|
// render the about template
|
||||||
|
|
@ -102,7 +85,7 @@ func (home *Home) publicHandler(ctx context.Context) http.Handler {
|
||||||
pc.About = template.HTML(builder.String())
|
pc.About = template.HTML(builder.String())
|
||||||
|
|
||||||
// check if we should show the list of WissKIs
|
// check if we should show the list of WissKIs
|
||||||
pc.ListEnabled = home.ShouldShowList(r)
|
pc.ListEnabled = home.Dependencies.ListInstances.ShouldShowList(r)
|
||||||
|
|
||||||
// title of the list
|
// title of the list
|
||||||
pc.ListTitle = home.Config.Home.List.Title
|
pc.ListTitle = home.Config.Home.List.Title
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{range .Instances}}
|
{{range .Instances}}
|
||||||
{{ if and .Running (not .NoPrefixes) }}
|
|
||||||
<div class="pure-u-1 pure-u-md-1-3">
|
<div class="pure-u-1 pure-u-md-1-3">
|
||||||
<h3>{{.Slug}}</h3>
|
<h3>{{.Slug}}</h3>
|
||||||
<p>
|
<p>
|
||||||
|
|
@ -23,5 +22,4 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ end }}
|
|
||||||
{{ end }}
|
{{ end }}
|
||||||
71
internal/dis/component/server/list/api.go
Normal file
71
internal/dis/component/server/list/api.go
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
package list
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/api"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/scopes"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
// API implements an API to list all instances
|
||||||
|
type API struct {
|
||||||
|
component.Base
|
||||||
|
Dependencies struct {
|
||||||
|
ListInstances *ListInstances
|
||||||
|
Auth *auth.Auth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lia *API) Routes() component.Routes {
|
||||||
|
return component.Routes{
|
||||||
|
Prefix: "/api/v1/instances/directory",
|
||||||
|
Exact: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// APISystem represents a system returned by the api
|
||||||
|
type APISystem struct {
|
||||||
|
Slug string
|
||||||
|
URL string
|
||||||
|
Tagline string
|
||||||
|
|
||||||
|
EntityCount int
|
||||||
|
BundleCount int
|
||||||
|
LastEdit time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *API) HandleRoute(ctx context.Context, path string) (http.Handler, error) {
|
||||||
|
return &api.Handler[[]APISystem]{
|
||||||
|
Config: a.Config,
|
||||||
|
Auth: a.Dependencies.Auth,
|
||||||
|
|
||||||
|
Methods: []string{"GET"},
|
||||||
|
Scope: scopes.ScopeInstanceDirectory,
|
||||||
|
|
||||||
|
Handler: func(s string, r *http.Request) ([]APISystem, error) {
|
||||||
|
var statuses []status.WissKI
|
||||||
|
if a.Dependencies.ListInstances.ShouldShowList(r) {
|
||||||
|
statuses = a.Dependencies.ListInstances.infos.Get(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(statuses) == 0 {
|
||||||
|
return []APISystem{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
infos := make([]APISystem, len(statuses))
|
||||||
|
for i, status := range statuses {
|
||||||
|
infos[i].Slug = status.Slug
|
||||||
|
infos[i].URL = status.URL
|
||||||
|
infos[i].EntityCount = status.Statistics.Bundles.TotalCount()
|
||||||
|
infos[i].BundleCount = status.Statistics.Bundles.TotalBundles
|
||||||
|
infos[i].LastEdit = status.Statistics.Bundles.LastEdit().Time
|
||||||
|
}
|
||||||
|
return infos, nil
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
130
internal/dis/component/server/list/list.go
Normal file
130
internal/dis/component/server/list/list.go
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
package list
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/status"
|
||||||
|
"github.com/tkw1536/pkglib/lazy"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ListInstances holds information about all instances
|
||||||
|
type ListInstances struct {
|
||||||
|
component.Base
|
||||||
|
|
||||||
|
names lazy.Lazy[map[string]struct{}] // instance names
|
||||||
|
infos lazy.Lazy[[]status.WissKI] // list of home instances (updated via cron)
|
||||||
|
|
||||||
|
Dependencies struct {
|
||||||
|
Auth *auth.Auth
|
||||||
|
Instances *instances.Instances
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (li *ListInstances) Names() map[string]struct{} {
|
||||||
|
return li.names.Get(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (li *ListInstances) Infos() []status.WissKI {
|
||||||
|
return li.infos.Get(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ShouldShowList determines if a list should be shown for the given request
|
||||||
|
func (li *ListInstances) ShouldShowList(r *http.Request) bool {
|
||||||
|
allowPrivate := li.Config.Home.List.Private.Value
|
||||||
|
allowPublic := li.Config.Home.List.Public.Value
|
||||||
|
|
||||||
|
if allowPrivate == allowPublic {
|
||||||
|
return allowPrivate
|
||||||
|
}
|
||||||
|
|
||||||
|
user, _ := li.Dependencies.Auth.UserOf(r)
|
||||||
|
if user == nil {
|
||||||
|
return allowPublic
|
||||||
|
} else {
|
||||||
|
return allowPrivate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ component.Cronable = (*ListInstances)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
func (li *ListInstances) TaskName() string {
|
||||||
|
return "instance list and status"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (li *ListInstances) Cron(ctx context.Context) (err error) {
|
||||||
|
{
|
||||||
|
names, e := li.getNames(ctx)
|
||||||
|
if err == nil {
|
||||||
|
li.names.Set(names)
|
||||||
|
} else {
|
||||||
|
err = e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
infos, e := li.getInfos(ctx)
|
||||||
|
if err == nil {
|
||||||
|
li.infos.Set(infos)
|
||||||
|
} else {
|
||||||
|
err = e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// getNames returns the names of the given instances
|
||||||
|
func (li *ListInstances) getNames(ctx context.Context) (map[string]struct{}, error) {
|
||||||
|
wissKIs, err := li.Dependencies.Instances.All(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
names := make(map[string]struct{}, len(wissKIs))
|
||||||
|
for _, w := range wissKIs {
|
||||||
|
names[w.Slug] = struct{}{}
|
||||||
|
}
|
||||||
|
return names, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getInfos returns the names of the given instances
|
||||||
|
func (li *ListInstances) getInfos(ctx context.Context) ([]status.WissKI, error) {
|
||||||
|
// find all the WissKIs
|
||||||
|
wissKIs, err := li.Dependencies.Instances.All(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
infos := make([]status.WissKI, len(wissKIs))
|
||||||
|
|
||||||
|
// determine their infos
|
||||||
|
var eg errgroup.Group
|
||||||
|
for i, instance := range wissKIs {
|
||||||
|
i := i
|
||||||
|
wissKI := instance
|
||||||
|
eg.Go(func() (err error) {
|
||||||
|
infos[i], err = wissKI.Info().Information(ctx, false)
|
||||||
|
return
|
||||||
|
})
|
||||||
|
}
|
||||||
|
eg.Wait()
|
||||||
|
|
||||||
|
// filter them by those that are running and do not have prefixes excluded
|
||||||
|
infosF := infos[:0]
|
||||||
|
for _, info := range infos {
|
||||||
|
if info.NoPrefixes || !info.Running {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
infosF = append(infosF, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
// and return them
|
||||||
|
return infos[:len(infosF):len(infosF)], nil
|
||||||
|
}
|
||||||
|
|
@ -2,15 +2,19 @@ package news
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
||||||
"github.com/tkw1536/pkglib/httpx"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/api"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/scopes"
|
||||||
)
|
)
|
||||||
|
|
||||||
type API struct {
|
type API struct {
|
||||||
component.Base
|
component.Base
|
||||||
|
Dependencies struct {
|
||||||
|
Auth *auth.Auth
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -21,23 +25,19 @@ func (api *API) Routes() component.Routes {
|
||||||
return component.Routes{
|
return component.Routes{
|
||||||
Prefix: "/api/v1/news/",
|
Prefix: "/api/v1/news/",
|
||||||
Exact: true,
|
Exact: true,
|
||||||
Decorator: api.Config.HTTP.APIDecorator("GET"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) HandleRoute(ctx context.Context, path string) (http.Handler, error) {
|
func (a *API) HandleRoute(ctx context.Context, path string) (http.Handler, error) {
|
||||||
items, err := Items()
|
return &api.Handler[[]Item]{
|
||||||
if err != nil {
|
Config: a.Config,
|
||||||
return nil, err
|
Auth: a.Dependencies.Auth,
|
||||||
}
|
|
||||||
|
|
||||||
data, err := json.Marshal(items)
|
Methods: []string{"GET"},
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return httpx.Response{
|
Scope: scopes.ScopeListNews,
|
||||||
ContentType: "application/json",
|
Handler: func(s string, r *http.Request) ([]Item, error) {
|
||||||
Body: data,
|
return Items()
|
||||||
|
},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ import (
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/cron"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/cron"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/home"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/home"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/legal"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/legal"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/list"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/logo"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/logo"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/news"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/news"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/server/templating"
|
||||||
|
|
@ -148,6 +149,8 @@ func (dis *Distillery) allComponents() []initFunc {
|
||||||
//scopes
|
//scopes
|
||||||
auto[*scopes.UserLoggedIn],
|
auto[*scopes.UserLoggedIn],
|
||||||
auto[*scopes.AdminLoggedIn],
|
auto[*scopes.AdminLoggedIn],
|
||||||
|
auto[*scopes.ListInstancesScope],
|
||||||
|
auto[*scopes.ListNewsScope],
|
||||||
|
|
||||||
// instances
|
// instances
|
||||||
auto[*instances.Instances],
|
auto[*instances.Instances],
|
||||||
|
|
@ -174,6 +177,7 @@ func (dis *Distillery) allComponents() []initFunc {
|
||||||
auto[*server.Server],
|
auto[*server.Server],
|
||||||
|
|
||||||
auto[*home.Home],
|
auto[*home.Home],
|
||||||
|
auto[*list.ListInstances],
|
||||||
manual(func(resolver *resolver.Resolver) {
|
manual(func(resolver *resolver.Resolver) {
|
||||||
resolver.RefreshInterval = time.Minute
|
resolver.RefreshInterval = time.Minute
|
||||||
}),
|
}),
|
||||||
|
|
@ -190,11 +194,9 @@ func (dis *Distillery) allComponents() []initFunc {
|
||||||
|
|
||||||
// Cron
|
// Cron
|
||||||
auto[*cron.Cron],
|
auto[*cron.Cron],
|
||||||
auto[*home.UpdateHome],
|
|
||||||
auto[*home.UpdateInstanceList],
|
|
||||||
|
|
||||||
// API
|
// API
|
||||||
auto[*home.API],
|
auto[*list.API],
|
||||||
auto[*news.API],
|
auto[*news.API],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue