Initial QSfera import
This commit is contained in:
@@ -0,0 +1,430 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
collaboration "github.com/cs3org/go-cs3apis/cs3/sharing/collaboration/v1beta1"
|
||||
storageprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/qsfera/server/pkg/l10n"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/events"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/storagespace"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
)
|
||||
|
||||
//go:embed l10n/locale
|
||||
var _translationFS embed.FS
|
||||
|
||||
var (
|
||||
_resourceTypeResource = "resource"
|
||||
_resourceTypeSpace = "storagespace"
|
||||
_resourceTypeShare = "share"
|
||||
_resourceTypeGlobal = "global"
|
||||
|
||||
_domain = "userlog"
|
||||
)
|
||||
|
||||
// OC10Notification is the oc10 style representation of an event
|
||||
// some fields are left out for simplicity
|
||||
type OC10Notification struct {
|
||||
EventID string `json:"notification_id"`
|
||||
Service string `json:"app"`
|
||||
UserName string `json:"user"`
|
||||
Timestamp string `json:"datetime"`
|
||||
ResourceID string `json:"object_id"`
|
||||
ResourceType string `json:"object_type"`
|
||||
Subject string `json:"subject"`
|
||||
SubjectRaw string `json:"subjectRich"`
|
||||
Message string `json:"message"`
|
||||
MessageRaw string `json:"messageRich"`
|
||||
MessageDetails map[string]any `json:"messageRichParameters"`
|
||||
}
|
||||
|
||||
// Converter is responsible for converting eventhistory events to OC10Notifications
|
||||
type Converter struct {
|
||||
locale string
|
||||
gatewaySelector pool.Selectable[gateway.GatewayAPIClient]
|
||||
serviceName string
|
||||
translationPath string
|
||||
defaultLanguage string
|
||||
serviceAccountContext context.Context
|
||||
|
||||
// cached within one request not to query other service too much
|
||||
spaces map[string]*storageprovider.StorageSpace
|
||||
users map[string]*user.User
|
||||
resources map[string]*storageprovider.ResourceInfo
|
||||
}
|
||||
|
||||
// NewConverter returns a new Converter
|
||||
func NewConverter(ctx context.Context, loc string, gatewaySelector pool.Selectable[gateway.GatewayAPIClient], name, translationPath, defaultLanguage string) *Converter {
|
||||
return &Converter{
|
||||
locale: loc,
|
||||
gatewaySelector: gatewaySelector,
|
||||
serviceName: name,
|
||||
translationPath: translationPath,
|
||||
defaultLanguage: defaultLanguage,
|
||||
serviceAccountContext: ctx,
|
||||
spaces: make(map[string]*storageprovider.StorageSpace),
|
||||
users: make(map[string]*user.User),
|
||||
resources: make(map[string]*storageprovider.ResourceInfo),
|
||||
}
|
||||
}
|
||||
|
||||
// ConvertEvent converts an eventhistory event to an OC10Notification
|
||||
func (c *Converter) ConvertEvent(eventid string, event any) (OC10Notification, error) {
|
||||
switch ev := event.(type) {
|
||||
default:
|
||||
return OC10Notification{}, fmt.Errorf("unknown event type: %T", ev)
|
||||
// file related
|
||||
case events.PostprocessingStepFinished:
|
||||
switch ev.FinishedStep {
|
||||
case events.PPStepAntivirus:
|
||||
res := ev.Result.(events.VirusscanResult)
|
||||
return c.virusMessage(eventid, VirusFound, ev.ExecutingUser, res.ResourceID, ev.Filename, res.Description, res.Scandate)
|
||||
case events.PPStepPolicies:
|
||||
return c.policiesMessage(eventid, PoliciesEnforced, ev.ExecutingUser, ev.Filename, time.Now())
|
||||
default:
|
||||
return OC10Notification{}, fmt.Errorf("unknown postprocessing step: %s", ev.FinishedStep)
|
||||
}
|
||||
|
||||
// space related
|
||||
case events.SpaceDisabled:
|
||||
return c.spaceMessage(eventid, SpaceDisabled, ev.Executant, ev.ID.GetOpaqueId(), ev.Timestamp)
|
||||
case events.SpaceDeleted:
|
||||
return c.spaceDeletedMessage(eventid, ev.Executant, ev.ID.GetOpaqueId(), ev.SpaceName, ev.Timestamp)
|
||||
case events.SpaceShared:
|
||||
return c.spaceMessage(eventid, SpaceShared, ev.Executant, ev.ID.GetOpaqueId(), ev.Timestamp)
|
||||
case events.SpaceUnshared:
|
||||
return c.spaceMessage(eventid, SpaceUnshared, ev.Executant, ev.ID.GetOpaqueId(), ev.Timestamp)
|
||||
case events.SpaceMembershipExpired:
|
||||
return c.spaceMessage(eventid, SpaceMembershipExpired, ev.SpaceOwner, ev.SpaceID.GetOpaqueId(), ev.ExpiredAt)
|
||||
|
||||
// share related
|
||||
case events.ShareCreated:
|
||||
return c.shareMessage(eventid, ShareCreated, ev.Executant, ev.ItemID, ev.ShareID, utils.TSToTime(ev.CTime))
|
||||
case events.ShareExpired:
|
||||
return c.shareMessage(eventid, ShareExpired, ev.ShareOwner, ev.ItemID, ev.ShareID, ev.ExpiredAt)
|
||||
case events.ShareRemoved:
|
||||
return c.shareMessage(eventid, ShareRemoved, ev.Executant, ev.ItemID, ev.ShareID, ev.Timestamp)
|
||||
}
|
||||
}
|
||||
|
||||
// ConvertGlobalEvent converts a global event to an OC10Notification
|
||||
func (c *Converter) ConvertGlobalEvent(typ string, data json.RawMessage) (OC10Notification, error) {
|
||||
switch typ {
|
||||
default:
|
||||
return OC10Notification{}, fmt.Errorf("unknown global event type: %s", typ)
|
||||
case "deprovision":
|
||||
var dd DeprovisionData
|
||||
if err := json.Unmarshal(data, &dd); err != nil {
|
||||
return OC10Notification{}, err
|
||||
}
|
||||
|
||||
return c.deprovisionMessage(PlatformDeprovision, dd.DeprovisionDate.Format(dd.DeprovisionFormat))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (c *Converter) spaceDeletedMessage(eventid string, executant *user.UserId, spaceid string, spacename string, ts time.Time) (OC10Notification, error) {
|
||||
usr, err := c.getUser(context.Background(), executant)
|
||||
if err != nil {
|
||||
return OC10Notification{}, err
|
||||
}
|
||||
|
||||
subj, subjraw, msg, msgraw, err := composeMessage(SpaceDeleted, c.locale, c.defaultLanguage, c.translationPath, map[string]any{
|
||||
"username": usr.GetDisplayName(),
|
||||
"spacename": spacename,
|
||||
})
|
||||
if err != nil {
|
||||
return OC10Notification{}, err
|
||||
}
|
||||
|
||||
space := &storageprovider.StorageSpace{Id: &storageprovider.StorageSpaceId{OpaqueId: spaceid}, Name: spacename}
|
||||
|
||||
return OC10Notification{
|
||||
EventID: eventid,
|
||||
Service: c.serviceName,
|
||||
UserName: usr.GetUsername(),
|
||||
Timestamp: ts.Format(time.RFC3339Nano),
|
||||
ResourceID: spaceid,
|
||||
ResourceType: _resourceTypeSpace,
|
||||
Subject: subj,
|
||||
SubjectRaw: subjraw,
|
||||
Message: msg,
|
||||
MessageRaw: msgraw,
|
||||
MessageDetails: generateDetails(usr, space, nil, nil),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Converter) spaceMessage(eventid string, nt NotificationTemplate, executant *user.UserId, spaceid string, ts time.Time) (OC10Notification, error) {
|
||||
usr, err := c.getUser(context.Background(), executant)
|
||||
if err != nil {
|
||||
return OC10Notification{}, err
|
||||
}
|
||||
|
||||
space, err := c.getSpace(c.serviceAccountContext, spaceid)
|
||||
if err != nil {
|
||||
return OC10Notification{}, err
|
||||
}
|
||||
|
||||
subj, subjraw, msg, msgraw, err := composeMessage(nt, c.locale, c.defaultLanguage, c.translationPath, map[string]any{
|
||||
"username": usr.GetDisplayName(),
|
||||
"spacename": space.GetName(),
|
||||
})
|
||||
if err != nil {
|
||||
return OC10Notification{}, err
|
||||
}
|
||||
|
||||
return OC10Notification{
|
||||
EventID: eventid,
|
||||
Service: c.serviceName,
|
||||
UserName: usr.GetUsername(),
|
||||
Timestamp: ts.Format(time.RFC3339Nano),
|
||||
ResourceID: spaceid,
|
||||
ResourceType: _resourceTypeSpace,
|
||||
Subject: subj,
|
||||
SubjectRaw: subjraw,
|
||||
Message: msg,
|
||||
MessageRaw: msgraw,
|
||||
MessageDetails: generateDetails(usr, space, nil, nil),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Converter) shareMessage(eventid string, nt NotificationTemplate, executant *user.UserId, resourceid *storageprovider.ResourceId, shareid *collaboration.ShareId, ts time.Time) (OC10Notification, error) {
|
||||
usr, err := c.getUser(context.Background(), executant)
|
||||
if err != nil {
|
||||
return OC10Notification{}, err
|
||||
}
|
||||
|
||||
info, err := c.getResource(c.serviceAccountContext, resourceid)
|
||||
if err != nil {
|
||||
return OC10Notification{}, err
|
||||
}
|
||||
|
||||
subj, subjraw, msg, msgraw, err := composeMessage(nt, c.locale, c.defaultLanguage, c.translationPath, map[string]any{
|
||||
"username": usr.GetDisplayName(),
|
||||
"resourcename": info.GetName(),
|
||||
})
|
||||
if err != nil {
|
||||
return OC10Notification{}, err
|
||||
}
|
||||
|
||||
return OC10Notification{
|
||||
EventID: eventid,
|
||||
Service: c.serviceName,
|
||||
UserName: usr.GetUsername(),
|
||||
Timestamp: ts.Format(time.RFC3339Nano),
|
||||
ResourceID: storagespace.FormatResourceID(info.GetId()),
|
||||
ResourceType: _resourceTypeShare,
|
||||
Subject: subj,
|
||||
SubjectRaw: subjraw,
|
||||
Message: msg,
|
||||
MessageRaw: msgraw,
|
||||
MessageDetails: generateDetails(usr, nil, info, shareid),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Converter) virusMessage(eventid string, nt NotificationTemplate, executant *user.User, rid *storageprovider.ResourceId, filename string, virus string, ts time.Time) (OC10Notification, error) {
|
||||
subj, subjraw, msg, msgraw, err := composeMessage(nt, c.locale, c.defaultLanguage, c.translationPath, map[string]any{
|
||||
"resourcename": filename,
|
||||
"virusdescription": virus,
|
||||
})
|
||||
if err != nil {
|
||||
return OC10Notification{}, err
|
||||
}
|
||||
|
||||
dets := map[string]any{
|
||||
"resource": map[string]string{
|
||||
"name": filename,
|
||||
},
|
||||
"virus": map[string]any{
|
||||
"name": virus,
|
||||
"scandate": ts,
|
||||
},
|
||||
}
|
||||
|
||||
return OC10Notification{
|
||||
EventID: eventid,
|
||||
Service: c.serviceName,
|
||||
UserName: executant.GetUsername(),
|
||||
Timestamp: ts.Format(time.RFC3339Nano),
|
||||
ResourceID: storagespace.FormatResourceID(rid),
|
||||
ResourceType: _resourceTypeResource,
|
||||
Subject: subj,
|
||||
SubjectRaw: subjraw,
|
||||
Message: msg,
|
||||
MessageRaw: msgraw,
|
||||
MessageDetails: dets,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Converter) policiesMessage(eventid string, nt NotificationTemplate, executant *user.User, filename string, ts time.Time) (OC10Notification, error) {
|
||||
subj, subjraw, msg, msgraw, err := composeMessage(nt, c.locale, c.defaultLanguage, c.translationPath, map[string]any{
|
||||
"resourcename": filename,
|
||||
})
|
||||
if err != nil {
|
||||
return OC10Notification{}, err
|
||||
}
|
||||
|
||||
dets := map[string]any{
|
||||
"resource": map[string]string{
|
||||
"name": filename,
|
||||
},
|
||||
}
|
||||
|
||||
return OC10Notification{
|
||||
EventID: eventid,
|
||||
Service: c.serviceName,
|
||||
UserName: executant.GetUsername(),
|
||||
Timestamp: ts.Format(time.RFC3339Nano),
|
||||
ResourceType: _resourceTypeResource,
|
||||
Subject: subj,
|
||||
SubjectRaw: subjraw,
|
||||
Message: msg,
|
||||
MessageRaw: msgraw,
|
||||
MessageDetails: dets,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Converter) deprovisionMessage(nt NotificationTemplate, deproDate string) (OC10Notification, error) {
|
||||
subj, subjraw, msg, msgraw, err := composeMessage(nt, c.locale, c.defaultLanguage, c.translationPath, map[string]any{
|
||||
"date": deproDate,
|
||||
})
|
||||
if err != nil {
|
||||
return OC10Notification{}, err
|
||||
}
|
||||
|
||||
return OC10Notification{
|
||||
EventID: "deprovision",
|
||||
Service: c.serviceName,
|
||||
// UserName: executant.GetUsername(), // TODO: do we need the deprovisioner?
|
||||
Timestamp: time.Now().Format(time.RFC3339Nano), // Fake timestamp? Or we store one with the event?
|
||||
ResourceType: _resourceTypeResource,
|
||||
Subject: subj,
|
||||
SubjectRaw: subjraw,
|
||||
Message: msg,
|
||||
MessageRaw: msgraw,
|
||||
MessageDetails: map[string]any{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Converter) getSpace(ctx context.Context, spaceID string) (*storageprovider.StorageSpace, error) {
|
||||
if space, ok := c.spaces[spaceID]; ok {
|
||||
return space, nil
|
||||
}
|
||||
gwc, err := c.gatewaySelector.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
space, err := utils.GetSpace(ctx, spaceID, gwc)
|
||||
if err == nil {
|
||||
c.spaces[spaceID] = space
|
||||
}
|
||||
return space, err
|
||||
}
|
||||
|
||||
func (c *Converter) getResource(ctx context.Context, resourceID *storageprovider.ResourceId) (*storageprovider.ResourceInfo, error) {
|
||||
if r, ok := c.resources[resourceID.GetOpaqueId()]; ok {
|
||||
return r, nil
|
||||
}
|
||||
gwc, err := c.gatewaySelector.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resource, err := utils.GetResourceByID(ctx, resourceID, gwc)
|
||||
if err == nil {
|
||||
c.resources[resourceID.GetOpaqueId()] = resource
|
||||
}
|
||||
return resource, err
|
||||
}
|
||||
|
||||
func (c *Converter) getUser(ctx context.Context, userID *user.UserId) (*user.User, error) {
|
||||
if u, ok := c.users[userID.GetOpaqueId()]; ok {
|
||||
return u, nil
|
||||
}
|
||||
gwc, err := c.gatewaySelector.Next()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
usr, err := utils.GetUserNoGroups(ctx, userID, gwc)
|
||||
if err == nil {
|
||||
c.users[userID.GetOpaqueId()] = usr
|
||||
}
|
||||
return usr, err
|
||||
}
|
||||
|
||||
func composeMessage(nt NotificationTemplate, locale, defaultLocale, path string, vars map[string]any) (string, string, string, string, error) {
|
||||
subjectraw, messageraw := loadTemplates(nt, locale, defaultLocale, path)
|
||||
|
||||
subject, err := executeTemplate(subjectraw, vars)
|
||||
if err != nil {
|
||||
return "", "", "", "", err
|
||||
}
|
||||
|
||||
message, err := executeTemplate(messageraw, vars)
|
||||
return subject, subjectraw, message, messageraw, err
|
||||
}
|
||||
|
||||
func loadTemplates(nt NotificationTemplate, locale, defaultLocale, path string) (string, string) {
|
||||
t := l10n.NewTranslatorFromCommonConfig(defaultLocale, _domain, path, _translationFS, "l10n/locale").Locale(locale)
|
||||
return t.Get(nt.Subject, []any{}...), t.Get(nt.Message, []any{}...)
|
||||
}
|
||||
|
||||
func executeTemplate(raw string, vars map[string]any) (string, error) {
|
||||
for o, n := range _placeholders {
|
||||
raw = strings.ReplaceAll(raw, o, n)
|
||||
}
|
||||
tpl, err := template.New("").Parse(raw)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var writer bytes.Buffer
|
||||
if err := tpl.Execute(&writer, vars); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return writer.String(), nil
|
||||
}
|
||||
|
||||
func generateDetails(user *user.User, space *storageprovider.StorageSpace, item *storageprovider.ResourceInfo, shareid *collaboration.ShareId) map[string]any {
|
||||
details := make(map[string]any)
|
||||
|
||||
if user != nil {
|
||||
details["user"] = map[string]string{
|
||||
"id": user.GetId().GetOpaqueId(),
|
||||
"name": user.GetUsername(),
|
||||
"displayname": user.GetDisplayName(),
|
||||
}
|
||||
}
|
||||
|
||||
if space != nil {
|
||||
details["space"] = map[string]string{
|
||||
"id": space.GetId().GetOpaqueId(),
|
||||
"name": space.GetName(),
|
||||
}
|
||||
}
|
||||
|
||||
if item != nil {
|
||||
details["resource"] = map[string]string{
|
||||
"id": storagespace.FormatResourceID(item.GetId()),
|
||||
"name": item.GetName(),
|
||||
}
|
||||
}
|
||||
|
||||
if shareid != nil {
|
||||
details["share"] = map[string]string{
|
||||
"id": shareid.GetOpaqueId(),
|
||||
}
|
||||
}
|
||||
|
||||
return details
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
"github.com/qsfera/server/pkg/log"
|
||||
"github.com/qsfera/server/pkg/middleware"
|
||||
settingssvc "github.com/qsfera/server/protogen/gen/qsfera/services/settings/v0"
|
||||
"github.com/qsfera/server/services/settings/pkg/store/defaults"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/events"
|
||||
micrometadata "go-micro.dev/v4/metadata"
|
||||
)
|
||||
|
||||
type userlogFilter struct {
|
||||
log log.Logger
|
||||
valueClient settingssvc.ValueService
|
||||
}
|
||||
|
||||
func newUserlogFilter(l log.Logger, vc settingssvc.ValueService) *userlogFilter {
|
||||
return &userlogFilter{log: l, valueClient: vc}
|
||||
}
|
||||
|
||||
// execute removes users who should not receive an in-app notifications for the event
|
||||
func (ulf userlogFilter) execute(ctx context.Context, event events.Event, executant *user.UserId, users []string) []string {
|
||||
filteredUsers := ulf.filterExecutant(users, executant)
|
||||
return ulf.filterUsersBySettings(ctx, filteredUsers, event)
|
||||
}
|
||||
|
||||
// filterExecutant removes the executant
|
||||
func (ulf userlogFilter) filterExecutant(users []string, executant *user.UserId) []string {
|
||||
var filteredUsers []string
|
||||
for _, u := range users {
|
||||
if u != executant.GetOpaqueId() {
|
||||
filteredUsers = append(filteredUsers, u)
|
||||
}
|
||||
}
|
||||
return filteredUsers
|
||||
}
|
||||
|
||||
// filterUsersBySettings removes users who have disabled in-app notifications for the event
|
||||
func (ulf userlogFilter) filterUsersBySettings(ctx context.Context, users []string, event events.Event) []string {
|
||||
var filteredUsers []string
|
||||
var settingId string
|
||||
// map type to settings key
|
||||
switch event.Event.(type) {
|
||||
case events.ShareCreated:
|
||||
settingId = defaults.SettingUUIDProfileEventShareCreated
|
||||
case events.ShareRemoved:
|
||||
settingId = defaults.SettingUUIDProfileEventShareRemoved
|
||||
case events.ShareExpired:
|
||||
settingId = defaults.SettingUUIDProfileEventShareExpired
|
||||
case events.SpaceShared:
|
||||
settingId = defaults.SettingUUIDProfileEventSpaceShared
|
||||
case events.SpaceUnshared:
|
||||
settingId = defaults.SettingUUIDProfileEventSpaceUnshared
|
||||
case events.SpaceMembershipExpired:
|
||||
settingId = defaults.SettingUUIDProfileEventSpaceMembershipExpired
|
||||
case events.SpaceDisabled:
|
||||
settingId = defaults.SettingUUIDProfileEventSpaceDisabled
|
||||
case events.SpaceDeleted:
|
||||
settingId = defaults.SettingUUIDProfileEventSpaceDeleted
|
||||
default:
|
||||
// event that cannot be disabled
|
||||
return users
|
||||
}
|
||||
|
||||
for _, u := range users {
|
||||
enabled, err := getSetting(ctx, ulf.valueClient, u, settingId)
|
||||
if err != nil {
|
||||
ulf.log.Error().Err(err).Str("userId", u).Str("settingId", settingId).Msg("cannot get user event setting")
|
||||
continue
|
||||
}
|
||||
if enabled {
|
||||
filteredUsers = append(filteredUsers, u)
|
||||
}
|
||||
}
|
||||
|
||||
return filteredUsers
|
||||
}
|
||||
|
||||
func getSetting(ctx context.Context, vc settingssvc.ValueService, userId string, settingId string) (bool, error) {
|
||||
resp, err := vc.GetValueByUniqueIdentifiers(
|
||||
micrometadata.Set(ctx, middleware.AccountID, userId),
|
||||
&settingssvc.GetValueByUniqueIdentifiersRequest{
|
||||
AccountUuid: userId,
|
||||
SettingId: settingId,
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
val := resp.GetValue().GetValue().GetCollectionValue().GetValues()
|
||||
for _, option := range val {
|
||||
if option.GetKey() == "in-app" {
|
||||
return option.GetBoolValue(), nil
|
||||
}
|
||||
}
|
||||
return false, errors.New("no setting found")
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
"github.com/qsfera/server/pkg/log"
|
||||
settingsmsg "github.com/qsfera/server/protogen/gen/qsfera/messages/settings/v0"
|
||||
settings "github.com/qsfera/server/protogen/gen/qsfera/services/settings/v0"
|
||||
settingsmocks "github.com/qsfera/server/protogen/gen/qsfera/services/settings/v0/mocks"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/events"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("NotificationFilter", func() {
|
||||
var (
|
||||
testLogger = log.NewLogger()
|
||||
vs *settingsmocks.ValueService
|
||||
ulf userlogFilter
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
vs = &settingsmocks.ValueService{}
|
||||
ulf = userlogFilter{
|
||||
log: testLogger,
|
||||
valueClient: vs,
|
||||
}
|
||||
})
|
||||
|
||||
setupMockValueService := func(inApp bool) *settingsmocks.ValueService {
|
||||
vs := settingsmocks.ValueService{}
|
||||
vs.On("GetValueByUniqueIdentifiers", mock.Anything, mock.Anything).Return(&settings.GetValueResponse{
|
||||
Value: &settingsmsg.ValueWithIdentifier{
|
||||
Value: &settingsmsg.Value{
|
||||
Value: &settingsmsg.Value_CollectionValue{
|
||||
CollectionValue: &settingsmsg.CollectionValue{
|
||||
Values: []*settingsmsg.CollectionOption{
|
||||
{
|
||||
Key: "in-app",
|
||||
Option: &settingsmsg.CollectionOption_BoolValue{BoolValue: inApp},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
return &vs
|
||||
}
|
||||
|
||||
Describe("execute", func() {
|
||||
It("handles executants", func() {
|
||||
vs.On("GetValueByUniqueIdentifiers", mock.Anything, mock.Anything).Return(nil, nil)
|
||||
|
||||
Expect(ulf.execute(context.TODO(), events.Event{}, &user.UserId{OpaqueId: "executant"}, []string{"foo"})).To(ConsistOf("foo"))
|
||||
})
|
||||
It("handles connection errors", func() {
|
||||
vs.On("GetValueByUniqueIdentifiers", mock.Anything, mock.Anything).Return(nil, errors.New("no connection to ValueService"))
|
||||
|
||||
Expect(ulf.execute(context.TODO(), events.Event{Event: events.ShareCreated{}}, nil, []string{"foo"})).To(BeEmpty())
|
||||
})
|
||||
It("handles no setting", func() {
|
||||
vs.On("GetValueByUniqueIdentifiers", mock.Anything, mock.Anything).Return(&settings.GetValueResponse{}, nil)
|
||||
|
||||
Expect(ulf.execute(context.TODO(), events.Event{Event: events.ShareCreated{}}, nil, []string{"foo"})).To(BeEmpty())
|
||||
})
|
||||
It("handles nil response", func() {
|
||||
vs.On("GetValueByUniqueIdentifiers", mock.Anything, mock.Anything).Return(nil, nil)
|
||||
|
||||
Expect(ulf.execute(context.TODO(), events.Event{Event: events.ShareCreated{}}, nil, []string{"foo"})).To(BeEmpty())
|
||||
})
|
||||
It("handles events that can not be disabled", func() {
|
||||
ulf.valueClient = setupMockValueService(true)
|
||||
|
||||
Expect(ulf.execute(context.TODO(), events.Event{Event: events.BytesReceived{}}, nil, []string{"foo"})).To(ConsistOf("foo"))
|
||||
})
|
||||
|
||||
It("handles ShareCreated events", func() {
|
||||
ulf.valueClient = setupMockValueService(true)
|
||||
|
||||
Expect(ulf.execute(context.TODO(), events.Event{Event: events.ShareCreated{}}, nil, []string{"foo"})).To(ConsistOf("foo"))
|
||||
})
|
||||
|
||||
It("handles ShareRemoved events", func() {
|
||||
ulf.valueClient = setupMockValueService(true)
|
||||
|
||||
Expect(ulf.execute(context.TODO(), events.Event{Event: events.ShareRemoved{}}, nil, []string{"foo"})).To(ConsistOf("foo"))
|
||||
})
|
||||
|
||||
It("handles ShareExpired events", func() {
|
||||
ulf.valueClient = setupMockValueService(true)
|
||||
|
||||
Expect(ulf.execute(context.TODO(), events.Event{Event: events.ShareExpired{}}, nil, []string{"foo"})).To(ConsistOf("foo"))
|
||||
})
|
||||
|
||||
It("handles SpaceShared enabled", func() {
|
||||
ulf.valueClient = setupMockValueService(true)
|
||||
|
||||
Expect(ulf.execute(context.TODO(), events.Event{Event: events.SpaceShared{}}, nil, []string{"foo"})).To(ConsistOf("foo"))
|
||||
})
|
||||
|
||||
It("handles SpaceUnshared enabled", func() {
|
||||
ulf.valueClient = setupMockValueService(true)
|
||||
|
||||
Expect(ulf.execute(context.TODO(), events.Event{Event: events.SpaceUnshared{}}, nil, []string{"foo"})).To(ConsistOf("foo"))
|
||||
})
|
||||
|
||||
It("handles SpaceMembershipExpired enabled", func() {
|
||||
ulf.valueClient = setupMockValueService(true)
|
||||
|
||||
Expect(ulf.execute(context.TODO(), events.Event{Event: events.SpaceMembershipExpired{}}, nil, []string{"foo"})).To(ConsistOf("foo"))
|
||||
})
|
||||
|
||||
It("handles SpaceDisabled enabled", func() {
|
||||
ulf.valueClient = setupMockValueService(true)
|
||||
|
||||
Expect(ulf.execute(context.TODO(), events.Event{Event: events.SpaceDisabled{}}, nil, []string{"foo"})).To(ConsistOf("foo"))
|
||||
})
|
||||
|
||||
It("handles SpaceDeleted enabled", func() {
|
||||
ulf.valueClient = setupMockValueService(true)
|
||||
|
||||
Expect(ulf.execute(context.TODO(), events.Event{Event: events.SpaceDeleted{}}, nil, []string{"foo"})).To(ConsistOf("foo"))
|
||||
})
|
||||
|
||||
It("handles ShareCreated disabled", func() {
|
||||
ulf.valueClient = setupMockValueService(false)
|
||||
|
||||
Expect(ulf.execute(context.TODO(), events.Event{Event: events.ShareCreated{}}, nil, []string{"foo"})).To(BeEmpty())
|
||||
})
|
||||
|
||||
It("handles ShareRemoved disabled", func() {
|
||||
ulf.valueClient = setupMockValueService(false)
|
||||
|
||||
Expect(ulf.execute(context.TODO(), events.Event{Event: events.ShareRemoved{}}, nil, []string{"foo"})).To(BeEmpty())
|
||||
})
|
||||
|
||||
It("handles ShareExpired disabled", func() {
|
||||
ulf.valueClient = setupMockValueService(false)
|
||||
|
||||
Expect(ulf.execute(context.TODO(), events.Event{Event: events.ShareExpired{}}, nil, []string{"foo"})).To(BeEmpty())
|
||||
})
|
||||
|
||||
It("handles SpaceShared disabled", func() {
|
||||
ulf.valueClient = setupMockValueService(false)
|
||||
|
||||
Expect(ulf.execute(context.TODO(), events.Event{Event: events.SpaceShared{}}, nil, []string{"foo"})).To(BeEmpty())
|
||||
})
|
||||
|
||||
It("handles SpaceUnshared disabled", func() {
|
||||
ulf.valueClient = setupMockValueService(false)
|
||||
|
||||
Expect(ulf.execute(context.TODO(), events.Event{Event: events.SpaceUnshared{}}, nil, []string{"foo"})).To(BeEmpty())
|
||||
})
|
||||
|
||||
It("handles SpaceMembershipExpired disabled", func() {
|
||||
ulf.valueClient = setupMockValueService(false)
|
||||
|
||||
Expect(ulf.execute(context.TODO(), events.Event{Event: events.SpaceMembershipExpired{}}, nil, []string{"foo"})).To(BeEmpty())
|
||||
})
|
||||
|
||||
It("handles SpaceDisabled disabled", func() {
|
||||
ulf.valueClient = setupMockValueService(false)
|
||||
|
||||
Expect(ulf.execute(context.TODO(), events.Event{Event: events.SpaceDisabled{}}, nil, []string{"foo"})).To(BeEmpty())
|
||||
})
|
||||
|
||||
It("handles SpaceDeleted disabled", func() {
|
||||
ulf.valueClient = setupMockValueService(false)
|
||||
|
||||
Expect(ulf.execute(context.TODO(), events.Event{Event: events.SpaceDeleted{}}, nil, []string{"foo"})).To(BeEmpty())
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,17 @@
|
||||
package service
|
||||
|
||||
import "time"
|
||||
|
||||
var (
|
||||
_globalEventsKey = "global-events"
|
||||
)
|
||||
|
||||
// DeprovisionData is the data needed for the deprovision global event
|
||||
type DeprovisionData struct {
|
||||
// The deprovision date
|
||||
DeprovisionDate time.Time `json:"deprovision_date"`
|
||||
// The Format of the deprvision date
|
||||
DeprovisionFormat string
|
||||
// The user who stored the deprovision message
|
||||
Deprovisioner string
|
||||
}
|
||||
@@ -0,0 +1,265 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/qsfera/server/pkg/roles"
|
||||
"github.com/qsfera/server/services/graph/pkg/errorcode"
|
||||
settings "github.com/qsfera/server/services/settings/pkg/service/v0"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/appctx"
|
||||
revactx "github.com/opencloud-eu/reva/v2/pkg/ctx"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
)
|
||||
|
||||
// HeaderAcceptLanguage is the header where the client can set the locale
|
||||
var HeaderAcceptLanguage = "Accept-Language"
|
||||
|
||||
// ServeHTTP fulfills Handler interface
|
||||
func (ul *UserlogService) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ul.m.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
// HandleGetEvents is the GET handler for events
|
||||
func (ul *UserlogService) HandleGetEvents(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, span := ul.tracer.Start(r.Context(), "HandleGetEvents")
|
||||
defer span.End()
|
||||
u, ok := revactx.ContextGetUser(ctx)
|
||||
if !ok {
|
||||
ul.log.Error().Int("returned statuscode", http.StatusUnauthorized).Msg("user unauthorized")
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
evs, err := ul.GetEvents(ctx, u.GetId().GetOpaqueId())
|
||||
if err != nil {
|
||||
ul.log.Error().Err(err).Int("returned statuscode", http.StatusInternalServerError).Msg("get events failed")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
span.SetAttributes(attribute.KeyValue{
|
||||
Key: "events",
|
||||
Value: attribute.IntValue(len(evs)),
|
||||
})
|
||||
|
||||
gwc, err := ul.gatewaySelector.Next()
|
||||
if err != nil {
|
||||
ul.log.Error().Err(err).Msg("cant get gateway client")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
ctx, err = utils.GetServiceUserContext(ul.cfg.ServiceAccount.ServiceAccountID, gwc, ul.cfg.ServiceAccount.ServiceAccountSecret)
|
||||
if err != nil {
|
||||
ul.log.Error().Err(err).Msg("cant get service account")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
conv := NewConverter(ctx, r.Header.Get(HeaderAcceptLanguage), ul.gatewaySelector, ul.cfg.Service.Name, ul.cfg.TranslationPath, ul.cfg.DefaultLanguage)
|
||||
|
||||
var outdatedEvents []string
|
||||
resp := GetEventResponseOC10{}
|
||||
for _, e := range evs {
|
||||
etype, ok := ul.registeredEvents[e.Type]
|
||||
if !ok {
|
||||
ul.log.Error().Str("eventid", e.Id).Str("eventtype", e.Type).Msg("event not registered")
|
||||
continue
|
||||
}
|
||||
|
||||
einterface, err := etype.Unmarshal(e.Event)
|
||||
if err != nil {
|
||||
ul.log.Error().Str("eventid", e.Id).Str("eventtype", e.Type).Msg("failed to umarshal event")
|
||||
continue
|
||||
}
|
||||
|
||||
noti, err := conv.ConvertEvent(e.Id, einterface)
|
||||
if err != nil {
|
||||
if utils.IsErrNotFound(err) || utils.IsErrPermissionDenied(err) {
|
||||
outdatedEvents = append(outdatedEvents, e.Id)
|
||||
continue
|
||||
}
|
||||
ul.log.Error().Err(err).Str("eventid", e.Id).Str("eventtype", e.Type).Msg("failed to convert event")
|
||||
continue
|
||||
}
|
||||
|
||||
resp.OCS.Data = append(resp.OCS.Data, noti)
|
||||
}
|
||||
|
||||
// delete outdated events asynchronously
|
||||
if len(outdatedEvents) > 0 {
|
||||
go func() {
|
||||
err := ul.DeleteEvents(u.GetId().GetOpaqueId(), outdatedEvents)
|
||||
if err != nil {
|
||||
ul.log.Error().Err(err).Msg("failed to delete events")
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
glevs, err := ul.GetGlobalEvents(ctx)
|
||||
if err != nil {
|
||||
ul.log.Error().Err(err).Int("returned statuscode", http.StatusInternalServerError).Msg("get global events failed")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
for t, data := range glevs {
|
||||
noti, err := conv.ConvertGlobalEvent(t, data)
|
||||
if err != nil {
|
||||
ul.log.Error().Err(err).Str("eventtype", t).Msg("failed to convert event")
|
||||
continue
|
||||
}
|
||||
|
||||
resp.OCS.Data = append(resp.OCS.Data, noti)
|
||||
}
|
||||
|
||||
resp.OCS.Meta.StatusCode = http.StatusOK
|
||||
b, _ := json.Marshal(resp)
|
||||
w.Write(b)
|
||||
}
|
||||
|
||||
// HandlePostGlobaelEvent is the POST handler for global events
|
||||
func (ul *UserlogService) HandlePostGlobalEvent(w http.ResponseWriter, r *http.Request) {
|
||||
var req PostEventsRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
ul.log.Error().Err(err).Int("returned statuscode", http.StatusBadRequest).Msg("request body is malformed")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := ul.StoreGlobalEvent(r.Context(), req.Type, req.Data); err != nil {
|
||||
ul.log.Error().Err(err).Msg("post: error storing global event")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
// HandleDeleteGlobalEvent is the DELETE handler for global events
|
||||
func (ul *UserlogService) HandleDeleteGlobalEvent(w http.ResponseWriter, r *http.Request) {
|
||||
var req DeleteEventsRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
ul.log.Error().Err(err).Int("returned statuscode", http.StatusBadRequest).Msg("request body is malformed")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := ul.DeleteGlobalEvents(r.Context(), req.IDs); err != nil {
|
||||
ul.log.Error().Err(err).Int("returned statuscode", http.StatusInternalServerError).Msg("delete events failed")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
// HandleDeleteEvents is the DELETE handler for events
|
||||
func (ul *UserlogService) HandleDeleteEvents(w http.ResponseWriter, r *http.Request) {
|
||||
u, ok := revactx.ContextGetUser(r.Context())
|
||||
if !ok {
|
||||
ul.log.Error().Int("returned statuscode", http.StatusUnauthorized).Msg("user unauthorized")
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
var req DeleteEventsRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
ul.log.Error().Err(err).Int("returned statuscode", http.StatusBadRequest).Msg("request body is malformed")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := ul.DeleteEvents(u.GetId().GetOpaqueId(), req.IDs); err != nil {
|
||||
ul.log.Error().Err(err).Int("returned statuscode", http.StatusInternalServerError).Msg("delete events failed")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
// GetEventResponseOC10 is the response from GET events endpoint in oc10 style
|
||||
type GetEventResponseOC10 struct {
|
||||
OCS struct {
|
||||
Meta struct {
|
||||
Message string `json:"message"`
|
||||
Status string `json:"status"`
|
||||
StatusCode int `json:"statuscode"`
|
||||
} `json:"meta"`
|
||||
Data []OC10Notification `json:"data"`
|
||||
} `json:"ocs"`
|
||||
}
|
||||
|
||||
// DeleteEventsRequest is the expected body for the delete request
|
||||
type DeleteEventsRequest struct {
|
||||
IDs []string `json:"ids"`
|
||||
}
|
||||
|
||||
// PostEventsRequest is the expected body for the post request
|
||||
type PostEventsRequest struct {
|
||||
// the event type, e.g. "deprovision"
|
||||
Type string `json:"type"`
|
||||
// arbitray data for the event
|
||||
Data map[string]string `json:"data"`
|
||||
}
|
||||
|
||||
// RequireAdminOrSecret middleware allows only requests if the requesting user is an admin or knows the static secret
|
||||
func RequireAdminOrSecret(rm *roles.Manager, secret string) func(http.HandlerFunc) http.HandlerFunc {
|
||||
return func(next http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
// allow bypassing admin requirement by sending the correct secret
|
||||
if secret != "" && r.Header.Get("secret") == secret {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
isadmin, err := isAdmin(r.Context(), rm)
|
||||
if err != nil {
|
||||
errorcode.GeneralException.Render(w, r, http.StatusInternalServerError, "")
|
||||
return
|
||||
}
|
||||
|
||||
if isadmin {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
errorcode.ItemNotFound.Render(w, r, http.StatusNotFound, "Not found")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// isAdmin determines if the user in the context is an admin / has account management permissions
|
||||
func isAdmin(ctx context.Context, rm *roles.Manager) (bool, error) {
|
||||
logger := appctx.GetLogger(ctx)
|
||||
|
||||
u, ok := revactx.ContextGetUser(ctx)
|
||||
uid := u.GetId().GetOpaqueId()
|
||||
if !ok || uid == "" {
|
||||
logger.Error().Str("userid", uid).Msg("user not in context")
|
||||
return false, errors.New("no user in context")
|
||||
}
|
||||
// get roles from context
|
||||
roleIDs, ok := roles.ReadRoleIDsFromContext(ctx)
|
||||
if !ok {
|
||||
logger.Debug().Str("userid", uid).Msg("No roles in context, contacting settings service")
|
||||
var err error
|
||||
roleIDs, err = rm.FindRoleIDsForUser(ctx, uid)
|
||||
if err != nil {
|
||||
logger.Err(err).Str("userid", uid).Msg("failed to get roles for user")
|
||||
return false, err
|
||||
}
|
||||
|
||||
if len(roleIDs) == 0 {
|
||||
logger.Err(err).Str("userid", uid).Msg("user has no roles")
|
||||
return false, errors.New("user has no roles")
|
||||
}
|
||||
}
|
||||
|
||||
// check if permission is present in roles of the authenticated account
|
||||
return rm.FindPermissionByID(ctx, roleIDs, settings.AccountManagementPermissionID) != nil, nil
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
|
||||
[o:qsfera-eu:p:qsfera-eu:r:qsfera-userlog]
|
||||
file_filter = locale/<lang>/LC_MESSAGES/userlog.po
|
||||
minimum_perc = 75
|
||||
resource_name = qsfera-userlog
|
||||
source_file = userlog.pot
|
||||
source_lang = en
|
||||
type = PO
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
# 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.
|
||||
#
|
||||
# Translators:
|
||||
# Ivan Fustero, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2026-05-07 00:02+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: Ivan Fustero, 2025\n"
|
||||
"Language-Team: Catalan (https://app.transifex.com/qsfera-eu/teams/204053/ca/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: ca\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: pkg/service/templates.go:39
|
||||
msgid "Access to Space {space} lost"
|
||||
msgstr "S'ha perdut l'accés a l'espai {space}"
|
||||
|
||||
#: pkg/service/templates.go:54
|
||||
msgid "Access to {resource} expired"
|
||||
msgstr "L'accés a {resource} ha caducat"
|
||||
|
||||
#: pkg/service/templates.go:59
|
||||
msgid ""
|
||||
"Attention! The instance will be shut down and deprovisioned on {date}. "
|
||||
"Download all your data before that date as no access past that date is "
|
||||
"possible."
|
||||
msgstr ""
|
||||
"Atenció! La instància es tancarà el {date}. Descarrega les dades abans, "
|
||||
"perquè després no s’hi podrà accedir."
|
||||
|
||||
#: pkg/service/templates.go:58
|
||||
msgid "Instance will be shut down and deprovisioned"
|
||||
msgstr "La instància es tancarà i es desactivarà"
|
||||
|
||||
#: pkg/service/templates.go:38
|
||||
msgid "Membership expired"
|
||||
msgstr "La pertinença ha expirat"
|
||||
|
||||
#: pkg/service/templates.go:13
|
||||
msgid "Policies enforced"
|
||||
msgstr "Polítiques aplicades"
|
||||
|
||||
#: pkg/service/templates.go:23
|
||||
msgid "Removed from Space"
|
||||
msgstr "Eliminat de l'espai"
|
||||
|
||||
#: pkg/service/templates.go:43
|
||||
msgid "Resource shared"
|
||||
msgstr "Recurs compartit"
|
||||
|
||||
#: pkg/service/templates.go:48
|
||||
msgid "Resource unshared"
|
||||
msgstr "Recurs sense compartir"
|
||||
|
||||
#: pkg/service/templates.go:53
|
||||
msgid "Share expired"
|
||||
msgstr "La compartició ha caducat"
|
||||
|
||||
#: pkg/service/templates.go:33
|
||||
msgid "Space deleted"
|
||||
msgstr "Espai eliminat"
|
||||
|
||||
#: pkg/service/templates.go:28
|
||||
msgid "Space disabled"
|
||||
msgstr "Espai desactivat"
|
||||
|
||||
#: pkg/service/templates.go:18
|
||||
msgid "Space shared"
|
||||
msgstr "Espai compartit"
|
||||
|
||||
#: pkg/service/templates.go:14
|
||||
msgid ""
|
||||
"The file {resource} was deleted because it violates the restrictions of this"
|
||||
" cloud. This could be due to an unsupported file type, potentially harmful "
|
||||
"or prohibited content, or file size limits."
|
||||
msgstr ""
|
||||
|
||||
#: pkg/service/templates.go:8
|
||||
msgid "Virus found"
|
||||
msgstr "S'ha trobat un virus"
|
||||
|
||||
#: pkg/service/templates.go:9
|
||||
msgid "Virus found in {resource}. Upload not possible. Virus: {virus}"
|
||||
msgstr ""
|
||||
"S'ha trobat un virus a {resource}. La càrrega no és possible. Virus: {virus}"
|
||||
|
||||
#: pkg/service/templates.go:19
|
||||
msgid "{user} added you to Space {space}"
|
||||
msgstr "{user} us ha afegit a l'espai {space}"
|
||||
|
||||
#: pkg/service/templates.go:34
|
||||
msgid "{user} deleted Space {space}"
|
||||
msgstr "{user} ha suprimit l'espai {space}"
|
||||
|
||||
#: pkg/service/templates.go:29
|
||||
msgid "{user} disabled Space {space}"
|
||||
msgstr "{user} ha desactvat l'espai {space}"
|
||||
|
||||
#: pkg/service/templates.go:24
|
||||
msgid "{user} removed you from Space {space}"
|
||||
msgstr "{user} us ha tret de l'espai {space}"
|
||||
|
||||
#: pkg/service/templates.go:44
|
||||
msgid "{user} shared {resource} with you"
|
||||
msgstr "{user} ha compartit {resource} amb vós"
|
||||
|
||||
#: pkg/service/templates.go:49
|
||||
msgid "{user} unshared {resource} with you"
|
||||
msgstr "{user} ha deixat de compartir {resource} amb vós"
|
||||
@@ -0,0 +1,126 @@
|
||||
# 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.
|
||||
#
|
||||
# Translators:
|
||||
# Jörn Friedrich Dreyer <jfd@butonic.de>, 2025
|
||||
# Jannick Kuhr, 2026
|
||||
# Christian Richter, 2026
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2026-05-08 00:02+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: Christian Richter, 2026\n"
|
||||
"Language-Team: German (https://app.transifex.com/qsfera-eu/teams/204053/de/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: de\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: pkg/service/templates.go:39
|
||||
msgid "Access to Space {space} lost"
|
||||
msgstr "Zugriff auf Space {space} verloren"
|
||||
|
||||
#: pkg/service/templates.go:54
|
||||
msgid "Access to {resource} expired"
|
||||
msgstr "Zugriff auf {resource} abgelaufen"
|
||||
|
||||
#: pkg/service/templates.go:59
|
||||
msgid ""
|
||||
"Attention! The instance will be shut down and deprovisioned on {date}. "
|
||||
"Download all your data before that date as no access past that date is "
|
||||
"possible."
|
||||
msgstr ""
|
||||
"Achtung! Diese Instanz wird am {date} heruntergefahren und außer Betrieb "
|
||||
"genommen werden. Laden Sie Ihre Daten vor diesem Tag herunter, da Sie danach"
|
||||
" nicht mehr darauf zugreifen können."
|
||||
|
||||
#: pkg/service/templates.go:58
|
||||
msgid "Instance will be shut down and deprovisioned"
|
||||
msgstr "Instanz wird heruntergefahren und außer Betrieb genommen werden."
|
||||
|
||||
#: pkg/service/templates.go:38
|
||||
msgid "Membership expired"
|
||||
msgstr "Mitgliedschaft abgelaufen"
|
||||
|
||||
#: pkg/service/templates.go:13
|
||||
msgid "Policies enforced"
|
||||
msgstr "Verstoß gegen Richtlinien"
|
||||
|
||||
#: pkg/service/templates.go:23
|
||||
msgid "Removed from Space"
|
||||
msgstr "Aus Space entfernt"
|
||||
|
||||
#: pkg/service/templates.go:43
|
||||
msgid "Resource shared"
|
||||
msgstr "Neue Freigabe"
|
||||
|
||||
#: pkg/service/templates.go:48
|
||||
msgid "Resource unshared"
|
||||
msgstr "Freigabe entfernt"
|
||||
|
||||
#: pkg/service/templates.go:53
|
||||
msgid "Share expired"
|
||||
msgstr "Freigabe abgelaufen"
|
||||
|
||||
#: pkg/service/templates.go:33
|
||||
msgid "Space deleted"
|
||||
msgstr "Space gelöscht"
|
||||
|
||||
#: pkg/service/templates.go:28
|
||||
msgid "Space disabled"
|
||||
msgstr "Space deaktiviert"
|
||||
|
||||
#: pkg/service/templates.go:18
|
||||
msgid "Space shared"
|
||||
msgstr "Space freigegeben"
|
||||
|
||||
#: pkg/service/templates.go:14
|
||||
msgid ""
|
||||
"The file {resource} was deleted because it violates the restrictions of this"
|
||||
" cloud. This could be due to an unsupported file type, potentially harmful "
|
||||
"or prohibited content, or file size limits."
|
||||
msgstr ""
|
||||
"\"Die Datei {resource} wurde gelöscht, weil sie gegen die Begrenzungen "
|
||||
"dieser Cloud verstößt. Das kann ein nicht zugelassener Dateityp sein, "
|
||||
"potentiell gefährliche oder verbotene Inhalte oder Dateigrößenbegrenzungen\""
|
||||
|
||||
#: pkg/service/templates.go:8
|
||||
msgid "Virus found"
|
||||
msgstr "Virus gefunden"
|
||||
|
||||
#: pkg/service/templates.go:9
|
||||
msgid "Virus found in {resource}. Upload not possible. Virus: {virus}"
|
||||
msgstr ""
|
||||
"In {resource} wurde potenziell schädlicher Code gefunden. Das Hochladen wurde abgebrochen.\n"
|
||||
"Grund: {virus}"
|
||||
|
||||
#: pkg/service/templates.go:19
|
||||
msgid "{user} added you to Space {space}"
|
||||
msgstr "{user} hat Sie zu Space {space} hinzugefügt"
|
||||
|
||||
#: pkg/service/templates.go:34
|
||||
msgid "{user} deleted Space {space}"
|
||||
msgstr "{user} hat Space {space} gelöscht"
|
||||
|
||||
#: pkg/service/templates.go:29
|
||||
msgid "{user} disabled Space {space}"
|
||||
msgstr "{user} hat Space {space} deaktiviert"
|
||||
|
||||
#: pkg/service/templates.go:24
|
||||
msgid "{user} removed you from Space {space}"
|
||||
msgstr "{user} hat Sie aus Space {space} entfernt."
|
||||
|
||||
#: pkg/service/templates.go:44
|
||||
msgid "{user} shared {resource} with you"
|
||||
msgstr "{user} hat {resource} mit Ihnen geteilt"
|
||||
|
||||
#: pkg/service/templates.go:49
|
||||
msgid "{user} unshared {resource} with you"
|
||||
msgstr "{user} hat die Freigabe auf {resource} für Sie aufgehoben"
|
||||
@@ -0,0 +1,124 @@
|
||||
# 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.
|
||||
#
|
||||
# Translators:
|
||||
# Efstathios Iosifidis <eiosifidis@gmail.com>, 2026
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2026-05-08 00:02+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: Efstathios Iosifidis <eiosifidis@gmail.com>, 2026\n"
|
||||
"Language-Team: Greek (https://app.transifex.com/qsfera-eu/teams/204053/el/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: el\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: pkg/service/templates.go:39
|
||||
msgid "Access to Space {space} lost"
|
||||
msgstr "Η πρόσβαση στον Χώρο {space} χάθηκε"
|
||||
|
||||
#: pkg/service/templates.go:54
|
||||
msgid "Access to {resource} expired"
|
||||
msgstr "Η πρόσβαση στο στοιχείο {resource} έληξε"
|
||||
|
||||
#: pkg/service/templates.go:59
|
||||
msgid ""
|
||||
"Attention! The instance will be shut down and deprovisioned on {date}. "
|
||||
"Download all your data before that date as no access past that date is "
|
||||
"possible."
|
||||
msgstr ""
|
||||
"Προσοχή! Η παρουσία (instance) θα απενεργοποιηθεί και θα αποδεσμευτεί στις "
|
||||
"{date}. Κατεβάστε όλα τα δεδομένα σας πριν από αυτή την ημερομηνία, καθώς "
|
||||
"μετά δεν θα είναι δυνατή η πρόσβαση."
|
||||
|
||||
#: pkg/service/templates.go:58
|
||||
msgid "Instance will be shut down and deprovisioned"
|
||||
msgstr "Η παρουσία (instance) θα τερματιστεί και θα αποδεσμευτεί"
|
||||
|
||||
#: pkg/service/templates.go:38
|
||||
msgid "Membership expired"
|
||||
msgstr "Η ιδιότητα μέλους έληξε"
|
||||
|
||||
#: pkg/service/templates.go:13
|
||||
msgid "Policies enforced"
|
||||
msgstr "Εφαρμογή πολιτικών"
|
||||
|
||||
#: pkg/service/templates.go:23
|
||||
msgid "Removed from Space"
|
||||
msgstr "Αφαίρεση από τον Χώρο"
|
||||
|
||||
#: pkg/service/templates.go:43
|
||||
msgid "Resource shared"
|
||||
msgstr "Διαμοιρασμός πόρου"
|
||||
|
||||
#: pkg/service/templates.go:48
|
||||
msgid "Resource unshared"
|
||||
msgstr "Διακοπή διαμοιρασμού πόρου"
|
||||
|
||||
#: pkg/service/templates.go:53
|
||||
msgid "Share expired"
|
||||
msgstr "Ο διαμοιρασμός έληξε"
|
||||
|
||||
#: pkg/service/templates.go:33
|
||||
msgid "Space deleted"
|
||||
msgstr "Διαγραφή χώρου"
|
||||
|
||||
#: pkg/service/templates.go:28
|
||||
msgid "Space disabled"
|
||||
msgstr "Απενεργοποίηση χώρου"
|
||||
|
||||
#: pkg/service/templates.go:18
|
||||
msgid "Space shared"
|
||||
msgstr "Διαμοιρασμός χώρου"
|
||||
|
||||
#: pkg/service/templates.go:14
|
||||
msgid ""
|
||||
"The file {resource} was deleted because it violates the restrictions of this"
|
||||
" cloud. This could be due to an unsupported file type, potentially harmful "
|
||||
"or prohibited content, or file size limits."
|
||||
msgstr ""
|
||||
"Το αρχείο {resource} διαγράφηκε επειδή παραβιάζει τους περιορισμούς αυτού "
|
||||
"του cloud. Αυτό μπορεί να οφείλεται σε μη υποστηριζόμενο τύπο αρχείου, σε "
|
||||
"περιεχόμενο που ενδέχεται να είναι επιβλαβές ή απαγορευμένο, ή σε όρια "
|
||||
"μεγέθους αρχείου."
|
||||
|
||||
#: pkg/service/templates.go:8
|
||||
msgid "Virus found"
|
||||
msgstr "Βρέθηκε ιός"
|
||||
|
||||
#: pkg/service/templates.go:9
|
||||
msgid "Virus found in {resource}. Upload not possible. Virus: {virus}"
|
||||
msgstr ""
|
||||
"Βρέθηκε ιός στο {resource}. Η μεταφόρτωση δεν είναι δυνατή. Ιός: {virus}"
|
||||
|
||||
#: pkg/service/templates.go:19
|
||||
msgid "{user} added you to Space {space}"
|
||||
msgstr "Ο/Η {user} σάς πρόσθεσε στον Χώρο {space}"
|
||||
|
||||
#: pkg/service/templates.go:34
|
||||
msgid "{user} deleted Space {space}"
|
||||
msgstr "Ο/Η {user} διέγραψε τον Χώρο {space}"
|
||||
|
||||
#: pkg/service/templates.go:29
|
||||
msgid "{user} disabled Space {space}"
|
||||
msgstr "Ο/Η {user} απενεργοποίησε τον Χώρο {space}"
|
||||
|
||||
#: pkg/service/templates.go:24
|
||||
msgid "{user} removed you from Space {space}"
|
||||
msgstr "Ο/Η {user} σάς αφαίρεσε από τον Χώρο {space}"
|
||||
|
||||
#: pkg/service/templates.go:44
|
||||
msgid "{user} shared {resource} with you"
|
||||
msgstr "Ο/Η {user} διαμοιράστηκε το {resource} μαζί σας"
|
||||
|
||||
#: pkg/service/templates.go:49
|
||||
msgid "{user} unshared {resource} with you"
|
||||
msgstr "Ο/Η {user} διέκοψε τον διαμοιρασμό του {resource} μαζί σας"
|
||||
@@ -0,0 +1,120 @@
|
||||
# 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.
|
||||
#
|
||||
# Translators:
|
||||
# Elías Martín, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2026-05-07 00:02+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: Elías Martín, 2025\n"
|
||||
"Language-Team: Spanish (https://app.transifex.com/qsfera-eu/teams/204053/es/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: es\n"
|
||||
"Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
|
||||
|
||||
#: pkg/service/templates.go:39
|
||||
msgid "Access to Space {space} lost"
|
||||
msgstr "Acceso al Espacio {space} perdido"
|
||||
|
||||
#: pkg/service/templates.go:54
|
||||
msgid "Access to {resource} expired"
|
||||
msgstr "Acceso a {resource} expirado"
|
||||
|
||||
#: pkg/service/templates.go:59
|
||||
msgid ""
|
||||
"Attention! The instance will be shut down and deprovisioned on {date}. "
|
||||
"Download all your data before that date as no access past that date is "
|
||||
"possible."
|
||||
msgstr ""
|
||||
"¡Atención! Esta instancia se dará se parará y deprovisonará el {date}. "
|
||||
"Descarga todos tus datos antes de la fecha límmite, transcurrido el plazo no"
|
||||
" se podrá acceder más a los datos."
|
||||
|
||||
#: pkg/service/templates.go:58
|
||||
msgid "Instance will be shut down and deprovisioned"
|
||||
msgstr "La instancia será apagada y se desaprovisionará de sus recursos"
|
||||
|
||||
#: pkg/service/templates.go:38
|
||||
msgid "Membership expired"
|
||||
msgstr "Membresía expirada"
|
||||
|
||||
#: pkg/service/templates.go:13
|
||||
msgid "Policies enforced"
|
||||
msgstr "Políticas implementadas"
|
||||
|
||||
#: pkg/service/templates.go:23
|
||||
msgid "Removed from Space"
|
||||
msgstr "Eliminado de Espacio"
|
||||
|
||||
#: pkg/service/templates.go:43
|
||||
msgid "Resource shared"
|
||||
msgstr "Recurso compartido"
|
||||
|
||||
#: pkg/service/templates.go:48
|
||||
msgid "Resource unshared"
|
||||
msgstr "Recurso no compartido"
|
||||
|
||||
#: pkg/service/templates.go:53
|
||||
msgid "Share expired"
|
||||
msgstr "La compartición ha expirado"
|
||||
|
||||
#: pkg/service/templates.go:33
|
||||
msgid "Space deleted"
|
||||
msgstr "Espacio borrado"
|
||||
|
||||
#: pkg/service/templates.go:28
|
||||
msgid "Space disabled"
|
||||
msgstr "Espacio deshabilitado"
|
||||
|
||||
#: pkg/service/templates.go:18
|
||||
msgid "Space shared"
|
||||
msgstr "Espacio compartido"
|
||||
|
||||
#: pkg/service/templates.go:14
|
||||
msgid ""
|
||||
"The file {resource} was deleted because it violates the restrictions of this"
|
||||
" cloud. This could be due to an unsupported file type, potentially harmful "
|
||||
"or prohibited content, or file size limits."
|
||||
msgstr ""
|
||||
|
||||
#: pkg/service/templates.go:8
|
||||
msgid "Virus found"
|
||||
msgstr "Virus encontrado"
|
||||
|
||||
#: pkg/service/templates.go:9
|
||||
msgid "Virus found in {resource}. Upload not possible. Virus: {virus}"
|
||||
msgstr ""
|
||||
"Se encontró un visrus en {resource}. No se permite la subida. Virus: {virus}"
|
||||
|
||||
#: pkg/service/templates.go:19
|
||||
msgid "{user} added you to Space {space}"
|
||||
msgstr "{user} te añadió al Espacio {space}"
|
||||
|
||||
#: pkg/service/templates.go:34
|
||||
msgid "{user} deleted Space {space}"
|
||||
msgstr "{user} borró el Espacio {space}"
|
||||
|
||||
#: pkg/service/templates.go:29
|
||||
msgid "{user} disabled Space {space}"
|
||||
msgstr "{user} deshabilitó el Espacio {space}"
|
||||
|
||||
#: pkg/service/templates.go:24
|
||||
msgid "{user} removed you from Space {space}"
|
||||
msgstr "{user} te eliminó del Espacio {space}"
|
||||
|
||||
#: pkg/service/templates.go:44
|
||||
msgid "{user} shared {resource} with you"
|
||||
msgstr "{user} compartió {resource} contigo"
|
||||
|
||||
#: pkg/service/templates.go:49
|
||||
msgid "{user} unshared {resource} with you"
|
||||
msgstr "{user} dejó de compartir {resource} contigo"
|
||||
@@ -0,0 +1,123 @@
|
||||
# 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.
|
||||
#
|
||||
# Translators:
|
||||
# Jiri Grönroos <jiri.gronroos@iki.fi>, 2026
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2026-04-23 00:03+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: Jiri Grönroos <jiri.gronroos@iki.fi>, 2026\n"
|
||||
"Language-Team: Finnish (https://app.transifex.com/qsfera-eu/teams/204053/fi/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: fi\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: pkg/service/templates.go:39
|
||||
msgid "Access to Space {space} lost"
|
||||
msgstr "Pääsy avaruuteen {space} menetetty"
|
||||
|
||||
#: pkg/service/templates.go:54
|
||||
msgid "Access to {resource} expired"
|
||||
msgstr "Pääsy resurssiin {resource} vanheni"
|
||||
|
||||
#: pkg/service/templates.go:59
|
||||
msgid ""
|
||||
"Attention! The instance will be shut down and deprovisioned on {date}. "
|
||||
"Download all your data before that date as no access past that date is "
|
||||
"possible."
|
||||
msgstr ""
|
||||
"Huomio! Tämä instanssi sammutetaan ja poistetaan {date}. Lataa datasi ennen "
|
||||
"kyseistä päivää, sillä pääsy sen jälkeen ei ole mahdollista."
|
||||
|
||||
#: pkg/service/templates.go:58
|
||||
msgid "Instance will be shut down and deprovisioned"
|
||||
msgstr "Instanssi sammutetaan ja poistetaan käytöstä"
|
||||
|
||||
#: pkg/service/templates.go:38
|
||||
msgid "Membership expired"
|
||||
msgstr "Jäsenyys vanhentunut"
|
||||
|
||||
#: pkg/service/templates.go:13
|
||||
msgid "Policies enforced"
|
||||
msgstr "Käytännöt pakotettu"
|
||||
|
||||
#: pkg/service/templates.go:23
|
||||
msgid "Removed from Space"
|
||||
msgstr "Poistettu avaruudesta"
|
||||
|
||||
#: pkg/service/templates.go:43
|
||||
msgid "Resource shared"
|
||||
msgstr "Resurssi jaettu"
|
||||
|
||||
#: pkg/service/templates.go:48
|
||||
msgid "Resource unshared"
|
||||
msgstr "Resurssin jako lopetettu"
|
||||
|
||||
#: pkg/service/templates.go:53
|
||||
msgid "Share expired"
|
||||
msgstr "Jako vanhentunut"
|
||||
|
||||
#: pkg/service/templates.go:33
|
||||
msgid "Space deleted"
|
||||
msgstr "Jako poistettu"
|
||||
|
||||
#: pkg/service/templates.go:28
|
||||
msgid "Space disabled"
|
||||
msgstr "Avaruus poistettu käytöstä"
|
||||
|
||||
#: pkg/service/templates.go:18
|
||||
msgid "Space shared"
|
||||
msgstr "Avaruus jaettu"
|
||||
|
||||
#: pkg/service/templates.go:14
|
||||
msgid ""
|
||||
"The file {resource} was deleted because it violates the restrictions of this"
|
||||
" cloud. This could be due to an unsupported file type, potentially harmful "
|
||||
"or prohibited content, or file size limits."
|
||||
msgstr ""
|
||||
"Tiedosto {resource} poistettiin, koska se loukkaa tämän pilven rajoituksia. "
|
||||
"Syy saattaa olla ei-tuettu tiedostomuoto, mahdollisesti haitallinen tai "
|
||||
"kielletty sisältö tai tiedostojen kokorajat."
|
||||
|
||||
#: pkg/service/templates.go:8
|
||||
msgid "Virus found"
|
||||
msgstr "Virus löydetty"
|
||||
|
||||
#: pkg/service/templates.go:9
|
||||
msgid "Virus found in {resource}. Upload not possible. Virus: {virus}"
|
||||
msgstr ""
|
||||
"Virus paikannettu resurssiin {resource}. Lähetys ei ole mahdollista. Virus: "
|
||||
"{virus}"
|
||||
|
||||
#: pkg/service/templates.go:19
|
||||
msgid "{user} added you to Space {space}"
|
||||
msgstr "{user} lisäsi sinut avaruuteen {space}"
|
||||
|
||||
#: pkg/service/templates.go:34
|
||||
msgid "{user} deleted Space {space}"
|
||||
msgstr "{user} poisti avaruuden {space}"
|
||||
|
||||
#: pkg/service/templates.go:29
|
||||
msgid "{user} disabled Space {space}"
|
||||
msgstr "{user} poisti käytöstä avaruuden {space}"
|
||||
|
||||
#: pkg/service/templates.go:24
|
||||
msgid "{user} removed you from Space {space}"
|
||||
msgstr "{user} poisti sinut avaruudesta {space}"
|
||||
|
||||
#: pkg/service/templates.go:44
|
||||
msgid "{user} shared {resource} with you"
|
||||
msgstr "{user} jakoi {resource} kanssasi"
|
||||
|
||||
#: pkg/service/templates.go:49
|
||||
msgid "{user} unshared {resource} with you"
|
||||
msgstr "{user} lopettu resurssin {resource} jakamisen kanssasi"
|
||||
@@ -0,0 +1,125 @@
|
||||
# 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.
|
||||
#
|
||||
# Translators:
|
||||
# eric_G <junk.eg@free.fr>, 2025
|
||||
# Benoît Aguesse, 2026
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2026-04-23 00:03+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: Benoît Aguesse, 2026\n"
|
||||
"Language-Team: French (https://app.transifex.com/qsfera-eu/teams/204053/fr/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: fr\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
|
||||
|
||||
#: pkg/service/templates.go:39
|
||||
msgid "Access to Space {space} lost"
|
||||
msgstr "Accès à l'Espace {space} perdu"
|
||||
|
||||
#: pkg/service/templates.go:54
|
||||
msgid "Access to {resource} expired"
|
||||
msgstr "L'accès à {resource} a expiré"
|
||||
|
||||
#: pkg/service/templates.go:59
|
||||
msgid ""
|
||||
"Attention! The instance will be shut down and deprovisioned on {date}. "
|
||||
"Download all your data before that date as no access past that date is "
|
||||
"possible."
|
||||
msgstr ""
|
||||
"Attention ! L'instance sera fermée et déprovisionnée le {date}. Téléchargez "
|
||||
"toutes vos données avant cette date car aucun accès ne sera possible après "
|
||||
"cette date."
|
||||
|
||||
#: pkg/service/templates.go:58
|
||||
msgid "Instance will be shut down and deprovisioned"
|
||||
msgstr "L'instance sera fermée et déprovisionnée."
|
||||
|
||||
#: pkg/service/templates.go:38
|
||||
msgid "Membership expired"
|
||||
msgstr "Adhésion expirée"
|
||||
|
||||
#: pkg/service/templates.go:13
|
||||
msgid "Policies enforced"
|
||||
msgstr "Politiques appliquées"
|
||||
|
||||
#: pkg/service/templates.go:23
|
||||
msgid "Removed from Space"
|
||||
msgstr "Retiré de l'Espace"
|
||||
|
||||
#: pkg/service/templates.go:43
|
||||
msgid "Resource shared"
|
||||
msgstr "Ressource partagée"
|
||||
|
||||
#: pkg/service/templates.go:48
|
||||
msgid "Resource unshared"
|
||||
msgstr "Ressource non partagée"
|
||||
|
||||
#: pkg/service/templates.go:53
|
||||
msgid "Share expired"
|
||||
msgstr "Partage expiré"
|
||||
|
||||
#: pkg/service/templates.go:33
|
||||
msgid "Space deleted"
|
||||
msgstr "Espace supprimé"
|
||||
|
||||
#: pkg/service/templates.go:28
|
||||
msgid "Space disabled"
|
||||
msgstr "Espace désactivé"
|
||||
|
||||
#: pkg/service/templates.go:18
|
||||
msgid "Space shared"
|
||||
msgstr "Espace partagé"
|
||||
|
||||
#: pkg/service/templates.go:14
|
||||
msgid ""
|
||||
"The file {resource} was deleted because it violates the restrictions of this"
|
||||
" cloud. This could be due to an unsupported file type, potentially harmful "
|
||||
"or prohibited content, or file size limits."
|
||||
msgstr ""
|
||||
"Le fichier {resource} a été supprimé car il violait les restrictions de ce "
|
||||
"cloud. Cela peut être dû à un type de fichier non pris en charge, du contenu"
|
||||
" potentiellement malveillant ou prohibé ou des limites de taille de "
|
||||
"fichiers."
|
||||
|
||||
#: pkg/service/templates.go:8
|
||||
msgid "Virus found"
|
||||
msgstr "Virus détecté"
|
||||
|
||||
#: pkg/service/templates.go:9
|
||||
msgid "Virus found in {resource}. Upload not possible. Virus: {virus}"
|
||||
msgstr ""
|
||||
"Virus trouvé dans {resource}. Téléversement impossible. Virus : {virus}"
|
||||
|
||||
#: pkg/service/templates.go:19
|
||||
msgid "{user} added you to Space {space}"
|
||||
msgstr "{user} vous a ajouté à l'Espace {space}"
|
||||
|
||||
#: pkg/service/templates.go:34
|
||||
msgid "{user} deleted Space {space}"
|
||||
msgstr "{user} a supprimé l'Espace {space}"
|
||||
|
||||
#: pkg/service/templates.go:29
|
||||
msgid "{user} disabled Space {space}"
|
||||
msgstr "{utilisateur} a désactivé Espace {espace}"
|
||||
|
||||
#: pkg/service/templates.go:24
|
||||
msgid "{user} removed you from Space {space}"
|
||||
msgstr "{user} vous a retiré de l'Espace {space}"
|
||||
|
||||
#: pkg/service/templates.go:44
|
||||
msgid "{user} shared {resource} with you"
|
||||
msgstr "{user} a partagé {resource} avec vous"
|
||||
|
||||
#: pkg/service/templates.go:49
|
||||
msgid "{user} unshared {resource} with you"
|
||||
msgstr "{user} a annulé le partage de {resource} avec vous"
|
||||
@@ -0,0 +1,121 @@
|
||||
# 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.
|
||||
#
|
||||
# Translators:
|
||||
# mitibor, 2026
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2026-05-07 00:02+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: mitibor, 2026\n"
|
||||
"Language-Team: Hungarian (https://app.transifex.com/qsfera-eu/teams/204053/hu/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: hu\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: pkg/service/templates.go:39
|
||||
msgid "Access to Space {space} lost"
|
||||
msgstr "A hozzáférés megszűnt a(z) {space} tárhelyhez"
|
||||
|
||||
#: pkg/service/templates.go:54
|
||||
msgid "Access to {resource} expired"
|
||||
msgstr "A hozzáférés a(z) {resource} erőforráshoz lejárt"
|
||||
|
||||
#: pkg/service/templates.go:59
|
||||
msgid ""
|
||||
"Attention! The instance will be shut down and deprovisioned on {date}. "
|
||||
"Download all your data before that date as no access past that date is "
|
||||
"possible."
|
||||
msgstr ""
|
||||
"Figyelem! A példány {date} napon leállításra és felszámolásra kerül. Kérjük,"
|
||||
" ezen időpont előtt töltse le minden adatát, mivel ezt követően a hozzáférés"
|
||||
" már nem lesz lehetséges."
|
||||
|
||||
#: pkg/service/templates.go:58
|
||||
msgid "Instance will be shut down and deprovisioned"
|
||||
msgstr "A példány leállításra és felszámolásra kerül"
|
||||
|
||||
#: pkg/service/templates.go:38
|
||||
msgid "Membership expired"
|
||||
msgstr "A tagság lejárt"
|
||||
|
||||
#: pkg/service/templates.go:13
|
||||
msgid "Policies enforced"
|
||||
msgstr "Irányelvek érvényesítve"
|
||||
|
||||
#: pkg/service/templates.go:23
|
||||
msgid "Removed from Space"
|
||||
msgstr "Eltávolítva a tárhelyről"
|
||||
|
||||
#: pkg/service/templates.go:43
|
||||
msgid "Resource shared"
|
||||
msgstr "Erőforrás megosztva"
|
||||
|
||||
#: pkg/service/templates.go:48
|
||||
msgid "Resource unshared"
|
||||
msgstr "Erőforrás megosztása visszavonva"
|
||||
|
||||
#: pkg/service/templates.go:53
|
||||
msgid "Share expired"
|
||||
msgstr "A megosztás lejárt"
|
||||
|
||||
#: pkg/service/templates.go:33
|
||||
msgid "Space deleted"
|
||||
msgstr "Tárhely törölve"
|
||||
|
||||
#: pkg/service/templates.go:28
|
||||
msgid "Space disabled"
|
||||
msgstr "Tárhely letiltva"
|
||||
|
||||
#: pkg/service/templates.go:18
|
||||
msgid "Space shared"
|
||||
msgstr "Tárhely megosztva"
|
||||
|
||||
#: pkg/service/templates.go:14
|
||||
msgid ""
|
||||
"The file {resource} was deleted because it violates the restrictions of this"
|
||||
" cloud. This could be due to an unsupported file type, potentially harmful "
|
||||
"or prohibited content, or file size limits."
|
||||
msgstr ""
|
||||
|
||||
#: pkg/service/templates.go:8
|
||||
msgid "Virus found"
|
||||
msgstr "Vírus található"
|
||||
|
||||
#: pkg/service/templates.go:9
|
||||
msgid "Virus found in {resource}. Upload not possible. Virus: {virus}"
|
||||
msgstr ""
|
||||
"Vírus található a(z) {resource} fájlban. A feltöltés nem lehetséges. Vírus: "
|
||||
"{virus}"
|
||||
|
||||
#: pkg/service/templates.go:19
|
||||
msgid "{user} added you to Space {space}"
|
||||
msgstr "{user} hozzáadta Önt a(z) {space} tárhelyhez"
|
||||
|
||||
#: pkg/service/templates.go:34
|
||||
msgid "{user} deleted Space {space}"
|
||||
msgstr "{user} törölte a(z) {space} tárhelyet"
|
||||
|
||||
#: pkg/service/templates.go:29
|
||||
msgid "{user} disabled Space {space}"
|
||||
msgstr "{user} letiltotta a(z) {space} tárhelyet"
|
||||
|
||||
#: pkg/service/templates.go:24
|
||||
msgid "{user} removed you from Space {space}"
|
||||
msgstr "{user} eltávolította Önt a(z) {space} tárhelyről"
|
||||
|
||||
#: pkg/service/templates.go:44
|
||||
msgid "{user} shared {resource} with you"
|
||||
msgstr "{user} megosztotta Önnel a következő erőforrást: {resource}"
|
||||
|
||||
#: pkg/service/templates.go:49
|
||||
msgid "{user} unshared {resource} with you"
|
||||
msgstr "{user} visszavonta a(z) {resource} erőforrás megosztását Önnel"
|
||||
@@ -0,0 +1,120 @@
|
||||
# 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.
|
||||
#
|
||||
# Translators:
|
||||
# Simone Broglia, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2026-05-07 00:02+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: Simone Broglia, 2025\n"
|
||||
"Language-Team: Italian (https://app.transifex.com/qsfera-eu/teams/204053/it/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: it\n"
|
||||
"Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
|
||||
|
||||
#: pkg/service/templates.go:39
|
||||
msgid "Access to Space {space} lost"
|
||||
msgstr "Accesso allo Spazio {space} perso."
|
||||
|
||||
#: pkg/service/templates.go:54
|
||||
msgid "Access to {resource} expired"
|
||||
msgstr "Accesso allo Spazio {space} scaduto."
|
||||
|
||||
#: pkg/service/templates.go:59
|
||||
msgid ""
|
||||
"Attention! The instance will be shut down and deprovisioned on {date}. "
|
||||
"Download all your data before that date as no access past that date is "
|
||||
"possible."
|
||||
msgstr ""
|
||||
"Attenzione! L'istanza verrà disattivata e deprovisionata il {date}. Scarica "
|
||||
"tutti i tuoi dati prima di tale data, poiché non sarà più possibile "
|
||||
"accedervi successivamente."
|
||||
|
||||
#: pkg/service/templates.go:58
|
||||
msgid "Instance will be shut down and deprovisioned"
|
||||
msgstr "L'istanza verrà disattivata e deprovisionata."
|
||||
|
||||
#: pkg/service/templates.go:38
|
||||
msgid "Membership expired"
|
||||
msgstr "Iscrizione scaduta"
|
||||
|
||||
#: pkg/service/templates.go:13
|
||||
msgid "Policies enforced"
|
||||
msgstr "Regole applicate"
|
||||
|
||||
#: pkg/service/templates.go:23
|
||||
msgid "Removed from Space"
|
||||
msgstr "Rimosso dallo Spazio"
|
||||
|
||||
#: pkg/service/templates.go:43
|
||||
msgid "Resource shared"
|
||||
msgstr "Risorsa condivisa"
|
||||
|
||||
#: pkg/service/templates.go:48
|
||||
msgid "Resource unshared"
|
||||
msgstr "Risorsa non più condivisa"
|
||||
|
||||
#: pkg/service/templates.go:53
|
||||
msgid "Share expired"
|
||||
msgstr "Risorsa scaduta"
|
||||
|
||||
#: pkg/service/templates.go:33
|
||||
msgid "Space deleted"
|
||||
msgstr "Spazio eliminato"
|
||||
|
||||
#: pkg/service/templates.go:28
|
||||
msgid "Space disabled"
|
||||
msgstr "Spazio disabilitato"
|
||||
|
||||
#: pkg/service/templates.go:18
|
||||
msgid "Space shared"
|
||||
msgstr "Spazio condiviso"
|
||||
|
||||
#: pkg/service/templates.go:14
|
||||
msgid ""
|
||||
"The file {resource} was deleted because it violates the restrictions of this"
|
||||
" cloud. This could be due to an unsupported file type, potentially harmful "
|
||||
"or prohibited content, or file size limits."
|
||||
msgstr ""
|
||||
|
||||
#: pkg/service/templates.go:8
|
||||
msgid "Virus found"
|
||||
msgstr "Virus trovato"
|
||||
|
||||
#: pkg/service/templates.go:9
|
||||
msgid "Virus found in {resource}. Upload not possible. Virus: {virus}"
|
||||
msgstr ""
|
||||
"Virus trovato in {resource}. Caricamento non possibile. Virus: {virus}"
|
||||
|
||||
#: pkg/service/templates.go:19
|
||||
msgid "{user} added you to Space {space}"
|
||||
msgstr "{user} ti ha aggiunto allo Spazio {space}"
|
||||
|
||||
#: pkg/service/templates.go:34
|
||||
msgid "{user} deleted Space {space}"
|
||||
msgstr "{user} ha eliminato lo Spazio {space}."
|
||||
|
||||
#: pkg/service/templates.go:29
|
||||
msgid "{user} disabled Space {space}"
|
||||
msgstr "{user} ha disattivato lo Spazio {space}."
|
||||
|
||||
#: pkg/service/templates.go:24
|
||||
msgid "{user} removed you from Space {space}"
|
||||
msgstr "{user} ha rimosso lo Spazio {space}."
|
||||
|
||||
#: pkg/service/templates.go:44
|
||||
msgid "{user} shared {resource} with you"
|
||||
msgstr "{user} ha condiviso {resource} con te."
|
||||
|
||||
#: pkg/service/templates.go:49
|
||||
msgid "{user} unshared {resource} with you"
|
||||
msgstr "{user} ha revocato la condivisione di {resource} con te"
|
||||
@@ -0,0 +1,118 @@
|
||||
# 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.
|
||||
#
|
||||
# Translators:
|
||||
# iikaka88, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2026-05-07 00:02+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: iikaka88, 2025\n"
|
||||
"Language-Team: Japanese (https://app.transifex.com/qsfera-eu/teams/204053/ja/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: ja\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
#: pkg/service/templates.go:39
|
||||
msgid "Access to Space {space} lost"
|
||||
msgstr "スペース {space} へのアクセス権が失われました"
|
||||
|
||||
#: pkg/service/templates.go:54
|
||||
msgid "Access to {resource} expired"
|
||||
msgstr "{resource} へのアクセス期限が切れました"
|
||||
|
||||
#: pkg/service/templates.go:59
|
||||
msgid ""
|
||||
"Attention! The instance will be shut down and deprovisioned on {date}. "
|
||||
"Download all your data before that date as no access past that date is "
|
||||
"possible."
|
||||
msgstr ""
|
||||
"【重要】このインスタンスは {date} "
|
||||
"に停止および削除されます。その日以降はアクセスできなくなるため、期日までにすべてのデータをダウンロードしてください。"
|
||||
|
||||
#: pkg/service/templates.go:58
|
||||
msgid "Instance will be shut down and deprovisioned"
|
||||
msgstr "インスタンスは停止および削除されます"
|
||||
|
||||
#: pkg/service/templates.go:38
|
||||
msgid "Membership expired"
|
||||
msgstr "メンバーシップの期限が切れました"
|
||||
|
||||
#: pkg/service/templates.go:13
|
||||
msgid "Policies enforced"
|
||||
msgstr "ポリシーが適用されました"
|
||||
|
||||
#: pkg/service/templates.go:23
|
||||
msgid "Removed from Space"
|
||||
msgstr "スペースから削除されました"
|
||||
|
||||
#: pkg/service/templates.go:43
|
||||
msgid "Resource shared"
|
||||
msgstr "リソースが共有されました"
|
||||
|
||||
#: pkg/service/templates.go:48
|
||||
msgid "Resource unshared"
|
||||
msgstr "リソースの共有が解除されました"
|
||||
|
||||
#: pkg/service/templates.go:53
|
||||
msgid "Share expired"
|
||||
msgstr "共有期限が切れました"
|
||||
|
||||
#: pkg/service/templates.go:33
|
||||
msgid "Space deleted"
|
||||
msgstr "スペースが削除されました"
|
||||
|
||||
#: pkg/service/templates.go:28
|
||||
msgid "Space disabled"
|
||||
msgstr "スペースが無効化されました"
|
||||
|
||||
#: pkg/service/templates.go:18
|
||||
msgid "Space shared"
|
||||
msgstr "スペースが共有されました"
|
||||
|
||||
#: pkg/service/templates.go:14
|
||||
msgid ""
|
||||
"The file {resource} was deleted because it violates the restrictions of this"
|
||||
" cloud. This could be due to an unsupported file type, potentially harmful "
|
||||
"or prohibited content, or file size limits."
|
||||
msgstr ""
|
||||
|
||||
#: pkg/service/templates.go:8
|
||||
msgid "Virus found"
|
||||
msgstr "ウイルスが検出されました"
|
||||
|
||||
#: pkg/service/templates.go:9
|
||||
msgid "Virus found in {resource}. Upload not possible. Virus: {virus}"
|
||||
msgstr "{resource} 内にウイルスが検出されました。アップロードできません。ウイルス名: {virus}"
|
||||
|
||||
#: pkg/service/templates.go:19
|
||||
msgid "{user} added you to Space {space}"
|
||||
msgstr "{user} があなたをスペース「{space}」に追加しました"
|
||||
|
||||
#: pkg/service/templates.go:34
|
||||
msgid "{user} deleted Space {space}"
|
||||
msgstr "{user} がスペース「{space}」を削除しました"
|
||||
|
||||
#: pkg/service/templates.go:29
|
||||
msgid "{user} disabled Space {space}"
|
||||
msgstr "{user} がスペース「{space}」を無効にしました"
|
||||
|
||||
#: pkg/service/templates.go:24
|
||||
msgid "{user} removed you from Space {space}"
|
||||
msgstr "{user} があなたをスペース「{space}」から削除しました"
|
||||
|
||||
#: pkg/service/templates.go:44
|
||||
msgid "{user} shared {resource} with you"
|
||||
msgstr "{user} が {resource} をあなたと共有しました"
|
||||
|
||||
#: pkg/service/templates.go:49
|
||||
msgid "{user} unshared {resource} with you"
|
||||
msgstr "{user} が {resource} の共有を解除しました"
|
||||
@@ -0,0 +1,118 @@
|
||||
# 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.
|
||||
#
|
||||
# Translators:
|
||||
# gapho shin, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2026-05-07 00:02+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: gapho shin, 2025\n"
|
||||
"Language-Team: Korean (https://app.transifex.com/qsfera-eu/teams/204053/ko/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: ko\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
#: pkg/service/templates.go:39
|
||||
msgid "Access to Space {space} lost"
|
||||
msgstr "{space}에 대한 액세스가 손실되었습니다"
|
||||
|
||||
#: pkg/service/templates.go:54
|
||||
msgid "Access to {resource} expired"
|
||||
msgstr "{resource}에 대한 액세스가 만료되었습니다"
|
||||
|
||||
#: pkg/service/templates.go:59
|
||||
msgid ""
|
||||
"Attention! The instance will be shut down and deprovisioned on {date}. "
|
||||
"Download all your data before that date as no access past that date is "
|
||||
"possible."
|
||||
msgstr ""
|
||||
"주의하세요! 인스턴스는 {date}에 종료되고 프로비저닝이 해제됩니다. 해당 날짜 이후에는 액세스할 수 없으므로 해당 날짜 이전에 모든 "
|
||||
"데이터를 다운로드하세요."
|
||||
|
||||
#: pkg/service/templates.go:58
|
||||
msgid "Instance will be shut down and deprovisioned"
|
||||
msgstr "인스턴스가 종료되고 프로비저닝 해제됩니다"
|
||||
|
||||
#: pkg/service/templates.go:38
|
||||
msgid "Membership expired"
|
||||
msgstr "멤버십 만료"
|
||||
|
||||
#: pkg/service/templates.go:13
|
||||
msgid "Policies enforced"
|
||||
msgstr "시행되는 정책"
|
||||
|
||||
#: pkg/service/templates.go:23
|
||||
msgid "Removed from Space"
|
||||
msgstr "스페이스로부터 제거"
|
||||
|
||||
#: pkg/service/templates.go:43
|
||||
msgid "Resource shared"
|
||||
msgstr "자원 공유"
|
||||
|
||||
#: pkg/service/templates.go:48
|
||||
msgid "Resource unshared"
|
||||
msgstr "리소스 비공유"
|
||||
|
||||
#: pkg/service/templates.go:53
|
||||
msgid "Share expired"
|
||||
msgstr "공유 만료"
|
||||
|
||||
#: pkg/service/templates.go:33
|
||||
msgid "Space deleted"
|
||||
msgstr "삭제된 스페이스"
|
||||
|
||||
#: pkg/service/templates.go:28
|
||||
msgid "Space disabled"
|
||||
msgstr "스페이스 비활성화"
|
||||
|
||||
#: pkg/service/templates.go:18
|
||||
msgid "Space shared"
|
||||
msgstr "스페이스 공유"
|
||||
|
||||
#: pkg/service/templates.go:14
|
||||
msgid ""
|
||||
"The file {resource} was deleted because it violates the restrictions of this"
|
||||
" cloud. This could be due to an unsupported file type, potentially harmful "
|
||||
"or prohibited content, or file size limits."
|
||||
msgstr ""
|
||||
|
||||
#: pkg/service/templates.go:8
|
||||
msgid "Virus found"
|
||||
msgstr "바이러스 발견"
|
||||
|
||||
#: pkg/service/templates.go:9
|
||||
msgid "Virus found in {resource}. Upload not possible. Virus: {virus}"
|
||||
msgstr "{resource}에서 바이러스를 발견했습니다. 업로드할 수 없습니다. 바이러스: {virus}"
|
||||
|
||||
#: pkg/service/templates.go:19
|
||||
msgid "{user} added you to Space {space}"
|
||||
msgstr "{user}가 스페이스 {space}를 추가했습니다"
|
||||
|
||||
#: pkg/service/templates.go:34
|
||||
msgid "{user} deleted Space {space}"
|
||||
msgstr "{user} 가 스페이스 {space} 삭제했습니다"
|
||||
|
||||
#: pkg/service/templates.go:29
|
||||
msgid "{user} disabled Space {space}"
|
||||
msgstr "{user}가 스페이스 {space} 비활성화했습니다"
|
||||
|
||||
#: pkg/service/templates.go:24
|
||||
msgid "{user} removed you from Space {space}"
|
||||
msgstr "{user}가 스페이스 {space}를 제거했습니다"
|
||||
|
||||
#: pkg/service/templates.go:44
|
||||
msgid "{user} shared {resource} with you"
|
||||
msgstr "{user}가 {resource}을 공유했습니다"
|
||||
|
||||
#: pkg/service/templates.go:49
|
||||
msgid "{user} unshared {resource} with you"
|
||||
msgstr "{user}가 {resource}을 공유해제 했습니다"
|
||||
@@ -0,0 +1,124 @@
|
||||
# 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.
|
||||
#
|
||||
# Translators:
|
||||
# Stephan Paternotte <stephan@paternottes.net>, 2026
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2026-05-01 00:02+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: Stephan Paternotte <stephan@paternottes.net>, 2026\n"
|
||||
"Language-Team: Dutch (https://app.transifex.com/qsfera-eu/teams/204053/nl/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: nl\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: pkg/service/templates.go:39
|
||||
msgid "Access to Space {space} lost"
|
||||
msgstr "Toegang tot ruimte {space} kwijtgeraakt"
|
||||
|
||||
#: pkg/service/templates.go:54
|
||||
msgid "Access to {resource} expired"
|
||||
msgstr "Toegang tot {resource} verlopen"
|
||||
|
||||
#: pkg/service/templates.go:59
|
||||
msgid ""
|
||||
"Attention! The instance will be shut down and deprovisioned on {date}. "
|
||||
"Download all your data before that date as no access past that date is "
|
||||
"possible."
|
||||
msgstr ""
|
||||
"Let op! De instantie wordt op {date} afgesloten en afgeschakeld. Download "
|
||||
"voor deze datum al je gegevens, aangezien erna geen toegang meer mogelijk "
|
||||
"is."
|
||||
|
||||
#: pkg/service/templates.go:58
|
||||
msgid "Instance will be shut down and deprovisioned"
|
||||
msgstr "De instantie zal worden afgesloten en afgeschakeld"
|
||||
|
||||
#: pkg/service/templates.go:38
|
||||
msgid "Membership expired"
|
||||
msgstr "Lidmaatschap verlopen"
|
||||
|
||||
#: pkg/service/templates.go:13
|
||||
msgid "Policies enforced"
|
||||
msgstr "Beleid afgedwongen"
|
||||
|
||||
#: pkg/service/templates.go:23
|
||||
msgid "Removed from Space"
|
||||
msgstr "Verwijderd uit ruimte"
|
||||
|
||||
#: pkg/service/templates.go:43
|
||||
msgid "Resource shared"
|
||||
msgstr "Hulpbron gedeeld"
|
||||
|
||||
#: pkg/service/templates.go:48
|
||||
msgid "Resource unshared"
|
||||
msgstr "Delen van hulpbron beëindigd"
|
||||
|
||||
#: pkg/service/templates.go:53
|
||||
msgid "Share expired"
|
||||
msgstr "Share verlopen"
|
||||
|
||||
#: pkg/service/templates.go:33
|
||||
msgid "Space deleted"
|
||||
msgstr "Ruimte verwijderd"
|
||||
|
||||
#: pkg/service/templates.go:28
|
||||
msgid "Space disabled"
|
||||
msgstr "Ruimte uitgeschakeld"
|
||||
|
||||
#: pkg/service/templates.go:18
|
||||
msgid "Space shared"
|
||||
msgstr "Ruimte gedeeld"
|
||||
|
||||
#: pkg/service/templates.go:14
|
||||
msgid ""
|
||||
"The file {resource} was deleted because it violates the restrictions of this"
|
||||
" cloud. This could be due to an unsupported file type, potentially harmful "
|
||||
"or prohibited content, or file size limits."
|
||||
msgstr ""
|
||||
"Het bestand {resource} is verwijderd omdat het de beperkingen van deze cloud"
|
||||
" schendt. Dit kan te wijten zijn aan een niet-ondersteund bestandstype, "
|
||||
"potentieel schadelijke of verboden inhoud of limieten voor de "
|
||||
"bestandsgrootte."
|
||||
|
||||
#: pkg/service/templates.go:8
|
||||
msgid "Virus found"
|
||||
msgstr "Virus aangetroffen"
|
||||
|
||||
#: pkg/service/templates.go:9
|
||||
msgid "Virus found in {resource}. Upload not possible. Virus: {virus}"
|
||||
msgstr ""
|
||||
"Virus aangetroffen in {resource}. Uploaden niet mogelijk. Virus: {virus}"
|
||||
|
||||
#: pkg/service/templates.go:19
|
||||
msgid "{user} added you to Space {space}"
|
||||
msgstr "{user} heeft jou toegevoegd aan ruimte {space}"
|
||||
|
||||
#: pkg/service/templates.go:34
|
||||
msgid "{user} deleted Space {space}"
|
||||
msgstr "{user} heeft ruimte {space} verwijderd"
|
||||
|
||||
#: pkg/service/templates.go:29
|
||||
msgid "{user} disabled Space {space}"
|
||||
msgstr "{user} heeft ruimte {space} uitgeschakeld"
|
||||
|
||||
#: pkg/service/templates.go:24
|
||||
msgid "{user} removed you from Space {space}"
|
||||
msgstr "{user} heeft jou verwijderd uit ruimte {space}"
|
||||
|
||||
#: pkg/service/templates.go:44
|
||||
msgid "{user} shared {resource} with you"
|
||||
msgstr "{user} heeft {resource} met jou gedeeld"
|
||||
|
||||
#: pkg/service/templates.go:49
|
||||
msgid "{user} unshared {resource} with you"
|
||||
msgstr "{user} heeft het delen van {resource} met jou beëindigd"
|
||||
@@ -0,0 +1,122 @@
|
||||
# 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.
|
||||
#
|
||||
# Translators:
|
||||
# skrzat <skrzacikus@gmail.com>, 2025
|
||||
# Maksymilian Styżej, 2025
|
||||
# Radoslaw Posim, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2026-05-07 00:02+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: Radoslaw Posim, 2025\n"
|
||||
"Language-Team: Polish (https://app.transifex.com/qsfera-eu/teams/204053/pl/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: pl\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
|
||||
|
||||
#: pkg/service/templates.go:39
|
||||
msgid "Access to Space {space} lost"
|
||||
msgstr "Dostęp do Przestrzeni {space} utracony"
|
||||
|
||||
#: pkg/service/templates.go:54
|
||||
msgid "Access to {resource} expired"
|
||||
msgstr "Dostęp do {resource} wygasł"
|
||||
|
||||
#: pkg/service/templates.go:59
|
||||
msgid ""
|
||||
"Attention! The instance will be shut down and deprovisioned on {date}. "
|
||||
"Download all your data before that date as no access past that date is "
|
||||
"possible."
|
||||
msgstr ""
|
||||
"Uwaga! Ta instancja zostanie wyłączona i usunięta w dniu {date}. Pobierz "
|
||||
"wszystkie swoje dane przed tym terminem, ponieważ po upływie tej daty dostęp"
|
||||
" nie będzie możliwy."
|
||||
|
||||
#: pkg/service/templates.go:58
|
||||
msgid "Instance will be shut down and deprovisioned"
|
||||
msgstr "Instancja zostanie wyłączona i usunięta."
|
||||
|
||||
#: pkg/service/templates.go:38
|
||||
msgid "Membership expired"
|
||||
msgstr "Członkostwo wygasło"
|
||||
|
||||
#: pkg/service/templates.go:13
|
||||
msgid "Policies enforced"
|
||||
msgstr "Zastosowano obowiązujące polityki"
|
||||
|
||||
#: pkg/service/templates.go:23
|
||||
msgid "Removed from Space"
|
||||
msgstr "Usunięto z Przestrzeni"
|
||||
|
||||
#: pkg/service/templates.go:43
|
||||
msgid "Resource shared"
|
||||
msgstr "Zasób udostępniony"
|
||||
|
||||
#: pkg/service/templates.go:48
|
||||
msgid "Resource unshared"
|
||||
msgstr "Udostępnianie zasobu zostało zakończone"
|
||||
|
||||
#: pkg/service/templates.go:53
|
||||
msgid "Share expired"
|
||||
msgstr "Udostępnienie wygasło"
|
||||
|
||||
#: pkg/service/templates.go:33
|
||||
msgid "Space deleted"
|
||||
msgstr "Przestrzeń usunięta"
|
||||
|
||||
#: pkg/service/templates.go:28
|
||||
msgid "Space disabled"
|
||||
msgstr "Przestrzeń wyłączona"
|
||||
|
||||
#: pkg/service/templates.go:18
|
||||
msgid "Space shared"
|
||||
msgstr "Przestrzeń udostępniona"
|
||||
|
||||
#: pkg/service/templates.go:14
|
||||
msgid ""
|
||||
"The file {resource} was deleted because it violates the restrictions of this"
|
||||
" cloud. This could be due to an unsupported file type, potentially harmful "
|
||||
"or prohibited content, or file size limits."
|
||||
msgstr ""
|
||||
|
||||
#: pkg/service/templates.go:8
|
||||
msgid "Virus found"
|
||||
msgstr "Znaleziono wirusa"
|
||||
|
||||
#: pkg/service/templates.go:9
|
||||
msgid "Virus found in {resource}. Upload not possible. Virus: {virus}"
|
||||
msgstr ""
|
||||
"Znaleziono wirusa w {resource}. Przesłanie jest niemożliwe. Wirus: {virus}"
|
||||
|
||||
#: pkg/service/templates.go:19
|
||||
msgid "{user} added you to Space {space}"
|
||||
msgstr "{user} dodał Ciebie do Przestrzeni {space}"
|
||||
|
||||
#: pkg/service/templates.go:34
|
||||
msgid "{user} deleted Space {space}"
|
||||
msgstr "{user} usunął Przestrzeń {space}"
|
||||
|
||||
#: pkg/service/templates.go:29
|
||||
msgid "{user} disabled Space {space}"
|
||||
msgstr "{user} wyłączył Przestrzeń {space}"
|
||||
|
||||
#: pkg/service/templates.go:24
|
||||
msgid "{user} removed you from Space {space}"
|
||||
msgstr "{user} usunął Ciebie z Przestrzeni {space}"
|
||||
|
||||
#: pkg/service/templates.go:44
|
||||
msgid "{user} shared {resource} with you"
|
||||
msgstr "{user} udostępnił Ci zasób {resource}"
|
||||
|
||||
#: pkg/service/templates.go:49
|
||||
msgid "{user} unshared {resource} with you"
|
||||
msgstr "{user} wyłączył udostępnianie zasobu {resource} dla Ciebie"
|
||||
@@ -0,0 +1,120 @@
|
||||
# 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.
|
||||
#
|
||||
# Translators:
|
||||
# Mário Machado, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2026-05-07 00:02+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: Mário Machado, 2025\n"
|
||||
"Language-Team: Portuguese (https://app.transifex.com/qsfera-eu/teams/204053/pt/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: pt\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n"
|
||||
|
||||
#: pkg/service/templates.go:39
|
||||
msgid "Access to Space {space} lost"
|
||||
msgstr "Acesso ao Espaço {space} perdido"
|
||||
|
||||
#: pkg/service/templates.go:54
|
||||
msgid "Access to {resource} expired"
|
||||
msgstr "Acesso a {resource} expirou"
|
||||
|
||||
#: pkg/service/templates.go:59
|
||||
msgid ""
|
||||
"Attention! The instance will be shut down and deprovisioned on {date}. "
|
||||
"Download all your data before that date as no access past that date is "
|
||||
"possible."
|
||||
msgstr ""
|
||||
"Atenção! A instância será encerrada e desprovisionada a {date}. Descarregue "
|
||||
"todos os seus dados antes dessa data, pois o acesso não será possível depois"
|
||||
" desse prazo."
|
||||
|
||||
#: pkg/service/templates.go:58
|
||||
msgid "Instance will be shut down and deprovisioned"
|
||||
msgstr "A instância será encerrada e desprovisionada"
|
||||
|
||||
#: pkg/service/templates.go:38
|
||||
msgid "Membership expired"
|
||||
msgstr "Subscrição expirada"
|
||||
|
||||
#: pkg/service/templates.go:13
|
||||
msgid "Policies enforced"
|
||||
msgstr "Políticas aplicadas"
|
||||
|
||||
#: pkg/service/templates.go:23
|
||||
msgid "Removed from Space"
|
||||
msgstr "Removido do Espaço"
|
||||
|
||||
#: pkg/service/templates.go:43
|
||||
msgid "Resource shared"
|
||||
msgstr "Recurso partilhado"
|
||||
|
||||
#: pkg/service/templates.go:48
|
||||
msgid "Resource unshared"
|
||||
msgstr "Recurso cuja partilha foi removida"
|
||||
|
||||
#: pkg/service/templates.go:53
|
||||
msgid "Share expired"
|
||||
msgstr "Partilha expirada"
|
||||
|
||||
#: pkg/service/templates.go:33
|
||||
msgid "Space deleted"
|
||||
msgstr "Espaço eliminado"
|
||||
|
||||
#: pkg/service/templates.go:28
|
||||
msgid "Space disabled"
|
||||
msgstr "Espaço desativado"
|
||||
|
||||
#: pkg/service/templates.go:18
|
||||
msgid "Space shared"
|
||||
msgstr "Espaço partilhado"
|
||||
|
||||
#: pkg/service/templates.go:14
|
||||
msgid ""
|
||||
"The file {resource} was deleted because it violates the restrictions of this"
|
||||
" cloud. This could be due to an unsupported file type, potentially harmful "
|
||||
"or prohibited content, or file size limits."
|
||||
msgstr ""
|
||||
|
||||
#: pkg/service/templates.go:8
|
||||
msgid "Virus found"
|
||||
msgstr "Vírus encontrado"
|
||||
|
||||
#: pkg/service/templates.go:9
|
||||
msgid "Virus found in {resource}. Upload not possible. Virus: {virus}"
|
||||
msgstr ""
|
||||
"Vírus encontrado em {resource}. Carregamento não é possível. Vírus: {virus}"
|
||||
|
||||
#: pkg/service/templates.go:19
|
||||
msgid "{user} added you to Space {space}"
|
||||
msgstr "{user} adicionou-o(a) ao Espaço {space}"
|
||||
|
||||
#: pkg/service/templates.go:34
|
||||
msgid "{user} deleted Space {space}"
|
||||
msgstr "{user} eliminou o Espaço {space}"
|
||||
|
||||
#: pkg/service/templates.go:29
|
||||
msgid "{user} disabled Space {space}"
|
||||
msgstr "{user} desativou o Espaço {space}"
|
||||
|
||||
#: pkg/service/templates.go:24
|
||||
msgid "{user} removed you from Space {space}"
|
||||
msgstr "{user} removeu-o(a) do Espaço {space}"
|
||||
|
||||
#: pkg/service/templates.go:44
|
||||
msgid "{user} shared {resource} with you"
|
||||
msgstr "{user} partilhou {resource} consigo"
|
||||
|
||||
#: pkg/service/templates.go:49
|
||||
msgid "{user} unshared {resource} with you"
|
||||
msgstr "{user} removeu a partilha de {resource} consigo"
|
||||
@@ -0,0 +1,123 @@
|
||||
# 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.
|
||||
#
|
||||
# Translators:
|
||||
# Lulufox, 2025
|
||||
# Danila Kononov, 2026
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2026-04-28 00:03+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: Danila Kononov, 2026\n"
|
||||
"Language-Team: Russian (https://app.transifex.com/qsfera-eu/teams/204053/ru/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: ru\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n"
|
||||
|
||||
#: pkg/service/templates.go:39
|
||||
msgid "Access to Space {space} lost"
|
||||
msgstr "Доступ к пространству {space} потерян"
|
||||
|
||||
#: pkg/service/templates.go:54
|
||||
msgid "Access to {resource} expired"
|
||||
msgstr "Доступ к {resource} больше не действителен"
|
||||
|
||||
#: pkg/service/templates.go:59
|
||||
msgid ""
|
||||
"Attention! The instance will be shut down and deprovisioned on {date}. "
|
||||
"Download all your data before that date as no access past that date is "
|
||||
"possible."
|
||||
msgstr ""
|
||||
"Внимание! Система будет отключена {date}. Выгрузите все ваши данные до этой "
|
||||
"даты - после этого сделать это будет невозможно."
|
||||
|
||||
#: pkg/service/templates.go:58
|
||||
msgid "Instance will be shut down and deprovisioned"
|
||||
msgstr "Система будет отключена"
|
||||
|
||||
#: pkg/service/templates.go:38
|
||||
msgid "Membership expired"
|
||||
msgstr "Членство истекло"
|
||||
|
||||
#: pkg/service/templates.go:13
|
||||
msgid "Policies enforced"
|
||||
msgstr "Правила применены"
|
||||
|
||||
#: pkg/service/templates.go:23
|
||||
msgid "Removed from Space"
|
||||
msgstr "Удалить из пространства"
|
||||
|
||||
#: pkg/service/templates.go:43
|
||||
msgid "Resource shared"
|
||||
msgstr "Ресурс доступен для совместного использования"
|
||||
|
||||
#: pkg/service/templates.go:48
|
||||
msgid "Resource unshared"
|
||||
msgstr "Ресурс не доступен для совместного использования"
|
||||
|
||||
#: pkg/service/templates.go:53
|
||||
msgid "Share expired"
|
||||
msgstr "Срок совместного использования истек"
|
||||
|
||||
#: pkg/service/templates.go:33
|
||||
msgid "Space deleted"
|
||||
msgstr "Пространство удалено"
|
||||
|
||||
#: pkg/service/templates.go:28
|
||||
msgid "Space disabled"
|
||||
msgstr "Пространство отключено"
|
||||
|
||||
#: pkg/service/templates.go:18
|
||||
msgid "Space shared"
|
||||
msgstr "Пространство доступно для совместного использования"
|
||||
|
||||
#: pkg/service/templates.go:14
|
||||
msgid ""
|
||||
"The file {resource} was deleted because it violates the restrictions of this"
|
||||
" cloud. This could be due to an unsupported file type, potentially harmful "
|
||||
"or prohibited content, or file size limits."
|
||||
msgstr ""
|
||||
"Файл {resource} был удален за нарушение правил этого облака. Вероятные "
|
||||
"причины: неподдерживаемый формат файла, потенциально опасное содержимое или "
|
||||
"превышен лимит объёма файла."
|
||||
|
||||
#: pkg/service/templates.go:8
|
||||
msgid "Virus found"
|
||||
msgstr "Найден вирус"
|
||||
|
||||
#: pkg/service/templates.go:9
|
||||
msgid "Virus found in {resource}. Upload not possible. Virus: {virus}"
|
||||
msgstr ""
|
||||
"В ресурсе {resource} обнаружен вирус. Загрузка невозможна. Вирус: {virus}"
|
||||
|
||||
#: pkg/service/templates.go:19
|
||||
msgid "{user} added you to Space {space}"
|
||||
msgstr "{user} добавил Вас в пространство {space}"
|
||||
|
||||
#: pkg/service/templates.go:34
|
||||
msgid "{user} deleted Space {space}"
|
||||
msgstr "{user} удалил(-ла) пространство {space}"
|
||||
|
||||
#: pkg/service/templates.go:29
|
||||
msgid "{user} disabled Space {space}"
|
||||
msgstr "{user} отключил пространство {space}"
|
||||
|
||||
#: pkg/service/templates.go:24
|
||||
msgid "{user} removed you from Space {space}"
|
||||
msgstr "{user} удалил вас из пространства {space}"
|
||||
|
||||
#: pkg/service/templates.go:44
|
||||
msgid "{user} shared {resource} with you"
|
||||
msgstr "{user} поделился(-лась) с вами {resource}"
|
||||
|
||||
#: pkg/service/templates.go:49
|
||||
msgid "{user} unshared {resource} with you"
|
||||
msgstr "{user} забрал у вас доступ к {resource}"
|
||||
@@ -0,0 +1,119 @@
|
||||
# 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.
|
||||
#
|
||||
# Translators:
|
||||
# Daniel Nylander <po@danielnylander.se>, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2026-05-07 00:02+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: Daniel Nylander <po@danielnylander.se>, 2025\n"
|
||||
"Language-Team: Swedish (https://app.transifex.com/qsfera-eu/teams/204053/sv/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: sv\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
#: pkg/service/templates.go:39
|
||||
msgid "Access to Space {space} lost"
|
||||
msgstr "Tillgång till arbetsytan {space} förlorades"
|
||||
|
||||
#: pkg/service/templates.go:54
|
||||
msgid "Access to {resource} expired"
|
||||
msgstr "Åtkomst till {resource} har gått ut"
|
||||
|
||||
#: pkg/service/templates.go:59
|
||||
msgid ""
|
||||
"Attention! The instance will be shut down and deprovisioned on {date}. "
|
||||
"Download all your data before that date as no access past that date is "
|
||||
"possible."
|
||||
msgstr ""
|
||||
"Observera! Instansen kommer att stängas ned och avprovisioneras den {date}. "
|
||||
"Ladda ner alla dina data före det datumet, eftersom det inte går att komma "
|
||||
"åt dem efter det datumet."
|
||||
|
||||
#: pkg/service/templates.go:58
|
||||
msgid "Instance will be shut down and deprovisioned"
|
||||
msgstr "Instansen kommer att stängas av och avprovisioneras"
|
||||
|
||||
#: pkg/service/templates.go:38
|
||||
msgid "Membership expired"
|
||||
msgstr "Medlemskapet har gått ut"
|
||||
|
||||
#: pkg/service/templates.go:13
|
||||
msgid "Policies enforced"
|
||||
msgstr "Genomdrivna riktlinjer"
|
||||
|
||||
#: pkg/service/templates.go:23
|
||||
msgid "Removed from Space"
|
||||
msgstr "Togs bort från arbetsytan"
|
||||
|
||||
#: pkg/service/templates.go:43
|
||||
msgid "Resource shared"
|
||||
msgstr "Resurs delad"
|
||||
|
||||
#: pkg/service/templates.go:48
|
||||
msgid "Resource unshared"
|
||||
msgstr "Resursen delas inte"
|
||||
|
||||
#: pkg/service/templates.go:53
|
||||
msgid "Share expired"
|
||||
msgstr "Delning har löpt ut"
|
||||
|
||||
#: pkg/service/templates.go:33
|
||||
msgid "Space deleted"
|
||||
msgstr "Arbetsytan togs bort"
|
||||
|
||||
#: pkg/service/templates.go:28
|
||||
msgid "Space disabled"
|
||||
msgstr "Arbetsytan inaktiverad"
|
||||
|
||||
#: pkg/service/templates.go:18
|
||||
msgid "Space shared"
|
||||
msgstr "Arbetsytan delad"
|
||||
|
||||
#: pkg/service/templates.go:14
|
||||
msgid ""
|
||||
"The file {resource} was deleted because it violates the restrictions of this"
|
||||
" cloud. This could be due to an unsupported file type, potentially harmful "
|
||||
"or prohibited content, or file size limits."
|
||||
msgstr ""
|
||||
|
||||
#: pkg/service/templates.go:8
|
||||
msgid "Virus found"
|
||||
msgstr "Virus hittades"
|
||||
|
||||
#: pkg/service/templates.go:9
|
||||
msgid "Virus found in {resource}. Upload not possible. Virus: {virus}"
|
||||
msgstr "Virus hittades i {resource}. Uppladdning omöjlig. Virus: {virus}"
|
||||
|
||||
#: pkg/service/templates.go:19
|
||||
msgid "{user} added you to Space {space}"
|
||||
msgstr "{user} har lagt till dig i arbetsytan {space}"
|
||||
|
||||
#: pkg/service/templates.go:34
|
||||
msgid "{user} deleted Space {space}"
|
||||
msgstr "{user} tog bort arbetsytan {space}"
|
||||
|
||||
#: pkg/service/templates.go:29
|
||||
msgid "{user} disabled Space {space}"
|
||||
msgstr "{user} inaktiverade arbetsytan {space}"
|
||||
|
||||
#: pkg/service/templates.go:24
|
||||
msgid "{user} removed you from Space {space}"
|
||||
msgstr "{user} har tagit bort dig från arbetsytan {space}"
|
||||
|
||||
#: pkg/service/templates.go:44
|
||||
msgid "{user} shared {resource} with you"
|
||||
msgstr "{user} delade {resource} med dig"
|
||||
|
||||
#: pkg/service/templates.go:49
|
||||
msgid "{user} unshared {resource} with you"
|
||||
msgstr "{user} tog bort delning av {resource} med dig"
|
||||
@@ -0,0 +1,116 @@
|
||||
# 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.
|
||||
#
|
||||
# Translators:
|
||||
# YQS Yang, 2025
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: EMAIL\n"
|
||||
"POT-Creation-Date: 2026-05-07 00:02+0000\n"
|
||||
"PO-Revision-Date: 2025-01-27 10:17+0000\n"
|
||||
"Last-Translator: YQS Yang, 2025\n"
|
||||
"Language-Team: Chinese (https://app.transifex.com/qsfera-eu/teams/204053/zh/)\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Language: zh\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
#: pkg/service/templates.go:39
|
||||
msgid "Access to Space {space} lost"
|
||||
msgstr "无法访问空间{space}"
|
||||
|
||||
#: pkg/service/templates.go:54
|
||||
msgid "Access to {resource} expired"
|
||||
msgstr "对{resource}的访问权限已过期"
|
||||
|
||||
#: pkg/service/templates.go:59
|
||||
msgid ""
|
||||
"Attention! The instance will be shut down and deprovisioned on {date}. "
|
||||
"Download all your data before that date as no access past that date is "
|
||||
"possible."
|
||||
msgstr "请注意!实例将于{date}关闭并解除配置。请在此日期前下载所有数据,之后将无法访问。"
|
||||
|
||||
#: pkg/service/templates.go:58
|
||||
msgid "Instance will be shut down and deprovisioned"
|
||||
msgstr "实例将被关闭并解除配置"
|
||||
|
||||
#: pkg/service/templates.go:38
|
||||
msgid "Membership expired"
|
||||
msgstr "成员资格已过期"
|
||||
|
||||
#: pkg/service/templates.go:13
|
||||
msgid "Policies enforced"
|
||||
msgstr "策略已执行"
|
||||
|
||||
#: pkg/service/templates.go:23
|
||||
msgid "Removed from Space"
|
||||
msgstr "已从空间中移除"
|
||||
|
||||
#: pkg/service/templates.go:43
|
||||
msgid "Resource shared"
|
||||
msgstr "资源已共享"
|
||||
|
||||
#: pkg/service/templates.go:48
|
||||
msgid "Resource unshared"
|
||||
msgstr "资源已取消共享"
|
||||
|
||||
#: pkg/service/templates.go:53
|
||||
msgid "Share expired"
|
||||
msgstr "共享已过期"
|
||||
|
||||
#: pkg/service/templates.go:33
|
||||
msgid "Space deleted"
|
||||
msgstr "空间已删除"
|
||||
|
||||
#: pkg/service/templates.go:28
|
||||
msgid "Space disabled"
|
||||
msgstr "空间已禁用"
|
||||
|
||||
#: pkg/service/templates.go:18
|
||||
msgid "Space shared"
|
||||
msgstr "空间已共享"
|
||||
|
||||
#: pkg/service/templates.go:14
|
||||
msgid ""
|
||||
"The file {resource} was deleted because it violates the restrictions of this"
|
||||
" cloud. This could be due to an unsupported file type, potentially harmful "
|
||||
"or prohibited content, or file size limits."
|
||||
msgstr ""
|
||||
|
||||
#: pkg/service/templates.go:8
|
||||
msgid "Virus found"
|
||||
msgstr "发现病毒"
|
||||
|
||||
#: pkg/service/templates.go:9
|
||||
msgid "Virus found in {resource}. Upload not possible. Virus: {virus}"
|
||||
msgstr "在{resource}中发现病毒。无法上传。病毒名称:{virus}"
|
||||
|
||||
#: pkg/service/templates.go:19
|
||||
msgid "{user} added you to Space {space}"
|
||||
msgstr "{user}已将您添加到空间{space}"
|
||||
|
||||
#: pkg/service/templates.go:34
|
||||
msgid "{user} deleted Space {space}"
|
||||
msgstr "{user}已删除空间{space}"
|
||||
|
||||
#: pkg/service/templates.go:29
|
||||
msgid "{user} disabled Space {space}"
|
||||
msgstr "{user}已禁用空间{space}"
|
||||
|
||||
#: pkg/service/templates.go:24
|
||||
msgid "{user} removed you from Space {space}"
|
||||
msgstr "{user}已将您从空间{space}中移除"
|
||||
|
||||
#: pkg/service/templates.go:44
|
||||
msgid "{user} shared {resource} with you"
|
||||
msgstr "{user}已与您共享{resource}"
|
||||
|
||||
#: pkg/service/templates.go:49
|
||||
msgid "{user} unshared {resource} with you"
|
||||
msgstr "{user}已取消与您共享{resource}"
|
||||
@@ -0,0 +1,109 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/qsfera/server/pkg/log"
|
||||
ehsvc "github.com/qsfera/server/protogen/gen/qsfera/services/eventhistory/v0"
|
||||
settingssvc "github.com/qsfera/server/protogen/gen/qsfera/services/settings/v0"
|
||||
"github.com/qsfera/server/services/userlog/pkg/config"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/events"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"go-micro.dev/v4/store"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
// Option for the userlog service
|
||||
type Option func(*Options)
|
||||
|
||||
// Options for the userlog service
|
||||
type Options struct {
|
||||
Logger log.Logger
|
||||
Stream events.Stream
|
||||
Mux *chi.Mux
|
||||
Store store.Store
|
||||
Config *config.Config
|
||||
HistoryClient ehsvc.EventHistoryService
|
||||
GatewaySelector pool.Selectable[gateway.GatewayAPIClient]
|
||||
ValueClient settingssvc.ValueService
|
||||
RoleClient settingssvc.RoleService
|
||||
RegisteredEvents []events.Unmarshaller
|
||||
TraceProvider trace.TracerProvider
|
||||
}
|
||||
|
||||
// Logger configures a logger for the userlog service
|
||||
func Logger(log log.Logger) Option {
|
||||
return func(o *Options) {
|
||||
o.Logger = log
|
||||
}
|
||||
}
|
||||
|
||||
// Stream configures an event stream for the userlog service
|
||||
func Stream(s events.Stream) Option {
|
||||
return func(o *Options) {
|
||||
o.Stream = s
|
||||
}
|
||||
}
|
||||
|
||||
// Mux defines the muxer for the userlog service
|
||||
func Mux(m *chi.Mux) Option {
|
||||
return func(o *Options) {
|
||||
o.Mux = m
|
||||
}
|
||||
}
|
||||
|
||||
// Store defines the store for the userlog service
|
||||
func Store(s store.Store) Option {
|
||||
return func(o *Options) {
|
||||
o.Store = s
|
||||
}
|
||||
}
|
||||
|
||||
// Config adds the config for the userlog service
|
||||
func Config(c *config.Config) Option {
|
||||
return func(o *Options) {
|
||||
o.Config = c
|
||||
}
|
||||
}
|
||||
|
||||
// HistoryClient adds a grpc client for the eventhistory service
|
||||
func HistoryClient(hc ehsvc.EventHistoryService) Option {
|
||||
return func(o *Options) {
|
||||
o.HistoryClient = hc
|
||||
}
|
||||
}
|
||||
|
||||
// GatewaySelector adds a grpc client selector for the gateway service
|
||||
func GatewaySelector(gatewaySelector pool.Selectable[gateway.GatewayAPIClient]) Option {
|
||||
return func(o *Options) {
|
||||
o.GatewaySelector = gatewaySelector
|
||||
}
|
||||
}
|
||||
|
||||
// RegisteredEvents registers the events the service should listen to
|
||||
func RegisteredEvents(e []events.Unmarshaller) Option {
|
||||
return func(o *Options) {
|
||||
o.RegisteredEvents = e
|
||||
}
|
||||
}
|
||||
|
||||
// ValueClient adds a grpc client for the value service
|
||||
func ValueClient(vs settingssvc.ValueService) Option {
|
||||
return func(o *Options) {
|
||||
o.ValueClient = vs
|
||||
}
|
||||
}
|
||||
|
||||
// RoleClient adds a grpc client for the role service
|
||||
func RoleClient(rs settingssvc.RoleService) Option {
|
||||
return func(o *Options) {
|
||||
o.RoleClient = rs
|
||||
}
|
||||
}
|
||||
|
||||
// TraceProvider adds a tracer provider for the userlog service
|
||||
func TraceProvider(tp trace.TracerProvider) Option {
|
||||
return func(o *Options) {
|
||||
o.TraceProvider = tp
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,462 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/events"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
"go-micro.dev/v4/store"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/qsfera/server/pkg/l10n"
|
||||
"github.com/qsfera/server/pkg/log"
|
||||
"github.com/qsfera/server/pkg/roles"
|
||||
ehmsg "github.com/qsfera/server/protogen/gen/qsfera/messages/eventhistory/v0"
|
||||
ehsvc "github.com/qsfera/server/protogen/gen/qsfera/services/eventhistory/v0"
|
||||
settingssvc "github.com/qsfera/server/protogen/gen/qsfera/services/settings/v0"
|
||||
"github.com/qsfera/server/services/userlog/pkg/config"
|
||||
)
|
||||
|
||||
// UserlogService is the service responsible for user activities
|
||||
type UserlogService struct {
|
||||
log log.Logger
|
||||
m *chi.Mux
|
||||
store store.Store
|
||||
cfg *config.Config
|
||||
historyClient ehsvc.EventHistoryService
|
||||
gatewaySelector pool.Selectable[gateway.GatewayAPIClient]
|
||||
valueClient settingssvc.ValueService
|
||||
registeredEvents map[string]events.Unmarshaller
|
||||
tp trace.TracerProvider
|
||||
tracer trace.Tracer
|
||||
publisher events.Publisher
|
||||
filter *userlogFilter
|
||||
}
|
||||
|
||||
// NewUserlogService returns an EventHistory service
|
||||
func NewUserlogService(opts ...Option) (*UserlogService, error) {
|
||||
o := &Options{}
|
||||
for _, opt := range opts {
|
||||
opt(o)
|
||||
}
|
||||
|
||||
if o.Stream == nil || o.Store == nil {
|
||||
return nil, fmt.Errorf("need non nil stream (%v) and store (%v) to work properly", o.Stream, o.Store)
|
||||
}
|
||||
|
||||
ch, err := events.Consume(o.Stream, "userlog", o.RegisteredEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ul := &UserlogService{
|
||||
log: o.Logger,
|
||||
m: o.Mux,
|
||||
store: o.Store,
|
||||
cfg: o.Config,
|
||||
historyClient: o.HistoryClient,
|
||||
gatewaySelector: o.GatewaySelector,
|
||||
valueClient: o.ValueClient,
|
||||
registeredEvents: make(map[string]events.Unmarshaller),
|
||||
tp: o.TraceProvider,
|
||||
tracer: o.TraceProvider.Tracer("github.com/qsfera/server/services/userlog/pkg/service"),
|
||||
publisher: o.Stream,
|
||||
filter: newUserlogFilter(o.Logger, o.ValueClient),
|
||||
}
|
||||
|
||||
for _, e := range o.RegisteredEvents {
|
||||
typ := reflect.TypeOf(e)
|
||||
ul.registeredEvents[typ.String()] = e
|
||||
}
|
||||
|
||||
m := roles.NewManager(
|
||||
// TODO: caching?
|
||||
roles.Logger(o.Logger),
|
||||
roles.RoleService(o.RoleClient),
|
||||
)
|
||||
|
||||
ul.m.Route("/ocs/v2.php/apps/notifications/api/v1/notifications", func(r chi.Router) {
|
||||
r.Get("/", ul.HandleGetEvents)
|
||||
r.Delete("/", ul.HandleDeleteEvents)
|
||||
r.Post("/global", RequireAdminOrSecret(&m, o.Config.GlobalNotificationsSecret)(ul.HandlePostGlobalEvent))
|
||||
r.Delete("/global", RequireAdminOrSecret(&m, o.Config.GlobalNotificationsSecret)(ul.HandleDeleteGlobalEvent))
|
||||
})
|
||||
|
||||
go ul.MemorizeEvents(ch)
|
||||
|
||||
return ul, nil
|
||||
}
|
||||
|
||||
// MemorizeEvents stores eventIDs a user wants to receive
|
||||
func (ul *UserlogService) MemorizeEvents(ch <-chan events.Event) {
|
||||
for i := 0; i < ul.cfg.MaxConcurrency; i++ {
|
||||
go func(ch <-chan events.Event) {
|
||||
for event := range ch {
|
||||
ul.processEvent(event)
|
||||
}
|
||||
}(ch)
|
||||
}
|
||||
}
|
||||
|
||||
func (ul *UserlogService) processEvent(event events.Event) {
|
||||
// for each event we need to:
|
||||
// I) find users eligible to receive the event
|
||||
var (
|
||||
users []string
|
||||
executant *user.UserId
|
||||
err error
|
||||
)
|
||||
|
||||
gwc, err := ul.gatewaySelector.Next()
|
||||
if err != nil {
|
||||
ul.log.Error().Err(err).Msg("cannot get gateway client")
|
||||
return
|
||||
}
|
||||
|
||||
ctx, err := utils.GetServiceUserContext(ul.cfg.ServiceAccount.ServiceAccountID, gwc, ul.cfg.ServiceAccount.ServiceAccountSecret)
|
||||
if err != nil {
|
||||
ul.log.Error().Err(err).Msg("cannot get service account")
|
||||
return
|
||||
}
|
||||
|
||||
gwc, err = ul.gatewaySelector.Next()
|
||||
if err != nil {
|
||||
ul.log.Error().Err(err).Msg("cannot get gateway client")
|
||||
return
|
||||
}
|
||||
switch e := event.Event.(type) {
|
||||
default:
|
||||
err = errors.New("unhandled event")
|
||||
// file related
|
||||
case events.PostprocessingStepFinished:
|
||||
switch e.FinishedStep {
|
||||
case events.PPStepAntivirus:
|
||||
result := e.Result.(events.VirusscanResult)
|
||||
if !result.Infected {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: should space mangers also be informed?
|
||||
users = append(users, e.ExecutingUser.GetId().GetOpaqueId())
|
||||
case events.PPStepPolicies:
|
||||
if e.Outcome == events.PPOutcomeContinue {
|
||||
return
|
||||
}
|
||||
users = append(users, e.ExecutingUser.GetId().GetOpaqueId())
|
||||
default:
|
||||
return
|
||||
}
|
||||
|
||||
// space related // TODO: how to find spaceadmins?
|
||||
case events.SpaceDisabled:
|
||||
executant = e.Executant
|
||||
users, err = utils.GetSpaceMembers(ctx, e.ID.GetOpaqueId(), gwc, utils.ViewerRole)
|
||||
case events.SpaceDeleted:
|
||||
executant = e.Executant
|
||||
for u := range e.FinalMembers {
|
||||
users = append(users, u)
|
||||
}
|
||||
case events.SpaceShared:
|
||||
executant = e.Executant
|
||||
users, err = utils.ResolveID(ctx, e.GranteeUserID, e.GranteeGroupID, gwc)
|
||||
case events.SpaceUnshared:
|
||||
executant = e.Executant
|
||||
users, err = utils.ResolveID(ctx, e.GranteeUserID, e.GranteeGroupID, gwc)
|
||||
case events.SpaceMembershipExpired:
|
||||
users, err = utils.ResolveID(ctx, e.GranteeUserID, e.GranteeGroupID, gwc)
|
||||
|
||||
// share related
|
||||
case events.ShareCreated:
|
||||
executant = e.Executant
|
||||
users, err = utils.ResolveID(ctx, e.GranteeUserID, e.GranteeGroupID, gwc)
|
||||
case events.ShareRemoved:
|
||||
executant = e.Executant
|
||||
users, err = utils.ResolveID(ctx, e.GranteeUserID, e.GranteeGroupID, gwc)
|
||||
case events.ShareExpired:
|
||||
users, err = utils.ResolveID(ctx, e.GranteeUserID, e.GranteeGroupID, gwc)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// TODO: Find out why this errors on ci pipeline
|
||||
ul.log.Debug().Err(err).Interface("event", event).Msg("error gathering members for event")
|
||||
return
|
||||
}
|
||||
|
||||
// II) filter users who want to receive the event
|
||||
users = ul.filter.execute(ctx, event, executant, users)
|
||||
|
||||
// III) store the eventID for each user
|
||||
for _, id := range users {
|
||||
if err := ul.addEventToUser(id, event); err != nil {
|
||||
ul.log.Error().Err(err).Str("userID", id).Str("eventid", event.ID).Msg("failed to store event for user")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// IV) send sses
|
||||
if !ul.cfg.DisableSSE {
|
||||
if err := ul.sendSSE(ctx, users, event, ul.gatewaySelector); err != nil {
|
||||
ul.log.Error().Err(err).Interface("userid", users).Str("eventid", event.ID).Msg("cannot create sse event")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetEvents allows retrieving events from the eventhistory by userid
|
||||
func (ul *UserlogService) GetEvents(ctx context.Context, userid string) ([]*ehmsg.Event, error) {
|
||||
ctx, span := ul.tracer.Start(ctx, "GetEvents")
|
||||
defer span.End()
|
||||
rec, err := ul.store.Read(userid)
|
||||
if err != nil && err != store.ErrNotFound {
|
||||
ul.log.Error().Err(err).Str("userid", userid).Msg("failed to read record from store")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(rec) == 0 {
|
||||
// no events available
|
||||
return []*ehmsg.Event{}, nil
|
||||
}
|
||||
|
||||
var eventIDs []string
|
||||
if err := json.Unmarshal(rec[0].Value, &eventIDs); err != nil {
|
||||
ul.log.Error().Err(err).Str("userid", userid).Msg("failed to umarshal record from store")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := ul.historyClient.GetEvents(ctx, &ehsvc.GetEventsRequest{Ids: eventIDs})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// remove expired events from list asynchronously
|
||||
go func() {
|
||||
if err := ul.removeExpiredEvents(userid, eventIDs, resp.GetEvents()); err != nil {
|
||||
ul.log.Error().Err(err).Str("userid", userid).Msg("could not remove expired events from user")
|
||||
}
|
||||
}()
|
||||
|
||||
return resp.GetEvents(), nil
|
||||
}
|
||||
|
||||
// DeleteEvents will delete the specified events
|
||||
func (ul *UserlogService) DeleteEvents(userid string, evids []string) error {
|
||||
toDelete := make(map[string]struct{})
|
||||
for _, e := range evids {
|
||||
toDelete[e] = struct{}{}
|
||||
}
|
||||
|
||||
return ul.alterUserEventList(userid, func(ids []string) []string {
|
||||
var newids []string
|
||||
for _, id := range ids {
|
||||
if _, del := toDelete[id]; del {
|
||||
continue
|
||||
}
|
||||
|
||||
newids = append(newids, id)
|
||||
}
|
||||
return newids
|
||||
})
|
||||
}
|
||||
|
||||
// StoreGlobalEvent will store a global event that will be returned with each `GetEvents` request
|
||||
func (ul *UserlogService) StoreGlobalEvent(ctx context.Context, typ string, data map[string]string) error {
|
||||
ctx, span := ul.tracer.Start(ctx, "StoreGlobalEvent")
|
||||
defer span.End()
|
||||
switch typ {
|
||||
default:
|
||||
return fmt.Errorf("unknown event type: %s", typ)
|
||||
case "deprovision":
|
||||
dps, ok := data["deprovision_date"]
|
||||
if !ok {
|
||||
return errors.New("need 'deprovision_date' in request body")
|
||||
}
|
||||
|
||||
format := data["deprovision_date_format"]
|
||||
if format == "" {
|
||||
format = time.RFC3339
|
||||
}
|
||||
|
||||
date, err := time.Parse(format, dps)
|
||||
if err != nil {
|
||||
fmt.Println("", format, "\n", dps)
|
||||
return fmt.Errorf("cannot parse time to format. time: '%s' format: '%s'", dps, format)
|
||||
}
|
||||
|
||||
ev := DeprovisionData{
|
||||
DeprovisionDate: date,
|
||||
DeprovisionFormat: format,
|
||||
}
|
||||
|
||||
b, err := json.Marshal(ev)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ul.alterGlobalEvents(ctx, func(evs map[string]json.RawMessage) error {
|
||||
evs[typ] = b
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// GetGlobalEvents will return all global events
|
||||
func (ul *UserlogService) GetGlobalEvents(ctx context.Context) (map[string]json.RawMessage, error) {
|
||||
_, span := ul.tracer.Start(ctx, "GetGlobalEvents")
|
||||
defer span.End()
|
||||
out := make(map[string]json.RawMessage)
|
||||
|
||||
recs, err := ul.store.Read(_globalEventsKey)
|
||||
if err != nil && err != store.ErrNotFound {
|
||||
return out, err
|
||||
}
|
||||
|
||||
if len(recs) > 0 {
|
||||
if err := json.Unmarshal(recs[0].Value, &out); err != nil {
|
||||
return out, err
|
||||
}
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// DeleteGlobalEvents will delete the specified event
|
||||
func (ul *UserlogService) DeleteGlobalEvents(ctx context.Context, evnames []string) error {
|
||||
_, span := ul.tracer.Start(ctx, "DeleteGlobalEvents")
|
||||
defer span.End()
|
||||
return ul.alterGlobalEvents(ctx, func(evs map[string]json.RawMessage) error {
|
||||
for _, name := range evnames {
|
||||
delete(evs, name)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (ul *UserlogService) addEventToUser(userid string, event events.Event) error {
|
||||
return ul.alterUserEventList(userid, func(ids []string) []string {
|
||||
return append(ids, event.ID)
|
||||
})
|
||||
}
|
||||
|
||||
func (ul *UserlogService) sendSSE(ctx context.Context, userIDs []string, event events.Event, gatewaySelector pool.Selectable[gateway.GatewayAPIClient]) error {
|
||||
m := make(map[string]events.SendSSE)
|
||||
|
||||
for _, userid := range userIDs {
|
||||
loc := l10n.MustGetUserLocale(ctx, userid, "", ul.valueClient)
|
||||
if ev, ok := m[loc]; ok {
|
||||
ev.UserIDs = append(m[loc].UserIDs, userid)
|
||||
m[loc] = ev
|
||||
continue
|
||||
}
|
||||
|
||||
ev, err := NewConverter(ctx, loc, gatewaySelector, ul.cfg.Service.Name, ul.cfg.TranslationPath, ul.cfg.DefaultLanguage).ConvertEvent(event.ID, event.Event)
|
||||
if err != nil {
|
||||
if utils.IsErrNotFound(err) || utils.IsErrPermissionDenied(err) {
|
||||
// the resource was not found, we assume it is deleted
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
b, err := json.Marshal(ev)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m[loc] = events.SendSSE{
|
||||
UserIDs: []string{userid},
|
||||
Type: "userlog-notification",
|
||||
Message: b,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for _, ev := range m {
|
||||
if err := events.Publish(ctx, ul.publisher, ev); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ul *UserlogService) removeExpiredEvents(userid string, all []string, received []*ehmsg.Event) error {
|
||||
exists := make(map[string]struct{}, len(received))
|
||||
for _, e := range received {
|
||||
exists[e.Id] = struct{}{}
|
||||
}
|
||||
|
||||
var toDelete []string
|
||||
for _, eid := range all {
|
||||
if _, ok := exists[eid]; !ok {
|
||||
toDelete = append(toDelete, eid)
|
||||
}
|
||||
}
|
||||
|
||||
if len(toDelete) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ul.DeleteEvents(userid, toDelete)
|
||||
}
|
||||
|
||||
func (ul *UserlogService) alterUserEventList(userid string, alter func([]string) []string) error {
|
||||
recs, err := ul.store.Read(userid)
|
||||
if err != nil && err != store.ErrNotFound {
|
||||
return err
|
||||
}
|
||||
|
||||
var ids []string
|
||||
if len(recs) > 0 {
|
||||
if err := json.Unmarshal(recs[0].Value, &ids); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
ids = alter(ids)
|
||||
|
||||
// store reacts unforseeable when trying to store nil values
|
||||
if len(ids) == 0 {
|
||||
return ul.store.Delete(userid)
|
||||
}
|
||||
|
||||
b, err := json.Marshal(ids)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ul.store.Write(&store.Record{
|
||||
Key: userid,
|
||||
Value: b,
|
||||
})
|
||||
}
|
||||
|
||||
func (ul *UserlogService) alterGlobalEvents(ctx context.Context, alter func(map[string]json.RawMessage) error) error {
|
||||
_, span := ul.tracer.Start(ctx, "alterGlobalEvents")
|
||||
defer span.End()
|
||||
evs, err := ul.GetGlobalEvents(ctx)
|
||||
if err != nil && err != store.ErrNotFound {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := alter(evs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val, err := json.Marshal(evs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ul.store.Write(&store.Record{
|
||||
Key: "global-events",
|
||||
Value: val,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package service_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
mRegistry "go-micro.dev/v4/registry"
|
||||
|
||||
"github.com/qsfera/server/pkg/registry"
|
||||
)
|
||||
|
||||
func init() {
|
||||
r := registry.GetRegistry(registry.Inmemory())
|
||||
service := registry.BuildGRPCService("qsfera.api.gateway", "", "", "")
|
||||
service.Nodes = []*mRegistry.Node{{
|
||||
Address: "any",
|
||||
}}
|
||||
|
||||
_ = r.Register(service)
|
||||
}
|
||||
func TestSearch(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Userlog service Suite")
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
package service_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
settingsmsg "github.com/qsfera/server/protogen/gen/qsfera/messages/settings/v0"
|
||||
|
||||
gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1"
|
||||
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
rpc "github.com/cs3org/go-cs3apis/cs3/rpc/v1beta1"
|
||||
provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/google/uuid"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/events"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/rgrpc/todo/pool"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/store"
|
||||
"github.com/opencloud-eu/reva/v2/pkg/utils"
|
||||
cs3mocks "github.com/opencloud-eu/reva/v2/tests/cs3mocks/mocks"
|
||||
"github.com/stretchr/testify/mock"
|
||||
microevents "go-micro.dev/v4/events"
|
||||
microstore "go-micro.dev/v4/store"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/qsfera/server/pkg/log"
|
||||
ehmsg "github.com/qsfera/server/protogen/gen/qsfera/messages/eventhistory/v0"
|
||||
ehsvc "github.com/qsfera/server/protogen/gen/qsfera/services/eventhistory/v0"
|
||||
"github.com/qsfera/server/protogen/gen/qsfera/services/eventhistory/v0/mocks"
|
||||
settingssvc "github.com/qsfera/server/protogen/gen/qsfera/services/settings/v0"
|
||||
settingsmocks "github.com/qsfera/server/protogen/gen/qsfera/services/settings/v0/mocks"
|
||||
"github.com/qsfera/server/services/userlog/pkg/config"
|
||||
"github.com/qsfera/server/services/userlog/pkg/service"
|
||||
)
|
||||
|
||||
var _ = Describe("UserlogService", func() {
|
||||
var (
|
||||
cfg = &config.Config{
|
||||
MaxConcurrency: 5,
|
||||
}
|
||||
|
||||
ul *service.UserlogService
|
||||
bus testBus
|
||||
sto microstore.Store
|
||||
|
||||
gatewayClient *cs3mocks.GatewayAPIClient
|
||||
gatewaySelector pool.Selectable[gateway.GatewayAPIClient]
|
||||
|
||||
ehc mocks.EventHistoryService
|
||||
vc settingsmocks.ValueService
|
||||
)
|
||||
|
||||
BeforeEach(func() {
|
||||
var err error
|
||||
sto = store.Create()
|
||||
bus = testBus(make(chan events.Event))
|
||||
|
||||
pool.RemoveSelector("GatewaySelector" + "qsfera.api.gateway")
|
||||
gatewayClient = &cs3mocks.GatewayAPIClient{}
|
||||
gatewaySelector = pool.GetSelector[gateway.GatewayAPIClient](
|
||||
"GatewaySelector",
|
||||
"qsfera.api.gateway",
|
||||
func(cc grpc.ClientConnInterface) gateway.GatewayAPIClient {
|
||||
return gatewayClient
|
||||
},
|
||||
)
|
||||
|
||||
o := utils.AppendJSONToOpaque(nil, "grants", map[string]*provider.ResourcePermissions{"userid": {Stat: true}})
|
||||
gatewayClient.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(&provider.ListStorageSpacesResponse{StorageSpaces: []*provider.StorageSpace{
|
||||
{
|
||||
Opaque: o,
|
||||
SpaceType: "project",
|
||||
},
|
||||
}, Status: &rpc.Status{Code: rpc.Code_CODE_OK}}, nil)
|
||||
gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(&user.GetUserResponse{User: &user.User{Id: &user.UserId{OpaqueId: "userid"}}, Status: &rpc.Status{Code: rpc.Code_CODE_OK}}, nil)
|
||||
gatewayClient.On("Authenticate", mock.Anything, mock.Anything).Return(&gateway.AuthenticateResponse{Status: &rpc.Status{Code: rpc.Code_CODE_OK}}, nil)
|
||||
vc.On("GetValueByUniqueIdentifiers", mock.Anything, mock.Anything).Return(&settingssvc.GetValueResponse{
|
||||
Value: &settingsmsg.ValueWithIdentifier{
|
||||
Value: &settingsmsg.Value{
|
||||
Value: &settingsmsg.Value_CollectionValue{
|
||||
CollectionValue: &settingsmsg.CollectionValue{
|
||||
Values: []*settingsmsg.CollectionOption{
|
||||
{
|
||||
Key: "in-app",
|
||||
Option: &settingsmsg.CollectionOption_BoolValue{BoolValue: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil)
|
||||
|
||||
ul, err = service.NewUserlogService(
|
||||
service.Config(cfg),
|
||||
service.Stream(bus),
|
||||
service.Store(sto),
|
||||
service.Logger(log.NewLogger()),
|
||||
service.Mux(chi.NewMux()),
|
||||
service.GatewaySelector(gatewaySelector),
|
||||
service.HistoryClient(&ehc),
|
||||
service.ValueClient(&vc),
|
||||
service.RegisteredEvents([]events.Unmarshaller{
|
||||
events.SpaceDisabled{},
|
||||
}),
|
||||
service.TraceProvider(trace.NewNoopTracerProvider()),
|
||||
)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
It("it stores, returns and deletes a couple of events", func() {
|
||||
ids := make(map[string]struct{})
|
||||
ids[bus.publish(events.SpaceDisabled{Executant: &user.UserId{OpaqueId: "executinguserid"}})] = struct{}{}
|
||||
ids[bus.publish(events.SpaceDisabled{Executant: &user.UserId{OpaqueId: "executinguserid"}})] = struct{}{}
|
||||
ids[bus.publish(events.SpaceDisabled{Executant: &user.UserId{OpaqueId: "executinguserid"}})] = struct{}{}
|
||||
// ids[bus.Publish(events.SpaceMembershipExpired{SpaceOwner: &user.UserId{OpaqueId: "userid"}})] = struct{}{}
|
||||
// ids[bus.Publish(events.ShareCreated{Executant: &user.UserId{OpaqueId: "userid"}})] = struct{}{}
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
var events []*ehmsg.Event
|
||||
for id := range ids {
|
||||
events = append(events, &ehmsg.Event{Id: id})
|
||||
}
|
||||
|
||||
ehc.On("GetEvents", mock.Anything, mock.Anything).Return(&ehsvc.GetEventsResponse{Events: events}, nil)
|
||||
|
||||
evs, err := ul.GetEvents(context.Background(), "userid")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(len(evs)).To(Equal(len(ids)))
|
||||
|
||||
var evids []string
|
||||
for _, e := range evs {
|
||||
_, exists := ids[e.Id]
|
||||
Expect(exists).To(BeTrue())
|
||||
delete(ids, e.Id)
|
||||
evids = append(evids, e.Id)
|
||||
}
|
||||
|
||||
Expect(len(ids)).To(Equal(0))
|
||||
err = ul.DeleteEvents("userid", evids)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
evs, err = ul.GetEvents(context.Background(), "userid")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(len(evs)).To(Equal(0))
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
close(bus)
|
||||
})
|
||||
})
|
||||
|
||||
type testBus chan events.Event
|
||||
|
||||
func (tb testBus) Consume(_ string, _ ...microevents.ConsumeOption) (<-chan microevents.Event, error) {
|
||||
ch := make(chan microevents.Event)
|
||||
go func() {
|
||||
for ev := range tb {
|
||||
b, _ := json.Marshal(ev.Event)
|
||||
ch <- microevents.Event{
|
||||
Payload: b,
|
||||
Metadata: map[string]string{
|
||||
events.MetadatakeyEventID: ev.ID,
|
||||
events.MetadatakeyEventType: ev.Type,
|
||||
},
|
||||
}
|
||||
}
|
||||
}()
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
func (tb testBus) Publish(_ string, _ any, _ ...microevents.PublishOption) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (tb testBus) publish(e any) string {
|
||||
ev := events.Event{
|
||||
ID: uuid.New().String(),
|
||||
Type: reflect.TypeOf(e).String(),
|
||||
Event: e,
|
||||
}
|
||||
|
||||
tb <- ev
|
||||
return ev.ID
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package service
|
||||
|
||||
import "github.com/qsfera/server/pkg/l10n"
|
||||
|
||||
// the available templates
|
||||
var (
|
||||
VirusFound = NotificationTemplate{
|
||||
Subject: l10n.Template("Virus found"),
|
||||
Message: l10n.Template("Virus found in {resource}. Upload not possible. Virus: {virus}"),
|
||||
}
|
||||
|
||||
PoliciesEnforced = NotificationTemplate{
|
||||
Subject: l10n.Template("Policies enforced"),
|
||||
Message: l10n.Template("The file {resource} was deleted because it violates the restrictions of this cloud. This could be due to an unsupported file type, potentially harmful or prohibited content, or file size limits."),
|
||||
}
|
||||
|
||||
SpaceShared = NotificationTemplate{
|
||||
Subject: l10n.Template("Space shared"),
|
||||
Message: l10n.Template("{user} added you to Space {space}"),
|
||||
}
|
||||
|
||||
SpaceUnshared = NotificationTemplate{
|
||||
Subject: l10n.Template("Removed from Space"),
|
||||
Message: l10n.Template("{user} removed you from Space {space}"),
|
||||
}
|
||||
|
||||
SpaceDisabled = NotificationTemplate{
|
||||
Subject: l10n.Template("Space disabled"),
|
||||
Message: l10n.Template("{user} disabled Space {space}"),
|
||||
}
|
||||
|
||||
SpaceDeleted = NotificationTemplate{
|
||||
Subject: l10n.Template("Space deleted"),
|
||||
Message: l10n.Template("{user} deleted Space {space}"),
|
||||
}
|
||||
|
||||
SpaceMembershipExpired = NotificationTemplate{
|
||||
Subject: l10n.Template("Membership expired"),
|
||||
Message: l10n.Template("Access to Space {space} lost"),
|
||||
}
|
||||
|
||||
ShareCreated = NotificationTemplate{
|
||||
Subject: l10n.Template("Resource shared"),
|
||||
Message: l10n.Template("{user} shared {resource} with you"),
|
||||
}
|
||||
|
||||
ShareRemoved = NotificationTemplate{
|
||||
Subject: l10n.Template("Resource unshared"),
|
||||
Message: l10n.Template("{user} unshared {resource} with you"),
|
||||
}
|
||||
|
||||
ShareExpired = NotificationTemplate{
|
||||
Subject: l10n.Template("Share expired"),
|
||||
Message: l10n.Template("Access to {resource} expired"),
|
||||
}
|
||||
|
||||
PlatformDeprovision = NotificationTemplate{
|
||||
Subject: l10n.Template("Instance will be shut down and deprovisioned"),
|
||||
Message: l10n.Template("Attention! The instance will be shut down and deprovisioned on {date}. Download all your data before that date as no access past that date is possible."),
|
||||
}
|
||||
)
|
||||
|
||||
// holds the information to turn the raw template into a parseable go template
|
||||
var _placeholders = map[string]string{
|
||||
"{user}": "{{ .username }}",
|
||||
"{space}": "{{ .spacename }}",
|
||||
"{resource}": "{{ .resourcename }}",
|
||||
"{virus}": "{{ .virusdescription }}",
|
||||
"{date}": "{{ .date }}",
|
||||
}
|
||||
|
||||
// NotificationTemplate is the data structure for the notifications
|
||||
type NotificationTemplate struct {
|
||||
Subject string
|
||||
Message string
|
||||
}
|
||||
Reference in New Issue
Block a user