Add support for icons
- Add icon endpoint for list and upload - Add endpoint to update icon to specific page - Add compose icon settings for max size and mimetype limitation
This commit is contained in:
@@ -255,6 +255,8 @@ endpoints:
|
||||
- sqlxTypes github.com/jmoiron/sqlx/types
|
||||
- github.com/cortezaproject/corteza/server/pkg/locale
|
||||
- github.com/cortezaproject/corteza/server/pkg/label
|
||||
- github.com/cortezaproject/corteza/server/pkg/str
|
||||
- github.com/cortezaproject/corteza/server/compose/types
|
||||
parameters:
|
||||
path:
|
||||
- type: uint64
|
||||
@@ -484,15 +486,70 @@ endpoints:
|
||||
path: "/{pageID}/translation"
|
||||
parameters:
|
||||
path:
|
||||
- type: uint64
|
||||
name: pageID
|
||||
required: true
|
||||
title: ID
|
||||
- type: uint64
|
||||
name: pageID
|
||||
required: true
|
||||
title: ID
|
||||
post:
|
||||
- name: translations
|
||||
type: locale.ResourceTranslationSet
|
||||
title: Resource translation to upsert
|
||||
required: true
|
||||
- name: translations
|
||||
type: locale.ResourceTranslationSet
|
||||
title: Resource translation to upsert
|
||||
required: true
|
||||
- name: updateIcon
|
||||
path: "/{pageID}/icon"
|
||||
method: PATCH
|
||||
title: Update icon for page
|
||||
parameters:
|
||||
path:
|
||||
- type: uint64
|
||||
name: pageID
|
||||
required: true
|
||||
title: Page ID
|
||||
post:
|
||||
- name: type
|
||||
type: "types.IconType"
|
||||
required: true
|
||||
title: Icon type
|
||||
- name: source
|
||||
type: string
|
||||
title: Icon source/library
|
||||
- name: style
|
||||
type: map[string]string
|
||||
title: Icon style
|
||||
parser: str.ParseStrings
|
||||
|
||||
- title: icons
|
||||
description: Compose icons
|
||||
entrypoint: icon
|
||||
path: "/icon"
|
||||
apis:
|
||||
- name: list
|
||||
method: GET
|
||||
title: List icons
|
||||
path: "/"
|
||||
parameters:
|
||||
get:
|
||||
- type: uint
|
||||
name: limit
|
||||
title: Limit
|
||||
- type: bool
|
||||
name: incTotal
|
||||
title: Include total counter
|
||||
- type: string
|
||||
name: pageCursor
|
||||
title: Page cursor
|
||||
- type: string
|
||||
name: sort
|
||||
title: Sort items
|
||||
- name: upload
|
||||
path: "/"
|
||||
method: POST
|
||||
title: Upload icon
|
||||
parameters:
|
||||
post:
|
||||
- name: icon
|
||||
type: "*multipart.FileHeader"
|
||||
title: Icon to upload
|
||||
|
||||
- title: Modules
|
||||
description: Compose module definitions
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package handlers
|
||||
|
||||
// This file is auto-generated.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
//
|
||||
// Definitions file that controls how this file is generated:
|
||||
//
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/cortezaproject/corteza/server/compose/rest/request"
|
||||
"github.com/cortezaproject/corteza/server/pkg/api"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type (
|
||||
// Internal API interface
|
||||
IconAPI interface {
|
||||
List(context.Context, *request.IconList) (interface{}, error)
|
||||
Upload(context.Context, *request.IconUpload) (interface{}, error)
|
||||
}
|
||||
|
||||
// HTTP API interface
|
||||
Icon struct {
|
||||
List func(http.ResponseWriter, *http.Request)
|
||||
Upload func(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
)
|
||||
|
||||
func NewIcon(h IconAPI) *Icon {
|
||||
return &Icon{
|
||||
List: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewIconList()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.List(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
Upload: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewIconUpload()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.Upload(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (h Icon) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) {
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(middlewares...)
|
||||
r.Get("/icon/", h.List)
|
||||
r.Post("/icon/", h.Upload)
|
||||
})
|
||||
}
|
||||
@@ -30,6 +30,7 @@ type (
|
||||
TriggerScript(context.Context, *request.PageTriggerScript) (interface{}, error)
|
||||
ListTranslations(context.Context, *request.PageListTranslations) (interface{}, error)
|
||||
UpdateTranslations(context.Context, *request.PageUpdateTranslations) (interface{}, error)
|
||||
UpdateIcon(context.Context, *request.PageUpdateIcon) (interface{}, error)
|
||||
}
|
||||
|
||||
// HTTP API interface
|
||||
@@ -45,6 +46,7 @@ type (
|
||||
TriggerScript func(http.ResponseWriter, *http.Request)
|
||||
ListTranslations func(http.ResponseWriter, *http.Request)
|
||||
UpdateTranslations func(http.ResponseWriter, *http.Request)
|
||||
UpdateIcon func(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -224,6 +226,22 @@ func NewPage(h PageAPI) *Page {
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
UpdateIcon: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewPageUpdateIcon()
|
||||
if err := params.Fill(r); err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
value, err := h.UpdateIcon(r.Context(), params)
|
||||
if err != nil {
|
||||
api.Send(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.Send(w, r, value)
|
||||
},
|
||||
}
|
||||
@@ -243,5 +261,6 @@ func (h Page) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.H
|
||||
r.Post("/namespace/{namespaceID}/page/{pageID}/trigger", h.TriggerScript)
|
||||
r.Get("/namespace/{namespaceID}/page/{pageID}/translation", h.ListTranslations)
|
||||
r.Patch("/namespace/{namespaceID}/page/{pageID}/translation", h.UpdateTranslations)
|
||||
r.Patch("/namespace/{namespaceID}/page/{pageID}/icon", h.UpdateIcon)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"mime/multipart"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/rest/request"
|
||||
"github.com/cortezaproject/corteza/server/compose/service"
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/filter"
|
||||
)
|
||||
|
||||
type (
|
||||
iconPayload struct {
|
||||
*attachmentPayload
|
||||
}
|
||||
|
||||
iconSetPayload struct {
|
||||
Filter types.IconFilter `json:"filter"`
|
||||
Set []*iconPayload `json:"set"`
|
||||
}
|
||||
|
||||
Icon struct {
|
||||
locale service.ResourceTranslationsManagerService
|
||||
attachment service.AttachmentService
|
||||
ac iconAccessController
|
||||
}
|
||||
|
||||
iconAccessController interface {
|
||||
CanGrant(context.Context) bool
|
||||
}
|
||||
)
|
||||
|
||||
func (Icon) New() *Icon {
|
||||
return &Icon{
|
||||
locale: service.DefaultResourceTranslation,
|
||||
attachment: service.DefaultAttachment,
|
||||
ac: service.DefaultAccessControl,
|
||||
}
|
||||
}
|
||||
|
||||
func (ctrl *Icon) List(ctx context.Context, r *request.IconList) (interface{}, error) {
|
||||
var (
|
||||
err error
|
||||
f = types.AttachmentFilter{
|
||||
Kind: types.IconAttachment,
|
||||
}
|
||||
set types.AttachmentSet
|
||||
)
|
||||
|
||||
if f.Paging, err = filter.NewPaging(r.Limit, r.PageCursor); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if f.Sorting, err = filter.NewSorting(r.Sort); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
set, f, err = ctrl.attachment.Find(ctx, f)
|
||||
return ctrl.makeIconFilterPayload(ctx, set, f, err)
|
||||
}
|
||||
|
||||
func (ctrl *Icon) Upload(ctx context.Context, r *request.IconUpload) (interface{}, error) {
|
||||
file, err := r.Icon.Open()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func(file multipart.File) {
|
||||
err = file.Close()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}(file)
|
||||
|
||||
a, err := ctrl.attachment.CreateIconAttachment(
|
||||
ctx,
|
||||
r.Icon.Filename,
|
||||
r.Icon.Size,
|
||||
file,
|
||||
)
|
||||
|
||||
return makeAttachmentPayload(ctx, a, err)
|
||||
}
|
||||
|
||||
func (ctrl *Icon) makeIconFilterPayload(ctx context.Context, nn types.AttachmentSet, f types.AttachmentFilter, err error) (*iconSetPayload, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
a *attachmentPayload
|
||||
ff types.IconFilter
|
||||
)
|
||||
|
||||
ff.Paging = f.Paging
|
||||
ff.Sorting = f.Sorting
|
||||
|
||||
res := &iconSetPayload{Filter: ff, Set: make([]*iconPayload, len(nn))}
|
||||
|
||||
for i := range nn {
|
||||
a, _ = makeAttachmentPayload(ctx, nn[i], nil)
|
||||
res.Set[i] = &iconPayload{a}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/compose/rest/request"
|
||||
"github.com/cortezaproject/corteza/server/compose/service"
|
||||
"github.com/cortezaproject/corteza/server/compose/service/event"
|
||||
@@ -29,6 +28,10 @@ type (
|
||||
Set []*pagePayload `json:"set"`
|
||||
}
|
||||
|
||||
pageIconPayload struct {
|
||||
*types.PageConfigIcon
|
||||
}
|
||||
|
||||
Page struct {
|
||||
page interface {
|
||||
FindByID(ctx context.Context, namespaceID, pageID uint64) (*types.Page, error)
|
||||
@@ -42,6 +45,8 @@ type (
|
||||
Update(ctx context.Context, page *types.Page) (*types.Page, error)
|
||||
DeleteByID(ctx context.Context, namespaceID, pageID uint64, pds types.PageChildrenDeleteStrategy) error
|
||||
|
||||
UpdateIcon(ctx context.Context, namespaceID, pageID uint64, icon *types.PageConfigIcon) (out *types.PageConfigIcon, err error)
|
||||
|
||||
Reorder(ctx context.Context, namespaceID, selfID uint64, pageIDs []uint64) error
|
||||
}
|
||||
locale service.ResourceTranslationsManagerService
|
||||
@@ -91,6 +96,7 @@ func (ctrl *Page) List(ctx context.Context, r *request.PageList) (interface{}, e
|
||||
}
|
||||
|
||||
set, filter, err := ctrl.page.Find(ctx, f)
|
||||
|
||||
return ctrl.makeFilterPayload(ctx, set, filter, err)
|
||||
}
|
||||
|
||||
@@ -238,6 +244,20 @@ func (ctrl *Page) TriggerScript(ctx context.Context, r *request.PageTriggerScrip
|
||||
return ctrl.makePayload(ctx, page, err)
|
||||
}
|
||||
|
||||
func (ctrl *Page) UpdateIcon(ctx context.Context, r *request.PageUpdateIcon) (interface{}, error) {
|
||||
var (
|
||||
err error
|
||||
icon = &types.PageConfigIcon{
|
||||
Type: r.Type,
|
||||
Src: r.Source,
|
||||
Style: r.Style,
|
||||
}
|
||||
)
|
||||
|
||||
icon, err = ctrl.page.UpdateIcon(ctx, r.NamespaceID, r.PageID, icon)
|
||||
return ctrl.makeIconPayload(ctx, icon, err)
|
||||
}
|
||||
|
||||
func (ctrl Page) makePayload(ctx context.Context, c *types.Page, err error) (*pagePayload, error) {
|
||||
if err != nil || c == nil {
|
||||
return nil, err
|
||||
@@ -253,6 +273,16 @@ func (ctrl Page) makePayload(ctx context.Context, c *types.Page, err error) (*pa
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ctrl Page) makeIconPayload(_ context.Context, i *types.PageConfigIcon, err error) (*pageIconPayload, error) {
|
||||
if err != nil || i == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pageIconPayload{
|
||||
PageConfigIcon: i,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ctrl Page) makeTreePayload(ctx context.Context, pp types.PageSet, err error) ([]*pagePayload, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
package request
|
||||
|
||||
// This file is auto-generated.
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
//
|
||||
// Definitions file that controls how this file is generated:
|
||||
//
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza/server/pkg/payload"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// dummy vars to prevent
|
||||
// unused imports complain
|
||||
var (
|
||||
_ = chi.URLParam
|
||||
_ = multipart.ErrMessageTooLarge
|
||||
_ = payload.ParseUint64s
|
||||
_ = strings.ToLower
|
||||
_ = io.EOF
|
||||
_ = fmt.Errorf
|
||||
_ = json.NewEncoder
|
||||
)
|
||||
|
||||
type (
|
||||
// Internal API interface
|
||||
IconList struct {
|
||||
// Limit GET parameter
|
||||
//
|
||||
// Limit
|
||||
Limit uint
|
||||
|
||||
// IncTotal GET parameter
|
||||
//
|
||||
// Include total counter
|
||||
IncTotal bool
|
||||
|
||||
// PageCursor GET parameter
|
||||
//
|
||||
// Page cursor
|
||||
PageCursor string
|
||||
|
||||
// Sort GET parameter
|
||||
//
|
||||
// Sort items
|
||||
Sort string
|
||||
}
|
||||
|
||||
IconUpload struct {
|
||||
// Icon POST parameter
|
||||
//
|
||||
// Icon to upload
|
||||
Icon *multipart.FileHeader
|
||||
}
|
||||
)
|
||||
|
||||
// NewIconList request
|
||||
func NewIconList() *IconList {
|
||||
return &IconList{}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r IconList) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"limit": r.Limit,
|
||||
"incTotal": r.IncTotal,
|
||||
"pageCursor": r.PageCursor,
|
||||
"sort": r.Sort,
|
||||
}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r IconList) GetLimit() uint {
|
||||
return r.Limit
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r IconList) GetIncTotal() bool {
|
||||
return r.IncTotal
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r IconList) GetPageCursor() string {
|
||||
return r.PageCursor
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r IconList) GetSort() string {
|
||||
return r.Sort
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *IconList) Fill(req *http.Request) (err error) {
|
||||
|
||||
{
|
||||
// GET params
|
||||
tmp := req.URL.Query()
|
||||
|
||||
if val, ok := tmp["limit"]; ok && len(val) > 0 {
|
||||
r.Limit, err = payload.ParseUint(val[0]), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["incTotal"]; ok && len(val) > 0 {
|
||||
r.IncTotal, err = payload.ParseBool(val[0]), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["pageCursor"]; ok && len(val) > 0 {
|
||||
r.PageCursor, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if val, ok := tmp["sort"]; ok && len(val) > 0 {
|
||||
r.Sort, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// NewIconUpload request
|
||||
func NewIconUpload() *IconUpload {
|
||||
return &IconUpload{}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r IconUpload) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"icon": r.Icon,
|
||||
}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r IconUpload) GetIcon() *multipart.FileHeader {
|
||||
return r.Icon
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *IconUpload) Fill(req *http.Request) (err error) {
|
||||
|
||||
if strings.HasPrefix(strings.ToLower(req.Header.Get("content-type")), "application/json") {
|
||||
err = json.NewDecoder(req.Body).Decode(r)
|
||||
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
err = nil
|
||||
case err != nil:
|
||||
return fmt.Errorf("error parsing http request body: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Caching 32MB to memory, the rest to disk
|
||||
if err = req.ParseMultipartForm(32 << 20); err != nil && err != http.ErrNotMultipart {
|
||||
return err
|
||||
} else if err == nil {
|
||||
// Multipart params
|
||||
|
||||
// Ignoring icon as its handled in the POST params section
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
if err = req.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// POST params
|
||||
|
||||
if _, r.Icon, err = req.FormFile("icon"); err != nil {
|
||||
return fmt.Errorf("error processing uploaded file: %w", err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -11,9 +11,11 @@ package request
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza/server/compose/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/label"
|
||||
"github.com/cortezaproject/corteza/server/pkg/locale"
|
||||
"github.com/cortezaproject/corteza/server/pkg/payload"
|
||||
"github.com/cortezaproject/corteza/server/pkg/str"
|
||||
"github.com/go-chi/chi/v5"
|
||||
sqlxTypes "github.com/jmoiron/sqlx/types"
|
||||
"io"
|
||||
@@ -322,6 +324,33 @@ type (
|
||||
// Resource translation to upsert
|
||||
Translations locale.ResourceTranslationSet
|
||||
}
|
||||
|
||||
PageUpdateIcon struct {
|
||||
// NamespaceID PATH parameter
|
||||
//
|
||||
// Namespace ID
|
||||
NamespaceID uint64 `json:",string"`
|
||||
|
||||
// PageID PATH parameter
|
||||
//
|
||||
// Page ID
|
||||
PageID uint64 `json:",string"`
|
||||
|
||||
// Type POST parameter
|
||||
//
|
||||
// Icon type
|
||||
Type types.IconType
|
||||
|
||||
// Source POST parameter
|
||||
//
|
||||
// Icon source/library
|
||||
Source string
|
||||
|
||||
// Style POST parameter
|
||||
//
|
||||
// Icon style
|
||||
Style map[string]string
|
||||
}
|
||||
)
|
||||
|
||||
// NewPageList request
|
||||
@@ -1608,3 +1637,148 @@ func (r *PageUpdateTranslations) Fill(req *http.Request) (err error) {
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// NewPageUpdateIcon request
|
||||
func NewPageUpdateIcon() *PageUpdateIcon {
|
||||
return &PageUpdateIcon{}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PageUpdateIcon) Auditable() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"namespaceID": r.NamespaceID,
|
||||
"pageID": r.PageID,
|
||||
"type": r.Type,
|
||||
"source": r.Source,
|
||||
"style": r.Style,
|
||||
}
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PageUpdateIcon) GetNamespaceID() uint64 {
|
||||
return r.NamespaceID
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PageUpdateIcon) GetPageID() uint64 {
|
||||
return r.PageID
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PageUpdateIcon) GetType() types.IconType {
|
||||
return r.Type
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PageUpdateIcon) GetSource() string {
|
||||
return r.Source
|
||||
}
|
||||
|
||||
// Auditable returns all auditable/loggable parameters
|
||||
func (r PageUpdateIcon) GetStyle() map[string]string {
|
||||
return r.Style
|
||||
}
|
||||
|
||||
// Fill processes request and fills internal variables
|
||||
func (r *PageUpdateIcon) Fill(req *http.Request) (err error) {
|
||||
|
||||
if strings.HasPrefix(strings.ToLower(req.Header.Get("content-type")), "application/json") {
|
||||
err = json.NewDecoder(req.Body).Decode(r)
|
||||
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
err = nil
|
||||
case err != nil:
|
||||
return fmt.Errorf("error parsing http request body: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Caching 32MB to memory, the rest to disk
|
||||
if err = req.ParseMultipartForm(32 << 20); err != nil && err != http.ErrNotMultipart {
|
||||
return err
|
||||
} else if err == nil {
|
||||
// Multipart params
|
||||
|
||||
if val, ok := req.MultipartForm.Value["type"]; ok && len(val) > 0 {
|
||||
r.Type, err = types.IconType(val[0]), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.MultipartForm.Value["source"]; ok && len(val) > 0 {
|
||||
r.Source, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.MultipartForm.Value["style[]"]; ok {
|
||||
r.Style, err = str.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := req.MultipartForm.Value["style"]; ok {
|
||||
r.Style, err = str.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
if err = req.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// POST params
|
||||
|
||||
if val, ok := req.Form["type"]; ok && len(val) > 0 {
|
||||
r.Type, err = types.IconType(val[0]), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.Form["source"]; ok && len(val) > 0 {
|
||||
r.Source, err = val[0], nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if val, ok := req.Form["style[]"]; ok {
|
||||
r.Style, err = str.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if val, ok := req.Form["style"]; ok {
|
||||
r.Style, err = str.ParseStrings(val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
var val string
|
||||
// path params
|
||||
|
||||
val = chi.URLParam(req, "namespaceID")
|
||||
r.NamespaceID, err = payload.ParseUint64(val), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
val = chi.URLParam(req, "pageID")
|
||||
r.PageID, err = payload.ParseUint64(val), nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ func MountRoutes() func(r chi.Router) {
|
||||
module = Module{}.New()
|
||||
record = Record{}.New()
|
||||
page = Page{}.New()
|
||||
pageIcon = Icon{}.New()
|
||||
chart = Chart{}.New()
|
||||
notification = Notification{}.New()
|
||||
attachment = Attachment{}.New()
|
||||
@@ -34,6 +35,7 @@ func MountRoutes() func(r chi.Router) {
|
||||
handlers.NewPermissions(Permissions{}.New()).MountRoutes(r)
|
||||
handlers.NewNamespace(namespace).MountRoutes(r)
|
||||
handlers.NewPage(page).MountRoutes(r)
|
||||
handlers.NewIcon(pageIcon).MountRoutes(r)
|
||||
handlers.NewAutomation(automation).MountRoutes(r)
|
||||
handlers.NewModule(module).MountRoutes(r)
|
||||
handlers.NewRecord(record).MountRoutes(r)
|
||||
|
||||
@@ -59,6 +59,7 @@ type (
|
||||
FindByID(ctx context.Context, namespaceID, attachmentID uint64) (*types.Attachment, error)
|
||||
Find(ctx context.Context, filter types.AttachmentFilter) (types.AttachmentSet, types.AttachmentFilter, error)
|
||||
CreatePageAttachment(ctx context.Context, namespaceID uint64, name string, size int64, fh io.ReadSeeker, pageID uint64) (*types.Attachment, error)
|
||||
CreateIconAttachment(ctx context.Context, name string, size int64, fh io.ReadSeeker) (*types.Attachment, error)
|
||||
CreateRecordAttachment(ctx context.Context, namespaceID uint64, name string, size int64, fh io.ReadSeeker, moduleID, recordID uint64, fieldName string) (*types.Attachment, error)
|
||||
CreateNamespaceAttachment(ctx context.Context, name string, size int64, fh io.ReadSeeker) (*types.Attachment, error)
|
||||
OpenOriginal(att *types.Attachment) (io.ReadSeekCloser, error)
|
||||
@@ -82,7 +83,7 @@ func (svc attachment) Find(ctx context.Context, filter types.AttachmentFilter) (
|
||||
)
|
||||
|
||||
err = func() error {
|
||||
if filter.NamespaceID == 0 {
|
||||
if filter.NamespaceID == 0 && filter.Kind != types.IconAttachment {
|
||||
return AttachmentErrInvalidNamespaceID()
|
||||
}
|
||||
|
||||
@@ -111,7 +112,7 @@ func (svc attachment) Find(ctx context.Context, filter types.AttachmentFilter) (
|
||||
}
|
||||
}
|
||||
|
||||
set, f, err = store.SearchComposeAttachments(ctx, svc.store, f)
|
||||
set, f, err = store.SearchComposeAttachments(ctx, svc.store, filter)
|
||||
return err
|
||||
}()
|
||||
|
||||
@@ -163,7 +164,7 @@ func (svc attachment) DeleteByID(ctx context.Context, namespaceID, attachmentID
|
||||
return svc.recordAction(ctx, aProps, AttachmentActionDelete, err)
|
||||
}
|
||||
|
||||
//func (svc attachment) findNamespaceByID(namespaceID uint64) (ns *types.Namespace, err error) {
|
||||
// func (svc attachment) findNamespaceByID(namespaceID uint64) (ns *types.Namespace, err error) {
|
||||
// if namespaceID == 0 {
|
||||
// return nil, AttachmentErrInvalidNamespaceID()
|
||||
// }
|
||||
@@ -178,9 +179,9 @@ func (svc attachment) DeleteByID(ctx context.Context, namespaceID, attachmentID
|
||||
// }
|
||||
//
|
||||
// return ns, nil
|
||||
//}
|
||||
// }
|
||||
//
|
||||
//func (svc attachment) findPageByID(namespaceID, pageID uint64) (p *types.Page, err error) {
|
||||
// func (svc attachment) findPageByID(namespaceID, pageID uint64) (p *types.Page, err error) {
|
||||
// if pageID == 0 {
|
||||
// return nil, AttachmentErrInvalidPageID()
|
||||
// }
|
||||
@@ -195,9 +196,9 @@ func (svc attachment) DeleteByID(ctx context.Context, namespaceID, attachmentID
|
||||
// }
|
||||
//
|
||||
// return p, nil
|
||||
//}
|
||||
// }
|
||||
//
|
||||
//func (svc attachment) findModuleByID(namespaceID, moduleID uint64) (m *types.Module, err error) {
|
||||
// func (svc attachment) findModuleByID(namespaceID, moduleID uint64) (m *types.Module, err error) {
|
||||
// if moduleID == 0 {
|
||||
// return nil, AttachmentErrInvalidModuleID()
|
||||
// }
|
||||
@@ -212,9 +213,9 @@ func (svc attachment) DeleteByID(ctx context.Context, namespaceID, attachmentID
|
||||
// }
|
||||
//
|
||||
// return m, nil
|
||||
//}
|
||||
// }
|
||||
//
|
||||
//func (svc attachment) findRecordByID(m *types.Module, recordID uint64) (r *types.Record, err error) {
|
||||
// func (svc attachment) findRecordByID(m *types.Module, recordID uint64) (r *types.Record, err error) {
|
||||
// if recordID == 0 {
|
||||
// return nil, AttachmentErrInvalidRecordID()
|
||||
// }
|
||||
@@ -229,7 +230,7 @@ func (svc attachment) DeleteByID(ctx context.Context, namespaceID, attachmentID
|
||||
// }
|
||||
//
|
||||
// return r, nil
|
||||
//}
|
||||
// }
|
||||
|
||||
func (svc attachment) OpenOriginal(att *types.Attachment) (io.ReadSeekCloser, error) {
|
||||
if len(att.Url) == 0 {
|
||||
@@ -281,20 +282,10 @@ func (svc attachment) CreatePageAttachment(ctx context.Context, namespaceID uint
|
||||
var (
|
||||
maxSize = int64(systemService.CurrentSettings.Compose.Page.Attachments.MaxSize) * megabyte
|
||||
allowedTypes = systemService.CurrentSettings.Compose.Page.Attachments.Mimetypes
|
||||
mimeType *mimetype.MIME
|
||||
)
|
||||
|
||||
if maxSize > 0 && maxSize < size {
|
||||
return AttachmentErrTooLarge().Apply(
|
||||
errors.Meta("size", size),
|
||||
errors.Meta("maxSize", maxSize),
|
||||
)
|
||||
}
|
||||
|
||||
if mimeType, err = svc.extractMimetype(fh); err != nil {
|
||||
if err = svc.verifySizeAndMimetype(fh, size, maxSize, allowedTypes); err != nil {
|
||||
return err
|
||||
} else if !svc.checkMimeType(mimeType, allowedTypes...) {
|
||||
return AttachmentErrNotAllowedToUploadThisType()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,6 +302,63 @@ func (svc attachment) CreatePageAttachment(ctx context.Context, namespaceID uint
|
||||
|
||||
}
|
||||
|
||||
func (svc attachment) CreateIconAttachment(ctx context.Context, name string, size int64, fh io.ReadSeeker) (att *types.Attachment, err error) {
|
||||
var (
|
||||
aProps = &attachmentActionProps{}
|
||||
)
|
||||
|
||||
err = store.Tx(ctx, svc.store, func(ctx context.Context, s store.Storer) (err error) {
|
||||
if size == 0 {
|
||||
return AttachmentErrNotAllowedToCreateEmptyAttachment()
|
||||
}
|
||||
|
||||
{
|
||||
// Verify size and type of the uploaded page attachment
|
||||
// Max size & allowed mime-types are pulled from the current settings
|
||||
var (
|
||||
maxSize = int64(systemService.CurrentSettings.Compose.Icon.Attachments.MaxSize) * megabyte
|
||||
allowedTypes = systemService.CurrentSettings.Compose.Icon.Attachments.Mimetypes
|
||||
)
|
||||
|
||||
if err = svc.verifySizeAndMimetype(fh, size, maxSize, allowedTypes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
att = &types.Attachment{
|
||||
Name: strings.TrimSpace(name),
|
||||
Kind: types.IconAttachment,
|
||||
}
|
||||
|
||||
return svc.create(ctx, s, name, size, fh, att)
|
||||
})
|
||||
|
||||
return att, svc.recordAction(ctx, aProps, AttachmentActionCreate, err)
|
||||
}
|
||||
|
||||
func (svc attachment) verifySizeAndMimetype(fh io.ReadSeeker, size, maxSize int64, allowedTypes []string) (err error) {
|
||||
// Verify size and type of the uploaded page attachment
|
||||
// Max size & allowed mime-types are pulled from the current settings
|
||||
var (
|
||||
mimeType *mimetype.MIME
|
||||
)
|
||||
|
||||
if maxSize > 0 && maxSize < size {
|
||||
return AttachmentErrTooLarge().Apply(
|
||||
errors.Meta("size", size),
|
||||
errors.Meta("maxSize", maxSize),
|
||||
)
|
||||
}
|
||||
|
||||
if mimeType, err = svc.extractMimetype(fh); err != nil {
|
||||
return err
|
||||
} else if !svc.checkMimeType(mimeType, allowedTypes...) {
|
||||
return AttachmentErrNotAllowedToUploadThisType()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (svc attachment) CreateRecordAttachment(ctx context.Context, namespaceID uint64, name string, size int64, fh io.ReadSeeker, moduleID, recordID uint64, fieldName string) (att *types.Attachment, err error) {
|
||||
var (
|
||||
ns *types.Namespace
|
||||
|
||||
@@ -231,7 +231,6 @@ func (svc page) Tree(ctx context.Context, namespaceID uint64) (tree types.PageSe
|
||||
}
|
||||
|
||||
// Reorder pages
|
||||
//
|
||||
func (svc page) Reorder(ctx context.Context, namespaceID, parentID uint64, pageIDs []uint64) (err error) {
|
||||
var (
|
||||
aProps = &pageActionProps{page: &types.Page{ID: parentID}}
|
||||
@@ -442,6 +441,23 @@ func (svc page) UndeleteByID(ctx context.Context, namespaceID, pageID uint64) er
|
||||
})
|
||||
}
|
||||
|
||||
func (svc page) UpdateIcon(ctx context.Context, namespaceID, pageID uint64, icon *types.PageConfigIcon) (out *types.PageConfigIcon, err error) {
|
||||
err = store.Tx(ctx, svc.store, func(ctx context.Context, s store.Storer) (err error) {
|
||||
ns, p, err := loadPageCombo(ctx, s, namespaceID, pageID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
p.Config.NavItem.Icon = icon
|
||||
p, err = svc.updater(ctx, svc.store, ns, p, PageActionUpdate, svc.handleUpdate(ctx, p))
|
||||
out = p.Config.NavItem.Icon
|
||||
|
||||
return
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (svc page) updater(ctx context.Context, s store.Storer, ns *types.Namespace, res *types.Page, action func(...*pageActionProps) *pageAction, fn pageUpdateHandler) (*types.Page, error) {
|
||||
var (
|
||||
changes pageChanges
|
||||
|
||||
@@ -60,14 +60,27 @@ type (
|
||||
Image *AttachmentImageMeta `json:"image,omitempty"`
|
||||
}
|
||||
|
||||
AttachmentIconMeta struct {
|
||||
Name string `json:"name"`
|
||||
Library string `json:"library"`
|
||||
}
|
||||
|
||||
AttachmentIconSvgMeta struct {
|
||||
Src string `json:"src"`
|
||||
}
|
||||
|
||||
AttachmentMeta struct {
|
||||
Original AttachmentFileMeta `json:"original"`
|
||||
Preview *AttachmentFileMeta `json:"preview,omitempty"`
|
||||
|
||||
Icon *AttachmentIconMeta `json:"icon,omitempty"`
|
||||
IconSvg *AttachmentIconSvgMeta `json:"iconSvg,omitempty"`
|
||||
}
|
||||
)
|
||||
|
||||
const (
|
||||
PageAttachment string = "page"
|
||||
IconAttachment string = "icon"
|
||||
RecordAttachment string = "record"
|
||||
NamespaceAttachment string = "namespace"
|
||||
)
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"github.com/cortezaproject/corteza/server/pkg/filter"
|
||||
)
|
||||
|
||||
type (
|
||||
Icon struct {
|
||||
}
|
||||
|
||||
IconFilter struct {
|
||||
// Check fn is called by store backend for each resource found function can
|
||||
// modify the resource and return false if store should not return it
|
||||
//
|
||||
// Store then loads additional resources to satisfy the paging parameters
|
||||
Check func(icon *Icon) (bool, error) `json:"-"`
|
||||
|
||||
// Standard helpers for paging and sorting
|
||||
filter.Sorting
|
||||
filter.Paging
|
||||
}
|
||||
|
||||
IconType string
|
||||
)
|
||||
|
||||
const (
|
||||
// IconTypeLink empty or "link" (default):
|
||||
// Indicate that src will contain an absolute or relative link to an icon.
|
||||
// Can also be used for inline images (storing "base64:" prefixed string in source).
|
||||
// This type and reference is not validated by the backend.
|
||||
IconTypeLink IconType = "link" // type, source
|
||||
// IconTypeLibrary "library"
|
||||
// Source references an icon from a library. Ref's value should be in the following
|
||||
// notation: "font-awesome://<icon-identifier>".
|
||||
// This type and source is not validated by the backend.
|
||||
IconTypeLibrary IconType = "library" // type, library(font-awesome), icon name
|
||||
// IconTypeInlineSvg "svg"
|
||||
// SRC contains raw SVG document
|
||||
IconTypeInlineSvg IconType = "inline-svg" // source
|
||||
// IconTypeAttachment "attachment"
|
||||
// Reference (ID) to an existing attachment in local Corteza instance is expected
|
||||
// This type and reference must be validated by the backend.
|
||||
IconTypeAttachment IconType = "attachment" // type, Icon, name
|
||||
)
|
||||
@@ -95,19 +95,21 @@ type (
|
||||
PageConfig struct {
|
||||
// How page is presented in the navigation
|
||||
NavItem struct {
|
||||
Icon *PageConfigIcon `json:"icon,omitempty"`
|
||||
// Expanded menu
|
||||
Expanded bool `json:"expanded"`
|
||||
Icon *PageConfigIcon `json:"icon,omitempty"`
|
||||
} `json:"navItem"`
|
||||
|
||||
Buttons *PageButtonConfig `json:"buttons,omitempty"`
|
||||
|
||||
//// Example how page-config structure can evolve in the future
|
||||
//Views []struct {
|
||||
// // Example how page-config structure can evolve in the future
|
||||
// Views []struct {
|
||||
// // what kind of output is this view intended for (screen, mobile...?)
|
||||
// Output string
|
||||
//
|
||||
// // Migrated page blocks, might be replaced someday with a more complex structure
|
||||
// Blocks []PageBlock
|
||||
//}
|
||||
// }
|
||||
}
|
||||
|
||||
PageConfigIcon struct {
|
||||
@@ -129,14 +131,14 @@ type (
|
||||
// Type: "svg"
|
||||
// SRC contains raw SVG document
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// //////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Other types that might be implemented in the future:
|
||||
// "attachment"
|
||||
// Reference (ID) to an existing attachment in local Corteza instance is expected
|
||||
// This type and reference must be validated by the backend.
|
||||
|
||||
Type string `json:"type,omitempty"`
|
||||
Src string `json:"src"`
|
||||
Type IconType `json:"type,omitempty"`
|
||||
Src string `json:"src"`
|
||||
|
||||
// Any custom styling that should be applied to the icon
|
||||
Style map[string]string `json:"style,omitempty"`
|
||||
|
||||
Generated
+35
@@ -25,6 +25,11 @@ type (
|
||||
// This type is auto-generated.
|
||||
DeDupRuleSet []*DeDupRule
|
||||
|
||||
// IconSet slice of Icon
|
||||
//
|
||||
// This type is auto-generated.
|
||||
IconSet []*Icon
|
||||
|
||||
// ModuleSet slice of Module
|
||||
//
|
||||
// This type is auto-generated.
|
||||
@@ -203,6 +208,36 @@ func (set DeDupRuleSet) Filter(f func(*DeDupRule) (bool, error)) (out DeDupRuleS
|
||||
return
|
||||
}
|
||||
|
||||
// Walk iterates through every slice item and calls w(Icon) err
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func (set IconSet) Walk(w func(*Icon) error) (err error) {
|
||||
for i := range set {
|
||||
if err = w(set[i]); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Filter iterates through every slice item, calls f(Icon) (bool, err) and return filtered slice
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func (set IconSet) Filter(f func(*Icon) (bool, error)) (out IconSet, err error) {
|
||||
var ok bool
|
||||
out = IconSet{}
|
||||
for i := range set {
|
||||
if ok, err = f(set[i]); err != nil {
|
||||
return
|
||||
} else if ok {
|
||||
out = append(out, set[i])
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Walk iterates through every slice item and calls w(Module) err
|
||||
//
|
||||
// This function is auto-generated.
|
||||
|
||||
+56
@@ -250,6 +250,62 @@ func TestDeDupRuleSetFilter(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIconSetWalk(t *testing.T) {
|
||||
var (
|
||||
value = make(IconSet, 3)
|
||||
req = require.New(t)
|
||||
)
|
||||
|
||||
// check walk with no errors
|
||||
{
|
||||
err := value.Walk(func(*Icon) error {
|
||||
return nil
|
||||
})
|
||||
req.NoError(err)
|
||||
}
|
||||
|
||||
// check walk with error
|
||||
req.Error(value.Walk(func(*Icon) error { return fmt.Errorf("walk error") }))
|
||||
}
|
||||
|
||||
func TestIconSetFilter(t *testing.T) {
|
||||
var (
|
||||
value = make(IconSet, 3)
|
||||
req = require.New(t)
|
||||
)
|
||||
|
||||
// filter nothing
|
||||
{
|
||||
set, err := value.Filter(func(*Icon) (bool, error) {
|
||||
return true, nil
|
||||
})
|
||||
req.NoError(err)
|
||||
req.Equal(len(set), len(value))
|
||||
}
|
||||
|
||||
// filter one item
|
||||
{
|
||||
found := false
|
||||
set, err := value.Filter(func(*Icon) (bool, error) {
|
||||
if !found {
|
||||
found = true
|
||||
return found, nil
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
req.NoError(err)
|
||||
req.Len(set, 1)
|
||||
}
|
||||
|
||||
// filter error
|
||||
{
|
||||
_, err := value.Filter(func(*Icon) (bool, error) {
|
||||
return false, fmt.Errorf("filter error")
|
||||
})
|
||||
req.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestModuleSetWalk(t *testing.T) {
|
||||
var (
|
||||
value = make(ModuleSet, 3)
|
||||
|
||||
@@ -8,6 +8,8 @@ types:
|
||||
labelResourceType: compose:module:field
|
||||
Page:
|
||||
labelResourceType: compose:page
|
||||
Icon:
|
||||
noIdField: true
|
||||
Chart:
|
||||
labelResourceType: compose:chart
|
||||
Record: {}
|
||||
|
||||
@@ -2,12 +2,9 @@ package label
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/pkg/handle"
|
||||
"github.com/cortezaproject/corteza/server/pkg/label/types"
|
||||
"github.com/cortezaproject/corteza/server/pkg/str"
|
||||
"github.com/cortezaproject/corteza/server/store"
|
||||
)
|
||||
|
||||
@@ -45,35 +42,7 @@ func Changed(old, new map[string]string) bool {
|
||||
|
||||
// ParseStrings converts slice of strings with "key=val" format into
|
||||
func ParseStrings(ss []string) (m map[string]string, err error) {
|
||||
if len(ss) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
m = make(map[string]string)
|
||||
|
||||
for _, s := range ss {
|
||||
if strings.HasPrefix(s, "{") && strings.HasSuffix(s, "}") {
|
||||
// assume json
|
||||
if err = json.Unmarshal([]byte(s), &m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
kv := strings.SplitN(s, "=", 2)
|
||||
if len(kv) != 2 {
|
||||
return nil, fmt.Errorf("invalid label format")
|
||||
}
|
||||
|
||||
if !handle.IsValid(kv[0]) {
|
||||
return nil, fmt.Errorf("invalid label key format")
|
||||
}
|
||||
|
||||
m[kv[0]] = kv[1]
|
||||
}
|
||||
|
||||
return m, nil
|
||||
return str.ParseStrings(ss)
|
||||
}
|
||||
|
||||
// Search queries all matching (by kind and key-value filter) labels
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package str
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/cortezaproject/corteza/server/pkg/handle"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -29,3 +32,35 @@ func Match(str1, str2 string, algorithm int) bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func ParseStrings(ss []string) (m map[string]string, err error) {
|
||||
if len(ss) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
m = make(map[string]string)
|
||||
|
||||
for _, s := range ss {
|
||||
if strings.HasPrefix(s, "{") && strings.HasSuffix(s, "}") {
|
||||
// assume json
|
||||
if err = json.Unmarshal([]byte(s), &m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
kv := strings.SplitN(s, "=", 2)
|
||||
if len(kv) != 2 {
|
||||
return nil, fmt.Errorf("invalid label format")
|
||||
}
|
||||
|
||||
if !handle.IsValid(kv[0]) {
|
||||
return nil, fmt.Errorf("invalid label key format")
|
||||
}
|
||||
|
||||
m[kv[0]] = kv[1]
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
@@ -4,3 +4,7 @@ settings:
|
||||
|
||||
compose.page.attachments.max-size: 10
|
||||
compose.page.attachments.mimetypes: []
|
||||
|
||||
compose.icon.attachments.max-size: 10
|
||||
compose.icon.attachments.mimetypes: []
|
||||
|
||||
|
||||
@@ -89,6 +89,8 @@ func DefaultFilters() (f *extendedFilters) {
|
||||
return
|
||||
}
|
||||
|
||||
case composeType.IconAttachment:
|
||||
|
||||
case composeType.RecordAttachment:
|
||||
panic("@todo pending implementation")
|
||||
// query = query.
|
||||
|
||||
@@ -11,15 +11,14 @@ package request
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/pkg/label"
|
||||
"github.com/cortezaproject/corteza/server/pkg/payload"
|
||||
"github.com/cortezaproject/corteza/server/system/types"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// dummy vars to prevent
|
||||
|
||||
+2
-3
@@ -11,13 +11,12 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/pkg/actionlog"
|
||||
"github.com/cortezaproject/corteza/server/pkg/errors"
|
||||
"github.com/cortezaproject/corteza/server/pkg/locale"
|
||||
"github.com/cortezaproject/corteza/server/system/types"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type (
|
||||
|
||||
@@ -75,7 +75,7 @@ type (
|
||||
|
||||
PasswordConstraints PasswordConstraints `kv:"password-constraints" json:"passwordConstraints"`
|
||||
|
||||
ProfileAvatar struct { Enabled bool } `kv:"profile-avatar" json:"profile-avatar"`
|
||||
ProfileAvatar struct{ Enabled bool } `kv:"profile-avatar" json:"profile-avatar"`
|
||||
} `json:"internal"`
|
||||
|
||||
External struct {
|
||||
@@ -130,7 +130,7 @@ type (
|
||||
Enforced bool
|
||||
|
||||
// Require fresh Email OTP on every client authorization
|
||||
//Strict bool
|
||||
// Strict bool
|
||||
|
||||
Expires uint
|
||||
} `kv:"email-otp"`
|
||||
@@ -143,7 +143,7 @@ type (
|
||||
Enforced bool
|
||||
|
||||
// Require fresh TOTP on every client authorization
|
||||
//Strict bool
|
||||
// Strict bool
|
||||
|
||||
// TOTP issuer, defaults to "Corteza"
|
||||
Issuer string
|
||||
@@ -155,7 +155,7 @@ type (
|
||||
FromName string `kv:"from-name"`
|
||||
} `json:"-"`
|
||||
|
||||
//Auth Background Image settings
|
||||
// Auth Background Image settings
|
||||
UI struct {
|
||||
BackgroundImageSrc string `kv:"background-image-src" json:"backgroundImageSrc"`
|
||||
Styles string `kv:"styles" json:"styles"`
|
||||
@@ -208,6 +208,18 @@ type (
|
||||
Mimetypes []string
|
||||
}
|
||||
}
|
||||
|
||||
// Icon related settings
|
||||
Icon struct {
|
||||
// @todo implementation
|
||||
Attachments struct {
|
||||
// What is max size (in MB, so: MaxSize x 2^20)
|
||||
MaxSize uint `kv:"max-size"`
|
||||
|
||||
// List of mime-types we support,
|
||||
Mimetypes []string
|
||||
}
|
||||
}
|
||||
} `kv:"compose" json:"compose"`
|
||||
|
||||
// Federation settings
|
||||
|
||||
Reference in New Issue
Block a user