File uploads

This commit is contained in:
Magnus Åhall 2023-06-22 16:48:31 +02:00
parent 8a3970645f
commit 86cedf9531
6 changed files with 139 additions and 15 deletions

13
file.go
View File

@ -46,12 +46,21 @@ func (session Session) AddFile(file *File) (err error) { // {{{
fmt.Printf("%#v\n", file) fmt.Printf("%#v\n", file)
return return
} // }}} } // }}}
func (session Session) Files(nodeID int) (files []File, err error) { // {{{ func (session Session) Files(nodeID, fileID int) (files []File, err error) { // {{{
var rows *sqlx.Rows var rows *sqlx.Rows
rows, err = db.Queryx( rows, err = db.Queryx(
`SELECT * FROM file WHERE user_id = $1 AND node_id = $2`, `SELECT *
FROM file
WHERE
user_id = $1 AND
node_id = $2 AND
CASE $3::int
WHEN 0 THEN true
ELSE id = $3
END`,
session.UserID, session.UserID,
nodeID, nodeID,
fileID,
) )
if err != nil { if err != nil {
return return

73
main.go
View File

@ -20,7 +20,7 @@ import (
_ "embed" _ "embed"
) )
const VERSION = "v0.1.2"; const VERSION = "v0.1.4";
const LISTEN_HOST = "0.0.0.0"; const LISTEN_HOST = "0.0.0.0";
const DB_SCHEMA = 5 const DB_SCHEMA = 5
@ -31,7 +31,6 @@ var (
config Config config Config
) )
func init() {// {{{ func init() {// {{{
flag.IntVar( flag.IntVar(
&flagPort, &flagPort,
@ -76,6 +75,7 @@ func main() {// {{{
http.HandleFunc("/node/rename", nodeRename) http.HandleFunc("/node/rename", nodeRename)
http.HandleFunc("/node/delete", nodeDelete) http.HandleFunc("/node/delete", nodeDelete)
http.HandleFunc("/node/upload", nodeUpload) http.HandleFunc("/node/upload", nodeUpload)
http.HandleFunc("/node/download", nodeDownload)
http.HandleFunc("/ws", websocketHandler) http.HandleFunc("/ws", websocketHandler)
http.HandleFunc("/", staticHandler) http.HandleFunc("/", staticHandler)
@ -443,6 +443,73 @@ func nodeUpload(w http.ResponseWriter, r *http.Request) {// {{{
"File": nodeFile, "File": nodeFile,
}) })
}// }}} }// }}}
func nodeDownload(w http.ResponseWriter, r *http.Request) {// {{{
log.Println("/node/download")
var err error
var session Session
var files []File
if session, _, err = ValidateSession(r, true); err != nil {
responseError(w, err)
return
}
req := struct {
NodeID int
FileID int
}{}
if err = parseRequest(r, &req); err != nil {
responseError(w, err)
return
}
files, err = session.Files(req.NodeID, req.FileID)
if err != nil {
responseError(w, err)
return
}
if len(files) != 1 {
responseError(w, fmt.Errorf("File not found"))
return
}
var file *os.File
fname := filepath.Join(
config.Application.Directories.Upload,
files[0].MD5[0:1],
files[0].MD5[1:2],
files[0].MD5[2:3],
files[0].MD5,
)
file, err = os.Open(fname)
if err != nil {
responseError(w, err)
return
}
var finfo os.FileInfo
finfo, err = file.Stat()
if err != nil {
responseError(w, err)
return
}
read := 1
var buf []byte
for read > 0 {
buf = make([]byte, 65536)
read, err = file.Read(buf)
if read > 0 {
w.Write(buf[0:read])
}
}
w.Header().Add("Content-Type", files[0].MIME)
w.Header().Add("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, files[0].Filename))
w.Header().Add("Content-Length", strconv.Itoa(int(finfo.Size())))
}// }}}
func nodeFiles(w http.ResponseWriter, r *http.Request) {// {{{ func nodeFiles(w http.ResponseWriter, r *http.Request) {// {{{
var err error var err error
var session Session var session Session
@ -461,7 +528,7 @@ func nodeFiles(w http.ResponseWriter, r *http.Request) {// {{{
return return
} }
files, err = session.Files(req.NodeID) files, err = session.Files(req.NodeID, 0)
if err != nil { if err != nil {
responseError(w, err) responseError(w, err)
return return

View File

@ -204,7 +204,7 @@ func (session Session) Node(nodeID int) (node Node, err error) {// {{{
} }
node.Crumbs, err = session.NodeCrumbs(node.ID) node.Crumbs, err = session.NodeCrumbs(node.ID)
node.Files, err = session.Files(node.ID) node.Files, err = session.Files(node.ID, 0)
return return
}// }}} }// }}}

View File

@ -204,7 +204,7 @@ header .menu {
#file-section { #file-section {
justify-self: center; justify-self: center;
width: 900px; width: 900px;
margin-top: 32px; margin-top: 16px;
padding: 32px; padding: 32px;
background: #f5f5f5; background: #f5f5f5;
} }
@ -224,6 +224,10 @@ header .menu {
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
cursor: pointer;
}
#file-section .files .filename:hover {
text-decoration: underline;
} }
#file-section .files .size { #file-section .files .size {
white-space: nowrap; white-space: nowrap;
@ -232,13 +236,17 @@ header .menu {
.tree { .tree {
padding: 16px; padding: 16px;
} }
@media only screen and (max-width: 100ex) { @media only screen and (max-width: 932px) {
.node-content { .node-content {
width: 100%; width: calc(100% - 32px);
margin-left: 16px;
padding: 16px;
justify-self: start; justify-self: start;
} }
#file-section { #file-section {
width: 100%; width: calc(100% - 32px);
padding: 16px;
margin-left: 16px;
justify-self: start; justify-self: start;
} }
} }

View File

@ -226,7 +226,7 @@ class NodeFiles extends Component {
}) })
.map(file=> .map(file=>
html` html`
<div class="filename">${file.Filename}</div> <div class="filename" onclick=${()=>node.download(file.ID)}>${file.Filename}</div>
<div class="size">${this.formatSize(file.Size)}</div> <div class="size">${this.formatSize(file.Size)}</div>
` `
) )
@ -308,6 +308,37 @@ class Node {
.then(callback) .then(callback)
.catch(this.app.responseError) .catch(this.app.responseError)
}//}}} }//}}}
download(fileID) {//{{{
let headers = {
'Content-Type': 'application/json',
}
if(this.app.session.UUID !== '')
headers['X-Session-Id'] = this.app.session.UUID
let fname = ""
fetch("/node/download", {
method: 'POST',
headers,
body: JSON.stringify({
NodeID: this.ID,
FileID: fileID,
}),
})
.then(response=>{
console.log(...response.headers);
return response.blob()
})
.then(blob=>{
let url = window.URL.createObjectURL(blob)
let a = document.createElement('a');
a.href = url;
a.download = "filename.xlsx";
document.body.appendChild(a); // we need to append the element to the dom -> otherwise it will not work in firefox
a.click();
a.remove(); //afterwards we remove the element again
})
}//}}}
children(callback) {//{{{ children(callback) {//{{{
this.app.request('/node/tree', { StartNodeID: this.ID }) this.app.request('/node/tree', { StartNodeID: this.ID })
.then(res=>{ .then(res=>{

View File

@ -240,7 +240,7 @@ header {
#file-section { #file-section {
justify-self: center; justify-self: center;
width: 900px; width: 900px;
margin-top: 32px; margin-top: 16px;
padding: 32px; padding: 32px;
background: #f5f5f5; background: #f5f5f5;
@ -261,6 +261,11 @@ header {
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
cursor: pointer;
&:hover {
text-decoration: underline;
}
} }
.size { .size {
@ -274,14 +279,18 @@ header {
padding: 16px; padding: 16px;
} }
@media only screen and (max-width: 100ex) { @media only screen and (max-width: 932px) {
.node-content { .node-content {
width: 100%; width: calc(100% - 32px);
margin-left: 16px;
padding: 16px;
justify-self: start; justify-self: start;
} }
#file-section { #file-section {
width: 100%; width: calc(100% - 32px);
padding: 16px;
margin-left: 16px;
justify-self: start; justify-self: start;
} }
} }