mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-06-13 10:34:43 -04:00
enhancement: make collaboration font management functionality public
This commit is contained in:
committed by
Benedikt Kulmann
parent
283580d2a4
commit
159785a3b5
8
.gitignore
vendored
8
.gitignore
vendored
@@ -65,4 +65,10 @@ go.work.sum
|
||||
.DS_Store
|
||||
|
||||
# example deployments
|
||||
**/opencloud-sandbox-*
|
||||
**/opencloud-sandbox-*
|
||||
|
||||
# web apps
|
||||
!./services/web/assets/
|
||||
!./services/web/assets/apps/
|
||||
!./services/web/assets/apps/collaboration-settings
|
||||
!./services/web/assets/apps/collaboration-settings/**
|
||||
|
||||
39
services/collaboration/pkg/collaboration/collaboration.go
Normal file
39
services/collaboration/pkg/collaboration/collaboration.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package collaboration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
permissionsapi "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1"
|
||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
revactx "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
||||
)
|
||||
|
||||
type Permission string
|
||||
|
||||
const (
|
||||
PermissionCollaborationManageFonts Permission = "Collaboration.Fonts.Manage"
|
||||
)
|
||||
|
||||
func CheckPermissions(gatewayClient gateway.GatewayAPIClient, ctx context.Context, permission Permission) (*userpb.User, bool, error) {
|
||||
user, ok := revactx.ContextGetUser(ctx)
|
||||
if !ok {
|
||||
return nil, false, fmt.Errorf("could not get user from context")
|
||||
}
|
||||
|
||||
rsp, err := gatewayClient.CheckPermission(ctx, &permissionsapi.CheckPermissionRequest{
|
||||
Permission: string(permission),
|
||||
SubjectRef: &permissionsapi.SubjectReference{
|
||||
Spec: &permissionsapi.SubjectReference_UserId{
|
||||
UserId: user.GetId(),
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return user, false, fmt.Errorf("could not check permissions: %w", err)
|
||||
}
|
||||
|
||||
return user, rsp.GetStatus().GetCode() == rpc.Code_CODE_OK, nil
|
||||
}
|
||||
@@ -4,27 +4,32 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
"os/signal"
|
||||
"time"
|
||||
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/store"
|
||||
"github.com/spf13/afero"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"go-micro.dev/v4/selector"
|
||||
microstore "go-micro.dev/v4/store"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/config/configlog"
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
"github.com/opencloud-eu/opencloud/pkg/registry"
|
||||
"github.com/opencloud-eu/opencloud/pkg/runner"
|
||||
"github.com/opencloud-eu/opencloud/pkg/tracing"
|
||||
"github.com/opencloud-eu/opencloud/pkg/x/io/fsx"
|
||||
"github.com/opencloud-eu/opencloud/services/collaboration/pkg/config"
|
||||
"github.com/opencloud-eu/opencloud/services/collaboration/pkg/config/parser"
|
||||
"github.com/opencloud-eu/opencloud/services/collaboration/pkg/connector"
|
||||
"github.com/opencloud-eu/opencloud/services/collaboration/pkg/font"
|
||||
"github.com/opencloud-eu/opencloud/services/collaboration/pkg/helpers"
|
||||
"github.com/opencloud-eu/opencloud/services/collaboration/pkg/server/debug"
|
||||
"github.com/opencloud-eu/opencloud/services/collaboration/pkg/server/grpc"
|
||||
"github.com/opencloud-eu/opencloud/services/collaboration/pkg/server/http"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/store"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"go-micro.dev/v4/selector"
|
||||
microstore "go-micro.dev/v4/store"
|
||||
)
|
||||
|
||||
// Server is the entrypoint for the server command.
|
||||
@@ -137,6 +142,35 @@ func Server(cfg *config.Config) *cobra.Command {
|
||||
}
|
||||
gr.Add(runner.NewGolangHttpServerRunner(cfg.Service.Name+".debug", debugServer))
|
||||
|
||||
var fontService font.Service
|
||||
{
|
||||
fontFS := afero.NewBasePathFs(fsx.NewOsFs(), cfg.Font.AssetPath)
|
||||
if err := fontFS.MkdirAll("/", 0o755); err != nil {
|
||||
logger.Error().Err(err).Msg("Failed to initialize the fonts directory")
|
||||
return err
|
||||
}
|
||||
|
||||
fontServiceRootURI, err := url.JoinPath(cfg.Commons.OpenCloudURL, "/collaboration/fonts")
|
||||
if err := fontFS.MkdirAll("/", 0o755); err != nil {
|
||||
logger.Error().Err(err).Msg("Failed to build font service root uri")
|
||||
return err
|
||||
}
|
||||
|
||||
service, err := font.NewService(
|
||||
font.ServiceOptions{}.
|
||||
WithFontFS(fontFS).
|
||||
WithRootURI(fontServiceRootURI).
|
||||
WithGatewaySelector(gatewaySelector).
|
||||
WithLogger(logger).
|
||||
WithPreviewText(cfg.Font.PreviewText),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fontService = service
|
||||
}
|
||||
|
||||
// start HTTP server
|
||||
httpServer, err := http.Server(
|
||||
http.Adapter(connector.NewHttpAdapter(gatewaySelector, cfg, st, selector.NewSelector(selector.Registry(registry.GetRegistry())))),
|
||||
@@ -145,6 +179,7 @@ func Server(cfg *config.Config) *cobra.Command {
|
||||
http.Context(ctx),
|
||||
http.TracerProvider(traceProvider),
|
||||
http.Store(st),
|
||||
http.FontService(fontService),
|
||||
)
|
||||
if err != nil {
|
||||
logger.Info().Err(err).Str("transport", "http").Msg("Failed to initialize server")
|
||||
|
||||
@@ -12,6 +12,7 @@ type Config struct {
|
||||
|
||||
Service Service `yaml:"-"`
|
||||
App App `yaml:"app"`
|
||||
Font Font `yaml:"font"`
|
||||
Store Store `yaml:"store"`
|
||||
|
||||
TokenManager *TokenManager `yaml:"token_manager"`
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package defaults
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/config/defaults"
|
||||
"github.com/opencloud-eu/opencloud/pkg/shared"
|
||||
"github.com/opencloud-eu/opencloud/pkg/structs"
|
||||
"github.com/opencloud-eu/opencloud/services/collaboration/pkg/config"
|
||||
@@ -33,6 +35,10 @@ func DefaultConfig() *config.Config {
|
||||
Duration: "12h",
|
||||
},
|
||||
},
|
||||
Font: config.Font{
|
||||
AssetPath: filepath.Join(defaults.BaseDataPath(), "collaboration/fonts"),
|
||||
PreviewText: "OpenCloud",
|
||||
},
|
||||
Store: config.Store{
|
||||
Store: "nats-js-kv",
|
||||
Nodes: []string{"127.0.0.1:9233"},
|
||||
|
||||
6
services/collaboration/pkg/config/font.go
Normal file
6
services/collaboration/pkg/config/font.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package config
|
||||
|
||||
type Font struct {
|
||||
AssetPath string `yaml:"asset_path" env:"COLLABORATION_FONT_ASSET_PATH" desc:"Serve fonts from a path on the filesystem instead of the builtin assets. If not defined, the root directory derives from $OC_BASE_DATA_PATH/collaboration/fonts" introductionVersion:"%%NEXT%%"`
|
||||
PreviewText string `yaml:"preview_text" env:"COLLABORATION_FONT_PREVIEW_TEXT" desc:"The text that will be displayed in the font preview." introductionVersion:"%%NEXT%%"`
|
||||
}
|
||||
350
services/collaboration/pkg/font/service.go
Normal file
350
services/collaboration/pkg/font/service.go
Normal file
@@ -0,0 +1,350 @@
|
||||
package font
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"image/png"
|
||||
"io"
|
||||
"mime"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/afero"
|
||||
"golang.org/x/image/font"
|
||||
"golang.org/x/image/font/opentype"
|
||||
"golang.org/x/image/font/sfnt"
|
||||
"golang.org/x/image/math/fixed"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
"github.com/opencloud-eu/opencloud/services/collaboration/pkg/collaboration"
|
||||
)
|
||||
|
||||
type ServiceOptions struct {
|
||||
logger log.Logger `validate:"required"`
|
||||
fontFS afero.Fs `validate:"required"`
|
||||
rootURI string `validate:"required,url"`
|
||||
previewText string `validate:"required,min=1"`
|
||||
gatewaySelector pool.Selectable[gateway.GatewayAPIClient] `validate:"required"`
|
||||
}
|
||||
|
||||
func (o ServiceOptions) WithFontFS(fSys afero.Fs) ServiceOptions {
|
||||
o.fontFS = fSys
|
||||
return o
|
||||
}
|
||||
|
||||
func (o ServiceOptions) WithRootURI(rootURI string) ServiceOptions {
|
||||
o.rootURI = rootURI
|
||||
return o
|
||||
}
|
||||
|
||||
func (o ServiceOptions) WithLogger(logger log.Logger) ServiceOptions {
|
||||
o.logger = logger
|
||||
return o
|
||||
}
|
||||
|
||||
func (o ServiceOptions) WithPreviewText(txt string) ServiceOptions {
|
||||
o.previewText = txt
|
||||
return o
|
||||
}
|
||||
|
||||
func (o ServiceOptions) WithGatewaySelector(gws pool.Selectable[gateway.GatewayAPIClient]) ServiceOptions {
|
||||
o.gatewaySelector = gws
|
||||
return o
|
||||
}
|
||||
|
||||
func (o ServiceOptions) validate() error {
|
||||
return validator.New(
|
||||
validator.WithPrivateFieldValidation(),
|
||||
validator.WithRequiredStructEnabled(),
|
||||
).Struct(o)
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
logger log.Logger
|
||||
fontFS afero.Fs
|
||||
rootURI string
|
||||
previewText string
|
||||
gatewaySelector pool.Selectable[gateway.GatewayAPIClient]
|
||||
}
|
||||
|
||||
func NewService(options ServiceOptions) (Service, error) {
|
||||
if err := options.validate(); err != nil {
|
||||
return Service{}, err
|
||||
}
|
||||
|
||||
return Service(options), nil
|
||||
}
|
||||
|
||||
func (s Service) DeleteFont(w http.ResponseWriter, r *http.Request) {
|
||||
gatewayClient, err := s.gatewaySelector.Next()
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
_, canManage, err := collaboration.CheckPermissions(gatewayClient, r.Context(), collaboration.PermissionCollaborationManageFonts)
|
||||
switch {
|
||||
case err != nil:
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
case !canManage:
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
fontName := r.PathValue("id")
|
||||
if fontName == "" {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = s.fontFS.Remove(fontName)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func (s Service) GetFont(w http.ResponseWriter, r *http.Request) {
|
||||
fontName := r.PathValue("id")
|
||||
if fontName == "" {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
b, err := s.getFont(fontName)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
contentType := mime.TypeByExtension(filepath.Ext(fontName))
|
||||
if contentType == "" {
|
||||
contentType = "application/octet-stream"
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
w.Header().Set("Content-Length", strconv.Itoa(len(b)))
|
||||
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
w.Header().Set("X-Content-Type-Options", "nosniff")
|
||||
|
||||
_, _ = w.Write(b)
|
||||
}
|
||||
|
||||
func (s Service) PreviewFont(w http.ResponseWriter, r *http.Request) {
|
||||
fontName := r.PathValue("id")
|
||||
if fontName == "" {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
b, err := s.getFont(fontName)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
f, err := opentype.Parse(b)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
face, err := opentype.NewFace(f, &opentype.FaceOptions{
|
||||
Size: 55,
|
||||
DPI: 72,
|
||||
Hinting: font.HintingFull,
|
||||
})
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
_ = face.Close()
|
||||
}()
|
||||
|
||||
width := 300
|
||||
height := 70
|
||||
img := image.NewRGBA(image.Rect(0, 0, width, height))
|
||||
|
||||
bg := color.RGBA{R: 255, G: 255, B: 255, A: 255}
|
||||
for i := range img.Pix {
|
||||
img.Pix[i] = bg.R
|
||||
}
|
||||
|
||||
d := &font.Drawer{
|
||||
Dst: img,
|
||||
Src: image.NewUniform(color.RGBA{0, 0, 0, 255}),
|
||||
Face: face,
|
||||
Dot: fixed.Point26_6{X: fixed.I(10), Y: fixed.I(50)},
|
||||
}
|
||||
|
||||
d.DrawString(s.previewText)
|
||||
|
||||
w.Header().Set("Content-Type", "image/png")
|
||||
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
|
||||
if err := png.Encode(w, img); err != nil {
|
||||
http.Error(w, "Failed to encode image", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s Service) ListFonts(w http.ResponseWriter, _ *http.Request) {
|
||||
fontFiles, err := afero.NewIOFS(s.fontFS).ReadDir(".")
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
fontMap := []map[string]any{}
|
||||
for _, fontFile := range fontFiles {
|
||||
func() {
|
||||
uri, err := url.JoinPath(s.rootURI, path.Base(fontFile.Name()))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fontLogger := s.logger.Debug().Str("font", fontFile.Name())
|
||||
|
||||
b, err := s.getFont(fontFile.Name())
|
||||
if err != nil {
|
||||
fontLogger.Err(err).Msg("could not get font")
|
||||
return
|
||||
}
|
||||
|
||||
fnt, err := sfnt.Parse(b)
|
||||
if err != nil {
|
||||
fontLogger.Err(err).Msg("could not parse font")
|
||||
return
|
||||
}
|
||||
|
||||
buf := new(sfnt.Buffer)
|
||||
nameID := func(id sfnt.NameID) string {
|
||||
name, err := fnt.Name(buf, id)
|
||||
if err != nil {
|
||||
fontLogger.Err(err).Msg("could not extract font details")
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
fontMap = append(fontMap, map[string]any{
|
||||
"file": path.Base(fontFile.Name()),
|
||||
"copyright": nameID(sfnt.NameIDCopyright),
|
||||
"family": nameID(sfnt.NameIDFamily),
|
||||
"version": nameID(sfnt.NameIDVersion),
|
||||
"trademark": nameID(sfnt.NameIDTrademark),
|
||||
"manufacturer": nameID(sfnt.NameIDManufacturer),
|
||||
"designer": nameID(sfnt.NameIDDesigner),
|
||||
"description": nameID(sfnt.NameIDDescription),
|
||||
"vendor_url": nameID(sfnt.NameIDVendorURL),
|
||||
"designer_url": nameID(sfnt.NameIDDesignerURL),
|
||||
"license": nameID(sfnt.NameIDLicense),
|
||||
"license_url": nameID(sfnt.NameIDLicenseURL),
|
||||
"uri": uri,
|
||||
// if stamp property changes, the font file will be re-downloaded by collabora
|
||||
"stamp": fmt.Sprintf("%x", sha256.Sum256(b)),
|
||||
})
|
||||
}()
|
||||
}
|
||||
|
||||
b, err := json.Marshal(map[string]any{
|
||||
"kind": "fontconfiguration",
|
||||
"server": "OpenCloud Fonts",
|
||||
"fonts": fontMap,
|
||||
})
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, err = w.Write(b)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s Service) UploadFont(w http.ResponseWriter, r *http.Request) {
|
||||
gatewayClient, err := s.gatewaySelector.Next()
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
_, canManage, err := collaboration.CheckPermissions(gatewayClient, r.Context(), collaboration.PermissionCollaborationManageFonts)
|
||||
switch {
|
||||
case err != nil:
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
case !canManage:
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
file, fileHeader, err := r.FormFile("font")
|
||||
switch {
|
||||
case err != nil && errors.Is(err, http.ErrMissingFile):
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
case err != nil:
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
_ = file.Close()
|
||||
}()
|
||||
|
||||
b, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = sfnt.Parse(b); err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = afero.WriteFile(s.fontFS, fileHeader.Filename, b, 0o666)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func (s Service) getFont(name string) ([]byte, error) {
|
||||
fontFile, err := s.fontFS.Open(name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not open font file: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
_ = fontFile.Close()
|
||||
}()
|
||||
|
||||
fontStat, err := fontFile.Stat()
|
||||
if err != nil || fontStat.IsDir() {
|
||||
return nil, fmt.Errorf("could not stat font file: %w", err)
|
||||
}
|
||||
|
||||
b, err := io.ReadAll(fontFile)
|
||||
if err != nil || len(b) == 0 {
|
||||
return nil, fmt.Errorf("could not read font file: %w", err)
|
||||
}
|
||||
|
||||
return b, nil
|
||||
}
|
||||
224
services/collaboration/pkg/font/service_test.go
Normal file
224
services/collaboration/pkg/font/service_test.go
Normal file
@@ -0,0 +1,224 @@
|
||||
package font_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"embed"
|
||||
"io"
|
||||
"io/fs"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
cs3Permissions "github.com/cs3org/go-cs3apis/cs3/permissions/v1beta1"
|
||||
cs3RPC "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
revaCtx "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
cs3mocks "github.com/opencloud-eu/reva/v2/tests/cs3mocks/mocks"
|
||||
"github.com/spf13/afero"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/tidwall/gjson"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
"github.com/opencloud-eu/opencloud/services/collaboration/pkg/font"
|
||||
)
|
||||
|
||||
//go:embed testdata/*
|
||||
var testdata embed.FS
|
||||
|
||||
// Helper to create a gatewaySelector
|
||||
func newGatewaySelector() pool.Selectable[gateway.GatewayAPIClient] {
|
||||
gatewayAPIClient := &cs3mocks.GatewayAPIClient{}
|
||||
gatewayAPIClient.On("CheckPermission", mock.Anything, mock.Anything).Return(
|
||||
&cs3Permissions.CheckPermissionResponse{
|
||||
Status: &cs3RPC.Status{
|
||||
Code: cs3RPC.Code_CODE_OK,
|
||||
},
|
||||
}, nil)
|
||||
gatewaySelector := pool.GetSelector[gateway.GatewayAPIClient](
|
||||
"GatewaySelector",
|
||||
"eu.opencloud.api.gateway",
|
||||
func(cc grpc.ClientConnInterface) gateway.GatewayAPIClient {
|
||||
return gatewayAPIClient
|
||||
},
|
||||
)
|
||||
|
||||
return gatewaySelector
|
||||
}
|
||||
|
||||
func TestService_PreviewFont(t *testing.T) {
|
||||
testFS := afero.NewMemMapFs()
|
||||
svc, err := font.NewService(
|
||||
font.ServiceOptions{}.
|
||||
WithFontFS(testFS).
|
||||
WithPreviewText("a").
|
||||
WithLogger(log.NopLogger()).
|
||||
WithRootURI("http://test.local").
|
||||
WithGatewaySelector(newGatewaySelector()),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
testDataFontB, err := testdata.ReadFile("testdata/arimo-regular.ttf")
|
||||
require.NoError(t, err)
|
||||
|
||||
testDataFontPNG, err := testdata.ReadFile("testdata/arimo-regular.png")
|
||||
require.NoError(t, err)
|
||||
|
||||
_ = afero.WriteFile(testFS, "arimo-regular.ttf", testDataFontB, 0644)
|
||||
defer func() {
|
||||
require.NoError(t, testFS.Remove("arimo-regular.ttf"))
|
||||
}()
|
||||
|
||||
req, _ := http.NewRequest(http.MethodGet, "/", nil)
|
||||
req.SetPathValue("id", "arimo-regular.ttf")
|
||||
resp := httptest.NewRecorder()
|
||||
svc.PreviewFont(resp, req)
|
||||
require.Equal(t, resp.Body.Bytes(), testDataFontPNG)
|
||||
}
|
||||
|
||||
func TestService_DeleteFont(t *testing.T) {
|
||||
testFS := afero.NewMemMapFs()
|
||||
svc, err := font.NewService(
|
||||
font.ServiceOptions{}.
|
||||
WithFontFS(testFS).
|
||||
WithPreviewText("a").
|
||||
WithLogger(log.NopLogger()).
|
||||
WithRootURI("http://test.local").
|
||||
WithGatewaySelector(newGatewaySelector()),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
testDataFontB, err := testdata.ReadFile("testdata/arimo-regular.ttf")
|
||||
require.NoError(t, err)
|
||||
|
||||
_ = afero.WriteFile(testFS, "arimo-regular.ttf", testDataFontB, 0644)
|
||||
|
||||
_, err = testFS.Stat("arimo-regular.ttf") // ensure the file exists before deletion
|
||||
require.NoError(t, err)
|
||||
|
||||
req, _ := http.NewRequest(http.MethodGet, "/", nil)
|
||||
req.SetPathValue("id", "arimo-regular.ttf")
|
||||
req = req.WithContext(revaCtx.ContextSetUser(req.Context(), &userpb.User{
|
||||
Id: &userpb.UserId{
|
||||
OpaqueId: "user",
|
||||
},
|
||||
}))
|
||||
|
||||
resp := httptest.NewRecorder()
|
||||
svc.DeleteFont(resp, req)
|
||||
|
||||
_, err = testFS.Stat("arimo-regular.ttf") // ensure the file exists before deletion
|
||||
require.ErrorIs(t, err, fs.ErrNotExist)
|
||||
}
|
||||
|
||||
func TestService_GetFont(t *testing.T) {
|
||||
testFS := afero.NewMemMapFs()
|
||||
svc, err := font.NewService(
|
||||
font.ServiceOptions{}.
|
||||
WithFontFS(testFS).
|
||||
WithPreviewText("a").
|
||||
WithLogger(log.NopLogger()).
|
||||
WithRootURI("http://test.local").
|
||||
WithGatewaySelector(newGatewaySelector()),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
testDataFontB, err := testdata.ReadFile("testdata/arimo-regular.ttf")
|
||||
require.NoError(t, err)
|
||||
|
||||
_ = afero.WriteFile(testFS, "arimo-regular.ttf", testDataFontB, 0644)
|
||||
defer func() {
|
||||
require.NoError(t, testFS.Remove("arimo-regular.ttf"))
|
||||
}()
|
||||
|
||||
req, _ := http.NewRequest(http.MethodGet, "/", nil)
|
||||
req.SetPathValue("id", "arimo-regular.ttf")
|
||||
resp := httptest.NewRecorder()
|
||||
svc.GetFont(resp, req)
|
||||
require.Equal(t, resp.Body.Bytes(), testDataFontB)
|
||||
}
|
||||
|
||||
func TestService_ListFonts(t *testing.T) {
|
||||
testFS := afero.NewMemMapFs()
|
||||
svc, err := font.NewService(
|
||||
font.ServiceOptions{}.
|
||||
WithFontFS(testFS).
|
||||
WithPreviewText("a").
|
||||
WithLogger(log.NopLogger()).
|
||||
WithRootURI("http://test.local").
|
||||
WithGatewaySelector(newGatewaySelector()),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("no fonts", func(t *testing.T) {
|
||||
req, _ := http.NewRequest(http.MethodGet, "/", nil)
|
||||
resp := httptest.NewRecorder()
|
||||
svc.ListFonts(resp, req)
|
||||
|
||||
jsonData := gjson.Parse(resp.Body.String())
|
||||
require.Equal(t, jsonData.Get("fonts").String(), "[]") // empty array, not `null`
|
||||
})
|
||||
|
||||
t.Run("with fonts", func(t *testing.T) {
|
||||
testDataFontB, err := testdata.ReadFile("testdata/arimo-regular.ttf")
|
||||
require.NoError(t, err)
|
||||
|
||||
fontconfigurationB, err := testdata.ReadFile("testdata/fontconfiguration.json")
|
||||
require.NoError(t, err)
|
||||
|
||||
_ = afero.WriteFile(testFS, "arimo-regular.ttf", testDataFontB, 0644)
|
||||
defer func() {
|
||||
require.NoError(t, testFS.Remove("arimo-regular.ttf"))
|
||||
}()
|
||||
|
||||
req, _ := http.NewRequest(http.MethodGet, "/", nil)
|
||||
resp := httptest.NewRecorder()
|
||||
svc.ListFonts(resp, req)
|
||||
|
||||
jsonData := gjson.Parse(resp.Body.String())
|
||||
require.JSONEq(t, jsonData.String(), string(fontconfigurationB))
|
||||
})
|
||||
}
|
||||
|
||||
func TestService_UploadFont(t *testing.T) {
|
||||
testFS := afero.NewMemMapFs()
|
||||
svc, err := font.NewService(
|
||||
font.ServiceOptions{}.
|
||||
WithFontFS(testFS).
|
||||
WithPreviewText("a").
|
||||
WithLogger(log.NopLogger()).
|
||||
WithRootURI("http://test.local").
|
||||
WithGatewaySelector(newGatewaySelector()),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
testDataFontB, err := testdata.ReadFile("testdata/arimo-regular.ttf")
|
||||
require.NoError(t, err)
|
||||
|
||||
var b bytes.Buffer
|
||||
w := multipart.NewWriter(&b)
|
||||
part, _ := w.CreateFormFile("font", "arimo-regular.ttf")
|
||||
_, _ = part.Write(testDataFontB)
|
||||
_ = w.Close()
|
||||
|
||||
req, _ := http.NewRequest(http.MethodPost, "/", &b)
|
||||
req.Header.Set("Content-Type", w.FormDataContentType())
|
||||
req = req.WithContext(revaCtx.ContextSetUser(req.Context(), &userpb.User{
|
||||
Id: &userpb.UserId{
|
||||
OpaqueId: "user",
|
||||
},
|
||||
}))
|
||||
resp := httptest.NewRecorder()
|
||||
|
||||
svc.UploadFont(resp, req)
|
||||
testFSFontF, err := testFS.Open("arimo-regular.ttf")
|
||||
require.NoError(t, err)
|
||||
|
||||
testFSFontB, err := io.ReadAll(testFSFontF)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, testDataFontB, testFSFontB)
|
||||
}
|
||||
BIN
services/collaboration/pkg/font/testdata/arimo-regular.png
vendored
Normal file
BIN
services/collaboration/pkg/font/testdata/arimo-regular.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
BIN
services/collaboration/pkg/font/testdata/arimo-regular.ttf
vendored
Normal file
BIN
services/collaboration/pkg/font/testdata/arimo-regular.ttf
vendored
Normal file
Binary file not shown.
22
services/collaboration/pkg/font/testdata/fontconfiguration.json
vendored
Normal file
22
services/collaboration/pkg/font/testdata/fontconfiguration.json
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"fonts": [
|
||||
{
|
||||
"copyright": "Copyright 2020 The Arimo Project Authors (https://github.com/googlefonts/arimo)",
|
||||
"description": "Arimo was designed by Steve Matteson as an innovative, refreshing sans serif design that is metrically compatible with Arial(tm). Arimo offers improved on-screen readability characteristics and the pan-European WGL character set and solves the needs of developers looking for width-compatible fonts to address document portability across platforms.",
|
||||
"designer": "Steve Matteson",
|
||||
"designer_url": "http://www.monotype.com/studio",
|
||||
"family": "Arimo",
|
||||
"file": "arimo-regular.ttf",
|
||||
"license": "This Font Software is licensed under the SIL Open Font License, Version 1.1. This Font Software is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the SIL Open Font License for the specific language, permissions and limitations governing your use of this Font Software.",
|
||||
"license_url": "http://scripts.sil.org/OFL",
|
||||
"manufacturer": "Monotype Imaging Inc.",
|
||||
"stamp": "f405056d25ddc3ad89a852db367f1dc075d8c7212fddcf7c488e212efeb8b176",
|
||||
"trademark": "Arimo is a trademark of Google Inc.",
|
||||
"uri": "http://test.local/arimo-regular.ttf",
|
||||
"vendor_url": "http://www.google.com/get/noto/",
|
||||
"version": "Version 1.33"
|
||||
}
|
||||
],
|
||||
"kind": "fontconfiguration",
|
||||
"server": "OpenCloud Fonts"
|
||||
}
|
||||
1
services/collaboration/pkg/font/testdata/props.txt
vendored
Normal file
1
services/collaboration/pkg/font/testdata/props.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
arimo-regular.ttf: https://github.com/ryanoasis/nerd-fonts/blob/master/src/unpatched-fonts/Arimo/Regular/Arimo-Regular.ttf
|
||||
@@ -3,17 +3,19 @@ package http
|
||||
import (
|
||||
"context"
|
||||
|
||||
microstore "go-micro.dev/v4/store"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
"github.com/opencloud-eu/opencloud/services/collaboration/pkg/config"
|
||||
"github.com/opencloud-eu/opencloud/services/collaboration/pkg/connector"
|
||||
microstore "go-micro.dev/v4/store"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"github.com/opencloud-eu/opencloud/services/collaboration/pkg/font"
|
||||
)
|
||||
|
||||
// Option defines a single option function.
|
||||
type Option func(o *Options)
|
||||
|
||||
// Options defines the available options for this package.
|
||||
// Options define the available options for this package.
|
||||
type Options struct {
|
||||
Adapter *connector.HttpAdapter
|
||||
Logger log.Logger
|
||||
@@ -21,6 +23,7 @@ type Options struct {
|
||||
Config *config.Config
|
||||
TracerProvider trace.TracerProvider
|
||||
Store microstore.Store
|
||||
FontService font.Service
|
||||
}
|
||||
|
||||
// newOptions initializes the available default options.
|
||||
@@ -34,7 +37,7 @@ func newOptions(opts ...Option) Options {
|
||||
return opt
|
||||
}
|
||||
|
||||
// App provides a function to set the logger option.
|
||||
// Adapter provides a function to set the Adapter option.
|
||||
func Adapter(val *connector.HttpAdapter) Option {
|
||||
return func(o *Options) {
|
||||
o.Adapter = val
|
||||
@@ -75,3 +78,10 @@ func Store(val microstore.Store) Option {
|
||||
o.Store = val
|
||||
}
|
||||
}
|
||||
|
||||
// FontService provides a function to set the FontService option
|
||||
func FontService(val font.Service) Option {
|
||||
return func(o *Options) {
|
||||
o.FontService = val
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,9 @@ import (
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
chimiddleware "github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/riandyrn/otelchi"
|
||||
"go-micro.dev/v4"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/pkg/account"
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
"github.com/opencloud-eu/opencloud/pkg/middleware"
|
||||
@@ -13,8 +16,6 @@ import (
|
||||
"github.com/opencloud-eu/opencloud/pkg/tracing"
|
||||
"github.com/opencloud-eu/opencloud/pkg/version"
|
||||
colabmiddleware "github.com/opencloud-eu/opencloud/services/collaboration/pkg/middleware"
|
||||
"github.com/riandyrn/otelchi"
|
||||
"go-micro.dev/v4"
|
||||
)
|
||||
|
||||
// Server initializes the http service and server.
|
||||
@@ -94,6 +95,7 @@ func Server(opts ...Option) (http.Service, error) {
|
||||
|
||||
// prepareRoutes will prepare all the implemented routes
|
||||
func prepareRoutes(r *chi.Mux, options Options) {
|
||||
fontService := options.FontService
|
||||
adapter := options.Adapter
|
||||
logger := options.Logger
|
||||
// prepare basic logger for the request
|
||||
@@ -209,5 +211,21 @@ func prepareRoutes(r *chi.Mux, options Options) {
|
||||
adapter.GetAvatar(w, r)
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
r.Route("/collaboration", func(r chi.Router) {
|
||||
r.Route("/fonts", func(r chi.Router) {
|
||||
r.Get("/", fontService.ListFonts)
|
||||
r.Get("/{id}", fontService.GetFont)
|
||||
r.Get("/preview/{id}", fontService.PreviewFont)
|
||||
r.Route("/manage", func(r chi.Router) {
|
||||
r.Use(middleware.ExtractAccountUUID(
|
||||
account.Logger(options.Logger),
|
||||
account.JWTSecret(options.Config.TokenManager.JWTSecret),
|
||||
))
|
||||
r.Post("/", fontService.UploadFont)
|
||||
r.Delete("/{id}", fontService.DeleteFont)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -291,6 +291,16 @@ func DefaultPolicies() []config.Policy {
|
||||
Unprotected: true,
|
||||
SkipXAccessToken: true,
|
||||
},
|
||||
{
|
||||
Endpoint: "/collaboration/fonts/manage/",
|
||||
Service: "eu.opencloud.web.collaboration",
|
||||
// Method: "POST" // toDo: fails with method, WHY???
|
||||
},
|
||||
{
|
||||
Endpoint: "/collaboration",
|
||||
Service: "eu.opencloud.web.collaboration",
|
||||
Unprotected: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -7,12 +7,13 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/services/proxy/pkg/router"
|
||||
"github.com/opencloud-eu/opencloud/services/proxy/pkg/webdav"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"golang.org/x/text/cases"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/opencloud-eu/opencloud/services/proxy/pkg/router"
|
||||
"github.com/opencloud-eu/opencloud/services/proxy/pkg/webdav"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -79,6 +79,7 @@ func ServiceAccountBundle() *settingsmsg.Bundle {
|
||||
Settings: []*settingsmsg.Setting{
|
||||
AccountManagementPermission(All),
|
||||
ChangeLogoPermission(All),
|
||||
CollaborationManageFontsPermission(All),
|
||||
CreatePublicLinkPermission(All),
|
||||
CreateSharePermission(All),
|
||||
CreateSpacesPermission(All),
|
||||
@@ -115,6 +116,7 @@ func generateBundleAdminRole() *settingsmsg.Bundle {
|
||||
AccountManagementPermission(All),
|
||||
AutoAcceptSharesPermission(Own),
|
||||
ChangeLogoPermission(All),
|
||||
CollaborationManageFontsPermission(All),
|
||||
CreatePublicLinkPermission(All),
|
||||
CreateSharePermission(All),
|
||||
CreateSpacesPermission(All),
|
||||
|
||||
@@ -67,6 +67,25 @@ func ChangeLogoPermission(c settingsmsg.Permission_Constraint) *settingsmsg.Sett
|
||||
}
|
||||
}
|
||||
|
||||
// ManageFontsPermission is the permission to manage fonts
|
||||
func CollaborationManageFontsPermission(c settingsmsg.Permission_Constraint) *settingsmsg.Setting {
|
||||
return &settingsmsg.Setting{
|
||||
Id: "ed83fc10-1f54-4a9e-b5a7-fb517f5f3e01",
|
||||
Name: "Collaboration.Fonts.Manage",
|
||||
DisplayName: "Manage fonts",
|
||||
Description: "This permission permits to manage the collaboration fonts.",
|
||||
Resource: &settingsmsg.Resource{
|
||||
Type: settingsmsg.Resource_TYPE_SYSTEM,
|
||||
},
|
||||
Value: &settingsmsg.Setting_PermissionValue{
|
||||
PermissionValue: &settingsmsg.Permission{
|
||||
Operation: settingsmsg.Permission_OPERATION_READWRITE,
|
||||
Constraint: c,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// CreatePublicLinkPermission is the permission to create public links
|
||||
func CreatePublicLinkPermission(c settingsmsg.Permission_Constraint) *settingsmsg.Setting {
|
||||
return &settingsmsg.Setting{
|
||||
|
||||
@@ -13,11 +13,10 @@ include ../../.make/release.mk
|
||||
include ../../.make/docs.mk
|
||||
|
||||
.PHONY: node-generate-dev
|
||||
node-generate-dev: pull-assets
|
||||
node-generate-dev: pull-assets build-apps
|
||||
|
||||
.PHONY: node-generate-prod
|
||||
node-generate-prod: download-assets
|
||||
|
||||
node-generate-prod: download-assets build-apps
|
||||
|
||||
.PHONY: pull-assets
|
||||
pull-assets:
|
||||
@@ -43,6 +42,13 @@ download-assets:
|
||||
git clean -xfd assets
|
||||
curl --fail -slL -o- https://github.com/opencloud-eu/web/releases/download/$(WEB_ASSETS_VERSION)/web.tar.gz | tar xzf - -C assets/core/
|
||||
|
||||
.PHONY: build-apps
|
||||
build-apps:
|
||||
@for dir in ./assets/apps/*; do \
|
||||
echo "📦 Installing in $$dir"; \
|
||||
(cd "$$dir" && pnpm install && pnpm build) || exit 1; \
|
||||
done
|
||||
|
||||
.PHONY: ci-node-save-licenses
|
||||
ci-node-save-licenses:
|
||||
@mkdir -p ../../third-party-licenses/node/web
|
||||
|
||||
@@ -89,20 +89,28 @@ func List(logger log.Logger, data map[string]config.App, fSystems ...fs.FS) []Ap
|
||||
appData = data
|
||||
}
|
||||
|
||||
application, err := build(fSystem, name, appData)
|
||||
if err != nil {
|
||||
// if app creation fails, log the error and continue with the next app
|
||||
logger.Debug().Err(err).Str("path", entry.Name()).Msg("failed to load application")
|
||||
continue
|
||||
}
|
||||
for _, appRoot := range []string{
|
||||
name,
|
||||
path.Join(name, "dist"), // some applications have their artifacts in the dist/ folder
|
||||
} {
|
||||
application, err := build(fSystem, appRoot, appData)
|
||||
if err != nil {
|
||||
// if app creation fails, log the error and continue with the next app
|
||||
logger.Debug().Err(err).Str("path", entry.Name()).Msg("failed to load application")
|
||||
continue
|
||||
}
|
||||
|
||||
if application.Disabled {
|
||||
// if the app is disabled, skip it
|
||||
continue
|
||||
}
|
||||
if application.Disabled {
|
||||
// if the app is disabled, skip it
|
||||
continue
|
||||
}
|
||||
|
||||
// everything is fine, add the application to the list of applications
|
||||
registry[name] = application
|
||||
// everything is fine, add the application to the list of applications
|
||||
registry[name] = application
|
||||
|
||||
// application found, done here
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +131,9 @@ func build(fSystem fs.FS, id string, globalConfig config.App) (Application, erro
|
||||
if err != nil {
|
||||
return Application{}, errors.Join(err, ErrMissingManifest)
|
||||
}
|
||||
defer r.Close()
|
||||
defer func() {
|
||||
_ = r.Close()
|
||||
}()
|
||||
|
||||
if json.NewDecoder(r).Decode(&application) != nil {
|
||||
return Application{}, errors.Join(err, ErrInvalidManifest)
|
||||
|
||||
@@ -181,8 +181,20 @@ func TestList(t *testing.T) {
|
||||
"app-3/manifest.json": &fstest.MapFile{
|
||||
Data: []byte(`{"id":"app-3", "entrypoint":"entrypoint.js", "config": {"foo": "fs2"}}`),
|
||||
},
|
||||
}, fstest.MapFS{
|
||||
"app-unknown": dir,
|
||||
"app-unknown/bin/entrypoint.js": &fstest.MapFile{},
|
||||
"app-unknown/bin/manifest.json": &fstest.MapFile{
|
||||
Data: []byte(`{"id":"app-unknown", "entrypoint":"entrypoint.js"}`),
|
||||
},
|
||||
}, fstest.MapFS{
|
||||
"app-dist": dir,
|
||||
"app-dist/dist/entrypoint.js": &fstest.MapFile{},
|
||||
"app-dist/dist/manifest.json": &fstest.MapFile{
|
||||
Data: []byte(`{"id":"app-dist", "entrypoint":"entrypoint.js", "config": {"folder": "dist"}}`),
|
||||
},
|
||||
})
|
||||
g.Expect(len(applications)).To(gomega.Equal(3))
|
||||
g.Expect(len(applications)).To(gomega.Equal(4))
|
||||
|
||||
for _, application := range applications {
|
||||
switch {
|
||||
@@ -193,6 +205,8 @@ func TestList(t *testing.T) {
|
||||
case application.Entrypoint == "app-3/entrypoint.js":
|
||||
g.Expect(application.Config["foo"]).To(gomega.Equal("local conf 1"))
|
||||
g.Expect(application.Config["bar"]).To(gomega.Equal("local conf 2"))
|
||||
case application.Entrypoint == "app-dist/dist/entrypoint.js":
|
||||
g.Expect(application.Config["folder"]).To(gomega.Equal("dist"))
|
||||
default:
|
||||
t.Fatalf("unexpected application %s", application.Entrypoint)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ import (
|
||||
"github.com/opencloud-eu/opencloud/pkg/log"
|
||||
"github.com/opencloud-eu/opencloud/pkg/middleware"
|
||||
"github.com/opencloud-eu/opencloud/pkg/tracing"
|
||||
"github.com/opencloud-eu/opencloud/pkg/x/io/fsx"
|
||||
"github.com/opencloud-eu/opencloud/services/web/pkg/assets"
|
||||
"github.com/opencloud-eu/opencloud/services/web/pkg/config"
|
||||
"github.com/opencloud-eu/opencloud/services/web/pkg/theme"
|
||||
@@ -54,8 +53,6 @@ func NewService(opts ...Option) (Service, error) {
|
||||
logger: options.Logger,
|
||||
config: options.Config,
|
||||
mux: m,
|
||||
coreFS: options.CoreFS,
|
||||
themeFS: options.ThemeFS,
|
||||
gatewaySelector: options.GatewaySelector,
|
||||
}
|
||||
|
||||
@@ -92,7 +89,7 @@ func NewService(opts ...Option) (Service, error) {
|
||||
options.Config.HTTP.CacheTTL,
|
||||
))
|
||||
r.Mount("/", svc.Static(
|
||||
svc.coreFS,
|
||||
options.CoreFS,
|
||||
svc.config.HTTP.Root,
|
||||
options.Config.HTTP.CacheTTL,
|
||||
))
|
||||
@@ -110,8 +107,6 @@ type Web struct {
|
||||
logger log.Logger
|
||||
config *config.Config
|
||||
mux *chi.Mux
|
||||
coreFS fs.FS
|
||||
themeFS *fsx.FallbackFS
|
||||
gatewaySelector pool.Selectable[gateway.GatewayAPIClient]
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user