File uploads
This commit is contained in:
parent
5319492760
commit
910a7a15c7
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
notes
|
notes
|
||||||
|
upload
|
||||||
|
33
file.go
33
file.go
@ -12,24 +12,25 @@ import (
|
|||||||
type File struct {
|
type File struct {
|
||||||
ID int
|
ID int
|
||||||
UserID int `db:"user_id"`
|
UserID int `db:"user_id"`
|
||||||
|
NodeID int
|
||||||
Filename string
|
Filename string
|
||||||
Size int64
|
Size int64
|
||||||
MIME string
|
MIME string
|
||||||
MD5 string
|
MD5 string
|
||||||
Uploaded time.Time
|
Uploaded time.Time
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (session Session) AddFile(file *File) (err error) {// {{{
|
func (session Session) AddFile(file *File) (err error) { // {{{
|
||||||
file.UserID = session.UserID
|
file.UserID = session.UserID
|
||||||
|
|
||||||
var rows *sqlx.Rows
|
var rows *sqlx.Rows
|
||||||
rows, err = db.Queryx(`
|
rows, err = db.Queryx(`
|
||||||
INSERT INTO file(user_id, filename, size, mime, md5)
|
INSERT INTO file(user_id, node_id, filename, size, mime, md5)
|
||||||
VALUES($1, $2, $3, $4, $5)
|
VALUES($1, $2, $3, $4, $5, $6)
|
||||||
RETURNING id
|
RETURNING id
|
||||||
`,
|
`,
|
||||||
file.UserID,
|
file.UserID,
|
||||||
|
file.NodeID,
|
||||||
file.Filename,
|
file.Filename,
|
||||||
file.Size,
|
file.Size,
|
||||||
file.MIME,
|
file.MIME,
|
||||||
@ -44,6 +45,28 @@ func (session Session) AddFile(file *File) (err error) {// {{{
|
|||||||
err = rows.Scan(&file.ID)
|
err = rows.Scan(&file.ID)
|
||||||
fmt.Printf("%#v\n", file)
|
fmt.Printf("%#v\n", file)
|
||||||
return
|
return
|
||||||
}// }}}
|
} // }}}
|
||||||
|
func (session Session) Files(nodeID int) (files []File, err error) { // {{{
|
||||||
|
var rows *sqlx.Rows
|
||||||
|
rows, err = db.Queryx(
|
||||||
|
`SELECT * FROM files WHERE user_id = $1 AND node_id = $2`,
|
||||||
|
session.UserID,
|
||||||
|
nodeID,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
for rows.Next() {
|
||||||
|
file := File{}
|
||||||
|
if err = rows.StructScan(&file); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
files = append(files, file)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
} // }}}
|
||||||
|
|
||||||
// vim: foldmethod=marker
|
// vim: foldmethod=marker
|
||||||
|
54
main.go
54
main.go
@ -15,13 +15,14 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
)
|
)
|
||||||
|
|
||||||
const VERSION = "v0.1.2";
|
const VERSION = "v0.1.2";
|
||||||
const LISTEN_HOST = "0.0.0.0";
|
const LISTEN_HOST = "0.0.0.0";
|
||||||
const DB_SCHEMA = 4
|
const DB_SCHEMA = 5
|
||||||
|
|
||||||
var (
|
var (
|
||||||
flagPort int
|
flagPort int
|
||||||
@ -358,9 +359,9 @@ func nodeDelete(w http.ResponseWriter, r *http.Request) {// {{{
|
|||||||
})
|
})
|
||||||
}// }}}
|
}// }}}
|
||||||
func nodeUpload(w http.ResponseWriter, r *http.Request) {// {{{
|
func nodeUpload(w http.ResponseWriter, r *http.Request) {// {{{
|
||||||
|
log.Println("/node/upload")
|
||||||
var err error
|
var err error
|
||||||
var session Session
|
var session Session
|
||||||
log.Println("/node/upload")
|
|
||||||
|
|
||||||
if session, _, err = ValidateSession(r, true); err != nil {
|
if session, _, err = ValidateSession(r, true); err != nil {
|
||||||
responseError(w, err)
|
responseError(w, err)
|
||||||
@ -369,7 +370,8 @@ func nodeUpload(w http.ResponseWriter, r *http.Request) {// {{{
|
|||||||
|
|
||||||
// Parse our multipart form, 10 << 20 specifies a maximum
|
// Parse our multipart form, 10 << 20 specifies a maximum
|
||||||
// upload of 10 MB files.
|
// upload of 10 MB files.
|
||||||
r.ParseMultipartForm(100 << 20)
|
r.Body = http.MaxBytesReader(w, r.Body, 128<<20+512)
|
||||||
|
r.ParseMultipartForm(128 << 20)
|
||||||
|
|
||||||
// FormFile returns the first file for the given key `myFile`
|
// FormFile returns the first file for the given key `myFile`
|
||||||
// it also returns the FileHeader so we can get the Filename,
|
// it also returns the FileHeader so we can get the Filename,
|
||||||
@ -381,9 +383,8 @@ func nodeUpload(w http.ResponseWriter, r *http.Request) {// {{{
|
|||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
// Read metadata of file for database,
|
// Read metadata of file for database, and also file contents
|
||||||
// and also file contents for MD5, which is used
|
// for MD5, which is used to store the file on disk.
|
||||||
// to store the file on disk.
|
|
||||||
fileBytes, err := io.ReadAll(file)
|
fileBytes, err := io.ReadAll(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
responseError(w, err)
|
responseError(w, err)
|
||||||
@ -392,7 +393,14 @@ func nodeUpload(w http.ResponseWriter, r *http.Request) {// {{{
|
|||||||
md5sumBytes := md5.Sum(fileBytes)
|
md5sumBytes := md5.Sum(fileBytes)
|
||||||
md5sum := hex.EncodeToString(md5sumBytes[:])
|
md5sum := hex.EncodeToString(md5sumBytes[:])
|
||||||
|
|
||||||
|
|
||||||
|
var nodeID int
|
||||||
|
if nodeID, err = strconv.Atoi(r.PostFormValue("NodeID")); err != nil {
|
||||||
|
responseError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
nodeFile := File{
|
nodeFile := File{
|
||||||
|
NodeID: nodeID,
|
||||||
Filename: handler.Filename,
|
Filename: handler.Filename,
|
||||||
Size: handler.Size,
|
Size: handler.Size,
|
||||||
MIME: handler.Header.Get("Content-Type"),
|
MIME: handler.Header.Get("Content-Type"),
|
||||||
@ -403,6 +411,10 @@ func nodeUpload(w http.ResponseWriter, r *http.Request) {// {{{
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Files are stored in a directory structure composed of
|
||||||
|
// the first three characters in the md5sum, which is statistically
|
||||||
|
// distributed by design, making sure there aren't too many files in
|
||||||
|
// a single directory.
|
||||||
path := filepath.Join(
|
path := filepath.Join(
|
||||||
config.Application.Directories.Upload,
|
config.Application.Directories.Upload,
|
||||||
md5sum[0:1],
|
md5sum[0:1],
|
||||||
@ -428,6 +440,36 @@ func nodeUpload(w http.ResponseWriter, r *http.Request) {// {{{
|
|||||||
|
|
||||||
responseData(w, map[string]interface{}{
|
responseData(w, map[string]interface{}{
|
||||||
"OK": true,
|
"OK": true,
|
||||||
|
"File": nodeFile,
|
||||||
|
})
|
||||||
|
}// }}}
|
||||||
|
func nodeFiles(w http.ResponseWriter, r *http.Request) {// {{{
|
||||||
|
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
|
||||||
|
}{}
|
||||||
|
if err = parseRequest(r, &req); err != nil {
|
||||||
|
responseError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
files, err = session.Files(req.NodeID)
|
||||||
|
if err != nil {
|
||||||
|
responseError(w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
responseData(w, map[string]interface{}{
|
||||||
|
"OK": true,
|
||||||
|
"Files": files,
|
||||||
})
|
})
|
||||||
}// }}}
|
}// }}}
|
||||||
|
|
||||||
|
2
sql/0005.sql
Normal file
2
sql/0005.sql
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE public.file ADD node_id int4 NOT NULL;
|
||||||
|
ALTER TABLE public.file ADD CONSTRAINT file_node_fk FOREIGN KEY (node_id) REFERENCES public.node(id) ON DELETE RESTRICT ON UPDATE RESTRICT;
|
@ -34,7 +34,6 @@ h1 {
|
|||||||
background: rgba(0, 0, 0, 0.35);
|
background: rgba(0, 0, 0, 0.35);
|
||||||
}
|
}
|
||||||
#menu {
|
#menu {
|
||||||
display: none;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 24px;
|
top: 24px;
|
||||||
right: 24px;
|
right: 24px;
|
||||||
@ -44,9 +43,6 @@ h1 {
|
|||||||
box-shadow: 5px 5px 8px 0px rgba(0, 0, 0, 0.5);
|
box-shadow: 5px 5px 8px 0px rgba(0, 0, 0, 0.5);
|
||||||
z-index: 1025;
|
z-index: 1025;
|
||||||
}
|
}
|
||||||
#menu.show {
|
|
||||||
display: initial;
|
|
||||||
}
|
|
||||||
#menu .item {
|
#menu .item {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
border-bottom: 1px solid #aaa;
|
border-bottom: 1px solid #aaa;
|
||||||
|
@ -52,11 +52,11 @@ export class NodeUI extends Component {
|
|||||||
|
|
||||||
let upload = '';
|
let upload = '';
|
||||||
if(this.upload.value)
|
if(this.upload.value)
|
||||||
upload = html`<${UploadUI} app=${this} />`
|
upload = html`<${UploadUI} nodeui=${this} />`
|
||||||
|
|
||||||
let menu = '';
|
let menu = '';
|
||||||
if(this.menu.value)
|
if(this.menu.value)
|
||||||
upload = html`<${Menu} app=${this} />`
|
upload = html`<${Menu} nodeui=${this} />`
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
${menu}
|
${menu}
|
||||||
@ -234,6 +234,9 @@ class NodeContent extends Component {
|
|||||||
}//}}}
|
}//}}}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class NodeFiles extends Component {
|
||||||
|
}
|
||||||
|
|
||||||
class Node {
|
class Node {
|
||||||
constructor(app, nodeID) {//{{{
|
constructor(app, nodeID) {//{{{
|
||||||
this.app = app
|
this.app = app
|
||||||
@ -261,14 +264,14 @@ class Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Menu extends Component {
|
class Menu extends Component {
|
||||||
render({ app }) {//{{{
|
render({ nodeui }) {//{{{
|
||||||
return html`
|
return html`
|
||||||
<div id="blackout" onclick=${()=>app.menu.value = false}></div>
|
<div id="blackout" onclick=${()=>nodeui.menu.value = false}></div>
|
||||||
<div id="menu" class="${app.menu.value ? 'show' : ''}">
|
<div id="menu">
|
||||||
<div class="item" onclick=${()=>app.renameNode()}>Rename</div>
|
<div class="item" onclick=${()=>{ nodeui.renameNode(); nodeui.menu.value = false }}>Rename</div>
|
||||||
<div class="item separator" onclick=${()=>app.deleteNode()}>Delete</div>
|
<div class="item separator" onclick=${()=>{ nodeui.deleteNode(); nodeui.menu.value = false }}>Delete</div>
|
||||||
<div class="item separator" onclick=${()=>{ app.upload.value = true; app.menu.value = false }}>Upload</div>
|
<div class="item separator" onclick=${()=>{ nodeui.upload.value = true; nodeui.menu.value = false }}>Upload</div>
|
||||||
<div class="item" onclick=${()=>app.logout()}>Log out</div>
|
<div class="item" onclick=${()=>{ nodeui.logout(); nodeui.menu.value = false }}>Log out</div>
|
||||||
</div>
|
</div>
|
||||||
`
|
`
|
||||||
}//}}}
|
}//}}}
|
||||||
@ -303,6 +306,7 @@ class UploadUI extends Component {
|
|||||||
}//}}}
|
}//}}}
|
||||||
|
|
||||||
upload() {//{{{
|
upload() {//{{{
|
||||||
|
let nodeID = this.props.nodeui.node.value.ID
|
||||||
this.fileRefs = []
|
this.fileRefs = []
|
||||||
this.progressRefs = []
|
this.progressRefs = []
|
||||||
|
|
||||||
@ -311,17 +315,23 @@ class UploadUI extends Component {
|
|||||||
for(let i = 0; i < input.files.length; i++) {
|
for(let i = 0; i < input.files.length; i++) {
|
||||||
this.fileRefs.push(createRef())
|
this.fileRefs.push(createRef())
|
||||||
this.progressRefs.push(createRef())
|
this.progressRefs.push(createRef())
|
||||||
this.postFile(input.files[i], progress=>{
|
|
||||||
this.progressRefs[i].current.innerHTML = `${progress}%`
|
this.postFile(
|
||||||
}, ()=>{
|
input.files[i],
|
||||||
this.fileRefs[i].current.classList.add("done")
|
nodeID,
|
||||||
this.progressRefs[i].current.classList.add("done")
|
progress=>{
|
||||||
})
|
this.progressRefs[i].current.innerHTML = `${progress}%`
|
||||||
|
},
|
||||||
|
()=>{
|
||||||
|
this.fileRefs[i].current.classList.add("done")
|
||||||
|
this.progressRefs[i].current.classList.add("done")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}//}}}
|
}//}}}
|
||||||
postFile(file, progressCallback, doneCallback) {//{{{
|
postFile(file, nodeID, progressCallback, doneCallback) {//{{{
|
||||||
var formdata = new FormData()
|
var formdata = new FormData()
|
||||||
formdata.append('file', file)
|
formdata.append('file', file)
|
||||||
|
formdata.append('NodeID', nodeID)
|
||||||
|
|
||||||
var request = new XMLHttpRequest()
|
var request = new XMLHttpRequest()
|
||||||
|
|
||||||
|
@ -41,7 +41,6 @@ h1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#menu {
|
#menu {
|
||||||
display: none;
|
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 24px;
|
top: 24px;
|
||||||
right: 24px;
|
right: 24px;
|
||||||
@ -51,10 +50,6 @@ h1 {
|
|||||||
box-shadow: 5px 5px 8px 0px rgba(0,0,0,0.5);
|
box-shadow: 5px 5px 8px 0px rgba(0,0,0,0.5);
|
||||||
z-index: 1025;
|
z-index: 1025;
|
||||||
|
|
||||||
&.show {
|
|
||||||
display: initial;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
border-bottom: 1px solid #aaa;
|
border-bottom: 1px solid #aaa;
|
||||||
|
Loading…
Reference in New Issue
Block a user