allow to delete/create file and directory
This commit is contained in:
parent
997df7cf7c
commit
8305c01dee
3 changed files with 265 additions and 39 deletions
|
|
@ -19,6 +19,12 @@ type File struct {
|
|||
Extension string `json:"extension"`
|
||||
}
|
||||
|
||||
type Directory struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
type Tree struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
|
|
@ -39,7 +45,7 @@ func getFileId(path string) string {
|
|||
|
||||
func GetTree(name, path string) Tree {
|
||||
tree := Tree{
|
||||
Id: getFileId(name),
|
||||
Id: getFileId(path),
|
||||
Name: name,
|
||||
Directories: []Tree{},
|
||||
Path: path,
|
||||
|
|
@ -103,3 +109,27 @@ func GetFiles(files *[]File, path string) *[]File {
|
|||
|
||||
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@
|
|||
<div v-if="tree !== null && fileId === null" class="w-100">
|
||||
<div class="d-block d-md-flex justify-content-between p-3">
|
||||
<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 v-if="!tree.is_root" class="p-3 border-bottom cursor" @click="cd(tree.parent)">
|
||||
|
|
@ -11,14 +14,26 @@
|
|||
</span>
|
||||
..
|
||||
</div>
|
||||
<div v-for="item in tree.directories" class="p-3 border-bottom cursor" @click="cd(item)">
|
||||
<span class="text-warning">
|
||||
<i class="fa-solid fa-folder me-2"></i>
|
||||
</span>
|
||||
|
||||
{{ item.name }}
|
||||
<div v-for="item in tree.directories" class="p-3 border-bottom flex justify-content-center">
|
||||
<div class="float-end">
|
||||
<BDropdown text="" size="sm" variant="outline-primary">
|
||||
<BDropdownItem @click="doDelete(item)">Supprimer</BDropdownItem>
|
||||
</BDropdown>
|
||||
</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 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)">
|
||||
<span class="text-success">
|
||||
<i class="fa-solid fa-file-lines"></i>
|
||||
|
|
@ -95,8 +110,11 @@ import {
|
|||
BAlert,
|
||||
BFormSelect,
|
||||
BFormFile,
|
||||
BDropdown,
|
||||
BDropdownItem,
|
||||
} from 'bootstrap-vue-next'
|
||||
|
||||
let formFile = false
|
||||
const tree = ref(null)
|
||||
const parent = ref(null)
|
||||
const fileId = ref(null)
|
||||
|
|
@ -127,14 +145,15 @@ const isEditable = (item) => {
|
|||
].includes(item.type)
|
||||
}
|
||||
|
||||
const doAdd = () => {
|
||||
const doAddFile = () => {
|
||||
const data = {file: null}
|
||||
formFile = true
|
||||
|
||||
form.value = {
|
||||
action: `/api/file`,
|
||||
action: `/api/filemanager/file`,
|
||||
method: 'POST',
|
||||
data: data,
|
||||
label: 'Importer',
|
||||
label: 'Nouveau fichier',
|
||||
error: null,
|
||||
fields: [
|
||||
{
|
||||
|
|
@ -150,41 +169,103 @@ const doAdd = () => {
|
|||
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) => {
|
||||
e.preventDefault()
|
||||
|
||||
const payload = new FormData();
|
||||
payload.append('file', form.value.data.file)
|
||||
payload.append('path', tree.value.id)
|
||||
if (formFile) {
|
||||
const payload = new FormData();
|
||||
payload.append('file', form.value.data.file)
|
||||
payload.append('path', tree.value.id)
|
||||
|
||||
fetch(`/api/file`, {
|
||||
method: form.value.method,
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: payload,
|
||||
})
|
||||
.then((response) => {
|
||||
return response.json()
|
||||
fetch(`/api/filemanager/file`, {
|
||||
method: form.value.method,
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
},
|
||||
body: payload,
|
||||
})
|
||||
.then((data) => {
|
||||
if (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}`
|
||||
.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}`
|
||||
})
|
||||
} 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) => {
|
||||
options = options || {}
|
||||
|
||||
fetch('/api/files')
|
||||
fetch('/api/filemanager/files')
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
tree.value = withParents(data)
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@ package file
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"gitnet.fr/deblan/budget/config"
|
||||
|
|
@ -18,13 +20,16 @@ type Controller struct {
|
|||
func New(e *echo.Echo) *Controller {
|
||||
c := Controller{}
|
||||
|
||||
e.GET("/api/files", c.Files)
|
||||
e.POST("/api/file", c.File)
|
||||
e.GET("/api/filemanager/files", c.List)
|
||||
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
|
||||
}
|
||||
|
||||
func (ctrl *Controller) Files(c echo.Context) error {
|
||||
func (ctrl *Controller) List(c echo.Context) error {
|
||||
if nil == model.LoadSessionUser(c) {
|
||||
return c.Redirect(302, "/login")
|
||||
}
|
||||
|
|
@ -34,7 +39,7 @@ func (ctrl *Controller) Files(c echo.Context) error {
|
|||
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) {
|
||||
return c.Redirect(302, "/login")
|
||||
}
|
||||
|
|
@ -70,6 +75,116 @@ func (ctrl *Controller) File(c echo.Context) error {
|
|||
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 {
|
||||
if tree.Id == id {
|
||||
return &tree
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue