From 358ad1fdcdcfcfe2196db75a4ecc1c30ed192200 Mon Sep 17 00:00:00 2001 From: NGPixel Date: Sun, 10 Apr 2022 23:06:40 -0400 Subject: [PATCH] refactor: update schema with new structure / naming --- server/graph/directives/auth.js | 56 ---- server/graph/directives/rate-limit.js | 5 - server/graph/index.js | 17 +- server/graph/resolvers/analytics.js | 12 +- server/graph/resolvers/asset.js | 14 +- server/graph/resolvers/authentication.js | 64 ++--- server/graph/resolvers/comment.js | 22 +- server/graph/resolvers/contribute.js | 28 -- server/graph/resolvers/folder.js | 32 --- server/graph/resolvers/group.js | 22 +- server/graph/resolvers/localization.js | 16 +- server/graph/resolvers/mail.js | 14 +- server/graph/resolvers/navigation.js | 16 +- server/graph/resolvers/page.js | 44 ++- server/graph/resolvers/rendering.js | 8 +- server/graph/resolvers/search.js | 66 +---- server/graph/resolvers/site.js | 116 +------- server/graph/resolvers/storage.js | 266 +++++++++++++----- server/graph/resolvers/system.js | 283 ++------------------ server/graph/resolvers/tag.js | 58 ---- server/graph/resolvers/theming.js | 62 ----- server/graph/resolvers/user.js | 114 ++++---- server/graph/scalars/date.js | 33 ++- server/graph/scalars/json.js | 32 ++- server/graph/scalars/uuid.js | 55 ++-- server/graph/schemas/analytics.graphql | 32 +-- server/graph/schemas/asset.graphql | 42 +-- server/graph/schemas/authentication.graphql | 52 ++-- server/graph/schemas/comment.graphql | 80 +++--- server/graph/schemas/common.graphql | 20 +- server/graph/schemas/contribute.graphql | 29 -- server/graph/schemas/group.graphql | 99 +++---- server/graph/schemas/localization.graphql | 50 +--- server/graph/schemas/logging.graphql | 64 ----- server/graph/schemas/mail.graphql | 50 ++-- server/graph/schemas/navigation.graphql | 36 +-- server/graph/schemas/page.graphql | 282 +++++++++---------- server/graph/schemas/rendering.graphql | 30 +-- server/graph/schemas/search.graphql | 46 +--- server/graph/schemas/site.graphql | 95 +------ server/graph/schemas/storage.graphql | 103 +++---- server/graph/schemas/system.graphql | 170 ++++++------ server/graph/schemas/theming.graphql | 54 ---- server/graph/schemas/user.graphql | 211 +++++++-------- ux/src/components/SiteActivateDialog.vue | 185 +++++++------ ux/src/components/SiteCreateDialog.vue | 8 +- ux/src/components/SiteDeleteDialog.vue | 151 ++++++----- ux/src/pages/AdminSites.vue | 4 - 48 files changed, 1186 insertions(+), 2162 deletions(-) delete mode 100644 server/graph/directives/auth.js delete mode 100644 server/graph/directives/rate-limit.js delete mode 100644 server/graph/resolvers/contribute.js delete mode 100644 server/graph/resolvers/folder.js delete mode 100644 server/graph/resolvers/tag.js delete mode 100644 server/graph/resolvers/theming.js delete mode 100644 server/graph/schemas/contribute.graphql delete mode 100644 server/graph/schemas/logging.graphql delete mode 100644 server/graph/schemas/theming.graphql 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 @@ - 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 @@ - 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 () {