Skip to content

Commit

Permalink
Merge pull request #8723 from kobergj/SpaceTemplatesII
Browse files Browse the repository at this point in the history
Server-Side Space Templates
  • Loading branch information
kobergj committed Mar 28, 2024
2 parents 7f42738 + 24b5f85 commit e2eed1a
Show file tree
Hide file tree
Showing 23 changed files with 387 additions and 298 deletions.
5 changes: 5 additions & 0 deletions changelog/unreleased/space-templates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Enhancement: Make server side space templates production ready

Fixes several smaller bugs and adds some improvements to space templates, introduced with https://github.com/owncloud/ocis/pull/8558

https://github.com/owncloud/ocis/pull/8723
90 changes: 90 additions & 0 deletions ocis-pkg/l10n/l10n.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// package l10n holds translation mechanics that are used by user facing services (notifications, userlog, graph)
package l10n

import (
"context"
"errors"
"io/fs"
"os"

"github.com/leonelquinteros/gotext"
"github.com/owncloud/ocis/v2/ocis-pkg/middleware"
settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0"
"github.com/owncloud/ocis/v2/services/settings/pkg/store/defaults"
micrometadata "go-micro.dev/v4/metadata"
)

// Template marks a string as translatable
func Template(s string) string { return s }

// Translator is able to translate strings
type Translator struct {
fs fs.FS
defaultLocale string
domain string
}

// NewTranslator creates a Translator with library path and language code and load default domain
func NewTranslator(defaultLocale string, domain string, fsys fs.FS) Translator {
return Translator{
fs: fsys,
defaultLocale: defaultLocale,
domain: domain,
}
}

// NewTranslatorFromCommonConfig creates a new Translator from legacy config
func NewTranslatorFromCommonConfig(defaultLocale string, domain string, path string, fsys fs.FS, fsSubPath string) Translator {
var filesystem fs.FS
if path == "" {
filesystem, _ = fs.Sub(fsys, fsSubPath)
} else { // use custom path instead
filesystem = os.DirFS(path)
}
return NewTranslator(defaultLocale, domain, filesystem)
}

// Translate translates a string to the locale
func (t Translator) Translate(str, locale string) string {
return t.Locale(locale).Get(str)
}

// Locale returns the gotext.Locale, use `.Get` method to translate strings
func (t Translator) Locale(locale string) *gotext.Locale {
l := gotext.NewLocaleFS(locale, t.fs)
l.AddDomain(t.domain) // make domain configurable only if needed
if locale != "en" && len(l.GetTranslations()) == 0 {
l = gotext.NewLocaleFS(t.defaultLocale, t.fs)
l.AddDomain(t.domain) // make domain configurable only if needed
}
return l
}

// MustGetUserLocale returns the locale the user wants to use, omitting errors
func MustGetUserLocale(ctx context.Context, userID string, preferedLang string, vc settingssvc.ValueService) string {
if preferedLang != "" {
return preferedLang
}

locale, _ := GetUserLocale(ctx, userID, vc)
return locale
}

// GetUserLocale returns the locale of the user
func GetUserLocale(ctx context.Context, userID string, vc settingssvc.ValueService) (string, error) {
resp, err := vc.GetValueByUniqueIdentifiers(
micrometadata.Set(ctx, middleware.AccountID, userID),
&settingssvc.GetValueByUniqueIdentifiersRequest{
AccountUuid: userID,
SettingId: defaults.SettingUUIDProfileLanguage,
},
)
if err != nil {
return "", err
}
val := resp.GetValue().GetValue().GetListValue().GetValues()
if len(val) == 0 {
return "", errors.New("no language setting found")
}
return val[0].GetStringValue(), nil
}
25 changes: 25 additions & 0 deletions services/graph/Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
SHELL := bash
NAME := graph

# Where to write the files generated by this makefile.
OUTPUT_DIR = ./pkg/service/v0/l10n
TEMPLATE_FILE = ./pkg/service/v0/l10n/graph.pot

include ../../.make/recursion.mk

############ tooling ############
Expand Down Expand Up @@ -30,6 +34,27 @@ ci-go-generate: $(MOCKERY) # CI runs ci-node-generate automatically before this
.PHONY: ci-node-generate
ci-node-generate:

############ translations ########
.PHONY: l10n-pull
l10n-pull:
cd $(OUTPUT_DIR) && tx pull --all --force --skip --minimum-perc=75

.PHONY: l10n-push
l10n-push:
cd $(OUTPUT_DIR) && tx push -s --skip

.PHONY: l10n-read
l10n-read: $(GO_XGETTEXT)
go-xgettext -o $(OUTPUT_DIR)/graph.pot --keyword=l10n.Template --add-comments -s pkg/service/v0/spacetemplates.go

.PHONY: l10n-write
l10n-write:

.PHONY: l10n-clean
l10n-clean:
rm -f $(TEMPLATE_FILE);


############ licenses ############
.PHONY: ci-node-check-licenses
ci-node-check-licenses:
Expand Down
1 change: 1 addition & 0 deletions services/graph/pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ type Spaces struct {
UsersCacheTTL int `yaml:"users_cache_ttl" env:"GRAPH_SPACES_USERS_CACHE_TTL" desc:"Max TTL in seconds for the spaces users cache." introductionVersion:"pre5.0"`
GroupsCacheTTL int `yaml:"groups_cache_ttl" env:"GRAPH_SPACES_GROUPS_CACHE_TTL" desc:"Max TTL in seconds for the spaces groups cache." introductionVersion:"pre5.0"`
StorageUsersAddress string `yaml:"storage_users_address" env:"GRAPH_SPACES_STORAGE_USERS_ADDRESS" desc:"The address of the storage-users service." introductionVersion:"5.0"`
DefaultLanguage string `yaml:"default_language" env:"OCIS_DEFAULT_LANGUAGE" desc:"The default language used by services and the WebUI. If not defined, English will be used as default. See the documentation for more details." introductionVersion:"5.0"`
}

type LDAP struct {
Expand Down
37 changes: 26 additions & 11 deletions services/graph/pkg/service/v0/drives.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/cs3org/reva/v2/pkg/storagespace"
"github.com/cs3org/reva/v2/pkg/utils"
"github.com/owncloud/ocis/v2/ocis-pkg/conversions"
"github.com/owncloud/ocis/v2/ocis-pkg/l10n"
"github.com/owncloud/ocis/v2/ocis-pkg/service/grpc"
v0 "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0"
settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0"
Expand Down Expand Up @@ -326,15 +327,18 @@ func (g Graph) canCreateSpace(ctx context.Context, ownPersonalHome bool) bool {
func (g Graph) CreateDrive(w http.ResponseWriter, r *http.Request) {
logger := g.logger.SubloggerWithRequestID(r.Context())
logger.Info().Msg("calling create drive")
us, ok := revactx.ContextGetUser(r.Context())

ctx := r.Context()

us, ok := revactx.ContextGetUser(ctx)
if !ok {
logger.Debug().Msg("could not create drive: invalid user")
errorcode.NotAllowed.Render(w, r, http.StatusUnauthorized, "invalid user")
return
}

// TODO determine if the user tries to create his own personal space and pass that as a boolean
canCreateSpace := g.canCreateSpace(r.Context(), false)
canCreateSpace := g.canCreateSpace(ctx, false)
if !canCreateSpace {
logger.Debug().Bool("cancreatespace", canCreateSpace).Msg("could not create drive: insufficient permissions")
// if the permission is not existing for the user in context we can assume we don't have it. Return 401.
Expand Down Expand Up @@ -393,7 +397,7 @@ func (g Graph) CreateDrive(w http.ResponseWriter, r *http.Request) {
csr.Owner = us
}

resp, err := gatewayClient.CreateStorageSpace(r.Context(), &csr)
resp, err := gatewayClient.CreateStorageSpace(ctx, &csr)
if err != nil {
logger.Error().Err(err).Msg("could not create drive: transport error")
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
Expand Down Expand Up @@ -423,27 +427,38 @@ func (g Graph) CreateDrive(w http.ResponseWriter, r *http.Request) {
return
}

if driveType == _spaceTypeProject {
opaque, err := g.applySpaceTemplate(gatewayClient, resp.GetStorageSpace().GetRoot(), r.URL.Query().Get("template"))
if err != nil {
space := resp.GetStorageSpace()
if t := r.URL.Query().Get(TemplateParameter); t != "" && driveType == _spaceTypeProject {
loc := l10n.MustGetUserLocale(ctx, us.GetId().GetOpaqueId(), r.Header.Get(HeaderAcceptLanguage), g.valueService)
if err := g.applySpaceTemplate(ctx, gatewayClient, space.GetRoot(), t, loc); err != nil {
logger.Error().Err(err).Msg("could not apply template to space")
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
return
}

resp.StorageSpace.Opaque = utils.MergeOpaques(resp.GetStorageSpace().GetOpaque(), opaque)
// refetch the drive to get quota information - should we calculate this ourselves to avoid the extra call?
space, err = utils.GetSpace(ctx, space.GetId().GetOpaqueId(), gatewayClient)
if err != nil {
logger.Error().Err(err).Msg("could not refetch space")
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
return
}
}

newDrive, err := g.cs3StorageSpaceToDrive(r.Context(), webDavBaseURL, resp.GetStorageSpace(), APIVersion_1)
spaces, err := g.formatDrives(ctx, webDavBaseURL, []*storageprovider.StorageSpace{space}, APIVersion_1)
if err != nil {
logger.Debug().Err(err).Msg("could not create drive: error parsing drive")
logger.Debug().Err(err).Msg("could not get drive: error parsing grpc response")
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, err.Error())
return
}
newDrive.Special = g.getSpecialDriveItems(r.Context(), webDavBaseURL, resp.GetStorageSpace())
if len(spaces) == 0 {
logger.Error().Msg("could not convert space")
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "could not convert space")
return
}

render.Status(r, http.StatusCreated)
render.JSON(w, r, newDrive)
render.JSON(w, r, spaces[0])
}

// UpdateDrive updates the properties of a storage drive (space).
Expand Down
6 changes: 6 additions & 0 deletions services/graph/pkg/service/v0/graph_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -764,6 +764,12 @@ var _ = Describe("Graph", func() {
Constraint: v0.Permission_CONSTRAINT_ALL,
},
}, nil)

gatewayClient.On("GetQuota", mock.Anything, mock.Anything).Return(&provider.GetQuotaResponse{
Status: status.NewOK(ctx),
TotalBytes: 500,
}, nil)

jsonBody := []byte(`{"Name": "Test Space", "DriveType": "project", "Description": "This space is for testing", "DriveAlias": "project/testspace"}`)
r := httptest.NewRequest(http.MethodPost, "/graph/v1.0/drives", bytes.NewBuffer(jsonBody)).WithContext(ctx)
rr := httptest.NewRecorder()
Expand Down
9 changes: 9 additions & 0 deletions services/graph/pkg/service/v0/l10n/.tx/config
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[main]
host = https://www.transifex.com

[o:owncloud-org:p:owncloud:r:ocis-graph]
file_filter = locale/<lang>/LC_MESSAGES/graph.po
minimum_perc = 75
source_file = graph.pot
source_lang = en
type = PO
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2024-03-26 15:11+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <[email protected]>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"

#: pkg/service/v0/spacetemplates.go:29
msgid "Here you can add a description for this Space."
msgstr ""

6 changes: 0 additions & 6 deletions services/graph/pkg/service/v0/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package svc
import (
"crypto/tls"
"crypto/x509"
"embed"
"errors"
"fmt"
"net/http"
Expand Down Expand Up @@ -36,11 +35,6 @@ const (
displayNameAttr = "displayName"
)

var (
//go:embed spacetemplate/*
_spaceTemplateFS embed.FS
)

// Service defines the service handlers.
type Service interface {
ServeHTTP(http.ResponseWriter, *http.Request)
Expand Down
Binary file not shown.

This file was deleted.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit e2eed1a

Please sign in to comment.