Skip to content

Commit

Permalink
Add option to soft delete page icon
Browse files Browse the repository at this point in the history
  • Loading branch information
KinyaElGrande committed Oct 13, 2023
1 parent 399f9a7 commit a2820ab
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 8 deletions.
34 changes: 26 additions & 8 deletions client/web/compose/src/views/Admin/Pages/Edit.vue
Original file line number Diff line number Diff line change
Expand Up @@ -785,17 +785,28 @@
v-else
class="d-flex flex-wrap"
>
<img
<div
v-for="a in attachments"
:key="a.attachmentID"
:src="a.src"
:alt="a.name"
width="auto"
height="50"
:class="{ 'selected-icon': selectedAttachmentID === a.attachmentID }"
class="rounded pointer m-2"
@click="toggleSelectedIcon(a.attachmentID)"
class="d-flex flex-column align-items-center"
>
<img
:src="a.src"
:alt="a.name"
width="auto"
height="50"
:class="{ 'selected-icon': selectedAttachmentID === a.attachmentID }"
class="rounded pointer m-2"
@click="toggleSelectedIcon(a.attachmentID)"
>
<b-button
variant="outline-danger"
size="sm"
@click="deleteAttachment(a.attachmentID)"
>
X
</b-button>
</div>
</div>
</b-form-group>
</template>
Expand Down Expand Up @@ -1246,6 +1257,13 @@ export default {
.catch(this.toastErrorHandler(this.$t('notification:page.iconFetchFailed')))
},
async deleteAttachment (attachmentID) {
return this.$ComposeAPI.iconDelete({ iconID: attachmentID })
.then(() => {
this.fetchAttachments()
})
},
addLayoutAction () {
this.layoutEditor.layout.addAction()
},
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
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 a2820ab

Please sign in to comment.