commit
50f83b47d1
16 changed files with 19686 additions and 0 deletions
@ -0,0 +1,3 @@
@@ -0,0 +1,3 @@
|
||||
{ |
||||
"plugins": ["syntax-dynamic-import"] |
||||
} |
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
node_modules |
||||
data |
||||
custom/*.js |
||||
public/dist |
||||
config.js |
||||
*.sublime-workspace |
@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
|
||||
{ |
||||
"folders": |
||||
[ |
||||
{ |
||||
"path": "." |
||||
} |
||||
] |
||||
} |
@ -0,0 +1,12 @@
@@ -0,0 +1,12 @@
|
||||
const path = require('path'); |
||||
|
||||
module.exports = { |
||||
port: 3000, |
||||
|
||||
nodeModulesPath: path.join(__dirname, 'node_modules'), |
||||
userDatabasePath: path.join(__dirname, 'data', 'users'), |
||||
fileUploadPath: path.join(__dirname, 'data', 'files'), |
||||
fileUploadPublicPath: '/files', |
||||
|
||||
jwtSecret: 'change me to a random generated string' |
||||
}; |
@ -0,0 +1,165 @@
@@ -0,0 +1,165 @@
|
||||
'use strict' |
||||
|
||||
const config = require('./config'); |
||||
const JsonUserDatabase = require('./lib/JsonUserDatabase'); |
||||
|
||||
const _ = require('lodash'); |
||||
const express = require('express'); |
||||
const bodyParser = require('body-parser'); |
||||
const tus = require('tus-node-server'); |
||||
const jwt = require('jsonwebtoken'); |
||||
const path = require('path'); |
||||
|
||||
const webpack = require('webpack'); |
||||
const webpackDevMiddleware = require('webpack-dev-middleware'); |
||||
const webpackHotMiddleware = require('webpack-hot-middleware'); |
||||
const webpackConfig = require('./webpack.config.js'); |
||||
|
||||
/* |
||||
function metadataToObject(stringValue) |
||||
{ |
||||
const keyValuePairList = stringValue.split(','); |
||||
|
||||
return _.reduce(keyValuePairList , (metadata, keyValuePair) => { |
||||
let [key, base64Value] = keyValuePair.split(' '); |
||||
metadata[key] = new Buffer(base64Value, "base64").toString("ascii"); |
||||
|
||||
return metadata; |
||||
}, {}); |
||||
} |
||||
*/ |
||||
|
||||
|
||||
function checkAuthorization(req, res, onVerified) |
||||
{ |
||||
if (!req.headers.authorization || req.headers.authorization.split(' ')[0] !== 'Bearer') |
||||
{ |
||||
res.sendStatus(400); |
||||
return; |
||||
} |
||||
|
||||
var token = req.headers.authorization.split(' ')[1]; |
||||
jwt.verify(token, config.jwtSecret, (err, decoded) => |
||||
{ |
||||
if (err) |
||||
{ |
||||
res.sendStatus(403); |
||||
return; |
||||
} |
||||
|
||||
onVerified(decoded); |
||||
}); |
||||
} |
||||
|
||||
|
||||
(async function() |
||||
{ |
||||
const isDevelopment = process.env.NODE_ENV !== 'production'; |
||||
|
||||
const userDatabase = new JsonUserDatabase(config.userDatabasePath); |
||||
await userDatabase.load(); |
||||
|
||||
const tusServer = new tus.Server(); |
||||
tusServer.datastore = new tus.FileStore({ |
||||
path: config.fileUploadPublicPath, |
||||
directory: config.fileUploadPath |
||||
}); |
||||
|
||||
/* |
||||
tusServer.on(tus.EVENTS.EVENT_UPLOAD_COMPLETE, (event) => |
||||
{ |
||||
console.log(event); |
||||
|
||||
const metadata = metadataToObject(event.file.upload_metadata); |
||||
jwt.verify(metadata.token, config.jwtSecret, (err, decoded) => |
||||
{ |
||||
if (err) |
||||
return; |
||||
|
||||
const filePath = path.join(config.fileUploadPath, event.file.id); |
||||
console.log(filePath); |
||||
|
||||
// TODO save metadata for file and notify people
|
||||
}); |
||||
}); |
||||
*/ |
||||
|
||||
|
||||
const app = express(); |
||||
|
||||
app.use(bodyParser.urlencoded({ extended: true })); |
||||
app.use(bodyParser.json()); |
||||
|
||||
|
||||
// Token API
|
||||
app.post('/token/upload', async (req, res) => |
||||
{ |
||||
if (!req.body.code) |
||||
{ |
||||
res.sendStatus(400); |
||||
return; |
||||
} |
||||
|
||||
if (await userDatabase.isValidCode(req.body.code)) |
||||
{ |
||||
jwt.sign({ |
||||
code: req.body.code |
||||
}, config.jwtSecret, (err, token) => |
||||
{ |
||||
if (err) |
||||
res.sendStatus(500); |
||||
else |
||||
res.send(token); |
||||
}); |
||||
} |
||||
else |
||||
res.sendStatus(403); |
||||
}); |
||||
|
||||
|
||||
// Upload API
|
||||
app.post('/complete', (req, res) => |
||||
{ |
||||
if (!req.body.files) |
||||
{ |
||||
res.sendStatus(400); |
||||
return; |
||||
} |
||||
|
||||
checkAuthorization(req, res, (decoded) => |
||||
{ |
||||
console.log(req.body.files); |
||||
// TODO save set
|
||||
}); |
||||
}); |
||||
|
||||
|
||||
// Tus upload
|
||||
const uploadApp = express(); |
||||
uploadApp.all('*', (req, res) => |
||||
{ |
||||
checkAuthorization(req, res, (decoded) => |
||||
{ |
||||
tusServer.handle(req, res); |
||||
}); |
||||
}); |
||||
|
||||
app.use('/upload', uploadApp); |
||||
|
||||
|
||||
// Frontend
|
||||
if (isDevelopment) |
||||
{ |
||||
const compiler = webpack(webpackConfig); |
||||
|
||||
app.use(webpackDevMiddleware(compiler, { |
||||
publicPath: webpackConfig.output.publicPath |
||||
})); |
||||
|
||||
app.use(webpackHotMiddleware(compiler)); |
||||
} |
||||
|
||||
app.use(express.static(path.join(__dirname, 'public', 'dist'))); |
||||
|
||||
var server = app.listen(config.port, () => console.log('Recv running on port ' + server.address().port)); |
||||
})(); |
@ -0,0 +1,91 @@
@@ -0,0 +1,91 @@
|
||||
const debug = require('debug')('recv:JsonUserDatabase'); |
||||
const uuidv4 = require('uuid/v4'); |
||||
const fs = require('mz/fs'); |
||||
const mkdir = require('mkdir-promise'); |
||||
const path = require('path'); |
||||
|
||||
|
||||
class JsonUserDatabase |
||||
{ |
||||
constructor(path) |
||||
{ |
||||
this.path = path; |
||||
|
||||
this.codes = {}; |
||||
this.users = {}; |
||||
} |
||||
|
||||
|
||||
async load() |
||||
{ |
||||
debug('loading database from ' + this.path); |
||||
|
||||
try |
||||
{ |
||||
var files = await fs.readdir(this.path); |
||||
} |
||||
catch(err) |
||||
{ |
||||
if (err.code == 'ENOENT') |
||||
// Path does not exist, no worries
|
||||
files = null; |
||||
else |
||||
throw err; |
||||
} |
||||
|
||||
if (!files) |
||||
return; |
||||
|
||||
for (var i = 0; i < files.length; i++) |
||||
{ |
||||
var fullPath = path.join(this.path, files[i]); |
||||
var stats = await fs.lstat(fullPath); |
||||
|
||||
if (stats.isFile()) |
||||
{ |
||||
debug('loading ' + fullPath); |
||||
try |
||||
{ |
||||
var userInfo = JSON.parse(await fs.readFile(fullPath)); |
||||
if (userInfo.type !== 'user') |
||||
throw new Error('unsupported file type: ' + userInfo.type); |
||||
|
||||
this.users[userInfo.uid] = userInfo; |
||||
} |
||||
catch (err) |
||||
{ |
||||
console.error('error while loading file ' + fullPath + ', skipped:'); |
||||
console.error(err); |
||||
} |
||||
} |
||||
} |
||||
|
||||
debug(Object.keys(this.users).length + ' user(s) loaded'); |
||||
} |
||||
|
||||
|
||||
addUser(info) |
||||
{ |
||||
var userId = uuidv4(); |
||||
|
||||
// TODO add user
|
||||
// TODO save file
|
||||
|
||||
return userId; |
||||
} |
||||
|
||||
|
||||
isValidCode(code) |
||||
{ |
||||
debug('validating code: ' + code); |
||||
|
||||
// TODO check code
|
||||
var valid = true; |
||||
|
||||
debug('valid = ' + valid); |
||||
return valid; |
||||
} |
||||
} |
||||
|
||||
|
||||
module.exports = JsonUserDatabase; |
@ -0,0 +1,52 @@
@@ -0,0 +1,52 @@
|
||||
{ |
||||
"name": "recv", |
||||
"version": "1.0.0", |
||||
"description": "Recv - self-hosted web file transfer", |
||||
"main": "index.js", |
||||
"scripts": { |
||||
"dev": "node index.js", |
||||
"build": "webpack --mode production", |
||||
"test": "echo \"Error: no test specified\" && exit 1" |
||||
}, |
||||
"repository": { |
||||
"type": "git", |
||||
"url": "https://www.github.com/MvRens/Recv" |
||||
}, |
||||
"author": "Mark van Renswoude <mark@x2software.net>", |
||||
"license": "Unlicense", |
||||
"dependencies": { |
||||
"body-parser": "^1.18.2", |
||||
"debug": "^3.1.0", |
||||
"express": "^4.16.2", |
||||
"jsonwebtoken": "^8.1.1", |
||||
"mkdir-promise": "^1.0.0", |
||||
"mz": "^2.7.0", |
||||
"npm": "^5.7.1", |
||||
"tus-node-server": "^0.2.10", |
||||
"uuid": "^3.2.1", |
||||
"lodash": "^4.17.5" |
||||
}, |
||||
"devDependencies": { |
||||
"axios": "^0.18.0", |
||||
"babel-loader": "^7.1.4", |
||||
"babel-plugin-syntax-dynamic-import": "^6.18.0", |
||||
"css-loader": "^0.28.10", |
||||
"html-webpack-plugin": "^3.0.6", |
||||
"node-sass": "^4.7.2", |
||||
"purecss": "^1.0.0", |
||||
"sass-loader": "^6.0.7", |
||||
"style-loader": "^0.20.2", |
||||
"tus-js-client": "^1.4.5", |
||||
"uppy": "^0.23.2", |
||||
"vue": "^2.5.13", |
||||
"vue-i18n": "^7.4.2", |
||||
"vue-loader": "^14.2.1", |
||||
"vue-router": "^3.0.1", |
||||
"vue-style-loader": "^4.0.2", |
||||
"vue-template-compiler": "^2.5.13", |
||||
"webpack": "^4.1.0", |
||||
"webpack-cli": "^2.0.10", |
||||
"webpack-dev-middleware": "^3.0.1", |
||||
"webpack-hot-middleware": "^2.21.2" |
||||
} |
||||
} |
@ -0,0 +1,183 @@
@@ -0,0 +1,183 @@
|
||||
<template> |
||||
<div id="app"> |
||||
<div v-cloak> |
||||
<div class="notificationContainer"> |
||||
<div class="notification" :class="{ error: notification != null && notification.error }" v-if="notification != null" @click.prevent="hideNotification"> |
||||
<span class="message">{{ notification.message }}</span> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="content"> |
||||
<div class="logo"> |
||||
<img src="/images/logo.png"> |
||||
</div> |
||||
|
||||
<router-view></router-view> |
||||
</div> |
||||
|
||||
<div class="disclaimer"> |
||||
{{ $t('disclaimer') }} |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import _ from 'lodash'; |
||||
import shared from './shared'; |
||||
|
||||
|
||||
if (typeof customMessages !== 'undefined') |
||||
{ |
||||
_.merge(messages, customMessages); |
||||
|
||||
if (customMessages.hasOwnProperty('allLocales')) |
||||
{ |
||||
for (var key in messages) |
||||
{ |
||||
if (key !== 'allLocales' && messages.hasOwnProperty(key)) |
||||
_.merge(messages[key], customMessages.allLocales); |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
export default { |
||||
name: 'app', |
||||
data () { |
||||
return { |
||||
notification: null |
||||
} |
||||
}, |
||||
|
||||
created() |
||||
{ |
||||
var self = this; |
||||
document.title = self.$i18n.t('title'); |
||||
|
||||
self.notificationTimer = null; |
||||
|
||||
shared.$on('showNotification', (message, error) => |
||||
{ |
||||
var self = this; |
||||
self.notification = { |
||||
message: message, |
||||
error: error |
||||
}; |
||||
|
||||
if (self.notificationTimer != null) |
||||
clearTimeout(self.notificationTimer); |
||||
|
||||
self.notificationTimer = setTimeout(() => |
||||
{ |
||||
self.notification = null; |
||||
self.notificationTimer = null; |
||||
}, 5000); |
||||
}); |
||||
|
||||
shared.$on('hideNotification', () => |
||||
{ |
||||
var self = this; |
||||
self.notification = null; |
||||
|
||||
if (self.notificationTimer != null) |
||||
{ |
||||
clearTimeout(self.notificationTimer); |
||||
self.notificationTimer = null; |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss"> |
||||
@import '../../node_modules/purecss/build/base.css'; |
||||
@import '../../node_modules/purecss/build/buttons.css'; |
||||
@import '../../node_modules/purecss/build/forms.css'; |
||||
@import '../../node_modules/purecss/build/grids.css'; |
||||
|
||||
/* open-sans-regular - latin */ |
||||
@font-face { |
||||
font-family: 'Open Sans'; |
||||
font-style: normal; |
||||
font-weight: 400; |
||||
src: url('/fonts/open-sans-v15-latin-regular.eot'); /* IE9 Compat Modes */ |
||||
src: local('Open Sans Regular'), local('OpenSans-Regular'), |
||||
url('/fonts/open-sans-v15-latin-regular.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ |
||||
url('/fonts/open-sans-v15-latin-regular.woff2') format('woff2'), /* Super Modern Browsers */ |
||||
url('/fonts/open-sans-v15-latin-regular.woff') format('woff'), /* Modern Browsers */ |
||||
url('/fonts/open-sans-v15-latin-regular.ttf') format('truetype'), /* Safari, Android, iOS */ |
||||
url('/fonts/open-sans-v15-latin-regular.svg#OpenSans') format('svg'); /* Legacy iOS */ |
||||
} |
||||
|
||||
|
||||
body |
||||
{ |
||||
background-color: #f0f0f0; |
||||
font-family: 'Open Sans', sans-serif; |
||||
font-size: 12pt; |
||||
} |
||||
|
||||
#app |
||||
{ |
||||
margin-left: auto; |
||||
margin-right: auto; |
||||
max-width: 768px; |
||||
} |
||||
|
||||
.content |
||||
{ |
||||
background-color: white; |
||||
margin: 2rem; |
||||
padding: 2rem; |
||||
box-shadow: 0 0 15px #c0c0c0; |
||||
} |
||||
|
||||
|
||||
.notificationContainer |
||||
{ |
||||
position: fixed; |
||||
top: 1.5rem; |
||||
z-index: 666; |
||||
width: 512px; |
||||
left: 50%; |
||||
} |
||||
|
||||
|
||||
.notification |
||||
{ |
||||
background: #d5e8f6; |
||||
border: solid 1px rgba(0, 0, 0, 0.25); |
||||
box-shadow: 0 0 10px #c0c0c0; |
||||
color: black; |
||||
cursor: pointer; |
||||
padding: .5em; |
||||
margin-bottom: 2rem; |
||||
|
||||
position: relative; |
||||
left: -50%; |
||||
} |
||||
|
||||
.notification .message |
||||
{ |
||||
white-space: pre; |
||||
} |
||||
|
||||
.notification.error |
||||
{ |
||||
background: #973a38; |
||||
} |
||||
|
||||
|
||||
.logo |
||||
{ |
||||
margin-bottom: 2rem; |
||||
text-align: center; |
||||
} |
||||
|
||||
.disclaimer |
||||
{ |
||||
font-size: 8pt; |
||||
text-align: center; |
||||
} |
||||
</style> |
@ -0,0 +1,33 @@
@@ -0,0 +1,33 @@
|
||||
import Vue from 'vue'; |
||||
import VueI18n from 'vue-i18n'; |
||||
import VueRouter from 'vue-router'; |
||||
import App from './App.vue'; |
||||
import messages from './lang'; |
||||
|
||||
Vue.use(VueI18n); |
||||
Vue.use(VueRouter); |
||||
|
||||
const i18n = new VueI18n({ |
||||
locale: navigator.language.split('-')[0], |
||||
fallbackLocale: 'en', |
||||
messages: messages |
||||
}); |
||||
|
||||
|
||||
const Landing = () => import('./route/Landing.vue'); |
||||
const Upload = () => import('./route/Upload.vue'); |
||||
|
||||
const router = new VueRouter({ |
||||
routes: [ |
||||
{ path: '/', component: Landing }, |
||||
{ path: '/c/:codeParam', component: Landing, props: true }, |
||||
{ path: '/u/:codeParam', component: Upload, props: true } |
||||
] |
||||
}); |
||||
|
||||
new Vue({ |
||||
el: '#app', |
||||
i18n, |
||||
router, |
||||
render: h => h(App) |
||||
}); |
@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
|
||||
<!DOCTYPE html> |
||||
<html> |
||||
<head> |
||||
<meta charset="UTF-8"> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1"> |
||||
<title>Recv</title> |
||||
</head> |
||||
<body> |
||||
<div id="app"></div> |
||||
</body> |
||||
</html> |
@ -0,0 +1,47 @@
@@ -0,0 +1,47 @@
|
||||
export default { |
||||
en: { |
||||
title: 'File upload - Recv', |
||||
disclaimer: '', |
||||
|
||||
landing: { |
||||
invitePlaceholder: 'Code', |
||||
inviteButton: 'Check code', |
||||
inviteButtonChecking: 'Checking...', |
||||
|
||||
loginButton: 'Log in' |
||||
}, |
||||
|
||||
notification: { |
||||
invalidCode: 'The specified code is invalid or has expired' |
||||
}, |
||||
|
||||
uppyDashboard: { |
||||
done: 'Done', |
||||
dropPaste: 'Drop files here, paste or', |
||||
browse: 'browse' |
||||
} |
||||
}, |
||||
|
||||
nl: { |
||||
title: 'Bestandsoverdracht - Recv', |
||||
disclaimer: '', |
||||
|
||||
landing: { |
||||
invitePlaceholder: 'Code', |
||||
inviteButton: 'Controleer code', |
||||
inviteButtonChecking: 'Controleren...', |
||||
|
||||
loginButton: 'Inloggen' |
||||
}, |
||||
|
||||
notification: { |
||||
invalidCode: 'De ingevoerde code is ongeldig of verlopen' |
||||
}, |
||||
|
||||
uppyDashboard: { |
||||
done: 'Gereed', |
||||
dropPaste: 'Sleep bestanden, plak of ', |
||||
browse: 'selecteer' |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,94 @@
@@ -0,0 +1,94 @@
|
||||
<template> |
||||
<div id="landing"> |
||||
<form class="pure-form pure-form-stacked" @submit.prevent="checkCode"> |
||||
<fieldset class="pure-group"> |
||||
<input type="text" class="pure-input-1-2" v-model="code.value" :placeholder="$t('landing.invitePlaceholder')"> |
||||
</fieldset> |
||||
|
||||
<button type="submit" class="pure-button pure-button-primary" :disabled="code.value.trim() == '' || code.checking">{{ $t(code.checking ? 'landing.inviteButtonChecking' : 'landing.inviteButton') }} <span v-if="code.checking"><i class="fas fa-spinner fa-pulse"></i></span></button> |
||||
</form> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import axios from 'axios'; |
||||
import shared from '../shared'; |
||||
|
||||
export default { |
||||
name: 'app', |
||||
|
||||
props: [ |
||||
'codeParam' |
||||
], |
||||
|
||||
data () { |
||||
return { |
||||
code: { |
||||
value: '', |
||||
checking: false |
||||
} |
||||
} |
||||
}, |
||||
|
||||
created() |
||||
{ |
||||
var self = this; |
||||
if (self.codeParam) |
||||
{ |
||||
self.code.value = self.codeParam; |
||||
self.checkCode(); |
||||
} |
||||
}, |
||||
|
||||
methods: { |
||||
checkCode() |
||||
{ |
||||
var self = this; |
||||
if (self.code.checking) |
||||
return; |
||||
|
||||
self.code.checking = true; |
||||
|
||||
axios.post('/token/upload', { |
||||
code: self.code |
||||
}) |
||||
.then((response) => |
||||
{ |
||||
shared.token = response.data; |
||||
self.$router.push({ path: '/u/' + self.code.value }); |
||||
}) |
||||
.catch((error) => |
||||
{ |
||||
if (error.response && error.response.status == 403) |
||||
shared.$emit('showNotification', self.$i18n.t('notification.invalidCode'), false); |
||||
else |
||||
shared.$emit('showNotification', error.message); |
||||
}) |
||||
.then(() => |
||||
{ |
||||
self.code.checking = false; |
||||
}); |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss"> |
||||
@import '../../../node_modules/uppy/src/scss/uppy.scss'; |
||||
|
||||
|
||||
#landing |
||||
{ |
||||
text-align: center; |
||||
|
||||
input |
||||
{ |
||||
display: inline-block; |
||||
} |
||||
|
||||
.login |
||||
{ |
||||
margin-top: 5em; |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,107 @@
@@ -0,0 +1,107 @@
|
||||
<template> |
||||
<div id="upload"> |
||||
<router-link to="/">Terug</router-link> |
||||
<div class="uploadTarget"></div> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import _ from 'lodash'; |
||||
import shared from '../shared'; |
||||
import Uppy from 'uppy/lib/core'; |
||||
import Dashboard from 'uppy/lib/plugins/Dashboard'; |
||||
import Tus from 'uppy/lib/plugins/Tus'; |
||||
import axios from 'axios'; |
||||
|
||||
export default { |
||||
props: [ |
||||
'codeParam', |
||||
'i18n' |
||||
], |
||||
|
||||
data () { |
||||
return { |
||||
token: shared.token |
||||
} |
||||
}, |
||||
|
||||
created() |
||||
{ |
||||
var self = this; |
||||
|
||||
if (!self.token) |
||||
{ |
||||
if (self.codeParam) |
||||
self.$router.push('/c/' + self.codeParam); |
||||
else |
||||
self.$router.push('/c/'); |
||||
|
||||
return; |
||||
} |
||||
}, |
||||
|
||||
mounted() |
||||
{ |
||||
var self = this; |
||||
|
||||
self.uppy = Uppy({ |
||||
id: 'userUpload', |
||||
autoProceed: false, |
||||
debug: true |
||||
}) |
||||
.use(Dashboard, { |
||||
inline: true, |
||||
target: '.uploadTarget', |
||||
replaceTargetContent: true, |
||||
locale: { |
||||
strings: { |
||||
done: self.$i18n.t('uppyDashboard.done'), |
||||
dropPaste: self.$i18n.t('uppyDashboard.dropPaste'), |
||||
browse: self.$i18n.t('uppyDashboard.browse') |
||||
} |
||||
} |
||||
}) |
||||
.use(Tus, { |
||||
endpoint: '/upload/', |
||||
headers: { |
||||
Authorization: 'Bearer ' + self.token |
||||
} |
||||
}) |
||||
.run(); |
||||
|
||||
self.uppy.on('complete', (result) => |
||||
{ |
||||
axios.post('/complete', { |
||||
files: _.map(result.successful, (file) => |
||||
{ |
||||
return { |
||||
id: file.tus.uploadUrl.substr(file.tus.uploadUrl.lastIndexOf('/') + 1), |
||||
name: file.name |
||||
}; |
||||
}) |
||||
}, |
||||
{ |
||||
headers: { |
||||
Authorization: 'Bearer ' + self.token |
||||
} |
||||
}) |
||||
.then((response) => |
||||
{ |
||||
// TODO anything? |
||||
}) |
||||
.catch((error) => |
||||
{ |
||||
// TODO can we convince Uppy that the files actually failed? |
||||
}); |
||||
}); |
||||
|
||||
self.uppy.on('upload-success', (file, resp, uploadURL) => { |
||||
// Clear the uploadURL so the dashboard will not display it as a link. |
||||
// The file can't be viewed by the user anyways since JWT headers are required. |
||||
self.uppy.setFileState(file.id, { |
||||
uploadURL: false |
||||
}) |
||||
}); |
||||
} |
||||
} |
||||
</script> |
@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
|
||||
import Vue from 'vue'; |
||||
|
||||
export default new Vue({ |
||||
data () { |
||||
return { |
||||
token: null |
||||
} |
||||
}, |
||||
}); |
@ -0,0 +1,117 @@
@@ -0,0 +1,117 @@
|
||||
const path = require('path'); |
||||
const webpack = require('webpack'); |
||||
const HtmlWebpackPlugin = require('html-webpack-plugin'); |
||||
|
||||
module.exports = { |
||||
mode: 'development', |
||||
entry: './public/src/app.js', |
||||
|
||||
output: { |
||||
path: path.resolve(__dirname, './public/dist'), |
||||
publicPath: '/', |
||||
filename: 'build.js' |
||||
}, |
||||
|
||||
module: { |
||||
rules: [ |
||||
{ |
||||
test: /\.css$/, |
||||
use: [ |
||||
'vue-style-loader', |
||||
'css-loader' |
||||
], |
||||
}, |
||||
{ |
||||
test: /\.scss$/, |
||||
use: [ |
||||
'vue-style-loader', |
||||
'css-loader', |
||||
'sass-loader' |
||||
], |
||||
}, |
||||
{ |
||||
test: /\.vue$/, |
||||
loader: 'vue-loader', |
||||
options: { |
||||
loaders: { |
||||
'scss': [ |
||||
'vue-style-loader', |
||||
'css-loader', |
||||
'sass-loader' |
||||
] |
||||
} |
||||
} |
||||
}, |
||||
{ |
||||
test: /\.js$/, |
||||
loader: 'babel-loader', |
||||
exclude: /node_modules/ |
||||
}, |
||||
{ |
||||
test: /\.(png|jpg|gif|svg)$/, |
||||
loader: 'file-loader', |
||||
options: { |
||||
name: '[name].[ext]?[hash]' |
||||
} |
||||
} |
||||
] |
||||
}, |
||||
|
||||
plugins: [ |
||||
new HtmlWebpackPlugin({ |
||||
template: path.resolve(__dirname, './public/src/index.html') |
||||
}) |
||||
], |
||||
|
||||
resolve: { |
||||
alias: { |
||||
'vue$': 'vue/dist/vue.esm.js' |
||||
}, |
||||
extensions: ['*', '.js', '.vue', '.json'] |
||||
}, |
||||
|
||||
devServer: { |
||||
historyApiFallback: true, |
||||
noInfo: true, |
||||
overlay: true |
||||
}, |
||||
|
||||
performance: { |
||||
hints: false |
||||
}, |
||||
|
||||
devtool: '#eval-source-map' |
||||
} |
||||
|
||||
if (process.env.NODE_ENV === 'production') |
||||
{ |
||||
module.exports.devtool = '#source-map' |
||||
module.exports.plugins = (module.exports.plugins || []).concat([ |
||||
new webpack.DefinePlugin({ |
||||
'process.env': { |
||||
NODE_ENV: '"production"' |
||||
} |
||||
}), |
||||
new webpack.optimize.UglifyJsPlugin({ |
||||
sourceMap: true, |
||||
compress: { |
||||
warnings: false |
||||
} |
||||
}), |
||||
new webpack.LoaderOptionsPlugin({ |
||||
minimize: true |
||||
}) |
||||
]); |
||||
} |
||||
else |
||||
{ |
||||
module.exports.entry = [ |
||||
module.exports.entry, |
||||
'webpack-hot-middleware/client' |
||||
]; |
||||
|
||||
module.exports.plugins = (module.exports.plugins || []).concat([ |
||||
new webpack.HotModuleReplacementPlugin(), |
||||
new webpack.NoEmitOnErrorsPlugin() |
||||
]); |
||||
} |
Loading…
Reference in new issue