Skip to content

Commit

Permalink
Add an option to soft delete page icon
Browse files Browse the repository at this point in the history
  • Loading branch information
KinyaElGrande authored and katrinDY committed Oct 17, 2023
1 parent 399f9a7 commit afa8413
Show file tree
Hide file tree
Showing 11 changed files with 192 additions and 8 deletions.
2 changes: 1 addition & 1 deletion client/web/compose/src/themes/corteza-base/custom.scss
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ th {
-ms-flex: 1 1 auto;
flex: 1 1 auto;
margin-bottom: 0;
font-size: .9rem;
font-size: 0.9rem;
border-radius: 0.25rem;

.vs__selected-options {
Expand Down
58 changes: 51 additions & 7 deletions client/web/compose/src/views/Admin/Pages/Edit.vue
Original file line number Diff line number Diff line change
Expand Up @@ -721,14 +721,35 @@
<b-modal
v-model="showIconModal"
:title="$t('icon.configure')"
:ok-title="$t('label.saveAndClose')"
size="lg"
label-class="text-primary"
cancel-variant="link"
no-fade
@close="closeIconModal"
@ok="saveIconModal"
>
<template #modal-footer>
<b-button
variant="link"
class="text-primary"
@click="closeIconModal"
>
{{ $t('general:label.cancel') }}
</b-button>
<c-input-confirm
:disabled="attachments && !selectedAttachmentID"
:tooltip="$t('icon.tooltip.delete')"
size="md"
variant="danger"
class="py-1"
@confirmed="deleteIcon"
>
{{ $t('general:label.delete') }}
</c-input-confirm>
<b-button
variant="primary"
@click="saveIconModal"
>
{{ $t('general:label.saveAndClose') }}
</b-button>
</template>
<b-form-group
:label="$t('icon.upload')"
label-class="text-primary"
Expand Down Expand Up @@ -1236,7 +1257,9 @@ export default {
const baseURL = this.$ComposeAPI.baseURL
this.attachments = []
if (attachments) {
if (attachments.length === 0) {
this.icon = {}
} else {
attachments.forEach(a => {
const src = !a.url.includes(baseURL) ? this.makeAttachmentUrl(a.url) : a.url
this.attachments.push({ ...a, src })
Expand Down Expand Up @@ -1269,7 +1292,7 @@ export default {
openIconModal () {
this.linkUrl = this.icon.type === 'link' ? this.icon.src : ''
this.selectedAttachmentID = (this.attachments.find(a => a.url === this.icon.src) || {}).attachmentID
this.setCurrentIcon()
this.showIconModal = true
},
Expand All @@ -1282,11 +1305,18 @@ export default {
}
this.icon = { type, src }
if (type === 'link' && !src) {
this.icon = {}
}
this.showIconModal = false
},
closeIconModal () {
this.linkUrl = this.icon.type === 'link' ? this.icon.src : ''
this.selectedAttachmentID = (this.attachments.find(a => a.url === this.icon.src) || {}).attachmentID
this.setCurrentIcon()
this.showIconModal = false
},
makeAttachmentUrl (src) {
Expand Down Expand Up @@ -1329,6 +1359,20 @@ export default {
cancel()
})
},
deleteIcon () {
return this.$ComposeAPI.iconDelete({ iconID: this.selectedAttachmentID })
.then(() => {
this.fetchAttachments().then(() => {
this.setCurrentIcon()
})
})
.catch(this.toastErrorHandler(this.$t('notification:page.iconUndeleteFailed')))
},
setCurrentIcon () {
this.selectedAttachmentID = (this.attachments.find(a => a.url === this.icon.src) || {}).attachmentID
},
},
}
</script>
Expand Down
38 changes: 38 additions & 0 deletions lib/js/src/api-clients/compose.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1337,6 +1337,44 @@ export default class Compose {
return '/icon/'
}

// Delete icon
async iconDelete (a: KV, extra: AxiosRequestConfig = {}): Promise<KV> {
const {
iconID,
} = (a as KV) || {}
if (!iconID) {
throw Error('field iconID is empty')
}
const cfg: AxiosRequestConfig = {
...extra,
method: 'delete',
url: this.iconDeleteEndpoint({
iconID,
}),
}

return this.api().request(cfg).then(result => stdResolve(result))
}

iconDeleteCancellable (a: KV, extra: AxiosRequestConfig = {}): { response: (a: KV, extra?: AxiosRequestConfig) => Promise<KV>; cancel: () => void; } {
const cancelTokenSource = axios.CancelToken.source();
let options = {...extra, cancelToken: cancelTokenSource.token }

return {
response: () => this.iconDelete(a, options),
cancel: () => {
cancelTokenSource.cancel();
}
}
}

iconDeleteEndpoint (a: KV): string {
const {
iconID,
} = a || {}
return `/icon/${iconID}`
}

// List available page layouts
async pageLayoutListNamespace (a: KV, extra: AxiosRequestConfig = {}): Promise<KV> {
const {
Expand Down
1 change: 1 addition & 0 deletions locale/en/corteza-webapp-compose/notification.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ page:
cloneFailed: Could not clone this page
deleteFailed: Could not delete this page
iconFetchFailed: Could not fetch list of icons
iconUndeleteFailed: Could not delete selected icon
loadFailed: Could not load the page tree
noPages: No pages found
saveFailed: Could not save this page
Expand Down
2 changes: 2 additions & 0 deletions locale/en/corteza-webapp-compose/page.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ icon:
page: Page icon
upload: Upload icon
list: Uploaded icons
tooltip:
delete: Delete selected icon
url:
label: Or add URL to icon
import: 'Import page(s):'
Expand Down
10 changes: 10 additions & 0 deletions server/compose/rest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,16 @@ endpoints:
- name: icon
type: "*multipart.FileHeader"
title: Icon to upload
- name: delete
path: "/{iconID}"
method: DELETE
title: Delete icon
parameters:
path:
- type: uint64
name: iconID
required: true
title: Icon ID

- title: Page Layouts
description: Compose page layouts
Expand Down
19 changes: 19 additions & 0 deletions server/compose/rest/handlers/icon.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@ type (
IconAPI interface {
List(context.Context, *request.IconList) (interface{}, error)
Upload(context.Context, *request.IconUpload) (interface{}, error)
Delete(context.Context, *request.IconDelete) (interface{}, error)
}

// HTTP API interface
Icon struct {
List func(http.ResponseWriter, *http.Request)
Upload func(http.ResponseWriter, *http.Request)
Delete func(http.ResponseWriter, *http.Request)
}
)

Expand Down Expand Up @@ -62,6 +64,22 @@ func NewIcon(h IconAPI) *Icon {
return
}

api.Send(w, r, value)
},
Delete: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewIconDelete()
if err := params.Fill(r); err != nil {
api.Send(w, r, err)
return
}

value, err := h.Delete(r.Context(), params)
if err != nil {
api.Send(w, r, err)
return
}

api.Send(w, r, value)
},
}
Expand All @@ -72,5 +90,6 @@ func (h Icon) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.H
r.Use(middlewares...)
r.Get("/icon/", h.List)
r.Post("/icon/", h.Upload)
r.Delete("/icon/{iconID}", h.Delete)
})
}
19 changes: 19 additions & 0 deletions server/compose/rest/icon.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package rest

import (
"context"
"github.com/cortezaproject/corteza/server/pkg/api"
"github.com/cortezaproject/corteza/server/pkg/auth"
"github.com/cortezaproject/corteza/server/pkg/errors"
"mime/multipart"

"github.com/cortezaproject/corteza/server/compose/rest/request"
Expand Down Expand Up @@ -56,6 +59,9 @@ func (ctrl *Icon) List(ctx context.Context, r *request.IconList) (interface{}, e
return nil, err
}

//Get only the undeleted icons
f.Deleted = filter.StateExcluded

set, f, err = ctrl.attachment.Find(ctx, f)
return ctrl.makeIconFilterPayload(ctx, set, f, err)
}
Expand Down Expand Up @@ -105,3 +111,16 @@ func (ctrl *Icon) makeIconFilterPayload(ctx context.Context, nn types.Attachment

return res, nil
}

func (ctrl *Icon) Delete(ctx context.Context, r *request.IconDelete) (interface{}, error) {
if !auth.GetIdentityFromContext(ctx).Valid() {
return nil, errors.Unauthorized("cannot delete icon")
}

_, err := ctrl.attachment.FindByID(ctx, 0, r.IconID)
if err != nil {
return nil, err
}

return api.OK(), ctrl.attachment.DeleteByID(ctx, 0, r.IconID)
}
42 changes: 42 additions & 0 deletions server/compose/rest/request/icon.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ type (
// Icon to upload
Icon *multipart.FileHeader
}

IconDelete struct {
// IconID PATH parameter
//
// Icon ID
IconID uint64 `json:",string"`
}
)

// NewIconList request
Expand Down Expand Up @@ -191,3 +198,38 @@ func (r *IconUpload) Fill(req *http.Request) (err error) {

return err
}

// NewIconDelete request
func NewIconDelete() *IconDelete {
return &IconDelete{}
}

// Auditable returns all auditable/loggable parameters
func (r IconDelete) Auditable() map[string]interface{} {
return map[string]interface{}{
"iconID": r.IconID,
}
}

// Auditable returns all auditable/loggable parameters
func (r IconDelete) GetIconID() uint64 {
return r.IconID
}

// Fill processes request and fills internal variables
func (r *IconDelete) Fill(req *http.Request) (err error) {

{
var val string
// path params

val = chi.URLParam(req, "iconID")
r.IconID, err = payload.ParseUint64(val), nil
if err != nil {
return err
}

}

return err
}
2 changes: 2 additions & 0 deletions server/compose/types/attachment.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ type (
FieldName string `json:"fieldName,omitempty"`
Filter string `json:"filter"`

Deleted filter.State `json:"deleted"`

// 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
//
Expand Down
7 changes: 7 additions & 0 deletions server/store/adapters/rdbms/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,13 @@ func DefaultFilters() (f *extendedFilters) {
return
}

// Add a filter expression for deleted attachments
if f.Deleted == filter.StateExcluded {
ee = append(ee, goqu.C("deleted_at").IsNull())
} else if f.Deleted == filter.StateExclusive {
ee = append(ee, goqu.C("deleted_at").IsNotNull())
}

return ee, f, nil
}

Expand Down

0 comments on commit afa8413

Please sign in to comment.