diff --git a/server/graph/directives/auth.js b/server/graph/directives/auth.js
deleted file mode 100644
index 72fbce9e..00000000
--- a/server/graph/directives/auth.js
+++ /dev/null
@@ -1,56 +0,0 @@
-const { SchemaDirectiveVisitor } = require('graphql-tools')
-const { defaultFieldResolver } = require('graphql')
-const _ = require('lodash')
-
-class AuthDirective extends SchemaDirectiveVisitor {
- visitObject(type) {
- this.ensureFieldsWrapped(type)
- type._requiredAuthScopes = this.args.requires
- }
- // Visitor methods for nested types like fields and arguments
- // also receive a details object that provides information about
- // the parent and grandparent types.
- visitFieldDefinition(field, details) {
- this.ensureFieldsWrapped(details.objectType)
- field._requiredAuthScopes = this.args.requires
- }
-
- visitArgumentDefinition(argument, details) {
- this.ensureFieldsWrapped(details.objectType)
- argument._requiredAuthScopes = this.args.requires
- }
-
- ensureFieldsWrapped(objectType) {
- // Mark the GraphQLObjectType object to avoid re-wrapping:
- if (objectType._authFieldsWrapped) return
- objectType._authFieldsWrapped = true
-
- const fields = objectType.getFields()
-
- Object.keys(fields).forEach(fieldName => {
- const field = fields[fieldName]
- const { resolve = defaultFieldResolver } = field
- field.resolve = async function (...args) {
- // Get the required scopes from the field first, falling back
- // to the objectType if no scopes is required by the field:
- const requiredScopes = field._requiredAuthScopes || objectType._requiredAuthScopes
-
- if (!requiredScopes) {
- return resolve.apply(this, args)
- }
-
- const context = args[2]
- if (!context.req.user) {
- throw new Error('Unauthorized')
- }
- if (!_.some(context.req.user.permissions, pm => _.includes(requiredScopes, pm))) {
- throw new Error('Forbidden')
- }
-
- return resolve.apply(this, args)
- }
- })
- }
-}
-
-module.exports = AuthDirective
diff --git a/server/graph/directives/rate-limit.js b/server/graph/directives/rate-limit.js
deleted file mode 100644
index 325c8f4d..00000000
--- a/server/graph/directives/rate-limit.js
+++ /dev/null
@@ -1,5 +0,0 @@
-const { createRateLimitDirective } = require('graphql-rate-limit-directive')
-
-module.exports = createRateLimitDirective({
- keyGenerator: (directiveArgs, source, args, context, info) => `${context.req.ip}:${info.parentType}.${info.fieldName}`
-})
diff --git a/server/graph/index.js b/server/graph/index.js
index a617f08e..d0a57455 100644
--- a/server/graph/index.js
+++ b/server/graph/index.js
@@ -3,16 +3,20 @@ const fs = require('fs')
const path = require('path')
const autoload = require('auto-load')
const { makeExecutableSchema } = require('@graphql-tools/schema')
-const { rateLimitDirective } = require('graphql-rate-limit-directive')
+const { defaultKeyGenerator, rateLimitDirective } = require('graphql-rate-limit-directive')
const { GraphQLUpload } = require('graphql-upload')
-const { rateLimitDirectiveTypeDefs, rateLimitDirectiveTransformer } = rateLimitDirective()
/* global WIKI */
-WIKI.logger.info(`Loading GraphQL Schema...`)
+// Rate Limiter
+
+const { rateLimitDirectiveTypeDefs, rateLimitDirectiveTransformer } = rateLimitDirective({
+ keyGenerator: (directiveArgs, source, args, context, info) => `${context.req.ip}:${defaultKeyGenerator(directiveArgs, source, args, context, info)}`
+})
// Schemas
+WIKI.logger.info(`Loading GraphQL Schema...`)
const typeDefs = [
rateLimitDirectiveTypeDefs
]
@@ -23,7 +27,11 @@ schemas.forEach(schema => {
// Resolvers
+WIKI.logger.info(`Loading GraphQL Resolvers...`)
let resolvers = {
+ Date: require('./scalars/date'),
+ JSON: require('./scalars/json'),
+ UUID: require('./scalars/uuid'),
Upload: GraphQLUpload
}
const resolversObj = _.values(autoload(path.join(WIKI.SERVERPATH, 'graph/resolvers')))
@@ -33,11 +41,14 @@ resolversObj.forEach(resolver => {
// Make executable schema
+WIKI.logger.info(`Compiling GraphQL Schema...`)
let schema = makeExecutableSchema({
typeDefs,
resolvers
})
+// Apply schema transforms
+
schema = rateLimitDirectiveTransformer(schema)
WIKI.logger.info(`GraphQL Schema: [ OK ]`)
diff --git a/server/graph/resolvers/analytics.js b/server/graph/resolvers/analytics.js
index 50193705..ebcd87d3 100644
--- a/server/graph/resolvers/analytics.js
+++ b/server/graph/resolvers/analytics.js
@@ -5,13 +5,7 @@ const graphHelper = require('../../helpers/graph')
module.exports = {
Query: {
- async analytics() { return {} }
- },
- Mutation: {
- async analytics() { return {} }
- },
- AnalyticsQuery: {
- async providers(obj, args, context, info) {
+ async analyticsProviders(obj, args, context, info) {
let providers = await WIKI.models.analytics.getProviders(args.isEnabled)
providers = providers.map(stg => {
const providerInfo = _.find(WIKI.data.analytics, ['key', stg.key]) || {}
@@ -33,8 +27,8 @@ module.exports = {
return providers
}
},
- AnalyticsMutation: {
- async updateProviders(obj, args, context) {
+ Mutation: {
+ async updateAnalyticsProviders(obj, args, context) {
try {
for (let str of args.providers) {
await WIKI.models.analytics.query().patch({
diff --git a/server/graph/resolvers/asset.js b/server/graph/resolvers/asset.js
index 91efbdda..1b30a579 100644
--- a/server/graph/resolvers/asset.js
+++ b/server/graph/resolvers/asset.js
@@ -7,13 +7,7 @@ const assetHelper = require('../../helpers/asset')
module.exports = {
Query: {
- async assets() { return {} }
- },
- Mutation: {
- async assets() { return {} }
- },
- AssetQuery: {
- async list(obj, args, context) {
+ async assets(obj, args, context) {
let cond = {
folderId: args.folderId === 0 ? null : args.folderId
}
@@ -31,7 +25,7 @@ module.exports = {
kind: a.kind.toUpperCase()
}))
},
- async folders(obj, args, context) {
+ async assetsFolders(obj, args, context) {
const results = await WIKI.models.assetFolders.query().where({
parentId: args.parentFolderId === 0 ? null : args.parentFolderId
})
@@ -43,11 +37,11 @@ module.exports = {
})
}
},
- AssetMutation: {
+ Mutation: {
/**
* Create New Asset Folder
*/
- async createFolder(obj, args, context) {
+ async createAssetsFolder(obj, args, context) {
try {
const folderSlug = sanitize(args.slug).toLowerCase()
const parentFolderId = args.parentFolderId === 0 ? null : args.parentFolderId
diff --git a/server/graph/resolvers/authentication.js b/server/graph/resolvers/authentication.js
index ce7a152a..6ac72d92 100644
--- a/server/graph/resolvers/authentication.js
+++ b/server/graph/resolvers/authentication.js
@@ -7,12 +7,6 @@ const graphHelper = require('../../helpers/graph')
module.exports = {
Query: {
- async authentication () { return {} }
- },
- Mutation: {
- async authentication () { return {} }
- },
- AuthenticationQuery: {
/**
* List of API Keys
*/
@@ -34,7 +28,7 @@ module.exports = {
apiState () {
return WIKI.config.api.isEnabled
},
- async strategies () {
+ async authStrategies () {
return WIKI.data.authentication.map(stg => ({
...stg,
isAvailable: stg.isAvailable === true,
@@ -45,35 +39,35 @@ module.exports = {
})
}, []), 'key')
}))
- },
- /**
- * Fetch active authentication strategies
- */
- async activeStrategies (obj, args, context, info) {
- let strategies = await WIKI.models.authentication.getStrategies()
- strategies = strategies.map(stg => {
- const strategyInfo = _.find(WIKI.data.authentication, ['key', stg.strategyKey]) || {}
- return {
- ...stg,
- strategy: strategyInfo,
- config: _.sortBy(_.transform(stg.config, (res, value, key) => {
- const configData = _.get(strategyInfo.props, key, false)
- if (configData) {
- res.push({
- key,
- value: JSON.stringify({
- ...configData,
- value
- })
- })
- }
- }, []), 'key')
- }
- })
- return args.enabledOnly ? _.filter(strategies, 'isEnabled') : strategies
}
+ // /**
+ // * Fetch active authentication strategies
+ // */
+ // async activeStrategies (obj, args, context, info) {
+ // let strategies = await WIKI.models.authentication.getStrategies()
+ // strategies = strategies.map(stg => {
+ // const strategyInfo = _.find(WIKI.data.authentication, ['key', stg.strategyKey]) || {}
+ // return {
+ // ...stg,
+ // strategy: strategyInfo,
+ // config: _.sortBy(_.transform(stg.config, (res, value, key) => {
+ // const configData = _.get(strategyInfo.props, key, false)
+ // if (configData) {
+ // res.push({
+ // key,
+ // value: JSON.stringify({
+ // ...configData,
+ // value
+ // })
+ // })
+ // }
+ // }, []), 'key')
+ // }
+ // })
+ // return args.enabledOnly ? _.filter(strategies, 'isEnabled') : strategies
+ // }
},
- AuthenticationMutation: {
+ Mutation: {
/**
* Create New API Key
*/
@@ -197,7 +191,7 @@ module.exports = {
/**
* Update Authentication Strategies
*/
- async updateStrategies (obj, args, context) {
+ async updateAuthStrategies (obj, args, context) {
try {
const previousStrategies = await WIKI.models.authentication.getStrategies()
for (const str of args.strategies) {
diff --git a/server/graph/resolvers/comment.js b/server/graph/resolvers/comment.js
index ff30b55c..01308bb4 100644
--- a/server/graph/resolvers/comment.js
+++ b/server/graph/resolvers/comment.js
@@ -5,16 +5,10 @@ const graphHelper = require('../../helpers/graph')
module.exports = {
Query: {
- async comments() { return {} }
- },
- Mutation: {
- async comments() { return {} }
- },
- CommentQuery: {
/**
* Fetch list of Comments Providers
*/
- async providers(obj, args, context, info) {
+ async commentsProviders(obj, args, context, info) {
const providers = await WIKI.models.commentProviders.getProviders()
return providers.map(provider => {
const providerInfo = _.find(WIKI.data.commentProviders, ['key', provider.key]) || {}
@@ -39,7 +33,7 @@ module.exports = {
/**
* Fetch list of comments for a page
*/
- async list (obj, args, context) {
+ async comments (obj, args, context) {
const page = await WIKI.models.pages.query().select('id').findOne({ localeCode: args.locale, path: args.path })
if (page) {
if (WIKI.auth.checkAccess(context.req.user, ['read:comments'], args)) {
@@ -60,7 +54,7 @@ module.exports = {
/**
* Fetch a single comment
*/
- async single (obj, args, context) {
+ async commentById (obj, args, context) {
const cm = await WIKI.data.commentProvider.getCommentById(args.id)
if (!cm || !cm.pageId) {
throw new WIKI.Error.CommentNotFound()
@@ -86,11 +80,11 @@ module.exports = {
}
}
},
- CommentMutation: {
+ Mutation: {
/**
* Create New Comment
*/
- async create (obj, args, context) {
+ async createComment (obj, args, context) {
try {
const cmId = await WIKI.models.comments.postNewComment({
...args,
@@ -108,7 +102,7 @@ module.exports = {
/**
* Update an Existing Comment
*/
- async update (obj, args, context) {
+ async updateComment (obj, args, context) {
try {
const cmRender = await WIKI.models.comments.updateComment({
...args,
@@ -126,7 +120,7 @@ module.exports = {
/**
* Delete an Existing Comment
*/
- async delete (obj, args, context) {
+ async deleteComment (obj, args, context) {
try {
await WIKI.models.comments.deleteComment({
id: args.id,
@@ -143,7 +137,7 @@ module.exports = {
/**
* Update Comments Providers
*/
- async updateProviders(obj, args, context) {
+ async updateCommentsProviders(obj, args, context) {
try {
for (let provider of args.providers) {
await WIKI.models.commentProviders.query().patch({
diff --git a/server/graph/resolvers/contribute.js b/server/graph/resolvers/contribute.js
deleted file mode 100644
index cfeeac04..00000000
--- a/server/graph/resolvers/contribute.js
+++ /dev/null
@@ -1,28 +0,0 @@
-const request = require('request-promise')
-const _ = require('lodash')
-
-/* global WIKI */
-
-module.exports = {
- Query: {
- async contribute() { return {} }
- },
- ContributeQuery: {
- async contributors(obj, args, context, info) {
- try {
- const resp = await request({
- method: 'POST',
- uri: 'https://graph.requarks.io',
- json: true,
- body: {
- query: '{\n sponsors {\n list(kind: BACKER) {\n id\n source\n name\n joined\n website\n twitter\n avatar\n }\n }\n}\n',
- variables: {}
- }
- })
- return _.get(resp, 'data.sponsors.list', [])
- } catch (err) {
- WIKI.logger.warn(err)
- }
- }
- }
-}
diff --git a/server/graph/resolvers/folder.js b/server/graph/resolvers/folder.js
deleted file mode 100644
index e7a6945f..00000000
--- a/server/graph/resolvers/folder.js
+++ /dev/null
@@ -1,32 +0,0 @@
-module.exports = {
- // Query: {
- // folders(obj, args, context, info) {
- // return WIKI.models.Folder.findAll({ where: args })
- // }
- // },
- // Mutation: {
- // createFolder(obj, args) {
- // return WIKI.models.Folder.create(args)
- // },
- // deleteFolder(obj, args) {
- // return WIKI.models.Folder.destroy({
- // where: {
- // id: args.id
- // },
- // limit: 1
- // })
- // },
- // renameFolder(obj, args) {
- // return WIKI.models.Folder.update({
- // name: args.name
- // }, {
- // where: { id: args.id }
- // })
- // }
- // },
- // Folder: {
- // files(grp) {
- // return grp.getFiles()
- // }
- // }
-}
diff --git a/server/graph/resolvers/group.js b/server/graph/resolvers/group.js
index 3fc7db23..5495624e 100644
--- a/server/graph/resolvers/group.js
+++ b/server/graph/resolvers/group.js
@@ -7,16 +7,10 @@ const gql = require('graphql')
module.exports = {
Query: {
- async groups () { return {} }
- },
- Mutation: {
- async groups () { return {} }
- },
- GroupQuery: {
/**
* FETCH ALL GROUPS
*/
- async list () {
+ async groups () {
return WIKI.models.groups.query().select(
'groups.*',
WIKI.models.groups.relatedQuery('users').count().as('userCount')
@@ -25,15 +19,15 @@ module.exports = {
/**
* FETCH A SINGLE GROUP
*/
- async single(obj, args) {
+ async groupById(obj, args) {
return WIKI.models.groups.query().findById(args.id)
}
},
- GroupMutation: {
+ Mutation: {
/**
* ASSIGN USER TO GROUP
*/
- async assignUser (obj, args, { req }) {
+ async assignUserToGroup (obj, args, { req }) {
// Check for guest user
if (args.userId === 2) {
throw new gql.GraphQLError('Cannot assign the Guest user to a group.')
@@ -85,7 +79,7 @@ module.exports = {
/**
* CREATE NEW GROUP
*/
- async create (obj, args, { req }) {
+ async createGroup (obj, args, { req }) {
const group = await WIKI.models.groups.query().insertAndFetch({
name: args.name,
permissions: JSON.stringify(WIKI.data.groups.defaultPermissions),
@@ -102,7 +96,7 @@ module.exports = {
/**
* DELETE GROUP
*/
- async delete (obj, args) {
+ async deleteGroup (obj, args) {
if (args.id === 1 || args.id === 2) {
throw new gql.GraphQLError('Cannot delete this group.')
}
@@ -122,7 +116,7 @@ module.exports = {
/**
* UNASSIGN USER FROM GROUP
*/
- async unassignUser (obj, args) {
+ async unassignUserFromGroup (obj, args) {
if (args.userId === 2) {
throw new gql.GraphQLError('Cannot unassign Guest user')
}
@@ -149,7 +143,7 @@ module.exports = {
/**
* UPDATE GROUP
*/
- async update (obj, args, { req }) {
+ async updateGroup (obj, args, { req }) {
// Check for unsafe regex page rules
if (_.some(args.pageRules, pr => {
return pr.match === 'REGEX' && !safeRegex(pr.path)
diff --git a/server/graph/resolvers/localization.js b/server/graph/resolvers/localization.js
index b49dfbc2..d0a6c70e 100644
--- a/server/graph/resolvers/localization.js
+++ b/server/graph/resolvers/localization.js
@@ -5,12 +5,6 @@ const _ = require('lodash')
module.exports = {
Query: {
- async localization() { return {} }
- },
- Mutation: {
- async localization() { return {} }
- },
- LocalizationQuery: {
async locales(obj, args, context, info) {
let remoteLocales = await WIKI.cache.get('locales')
let localLocales = await WIKI.models.locales.query().select('code', 'isRTL', 'name', 'nativeName', 'createdAt', 'updatedAt', 'availability')
@@ -24,19 +18,11 @@ module.exports = {
}
})
},
- async config(obj, args, context, info) {
- return {
- locale: WIKI.config.lang.code,
- autoUpdate: WIKI.config.lang.autoUpdate,
- namespacing: WIKI.config.lang.namespacing,
- namespaces: WIKI.config.lang.namespaces
- }
- },
translations (obj, args, context, info) {
return WIKI.lang.getByNamespace(args.locale, args.namespace)
}
},
- LocalizationMutation: {
+ Mutation: {
async downloadLocale(obj, args, context) {
try {
const job = await WIKI.scheduler.registerJob({
diff --git a/server/graph/resolvers/mail.js b/server/graph/resolvers/mail.js
index d7e87397..a9f18746 100644
--- a/server/graph/resolvers/mail.js
+++ b/server/graph/resolvers/mail.js
@@ -5,21 +5,15 @@ const graphHelper = require('../../helpers/graph')
module.exports = {
Query: {
- async mail() { return {} }
- },
- Mutation: {
- async mail() { return {} }
- },
- MailQuery: {
- async config(obj, args, context, info) {
+ async mailConfig(obj, args, context, info) {
return {
...WIKI.config.mail,
pass: WIKI.config.mail.pass.length > 0 ? '********' : ''
}
}
},
- MailMutation: {
- async sendTest(obj, args, context) {
+ Mutation: {
+ async sendMailTest(obj, args, context) {
try {
if (_.isEmpty(args.recipientEmail) || args.recipientEmail.length < 6) {
throw new WIKI.Error.MailInvalidRecipient()
@@ -42,7 +36,7 @@ module.exports = {
return graphHelper.generateError(err)
}
},
- async updateConfig(obj, args, context) {
+ async updateMailConfig(obj, args, context) {
try {
WIKI.config.mail = {
senderName: args.senderName,
diff --git a/server/graph/resolvers/navigation.js b/server/graph/resolvers/navigation.js
index 73988c11..a6c02d60 100644
--- a/server/graph/resolvers/navigation.js
+++ b/server/graph/resolvers/navigation.js
@@ -4,21 +4,15 @@ const graphHelper = require('../../helpers/graph')
module.exports = {
Query: {
- async navigation () { return {} }
- },
- Mutation: {
- async navigation () { return {} }
- },
- NavigationQuery: {
- async tree (obj, args, context, info) {
+ async navigationTree (obj, args, context, info) {
return WIKI.models.navigation.getTree({ cache: false, locale: 'all', bypassAuth: true })
},
- config (obj, args, context, info) {
+ navigationConfig (obj, args, context, info) {
return WIKI.config.nav
}
},
- NavigationMutation: {
- async updateTree (obj, args, context) {
+ Mutation: {
+ async updateNavigationTree (obj, args, context) {
try {
await WIKI.models.navigation.query().patch({
config: args.tree
@@ -34,7 +28,7 @@ module.exports = {
return graphHelper.generateError(err)
}
},
- async updateConfig (obj, args, context) {
+ async updateNavigationConfig (obj, args, context) {
try {
WIKI.config.nav = {
mode: args.mode
diff --git a/server/graph/resolvers/page.js b/server/graph/resolvers/page.js
index e356ac0f..aabb227b 100644
--- a/server/graph/resolvers/page.js
+++ b/server/graph/resolvers/page.js
@@ -5,16 +5,10 @@ const graphHelper = require('../../helpers/graph')
module.exports = {
Query: {
- async pages() { return {} }
- },
- Mutation: {
- async pages() { return {} }
- },
- PageQuery: {
/**
* PAGE HISTORY
*/
- async history(obj, args, context, info) {
+ async pageHistoryById (obj, args, context, info) {
const page = await WIKI.models.pages.query().select('path', 'localeCode').findById(args.id)
if (WIKI.auth.checkAccess(context.req.user, ['read:history'], {
path: page.path,
@@ -32,7 +26,7 @@ module.exports = {
/**
* PAGE VERSION
*/
- async version(obj, args, context, info) {
+ async pageVersionById (obj, args, context, info) {
const page = await WIKI.models.pages.query().select('path', 'localeCode').findById(args.pageId)
if (WIKI.auth.checkAccess(context.req.user, ['read:history'], {
path: page.path,
@@ -49,7 +43,7 @@ module.exports = {
/**
* SEARCH PAGES
*/
- async search (obj, args, context) {
+ async searchPages (obj, args, context) {
if (WIKI.data.searchEngine) {
const resp = await WIKI.data.searchEngine.query(args.query, args)
return {
@@ -73,7 +67,7 @@ module.exports = {
/**
* LIST PAGES
*/
- async list (obj, args, context, info) {
+ async pages (obj, args, context, info) {
let results = await WIKI.models.pages.query().column([
'pages.id',
'path',
@@ -149,7 +143,7 @@ module.exports = {
/**
* FETCH SINGLE PAGE
*/
- async single (obj, args, context, info) {
+ async pageById (obj, args, context, info) {
let page = await WIKI.models.pages.getPageFromDb(args.id)
if (page) {
if (WIKI.auth.checkAccess(context.req.user, ['manage:pages', 'delete:pages'], {
@@ -220,7 +214,7 @@ module.exports = {
/**
* FETCH PAGE TREE
*/
- async tree (obj, args, context, info) {
+ async pageTree (obj, args, context, info) {
let curPage = null
if (!args.locale) { args.locale = WIKI.config.lang.code }
@@ -270,7 +264,7 @@ module.exports = {
/**
* FETCH PAGE LINKS
*/
- async links (obj, args, context, info) {
+ async pageLinks (obj, args, context, info) {
let results
if (WIKI.config.db.type === 'mysql' || WIKI.config.db.type === 'mariadb' || WIKI.config.db.type === 'sqlite') {
@@ -343,7 +337,7 @@ module.exports = {
/**
* FETCH LATEST VERSION FOR CONFLICT COMPARISON
*/
- async conflictLatest (obj, args, context, info) {
+ async checkConflictsLatest (obj, args, context, info) {
let page = await WIKI.models.pages.getPageFromDb(args.id)
if (page) {
if (WIKI.auth.checkAccess(context.req.user, ['write:pages', 'manage:pages'], {
@@ -363,11 +357,11 @@ module.exports = {
}
}
},
- PageMutation: {
+ Mutation: {
/**
* CREATE PAGE
*/
- async create(obj, args, context) {
+ async createPage(obj, args, context) {
try {
const page = await WIKI.models.pages.createPage({
...args,
@@ -384,7 +378,7 @@ module.exports = {
/**
* UPDATE PAGE
*/
- async update(obj, args, context) {
+ async updatePage(obj, args, context) {
try {
const page = await WIKI.models.pages.updatePage({
...args,
@@ -401,7 +395,7 @@ module.exports = {
/**
* CONVERT PAGE
*/
- async convert(obj, args, context) {
+ async convertPage(obj, args, context) {
try {
await WIKI.models.pages.convertPage({
...args,
@@ -415,9 +409,9 @@ module.exports = {
}
},
/**
- * MOVE PAGE
+ * RENAME PAGE
*/
- async move(obj, args, context) {
+ async renamePage(obj, args, context) {
try {
await WIKI.models.pages.movePage({
...args,
@@ -433,7 +427,7 @@ module.exports = {
/**
* DELETE PAGE
*/
- async delete(obj, args, context) {
+ async deletePage(obj, args, context) {
try {
await WIKI.models.pages.deletePage({
...args,
@@ -517,7 +511,7 @@ module.exports = {
/**
* REBUILD TREE
*/
- async rebuildTree(obj, args, context) {
+ async rebuildPageTree(obj, args, context) {
try {
await WIKI.models.pages.rebuildTree()
return {
@@ -530,7 +524,7 @@ module.exports = {
/**
* RENDER PAGE
*/
- async render (obj, args, context) {
+ async renderPage (obj, args, context) {
try {
const page = await WIKI.models.pages.query().findById(args.id)
if (!page) {
@@ -547,7 +541,7 @@ module.exports = {
/**
* RESTORE PAGE VERSION
*/
- async restore (obj, args, context) {
+ async restorePage (obj, args, context) {
try {
const page = await WIKI.models.pages.query().select('path', 'localeCode').findById(args.pageId)
if (!page) {
@@ -583,7 +577,7 @@ module.exports = {
/**
* Purge history
*/
- async purgeHistory (obj, args, context) {
+ async purgePagesHistory (obj, args, context) {
try {
await WIKI.models.pageHistory.purge(args.olderThan)
return {
diff --git a/server/graph/resolvers/rendering.js b/server/graph/resolvers/rendering.js
index b9331032..c2e4ba15 100644
--- a/server/graph/resolvers/rendering.js
+++ b/server/graph/resolvers/rendering.js
@@ -5,12 +5,6 @@ const graphHelper = require('../../helpers/graph')
module.exports = {
Query: {
- async rendering() { return {} }
- },
- Mutation: {
- async rendering() { return {} }
- },
- RenderingQuery: {
async renderers(obj, args, context, info) {
let renderers = await WIKI.models.renderers.getRenderers()
renderers = renderers.map(rdr => {
@@ -37,7 +31,7 @@ module.exports = {
return renderers
}
},
- RenderingMutation: {
+ Mutation: {
async updateRenderers(obj, args, context) {
try {
for (let rdr of args.renderers) {
diff --git a/server/graph/resolvers/search.js b/server/graph/resolvers/search.js
index 58d8f918..f10c693f 100644
--- a/server/graph/resolvers/search.js
+++ b/server/graph/resolvers/search.js
@@ -1,74 +1,10 @@
-const _ = require('lodash')
const graphHelper = require('../../helpers/graph')
/* global WIKI */
module.exports = {
- Query: {
- async search() { return {} }
- },
Mutation: {
- async search() { return {} }
- },
- SearchQuery: {
- async searchEngines(obj, args, context, info) {
- let searchEngines = await WIKI.models.searchEngines.getSearchEngines()
- searchEngines = searchEngines.map(searchEngine => {
- const searchEngineInfo = _.find(WIKI.data.searchEngines, ['key', searchEngine.key]) || {}
- return {
- ...searchEngineInfo,
- ...searchEngine,
- config: _.sortBy(_.transform(searchEngine.config, (res, value, key) => {
- const configData = _.get(searchEngineInfo.props, key, false)
- if (configData) {
- res.push({
- key,
- value: JSON.stringify({
- ...configData,
- value
- })
- })
- }
- }, []), 'key')
- }
- })
- // if (args.filter) { searchEngines = graphHelper.filter(searchEngines, args.filter) }
- if (args.orderBy) { searchEngines = _.sortBy(searchEngines, [args.orderBy]) }
- return searchEngines
- }
- },
- SearchMutation: {
- async updateSearchEngines(obj, args, context) {
- try {
- let newActiveEngine = ''
- for (let searchEngine of args.engines) {
- if (searchEngine.isEnabled) {
- newActiveEngine = searchEngine.key
- }
- await WIKI.models.searchEngines.query().patch({
- isEnabled: searchEngine.isEnabled,
- config: _.reduce(searchEngine.config, (result, value, key) => {
- _.set(result, `${value.key}`, _.get(JSON.parse(value.value), 'v', null))
- return result
- }, {})
- }).where('key', searchEngine.key)
- }
- if (newActiveEngine !== WIKI.data.searchEngine.key) {
- try {
- await WIKI.data.searchEngine.deactivate()
- } catch (err) {
- WIKI.logger.warn('Failed to deactivate previous search engine:', err)
- }
- }
- await WIKI.models.searchEngines.initEngine({ activate: true })
- return {
- responseResult: graphHelper.generateSuccess('Search Engines updated successfully')
- }
- } catch (err) {
- return graphHelper.generateError(err)
- }
- },
- async rebuildIndex (obj, args, context) {
+ async rebuildSearchIndex (obj, args, context) {
try {
await WIKI.data.searchEngine.rebuild()
return {
diff --git a/server/graph/resolvers/site.js b/server/graph/resolvers/site.js
index 6a732cec..1c5d36b0 100644
--- a/server/graph/resolvers/site.js
+++ b/server/graph/resolvers/site.js
@@ -40,9 +40,7 @@ module.exports = {
hostname: site.hostname,
isEnabled: site.isEnabled
} : null
- },
- // LEGACY
- async site() { return {} }
+ }
},
Mutation: {
/**
@@ -178,118 +176,6 @@ module.exports = {
return {
status: graphHelper.generateSuccess('Site favicon uploaded successfully')
}
- },
- // LEGACY
- async site() { return {} }
- },
- SiteQuery: {
- async config(obj, args, context, info) {
- return {
- host: WIKI.config.host,
- title: WIKI.config.title,
- company: WIKI.config.company,
- contentLicense: WIKI.config.contentLicense,
- logoUrl: WIKI.config.logoUrl,
- ...WIKI.config.seo,
- ...WIKI.config.features,
- ...WIKI.config.security,
- authAutoLogin: WIKI.config.auth.autoLogin,
- authEnforce2FA: WIKI.config.auth.enforce2FA,
- authHideLocal: WIKI.config.auth.hideLocal,
- authLoginBgUrl: WIKI.config.auth.loginBgUrl,
- authJwtAudience: WIKI.config.auth.audience,
- authJwtExpiration: WIKI.config.auth.tokenExpiration,
- authJwtRenewablePeriod: WIKI.config.auth.tokenRenewal,
- uploadMaxFileSize: WIKI.config.uploads.maxFileSize,
- uploadMaxFiles: WIKI.config.uploads.maxFiles,
- uploadScanSVG: WIKI.config.uploads.scanSVG,
- uploadForceDownload: WIKI.config.uploads.forceDownload
- }
- }
- },
- SiteMutation: {
- async updateConfig(obj, args, context) {
- try {
- if (args.host) {
- let siteHost = _.trim(args.host)
- if (siteHost.endsWith('/')) {
- siteHost = siteHost.slice(0, -1)
- }
- WIKI.config.host = siteHost
- }
-
- if (args.title) {
- WIKI.config.title = _.trim(args.title)
- }
-
- if (args.company) {
- WIKI.config.company = _.trim(args.company)
- }
-
- if (args.contentLicense) {
- WIKI.config.contentLicense = args.contentLicense
- }
-
- if (args.logoUrl) {
- WIKI.config.logoUrl = _.trim(args.logoUrl)
- }
-
- WIKI.config.seo = {
- description: _.get(args, 'description', WIKI.config.seo.description),
- robots: _.get(args, 'robots', WIKI.config.seo.robots),
- analyticsService: _.get(args, 'analyticsService', WIKI.config.seo.analyticsService),
- analyticsId: _.get(args, 'analyticsId', WIKI.config.seo.analyticsId)
- }
-
- WIKI.config.auth = {
- autoLogin: _.get(args, 'authAutoLogin', WIKI.config.auth.autoLogin),
- enforce2FA: _.get(args, 'authEnforce2FA', WIKI.config.auth.enforce2FA),
- hideLocal: _.get(args, 'authHideLocal', WIKI.config.auth.hideLocal),
- loginBgUrl: _.get(args, 'authLoginBgUrl', WIKI.config.auth.loginBgUrl),
- audience: _.get(args, 'authJwtAudience', WIKI.config.auth.audience),
- tokenExpiration: _.get(args, 'authJwtExpiration', WIKI.config.auth.tokenExpiration),
- tokenRenewal: _.get(args, 'authJwtRenewablePeriod', WIKI.config.auth.tokenRenewal)
- }
-
- WIKI.config.features = {
- featurePageRatings: _.get(args, 'featurePageRatings', WIKI.config.features.featurePageRatings),
- featurePageComments: _.get(args, 'featurePageComments', WIKI.config.features.featurePageComments),
- featurePersonalWikis: _.get(args, 'featurePersonalWikis', WIKI.config.features.featurePersonalWikis)
- }
-
- WIKI.config.security = {
- securityOpenRedirect: _.get(args, 'securityOpenRedirect', WIKI.config.security.securityOpenRedirect),
- securityIframe: _.get(args, 'securityIframe', WIKI.config.security.securityIframe),
- securityReferrerPolicy: _.get(args, 'securityReferrerPolicy', WIKI.config.security.securityReferrerPolicy),
- securityTrustProxy: _.get(args, 'securityTrustProxy', WIKI.config.security.securityTrustProxy),
- securitySRI: _.get(args, 'securitySRI', WIKI.config.security.securitySRI),
- securityHSTS: _.get(args, 'securityHSTS', WIKI.config.security.securityHSTS),
- securityHSTSDuration: _.get(args, 'securityHSTSDuration', WIKI.config.security.securityHSTSDuration),
- securityCSP: _.get(args, 'securityCSP', WIKI.config.security.securityCSP),
- securityCSPDirectives: _.get(args, 'securityCSPDirectives', WIKI.config.security.securityCSPDirectives)
- }
-
- WIKI.config.uploads = {
- maxFileSize: _.get(args, 'uploadMaxFileSize', WIKI.config.uploads.maxFileSize),
- maxFiles: _.get(args, 'uploadMaxFiles', WIKI.config.uploads.maxFiles),
- scanSVG: _.get(args, 'uploadScanSVG', WIKI.config.uploads.scanSVG),
- forceDownload: _.get(args, 'uploadForceDownload', WIKI.config.uploads.forceDownload)
- }
-
- await WIKI.configSvc.saveToDb(['host', 'title', 'company', 'contentLicense', 'seo', 'logoUrl', 'auth', 'features', 'security', 'uploads'])
-
- if (WIKI.config.security.securityTrustProxy) {
- WIKI.app.enable('trust proxy')
- } else {
- WIKI.app.disable('trust proxy')
- }
-
- return {
- responseResult: graphHelper.generateSuccess('Site configuration updated successfully')
- }
- } catch (err) {
- return graphHelper.generateError(err)
- }
}
}
}
diff --git a/server/graph/resolvers/storage.js b/server/graph/resolvers/storage.js
index b9de96a5..1f659835 100644
--- a/server/graph/resolvers/storage.js
+++ b/server/graph/resolvers/storage.js
@@ -1,97 +1,227 @@
const _ = require('lodash')
const graphHelper = require('../../helpers/graph')
+const { v4: uuid } = require('uuid')
/* global WIKI */
module.exports = {
Query: {
- async storage() { return {} }
- },
- Mutation: {
- async storage() { return {} }
- },
- StorageQuery: {
- async targets(obj, args, context, info) {
- let targets = await WIKI.models.storage.getTargets()
- targets = _.sortBy(targets.map(tgt => {
- const targetInfo = _.find(WIKI.data.storage, ['key', tgt.key]) || {}
+ async storageTargets (obj, args, context, info) {
+ const dbTargets = await WIKI.models.storage.getTargets({ siteId: args.siteId })
+ // targets = _.sortBy(targets.map(tgt => {
+ // const targetInfo = _.find(WIKI.data.storage, ['module', tgt.key]) || {}
+ // return {
+ // ...targetInfo,
+ // ...tgt,
+ // hasSchedule: (targetInfo.schedule !== false),
+ // syncInterval: targetInfo.syncInterval || targetInfo.schedule || 'P0D',
+ // syncIntervalDefault: targetInfo.schedule,
+ // config: _.sortBy(_.transform(tgt.config, (res, value, key) => {
+ // const configData = _.get(targetInfo.props, key, false)
+ // if (configData) {
+ // res.push({
+ // key,
+ // value: JSON.stringify({
+ // ...configData,
+ // value: (configData.sensitive && value.length > 0) ? '********' : value
+ // })
+ // })
+ // }
+ // }, []), 'key')
+ // }
+ // }), ['title', 'key'])
+ return _.sortBy(WIKI.storage.defs.map(md => {
+ const dbTarget = dbTargets.find(tg => tg.module === md.key)
return {
- ...targetInfo,
- ...tgt,
- hasSchedule: (targetInfo.schedule !== false),
- syncInterval: tgt.syncInterval || targetInfo.schedule || 'P0D',
- syncIntervalDefault: targetInfo.schedule,
- config: _.sortBy(_.transform(tgt.config, (res, value, key) => {
- const configData = _.get(targetInfo.props, key, false)
- if (configData) {
- res.push({
- key,
- value: JSON.stringify({
- ...configData,
- value: (configData.sensitive && value.length > 0) ? '********' : value
+ id: dbTarget?.id ?? uuid(),
+ isEnabled: dbTarget?.isEnabled ?? false,
+ module: md.key,
+ title: md.title,
+ description: md.description,
+ icon: md.icon,
+ banner: md.banner,
+ vendor: md.vendor,
+ website: md.website,
+ contentTypes: {
+ activeTypes: dbTarget?.contentTypes?.activeTypes ?? md.contentTypes.defaultTypesEnabled,
+ largeThreshold: dbTarget?.contentTypes?.largeThreshold ?? md.contentTypes.defaultLargeThreshold
+ },
+ assetDelivery: {
+ isStreamingSupported: md?.assetDelivery?.isStreamingSupported ?? false,
+ isDirectAccessSupported: md?.assetDelivery?.isDirectAccessSupported ?? false,
+ streaming: dbTarget?.assetDelivery?.streaming ?? md?.assetDelivery?.defaultStreamingEnabled ?? false,
+ directAccess: dbTarget?.assetDelivery?.directAccess ?? md?.assetDelivery?.defaultDirectAccessEnabled ?? false
+ },
+ versioning: {
+ isSupported: md?.versioning?.isSupported ?? false,
+ isForceEnabled: md?.versioning?.isForceEnabled ?? false,
+ enabled: dbTarget?.versioning?.enabled ?? md?.versioning?.defaultEnabled ?? false
+ },
+ sync: {},
+ status: {},
+ setup: {
+ handler: md?.setup?.handler,
+ state: dbTarget?.state?.setup ?? 'notconfigured',
+ values: md.setup?.handler
+ ? _.transform(md.setup.defaultValues,
+ (r, v, k) => {
+ r[k] = dbTarget?.config?.[k] ?? v
+ }, {})
+ : {}
+ },
+ config: _.transform(md.props, (r, v, k) => {
+ const cfValue = dbTarget?.config?.[k] ?? v.default
+ r[k] = {
+ ...v,
+ value: v.sensitive && cfValue ? '********' : cfValue,
+ ...v.enum && {
+ enum: v.enum.map(o => {
+ if (o.indexOf('|') > 0) {
+ const oParsed = o.split('|')
+ return {
+ value: oParsed[0],
+ label: oParsed[1]
+ }
+ } else {
+ return {
+ value: o,
+ label: o
+ }
+ }
})
- })
+ }
}
- }, []), 'key')
+ }, {}),
+ actions: md.actions
}
- }), ['title', 'key'])
- return targets
- },
- async status(obj, args, context, info) {
- let activeTargets = await WIKI.models.storage.query().where('isEnabled', true)
- return activeTargets.map(tgt => {
- const targetInfo = _.find(WIKI.data.storage, ['key', tgt.key]) || {}
- return {
- key: tgt.key,
- title: targetInfo.title,
- status: _.get(tgt, 'state.status', 'pending'),
- message: _.get(tgt, 'state.message', 'Initializing...'),
- lastAttempt: _.get(tgt, 'state.lastAttempt', null)
- }
- })
+ }), ['title'])
}
},
- StorageMutation: {
- async updateTargets(obj, args, context) {
+ Mutation: {
+ async updateStorageTargets (obj, args, context) {
+ WIKI.logger.debug(`Updating storage targets for site ${args.siteId}...`)
try {
- let dbTargets = await WIKI.models.storage.getTargets()
- for (let tgt of args.targets) {
- const currentDbTarget = _.find(dbTargets, ['key', tgt.key])
- if (!currentDbTarget) {
- continue
+ const dbTargets = await WIKI.models.storage.getTargets({ siteId: args.siteId })
+ for (const tgt of args.targets) {
+ const md = _.find(WIKI.storage.defs, ['key', tgt.module])
+ if (!md) {
+ throw new Error('Invalid module key for non-existent storage target.')
}
- await WIKI.models.storage.query().patch({
- isEnabled: tgt.isEnabled,
- mode: tgt.mode,
- syncInterval: tgt.syncInterval,
- config: _.reduce(tgt.config, (result, value, key) => {
- let configValue = _.get(JSON.parse(value.value), 'v', null)
- if (configValue === '********') {
- configValue = _.get(currentDbTarget.config, value.key, '')
- }
- _.set(result, `${value.key}`, configValue)
- return result
- }, {}),
- state: {
- status: 'pending',
- message: 'Initializing...',
- lastAttempt: null
+
+ const dbTarget = _.find(dbTargets, ['id', tgt.id])
+
+ // -> Build update config object
+ const updatedConfig = dbTarget?.config ?? {}
+ if (tgt.config) {
+ for (const [key, prop] of Object.entries(md.props)) {
+ if (prop.readOnly) { continue }
+ if (!Object.prototype.hasOwnProperty.call(tgt.config, key)) { continue }
+ if (prop.sensitive && tgt.config[key] === '********') { continue }
+ updatedConfig[key] = tgt.config[key]
}
- }).where('key', tgt.key)
+ }
+
+ // -> Target doesn't exist yet in the DB, let's create it
+ if (!dbTarget) {
+ WIKI.logger.debug(`No existing DB configuration for module ${tgt.module}. Creating a new one...`)
+ await WIKI.models.storage.query().insert({
+ id: tgt.id,
+ module: tgt.module,
+ siteId: args.siteId,
+ isEnabled: tgt.isEnabled ?? false,
+ contentTypes: {
+ activeTypes: tgt.contentTypes ?? md.contentTypes.defaultTypesEnabled ?? [],
+ largeThreshold: tgt.largeThreshold ?? md.contentTypes.defaultLargeThreshold ?? '5MB'
+ },
+ assetDelivery: {
+ streaming: tgt.assetDeliveryFileStreaming ?? md?.assetDelivery?.defaultStreamingEnabled ?? false,
+ directAccess: tgt.assetDeliveryDirectAccess ?? md?.assetDelivery?.defaultDirectAccessEnabled ?? false
+ },
+ versioning: {
+ enabled: tgt.useVersioning ?? md?.versioning?.defaultEnabled ?? false
+ },
+ state: {
+ current: 'ok'
+ },
+ config: updatedConfig
+ })
+ } else {
+ WIKI.logger.debug(`Updating DB configuration for module ${tgt.module}...`)
+ await WIKI.models.storage.query().patch({
+ isEnabled: tgt.isEnabled ?? dbTarget.isEnabled ?? false,
+ contentTypes: {
+ activeTypes: tgt.contentTypes ?? dbTarget?.contentTypes?.activeTypes ?? [],
+ largeThreshold: tgt.largeThreshold ?? dbTarget?.contentTypes?.largeThreshold ?? '5MB'
+ },
+ assetDelivery: {
+ streaming: tgt.assetDeliveryFileStreaming ?? dbTarget?.assetDelivery?.streaming ?? false,
+ directAccess: tgt.assetDeliveryDirectAccess ?? dbTarget?.assetDelivery?.directAccess ?? false
+ },
+ versioning: {
+ enabled: tgt.useVersioning ?? dbTarget?.versioning?.enabled ?? false
+ },
+ config: updatedConfig
+ }).where('id', tgt.id)
+ }
}
- await WIKI.models.storage.initTargets()
+ // await WIKI.models.storage.initTargets()
return {
- responseResult: graphHelper.generateSuccess('Storage targets updated successfully')
+ status: graphHelper.generateSuccess('Storage targets updated successfully')
}
} catch (err) {
return graphHelper.generateError(err)
}
},
- async executeAction(obj, args, context) {
+ async setupStorageTarget (obj, args, context) {
+ try {
+ const tgt = await WIKI.models.storage.query().findById(args.targetId)
+ if (!tgt) {
+ throw new Error('Not storage target matching this ID')
+ }
+ const md = _.find(WIKI.storage.defs, ['key', tgt.module])
+ if (!md) {
+ throw new Error('No matching storage module installed.')
+ }
+ if (!await WIKI.models.storage.ensureModule(md.key)) {
+ throw new Error('Failed to load storage module. Check logs for details.')
+ }
+ const result = await WIKI.storage.modules[md.key].setup(args.targetId, args.state)
+
+ return {
+ status: graphHelper.generateSuccess('Storage target setup step succeeded'),
+ state: result
+ }
+ } catch (err) {
+ return graphHelper.generateError(err)
+ }
+ },
+ async destroyStorageTargetSetup (obj, args, context) {
+ try {
+ const tgt = await WIKI.models.storage.query().findById(args.targetId)
+ if (!tgt) {
+ throw new Error('Not storage target matching this ID')
+ }
+ const md = _.find(WIKI.storage.defs, ['key', tgt.module])
+ if (!md) {
+ throw new Error('No matching storage module installed.')
+ }
+ if (!await WIKI.models.storage.ensureModule(md.key)) {
+ throw new Error('Failed to load storage module. Check logs for details.')
+ }
+ await WIKI.storage.modules[md.key].setupDestroy(args.targetId)
+
+ return {
+ status: graphHelper.generateSuccess('Storage target setup configuration destroyed succesfully.')
+ }
+ } catch (err) {
+ return graphHelper.generateError(err)
+ }
+ },
+ async executeStorageAction (obj, args, context) {
try {
await WIKI.models.storage.executeAction(args.targetKey, args.handler)
return {
- responseResult: graphHelper.generateSuccess('Action completed.')
+ status: graphHelper.generateSuccess('Action completed.')
}
} catch (err) {
return graphHelper.generateError(err)
diff --git a/server/graph/resolvers/system.js b/server/graph/resolvers/system.js
index a57eb728..3b3a120a 100644
--- a/server/graph/resolvers/system.js
+++ b/server/graph/resolvers/system.js
@@ -5,257 +5,55 @@ const os = require('os')
const filesize = require('filesize')
const path = require('path')
const fs = require('fs-extra')
-const moment = require('moment')
+const { DateTime } = require('luxon')
const graphHelper = require('../../helpers/graph')
-const request = require('request-promise')
-const crypto = require('crypto')
-const nanoid = require('nanoid/non-secure').customAlphabet('1234567890abcdef', 10)
/* global WIKI */
-const dbTypes = {
- mysql: 'MySQL',
- mariadb: 'MariaDB',
- postgres: 'PostgreSQL',
- sqlite: 'SQLite',
- mssql: 'MS SQL Server'
-}
-
module.exports = {
Query: {
- async system () { return {} }
- },
- Mutation: {
- async system () { return {} }
- },
- SystemQuery: {
- flags () {
+ systemFlags () {
return _.transform(WIKI.config.flags, (result, value, key) => {
result.push({ key, value })
}, [])
},
- async info () { return {} },
- async extensions () {
- const exts = Object.values(WIKI.extensions.ext).map(ext => _.pick(ext, ['key', 'title', 'description', 'isInstalled']))
- for (let ext of exts) {
+ async systemInfo () { return {} },
+ async systemExtensions () {
+ const exts = Object.values(WIKI.extensions.ext).map(ext => _.pick(ext, ['key', 'title', 'description', 'isInstalled', 'isInstallable']))
+ for (const ext of exts) {
ext.isCompatible = await WIKI.extensions.ext[ext.key].isCompatible()
}
return exts
+ },
+ systemSecurity () {
+ return WIKI.config.security
}
},
- SystemMutation: {
- async updateFlags (obj, args, context) {
+ Mutation: {
+ async updateSystemFlags (obj, args, context) {
WIKI.config.flags = _.transform(args.flags, (result, row) => {
_.set(result, row.key, row.value)
}, {})
await WIKI.configSvc.applyFlags()
await WIKI.configSvc.saveToDb(['flags'])
return {
- responseResult: graphHelper.generateSuccess('System Flags applied successfully')
+ status: graphHelper.generateSuccess('System Flags applied successfully')
}
},
- async resetTelemetryClientId (obj, args, context) {
- try {
- WIKI.telemetry.generateClientId()
- await WIKI.configSvc.saveToDb(['telemetry'])
- return {
- responseResult: graphHelper.generateSuccess('Telemetry state updated successfully')
- }
- } catch (err) {
- return graphHelper.generateError(err)
- }
- },
- async setTelemetry (obj, args, context) {
- try {
- _.set(WIKI.config, 'telemetry.isEnabled', args.enabled)
- WIKI.telemetry.enabled = args.enabled
- await WIKI.configSvc.saveToDb(['telemetry'])
- return {
- responseResult: graphHelper.generateSuccess('Telemetry Client ID has been reset successfully')
- }
- } catch (err) {
- return graphHelper.generateError(err)
- }
- },
- async performUpgrade (obj, args, context) {
- try {
- if (process.env.UPGRADE_COMPANION) {
- await request({
- method: 'POST',
- uri: 'http://wiki-update-companion/upgrade'
- })
- return {
- responseResult: graphHelper.generateSuccess('Upgrade has started.')
- }
- } else {
- throw new Error('You must run the wiki-update-companion container and pass the UPGRADE_COMPANION env var in order to use this feature.')
- }
- } catch (err) {
- return graphHelper.generateError(err)
- }
- },
- /**
- * Import Users from a v1 installation
- */
- async importUsersFromV1(obj, args, context) {
- try {
- const MongoClient = require('mongodb').MongoClient
- if (args.mongoDbConnString && args.mongoDbConnString.length > 10) {
- // -> Connect to DB
-
- const client = await MongoClient.connect(args.mongoDbConnString, {
- appname: `Wiki.js ${WIKI.version} Migration Tool`
- })
- const dbUsers = client.db().collection('users')
- const userCursor = dbUsers.find({ email: { '$ne': 'guest' } })
-
- const curDateISO = new Date().toISOString()
-
- let failed = []
- let usersCount = 0
- let groupsCount = 0
- let assignableGroups = []
- let reuseGroups = []
-
- // -> Create SINGLE group
-
- if (args.groupMode === `SINGLE`) {
- const singleGroup = await WIKI.models.groups.query().insert({
- name: `Import_${curDateISO}`,
- permissions: JSON.stringify(WIKI.data.groups.defaultPermissions),
- pageRules: JSON.stringify(WIKI.data.groups.defaultPageRules)
- })
- groupsCount++
- assignableGroups.push(singleGroup.id)
- }
-
- // -> Iterate all users
-
- while (await userCursor.hasNext()) {
- const usr = await userCursor.next()
-
- let usrGroup = []
- if (args.groupMode === `MULTI`) {
- // -> Check if global admin
-
- if (_.some(usr.rights, ['role', 'admin'])) {
- usrGroup.push(1)
- } else {
- // -> Check if identical group already exists
-
- const currentRights = _.sortBy(_.map(usr.rights, r => _.pick(r, ['role', 'path', 'exact', 'deny'])), ['role', 'path', 'exact', 'deny'])
- const ruleSetId = crypto.createHash('sha1').update(JSON.stringify(currentRights)).digest('base64')
- const existingGroup = _.find(reuseGroups, ['hash', ruleSetId])
- if (existingGroup) {
- usrGroup.push(existingGroup.groupId)
- } else {
- // -> Build new group
-
- const pageRules = _.map(usr.rights, r => {
- let roles = ['read:pages', 'read:assets', 'read:comments', 'write:comments']
- if (r.role === `write`) {
- roles = _.concat(roles, ['write:pages', 'manage:pages', 'read:source', 'read:history', 'write:assets', 'manage:assets'])
- }
- return {
- id: nanoid(),
- roles: roles,
- match: r.exact ? 'EXACT' : 'START',
- deny: r.deny,
- path: (r.path.indexOf('/') === 0) ? r.path.substring(1) : r.path,
- locales: []
- }
- })
-
- const perms = _.chain(pageRules).reject('deny').map('roles').union().flatten().value()
-
- // -> Create new group
-
- const newGroup = await WIKI.models.groups.query().insert({
- name: `Import_${curDateISO}_${groupsCount + 1}`,
- permissions: JSON.stringify(perms),
- pageRules: JSON.stringify(pageRules)
- })
- reuseGroups.push({
- groupId: newGroup.id,
- hash: ruleSetId
- })
- groupsCount++
- usrGroup.push(newGroup.id)
- }
- }
- }
-
- // -> Create User
-
- try {
- await WIKI.models.users.createNewUser({
- providerKey: usr.provider,
- email: usr.email,
- name: usr.name,
- passwordRaw: usr.password,
- groups: (usrGroup.length > 0) ? usrGroup : assignableGroups,
- mustChangePassword: false,
- sendWelcomeEmail: false
- })
- usersCount++
- } catch (err) {
- failed.push({
- provider: usr.provider,
- email: usr.email,
- error: err.message
- })
- WIKI.logger.warn(`${usr.email}: ${err}`)
- }
- }
-
- // -> Reload group permissions
-
- if (args.groupMode !== `NONE`) {
- await WIKI.auth.reloadGroups()
- WIKI.events.outbound.emit('reloadGroups')
- }
-
- client.close()
- return {
- responseResult: graphHelper.generateSuccess('Import completed.'),
- usersCount: usersCount,
- groupsCount: groupsCount,
- failed: failed
- }
- } else {
- throw new Error('MongoDB Connection String is missing or invalid.')
- }
- } catch (err) {
- return graphHelper.generateError(err)
- }
- },
- /**
- * Set HTTPS Redirection State
- */
- async setHTTPSRedirection (obj, args, context) {
- _.set(WIKI.config, 'server.sslRedir', args.enabled)
- await WIKI.configSvc.saveToDb(['server'])
+ async updateSystemSecurity (obj, args, context) {
+ WIKI.config.security = _.defaultsDeep(_.omit(args, ['__typename']), WIKI.config.security)
+ // TODO: broadcast config update
+ await WIKI.configSvc.saveToDb(['security'])
return {
- responseResult: graphHelper.generateSuccess('HTTP Redirection state set successfully.')
+ status: graphHelper.generateSuccess('System Security configuration applied successfully')
}
},
- /**
- * Renew SSL Certificate
- */
- async renewHTTPSCertificate (obj, args, context) {
+ async installExtension (obj, args, context) {
try {
- if (!WIKI.config.ssl.enabled) {
- throw new WIKI.Error.SystemSSLDisabled()
- } else if (WIKI.config.ssl.provider !== `letsencrypt`) {
- throw new WIKI.Error.SystemSSLRenewInvalidProvider()
- } else if (!WIKI.servers.le) {
- throw new WIKI.Error.SystemSSLLEUnavailable()
- } else {
- await WIKI.servers.le.requestCertificate()
- await WIKI.servers.restartServer('https')
- return {
- responseResult: graphHelper.generateSuccess('SSL Certificate renewed successfully.')
- }
+ await WIKI.extensions.ext[args.key].install()
+ // TODO: broadcast ext install
+ return {
+ status: graphHelper.generateSuccess('Extension installed successfully')
}
} catch (err) {
return graphHelper.generateError(err)
@@ -272,36 +70,11 @@ module.exports = {
currentVersion () {
return WIKI.version
},
- dbType () {
- return _.get(dbTypes, WIKI.config.db.type, 'Unknown DB')
- },
async dbVersion () {
- let version = 'Unknown Version'
- switch (WIKI.config.db.type) {
- case 'mariadb':
- case 'mysql':
- const resultMYSQL = await WIKI.models.knex.raw('SELECT VERSION() as version;')
- version = _.get(resultMYSQL, '[0][0].version', 'Unknown Version')
- break
- case 'mssql':
- const resultMSSQL = await WIKI.models.knex.raw('SELECT @@VERSION as version;')
- version = _.get(resultMSSQL, '[0].version', 'Unknown Version')
- break
- case 'postgres':
- version = _.get(WIKI.models, 'knex.client.version', 'Unknown Version')
- break
- case 'sqlite':
- version = _.get(WIKI.models, 'knex.client.driver.VERSION', 'Unknown Version')
- break
- }
- return version
+ return _.get(WIKI.models, 'knex.client.version', 'Unknown Version')
},
dbHost () {
- if (WIKI.config.db.type === 'sqlite') {
- return WIKI.config.db.storage
- } else {
- return WIKI.config.db.host
- }
+ return WIKI.config.db.host
},
hostname () {
return os.hostname()
@@ -319,7 +92,7 @@ module.exports = {
return WIKI.system.updates.version
},
latestVersionReleaseDate () {
- return moment.utc(WIKI.system.updates.releaseDate)
+ return DateTime.fromISO(WIKI.system.updates.releaseDate).toJSDate()
},
nodeVersion () {
return process.version.substr(1)
@@ -343,10 +116,10 @@ module.exports = {
return filesize(os.totalmem())
},
sslDomain () {
- return WIKI.config.ssl.enabled && WIKI.config.ssl.provider === `letsencrypt` ? WIKI.config.ssl.domain : null
+ return WIKI.config.ssl.enabled && WIKI.config.ssl.provider === 'letsencrypt' ? WIKI.config.ssl.domain : null
},
sslExpirationDate () {
- return WIKI.config.ssl.enabled && WIKI.config.ssl.provider === `letsencrypt` ? _.get(WIKI.config.letsencrypt, 'payload.expires', null) : null
+ return WIKI.config.ssl.enabled && WIKI.config.ssl.provider === 'letsencrypt' ? _.get(WIKI.config.letsencrypt, 'payload.expires', null) : null
},
sslProvider () {
return WIKI.config.ssl.enabled ? WIKI.config.ssl.provider : null
@@ -355,7 +128,7 @@ module.exports = {
return 'OK'
},
sslSubscriberEmail () {
- return WIKI.config.ssl.enabled && WIKI.config.ssl.provider === `letsencrypt` ? WIKI.config.ssl.subscriberEmail : null
+ return WIKI.config.ssl.enabled && WIKI.config.ssl.provider === 'letsencrypt' ? WIKI.config.ssl.subscriberEmail : null
},
telemetry () {
return WIKI.telemetry.enabled
diff --git a/server/graph/resolvers/tag.js b/server/graph/resolvers/tag.js
deleted file mode 100644
index 09a4b790..00000000
--- a/server/graph/resolvers/tag.js
+++ /dev/null
@@ -1,58 +0,0 @@
-module.exports = {
- // Query: {
- // tags(obj, args, context, info) {
- // return WIKI.models.Tag.findAll({ where: args })
- // }
- // },
- // Mutation: {
- // assignTagToDocument(obj, args) {
- // return WIKI.models.Tag.findById(args.tagId).then(tag => {
- // if (!tag) {
- // throw new gql.GraphQLError('Invalid Tag ID')
- // }
- // return WIKI.models.Document.findById(args.documentId).then(doc => {
- // if (!doc) {
- // throw new gql.GraphQLError('Invalid Document ID')
- // }
- // return tag.addDocument(doc)
- // })
- // })
- // },
- // createTag(obj, args) {
- // return WIKI.models.Tag.create(args)
- // },
- // deleteTag(obj, args) {
- // return WIKI.models.Tag.destroy({
- // where: {
- // id: args.id
- // },
- // limit: 1
- // })
- // },
- // removeTagFromDocument(obj, args) {
- // return WIKI.models.Tag.findById(args.tagId).then(tag => {
- // if (!tag) {
- // throw new gql.GraphQLError('Invalid Tag ID')
- // }
- // return WIKI.models.Document.findById(args.documentId).then(doc => {
- // if (!doc) {
- // throw new gql.GraphQLError('Invalid Document ID')
- // }
- // return tag.removeDocument(doc)
- // })
- // })
- // },
- // renameTag(obj, args) {
- // return WIKI.models.Group.update({
- // key: args.key
- // }, {
- // where: { id: args.id }
- // })
- // }
- // },
- // Tag: {
- // documents(tag) {
- // return tag.getDocuments()
- // }
- // }
-}
diff --git a/server/graph/resolvers/theming.js b/server/graph/resolvers/theming.js
deleted file mode 100644
index cfd63670..00000000
--- a/server/graph/resolvers/theming.js
+++ /dev/null
@@ -1,62 +0,0 @@
-const graphHelper = require('../../helpers/graph')
-const _ = require('lodash')
-const CleanCSS = require('clean-css')
-
-/* global WIKI */
-
-module.exports = {
- Query: {
- async theming() { return {} }
- },
- Mutation: {
- async theming() { return {} }
- },
- ThemingQuery: {
- async themes(obj, args, context, info) {
- return [{ // TODO
- key: 'default',
- title: 'Default',
- author: 'requarks.io'
- }]
- },
- async config(obj, args, context, info) {
- return {
- theme: WIKI.config.theming.theme,
- iconset: WIKI.config.theming.iconset,
- darkMode: WIKI.config.theming.darkMode,
- injectCSS: new CleanCSS({ format: 'beautify' }).minify(WIKI.config.theming.injectCSS).styles,
- injectHead: WIKI.config.theming.injectHead,
- injectBody: WIKI.config.theming.injectBody
- }
- }
- },
- ThemingMutation: {
- async setConfig(obj, args, context, info) {
- try {
- if (!_.isEmpty(args.injectCSS)) {
- args.injectCSS = new CleanCSS({
- inline: false
- }).minify(args.injectCSS).styles
- }
-
- WIKI.config.theming = {
- ...WIKI.config.theming,
- theme: args.theme,
- iconset: args.iconset,
- darkMode: args.darkMode,
- injectCSS: args.injectCSS || '',
- injectHead: args.injectHead || '',
- injectBody: args.injectBody || ''
- }
-
- await WIKI.configSvc.saveToDb(['theming'])
-
- return {
- responseResult: graphHelper.generateSuccess('Theme config updated')
- }
- } catch (err) {
- return graphHelper.generateError(err)
- }
- }
- }
-}
diff --git a/server/graph/resolvers/user.js b/server/graph/resolvers/user.js
index a94aef8a..f00f2d12 100644
--- a/server/graph/resolvers/user.js
+++ b/server/graph/resolvers/user.js
@@ -5,32 +5,54 @@ const _ = require('lodash')
module.exports = {
Query: {
- async users() { return {} }
- },
- Mutation: {
- async users() { return {} }
- },
- UserQuery: {
- async list(obj, args, context, info) {
- return WIKI.models.users.query()
- .select('id', 'email', 'name', 'providerKey', 'isSystem', 'isActive', 'createdAt', 'lastLoginAt')
- },
- async search(obj, args, context, info) {
- return WIKI.models.users.query()
- .where('email', 'like', `%${args.query}%`)
- .orWhere('name', 'like', `%${args.query}%`)
- .limit(10)
- .select('id', 'email', 'name', 'providerKey', 'createdAt')
- },
- async single(obj, args, context, info) {
- let usr = await WIKI.models.users.query().findById(args.id)
- usr.password = ''
- usr.tfaSecret = ''
+ /**
+ * FETCH ALL USERS
+ */
+ async users (obj, args, context, info) {
+ // -> Sanitize limit
+ let limit = args.pageSize ?? 20
+ if (limit < 1 || limit > 1000) {
+ limit = 1000
+ }
- const str = _.get(WIKI.auth.strategies, usr.providerKey)
- str.strategy = _.find(WIKI.data.authentication, ['key', str.strategyKey])
- usr.providerName = str.displayName
- usr.providerIs2FACapable = _.get(str, 'strategy.useForm', false)
+ // -> Sanitize offset
+ let offset = args.page ?? 1
+ if (offset < 1) {
+ offset = 1
+ }
+
+ // -> Fetch Users
+ return WIKI.models.users.query()
+ .select('id', 'email', 'name', 'isSystem', 'isActive', 'createdAt', 'lastLoginAt')
+ .where(builder => {
+ if (args.filter) {
+ builder.where('email', 'like', `%${args.filter}%`)
+ .orWhere('name', 'like', `%${args.filter}%`)
+ }
+ })
+ .orderBy(args.orderBy ?? 'name', args.orderByDirection ?? 'asc')
+ .offset((offset - 1) * limit)
+ .limit(limit)
+ },
+ /**
+ * FETCH A SINGLE USER
+ */
+ async userById (obj, args, context, info) {
+ const usr = await WIKI.models.users.query().findById(args.id)
+
+ // const str = _.get(WIKI.auth.strategies, usr.providerKey)
+ // str.strategy = _.find(WIKI.data.authentication, ['key', str.strategyKey])
+ // usr.providerName = str.displayName
+ // usr.providerIs2FACapable = _.get(str, 'strategy.useForm', false)
+
+ usr.auth = _.mapValues(usr.auth, (auth, providerKey) => {
+ if (auth.password) {
+ auth.password = '***'
+ }
+ auth.module = providerKey === '00910749-8ab6-498a-9be0-f4ca28ea5e52' ? 'google' : 'local'
+ auth._moduleName = providerKey === '00910749-8ab6-498a-9be0-f4ca28ea5e52' ? 'Google' : 'Local'
+ return auth
+ })
return usr
},
@@ -61,19 +83,19 @@ module.exports = {
.limit(10)
}
},
- UserMutation: {
- async create (obj, args) {
+ Mutation: {
+ async createUser (obj, args) {
try {
- await WIKI.models.users.createNewUser(args)
+ await WIKI.models.users.createNewUser({ ...args, passwordRaw: args.password, isVerified: true })
return {
- responseResult: graphHelper.generateSuccess('User created successfully')
+ status: graphHelper.generateSuccess('User created successfully')
}
} catch (err) {
return graphHelper.generateError(err)
}
},
- async delete (obj, args) {
+ async deleteUser (obj, args) {
try {
if (args.id <= 2) {
throw new WIKI.Error.UserDeleteProtected()
@@ -84,7 +106,7 @@ module.exports = {
WIKI.events.outbound.emit('addAuthRevoke', { id: args.id, kind: 'u' })
return {
- responseResult: graphHelper.generateSuccess('User deleted successfully')
+ status: graphHelper.generateSuccess('User deleted successfully')
}
} catch (err) {
if (err.message.indexOf('foreign') >= 0) {
@@ -94,40 +116,40 @@ module.exports = {
}
}
},
- async update (obj, args) {
+ async updateUser (obj, args) {
try {
- await WIKI.models.users.updateUser(args)
+ await WIKI.models.users.updateUser(args.id, args.patch)
return {
- responseResult: graphHelper.generateSuccess('User created successfully')
+ status: graphHelper.generateSuccess('User updated successfully')
}
} catch (err) {
return graphHelper.generateError(err)
}
},
- async verify (obj, args) {
+ async verifyUser (obj, args) {
try {
await WIKI.models.users.query().patch({ isVerified: true }).findById(args.id)
return {
- responseResult: graphHelper.generateSuccess('User verified successfully')
+ status: graphHelper.generateSuccess('User verified successfully')
}
} catch (err) {
return graphHelper.generateError(err)
}
},
- async activate (obj, args) {
+ async activateUser (obj, args) {
try {
await WIKI.models.users.query().patch({ isActive: true }).findById(args.id)
return {
- responseResult: graphHelper.generateSuccess('User activated successfully')
+ status: graphHelper.generateSuccess('User activated successfully')
}
} catch (err) {
return graphHelper.generateError(err)
}
},
- async deactivate (obj, args) {
+ async deactivateUser (obj, args) {
try {
if (args.id <= 2) {
throw new Error('Cannot deactivate system accounts.')
@@ -138,35 +160,35 @@ module.exports = {
WIKI.events.outbound.emit('addAuthRevoke', { id: args.id, kind: 'u' })
return {
- responseResult: graphHelper.generateSuccess('User deactivated successfully')
+ status: graphHelper.generateSuccess('User deactivated successfully')
}
} catch (err) {
return graphHelper.generateError(err)
}
},
- async enableTFA (obj, args) {
+ async enableUserTFA (obj, args) {
try {
await WIKI.models.users.query().patch({ tfaIsActive: true, tfaSecret: null }).findById(args.id)
return {
- responseResult: graphHelper.generateSuccess('User 2FA enabled successfully')
+ status: graphHelper.generateSuccess('User 2FA enabled successfully')
}
} catch (err) {
return graphHelper.generateError(err)
}
},
- async disableTFA (obj, args) {
+ async disableUserTFA (obj, args) {
try {
await WIKI.models.users.query().patch({ tfaIsActive: false, tfaSecret: null }).findById(args.id)
return {
- responseResult: graphHelper.generateSuccess('User 2FA disabled successfully')
+ status: graphHelper.generateSuccess('User 2FA disabled successfully')
}
} catch (err) {
return graphHelper.generateError(err)
}
},
- resetPassword (obj, args) {
+ resetUserPassword (obj, args) {
return false
},
async updateProfile (obj, args, context) {
@@ -203,7 +225,7 @@ module.exports = {
const newToken = await WIKI.models.users.refreshToken(usr.id)
return {
- responseResult: graphHelper.generateSuccess('User profile updated successfully'),
+ status: graphHelper.generateSuccess('User profile updated successfully'),
jwt: newToken.token
}
} catch (err) {
diff --git a/server/graph/scalars/date.js b/server/graph/scalars/date.js
index 472312ad..4a20a6ff 100644
--- a/server/graph/scalars/date.js
+++ b/server/graph/scalars/date.js
@@ -1,21 +1,18 @@
-
const gql = require('graphql')
-module.exports = {
- Date: new gql.GraphQLScalarType({
- name: 'Date',
- description: 'ISO date-time string at UTC',
- parseValue(value) {
- return new Date(value)
- },
- serialize(value) {
- return value.toISOString()
- },
- parseLiteral(ast) {
- if (ast.kind !== gql.Kind.STRING) {
- throw new TypeError('Date value must be an string!')
- }
- return new Date(ast.value)
+module.exports = new gql.GraphQLScalarType({
+ name: 'Date',
+ description: 'ISO date-time string at UTC',
+ parseValue(value) {
+ return new Date(value)
+ },
+ serialize(value) {
+ return value.toISOString()
+ },
+ parseLiteral(ast) {
+ if (ast.kind !== gql.Kind.STRING) {
+ throw new TypeError('Date value must be an string!')
}
- })
-}
+ return new Date(ast.value)
+ }
+})
diff --git a/server/graph/scalars/json.js b/server/graph/scalars/json.js
index 4ab8aa33..9fd63409 100644
--- a/server/graph/scalars/json.js
+++ b/server/graph/scalars/json.js
@@ -39,21 +39,19 @@ function parseObject (typeName, ast, variables) {
return value
}
-module.exports = {
- JSON: new GraphQLScalarType({
- name: 'JSON',
- description:
- 'The `JSON` scalar type represents JSON objects as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).',
- specifiedByUrl:
- 'http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf',
- serialize: ensureObject,
- parseValue: ensureObject,
- parseLiteral: (ast, variables) => {
- if (ast.kind !== Kind.OBJECT) {
- throw new TypeError(`JSONObject cannot represent non-object value: ${ast}`)
- }
-
- return parseObject('JSONObject', ast, variables)
+module.exports = new GraphQLScalarType({
+ name: 'JSON',
+ description:
+ 'The `JSON` scalar type represents JSON objects as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).',
+ specifiedByUrl:
+ 'http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf',
+ serialize: ensureObject,
+ parseValue: ensureObject,
+ parseLiteral: (ast, variables) => {
+ if (ast.kind !== Kind.OBJECT) {
+ throw new TypeError(`JSONObject cannot represent non-object value: ${ast}`)
}
- })
-}
+
+ return parseObject('JSONObject', ast, variables)
+ }
+})
diff --git a/server/graph/scalars/uuid.js b/server/graph/scalars/uuid.js
index 3b2de14b..046c4b4a 100644
--- a/server/graph/scalars/uuid.js
+++ b/server/graph/scalars/uuid.js
@@ -1,5 +1,4 @@
const { Kind, GraphQLScalarType } = require('graphql')
-// const { Kind } = require('graphql/language')
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
const nilUUID = '00000000-0000-0000-0000-000000000000'
@@ -8,32 +7,30 @@ function isUUID (value) {
return uuidRegex.test(value) || nilUUID === value
}
-module.exports = {
- UUID: new GraphQLScalarType({
- name: 'UUID',
- description: 'The `UUID` scalar type represents UUID values as specified by [RFC 4122](https://tools.ietf.org/html/rfc4122).',
- serialize: (value) => {
- if (!isUUID(value)) {
- throw new TypeError(`UUID cannot represent non-UUID value: ${value}`)
- }
-
- return value.toLowerCase()
- },
- parseValue: (value) => {
- if (!isUUID(value)) {
- throw new TypeError(`UUID cannot represent non-UUID value: ${value}`)
- }
-
- return value.toLowerCase()
- },
- parseLiteral: (ast) => {
- if (ast.kind === Kind.STRING) {
- if (isUUID(ast.value)) {
- return ast.value
- }
- }
-
- return undefined
+module.exports = new GraphQLScalarType({
+ name: 'UUID',
+ description: 'The `UUID` scalar type represents UUID values as specified by [RFC 4122](https://datatracker.ietf.org/doc/html/rfc4122).',
+ serialize: (value) => {
+ if (!isUUID(value)) {
+ throw new TypeError(`UUID cannot represent non-UUID value: ${value}`)
}
- })
-}
+
+ return value.toLowerCase()
+ },
+ parseValue: (value) => {
+ if (!isUUID(value)) {
+ throw new TypeError(`UUID cannot represent non-UUID value: ${value}`)
+ }
+
+ return value.toLowerCase()
+ },
+ parseLiteral: (ast) => {
+ if (ast.kind === Kind.STRING) {
+ if (isUUID(ast.value)) {
+ return ast.value
+ }
+ }
+
+ return undefined
+ }
+})
diff --git a/server/graph/schemas/analytics.graphql b/server/graph/schemas/analytics.graphql
index 437725a0..7fbd5941 100644
--- a/server/graph/schemas/analytics.graphql
+++ b/server/graph/schemas/analytics.graphql
@@ -3,45 +3,23 @@
# ===============================================
extend type Query {
- analytics: AnalyticsQuery
-}
-
-extend type Mutation {
- analytics: AnalyticsMutation
-}
-
-# -----------------------------------------------
-# QUERIES
-# -----------------------------------------------
-
-"""
-Queries for Analytics
-"""
-type AnalyticsQuery {
"""
Fetch list of Analytics providers and their configuration
"""
- providers(
+ analyticsProviders(
"Return only active providers"
isEnabled: Boolean
- ): [AnalyticsProvider] @auth(requires: ["manage:system"])
+ ): [AnalyticsProvider]
}
-# -----------------------------------------------
-# MUTATIONS
-# -----------------------------------------------
-
-"""
-Mutations for Analytics
-"""
-type AnalyticsMutation {
+extend type Mutation {
"""
Update a list of Analytics providers and their configuration
"""
- updateProviders(
+ updateAnalyticsProviders(
"List of providers"
providers: [AnalyticsProviderInput]!
- ): DefaultResponse @auth(requires: ["manage:system"])
+ ): DefaultResponse
}
# -----------------------------------------------
diff --git a/server/graph/schemas/asset.graphql b/server/graph/schemas/asset.graphql
index f07f1307..038715fc 100644
--- a/server/graph/schemas/asset.graphql
+++ b/server/graph/schemas/asset.graphql
@@ -3,49 +3,33 @@
# ===============================================
extend type Query {
- assets: AssetQuery
+ assets(
+ folderId: Int!
+ kind: AssetKind!
+ ): [AssetItem]
+
+ assetsFolders(
+ parentFolderId: Int!
+ ): [AssetFolder]
}
extend type Mutation {
- assets: AssetMutation
-}
-
-# -----------------------------------------------
-# QUERIES
-# -----------------------------------------------
-
-type AssetQuery {
- list(
- folderId: Int!
- kind: AssetKind!
- ): [AssetItem] @auth(requires: ["manage:system", "read:assets"])
-
- folders(
- parentFolderId: Int!
- ): [AssetFolder] @auth(requires: ["manage:system", "read:assets"])
-}
-
-# -----------------------------------------------
-# MUTATIONS
-# -----------------------------------------------
-
-type AssetMutation {
- createFolder(
+ createAssetsFolder(
parentFolderId: Int!
slug: String!
name: String
- ): DefaultResponse @auth(requires: ["manage:system", "write:assets"])
+ ): DefaultResponse
renameAsset(
id: Int!
filename: String!
- ): DefaultResponse @auth(requires: ["manage:system", "manage:assets"])
+ ): DefaultResponse
deleteAsset(
id: Int!
- ): DefaultResponse @auth(requires: ["manage:system", "manage:assets"])
+ ): DefaultResponse
- flushTempUploads: DefaultResponse @auth(requires: ["manage:system"])
+ flushTempUploads: DefaultResponse
}
# -----------------------------------------------
diff --git a/server/graph/schemas/authentication.graphql b/server/graph/schemas/authentication.graphql
index de023844..8834899f 100644
--- a/server/graph/schemas/authentication.graphql
+++ b/server/graph/schemas/authentication.graphql
@@ -3,40 +3,22 @@
# ===============================================
extend type Query {
- authentication: AuthenticationQuery
+ apiKeys: [AuthenticationApiKey]
+
+ apiState: Boolean
+
+ authStrategies(
+ enabledOnly: Boolean
+ ): [AuthenticationStrategy]
}
extend type Mutation {
- authentication: AuthenticationMutation
-}
-
-# -----------------------------------------------
-# QUERIES
-# -----------------------------------------------
-
-type AuthenticationQuery {
- apiKeys: [AuthenticationApiKey] @auth(requires: ["manage:system", "manage:api"])
-
- apiState: Boolean! @auth(requires: ["manage:system", "manage:api"])
-
- strategies: [AuthenticationStrategy] @auth(requires: ["manage:system"])
-
- activeStrategies(
- enabledOnly: Boolean
- ): [AuthenticationActiveStrategy]
-}
-
-# -----------------------------------------------
-# MUTATIONS
-# -----------------------------------------------
-
-type AuthenticationMutation {
createApiKey(
name: String!
expiration: String!
fullAccess: Boolean!
group: Int
- ): AuthenticationCreateApiKeyResponse @auth(requires: ["manage:system", "manage:api"])
+ ): AuthenticationCreateApiKeyResponse
login(
username: String!
@@ -67,19 +49,19 @@ type AuthenticationMutation {
revokeApiKey(
id: Int!
- ): DefaultResponse @auth(requires: ["manage:system", "manage:api"])
+ ): DefaultResponse
setApiState(
enabled: Boolean!
- ): DefaultResponse @auth(requires: ["manage:system", "manage:api"])
+ ): DefaultResponse
- updateStrategies(
+ updateAuthStrategies(
strategies: [AuthenticationStrategyInput]!
- ): DefaultResponse @auth(requires: ["manage:system"])
+ ): DefaultResponse
- regenerateCertificates: DefaultResponse @auth(requires: ["manage:system"])
+ regenerateCertificates: DefaultResponse
- resetGuestUser: DefaultResponse @auth(requires: ["manage:system"])
+ resetGuestUser: DefaultResponse
}
# -----------------------------------------------
@@ -113,7 +95,7 @@ type AuthenticationActiveStrategy {
}
type AuthenticationLoginResponse {
- responseResult: ResponseStatus
+ operation: Operation
jwt: String
mustChangePwd: Boolean
mustProvideTFA: Boolean
@@ -124,7 +106,7 @@ type AuthenticationLoginResponse {
}
type AuthenticationRegisterResponse {
- responseResult: ResponseStatus
+ operation: Operation
jwt: String
}
@@ -151,6 +133,6 @@ type AuthenticationApiKey {
}
type AuthenticationCreateApiKeyResponse {
- responseResult: ResponseStatus
+ operation: Operation
key: String
}
diff --git a/server/graph/schemas/comment.graphql b/server/graph/schemas/comment.graphql
index df73496a..be30b7fe 100644
--- a/server/graph/schemas/comment.graphql
+++ b/server/graph/schemas/comment.graphql
@@ -3,55 +3,39 @@
# ===============================================
extend type Query {
- comments: CommentQuery
+ commentsProviders: [CommentProvider]
+
+ comments(
+ locale: String!
+ path: String!
+ ): [CommentPost]!
+
+ commentById(
+ id: Int!
+ ): CommentPost
}
extend type Mutation {
- comments: CommentMutation
-}
-
-# -----------------------------------------------
-# QUERIES
-# -----------------------------------------------
-
-type CommentQuery {
- providers: [CommentProvider] @auth(requires: ["manage:system"])
-
- list(
- locale: String!
- path: String!
- ): [CommentPost]! @auth(requires: ["read:comments", "manage:system"])
-
- single(
- id: Int!
- ): CommentPost @auth(requires: ["read:comments", "manage:system"])
-}
-
-# -----------------------------------------------
-# MUTATIONS
-# -----------------------------------------------
-
-type CommentMutation {
- updateProviders(
+ updateCommentsProviders(
providers: [CommentProviderInput]
- ): DefaultResponse @auth(requires: ["manage:system"])
+ ): DefaultResponse
- create(
+ createComment(
pageId: Int!
replyTo: Int
content: String!
guestName: String
guestEmail: String
- ): CommentCreateResponse @auth(requires: ["write:comments", "manage:system"]) @rateLimit(limit: 1, duration: 15)
+ ): CommentCreateResponse @rateLimit(limit: 1, duration: 15)
- update(
+ updateComment(
id: Int!
content: String!
- ): CommentUpdateResponse @auth(requires: ["write:comments", "manage:comments", "manage:system"])
+ ): CommentUpdateResponse
- delete(
+ deleteComment(
id: Int!
- ): DefaultResponse @auth(requires: ["manage:comments", "manage:system"])
+ ): DefaultResponse
}
# -----------------------------------------------
@@ -59,9 +43,9 @@ type CommentMutation {
# -----------------------------------------------
type CommentProvider {
- isEnabled: Boolean!
- key: String!
- title: String!
+ isEnabled: Boolean
+ key: String
+ title: String
description: String
logo: String
website: String
@@ -76,23 +60,23 @@ input CommentProviderInput {
}
type CommentPost {
- id: Int!
- content: String! @auth(requires: ["write:comments", "manage:comments", "manage:system"])
- render: String!
- authorId: Int!
- authorName: String!
- authorEmail: String! @auth(requires: ["manage:system"])
- authorIP: String! @auth(requires: ["manage:system"])
- createdAt: Date!
- updatedAt: Date!
+ id: Int
+ content: String
+ render: String
+ authorId: Int
+ authorName: String
+ authorEmail: String
+ authorIP: String
+ createdAt: Date
+ updatedAt: Date
}
type CommentCreateResponse {
- responseResult: ResponseStatus
+ operation: Operation
id: Int
}
type CommentUpdateResponse {
- responseResult: ResponseStatus
+ operation: Operation
render: String
}
diff --git a/server/graph/schemas/common.graphql b/server/graph/schemas/common.graphql
index 8fb6f949..0954f435 100644
--- a/server/graph/schemas/common.graphql
+++ b/server/graph/schemas/common.graphql
@@ -2,7 +2,7 @@
# Wiki.js GraphQL Schema #
# ====================== #
-# DIRECTIVES
+# DIRECTIVES (deprecated)
# ----------
directive @auth(requires: [String]) on QUERY | FIELD_DEFINITION | ARGUMENT_DEFINITION
@@ -12,8 +12,8 @@ directive @auth(requires: [String]) on QUERY | FIELD_DEFINITION | ARGUMENT_DEFIN
# Generic Key Value Pair
type KeyValuePair {
- key: String!
- value: String!
+ key: String
+ value: String
}
# General Key Value Pair Input
input KeyValuePairInput {
@@ -23,14 +23,13 @@ input KeyValuePairInput {
# Generic Mutation Response
type DefaultResponse {
- responseResult: ResponseStatus
+ operation: Operation
}
-# Mutation Status
-type ResponseStatus {
- succeeded: Boolean!
- errorCode: Int!
- slug: String!
+# Mutation Operation
+type Operation {
+ succeeded: Boolean
+ slug: String
message: String
}
@@ -47,6 +46,3 @@ type Query
# Mutations (Create, Update, Delete)
type Mutation
-
-# Subscriptions (Push, Real-time)
-type Subscription
diff --git a/server/graph/schemas/contribute.graphql b/server/graph/schemas/contribute.graphql
deleted file mode 100644
index 9f2dd63f..00000000
--- a/server/graph/schemas/contribute.graphql
+++ /dev/null
@@ -1,29 +0,0 @@
-# ===============================================
-# CONTRIBUTE
-# ===============================================
-
-extend type Query {
- contribute: ContributeQuery
-}
-
-# -----------------------------------------------
-# QUERIES
-# -----------------------------------------------
-
-type ContributeQuery {
- contributors: [ContributeContributor]
-}
-
-# -----------------------------------------------
-# TYPES
-# -----------------------------------------------
-
-type ContributeContributor {
- id: String!
- source: String!
- name: String!
- joined: Date!
- website: String
- twitter: String
- avatar: String
-}
diff --git a/server/graph/schemas/group.graphql b/server/graph/schemas/group.graphql
index b1f99ade..4f247f28 100644
--- a/server/graph/schemas/group.graphql
+++ b/server/graph/schemas/group.graphql
@@ -3,58 +3,39 @@
# ===============================================
extend type Query {
- groups: GroupQuery
+ groups(
+ filter: String
+ orderBy: String
+ ): [Group]
+
+ groupById(
+ id: Int!
+ ): Group
}
extend type Mutation {
- groups: GroupMutation
-}
-
-# -----------------------------------------------
-# QUERIES
-# -----------------------------------------------
-
-type GroupQuery {
- list(
- filter: String
- orderBy: String
- ): [GroupMinimal] @auth(requires: ["write:groups", "manage:groups", "manage:system"])
-
- single(
- id: Int!
- ): Group @auth(requires: ["write:groups", "manage:groups", "manage:system"])
-}
-
-# -----------------------------------------------
-# MUTATIONS
-# -----------------------------------------------
-
-type GroupMutation {
- create(
+ createGroup(
name: String!
- ): GroupResponse @auth(requires: ["write:groups", "manage:groups", "manage:system"])
+ ): GroupResponse
- update(
+ updateGroup(
id: Int!
- name: String!
- redirectOnLogin: String!
- permissions: [String]!
- pageRules: [PageRuleInput]!
- ): DefaultResponse @auth(requires: ["write:groups", "manage:groups", "manage:system"])
+ patch: GroupUpdateInput!
+ ): DefaultResponse
- delete(
+ deleteGroup(
id: Int!
- ): DefaultResponse @auth(requires: ["write:groups", "manage:groups", "manage:system"])
+ ): DefaultResponse
- assignUser(
+ assignUserToGroup(
groupId: Int!
userId: Int!
- ): DefaultResponse @auth(requires: ["write:groups", "manage:groups", "manage:system"])
+ ): DefaultResponse
- unassignUser(
+ unassignUserFromGroup(
groupId: Int!
userId: Int!
- ): DefaultResponse @auth(requires: ["write:groups", "manage:groups", "manage:system"])
+ ): DefaultResponse
}
# -----------------------------------------------
@@ -62,38 +43,36 @@ type GroupMutation {
# -----------------------------------------------
type GroupResponse {
- responseResult: ResponseStatus!
+ operation: Operation
group: Group
}
-type GroupMinimal {
- id: Int!
- name: String!
- isSystem: Boolean!
- userCount: Int
- createdAt: Date!
- updatedAt: Date!
-}
-
type Group {
- id: Int!
- name: String!
- isSystem: Boolean!
+ id: Int
+ name: String
+ isSystem: Boolean
redirectOnLogin: String
- permissions: [String]!
+ permissions: [String]
pageRules: [PageRule]
users: [UserMinimal]
- createdAt: Date!
- updatedAt: Date!
+ createdAt: Date
+ updatedAt: Date
}
type PageRule {
- id: String!
- deny: Boolean!
- match: PageRuleMatch!
- roles: [String]!
- path: String!
- locales: [String]!
+ id: String
+ deny: Boolean
+ match: PageRuleMatch
+ roles: [String]
+ path: String
+ locales: [String]
+}
+
+input GroupUpdateInput {
+ name: String!
+ redirectOnLogin: String!
+ permissions: [String]!
+ pageRules: [PageRuleInput]!
}
input PageRuleInput {
diff --git a/server/graph/schemas/localization.graphql b/server/graph/schemas/localization.graphql
index 173c715e..8bdcd6ef 100644
--- a/server/graph/schemas/localization.graphql
+++ b/server/graph/schemas/localization.graphql
@@ -3,38 +3,21 @@
# ===============================================
extend type Query {
- localization: LocalizationQuery
-}
-
-extend type Mutation {
- localization: LocalizationMutation
-}
-
-# -----------------------------------------------
-# QUERIES
-# -----------------------------------------------
-
-type LocalizationQuery {
locales: [LocalizationLocale]
- config: LocalizationConfig
translations(locale: String!, namespace: String!): [Translation]
}
-# -----------------------------------------------
-# MUTATIONS
-# -----------------------------------------------
-
-type LocalizationMutation {
+extend type Mutation {
downloadLocale(
locale: String!
- ): DefaultResponse @auth(requires: ["manage:system"])
+ ): DefaultResponse
updateLocale(
locale: String!
autoUpdate: Boolean!
namespacing: Boolean!
namespaces: [String]!
- ): DefaultResponse @auth(requires: ["manage:system"])
+ ): DefaultResponse
}
# -----------------------------------------------
@@ -42,25 +25,18 @@ type LocalizationMutation {
# -----------------------------------------------
type LocalizationLocale {
- availability: Int!
- code: String!
- createdAt: Date!
+ availability: Int
+ code: String
+ createdAt: Date
installDate: Date
- isInstalled: Boolean!
- isRTL: Boolean!
- name: String!
- nativeName: String!
- updatedAt: Date!
-}
-
-type LocalizationConfig {
- locale: String!
- autoUpdate: Boolean!
- namespacing: Boolean!
- namespaces: [String]!
+ isInstalled: Boolean
+ isRTL: Boolean
+ name: String
+ nativeName: String
+ updatedAt: Date
}
type Translation {
- key: String!
- value: String!
+ key: String
+ value: String
}
diff --git a/server/graph/schemas/logging.graphql b/server/graph/schemas/logging.graphql
deleted file mode 100644
index cf4042b8..00000000
--- a/server/graph/schemas/logging.graphql
+++ /dev/null
@@ -1,64 +0,0 @@
-# ===============================================
-# LOGGING
-# ===============================================
-
-extend type Query {
- logging: LoggingQuery
-}
-
-extend type Mutation {
- logging: LoggingMutation
-}
-
-extend type Subscription {
- loggingLiveTrail: LoggerTrailLine
-}
-
-# -----------------------------------------------
-# QUERIES
-# -----------------------------------------------
-
-type LoggingQuery {
- loggers(
- filter: String
- orderBy: String
- ): [Logger] @auth(requires: ["manage:system"])
-}
-
-# -----------------------------------------------
-# MUTATIONS
-# -----------------------------------------------
-
-type LoggingMutation {
- updateLoggers(
- loggers: [LoggerInput]
- ): DefaultResponse @auth(requires: ["manage:system"])
-}
-
-# -----------------------------------------------
-# TYPES
-# -----------------------------------------------
-
-type Logger {
- isEnabled: Boolean!
- key: String!
- title: String!
- description: String
- logo: String
- website: String
- level: String
- config: [KeyValuePair]
-}
-
-input LoggerInput {
- isEnabled: Boolean!
- key: String!
- level: String!
- config: [KeyValuePairInput]
-}
-
-type LoggerTrailLine {
- level: String!
- output: String!
- timestamp: Date!
-}
diff --git a/server/graph/schemas/mail.graphql b/server/graph/schemas/mail.graphql
index f935c5ac..29386ef8 100644
--- a/server/graph/schemas/mail.graphql
+++ b/server/graph/schemas/mail.graphql
@@ -3,31 +3,15 @@
# ===============================================
extend type Query {
- mail: MailQuery
+ mailConfig: MailConfig
}
extend type Mutation {
- mail: MailMutation
-}
-
-# -----------------------------------------------
-# QUERIES
-# -----------------------------------------------
-
-type MailQuery {
- config: MailConfig @auth(requires: ["manage:system"])
-}
-
-# -----------------------------------------------
-# MUTATIONS
-# -----------------------------------------------
-
-type MailMutation {
- sendTest(
+ sendMailTest(
recipientEmail: String!
- ): DefaultResponse @auth(requires: ["manage:system"])
+ ): DefaultResponse
- updateConfig(
+ updateMailConfig(
senderName: String!
senderEmail: String!
host: String!
@@ -40,7 +24,7 @@ type MailMutation {
dkimDomainName: String!
dkimKeySelector: String!
dkimPrivateKey: String!
- ): DefaultResponse @auth(requires: ["manage:system"])
+ ): DefaultResponse
}
# -----------------------------------------------
@@ -48,16 +32,16 @@ type MailMutation {
# -----------------------------------------------
type MailConfig {
- senderName: String!
- senderEmail: String!
- host: String!
- port: Int!
- secure: Boolean!
- verifySSL: Boolean!
- user: String!
- pass: String!
- useDKIM: Boolean!
- dkimDomainName: String!
- dkimKeySelector: String!
- dkimPrivateKey: String!
+ senderName: String
+ senderEmail: String
+ host: String
+ port: Int
+ secure: Boolean
+ verifySSL: Boolean
+ user: String
+ pass: String
+ useDKIM: Boolean
+ dkimDomainName: String
+ dkimKeySelector: String
+ dkimPrivateKey: String
}
diff --git a/server/graph/schemas/navigation.graphql b/server/graph/schemas/navigation.graphql
index 26ef9a9c..602928e2 100644
--- a/server/graph/schemas/navigation.graphql
+++ b/server/graph/schemas/navigation.graphql
@@ -3,33 +3,21 @@
# ===============================================
extend type Query {
- navigation: NavigationQuery
-}
-
-extend type Mutation {
- navigation: NavigationMutation
-}
-
-# -----------------------------------------------
-# QUERIES
-# -----------------------------------------------
-
-type NavigationQuery {
- tree: [NavigationTree]! @auth(requires: ["manage:navigation", "manage:system"])
- config: NavigationConfig! @auth(requires: ["manage:navigation", "manage:system"])
+ navigationTree: [NavigationTree]
+ navigationConfig: NavigationConfig
}
# -----------------------------------------------
# MUTATIONS
# -----------------------------------------------
-type NavigationMutation {
- updateTree(
+extend type Mutation {
+ updateNavigationTree(
tree: [NavigationTreeInput]!
- ): DefaultResponse @auth(requires: ["manage:navigation", "manage:system"])
- updateConfig(
+ ): DefaultResponse
+ updateNavigationConfig(
mode: NavigationMode!
- ): DefaultResponse @auth(requires: ["manage:navigation", "manage:system"])
+ ): DefaultResponse
}
# -----------------------------------------------
@@ -37,8 +25,8 @@ type NavigationMutation {
# -----------------------------------------------
type NavigationTree {
- locale: String!
- items: [NavigationItem]!
+ locale: String
+ items: [NavigationItem]
}
input NavigationTreeInput {
@@ -47,8 +35,8 @@ input NavigationTreeInput {
}
type NavigationItem {
- id: String!
- kind: String!
+ id: String
+ kind: String
label: String
icon: String
targetType: String
@@ -69,7 +57,7 @@ input NavigationItemInput {
}
type NavigationConfig {
- mode: NavigationMode!
+ mode: NavigationMode
}
enum NavigationMode {
diff --git a/server/graph/schemas/page.graphql b/server/graph/schemas/page.graphql
index 10eebc85..c87106c4 100644
--- a/server/graph/schemas/page.graphql
+++ b/server/graph/schemas/page.graphql
@@ -3,36 +3,24 @@
# ===============================================
extend type Query {
- pages: PageQuery
-}
-
-extend type Mutation {
- pages: PageMutation
-}
-
-# -----------------------------------------------
-# QUERIES
-# -----------------------------------------------
-
-type PageQuery {
- history(
+ pageHistoryById(
id: Int!
offsetPage: Int
offsetSize: Int
- ): PageHistoryResult @auth(requires: ["manage:system", "read:history"])
+ ): PageHistoryResult
- version(
+ pageVersionById(
pageId: Int!
versionId: Int!
- ): PageVersion @auth(requires: ["manage:system", "read:history"])
+ ): PageVersion
- search(
+ searchPages(
query: String!
path: String
locale: String
- ): PageSearchResponse! @auth(requires: ["manage:system", "read:pages"])
+ ): PageSearchResponse!
- list(
+ pages(
limit: Int
orderBy: PageOrderBy
orderByDirection: PageOrderByDirection
@@ -40,46 +28,42 @@ type PageQuery {
locale: String
creatorId: Int
authorId: Int
- ): [PageListItem!]! @auth(requires: ["manage:system", "read:pages"])
+ ): [PageListItem!]!
- single(
+ pageById(
id: Int!
- ): Page @auth(requires: ["read:pages", "manage:system"])
+ ): Page
- tags: [PageTag]! @auth(requires: ["manage:system", "read:pages"])
+ tags: [PageTag]!
searchTags(
query: String!
- ): [String]! @auth(requires: ["manage:system", "read:pages"])
+ ): [String]!
- tree(
+ pageTree(
path: String
parent: Int
mode: PageTreeMode!
locale: String!
includeAncestors: Boolean
- ): [PageTreeItem] @auth(requires: ["manage:system", "read:pages"])
+ ): [PageTreeItem]
- links(
+ pageLinks(
locale: String!
- ): [PageLinkItem] @auth(requires: ["manage:system", "read:pages"])
+ ): [PageLinkItem]
checkConflicts(
id: Int!
checkoutDate: Date!
- ): Boolean! @auth(requires: ["write:pages", "manage:pages", "manage:system"])
+ ): Boolean!
- conflictLatest(
+ checkConflictsLatest(
id: Int!
- ): PageConflictLatest! @auth(requires: ["write:pages", "manage:pages", "manage:system"])
+ ): PageConflictLatest!
}
-# -----------------------------------------------
-# MUTATIONS
-# -----------------------------------------------
-
-type PageMutation {
- create(
+extend type Mutation {
+ createPage(
content: String!
description: String!
editor: String!
@@ -93,9 +77,9 @@ type PageMutation {
scriptJs: String
tags: [String]!
title: String!
- ): PageResponse @auth(requires: ["write:pages", "manage:pages", "manage:system"])
+ ): PageResponse
- update(
+ updatePage(
id: Int!
content: String
description: String
@@ -110,54 +94,54 @@ type PageMutation {
scriptJs: String
tags: [String]
title: String
- ): PageResponse @auth(requires: ["write:pages", "manage:pages", "manage:system"])
+ ): PageResponse
- convert(
+ convertPage(
id: Int!
editor: String!
- ): DefaultResponse @auth(requires: ["write:pages", "manage:pages", "manage:system"])
+ ): DefaultResponse
- move(
+ renamePage(
id: Int!
destinationPath: String!
destinationLocale: String!
- ): DefaultResponse @auth(requires: ["manage:pages", "manage:system"])
+ ): DefaultResponse
- delete(
+ deletePage(
id: Int!
- ): DefaultResponse @auth(requires: ["delete:pages", "manage:system"])
+ ): DefaultResponse
deleteTag(
id: Int!
- ): DefaultResponse @auth(requires: ["manage:system"])
+ ): DefaultResponse
updateTag(
id: Int!
tag: String!
title: String!
- ): DefaultResponse @auth(requires: ["manage:system"])
+ ): DefaultResponse
- flushCache: DefaultResponse @auth(requires: ["manage:system"])
+ flushCache: DefaultResponse
migrateToLocale(
sourceLocale: String!
targetLocale: String!
- ): PageMigrationResponse @auth(requires: ["manage:system"])
+ ): PageMigrationResponse
- rebuildTree: DefaultResponse @auth(requires: ["manage:system"])
+ rebuildPageTree: DefaultResponse
- render(
+ renderPage(
id: Int!
- ): DefaultResponse @auth(requires: ["manage:system"])
+ ): DefaultResponse
- restore(
+ restorePage(
pageId: Int!
versionId: Int!
- ): DefaultResponse @auth(requires: ["write:pages", "manage:pages", "manage:system"])
+ ): DefaultResponse
- purgeHistory (
+ purgePagesHistory (
olderThan: String!
- ): DefaultResponse @auth(requires: ["manage:system"])
+ ): DefaultResponse
}
# -----------------------------------------------
@@ -165,152 +149,152 @@ type PageMutation {
# -----------------------------------------------
type PageResponse {
- responseResult: ResponseStatus!
+ operation: Operation
page: Page
}
type PageMigrationResponse {
- responseResult: ResponseStatus!
+ operation: Operation
count: Int
}
type Page {
- id: Int!
- path: String!
- hash: String!
- title: String!
- description: String!
- isPrivate: Boolean! @auth(requires: ["write:pages", "manage:system"])
- isPublished: Boolean! @auth(requires: ["write:pages", "manage:system"])
- privateNS: String @auth(requires: ["write:pages", "manage:system"])
- publishStartDate: Date! @auth(requires: ["write:pages", "manage:system"])
- publishEndDate: Date! @auth(requires: ["write:pages", "manage:system"])
- tags: [PageTag]!
- content: String! @auth(requires: ["read:source", "write:pages", "manage:system"])
+ id: Int
+ path: String
+ hash: String
+ title: String
+ description: String
+ isPrivate: Boolean
+ isPublished: Boolean
+ privateNS: String
+ publishStartDate: Date
+ publishEndDate: Date
+ tags: [PageTag]
+ content: String
render: String
toc: String
- contentType: String!
- createdAt: Date!
- updatedAt: Date!
- editor: String! @auth(requires: ["write:pages", "manage:system"])
- locale: String!
+ contentType: String
+ createdAt: Date
+ updatedAt: Date
+ editor: String
+ locale: String
scriptCss: String
scriptJs: String
- authorId: Int! @auth(requires: ["write:pages", "manage:system"])
- authorName: String! @auth(requires: ["write:pages", "manage:system"])
- authorEmail: String! @auth(requires: ["write:pages", "manage:system"])
- creatorId: Int! @auth(requires: ["write:pages", "manage:system"])
- creatorName: String! @auth(requires: ["write:pages", "manage:system"])
- creatorEmail: String! @auth(requires: ["write:pages", "manage:system"])
+ authorId: Int
+ authorName: String
+ authorEmail: String
+ creatorId: Int
+ creatorName: String
+ creatorEmail: String
}
type PageTag {
- id: Int!
- tag: String!
+ id: Int
+ tag: String
title: String
- createdAt: Date!
- updatedAt: Date!
+ createdAt: Date
+ updatedAt: Date
}
type PageHistory {
- versionId: Int!
- versionDate: Date!
- authorId: Int!
- authorName: String!
- actionType: String!
+ versionId: Int
+ versionDate: Date
+ authorId: Int
+ authorName: String
+ actionType: String
valueBefore: String
valueAfter: String
}
type PageVersion {
- action: String!
- authorId: String!
- authorName: String!
- content: String!
- contentType: String!
- createdAt: Date!
- versionDate: Date!
- description: String!
- editor: String!
- isPrivate: Boolean!
- isPublished: Boolean!
- locale: String!
- pageId: Int!
- path: String!
- publishEndDate: Date!
- publishStartDate: Date!
- tags: [String]!
- title: String!
- versionId: Int!
+ action: String
+ authorId: String
+ authorName: String
+ content: String
+ contentType: String
+ createdAt: Date
+ versionDate: Date
+ description: String
+ editor: String
+ isPrivate: Boolean
+ isPublished: Boolean
+ locale: String
+ pageId: Int
+ path: String
+ publishEndDate: Date
+ publishStartDate: Date
+ tags: [String]
+ title: String
+ versionId: Int
}
type PageHistoryResult {
trail: [PageHistory]
- total: Int!
+ total: Int
}
type PageSearchResponse {
- results: [PageSearchResult]!
- suggestions: [String]!
- totalHits: Int!
+ results: [PageSearchResult]
+ suggestions: [String]
+ totalHits: Int
}
type PageSearchResult {
- id: String!
- title: String!
- description: String!
- path: String!
- locale: String!
+ id: String
+ title: String
+ description: String
+ path: String
+ locale: String
}
type PageListItem {
- id: Int!
- path: String!
- locale: String!
+ id: Int
+ path: String
+ locale: String
title: String
description: String
- contentType: String!
- isPublished: Boolean!
- isPrivate: Boolean!
+ contentType: String
+ isPublished: Boolean
+ isPrivate: Boolean
privateNS: String
- createdAt: Date!
- updatedAt: Date!
+ createdAt: Date
+ updatedAt: Date
tags: [String]
}
type PageTreeItem {
- id: Int!
- path: String!
- depth: Int!
- title: String!
- isPrivate: Boolean!
- isFolder: Boolean!
+ id: Int
+ path: String
+ depth: Int
+ title: String
+ isPrivate: Boolean
+ isFolder: Boolean
privateNS: String
parent: Int
pageId: Int
- locale: String!
+ locale: String
}
type PageLinkItem {
- id: Int!
- path: String!
- title: String!
- links: [String]!
+ id: Int
+ path: String
+ title: String
+ links: [String]
}
type PageConflictLatest {
- id: Int!
- authorId: String!
- authorName: String!
- content: String!
- createdAt: Date!
- description: String!
- isPublished: Boolean!
- locale: String!
- path: String!
+ id: Int
+ authorId: String
+ authorName: String
+ content: String
+ createdAt: Date
+ description: String
+ isPublished: Boolean
+ locale: String
+ path: String
tags: [String]
- title: String!
- updatedAt: Date!
+ title: String
+ updatedAt: Date
}
enum PageOrderBy {
diff --git a/server/graph/schemas/rendering.graphql b/server/graph/schemas/rendering.graphql
index 9b4d0ae9..8bffa602 100644
--- a/server/graph/schemas/rendering.graphql
+++ b/server/graph/schemas/rendering.graphql
@@ -3,32 +3,16 @@
# ===============================================
extend type Query {
- rendering: RenderingQuery
-}
-
-extend type Mutation {
- rendering: RenderingMutation
-}
-
-# -----------------------------------------------
-# QUERIES
-# -----------------------------------------------
-
-type RenderingQuery {
renderers(
filter: String
orderBy: String
- ): [Renderer] @auth(requires: ["manage:system"])
+ ): [Renderer]
}
-# -----------------------------------------------
-# MUTATIONS
-# -----------------------------------------------
-
-type RenderingMutation {
+extend type Mutation {
updateRenderers(
- renderers: [RendererInput]
- ): DefaultResponse @auth(requires: ["manage:system"])
+ renderers: [RendererInput]!
+ ): DefaultResponse
}
# -----------------------------------------------
@@ -36,9 +20,9 @@ type RenderingMutation {
# -----------------------------------------------
type Renderer {
- isEnabled: Boolean!
- key: String!
- title: String!
+ isEnabled: Boolean
+ key: String
+ title: String
description: String
icon: String
dependsOn: String
diff --git a/server/graph/schemas/search.graphql b/server/graph/schemas/search.graphql
index 5d313bd2..cb0a6db3 100644
--- a/server/graph/schemas/search.graphql
+++ b/server/graph/schemas/search.graphql
@@ -2,54 +2,10 @@
# SEARCH
# ===============================================
-extend type Query {
- search: SearchQuery
-}
-
extend type Mutation {
- search: SearchMutation
-}
-
-# -----------------------------------------------
-# QUERIES
-# -----------------------------------------------
-
-type SearchQuery {
- searchEngines(
- filter: String
- orderBy: String
- ): [SearchEngine] @auth(requires: ["manage:system"])
-}
-
-# -----------------------------------------------
-# MUTATIONS
-# -----------------------------------------------
-
-type SearchMutation {
- updateSearchEngines(
- engines: [SearchEngineInput]
- ): DefaultResponse @auth(requires: ["manage:system"])
-
- rebuildIndex: DefaultResponse @auth(requires: ["manage:system"])
+ rebuildSearchIndex: DefaultResponse
}
# -----------------------------------------------
# TYPES
# -----------------------------------------------
-
-type SearchEngine {
- isEnabled: Boolean!
- key: String!
- title: String!
- description: String
- logo: String
- website: String
- isAvailable: Boolean
- config: [KeyValuePair]
-}
-
-input SearchEngineInput {
- isEnabled: Boolean!
- key: String!
- config: [KeyValuePairInput]
-}
diff --git a/server/graph/schemas/site.graphql b/server/graph/schemas/site.graphql
index 069e5184..f6778180 100644
--- a/server/graph/schemas/site.graphql
+++ b/server/graph/schemas/site.graphql
@@ -13,9 +13,6 @@ extend type Query {
hostname: String!
exact: Boolean!
): Site @auth(requires: ["manage:system"])
-
- # Legacy
- site: SiteQuery
}
extend type Mutation {
@@ -42,59 +39,6 @@ extend type Mutation {
deleteSite (
id: UUID!
): DefaultResponse @auth(requires: ["manage:system"])
-
- # Legacy
- site: SiteMutation
-}
-
-# -----------------------------------------------
-# QUERIES
-# -----------------------------------------------
-
-type SiteQuery {
- config: SiteConfig @auth(requires: ["manage:system"])
-}
-
-# -----------------------------------------------
-# MUTATIONS
-# -----------------------------------------------
-
-type SiteMutation {
- updateConfig(
- host: String
- title: String
- description: String
- robots: [String]
- analyticsService: String
- analyticsId: String
- company: String
- contentLicense: String
- logoUrl: String
- authAutoLogin: Boolean
- authEnforce2FA: Boolean
- authHideLocal: Boolean
- authLoginBgUrl: String
- authJwtAudience: String
- authJwtExpiration: String
- authJwtRenewablePeriod: String
- featurePageRatings: Boolean
- featurePageComments: Boolean
- featurePersonalWikis: Boolean
- securityOpenRedirect: Boolean
- securityIframe: Boolean
- securityReferrerPolicy: Boolean
- securityTrustProxy: Boolean
- securitySRI: Boolean
- securityHSTS: Boolean
- securityHSTSDuration: Int
- securityCSP: Boolean
- securityCSPDirectives: String
- uploadMaxFileSize: Int
- uploadMaxFiles: Int
- uploadScanSVG: Boolean
- uploadForceDownload: Boolean
-
- ): DefaultResponse @auth(requires: ["manage:system"])
}
# -----------------------------------------------
@@ -174,7 +118,7 @@ enum SitePageRatingModes {
}
type SiteCreateResponse {
- status: ResponseStatus
+ operation: Operation
site: Site
}
@@ -227,40 +171,3 @@ input SiteThemeInput {
showSharingMenu: Boolean
showPrintBtn: Boolean
}
-
-# LEGACY
-
-type SiteConfig {
- host: String
- title: String
- description: String
- robots: [String]
- analyticsService: String
- analyticsId: String
- company: String
- contentLicense: String
- logoUrl: String
- authAutoLogin: Boolean
- authEnforce2FA: Boolean
- authHideLocal: Boolean
- authLoginBgUrl: String
- authJwtAudience: String
- authJwtExpiration: String
- authJwtRenewablePeriod: String
- featurePageRatings: Boolean
- featurePageComments: Boolean
- featurePersonalWikis: Boolean
- securityOpenRedirect: Boolean
- securityIframe: Boolean
- securityReferrerPolicy: Boolean
- securityTrustProxy: Boolean
- securitySRI: Boolean
- securityHSTS: Boolean
- securityHSTSDuration: Int
- securityCSP: Boolean
- securityCSPDirectives: String
- uploadMaxFileSize: Int
- uploadMaxFiles: Int
- uploadScanSVG: Boolean
- uploadForceDownload: Boolean
-}
diff --git a/server/graph/schemas/storage.graphql b/server/graph/schemas/storage.graphql
index 8519caae..b559fc79 100644
--- a/server/graph/schemas/storage.graphql
+++ b/server/graph/schemas/storage.graphql
@@ -3,35 +3,30 @@
# ===============================================
extend type Query {
- storage: StorageQuery
+ storageTargets(
+ siteId: UUID!
+ ): [StorageTarget]
}
extend type Mutation {
- storage: StorageMutation
-}
-
-# -----------------------------------------------
-# QUERIES
-# -----------------------------------------------
-
-type StorageQuery {
- targets: [StorageTarget] @auth(requires: ["manage:system"])
- status: [StorageStatus] @auth(requires: ["manage:system"])
-}
-
-# -----------------------------------------------
-# MUTATIONS
-# -----------------------------------------------
-
-type StorageMutation {
- updateTargets(
+ updateStorageTargets(
+ siteId: UUID!
targets: [StorageTargetInput]!
- ): DefaultResponse @auth(requires: ["manage:system"])
+ ): DefaultResponse
- executeAction(
- targetKey: String!
+ setupStorageTarget(
+ targetId: UUID!
+ state: JSON!
+ ): StorageTargetSetupResponse
+
+ destroyStorageTargetSetup(
+ targetId: UUID!
+ ): DefaultResponse
+
+ executeStorageAction(
+ targetId: UUID!
handler: String!
- ): DefaultResponse @auth(requires: ["manage:system"])
+ ): DefaultResponse
}
# -----------------------------------------------
@@ -39,40 +34,46 @@ type StorageMutation {
# -----------------------------------------------
type StorageTarget {
- isAvailable: Boolean!
- isEnabled: Boolean!
- key: String!
- title: String!
+ id: UUID
+ isEnabled: Boolean
+ module: String
+ title: String
description: String
- logo: String
+ icon: String
+ banner: String
+ vendor: String
website: String
- supportedModes: [String]
- mode: String
- hasSchedule: Boolean!
- syncInterval: String
- syncIntervalDefault: String
- config: [KeyValuePair]
- actions: [StorageTargetAction]
+ contentTypes: JSON
+ assetDelivery: JSON
+ versioning: JSON
+ sync: JSON
+ status: JSON
+ setup: JSON
+ config: JSON
+ actions: JSON
+}
+
+type StorageTargetSetupResponse {
+ operation: Operation
+ state: JSON
}
input StorageTargetInput {
- isEnabled: Boolean!
- key: String!
- mode: String!
+ id: UUID!
+ module: String!
+ isEnabled: Boolean
+ contentTypes: [String!]
+ largeThreshold: String
+ assetDeliveryFileStreaming: Boolean
+ assetDeliveryDirectAccess: Boolean
+ syncMode: StorageTargetSyncMode
syncInterval: String
- config: [KeyValuePairInput]
+ useVersioning: Boolean
+ config: JSON
}
-type StorageStatus {
- key: String!
- title: String!
- status: String!
- message: String!
- lastAttempt: String!
-}
-
-type StorageTargetAction {
- handler: String!
- label: String!
- hint: String!
+enum StorageTargetSyncMode {
+ PULL
+ PUSH
+ SYNC
}
diff --git a/server/graph/schemas/system.graphql b/server/graph/schemas/system.graphql
index 88bb6e35..de398e82 100644
--- a/server/graph/schemas/system.graphql
+++ b/server/graph/schemas/system.graphql
@@ -3,50 +3,41 @@
# ===============================================
extend type Query {
- system: SystemQuery
+ systemExtensions: [SystemExtension]
+ systemFlags: [SystemFlag]
+ systemInfo: SystemInfo
+ systemSecurity: SystemSecurity
}
extend type Mutation {
- system: SystemMutation
-}
-
-# -----------------------------------------------
-# QUERIES
-# -----------------------------------------------
-
-type SystemQuery {
- flags: [SystemFlag] @auth(requires: ["manage:system"])
- info: SystemInfo
- extensions: [SystemExtension]! @auth(requires: ["manage:system"])
-}
-
-# -----------------------------------------------
-# MUTATIONS
-# -----------------------------------------------
-
-type SystemMutation {
- updateFlags(
+ updateSystemFlags(
flags: [SystemFlagInput]!
- ): DefaultResponse @auth(requires: ["manage:system"])
+ ): DefaultResponse
- resetTelemetryClientId: DefaultResponse @auth(requires: ["manage:system"])
+ updateSystemSecurity(
+ authJwtAudience: String
+ authJwtExpiration: String
+ authJwtRenewablePeriod: String
+ corsConfig: String
+ corsMode: SystemSecurityCorsMode
+ cspDirectives: String
+ disallowFloc: Boolean
+ disallowIframe: Boolean
+ disallowOpenRedirect: Boolean
+ enforceCsp: Boolean
+ enforceHsts: Boolean
+ enforceSameOriginReferrerPolicy: Boolean
+ forceAssetDownload: Boolean
+ hstsDuration: Int
+ trustProxy: Boolean
+ uploadMaxFiles: Int
+ uploadMaxFileSize: Int
+ uploadScanSVG: Boolean
+ ): DefaultResponse
- setTelemetry(
- enabled: Boolean!
- ): DefaultResponse @auth(requires: ["manage:system"])
-
- performUpgrade: DefaultResponse @auth(requires: ["manage:system"])
-
- importUsersFromV1(
- mongoDbConnString: String!
- groupMode: SystemImportUsersGroupMode!
- ): SystemImportUsersResponse @auth(requires: ["manage:system"])
-
- setHTTPSRedirection(
- enabled: Boolean!
- ): DefaultResponse @auth(requires: ["manage:system"])
-
- renewHTTPSCertificate: DefaultResponse @auth(requires: ["manage:system"])
+ installExtension(
+ key: String!
+ ): DefaultResponse
}
# -----------------------------------------------
@@ -54,8 +45,8 @@ type SystemMutation {
# -----------------------------------------------
type SystemFlag {
- key: String!
- value: Boolean!
+ key: String
+ value: Boolean
}
input SystemFlagInput {
@@ -64,35 +55,35 @@ input SystemFlagInput {
}
type SystemInfo {
- configFile: String @auth(requires: ["manage:system"])
- cpuCores: Int @auth(requires: ["manage:system"])
- currentVersion: String @auth(requires: ["manage:system"])
- dbHost: String @auth(requires: ["manage:system"])
- dbType: String @auth(requires: ["manage:system"])
- dbVersion: String @auth(requires: ["manage:system"])
- groupsTotal: Int @auth(requires: ["manage:system", "manage:navigation", "manage:groups", "write:groups", "manage:users", "write:users"])
- hostname: String @auth(requires: ["manage:system"])
- httpPort: Int @auth(requires: ["manage:system"])
- httpRedirection: Boolean @auth(requires: ["manage:system"])
- httpsPort: Int @auth(requires: ["manage:system"])
- latestVersion: String @auth(requires: ["manage:system"])
- latestVersionReleaseDate: Date @auth(requires: ["manage:system"])
- nodeVersion: String @auth(requires: ["manage:system"])
- operatingSystem: String @auth(requires: ["manage:system"])
- pagesTotal: Int @auth(requires: ["manage:system", "manage:navigation", "manage:pages", "delete:pages"])
- platform: String @auth(requires: ["manage:system"])
- ramTotal: String @auth(requires: ["manage:system"])
- sslDomain: String @auth(requires: ["manage:system"])
- sslExpirationDate: Date @auth(requires: ["manage:system"])
- sslProvider: String @auth(requires: ["manage:system"])
- sslStatus: String @auth(requires: ["manage:system"])
- sslSubscriberEmail: String @auth(requires: ["manage:system"])
- tagsTotal: Int @auth(requires: ["manage:system", "manage:navigation", "manage:pages", "delete:pages"])
- telemetry: Boolean @auth(requires: ["manage:system"])
- telemetryClientId: String @auth(requires: ["manage:system"])
- upgradeCapable: Boolean @auth(requires: ["manage:system"])
- usersTotal: Int @auth(requires: ["manage:system", "manage:navigation", "manage:groups", "write:groups", "manage:users", "write:users"])
- workingDirectory: String @auth(requires: ["manage:system"])
+ configFile: String
+ cpuCores: Int
+ currentVersion: String
+ dbHost: String
+ dbType: String
+ dbVersion: String
+ groupsTotal: Int
+ hostname: String
+ httpPort: Int
+ httpRedirection: Boolean
+ httpsPort: Int
+ latestVersion: String
+ latestVersionReleaseDate: Date
+ nodeVersion: String
+ operatingSystem: String
+ pagesTotal: Int
+ platform: String
+ ramTotal: String
+ sslDomain: String
+ sslExpirationDate: Date
+ sslProvider: String
+ sslStatus: String
+ sslSubscriberEmail: String
+ tagsTotal: Int
+ telemetry: Boolean
+ telemetryClientId: String
+ upgradeCapable: Boolean
+ usersTotal: Int
+ workingDirectory: String
}
enum SystemImportUsersGroupMode {
@@ -102,7 +93,7 @@ enum SystemImportUsersGroupMode {
}
type SystemImportUsersResponse {
- responseResult: ResponseStatus
+ operation: Operation
usersCount: Int
groupsCount: Int
failed: [SystemImportUsersResponseFailed]
@@ -115,9 +106,38 @@ type SystemImportUsersResponseFailed {
}
type SystemExtension {
- key: String!
- title: String!
- description: String!
- isInstalled: Boolean!
- isCompatible: Boolean!
+ key: String
+ title: String
+ description: String
+ isInstalled: Boolean
+ isInstallable: Boolean
+ isCompatible: Boolean
+}
+
+type SystemSecurity {
+ authJwtAudience: String
+ authJwtExpiration: String
+ authJwtRenewablePeriod: String
+ corsConfig: String
+ corsMode: SystemSecurityCorsMode
+ cspDirectives: String
+ disallowFloc: Boolean
+ disallowIframe: Boolean
+ disallowOpenRedirect: Boolean
+ enforceCsp: Boolean
+ enforceHsts: Boolean
+ enforceSameOriginReferrerPolicy: Boolean
+ forceAssetDownload: Boolean
+ hstsDuration: Int
+ trustProxy: Boolean
+ uploadMaxFiles: Int
+ uploadMaxFileSize: Int
+ uploadScanSVG: Boolean
+}
+
+enum SystemSecurityCorsMode {
+ OFF
+ REFLECT
+ HOSTNAMES
+ REGEX
}
diff --git a/server/graph/schemas/theming.graphql b/server/graph/schemas/theming.graphql
deleted file mode 100644
index eddebad4..00000000
--- a/server/graph/schemas/theming.graphql
+++ /dev/null
@@ -1,54 +0,0 @@
-# ===============================================
-# THEMES
-# ===============================================
-
-extend type Query {
- theming: ThemingQuery
-}
-
-extend type Mutation {
- theming: ThemingMutation
-}
-
-# -----------------------------------------------
-# QUERIES
-# -----------------------------------------------
-
-type ThemingQuery {
- themes: [ThemingTheme] @auth(requires: ["manage:theme", "manage:system"])
- config: ThemingConfig @auth(requires: ["manage:theme", "manage:system"])
-}
-
-# -----------------------------------------------
-# MUTATIONS
-# -----------------------------------------------
-
-type ThemingMutation {
- setConfig(
- theme: String!
- iconset: String!
- darkMode: Boolean!
- injectCSS: String
- injectHead: String
- injectBody: String
- ): DefaultResponse @auth(requires: ["manage:theme", "manage:system"])
-}
-
-# -----------------------------------------------
-# TYPES
-# -----------------------------------------------
-
-type ThemingConfig {
- theme: String!
- iconset: String!
- darkMode: Boolean!
- injectCSS: String
- injectHead: String
- injectBody: String
-}
-
-type ThemingTheme {
- key: String
- title: String
- author: String
-}
diff --git a/server/graph/schemas/user.graphql b/server/graph/schemas/user.graphql
index 0beef80f..4cc5fb15 100644
--- a/server/graph/schemas/user.graphql
+++ b/server/graph/schemas/user.graphql
@@ -3,90 +3,65 @@
# ===============================================
extend type Query {
- users: UserQuery
-}
-
-extend type Mutation {
- users: UserMutation
-}
-
-# -----------------------------------------------
-# QUERIES
-# -----------------------------------------------
-
-type UserQuery {
- list(
+ users (
+ page: Int
+ pageSize: Int
+ orderBy: UserOrderBy
+ orderByDirection: OrderByDirection
+ # Filter by name / email
filter: String
- orderBy: String
- ): [UserMinimal] @auth(requires: ["write:users", "manage:users", "manage:system"])
+ ): [UserMinimal]
- search(
- query: String!
- ): [UserMinimal] @auth(requires: ["write:groups", "manage:groups", "write:users", "manage:users", "manage:system"])
-
- single(
- id: Int!
- ): User @auth(requires: ["manage:users", "manage:system"])
+ userById(
+ id: UUID!
+ ): User
profile: UserProfile
- lastLogins: [UserLastLogin] @auth(requires: ["write:groups", "manage:groups", "write:users", "manage:users", "manage:system"])
+ lastLogins: [UserLastLogin]
}
-# -----------------------------------------------
-# MUTATIONS
-# -----------------------------------------------
-
-type UserMutation {
- create(
+extend type Mutation {
+ createUser(
email: String!
name: String!
- passwordRaw: String
- providerKey: String!
- groups: [Int]!
- mustChangePassword: Boolean
- sendWelcomeEmail: Boolean
- ): UserResponse @auth(requires: ["write:users", "manage:users", "manage:system"])
+ password: String!
+ groups: [UUID]!
+ mustChangePassword: Boolean!
+ sendWelcomeEmail: Boolean!
+ ): UserResponse
- update(
- id: Int!
- email: String
- name: String
- newPassword: String
- groups: [Int]
- location: String
- jobTitle: String
- timezone: String
- dateFormat: String
- appearance: String
- ): DefaultResponse @auth(requires: ["manage:users", "manage:system"])
+ updateUser(
+ id: UUID!
+ patch: UserUpdateInput!
+ ): DefaultResponse
- delete(
- id: Int!
- replaceId: Int!
- ): DefaultResponse @auth(requires: ["manage:users", "manage:system"])
+ deleteUser(
+ id: UUID!
+ replaceId: UUID!
+ ): DefaultResponse
- verify(
- id: Int!
- ): DefaultResponse @auth(requires: ["manage:users", "manage:system"])
+ verifyUser(
+ id: UUID!
+ ): DefaultResponse
- activate(
- id: Int!
- ): DefaultResponse @auth(requires: ["manage:users", "manage:system"])
+ activateUser(
+ id: UUID!
+ ): DefaultResponse
- deactivate(
- id: Int!
- ): DefaultResponse @auth(requires: ["manage:users", "manage:system"])
+ deactivateUser(
+ id: UUID!
+ ): DefaultResponse
- enableTFA(
- id: Int!
- ): DefaultResponse @auth(requires: ["manage:users", "manage:system"])
+ enableUserTFA(
+ id: UUID!
+ ): DefaultResponse
- disableTFA(
- id: Int!
- ): DefaultResponse @auth(requires: ["manage:users", "manage:system"])
+ disableUserTFA(
+ id: UUID!
+ ): DefaultResponse
- resetPassword(
+ resetUserPassword(
id: Int!
): DefaultResponse
@@ -110,71 +85,83 @@ type UserMutation {
# -----------------------------------------------
type UserResponse {
- responseResult: ResponseStatus!
+ operation: Operation
user: User
}
type UserLastLogin {
- id: Int!
- name: String!
- lastLoginAt: Date!
+ id: UUID
+ name: String
+ lastLoginAt: Date
}
type UserMinimal {
- id: Int!
- name: String!
- email: String!
- providerKey: String!
- isSystem: Boolean!
- isActive: Boolean!
- createdAt: Date!
+ id: UUID
+ name: String
+ email: String
+ isSystem: Boolean
+ isActive: Boolean
+ createdAt: Date
lastLoginAt: Date
}
type User {
- id: Int!
- name: String!
- email: String!
- providerKey: String!
- providerName: String
- providerId: String
- providerIs2FACapable: Boolean
- isSystem: Boolean!
- isActive: Boolean!
- isVerified: Boolean!
- location: String!
- jobTitle: String!
- timezone: String!
- dateFormat: String!
- appearance: String!
- createdAt: Date!
- updatedAt: Date!
+ id: UUID
+ name: String
+ email: String
+ auth: JSON
+ isSystem: Boolean
+ isActive: Boolean
+ isVerified: Boolean
+ meta: JSON
+ prefs: JSON
+ createdAt: Date
+ updatedAt: Date
lastLoginAt: Date
- tfaIsActive: Boolean!
- groups: [Group]!
+ groups: [Group]
}
type UserProfile {
- id: Int!
- name: String!
- email: String!
+ id: Int
+ name: String
+ email: String
providerKey: String
providerName: String
- isSystem: Boolean!
- isVerified: Boolean!
- location: String!
- jobTitle: String!
- timezone: String!
- dateFormat: String!
- appearance: String!
- createdAt: Date!
- updatedAt: Date!
+ isSystem: Boolean
+ isVerified: Boolean
+ location: String
+ jobTitle: String
+ timezone: String
+ dateFormat: String
+ appearance: String
+ createdAt: Date
+ updatedAt: Date
lastLoginAt: Date
- groups: [String]!
- pagesTotal: Int!
+ groups: [String]
+ pagesTotal: Int
}
type UserTokenResponse {
- responseResult: ResponseStatus!
+ operation: Operation
jwt: String
}
+
+enum UserOrderBy {
+ id
+ email
+ name
+ createdAt
+ updatedAt
+ lastLoginAt
+}
+
+input UserUpdateInput {
+ email: String
+ name: String
+ newPassword: String
+ groups: [UUID!]
+ isActive: Boolean
+ isVerified: Boolean
+ meta: JSON
+ prefs: JSON
+}
diff --git a/ux/src/components/SiteActivateDialog.vue b/ux/src/components/SiteActivateDialog.vue
index df48898c..e6448cf1 100644
--- a/ux/src/components/SiteActivateDialog.vue
+++ b/ux/src/components/SiteActivateDialog.vue
@@ -1,114 +1,135 @@
-q-dialog(ref='dialog', @hide='onDialogHide')
+q-dialog(ref='dialogRef', @hide='onDialogHide')
q-card(style='min-width: 350px; max-width: 450px;')
q-card-section.card-header
q-icon(name='img:/_assets/icons/fluent-shutdown.svg', left, size='sm')
- span {{value ? $t(`admin.sites.activate`) : $t(`admin.sites.deactivate`)}}
+ span {{modelValue ? t(`admin.sites.activate`) : t(`admin.sites.deactivate`)}}
q-card-section
.text-body2
- i18n-t(:keypath='value ? `admin.sites.activateConfirm` : `admin.sites.deactivateConfirm`')
+ i18n-t(:keypath='modelValue ? `admin.sites.activateConfirm` : `admin.sites.deactivateConfirm`')
template(v-slot:siteTitle)
- strong {{site.title}}
+ strong {{props.site.title}}
q-card-actions.card-actions
q-space
q-btn.acrylic-btn(
flat
- :label='$t(`common.actions.cancel`)'
+ :label='t(`common.actions.cancel`)'
color='grey'
padding='xs md'
- @click='hide'
+ @click='onDialogCancel'
)
q-btn(
unelevated
- :label='value ? $t(`common.actions.activate`) : $t(`common.actions.deactivate`)'
- :color='value ? `positive` : `negative`'
+ :label='modelValue ? t(`common.actions.activate`) : t(`common.actions.deactivate`)'
+ :color='modelValue ? `positive` : `negative`'
padding='xs md'
@click='confirm'
+ :loading='state.isLoading'
)
-
diff --git a/ux/src/components/SiteCreateDialog.vue b/ux/src/components/SiteCreateDialog.vue
index c6cedb75..3836b3f5 100644
--- a/ux/src/components/SiteCreateDialog.vue
+++ b/ux/src/components/SiteCreateDialog.vue
@@ -66,6 +66,8 @@ import { reactive, ref } from 'vue'
import { useAdminStore } from '../stores/admin'
+// EMITS
+
defineEmits([
...useDialogPluginComponent.emits
])
@@ -114,7 +116,7 @@ async function create () {
hostname: $hostname
title: $title
) {
- status {
+ operation {
succeeded
message
}
@@ -126,7 +128,7 @@ async function create () {
title: state.siteName
}
})
- if (resp?.data?.createSite?.status?.succeeded) {
+ if (resp?.data?.createSite?.operation?.succeeded) {
$q.notify({
type: 'positive',
message: t('admin.sites.createSuccess')
@@ -134,7 +136,7 @@ async function create () {
await adminStore.fetchSites()
onDialogOK()
} else {
- throw new Error(resp?.data?.createSite?.status?.message || 'An unexpected error occured.')
+ throw new Error(resp?.data?.createSite?.operation?.message || 'An unexpected error occured.')
}
} catch (err) {
$q.notify({
diff --git a/ux/src/components/SiteDeleteDialog.vue b/ux/src/components/SiteDeleteDialog.vue
index 3262d74e..f9cb1848 100644
--- a/ux/src/components/SiteDeleteDialog.vue
+++ b/ux/src/components/SiteDeleteDialog.vue
@@ -1,94 +1,115 @@
-q-dialog(ref='dialog', @hide='onDialogHide')
+q-dialog(ref='dialogRef', @hide='onDialogHide')
q-card(style='min-width: 350px; max-width: 450px;')
q-card-section.card-header
q-icon(name='img:/_assets/icons/fluent-delete-bin.svg', left, size='sm')
- span {{$t(`admin.sites.delete`)}}
+ span {{t(`admin.sites.delete`)}}
q-card-section
.text-body2
i18n-t(keypath='admin.sites.deleteConfirm')
template(v-slot:siteTitle)
- strong {{site.title}}
+ strong {{props.site.title}}
.text-body2.q-mt-md
- strong.text-negative {{$t(`admin.sites.deleteConfirmWarn`)}}
+ strong.text-negative {{t(`admin.sites.deleteConfirmWarn`)}}
q-card-actions.card-actions
q-space
q-btn.acrylic-btn(
flat
- :label='$t(`common.actions.cancel`)'
+ :label='t(`common.actions.cancel`)'
color='grey'
padding='xs md'
- @click='hide'
+ @click='onDialogCancel'
)
q-btn(
unelevated
- :label='$t(`common.actions.delete`)'
+ :label='t(`common.actions.delete`)'
color='negative'
padding='xs md'
@click='confirm'
+ :loading='state.isLoading'
)
-
diff --git a/ux/src/pages/AdminSites.vue b/ux/src/pages/AdminSites.vue
index 1cb43c5d..ae9558c9 100644
--- a/ux/src/pages/AdminSites.vue
+++ b/ux/src/pages/AdminSites.vue
@@ -133,10 +133,6 @@ useMeta({
title: t('admin.sites.title')
})
-// DATA
-
-const loading = ref(false)
-
// METHODS
async function refresh () {