Commit 6fe49309 authored by Nick's avatar Nick

feat: admin auth + config ref + modules sidebar ui + GQL upload (wip)

parent 59683318
......@@ -5,11 +5,10 @@ import VueRouter from 'vue-router'
import VueClipboards from 'vue-clipboards'
import VeeValidate from 'vee-validate'
import { ApolloClient } from 'apollo-client'
import { createPersistedQueryLink } from 'apollo-link-persisted-queries'
import { BatchHttpLink } from 'apollo-link-batch-http'
import { ApolloLink, split } from 'apollo-link'
// import { createHttpLink } from 'apollo-link-http'
import { WebSocketLink } from 'apollo-link-ws'
import { createUploadLink } from 'apollo-upload-client'
import { ErrorLink } from 'apollo-link-error'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { getMainDefinition } from 'apollo-utilities'
......@@ -56,6 +55,33 @@ store.commit('user/REFRESH_AUTH')
const graphQLEndpoint = window.location.protocol + '//' + window.location.host + '/graphql'
const graphQLWSEndpoint = ((window.location.protocol === 'https:') ? 'wss:' : 'ws:') + '//' + window.location.host + '/graphql-subscriptions'
const graphQLFetch = async (uri, options) => {
// Strip __typename fields from variables
let body = JSON.parse(options.body)
body = body.map(bd => {
return ({
...bd,
variables: JSON.parse(JSON.stringify(bd.variables), (key, value) => { return key === '__typename' ? undefined : value })
})
})
options.body = JSON.stringify(body)
// Inject authentication token
const jwtToken = Cookies.get('jwt')
if (jwtToken) {
options.headers.Authorization = `Bearer ${jwtToken}`
}
const resp = await fetch(uri, options)
// Handle renewed JWT
const newJWT = resp.headers.get('new-jwt')
if (newJWT) {
Cookies.set('jwt', newJWT, { expires: 365 })
}
return resp
}
const graphQLLink = ApolloLink.from([
new ErrorLink(({ graphQLErrors, networkError }) => {
if (graphQLErrors) {
......@@ -79,7 +105,6 @@ const graphQLLink = ApolloLink.from([
})
}
}),
// createPersistedQueryLink(),
new BatchHttpLink({
includeExtensions: true,
uri: graphQLEndpoint,
......@@ -93,10 +118,6 @@ const graphQLLink = ApolloLink.from([
variables: JSON.parse(JSON.stringify(bd.variables), (key, value) => { return key === '__typename' ? undefined : value })
})
})
// body = {
// ...body,
// variables: JSON.parse(JSON.stringify(body.variables), (key, value) => { return key === '__typename' ? undefined : value })
// }
options.body = JSON.stringify(body)
// Inject authentication token
......@@ -117,6 +138,28 @@ const graphQLLink = ApolloLink.from([
})
])
const graphQLUploadLink = createUploadLink({
includeExtensions: true,
uri: graphQLEndpoint,
credentials: 'include',
fetch: async (uri, options) => {
// Inject authentication token
const jwtToken = Cookies.get('jwt')
if (jwtToken) {
options.headers.Authorization = `Bearer ${jwtToken}`
}
const resp = await fetch(uri, options)
// Handle renewed JWT
const newJWT = resp.headers.get('new-jwt')
if (newJWT) {
Cookies.set('jwt', newJWT, { expires: 365 })
}
return resp
}
})
const graphQLWSLink = new WebSocketLink({
uri: graphQLWSEndpoint,
options: {
......@@ -129,7 +172,7 @@ window.graphQL = new ApolloClient({
link: split(({ query }) => {
const { kind, operation } = getMainDefinition(query)
return kind === 'OperationDefinition' && operation === 'subscription'
}, graphQLWSLink, graphQLLink),
}, graphQLWSLink, split(operation => operation.getContext().hasUpload, graphQLUploadLink, graphQLLink)),
cache: new InMemoryCache(),
connectToDevTools: (process.env.node_env === 'development')
})
......
......@@ -49,7 +49,7 @@
v-list-tile(to='/auth')
v-list-tile-avatar: v-icon lock_outline
v-list-tile-title {{ $t('admin:auth.title') }}
v-list-tile(to='/editor')
v-list-tile(to='/editor', disabled)
v-list-tile-avatar: v-icon transform
v-list-tile-title {{ $t('admin:editor.title') }}
v-list-tile(to='/logging')
......
......@@ -21,17 +21,19 @@
v-card.animated.fadeInUp
v-toolbar(flat, color='primary', dark, dense)
.subheading Search Engine
v-card-text
v-radio-group.my-0(v-model='selectedEngine')
v-radio.my-1(
v-for='(engine, n) in engines'
:key='engine.key'
:label='engine.title'
:value='engine.key'
:disabled='!engine.isAvailable'
color='primary'
hide-details
)
v-list.py-0(two-line, dense)
template(v-for='(eng, idx) in engines')
v-list-tile(:key='eng.key', @click='selectedEngine = eng.key', :disabled='!eng.isAvailable')
v-list-tile-avatar
v-icon(color='grey', v-if='!eng.isAvailable') cancel
v-icon(color='primary', v-else-if='eng.key === selectedEngine') radio_button_checked
v-icon(color='grey', v-else) radio_button_unchecked
v-list-tile-content
v-list-tile-title.body-2(:class='!eng.isAvailable ? `grey--text` : (selectedEngine === eng.key ? `primary--text` : ``)') {{ eng.title }}
v-list-tile-sub-title.caption(:class='!eng.isAvailable ? `grey--text text--lighten-1` : (selectedEngine === eng.key ? `blue--text ` : ``)') {{ eng.description }}
v-list-tile-avatar(v-if='selectedEngine === eng.key')
v-icon.animated.fadeInLeft(color='primary') arrow_forward_ios
v-divider(v-if='idx < engines.length - 1')
v-flex(lg9, xs12)
v-card.wiki-form.animated.fadeInUp.wait-p2s
......
......@@ -9,7 +9,7 @@
v-toolbar.radius-7(color='teal lighten-5', dense, flat, height='44')
.body-2.teal--text Images
v-btn.ml-3.my-0.radius-7(outline, large, color='teal', disabled)
v-icon(left) keyboard_backspace
v-icon(left) keyboard_arrow_up
span Parent Folder
v-btn.my-0.radius-7(outline, large, color='teal')
v-icon(left) add
......@@ -48,14 +48,15 @@
ref='pond'
label-idle='Browse or Drop files here...'
allow-multiple='true'
accepted-file-types='image/jpeg, image/png'
accepted-file-types='image/jpeg, image/png, image/gif, image/svg'
:files='files'
max-files='10'
)
v-divider
v-card-actions.pa-3
.caption.grey--text.text-darken-2 Max 20 files, 5 MB each
.caption.grey--text.text-darken-2 Max 10 files, 5 MB each
v-spacer
v-btn(color='teal', dark) Upload
v-btn(color='teal', dark, @click='upload') Upload
v-card.mt-3.radius-7.animated.fadeInRight.wait-p4s(light)
v-card-text.pb-0
......@@ -96,11 +97,11 @@ import { sync } from 'vuex-pathify'
import vueFilePond from 'vue-filepond'
import 'filepond/dist/filepond.min.css'
import 'filepond-plugin-image-preview/dist/filepond-plugin-image-preview.min.css'
import FilePondPluginFileValidateType from 'filepond-plugin-file-validate-type'
import FilePondPluginImagePreview from 'filepond-plugin-image-preview'
const FilePond = vueFilePond(FilePondPluginFileValidateType, FilePondPluginImagePreview)
import uploadFileMutation from 'gql/editor/upload.gql'
const FilePond = vueFilePond(FilePondPluginFileValidateType)
export default {
components: {
......@@ -135,6 +136,21 @@ export default {
methods: {
insert () {
this.activeModal = ''
},
async upload () {
const files = this.$refs.pond.getFiles()
for (let fl of files) {
const resp = await this.$apollo.mutate({
mutation: uploadFileMutation,
variables: {
data: fl.file
},
context: {
hasUpload: true
}
})
console.info(resp)
}
}
}
}
......
......@@ -5,6 +5,7 @@ query {
key
title
description
isAvailable
useForm
logo
website
......
mutation ($file: Upload!) {
assets {
upload(data:$file) {
responseResult {
succeeded
message
}
}
}
}
......@@ -76,6 +76,7 @@
"graphql-rate-limit-directive": "0.1.0",
"graphql-subscriptions": "1.0.0",
"graphql-tools": "4.0.4",
"graphql-upload": "8.0.5",
"highlight.js": "9.14.2",
"i18next": "14.0.1",
"i18next-express-middleware": "1.7.1",
......@@ -192,6 +193,7 @@
"apollo-link-http": "1.5.11",
"apollo-link-persisted-queries": "0.2.2",
"apollo-link-ws": "1.0.14",
"apollo-upload-client": "10.0.0",
"apollo-utilities": "1.1.2",
"autoprefixer": "9.4.7",
"babel-eslint": "10.0.1",
......@@ -221,7 +223,6 @@
"file-loader": "3.0.1",
"filepond": "4.2.0",
"filepond-plugin-file-validate-type": "1.2.2",
"filepond-plugin-image-preview": "4.0.3",
"filesize.js": "1.0.2",
"grapesjs": "0.14.52",
"graphiql": "0.12.0",
......
exports.up = knex => {
const dbCompat = {
charset: (WIKI.config.db.type === `mysql` || WIKI.config.db.type === `mariadb`)
}
return knex.schema
.createTable('assetData', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.integer('id').primary()
table.binary('data').notNullable()
})
}
exports.down = knex => {
return knex.schema
.dropTableIfExists('assetData')
}
exports.up = knex => {
const dbCompat = {
charset: (WIKI.config.db.type === `mysql` || WIKI.config.db.type === `mariadb`)
}
return knex.schema
.createTable('assetData', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.integer('id').primary()
table.binary('data').notNullable()
})
}
exports.down = knex => {
return knex.schema
.dropTableIfExists('assetData')
}
......@@ -7,6 +7,7 @@ const PubSub = require('graphql-subscriptions').PubSub
const { LEVEL, MESSAGE } = require('triple-beam')
const Transport = require('winston-transport')
const { createRateLimitTypeDef } = require('graphql-rate-limit-directive')
const { GraphQLUpload } = require('graphql-upload')
/* global WIKI */
......@@ -26,7 +27,9 @@ schemas.forEach(schema => {
// Resolvers
let resolvers = {}
let resolvers = {
Upload: GraphQLUpload
}
const resolversObj = _.values(autoload(path.join(WIKI.SERVERPATH, 'graph/resolvers')))
resolversObj.forEach(resolver => {
_.merge(resolvers, resolver)
......
# ===============================================
# ASSETS
# ===============================================
extend type Query {
assets: AssetQuery
}
extend type Mutation {
assets: AssetMutation
}
# -----------------------------------------------
# QUERIES
# -----------------------------------------------
type AssetQuery {
list(
root: String
kind: [AssetKind]
): [AssetItem]
}
# -----------------------------------------------
# MUTATIONS
# -----------------------------------------------
type AssetMutation {
upload(
data: Upload!
): DefaultResponse
}
# -----------------------------------------------
# TYPES
# -----------------------------------------------
type AssetItem {
id: Int!
}
enum AssetKind {
IMAGE
BINARY
}
......@@ -58,6 +58,7 @@ type AuthenticationStrategy {
props: [String]
title: String!
description: String
isAvailable: Boolean
useForm: Boolean!
logo: String
color: String
......
......@@ -11,7 +11,6 @@ const https = require('https')
const path = require('path')
const _ = require('lodash')
const { ApolloServer } = require('apollo-server-express')
// const oauth2orize = require('oauth2orize')
/* global WIKI */
......@@ -62,12 +61,6 @@ module.exports = async () => {
}))
// ----------------------------------------
// OAuth2 Server
// ----------------------------------------
// const OAuth2Server = oauth2orize.createServer()
// ----------------------------------------
// Passport Authentication
// ----------------------------------------
......@@ -137,6 +130,7 @@ module.exports = async () => {
path: '/graphql-subscriptions'
}
})
app.use('/graphql', mw.upload)
apolloServer.applyMiddleware({ app })
// ----------------------------------------
......
const { graphqlUploadExpress } = require('graphql-upload')
/* global WIKI */
/**
* GraphQL File Upload Middleware
*/
module.exports = graphqlUploadExpress({ maxFileSize: 5000000, maxFiles: 20 })
......@@ -5,8 +5,21 @@ author: requarks.io
logo: https://static.requarks.io/logo/auth0.svg
color: deep-orange
website: https://auth0.com/
isAvailable: true
useForm: false
props:
domain: String
clientId: String
clientSecret: String
domain:
type: String
title: Domain
hint: Your Auth0 domain (e.g. something.auth0.com)
order: 1
clientId:
type: String
title: Client ID
hint: Application Client ID
order: 2
clientSecret:
type: String
title: Client Secret
hint: Application Client Secret
order: 3
......@@ -5,5 +5,6 @@ author: requarks.io
logo: https://static.requarks.io/logo/wikijs.svg
color: yellow darken-3
website: https://wiki.js.org
isAvailable: true
useForm: true
props: {}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment