feat: image upload + list root assets (wip)
This commit is contained in:
parent
89c07a716a
commit
6b886b6e3f
@ -304,11 +304,7 @@ export default {
|
|||||||
this.deletePageModal = true
|
this.deletePageModal = true
|
||||||
},
|
},
|
||||||
assets () {
|
assets () {
|
||||||
this.$store.commit('showNotification', {
|
window.location.assign(`/f`)
|
||||||
style: 'indigo',
|
|
||||||
message: `Coming soon...`,
|
|
||||||
icon: 'directions_boat'
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
logout () {
|
logout () {
|
||||||
Cookies.remove('jwt')
|
Cookies.remove('jwt')
|
||||||
|
@ -11,46 +11,82 @@
|
|||||||
v-btn.ml-3.my-0.radius-7(outline, large, color='teal', disabled)
|
v-btn.ml-3.my-0.radius-7(outline, large, color='teal', disabled)
|
||||||
v-icon(left) keyboard_arrow_up
|
v-icon(left) keyboard_arrow_up
|
||||||
span Parent Folder
|
span Parent Folder
|
||||||
v-btn.my-0.radius-7(outline, large, color='teal')
|
v-btn.my-0.mr-0.radius-7(outline, large, color='teal')
|
||||||
v-icon(left) add
|
v-icon(left) add
|
||||||
span New Folder
|
span New Folder
|
||||||
v-list.mt-3(dense, two-line)
|
v-list.mt-3(dense, two-line)
|
||||||
template(v-for='(item, idx) of [1,2,3,4,5,6,7,8,9,10]')
|
template(v-for='(asset, idx) of assets')
|
||||||
v-list-tile(@click='')
|
v-list-tile(@click='')
|
||||||
v-list-tile-avatar
|
v-list-tile-avatar
|
||||||
v-avatar.radius-7(color='teal')
|
v-avatar.radius-7(color='teal')
|
||||||
v-icon(dark) image
|
v-icon(dark) image
|
||||||
v-list-tile-content
|
v-list-tile-content
|
||||||
v-list-tile-title Image {{item}}
|
v-list-tile-title {{asset.filename}}
|
||||||
v-list-tile-sub-title 1024x768, 10 KBs
|
v-list-tile-sub-title 1024x768
|
||||||
v-list-tile-action
|
v-list-tile-action
|
||||||
.caption.pr-3 2019-04-07
|
.caption {{asset.updatedAt | moment('from')}}
|
||||||
|
v-divider.mx-3(vertical)
|
||||||
|
v-list-tile-action(style='flex-basis: 80px;')
|
||||||
|
.caption {{asset.fileSize | prettyBytes}}
|
||||||
|
v-divider.mx-3(vertical)
|
||||||
|
v-list-tile-action(style='flex-basis: 60px;')
|
||||||
|
v-chip.teal--text(label, small, color='teal lighten-5') {{asset.ext.toUpperCase().substring(1)}}
|
||||||
v-list-tile-action
|
v-list-tile-action
|
||||||
v-chip.teal--text(label, small, color='teal lighten-5') JPG
|
v-menu(offset-x)
|
||||||
v-divider(v-if='idx < 10 - 1')
|
v-btn(icon, slot='activator')
|
||||||
|
v-icon(color='grey darken-2') more_horiz
|
||||||
|
v-list.py-0
|
||||||
|
v-list-tile
|
||||||
|
v-list-tile-avatar
|
||||||
|
v-icon(color='teal') short_text
|
||||||
|
v-list-tile-content Properties
|
||||||
|
v-divider
|
||||||
|
v-list-tile
|
||||||
|
v-list-tile-avatar
|
||||||
|
v-icon(color='indigo') crop_rotate
|
||||||
|
v-list-tile-content Edit
|
||||||
|
v-divider
|
||||||
|
v-list-tile
|
||||||
|
v-list-tile-avatar
|
||||||
|
v-icon(color='blue') keyboard
|
||||||
|
v-list-tile-content Rename / Move
|
||||||
|
v-divider
|
||||||
|
v-list-tile
|
||||||
|
v-list-tile-avatar
|
||||||
|
v-icon(color='red') delete
|
||||||
|
v-list-tile-content Delete
|
||||||
|
v-divider(v-if='idx < assets.length - 1')
|
||||||
.d-flex.mt-3
|
.d-flex.mt-3
|
||||||
v-toolbar.radius-7(flat, color='grey lighten-4', dense, height='44')
|
v-toolbar.radius-7(flat, color='grey lighten-4', dense, height='44')
|
||||||
.body-2 / universe
|
.body-2 / #[em root]
|
||||||
v-spacer
|
v-spacer
|
||||||
.body-1.grey--text.text--darken-1 10 files
|
.body-1.grey--text.text--darken-1 10 files
|
||||||
v-btn.ml-3.my-0.radius-7(color='teal', large, @click='insert', disabled)
|
v-btn.ml-3.mr-0.my-0.radius-7(color='teal', large, @click='insert', disabled)
|
||||||
v-icon(left) save_alt
|
v-icon(left) save_alt
|
||||||
span Insert
|
span Insert
|
||||||
|
|
||||||
v-flex(xs3)
|
v-flex(xs3)
|
||||||
v-card.radius-7.animated.fadeInRight.wait-p3s(light)
|
v-card.radius-7.animated.fadeInRight.wait-p3s(light)
|
||||||
v-card-text
|
v-card-text
|
||||||
v-toolbar.radius-7(color='teal lighten-5', dense, flat)
|
.d-flex
|
||||||
v-icon.mr-3(color='teal') cloud_upload
|
v-toolbar.radius-7(color='teal lighten-5', dense, flat, height='44')
|
||||||
.body-2.teal--text Upload Images
|
v-icon.mr-3(color='teal') cloud_upload
|
||||||
|
.body-2.teal--text Upload Images
|
||||||
|
v-btn.my-0.ml-3.mr-0.radius-7(outline, large, color='teal', @click='browse')
|
||||||
|
v-icon(left) touch_app
|
||||||
|
span Browse
|
||||||
file-pond.mt-3(
|
file-pond.mt-3(
|
||||||
name='mediaUpload'
|
name='mediaUpload'
|
||||||
ref='pond'
|
ref='pond'
|
||||||
label-idle='Browse or Drop files here...'
|
label-idle='Browse or Drop files here...'
|
||||||
allow-multiple='true'
|
allow-multiple='true'
|
||||||
accepted-file-types='image/jpeg, image/png, image/gif, image/svg'
|
:accepted-file-types='[`image/jpeg`, `image/png`, `image/gif`, `image/svg`]'
|
||||||
:files='files'
|
:files='files'
|
||||||
max-files='10'
|
max-files='10'
|
||||||
|
server='/u'
|
||||||
|
:instant-upload='false'
|
||||||
|
:allow-revert='false'
|
||||||
|
@processfile='onFileProcessed'
|
||||||
)
|
)
|
||||||
v-divider
|
v-divider
|
||||||
v-card-actions.pa-3
|
v-card-actions.pa-3
|
||||||
@ -92,14 +128,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import { sync } from 'vuex-pathify'
|
import { sync } from 'vuex-pathify'
|
||||||
import vueFilePond from 'vue-filepond'
|
import vueFilePond from 'vue-filepond'
|
||||||
import 'filepond/dist/filepond.min.css'
|
import 'filepond/dist/filepond.min.css'
|
||||||
|
|
||||||
import FilePondPluginFileValidateType from 'filepond-plugin-file-validate-type'
|
import FilePondPluginFileValidateType from 'filepond-plugin-file-validate-type'
|
||||||
|
|
||||||
import uploadFileMutation from 'gql/editor/upload.gql'
|
import listAssetQuery from 'gql/editor/editor-media-query-list.gql'
|
||||||
|
|
||||||
const FilePond = vueFilePond(FilePondPluginFileValidateType)
|
const FilePond = vueFilePond(FilePondPluginFileValidateType)
|
||||||
|
|
||||||
@ -133,23 +169,81 @@ export default {
|
|||||||
},
|
},
|
||||||
activeModal: sync('editor/activeModal')
|
activeModal: sync('editor/activeModal')
|
||||||
},
|
},
|
||||||
|
filters: {
|
||||||
|
prettyBytes(num) {
|
||||||
|
if (typeof num !== 'number' || isNaN(num)) {
|
||||||
|
throw new TypeError('Expected a number')
|
||||||
|
}
|
||||||
|
|
||||||
|
var exponent
|
||||||
|
var unit
|
||||||
|
var neg = num < 0
|
||||||
|
var units = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
||||||
|
|
||||||
|
if (neg) {
|
||||||
|
num = -num
|
||||||
|
}
|
||||||
|
|
||||||
|
if (num < 1) {
|
||||||
|
return (neg ? '-' : '') + num + ' B'
|
||||||
|
}
|
||||||
|
|
||||||
|
exponent = Math.min(Math.floor(Math.log(num) / Math.log(1000)), units.length - 1)
|
||||||
|
num = (num / Math.pow(1000, exponent)).toFixed(2) * 1
|
||||||
|
unit = units[exponent]
|
||||||
|
|
||||||
|
return (neg ? '-' : '') + num + ' ' + unit
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
insert () {
|
insert () {
|
||||||
this.activeModal = ''
|
this.activeModal = ''
|
||||||
},
|
},
|
||||||
|
browse () {
|
||||||
|
this.$refs.pond.browse()
|
||||||
|
},
|
||||||
async upload () {
|
async upload () {
|
||||||
const files = this.$refs.pond.getFiles()
|
const files = this.$refs.pond.getFiles()
|
||||||
for (let fl of files) {
|
if (files.length < 1) {
|
||||||
const resp = await this.$apollo.mutate({
|
return this.$store.commit('showNotification', {
|
||||||
mutation: uploadFileMutation,
|
message: 'You must choose a file to upload first!',
|
||||||
variables: {
|
style: 'warning',
|
||||||
data: fl.file
|
icon: 'warning'
|
||||||
},
|
|
||||||
context: {
|
|
||||||
hasUpload: true
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
console.info(resp)
|
}
|
||||||
|
for (let file of files) {
|
||||||
|
file.setMetadata({
|
||||||
|
path: '/universe'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
await this.$refs.pond.processFiles()
|
||||||
|
},
|
||||||
|
async onFileProcessed (err, file) {
|
||||||
|
if (err) {
|
||||||
|
return this.$store.commit('showNotification', {
|
||||||
|
message: 'File upload failed.',
|
||||||
|
style: 'error',
|
||||||
|
icon: 'error'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_.delay(() => {
|
||||||
|
this.$refs.pond.removeFile(file.id)
|
||||||
|
}, 5000)
|
||||||
|
|
||||||
|
await this.$apollo.queries.assets.refetch()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
apollo: {
|
||||||
|
assets: {
|
||||||
|
query: listAssetQuery,
|
||||||
|
variables: {
|
||||||
|
kind: 'IMAGE'
|
||||||
|
},
|
||||||
|
throttle: 1000,
|
||||||
|
fetchPolicy: 'network-only',
|
||||||
|
update: (data) => data.assets.list,
|
||||||
|
watchLoading (isLoading) {
|
||||||
|
this.$store.commit(`loading${isLoading ? 'Start' : 'Stop'}`, 'editor-media-list-refresh')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -158,12 +252,25 @@ export default {
|
|||||||
|
|
||||||
<style lang='scss'>
|
<style lang='scss'>
|
||||||
.editor-modal-media {
|
.editor-modal-media {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 112px;
|
top: 112px;
|
||||||
left: 64px;
|
left: 64px;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
width: calc(100vw - 64px - 17px);
|
width: calc(100vw - 64px - 17px);
|
||||||
height: calc(100vh - 112px - 24px);
|
height: calc(100vh - 112px - 24px);
|
||||||
background-color: rgba(darken(mc('grey', '900'), 3%), .9) !important;
|
background-color: rgba(darken(mc('grey', '900'), 3%), .9) !important;
|
||||||
|
overflow: auto;
|
||||||
|
|
||||||
|
.filepond--root {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filepond--drop-label {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
> label {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
14
client/graph/editor/editor-media-query-list.gql
Normal file
14
client/graph/editor/editor-media-query-list.gql
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
query ($root: String, $kind: AssetKind!) {
|
||||||
|
assets {
|
||||||
|
list(root:$root, kind: $kind) {
|
||||||
|
id
|
||||||
|
filename
|
||||||
|
ext
|
||||||
|
kind
|
||||||
|
mime
|
||||||
|
fileSize
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +0,0 @@
|
|||||||
mutation ($file: Upload!) {
|
|
||||||
assets {
|
|
||||||
upload(data:$file) {
|
|
||||||
responseResult {
|
|
||||||
succeeded
|
|
||||||
message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -70,6 +70,15 @@ ssl:
|
|||||||
# Set to false to disable (default: 80):
|
# Set to false to disable (default: 80):
|
||||||
redirectNonSSLPort: 80
|
redirectNonSSLPort: 80
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------
|
||||||
|
# Database Pool Options
|
||||||
|
# ---------------------------------------------------------------------
|
||||||
|
# Refer to https://github.com/vincit/tarn.js for all possible options
|
||||||
|
|
||||||
|
pool:
|
||||||
|
# min: 2
|
||||||
|
# max: 10
|
||||||
|
|
||||||
# ---------------------------------------------------------------------
|
# ---------------------------------------------------------------------
|
||||||
# IP address the server should listen to
|
# IP address the server should listen to
|
||||||
# ---------------------------------------------------------------------
|
# ---------------------------------------------------------------------
|
||||||
@ -83,3 +92,15 @@ bindIP: 0.0.0.0
|
|||||||
# Possible values: error, warn, info (default), verbose, debug, silly
|
# Possible values: error, warn, info (default), verbose, debug, silly
|
||||||
|
|
||||||
logLevel: info
|
logLevel: info
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------
|
||||||
|
# Upload Limits
|
||||||
|
# ---------------------------------------------------------------------
|
||||||
|
# If you're using a reverse-proxy in front of Wiki.ks, you must also
|
||||||
|
# change your proxy upload limits!
|
||||||
|
|
||||||
|
uploads:
|
||||||
|
# Maximum upload size in bytes per file (default: 5242880 (5 MB))
|
||||||
|
maxFileSize: 5242880
|
||||||
|
# Maximum file uploads per request (default: 20)
|
||||||
|
maxFiles: 10
|
||||||
|
@ -79,7 +79,6 @@
|
|||||||
"graphql-rate-limit-directive": "1.0.1",
|
"graphql-rate-limit-directive": "1.0.1",
|
||||||
"graphql-subscriptions": "1.1.0",
|
"graphql-subscriptions": "1.1.0",
|
||||||
"graphql-tools": "4.0.4",
|
"graphql-tools": "4.0.4",
|
||||||
"graphql-upload": "8.0.6",
|
|
||||||
"highlight.js": "9.15.6",
|
"highlight.js": "9.15.6",
|
||||||
"i18next": "15.1.0",
|
"i18next": "15.1.0",
|
||||||
"i18next-express-middleware": "1.8.0",
|
"i18next-express-middleware": "1.8.0",
|
||||||
@ -152,6 +151,7 @@
|
|||||||
"request": "2.88.0",
|
"request": "2.88.0",
|
||||||
"request-promise": "4.2.4",
|
"request-promise": "4.2.4",
|
||||||
"safe-regex": "2.0.2",
|
"safe-regex": "2.0.2",
|
||||||
|
"sanitize-filename": "1.6.1",
|
||||||
"scim-query-filter-parser": "1.1.0",
|
"scim-query-filter-parser": "1.1.0",
|
||||||
"semver": "6.0.0",
|
"semver": "6.0.0",
|
||||||
"serve-favicon": "2.5.0",
|
"serve-favicon": "2.5.0",
|
||||||
|
@ -17,8 +17,12 @@ defaults:
|
|||||||
storage: ./db.sqlite
|
storage: ./db.sqlite
|
||||||
ssl:
|
ssl:
|
||||||
enabled: false
|
enabled: false
|
||||||
|
pool: {}
|
||||||
bindIP: 0.0.0.0
|
bindIP: 0.0.0.0
|
||||||
logLevel: info
|
logLevel: info
|
||||||
|
uploads:
|
||||||
|
maxFileSize: 5242880
|
||||||
|
maxFiles: 10
|
||||||
# DB defaults
|
# DB defaults
|
||||||
graphEndpoint: 'https://graph.requarks.io'
|
graphEndpoint: 'https://graph.requarks.io'
|
||||||
lang:
|
lang:
|
||||||
|
79
server/controllers/upload.js
Normal file
79
server/controllers/upload.js
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
const express = require('express')
|
||||||
|
const router = express.Router()
|
||||||
|
const _ = require('lodash')
|
||||||
|
const multer = require('multer')
|
||||||
|
const path = require('path')
|
||||||
|
const sanitize = require('sanitize-filename')
|
||||||
|
|
||||||
|
/* global WIKI */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload files
|
||||||
|
*/
|
||||||
|
router.post('/u', multer({
|
||||||
|
dest: path.join(WIKI.ROOTPATH, 'data/uploads'),
|
||||||
|
limits: {
|
||||||
|
fileSize: WIKI.config.uploads.maxFileSize,
|
||||||
|
files: WIKI.config.uploads.maxFiles
|
||||||
|
}
|
||||||
|
}).array('mediaUpload'), async (req, res, next) => {
|
||||||
|
if (!_.some(req.user.permissions, pm => _.includes(['write:assets', 'manage:system'], pm))) {
|
||||||
|
return res.status(403).json({
|
||||||
|
succeeded: false,
|
||||||
|
message: 'You are not authorized to upload files.'
|
||||||
|
})
|
||||||
|
} else if (req.files.length < 1) {
|
||||||
|
return res.status(400).json({
|
||||||
|
succeeded: false,
|
||||||
|
message: 'Missing upload payload.'
|
||||||
|
})
|
||||||
|
} else if (req.files.length > 1) {
|
||||||
|
return res.status(400).json({
|
||||||
|
succeeded: false,
|
||||||
|
message: 'You cannot upload multiple files within the same request.'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const fileMeta = _.get(req, 'files[0]', false)
|
||||||
|
if (!fileMeta) {
|
||||||
|
return res.status(500).json({
|
||||||
|
succeeded: false,
|
||||||
|
message: 'Missing upload file metadata.'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let folderPath = ''
|
||||||
|
try {
|
||||||
|
const folderRaw = _.get(req, 'body.mediaUpload', false)
|
||||||
|
if (folderRaw) {
|
||||||
|
folderPath = _.get(JSON.parse(folderRaw), 'path', false)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
return res.status(400).json({
|
||||||
|
succeeded: false,
|
||||||
|
message: 'Missing upload folder metadata.'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!WIKI.auth.checkAccess(req.user, ['write:assets'], { path: `${folderPath}/${fileMeta.originalname}`})) {
|
||||||
|
return res.status(403).json({
|
||||||
|
succeeded: false,
|
||||||
|
message: 'You are not authorized to upload files to this folder.'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
await WIKI.models.assets.upload({
|
||||||
|
...fileMeta,
|
||||||
|
originalname: sanitize(fileMeta.originalname).toLowerCase(),
|
||||||
|
folder: folderPath,
|
||||||
|
userId: req.user.id
|
||||||
|
})
|
||||||
|
res.send('ok')
|
||||||
|
})
|
||||||
|
|
||||||
|
router.get('/u', async (req, res, next) => {
|
||||||
|
res.json({
|
||||||
|
ok: true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
module.exports = router
|
@ -67,6 +67,7 @@ module.exports = {
|
|||||||
asyncStackTraces: WIKI.IS_DEBUG,
|
asyncStackTraces: WIKI.IS_DEBUG,
|
||||||
connection: dbConfig,
|
connection: dbConfig,
|
||||||
pool: {
|
pool: {
|
||||||
|
...WIKI.config.pool,
|
||||||
async afterCreate(conn, done) {
|
async afterCreate(conn, done) {
|
||||||
// -> Set Connection App Name
|
// -> Set Connection App Name
|
||||||
switch (WIKI.config.db.type) {
|
switch (WIKI.config.db.type) {
|
||||||
|
@ -1,15 +1,10 @@
|
|||||||
exports.up = knex => {
|
exports.up = knex => {
|
||||||
const dbCompat = {
|
|
||||||
charset: (WIKI.config.db.type === `mysql` || WIKI.config.db.type === `mariadb`),
|
|
||||||
noForeign: WIKI.config.db.type === 'sqlite'
|
|
||||||
}
|
|
||||||
return knex.schema
|
return knex.schema
|
||||||
// =====================================
|
// =====================================
|
||||||
// MODEL TABLES
|
// MODEL TABLES
|
||||||
// =====================================
|
// =====================================
|
||||||
// ASSETS ------------------------------
|
// ASSETS ------------------------------
|
||||||
.createTable('assets', table => {
|
.createTable('assets', table => {
|
||||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
|
||||||
table.increments('id').primary()
|
table.increments('id').primary()
|
||||||
table.string('filename').notNullable()
|
table.string('filename').notNullable()
|
||||||
table.string('basename').notNullable()
|
table.string('basename').notNullable()
|
||||||
@ -26,7 +21,6 @@ exports.up = knex => {
|
|||||||
})
|
})
|
||||||
// ASSET FOLDERS -----------------------
|
// ASSET FOLDERS -----------------------
|
||||||
.createTable('assetFolders', table => {
|
.createTable('assetFolders', table => {
|
||||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
|
||||||
table.increments('id').primary()
|
table.increments('id').primary()
|
||||||
table.string('name').notNullable()
|
table.string('name').notNullable()
|
||||||
table.string('slug').notNullable()
|
table.string('slug').notNullable()
|
||||||
@ -34,7 +28,6 @@ exports.up = knex => {
|
|||||||
})
|
})
|
||||||
// AUTHENTICATION ----------------------
|
// AUTHENTICATION ----------------------
|
||||||
.createTable('authentication', table => {
|
.createTable('authentication', table => {
|
||||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
|
||||||
table.string('key').notNullable().primary()
|
table.string('key').notNullable().primary()
|
||||||
table.boolean('isEnabled').notNullable().defaultTo(false)
|
table.boolean('isEnabled').notNullable().defaultTo(false)
|
||||||
table.json('config').notNullable()
|
table.json('config').notNullable()
|
||||||
@ -44,7 +37,6 @@ exports.up = knex => {
|
|||||||
})
|
})
|
||||||
// COMMENTS ----------------------------
|
// COMMENTS ----------------------------
|
||||||
.createTable('comments', table => {
|
.createTable('comments', table => {
|
||||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
|
||||||
table.increments('id').primary()
|
table.increments('id').primary()
|
||||||
table.text('content').notNullable()
|
table.text('content').notNullable()
|
||||||
table.string('createdAt').notNullable()
|
table.string('createdAt').notNullable()
|
||||||
@ -55,14 +47,12 @@ exports.up = knex => {
|
|||||||
})
|
})
|
||||||
// EDITORS -----------------------------
|
// EDITORS -----------------------------
|
||||||
.createTable('editors', table => {
|
.createTable('editors', table => {
|
||||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
|
||||||
table.string('key').notNullable().primary()
|
table.string('key').notNullable().primary()
|
||||||
table.boolean('isEnabled').notNullable().defaultTo(false)
|
table.boolean('isEnabled').notNullable().defaultTo(false)
|
||||||
table.json('config').notNullable()
|
table.json('config').notNullable()
|
||||||
})
|
})
|
||||||
// GROUPS ------------------------------
|
// GROUPS ------------------------------
|
||||||
.createTable('groups', table => {
|
.createTable('groups', table => {
|
||||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
|
||||||
table.increments('id').primary()
|
table.increments('id').primary()
|
||||||
table.string('name').notNullable()
|
table.string('name').notNullable()
|
||||||
table.json('permissions').notNullable()
|
table.json('permissions').notNullable()
|
||||||
@ -73,7 +63,6 @@ exports.up = knex => {
|
|||||||
})
|
})
|
||||||
// LOCALES -----------------------------
|
// LOCALES -----------------------------
|
||||||
.createTable('locales', table => {
|
.createTable('locales', table => {
|
||||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
|
||||||
table.string('code', 2).notNullable().primary()
|
table.string('code', 2).notNullable().primary()
|
||||||
table.json('strings')
|
table.json('strings')
|
||||||
table.boolean('isRTL').notNullable().defaultTo(false)
|
table.boolean('isRTL').notNullable().defaultTo(false)
|
||||||
@ -84,7 +73,6 @@ exports.up = knex => {
|
|||||||
})
|
})
|
||||||
// LOGGING ----------------------------
|
// LOGGING ----------------------------
|
||||||
.createTable('loggers', table => {
|
.createTable('loggers', table => {
|
||||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
|
||||||
table.string('key').notNullable().primary()
|
table.string('key').notNullable().primary()
|
||||||
table.boolean('isEnabled').notNullable().defaultTo(false)
|
table.boolean('isEnabled').notNullable().defaultTo(false)
|
||||||
table.string('level').notNullable().defaultTo('warn')
|
table.string('level').notNullable().defaultTo('warn')
|
||||||
@ -92,13 +80,11 @@ exports.up = knex => {
|
|||||||
})
|
})
|
||||||
// NAVIGATION ----------------------------
|
// NAVIGATION ----------------------------
|
||||||
.createTable('navigation', table => {
|
.createTable('navigation', table => {
|
||||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
|
||||||
table.string('key').notNullable().primary()
|
table.string('key').notNullable().primary()
|
||||||
table.json('config')
|
table.json('config')
|
||||||
})
|
})
|
||||||
// PAGE HISTORY ------------------------
|
// PAGE HISTORY ------------------------
|
||||||
.createTable('pageHistory', table => {
|
.createTable('pageHistory', table => {
|
||||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
|
||||||
table.increments('id').primary()
|
table.increments('id').primary()
|
||||||
table.string('path').notNullable()
|
table.string('path').notNullable()
|
||||||
table.string('hash').notNullable()
|
table.string('hash').notNullable()
|
||||||
@ -119,7 +105,6 @@ exports.up = knex => {
|
|||||||
})
|
})
|
||||||
// PAGES -------------------------------
|
// PAGES -------------------------------
|
||||||
.createTable('pages', table => {
|
.createTable('pages', table => {
|
||||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
|
||||||
table.increments('id').primary()
|
table.increments('id').primary()
|
||||||
table.string('path').notNullable()
|
table.string('path').notNullable()
|
||||||
table.string('hash').notNullable()
|
table.string('hash').notNullable()
|
||||||
@ -144,7 +129,6 @@ exports.up = knex => {
|
|||||||
})
|
})
|
||||||
// PAGE TREE ---------------------------
|
// PAGE TREE ---------------------------
|
||||||
.createTable('pageTree', table => {
|
.createTable('pageTree', table => {
|
||||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
|
||||||
table.increments('id').primary()
|
table.increments('id').primary()
|
||||||
table.string('path').notNullable()
|
table.string('path').notNullable()
|
||||||
table.integer('depth').unsigned().notNullable()
|
table.integer('depth').unsigned().notNullable()
|
||||||
@ -159,28 +143,24 @@ exports.up = knex => {
|
|||||||
})
|
})
|
||||||
// RENDERERS ---------------------------
|
// RENDERERS ---------------------------
|
||||||
.createTable('renderers', table => {
|
.createTable('renderers', table => {
|
||||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
|
||||||
table.string('key').notNullable().primary()
|
table.string('key').notNullable().primary()
|
||||||
table.boolean('isEnabled').notNullable().defaultTo(false)
|
table.boolean('isEnabled').notNullable().defaultTo(false)
|
||||||
table.json('config')
|
table.json('config')
|
||||||
})
|
})
|
||||||
// SEARCH ------------------------------
|
// SEARCH ------------------------------
|
||||||
.createTable('searchEngines', table => {
|
.createTable('searchEngines', table => {
|
||||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
|
||||||
table.string('key').notNullable().primary()
|
table.string('key').notNullable().primary()
|
||||||
table.boolean('isEnabled').notNullable().defaultTo(false)
|
table.boolean('isEnabled').notNullable().defaultTo(false)
|
||||||
table.json('config')
|
table.json('config')
|
||||||
})
|
})
|
||||||
// SETTINGS ----------------------------
|
// SETTINGS ----------------------------
|
||||||
.createTable('settings', table => {
|
.createTable('settings', table => {
|
||||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
|
||||||
table.string('key').notNullable().primary()
|
table.string('key').notNullable().primary()
|
||||||
table.json('value')
|
table.json('value')
|
||||||
table.string('updatedAt').notNullable()
|
table.string('updatedAt').notNullable()
|
||||||
})
|
})
|
||||||
// STORAGE -----------------------------
|
// STORAGE -----------------------------
|
||||||
.createTable('storage', table => {
|
.createTable('storage', table => {
|
||||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
|
||||||
table.string('key').notNullable().primary()
|
table.string('key').notNullable().primary()
|
||||||
table.boolean('isEnabled').notNullable().defaultTo(false)
|
table.boolean('isEnabled').notNullable().defaultTo(false)
|
||||||
table.string('mode', ['sync', 'push', 'pull']).notNullable().defaultTo('push')
|
table.string('mode', ['sync', 'push', 'pull']).notNullable().defaultTo('push')
|
||||||
@ -188,7 +168,6 @@ exports.up = knex => {
|
|||||||
})
|
})
|
||||||
// TAGS --------------------------------
|
// TAGS --------------------------------
|
||||||
.createTable('tags', table => {
|
.createTable('tags', table => {
|
||||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
|
||||||
table.increments('id').primary()
|
table.increments('id').primary()
|
||||||
table.string('tag').notNullable().unique()
|
table.string('tag').notNullable().unique()
|
||||||
table.string('title')
|
table.string('title')
|
||||||
@ -197,7 +176,6 @@ exports.up = knex => {
|
|||||||
})
|
})
|
||||||
// USER KEYS ---------------------------
|
// USER KEYS ---------------------------
|
||||||
.createTable('userKeys', table => {
|
.createTable('userKeys', table => {
|
||||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
|
||||||
table.increments('id').primary()
|
table.increments('id').primary()
|
||||||
table.string('kind').notNullable()
|
table.string('kind').notNullable()
|
||||||
table.string('token').notNullable()
|
table.string('token').notNullable()
|
||||||
@ -208,7 +186,6 @@ exports.up = knex => {
|
|||||||
})
|
})
|
||||||
// USERS -------------------------------
|
// USERS -------------------------------
|
||||||
.createTable('users', table => {
|
.createTable('users', table => {
|
||||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
|
||||||
table.increments('id').primary()
|
table.increments('id').primary()
|
||||||
table.string('email').notNullable()
|
table.string('email').notNullable()
|
||||||
table.string('name').notNullable()
|
table.string('name').notNullable()
|
||||||
@ -235,21 +212,18 @@ exports.up = knex => {
|
|||||||
// =====================================
|
// =====================================
|
||||||
// PAGE HISTORY TAGS ---------------------------
|
// PAGE HISTORY TAGS ---------------------------
|
||||||
.createTable('pageHistoryTags', table => {
|
.createTable('pageHistoryTags', table => {
|
||||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
|
||||||
table.increments('id').primary()
|
table.increments('id').primary()
|
||||||
table.integer('pageId').unsigned().references('id').inTable('pageHistory').onDelete('CASCADE')
|
table.integer('pageId').unsigned().references('id').inTable('pageHistory').onDelete('CASCADE')
|
||||||
table.integer('tagId').unsigned().references('id').inTable('tags').onDelete('CASCADE')
|
table.integer('tagId').unsigned().references('id').inTable('tags').onDelete('CASCADE')
|
||||||
})
|
})
|
||||||
// PAGE TAGS ---------------------------
|
// PAGE TAGS ---------------------------
|
||||||
.createTable('pageTags', table => {
|
.createTable('pageTags', table => {
|
||||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
|
||||||
table.increments('id').primary()
|
table.increments('id').primary()
|
||||||
table.integer('pageId').unsigned().references('id').inTable('pages').onDelete('CASCADE')
|
table.integer('pageId').unsigned().references('id').inTable('pages').onDelete('CASCADE')
|
||||||
table.integer('tagId').unsigned().references('id').inTable('tags').onDelete('CASCADE')
|
table.integer('tagId').unsigned().references('id').inTable('tags').onDelete('CASCADE')
|
||||||
})
|
})
|
||||||
// USER GROUPS -------------------------
|
// USER GROUPS -------------------------
|
||||||
.createTable('userGroups', table => {
|
.createTable('userGroups', table => {
|
||||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
|
||||||
table.increments('id').primary()
|
table.increments('id').primary()
|
||||||
table.integer('userId').unsigned().references('id').inTable('users').onDelete('CASCADE')
|
table.integer('userId').unsigned().references('id').inTable('users').onDelete('CASCADE')
|
||||||
table.integer('groupId').unsigned().references('id').inTable('groups').onDelete('CASCADE')
|
table.integer('groupId').unsigned().references('id').inTable('groups').onDelete('CASCADE')
|
||||||
@ -257,39 +231,7 @@ exports.up = knex => {
|
|||||||
// =====================================
|
// =====================================
|
||||||
// REFERENCES
|
// REFERENCES
|
||||||
// =====================================
|
// =====================================
|
||||||
// .table('assets', table => {
|
|
||||||
// dbCompat.noForeign ? table.integer('folderId').unsigned() : table.integer('folderId').unsigned().references('id').inTable('assetFolders')
|
|
||||||
// dbCompat.noForeign ? table.integer('authorId').unsigned() : table.integer('authorId').unsigned().references('id').inTable('users')
|
|
||||||
// })
|
|
||||||
// .table('comments', table => {
|
|
||||||
// dbCompat.noForeign ? table.integer('pageId').unsigned() : table.integer('pageId').unsigned().references('id').inTable('pages')
|
|
||||||
// dbCompat.noForeign ? table.integer('authorId').unsigned() : table.integer('authorId').unsigned().references('id').inTable('users')
|
|
||||||
// })
|
|
||||||
// .table('pageHistory', table => {
|
|
||||||
// dbCompat.noForeign ? table.integer('pageId').unsigned() : table.integer('pageId').unsigned().references('id').inTable('pages')
|
|
||||||
// dbCompat.noForeign ? table.string('editorKey') : table.string('editorKey').references('key').inTable('editors')
|
|
||||||
// dbCompat.noForeign ? table.string('localeCode', 2) : table.string('localeCode', 2).references('code').inTable('locales')
|
|
||||||
// dbCompat.noForeign ? table.integer('authorId').unsigned() : table.integer('authorId').unsigned().references('id').inTable('users')
|
|
||||||
// })
|
|
||||||
// .table('pages', table => {
|
|
||||||
// dbCompat.noForeign ? table.string('editorKey') : table.string('editorKey').references('key').inTable('editors')
|
|
||||||
// dbCompat.noForeign ? table.string('localeCode', 2) : table.string('localeCode', 2).references('code').inTable('locales')
|
|
||||||
// dbCompat.noForeign ? table.integer('authorId').unsigned() : table.integer('authorId').unsigned().references('id').inTable('users')
|
|
||||||
// dbCompat.noForeign ? table.integer('creatorId').unsigned() : table.integer('creatorId').unsigned().references('id').inTable('users')
|
|
||||||
// })
|
|
||||||
// .table('pageTree', table => {
|
|
||||||
// dbCompat.noForeign ? table.integer('parent').unsigned() : table.integer('parent').unsigned().references('id').inTable('pageTree')
|
|
||||||
// dbCompat.noForeign ? table.integer('pageId').unsigned() : table.integer('pageId').unsigned().references('id').inTable('pages')
|
|
||||||
// dbCompat.noForeign ? table.string('localeCode', 2) : table.string('localeCode', 2).references('code').inTable('locales')
|
|
||||||
// })
|
|
||||||
// .table('userKeys', table => {
|
|
||||||
// dbCompat.noForeign ? table.integer('userId').unsigned() : table.integer('userId').unsigned().references('id').inTable('users')
|
|
||||||
// })
|
|
||||||
.table('users', table => {
|
.table('users', table => {
|
||||||
// dbCompat.noForeign ? table.string('providerKey') : table.string('providerKey').references('key').inTable('authentication').notNullable().defaultTo('local')
|
|
||||||
// dbCompat.noForeign ? table.string('localeCode', 2) : table.string('localeCode', 2).references('code').inTable('locales').notNullable().defaultTo('en')
|
|
||||||
// dbCompat.noForeign ? table.string('defaultEditor') : table.string('defaultEditor').references('key').inTable('editors').notNullable().defaultTo('markdown')
|
|
||||||
|
|
||||||
table.unique(['providerKey', 'email'])
|
table.unique(['providerKey', 'email'])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
15
server/db/migrations-sqlite/2.0.0-beta.127.js
Normal file
15
server/db/migrations-sqlite/2.0.0-beta.127.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
exports.up = knex => {
|
||||||
|
return knex.schema
|
||||||
|
.table('assets', table => {
|
||||||
|
table.dropColumn('basename')
|
||||||
|
table.string('hash').notNullable()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.down = knex => {
|
||||||
|
return knex.schema
|
||||||
|
.table('assets', table => {
|
||||||
|
table.dropColumn('hash')
|
||||||
|
table.string('basename').notNullable()
|
||||||
|
})
|
||||||
|
}
|
@ -1,10 +1,6 @@
|
|||||||
exports.up = knex => {
|
exports.up = knex => {
|
||||||
const dbCompat = {
|
|
||||||
charset: (WIKI.config.db.type === `mysql` || WIKI.config.db.type === `mariadb`)
|
|
||||||
}
|
|
||||||
return knex.schema
|
return knex.schema
|
||||||
.createTable('assetData', table => {
|
.createTable('assetData', table => {
|
||||||
if (dbCompat.charset) { table.charset('utf8mb4') }
|
|
||||||
table.integer('id').primary()
|
table.integer('id').primary()
|
||||||
table.binary('data').notNullable()
|
table.binary('data').notNullable()
|
||||||
})
|
})
|
||||||
|
15
server/db/migrations/2.0.0-beta.127.js
Normal file
15
server/db/migrations/2.0.0-beta.127.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
exports.up = knex => {
|
||||||
|
return knex.schema
|
||||||
|
.table('assets', table => {
|
||||||
|
table.dropColumn('basename')
|
||||||
|
table.string('hash').notNullable()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.down = knex => {
|
||||||
|
return knex.schema
|
||||||
|
.table('assets', table => {
|
||||||
|
table.dropColumn('hash')
|
||||||
|
table.string('basename').notNullable()
|
||||||
|
})
|
||||||
|
}
|
@ -7,7 +7,7 @@ const PubSub = require('graphql-subscriptions').PubSub
|
|||||||
const { LEVEL, MESSAGE } = require('triple-beam')
|
const { LEVEL, MESSAGE } = require('triple-beam')
|
||||||
const Transport = require('winston-transport')
|
const Transport = require('winston-transport')
|
||||||
const { createRateLimitTypeDef } = require('graphql-rate-limit-directive')
|
const { createRateLimitTypeDef } = require('graphql-rate-limit-directive')
|
||||||
const { GraphQLUpload } = require('graphql-upload')
|
// const { GraphQLUpload } = require('graphql-upload')
|
||||||
|
|
||||||
/* global WIKI */
|
/* global WIKI */
|
||||||
|
|
||||||
@ -28,7 +28,7 @@ schemas.forEach(schema => {
|
|||||||
// Resolvers
|
// Resolvers
|
||||||
|
|
||||||
let resolvers = {
|
let resolvers = {
|
||||||
Upload: GraphQLUpload
|
// Upload: GraphQLUpload
|
||||||
}
|
}
|
||||||
const resolversObj = _.values(autoload(path.join(WIKI.SERVERPATH, 'graph/resolvers')))
|
const resolversObj = _.values(autoload(path.join(WIKI.SERVERPATH, 'graph/resolvers')))
|
||||||
resolversObj.forEach(resolver => {
|
resolversObj.forEach(resolver => {
|
||||||
|
60
server/graph/resolvers/asset.js
Normal file
60
server/graph/resolvers/asset.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
|
||||||
|
/* global WIKI */
|
||||||
|
|
||||||
|
const gql = require('graphql')
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
Query: {
|
||||||
|
async assets() { return {} }
|
||||||
|
},
|
||||||
|
Mutation: {
|
||||||
|
async assets() { return {} }
|
||||||
|
},
|
||||||
|
AssetQuery: {
|
||||||
|
async list(obj, args, context) {
|
||||||
|
const result = await WIKI.models.assets.query().where({
|
||||||
|
folderId: null,
|
||||||
|
kind: args.kind.toLowerCase()
|
||||||
|
})
|
||||||
|
return result.map(a => ({
|
||||||
|
...a,
|
||||||
|
kind: a.kind.toUpperCase()
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
AssetMutation: {
|
||||||
|
// deleteFile(obj, args) {
|
||||||
|
// return WIKI.models.File.destroy({
|
||||||
|
// where: {
|
||||||
|
// id: args.id
|
||||||
|
// },
|
||||||
|
// limit: 1
|
||||||
|
// })
|
||||||
|
// },
|
||||||
|
// renameFile(obj, args) {
|
||||||
|
// return WIKI.models.File.update({
|
||||||
|
// filename: args.filename
|
||||||
|
// }, {
|
||||||
|
// where: { id: args.id }
|
||||||
|
// })
|
||||||
|
// },
|
||||||
|
// moveFile(obj, args) {
|
||||||
|
// return WIKI.models.File.findById(args.fileId).then(fl => {
|
||||||
|
// if (!fl) {
|
||||||
|
// throw new gql.GraphQLError('Invalid File ID')
|
||||||
|
// }
|
||||||
|
// return WIKI.models.Folder.findById(args.folderId).then(fld => {
|
||||||
|
// if (!fld) {
|
||||||
|
// throw new gql.GraphQLError('Invalid Folder ID')
|
||||||
|
// }
|
||||||
|
// return fl.setFolder(fld)
|
||||||
|
// })
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
// File: {
|
||||||
|
// folder(fl) {
|
||||||
|
// return fl.getFolder()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
@ -1,51 +0,0 @@
|
|||||||
|
|
||||||
/* global WIKI */
|
|
||||||
|
|
||||||
const gql = require('graphql')
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
// Query: {
|
|
||||||
// files(obj, args, context, info) {
|
|
||||||
// return WIKI.models.File.findAll({ where: args })
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// Mutation: {
|
|
||||||
// uploadFile(obj, args) {
|
|
||||||
// // todo
|
|
||||||
// return WIKI.models.File.create(args)
|
|
||||||
// },
|
|
||||||
// deleteFile(obj, args) {
|
|
||||||
// return WIKI.models.File.destroy({
|
|
||||||
// where: {
|
|
||||||
// id: args.id
|
|
||||||
// },
|
|
||||||
// limit: 1
|
|
||||||
// })
|
|
||||||
// },
|
|
||||||
// renameFile(obj, args) {
|
|
||||||
// return WIKI.models.File.update({
|
|
||||||
// filename: args.filename
|
|
||||||
// }, {
|
|
||||||
// where: { id: args.id }
|
|
||||||
// })
|
|
||||||
// },
|
|
||||||
// moveFile(obj, args) {
|
|
||||||
// return WIKI.models.File.findById(args.fileId).then(fl => {
|
|
||||||
// if (!fl) {
|
|
||||||
// throw new gql.GraphQLError('Invalid File ID')
|
|
||||||
// }
|
|
||||||
// return WIKI.models.Folder.findById(args.folderId).then(fld => {
|
|
||||||
// if (!fld) {
|
|
||||||
// throw new gql.GraphQLError('Invalid Folder ID')
|
|
||||||
// }
|
|
||||||
// return fl.setFolder(fld)
|
|
||||||
// })
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// File: {
|
|
||||||
// folder(fl) {
|
|
||||||
// return fl.getFolder()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
|
@ -17,8 +17,8 @@ extend type Mutation {
|
|||||||
type AssetQuery {
|
type AssetQuery {
|
||||||
list(
|
list(
|
||||||
root: String
|
root: String
|
||||||
kind: [AssetKind]
|
kind: AssetKind
|
||||||
): [AssetItem]
|
): [AssetItem] @auth(requires: ["manage:system", "read:assets"])
|
||||||
}
|
}
|
||||||
|
|
||||||
# -----------------------------------------------
|
# -----------------------------------------------
|
||||||
@ -37,6 +37,20 @@ type AssetMutation {
|
|||||||
|
|
||||||
type AssetItem {
|
type AssetItem {
|
||||||
id: Int!
|
id: Int!
|
||||||
|
filename: String!
|
||||||
|
ext: String!
|
||||||
|
kind: AssetKind!
|
||||||
|
mime: String!
|
||||||
|
fileSize: Int!
|
||||||
|
metadata: String
|
||||||
|
createdAt: Date!
|
||||||
|
updatedAt: Date!
|
||||||
|
folder: AssetFolder
|
||||||
|
author: User
|
||||||
|
}
|
||||||
|
|
||||||
|
type AssetFolder {
|
||||||
|
id: Int!
|
||||||
}
|
}
|
||||||
|
|
||||||
enum AssetKind {
|
enum AssetKind {
|
||||||
|
12
server/helpers/asset.js
Normal file
12
server/helpers/asset.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
const crypto = require('crypto')
|
||||||
|
|
||||||
|
/* global WIKI */
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
/**
|
||||||
|
* Generate unique hash from page
|
||||||
|
*/
|
||||||
|
generateHash(assetPath) {
|
||||||
|
return crypto.createHash('sha1').update(assetPath).digest('hex')
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,6 @@ const _ = require('lodash')
|
|||||||
const crypto = require('crypto')
|
const crypto = require('crypto')
|
||||||
|
|
||||||
const localeSegmentRegex = /^[A-Z]{2}(-[A-Z]{2})?$/i
|
const localeSegmentRegex = /^[A-Z]{2}(-[A-Z]{2})?$/i
|
||||||
const systemSegmentRegex = /^[A-Z]\//i
|
|
||||||
|
|
||||||
/* global WIKI */
|
/* global WIKI */
|
||||||
|
|
||||||
@ -67,8 +66,17 @@ module.exports = {
|
|||||||
*/
|
*/
|
||||||
isReservedPath(rawPath) {
|
isReservedPath(rawPath) {
|
||||||
const firstSection = _.head(rawPath.split('/'))
|
const firstSection = _.head(rawPath.split('/'))
|
||||||
return _.some(WIKI.data.reservedPaths, p => {
|
if (firstSection.length === 1) {
|
||||||
return p === firstSection || systemSegmentRegex.test(rawPath)
|
return true
|
||||||
})
|
} else if (localeSegmentRegex.test(firstSection)) {
|
||||||
|
return true
|
||||||
|
} else if (
|
||||||
|
_.some(WIKI.data.reservedPaths, p => {
|
||||||
|
return p === firstSection
|
||||||
|
})) {
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -140,7 +140,6 @@ module.exports = async () => {
|
|||||||
path: '/graphql-subscriptions'
|
path: '/graphql-subscriptions'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
app.use('/graphql', mw.upload)
|
|
||||||
apolloServer.applyMiddleware({ app })
|
apolloServer.applyMiddleware({ app })
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
@ -148,6 +147,7 @@ module.exports = async () => {
|
|||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
app.use('/', ctrl.auth)
|
app.use('/', ctrl.auth)
|
||||||
|
app.use('/', ctrl.upload)
|
||||||
app.use('/', ctrl.common)
|
app.use('/', ctrl.common)
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
35
server/models/assetFolders.js
Normal file
35
server/models/assetFolders.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/* global WIKI */
|
||||||
|
|
||||||
|
const Model = require('objection').Model
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Users model
|
||||||
|
*/
|
||||||
|
module.exports = class AssetFolder extends Model {
|
||||||
|
static get tableName() { return 'assetFolders' }
|
||||||
|
|
||||||
|
static get jsonSchema () {
|
||||||
|
return {
|
||||||
|
type: 'object',
|
||||||
|
|
||||||
|
properties: {
|
||||||
|
id: {type: 'integer'},
|
||||||
|
name: {type: 'string'},
|
||||||
|
slug: {type: 'string'}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static get relationMappings() {
|
||||||
|
return {
|
||||||
|
parent: {
|
||||||
|
relation: Model.BelongsToOneRelation,
|
||||||
|
modelClass: AssetFolder,
|
||||||
|
join: {
|
||||||
|
from: 'assetFolders.folderId',
|
||||||
|
to: 'assetFolders.id'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
97
server/models/assets.js
Normal file
97
server/models/assets.js
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
/* global WIKI */
|
||||||
|
|
||||||
|
const Model = require('objection').Model
|
||||||
|
const moment = require('moment')
|
||||||
|
const path = require('path')
|
||||||
|
const fs = require('fs-extra')
|
||||||
|
const _ = require('lodash')
|
||||||
|
const assetHelper = require('../helpers/asset')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Users model
|
||||||
|
*/
|
||||||
|
module.exports = class Asset extends Model {
|
||||||
|
static get tableName() { return 'assets' }
|
||||||
|
|
||||||
|
static get jsonSchema () {
|
||||||
|
return {
|
||||||
|
type: 'object',
|
||||||
|
|
||||||
|
properties: {
|
||||||
|
id: {type: 'integer'},
|
||||||
|
filename: {type: 'string'},
|
||||||
|
hash: {type: 'string'},
|
||||||
|
ext: {type: 'string'},
|
||||||
|
kind: {type: 'string'},
|
||||||
|
mime: {type: 'string'},
|
||||||
|
fileSize: {type: 'integer'},
|
||||||
|
metadata: {type: 'object'},
|
||||||
|
createdAt: {type: 'string'},
|
||||||
|
updatedAt: {type: 'string'}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static get relationMappings() {
|
||||||
|
return {
|
||||||
|
author: {
|
||||||
|
relation: Model.BelongsToOneRelation,
|
||||||
|
modelClass: require('./users'),
|
||||||
|
join: {
|
||||||
|
from: 'assets.authorId',
|
||||||
|
to: 'users.id'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
folder: {
|
||||||
|
relation: Model.BelongsToOneRelation,
|
||||||
|
modelClass: require('./assetFolders'),
|
||||||
|
join: {
|
||||||
|
from: 'assets.folderId',
|
||||||
|
to: 'assetFolders.id'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async $beforeUpdate(opt, context) {
|
||||||
|
await super.$beforeUpdate(opt, context)
|
||||||
|
|
||||||
|
this.updatedAt = moment.utc().toISOString()
|
||||||
|
}
|
||||||
|
async $beforeInsert(context) {
|
||||||
|
await super.$beforeInsert(context)
|
||||||
|
|
||||||
|
this.createdAt = moment.utc().toISOString()
|
||||||
|
this.updatedAt = moment.utc().toISOString()
|
||||||
|
}
|
||||||
|
|
||||||
|
static async upload(opts) {
|
||||||
|
const fileInfo = path.parse(opts.originalname)
|
||||||
|
const fileHash = assetHelper.generateHash(`${opts.folder}/${opts.originalname}`)
|
||||||
|
|
||||||
|
// Create asset entry
|
||||||
|
const asset = await WIKI.models.assets.query().insert({
|
||||||
|
filename: opts.originalname,
|
||||||
|
hash: fileHash,
|
||||||
|
ext: fileInfo.ext,
|
||||||
|
kind: _.startsWith(opts.mimetype, 'image/') ? 'image' : 'binary',
|
||||||
|
mime: opts.mimetype,
|
||||||
|
fileSize: opts.size,
|
||||||
|
authorId: opts.userId
|
||||||
|
})
|
||||||
|
|
||||||
|
// Save asset data
|
||||||
|
try {
|
||||||
|
const fileBuffer = await fs.readFile(opts.path)
|
||||||
|
await WIKI.models.knex('assetData').insert({
|
||||||
|
id: asset.id,
|
||||||
|
data: fileBuffer
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
WIKI.logger.warn(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move temp upload to cache
|
||||||
|
await fs.move(opts.path, path.join(process.cwd(), `data/cache/${fileHash}.dat`))
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user