allow to delete/create file and directory

This commit is contained in:
Simon Vieille 2024-11-13 19:36:35 +01:00
commit 8305c01dee
3 changed files with 265 additions and 39 deletions

View file

@ -19,6 +19,12 @@ type File struct {
Extension string `json:"extension"` Extension string `json:"extension"`
} }
type Directory struct {
Id string `json:"id"`
Name string `json:"name"`
Path string `json:"path"`
}
type Tree struct { type Tree struct {
Id string `json:"id"` Id string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
@ -39,7 +45,7 @@ func getFileId(path string) string {
func GetTree(name, path string) Tree { func GetTree(name, path string) Tree {
tree := Tree{ tree := Tree{
Id: getFileId(name), Id: getFileId(path),
Name: name, Name: name,
Directories: []Tree{}, Directories: []Tree{},
Path: path, Path: path,
@ -103,3 +109,27 @@ func GetFiles(files *[]File, path string) *[]File {
return files return files
} }
func GetDirectories(files *[]Directory, path string) *[]Directory {
entries, err := os.ReadDir(path)
if err != nil {
return files
}
for _, e := range entries {
p := path + "/" + e.Name()
if e.IsDir() {
*files = append(*files, Directory{
Id: getFileId(p),
Name: e.Name(),
Path: p,
})
GetDirectories(files, p)
}
}
return files
}

View file

@ -2,7 +2,10 @@
<div v-if="tree !== null && fileId === null" class="w-100"> <div v-if="tree !== null && fileId === null" class="w-100">
<div class="d-block d-md-flex justify-content-between p-3"> <div class="d-block d-md-flex justify-content-between p-3">
<h3 class="mb-0">Fichiers</h3> <h3 class="mb-0">Fichiers</h3>
<BButton variant="primary" @click="doAdd">Ajouter</BButton> <div class="d-flex justify-content-between gap-2">
<BButton variant="primary" @click="doAddFile">+ fichier</BButton>
<BButton variant="primary" @click="doAddDirectory">+ Dossier</BButton>
</div>
</div> </div>
<div v-if="!tree.is_root" class="p-3 border-bottom cursor" @click="cd(tree.parent)"> <div v-if="!tree.is_root" class="p-3 border-bottom cursor" @click="cd(tree.parent)">
@ -11,14 +14,26 @@
</span> </span>
.. ..
</div> </div>
<div v-for="item in tree.directories" class="p-3 border-bottom cursor" @click="cd(item)"> <div v-for="item in tree.directories" class="p-3 border-bottom flex justify-content-center">
<span class="text-warning"> <div class="float-end">
<i class="fa-solid fa-folder me-2"></i> <BDropdown text="" size="sm" variant="outline-primary">
</span> <BDropdownItem @click="doDelete(item)">Supprimer</BDropdownItem>
</BDropdown>
{{ item.name }} </div>
<div class="cursor" @click="cd(item)">
<span class="text-warning cursor" >
<i class="fa-solid fa-folder me-2"></i>
</span>
{{ item.name }}
</div>
</div> </div>
<div v-for="item in tree.files"> <div v-for="item in tree.files">
<div class="float-end pt-3 me-3">
<BDropdown text="" size="sm" variant="outline-primary">
<BDropdownItem :href="`/api/filemanager/file/${item.id}/${item.name}`" target="_blank">Télécharger</BDropdownItem>
<BDropdownItem @click="doDelete(item)">Supprimer</BDropdownItem>
</BDropdown>
</div>
<div v-if="isEditable(item)" class="p-3 border-bottom cursor" @click="doEdit(item)"> <div v-if="isEditable(item)" class="p-3 border-bottom cursor" @click="doEdit(item)">
<span class="text-success"> <span class="text-success">
<i class="fa-solid fa-file-lines"></i> <i class="fa-solid fa-file-lines"></i>
@ -95,8 +110,11 @@ import {
BAlert, BAlert,
BFormSelect, BFormSelect,
BFormFile, BFormFile,
BDropdown,
BDropdownItem,
} from 'bootstrap-vue-next' } from 'bootstrap-vue-next'
let formFile = false
const tree = ref(null) const tree = ref(null)
const parent = ref(null) const parent = ref(null)
const fileId = ref(null) const fileId = ref(null)
@ -127,14 +145,15 @@ const isEditable = (item) => {
].includes(item.type) ].includes(item.type)
} }
const doAdd = () => { const doAddFile = () => {
const data = {file: null} const data = {file: null}
formFile = true
form.value = { form.value = {
action: `/api/file`, action: `/api/filemanager/file`,
method: 'POST', method: 'POST',
data: data, data: data,
label: 'Importer', label: 'Nouveau fichier',
error: null, error: null,
fields: [ fields: [
{ {
@ -150,41 +169,103 @@ const doAdd = () => {
formShow.value = true formShow.value = true
} }
const doAddDirectory = () => {
const data = {directory: null, path: tree.value.id}
formFile = false
form.value = {
action: `/api/filemanager/directory`,
method: 'POST',
data: data,
label: 'Nouveau dossier',
error: null,
fields: [
{
label: 'Nom',
widget: 'text',
required: true,
key: 'directory',
},
]
}
formShow.value = true
}
const doDelete = (item) => {
if (!confirm('Je confirme la suppression')) {
return
}
fetch(`/api/filemanager/file/${item.id}`, {
method: 'DELETE',
})
.then(() => {
refresh({current: tree.value.id})
})
}
const doSave = (e) => { const doSave = (e) => {
e.preventDefault() e.preventDefault()
const payload = new FormData(); if (formFile) {
payload.append('file', form.value.data.file) const payload = new FormData();
payload.append('path', tree.value.id) payload.append('file', form.value.data.file)
payload.append('path', tree.value.id)
fetch(`/api/file`, { fetch(`/api/filemanager/file`, {
method: form.value.method, method: form.value.method,
headers: { headers: {
'Accept': 'application/json', 'Accept': 'application/json',
}, },
body: payload, body: payload,
})
.then((response) => {
return response.json()
}) })
.then((data) => { .then((response) => {
if (data.code === 400) { return response.json()
form.value.error = data.message })
} else { .then((data) => {
form.value = null if (data && data.code === 400) {
formShow.value = false form.value.error = data.message
refresh({current: tree.value.id}) } else {
} form.value = null
}) formShow.value = false
.catch((err) => { refresh({current: tree.value.id})
form.value.error = `Une erreur s'est produite : ${err}` }
})
.catch((err) => {
form.value.error = `Une erreur s'est produite : ${err}`
})
} else {
fetch(`/api/filemanager/directory`, {
method: form.value.method,
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(form.value.data),
}) })
.then((response) => {
return response.json()
})
.then((data) => {
if (data && data.code === 400) {
form.value.error = data.message
} else {
form.value = null
formShow.value = false
refresh({current: tree.value.id})
}
})
.catch((err) => {
form.value.error = `Une erreur s'est produite : ${err}`
})
}
} }
const refresh = (options) => { const refresh = (options) => {
options = options || {} options = options || {}
fetch('/api/files') fetch('/api/filemanager/files')
.then((response) => response.json()) .then((response) => response.json())
.then((data) => { .then((data) => {
tree.value = withParents(data) tree.value = withParents(data)

View file

@ -2,8 +2,10 @@ package file
import ( import (
"bytes" "bytes"
"fmt"
"io" "io"
"os" "os"
"strings"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"gitnet.fr/deblan/budget/config" "gitnet.fr/deblan/budget/config"
@ -18,13 +20,16 @@ type Controller struct {
func New(e *echo.Echo) *Controller { func New(e *echo.Echo) *Controller {
c := Controller{} c := Controller{}
e.GET("/api/files", c.Files) e.GET("/api/filemanager/files", c.List)
e.POST("/api/file", c.File) e.POST("/api/filemanager/file", c.CreateFile)
e.POST("/api/filemanager/directory", c.CreateDirectory)
e.GET("/api/filemanager/file/:id/:name", c.Download)
e.DELETE("/api/filemanager/file/:id", c.Delete)
return &c return &c
} }
func (ctrl *Controller) Files(c echo.Context) error { func (ctrl *Controller) List(c echo.Context) error {
if nil == model.LoadSessionUser(c) { if nil == model.LoadSessionUser(c) {
return c.Redirect(302, "/login") return c.Redirect(302, "/login")
} }
@ -34,7 +39,7 @@ func (ctrl *Controller) Files(c echo.Context) error {
return c.JSON(200, tree) return c.JSON(200, tree)
} }
func (ctrl *Controller) File(c echo.Context) error { func (ctrl *Controller) CreateFile(c echo.Context) error {
if nil == model.LoadSessionUser(c) { if nil == model.LoadSessionUser(c) {
return c.Redirect(302, "/login") return c.Redirect(302, "/login")
} }
@ -70,6 +75,116 @@ func (ctrl *Controller) File(c echo.Context) error {
return c.JSON(400, false) return c.JSON(400, false)
} }
func (ctrl *Controller) CreateDirectory(c echo.Context) error {
if nil == model.LoadSessionUser(c) {
return c.Redirect(302, "/login")
}
type body struct {
Directory string `json:"directory" validate:"required"`
Path string `json:"path" validate:"required"`
}
value := new(body)
if err := c.Bind(value); err != nil {
return c.JSON(400, crud.Error{
Code: 400,
Message: "Bad request",
})
}
fmt.Printf("%+v\n", value)
value.Directory = strings.ReplaceAll(value.Directory, "..", "")
value.Directory = strings.ReplaceAll(value.Directory, "/", "")
value.Directory = strings.ReplaceAll(value.Directory, "\\", "")
value.Directory = strings.TrimSpace(value.Directory)
fmt.Printf("%+v\n", value)
if err := c.Validate(value); err != nil {
return c.JSON(400, crud.Error{
Code: 400,
Message: err.Error(),
})
}
output := searchDirectory(value.Path, f.GetTree("", config.Get().File.Path))
if output != nil {
dir := strings.ReplaceAll(output.Path+"/"+value.Directory, "//", "/")
os.MkdirAll(dir, 0750)
return c.JSON(200, true)
}
return c.JSON(400, crud.Error{
Code: 400,
Message: "Bad request",
})
}
func (ctrl *Controller) Download(c echo.Context) error {
if nil == model.LoadSessionUser(c) {
return c.Redirect(302, "/login")
}
var file string
var contentType string
files := []f.File{}
f.GetFiles(&files, config.Get().File.Path)
for _, f := range files {
if f.Id == c.Param("id") {
file = f.Path
contentType = f.Type
}
}
if file == "" {
return c.JSON(404, "File not found")
}
content, _ := os.ReadFile(file)
return c.Blob(200, contentType, content)
}
func (ctrl *Controller) Delete(c echo.Context) error {
if nil == model.LoadSessionUser(c) {
return c.Redirect(302, "/login")
}
var file string
files := []f.File{}
directories := []f.Directory{}
f.GetFiles(&files, config.Get().File.Path)
f.GetDirectories(&directories, config.Get().File.Path)
for _, f := range files {
if f.Id == c.Param("id") {
file = f.Path
}
}
for _, f := range directories {
if f.Id == c.Param("id") {
file = f.Path
}
}
if file == "" {
return c.JSON(404, "File not found")
}
os.RemoveAll(file)
return c.JSON(200, nil)
}
func searchDirectory(id string, tree f.Tree) *f.Tree { func searchDirectory(id string, tree f.Tree) *f.Tree {
if tree.Id == id { if tree.Id == id {
return &tree return &tree