Implement new API for user upload and artifacts
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Armin Friedl 2020-07-26 02:45:21 +02:00
parent e9ad499850
commit 4401659b5c
Signed by: armin
GPG key ID: 48C726EEE7FBCBC8
3 changed files with 296 additions and 357 deletions

View file

@ -2,6 +2,7 @@ package net.friedl.fling.service;
import java.util.UUID; import java.util.UUID;
import javax.persistence.EntityNotFoundException; import javax.persistence.EntityNotFoundException;
import javax.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -13,6 +14,7 @@ import net.friedl.fling.security.authentication.FlingToken;
@Slf4j @Slf4j
@Service @Service
@Transactional
public class AuthorizationService { public class AuthorizationService {
private FlingRepository flingRepository; private FlingRepository flingRepository;
@ -32,7 +34,8 @@ public class AuthorizationService {
return true; return true;
} }
if (!flingRepository.getOne(flingId).getAllowUpload()) { FlingEntity flingEntity = flingRepository.getOne(flingId);
if (flingEntity.getAllowUpload() == null || !flingEntity.getAllowUpload()) {
log.debug("Fling[.id={}] does not not allow uploads"); log.debug("Fling[.id={}] does not not allow uploads");
return false; return false;
} }

View file

@ -1,9 +1,10 @@
import log from 'loglevel'; import log from 'loglevel';
import React, {useState, useEffect, useRef} from 'react'; import React, { useState, useEffect, useRef } from 'react';
import {Switch, Route, useLocation, Link} from "react-router-dom"; import { Switch, Route, useLocation, Link } from "react-router-dom";
import {flingClient, artifactClient} from '../../util/flingclient'; import { FlingClient, AuthClient, ArtifactClient, fc } from '../../util/fc';
import { prettifyTimestamp, prettifyBytes } from '../../util/fn';
import upload from '../resources/upload.svg'; import upload from '../resources/upload.svg';
import drop from '../resources/drop.svg'; import drop from '../resources/drop.svg';
@ -12,39 +13,27 @@ function Artifacts(props) {
let [artifacts, setArtifacts] = useState([]); let [artifacts, setArtifacts] = useState([]);
useEffect(() => { useEffect(() => {
if(!props.fling) return; if (!props.fling) return;
artifactClient.getArtifacts(props.fling.id) let flingClient = new FlingClient();
.then((artifacts) => setArtifacts(artifacts)); flingClient.getArtifacts(props.fling.id)
.then(artifacts => setArtifacts(artifacts));
}, [props.fling]); }, [props.fling]);
function renderArtifact(artifact) { function renderArtifact(artifact) {
function readableBytes(bytes) {
if(bytes <= 0) return "0 KB";
var i = Math.floor(Math.log(bytes) / Math.log(1024)), return (
sizes = ['Byte', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
return (bytes / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + sizes[i];
}
function localizedDate(t) {
let d = new Date(t);
return d.toLocaleDateString();
}
return(
<div className="user-list-artifact"> <div className="user-list-artifact">
<div className="container"> <div className="container">
<div className="columns"> <div className="columns">
<div className="column col-8 col-sm-12"> <div className="column col-8 col-sm-12">
{artifact.name}<br /> {artifact.path}<br />
</div> </div>
<div className="column col-2 col-sm-6"> <div className="column col-2 col-sm-6">
<div className="text-gray">{readableBytes(artifact.size)}</div> <div className="text-gray"></div>
</div> </div>
<div className="column col-2 col-sm-6"> <div className="column col-2 col-sm-6">
<div className="text-gray float-right">{localizedDate(artifact.uploadTime)}</div> <div className="text-gray float-right">{prettifyTimestamp(artifact.creationTime)}</div>
</div> </div>
</div> </div>
</div> </div>
@ -68,34 +57,25 @@ function Upload(props) {
useEffect(() => { useEffect(() => {
// prevent browser from trying to open the file when drag event // prevent browser from trying to open the file when drag event
// not recognized properly // not recognized properly
window.addEventListener("dragover",function(e){ window.addEventListener("dragover", function(e) {
e.preventDefault(); e.preventDefault();
},false); }, false);
window.addEventListener("drop",function(e){ window.addEventListener("drop", function(e) {
e.preventDefault(); e.preventDefault();
},false); }, false);
}); });
function fileList() { function fileList() {
function readableBytes(bytes) {
if(bytes <= 0) return "0 KB";
var i = Math.floor(Math.log(bytes) / Math.log(1024)),
sizes = ['Byte', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
return (bytes / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + sizes[i];
}
let fileList = []; let fileList = [];
files.forEach((file,idx) => { files.forEach((file, idx) => {
if(!file.uploaded) { if (!file.uploaded) {
fileList.push( fileList.push(
<div className="column col-6 col-md-12 mb-2"> <div className="column col-6 col-md-12 mb-2">
<div className="card"> <div className="card">
<div className="card-header"> <div className="card-header">
<i className="icon icon-cross float-right c-hand" onClick={ev => deleteFile(idx)}/> <i className="icon icon-cross float-right c-hand" onClick={ev => deleteFile(idx)} />
<div className="card-title h5">{file.name}</div> <div className="card-title h5">{file.name}</div>
<div className="card-subtitle text-gray">{(new Date(file.lastModified)).toLocaleString()+", "+readableBytes(file.size)}</div> <div className="card-subtitle text-gray">{(new Date(file.lastModified)).toLocaleString() + ", " + prettifyBytes(file.size)}</div>
</div> </div>
</div> </div>
</div> </div>
@ -113,21 +93,12 @@ function Upload(props) {
} }
function totalSize() { function totalSize() {
function readableBytes(bytes) {
if(bytes <= 0) return "0 KB";
var i = Math.floor(Math.log(bytes) / Math.log(1024)),
sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
return (bytes / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + sizes[i];
}
let totalSize = 0; let totalSize = 0;
for(let file of files) { for (let file of files) {
totalSize += file.size; totalSize += file.size;
} }
return readableBytes(totalSize); return prettifyBytes(totalSize);
} }
function handleClick(ev) { function handleClick(ev) {
@ -161,21 +132,21 @@ function Upload(props) {
} }
function fileListToArray(fileList) { function fileListToArray(fileList) {
if(fileList === undefined || fileList === null) { if (fileList === undefined || fileList === null) {
return []; return [];
} }
let arr = []; let arr = [];
for (let i=0; i<fileList.length; i++) { arr.push(fileList[i]); } for (let i = 0; i < fileList.length; i++) { arr.push(fileList[i]); }
return arr; return arr;
} }
function handleOnDragEnter(ev) { function handleOnDragEnter(ev) {
stopEvent(ev); stopEvent(ev);
if(dragCount === 0) setDragging(true); if (dragCount === 0) setDragging(true);
setDragCount(dragCount+1); setDragCount(dragCount + 1);
} }
function handleOnDragLeave(ev) { function handleOnDragLeave(ev) {
@ -185,7 +156,7 @@ function Upload(props) {
dc -= 1; dc -= 1;
setDragCount(dc); setDragCount(dc);
if(dc === 0) setDragging(false); if (dc === 0) setDragging(false);
} }
function stopEvent(ev) { function stopEvent(ev) {
@ -194,7 +165,7 @@ function Upload(props) {
} }
function logFiles() { function logFiles() {
log.info("Files so far: ["+files.map((i) => i.name).join(',')+"]"); log.info("Files so far: [" + files.map((i) => i.name).join(',') + "]");
} }
function setFileUploaded(idx) { function setFileUploaded(idx) {
@ -204,24 +175,30 @@ function Upload(props) {
} }
function handleUpload() { function handleUpload() {
const flingClient = new FlingClient();
const artifactClient = new ArtifactClient();
files.forEach((file, idx) => { files.forEach((file, idx) => {
artifactClient.postArtifact(props.fling.id, file) let artifact = new fc.Artifact(file.name)
.then(response => {
flingClient.postArtifact(props.fling.id, { artifact: artifact })
.then(artifact => {
artifactClient.uploadArtifactData(artifact.id, { body: file });
setFileUploaded(idx); setFileUploaded(idx);
}); });
}); });
} }
function zoneContent(dragging) { function zoneContent(dragging) {
if(dragging){ if (dragging) {
return( return (
<> <>
<img className="dropzone-icon" alt="dropzone icon" src={drop} /> <img className="dropzone-icon" alt="dropzone icon" src={drop} />
<h5 className="text-primary">Drop now!</h5> <h5 className="text-primary">Drop now!</h5>
</> </>
); );
}else { } else {
return( return (
<> <>
<img className="dropzone-icon-upload" alt="dropzone icon" src={upload} /> <img className="dropzone-icon-upload" alt="dropzone icon" src={upload} />
<h5>Click or Drop</h5> <h5>Click or Drop</h5>
@ -230,7 +207,7 @@ function Upload(props) {
} }
} }
return( return (
<div className="container"> <div className="container">
{logFiles()} {logFiles()}
<div className="columns"> <div className="columns">
@ -275,34 +252,13 @@ export default function FlingUserList(props) {
let [infoText, setInfoText] = useState(""); let [infoText, setInfoText] = useState("");
let [inProgress, setInProgress] = useState(false); let [inProgress, setInProgress] = useState(false);
useEffect((flingId) => { useEffect(() => {
if(!flingId) return; if (!props.fling.id) return;
function readableBytes(bytes) { let flingClient = new FlingClient();
if(bytes <= 0) return "0 KB"; flingClient.getArtifacts(props.fling.id)
var i = Math.floor(Math.log(bytes) / Math.log(1024)),
sizes = ['Byte', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
return (bytes / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + sizes[i];
}
function localizedDate(t) {
let d = new Date(t);
return d.toLocaleDateString();
}
artifactClient.getArtifacts(flingId)
.then((artifacts) => { .then((artifacts) => {
let totalSize = 0; setInfoText(`${prettifyTimestamp(props.fling.creationTime)} - ${artifacts.length} files`);
let countArtifacts = 0;
for(let artifact of artifacts) {
totalSize += artifact.size;
countArtifacts++;
}
setInfoText(`${localizedDate(props.fling.creationTime)} - ${countArtifacts} files - ${readableBytes(totalSize)}`);
}); });
}, [props.fling.id, props.fling.creationTime]); }, [props.fling.id, props.fling.creationTime]);
@ -310,28 +266,30 @@ export default function FlingUserList(props) {
ev.preventDefault(); ev.preventDefault();
setInProgress(true); setInProgress(true);
let authClient = new AuthClient();
flingClient.packageFling(props.fling.id) authClient.deriveToken({ singleUse: true })
.then(downloadUrl => { .then(token => {
// We need this iframe hack because with a regular href, while // We need this iframe hack because with a regular href, while
// the browser downloads the file fine, it also reloads the page, hence // the browser downloads the file fine, it also reloads the page, hence
// loosing all logs and state // loosing all logs and state
let frame = document.createElement("iframe"); let frame = document.createElement("iframe");
frame.src = downloadUrl; let url = `${process.env.REACT_APP_API.replace(/\/+$/, '')}/api/fling/${props.fling.id}/data?derivedToken=${token}`;
iframeContainer.current.appendChild(frame); log.trace(`Generated download url: ${url}`);
frame.src = url;
setInProgress(false); setInProgress(false);
iframeContainer.current.appendChild(frame);
}); });
} }
function path(tail) { function path(tail) {
if(props.fling && props.fling.sharing) { if (props.fling && props.fling.shareId) {
return `/f/${props.fling.sharing.shareUrl}/${tail}`; return `/f/${props.fling.shareId}/${tail}`;
} }
return ""; return "";
} }
return( return (
<> <>
<div className="container-center"> <div className="container-center">
@ -342,17 +300,20 @@ export default function FlingUserList(props) {
<div className="card"> <div className="card">
<ul className="tab mx-2"> <ul className="tab mx-2">
<li className={`tab-item ${location.pathname !== path("upload") ? "active": ""}`}> <li className={`tab-item ${location.pathname !== path("upload") ? "active" : ""}`}>
<Link to={path("files")}>Files</Link> <Link to={path("files")}>Files</Link>
</li> </li>
<li className={`tab-item ${location.pathname === path("upload") ? "active": ""}`}> { props.fling.allowUpload
? <li className={`tab-item ${location.pathname === path("upload") ? "active" : ""}`}>
<Link to={path("upload")}>Upload</Link> <Link to={path("upload")}>Upload</Link>
</li> </li>
: <></>
}
<li className="tab-item tab-action"> <li className="tab-item tab-action">
<div className="card-title"> <div className="card-title">
{inProgress {inProgress
? <button className="m-2 btn btn-xs btn-secondary float-right user-list-loading" disabled="true" ? <button className="m-2 btn btn-xs btn-secondary float-right user-list-loading" disabled
onClick={(ev) => ev.preventDefault()}> onClick={(ev) => ev.preventDefault()}>
<div className="loading" /> Packaging <div className="loading" /> Packaging
</button> </button>

View file

@ -1,25 +0,0 @@
import log from 'loglevel';
import React, {useState, useEffect} from 'react';
import {useParams, BrowserRouter} from 'react-router-dom';
import {flingClient} from '../../util/flingclient';
import DirectDownload from './DirectDownload';
export default function FlingUserUpload(props) {
let { shareId } = useParams();
let [fling, setFling] = useState({});
useEffect(() => {
flingClient.getFlingByShareId(shareId)
.then(f => setFling(f));
}, [shareId]);
return(
<div>
{fling.sharing && fling.sharing.directDownload ? <DirectDownload fling={fling} />: ""}
</div>
);
}