Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2023.3.x feature delete page icon #1379

Merged
merged 1 commit into from
Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 68 additions & 7 deletions client/web/compose/src/views/Admin/Pages/Edit.vue
Original file line number Diff line number Diff line change
Expand Up @@ -733,14 +733,39 @@
<b-modal
v-model="showIconModal"
:title="$t('icon.configure')"
:ok-title="$t('label.saveAndClose')"
size="lg"
label-class="text-primary"
cancel-variant="link"
footer-class="d-flex align-items-center"
no-fade
@close="closeIconModal"
@ok="saveIconModal"
>
<template #modal-footer>
<c-input-confirm
v-if="attachments && selectedAttachmentID"
:disabled="(attachments && !selectedAttachmentID) || processingIcon"
size="md"
variant="danger"
@confirmed="deleteIcon"
>
{{ $t('icon.delete') }}
</c-input-confirm>

<div class="ml-auto">
<b-button
variant="link"
class="text-primary"
@click="closeIconModal"
>
{{ $t('general:label.cancel') }}
</b-button>
<b-button
variant="primary"
class="ml-2"
@click="saveIconModal"
>
{{ $t('general:label.saveAndClose') }}
</b-button>
</div>
</template>
<b-form-group
:label="$t('icon.upload')"
label-class="text-primary"
Expand Down Expand Up @@ -787,7 +812,7 @@
label-class="text-primary"
>
<div
v-if="processing"
v-if="processingIcon"
class="d-flex align-items-center justify-content-center h-100"
>
<b-spinner />
Expand Down Expand Up @@ -915,6 +940,7 @@ export default {
data () {
return {
processing: false,
processingIcon: false,

page: new compose.Page(),
initialPageState: new compose.Page(),
Expand Down Expand Up @@ -1243,19 +1269,26 @@ export default {
},

async fetchAttachments () {
this.processingIcon = true

return this.$ComposeAPI.iconList({ sort: 'id DESC' })
.then(({ set: attachments = [] }) => {
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 })
})
}
})
.catch(this.toastErrorHandler(this.$t('notification:page.iconFetchFailed')))
.finally(() => {
this.processingIcon = false
})
},

addLayoutAction () {
Expand All @@ -1281,7 +1314,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 @@ -1294,11 +1327,39 @@ export default {
}

this.icon = { type, src }

if (type === 'link' && !src) {
this.icon = {}
}

this.showIconModal = false
},

deleteIcon () {
this.processingIcon = true

return this.$ComposeAPI.iconDelete({ iconID: this.selectedAttachmentID }).then(() => {
return this.fetchAttachments().then(() => {
this.setCurrentIcon()
this.toastSuccess(this.$t('notification:page.iconDeleteSuccess'))
})
}).finally(() => {
this.processingIcon = false
}).catch(this.toastErrorHandler(this.$t('notification:page.iconDeleteFailed')))
},

closeIconModal () {
this.linkUrl = this.icon.type === 'link' ? this.icon.src : ''
this.setCurrentIcon()
this.showIconModal = false
},

setCurrentIcon () {
this.selectedAttachmentID = (this.attachments.find(a => a.url === this.icon.src) || {}).attachmentID

if (!this.selectedAttachmentID) {
this.icon = {}
}
},

makeAttachmentUrl (src) {
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
2 changes: 2 additions & 0 deletions locale/en/corteza-webapp-compose/notification.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ page:
cloneFailed: Could not clone this page
deleteFailed: Could not delete this page
iconFetchFailed: Could not fetch list of icons
iconDeleteFailed: Could not delete selected icon
iconDeleteSuccess: Icon deleted
loadFailed: Could not load the page tree
noPages: No pages found
saveFailed: Could not save this page
Expand Down
1 change: 1 addition & 0 deletions locale/en/corteza-webapp-compose/page.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ icon:
page: Page icon
upload: Upload icon
list: Uploaded icons
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