Prechádzať zdrojové kódy

feat: core improvements + local fs provider + UI fixes

NGPixel 6 rokov pred
rodič
commit
2817c72ec3
65 zmenil súbory, kde vykonal 469 pridanie a 251 odobranie
  1. 1 1
      client/app.js
  2. 23 3
      client/components/admin/admin-general.vue
  3. 12 0
      client/components/admin/admin-stats.vue
  4. 15 4
      client/components/admin/admin-storage.vue
  5. 61 46
      client/components/admin/admin-utilities.vue
  6. 28 21
      client/components/common/nav-header.vue
  7. 1 1
      client/components/editor.vue
  8. 4 1
      client/components/editor/editor-modal-properties.vue
  9. 1 1
      client/scss/app.scss
  10. 18 1
      client/scss/pages/_welcome.scss
  11. 2 2
      server/core/auth.js
  12. 3 3
      server/core/config.js
  13. 1 1
      server/core/db.js
  14. 3 2
      server/core/kernel.js
  15. 1 1
      server/core/localization.js
  16. 1 1
      server/core/system.js
  17. 2 0
      server/db/migrations/2.0.0.js
  18. 4 4
      server/graph/resolvers/authentication.js
  19. 4 4
      server/graph/resolvers/comment.js
  20. 5 5
      server/graph/resolvers/document.js
  21. 6 6
      server/graph/resolvers/file.js
  22. 4 4
      server/graph/resolvers/folder.js
  23. 10 10
      server/graph/resolvers/group.js
  24. 1 1
      server/graph/resolvers/localization.js
  25. 6 6
      server/graph/resolvers/page.js
  26. 5 5
      server/graph/resolvers/right.js
  27. 2 2
      server/graph/resolvers/setting.js
  28. 20 9
      server/graph/resolvers/storage.js
  29. 1 1
      server/graph/resolvers/system.js
  30. 8 8
      server/graph/resolvers/tag.js
  31. 6 6
      server/graph/resolvers/user.js
  32. 3 3
      server/jobs/fetch-graph-locale.js
  33. 2 2
      server/jobs/sync-graph-locales.js
  34. 4 4
      server/models/authentication.js
  35. 3 3
      server/models/editors.js
  36. 0 0
      server/models/groups.js
  37. 0 0
      server/models/locales.js
  38. 1 1
      server/models/pageHistory.js
  39. 14 7
      server/models/pages.js
  40. 1 1
      server/models/settings.js
  41. 39 23
      server/models/storage.js
  42. 0 0
      server/models/tags.js
  43. 5 5
      server/models/users.js
  44. 1 1
      server/modules/authentication/auth0/authentication.js
  45. 1 1
      server/modules/authentication/azure/authentication.js
  46. 1 1
      server/modules/authentication/cas/authentication.js
  47. 1 1
      server/modules/authentication/discord/authentication.js
  48. 1 1
      server/modules/authentication/dropbox/authentication.js
  49. 1 1
      server/modules/authentication/facebook/authentication.js
  50. 1 1
      server/modules/authentication/github/authentication.js
  51. 1 1
      server/modules/authentication/google/authentication.js
  52. 1 1
      server/modules/authentication/ldap/authentication.js
  53. 1 1
      server/modules/authentication/local/authentication.js
  54. 1 1
      server/modules/authentication/microsoft/authentication.js
  55. 1 1
      server/modules/authentication/oauth2/authentication.js
  56. 1 1
      server/modules/authentication/slack/authentication.js
  57. 1 1
      server/modules/authentication/twitch/authentication.js
  58. 7 0
      server/modules/storage/box/definition.yml
  59. 23 0
      server/modules/storage/box/storage.js
  60. 5 2
      server/modules/storage/disk/definition.yml
  61. 52 7
      server/modules/storage/disk/storage.js
  62. 20 4
      server/modules/storage/git/definition.yml
  63. 10 10
      server/setup.js
  64. 6 4
      server/views/main/welcome.pug
  65. 1 1
      wiki.js

+ 1 - 1
client/app.js

@@ -110,7 +110,7 @@ Vue.prototype.Velocity = Velocity
 Vue.component('admin', () => import(/* webpackChunkName: "admin" */ './components/admin.vue'))
 Vue.component('admin', () => import(/* webpackChunkName: "admin" */ './components/admin.vue'))
 Vue.component('editor', () => import(/* webpackChunkName: "editor" */ './components/editor.vue'))
 Vue.component('editor', () => import(/* webpackChunkName: "editor" */ './components/editor.vue'))
 Vue.component('login', () => import(/* webpackMode: "eager" */ './components/login.vue'))
 Vue.component('login', () => import(/* webpackMode: "eager" */ './components/login.vue'))
-Vue.component('nav-header', () => import(/* webpackMode: "eager" */ './components/nav-header.vue'))
+Vue.component('nav-header', () => import(/* webpackMode: "eager" */ './components/common/nav-header.vue'))
 Vue.component('profile', () => import(/* webpackChunkName: "profile" */ './components/profile.vue'))
 Vue.component('profile', () => import(/* webpackChunkName: "profile" */ './components/profile.vue'))
 Vue.component('setup', () => import(/* webpackChunkName: "setup" */ './components/setup.vue'))
 Vue.component('setup', () => import(/* webpackChunkName: "setup" */ './components/setup.vue'))
 Vue.component('v-card-chin', () => import(/* webpackMode: "eager" */ './components/common/v-card-chin.vue'))
 Vue.component('v-card-chin', () => import(/* webpackMode: "eager" */ './components/common/v-card-chin.vue'))

+ 23 - 3
client/components/admin/admin-general.vue

@@ -13,14 +13,34 @@
                     v-toolbar-title
                     v-toolbar-title
                       .subheading {{ $t('admin:general.siteInfo') }}
                       .subheading {{ $t('admin:general.siteInfo') }}
                   v-subheader General
                   v-subheader General
-                  .px-3
+                  .px-3.pb-3
                     v-text-field(label='Site Title', required, :counter='50', v-model='siteTitle', prepend-icon='public')
                     v-text-field(label='Site Title', required, :counter='50', v-model='siteTitle', prepend-icon='public')
-                  v-divider.mt-3
+                  v-divider
                   v-subheader SEO
                   v-subheader SEO
-                  .px-3
+                  .px-3.pb-3
                     v-text-field(label='Site Description', :counter='255', prepend-icon='public')
                     v-text-field(label='Site Description', :counter='255', prepend-icon='public')
                     v-text-field(label='Site Keywords', :counter='255', prepend-icon='public')
                     v-text-field(label='Site Keywords', :counter='255', prepend-icon='public')
                     v-select(label='Meta Robots', chips, tags, :items='metaRobots', v-model='metaRobotsSelection', prepend-icon='public')
                     v-select(label='Meta Robots', chips, tags, :items='metaRobots', v-model='metaRobotsSelection', prepend-icon='public')
+                  v-divider
+                  v-subheader Analytics
+                  .px-3.pb-3
+                    v-text-field(
+                      label='Google Analytics ID'
+                      :counter='255'
+                      prepend-icon='public'
+                      persistent-hint
+                      hint='Property tracking ID for Google Analytics.'
+                      )
+                  v-divider
+                  v-subheader Footer Copyright
+                  .px-3.pb-3
+                    v-text-field(
+                      label='Company / Organization Name'
+                      :counter='255'
+                      prepend-icon='public'
+                      persistent-hint
+                      hint='Name to use when displaying copyright notice in the footer. Leave empty to hide.'
+                      )
                   v-card-chin
                   v-card-chin
                     v-spacer
                     v-spacer
                     v-btn(color='primary')
                     v-btn(color='primary')

+ 12 - 0
client/components/admin/admin-stats.vue

@@ -4,10 +4,22 @@
       v-flex(xs12)
       v-flex(xs12)
         .headline.primary--text Statistics
         .headline.primary--text Statistics
         .subheading.grey--text Useful information about your wiki
         .subheading.grey--text Useful information about your wiki
+        .pa-3
+          fingerprint-spinner(
+            :animation-duration='1500'
+            :size='128'
+            color='#e91e63'
+            )
+          .caption.pink--text.mt-3 Compiling latest data...
 </template>
 </template>
 
 
 <script>
 <script>
+import { FingerprintSpinner } from 'epic-spinners'
+
 export default {
 export default {
+  components: {
+    FingerprintSpinner
+  },
   data() {
   data() {
     return {}
     return {}
   }
   }

+ 15 - 4
client/components/admin/admin-storage.vue

@@ -18,7 +18,6 @@
               :key='tgt.key'
               :key='tgt.key'
               :label='tgt.title'
               :label='tgt.title'
               color='primary'
               color='primary'
-              :disabled='tgt.key === `local`'
               hide-details
               hide-details
             )
             )
 
 
@@ -30,26 +29,38 @@
             template(v-else, v-for='cfg in tgt.config')
             template(v-else, v-for='cfg in tgt.config')
               v-select(
               v-select(
                 v-if='cfg.value.type === "string" && cfg.value.enum'
                 v-if='cfg.value.type === "string" && cfg.value.enum'
+                outline
+                background-color='grey lighten-2'
                 :items='cfg.value.enum'
                 :items='cfg.value.enum'
                 :key='cfg.key'
                 :key='cfg.key'
-                :label='cfg.key | startCase'
+                :label='cfg.value.title'
                 v-model='cfg.value.value'
                 v-model='cfg.value.value'
                 prepend-icon='settings_applications'
                 prepend-icon='settings_applications'
+                :hint='cfg.value.hint ? cfg.value.hint : ""'
+                persistent-hint
+                :class='cfg.value.hint ? "mb-2" : ""'
               )
               )
               v-switch(
               v-switch(
                 v-else-if='cfg.value.type === "boolean"'
                 v-else-if='cfg.value.type === "boolean"'
                 :key='cfg.key'
                 :key='cfg.key'
-                :label='cfg.key | startCase'
+                :label='cfg.value.title'
                 v-model='cfg.value.value'
                 v-model='cfg.value.value'
                 color='primary'
                 color='primary'
                 prepend-icon='settings_applications'
                 prepend-icon='settings_applications'
+                :hint='cfg.value.hint ? cfg.value.hint : ""'
+                persistent-hint
                 )
                 )
               v-text-field(
               v-text-field(
                 v-else
                 v-else
+                outline
+                background-color='grey lighten-2'
                 :key='cfg.key'
                 :key='cfg.key'
-                :label='cfg.key | startCase'
+                :label='cfg.value.title'
                 v-model='cfg.value.value'
                 v-model='cfg.value.value'
                 prepend-icon='settings_applications'
                 prepend-icon='settings_applications'
+                :hint='cfg.value.hint ? cfg.value.hint : ""'
+                persistent-hint
+                :class='cfg.value.hint ? "mb-2" : ""'
                 )
                 )
             v-divider.mt-3
             v-divider.mt-3
             v-subheader.pl-0 Sync Direction
             v-subheader.pl-0 Sync Direction

+ 61 - 46
client/components/admin/admin-utilities.vue

@@ -36,12 +36,18 @@
                 v-subheader Rescan Modules
                 v-subheader Rescan Modules
                 v-card-text.pt-0.pl-4
                 v-card-text.pt-0.pl-4
                   .body-1 Look for new modules on disk. Existing configurations will be merged.
                   .body-1 Look for new modules on disk. Existing configurations will be merged.
-                  v-btn(depressed).ml-0
-                    v-icon(left, color='grey') youtube_searched_for
-                    span Authentication
-                  v-btn(depressed).ml-0
-                    v-icon(left, color='grey') youtube_searched_for
-                    span Storage
+                  v-select.mt-3(
+                    v-model='rescanModuleType'
+                    :items='moduleTypes'
+                    label='Modules Type'
+                    outline
+                    background-color='grey lighten-1'
+                    hide-details
+                    dense
+                  )
+                  v-btn.ml-0(color='primary', depressed, dark)
+                    v-icon(left) chevron_right
+                    span Rescan
             v-flex(xs12, sm6)
             v-flex(xs12, sm6)
               v-card
               v-card
                 v-toolbar(:color='$vuetify.dark ? "" : "grey darken-3"', dark, dense, flat)
                 v-toolbar(:color='$vuetify.dark ? "" : "grey darken-3"', dark, dense, flat)
@@ -58,50 +64,50 @@
                     .subheading Graph Endpoint
                     .subheading Graph Endpoint
                 v-card-text
                 v-card-text
                   .body-1 The Graph API Endpoint from which remote resources like locales, themes and plugins are fetched.
                   .body-1 The Graph API Endpoint from which remote resources like locales, themes and plugins are fetched.
-                  .caption Do not change unless you know what you're doing!
-                  v-text-field.my-2.grey.lighten-3(solo, flat, label='Graph Endpoint', value='https://graph.requarks.io')
+                  .caption.red--text Do not change unless you know what you're doing!
+                  v-text-field.my-2(outline, hide-details, background-color='grey lighten-1', label='Graph Endpoint', value='https://graph.requarks.io')
                   v-btn.ml-0(color='primary', depressed, dark)
                   v-btn.ml-0(color='primary', depressed, dark)
                     v-icon(left) chevron_right
                     v-icon(left) chevron_right
                     span Save
                     span Save
 
 
       v-tab-item(key='telemetry', :transition='false', :reverse-transition='false')
       v-tab-item(key='telemetry', :transition='false', :reverse-transition='false')
-        v-card.pa-3
+        v-card
           v-form
           v-form
-            v-subheader What is telemetry?
-            .body-1.pl-3 Telemetry allows the developers of Wiki.js to improve the software by collecting basic anonymized data about its usage and the host info. #[br] This is entirely optional and #[strong absolutely no] private data (such as content or personal data) is collected.
-            .body-1.pt-3.pl-3 For maximum privacy, a random client ID is generated every time Wiki.js is started. This ID is used to group requests together while keeping complete anonymity.
-            v-divider
-            v-subheader What is collected?
-            .body-1.pl-3 When telemetry is enabled, only the following data is transmitted:
-            v-list(dense)
-              v-list-tile
-                v-list-tile-avatar: v-icon info_outline
-                v-list-tile-content: v-list-tile-title.caption Version of Wiki.js installed
-              v-list-tile
-                v-list-tile-avatar: v-icon info_outline
-                v-list-tile-content: v-list-tile-title.caption Basic OS information (version, CPU cores, RAM available)
-              v-list-tile
-                v-list-tile-avatar: v-icon info_outline
-                v-list-tile-content: v-list-tile-title.caption Crash debug data
-              v-list-tile
-                v-list-tile-avatar: v-icon info_outline
-                v-list-tile-content: v-list-tile-title.caption Setup analytics (step reached)
-            .body-2.pl-3
-            v-divider
-            v-subheader Settings
-            .pl-3
-              v-switch(
-                v-model='telemetry',
-                label='Enable Telemetry',
-                :value='true',
-                color='primary',
-                hint='Allow Wiki.js to transmit telemetry data.',
-                persistent-hint
-              )
-              .subheading.mt-3.grey--text.text--darken-1 Client ID
-              .body-1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
-            v-divider
-            v-card-actions
+            v-card-text
+              v-subheader What is telemetry?
+              .body-1.pl-3 Telemetry allows the developers of Wiki.js to improve the software by collecting basic anonymized data about its usage and the host info. #[br] This is entirely optional and #[strong absolutely no] private data (such as content or personal data) is collected.
+              .body-1.pt-3.pl-3 For maximum privacy, a random client ID is generated every time Wiki.js is started. This ID is used to group requests together while keeping complete anonymity.
+              v-divider.my-3
+              v-subheader What is collected?
+              .body-1.pl-3 When telemetry is enabled, only the following data is transmitted:
+              v-list(dense)
+                v-list-tile
+                  v-list-tile-avatar: v-icon info_outline
+                  v-list-tile-content: v-list-tile-title.caption Version of Wiki.js installed
+                v-list-tile
+                  v-list-tile-avatar: v-icon info_outline
+                  v-list-tile-content: v-list-tile-title.caption Basic OS information (version, CPU cores, RAM available)
+                v-list-tile
+                  v-list-tile-avatar: v-icon info_outline
+                  v-list-tile-content: v-list-tile-title.caption Crash debug data
+                v-list-tile
+                  v-list-tile-avatar: v-icon info_outline
+                  v-list-tile-content: v-list-tile-title.caption Setup analytics (step reached)
+              .body-2.pl-3
+              v-divider.my-3
+              v-subheader Settings
+              .pl-3
+                v-switch.mt-0(
+                  v-model='telemetry',
+                  label='Enable Telemetry',
+                  :value='true',
+                  color='primary',
+                  hint='Allow Wiki.js to transmit telemetry data.',
+                  persistent-hint
+                )
+                .subheading.mt-3.grey--text.text--darken-1 Client ID
+                .body-1 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+            v-card-chin
               v-btn(color='primary')
               v-btn(color='primary')
                 v-icon(left) chevron_right
                 v-icon(left) chevron_right
                 | Save Changes
                 | Save Changes
@@ -117,13 +123,13 @@
           v-btn.ml-3.mt-3(depressed, dark, color='grey darken-2', href='https://github.com/Requarks/wiki/issues', target='_blank')
           v-btn.ml-3.mt-3(depressed, dark, color='grey darken-2', href='https://github.com/Requarks/wiki/issues', target='_blank')
             icon-github-circle.mr-3(fillColor='#FFFFFF')
             icon-github-circle.mr-3(fillColor='#FFFFFF')
             span Submit an issue
             span Submit an issue
-          v-divider
+          v-divider.my-3
           v-subheader Suggest a New Feature / Enhancement
           v-subheader Suggest a New Feature / Enhancement
           .body-1.pl-3 Have an idea for a new feature or something that could be improved?
           .body-1.pl-3 Have an idea for a new feature or something that could be improved?
           v-btn.ml-3.mt-3(depressed, dark, color='indigo', href='https://requests.requarks.io/wiki', target='_blank')
           v-btn.ml-3.mt-3(depressed, dark, color='indigo', href='https://requests.requarks.io/wiki', target='_blank')
             v-icon(left) lightbulb_outline
             v-icon(left) lightbulb_outline
             span Submit an idea
             span Submit an idea
-          v-divider
+          v-divider.my-3
           v-subheader Questions / Comments
           v-subheader Questions / Comments
           .body-1.pl-3 Join our gitter channel. We are very active and friendly!
           .body-1.pl-3 Join our gitter channel. We are very active and friendly!
           v-btn.ml-3.mt-3(depressed, dark, color='pink', href='https://gitter.im/Requarks/wiki', target='_blank')
           v-btn.ml-3.mt-3(depressed, dark, color='pink', href='https://gitter.im/Requarks/wiki', target='_blank')
@@ -144,6 +150,15 @@ export default {
   data() {
   data() {
     return {
     return {
       tab: '0',
       tab: '0',
+      moduleTypes: [
+        { text: 'Authentication', value: 'authentication' },
+        { text: 'Editor', value: 'editor' },
+        { text: 'Logging', value: 'logging' },
+        { text: 'Rendering', value: 'renderer' },
+        { text: 'Search Engine', value: 'search' },
+        { text: 'Storage', value: 'storage' }
+      ],
+      rescanModuleType: 'authentication',
       telemetry: true
       telemetry: true
     }
     }
   },
   },

+ 28 - 21
client/components/nav-header.vue → client/components/common/nav-header.vue

@@ -1,5 +1,5 @@
 <template lang='pug'>
 <template lang='pug'>
-  v-toolbar(color='black', dark, app, clipped-left, fixed, flat, dense)
+  v-toolbar(color='black', dark, app, clipped-left, fixed, flat, :dense='dense')
     v-menu(open-on-hover, offset-y, bottom, left, nudge-top='-18', min-width='250')
     v-menu(open-on-hover, offset-y, bottom, left, nudge-top='-18', min-width='250')
       v-toolbar-side-icon(slot='activator')
       v-toolbar-side-icon(slot='activator')
         v-icon view_module
         v-icon view_module
@@ -41,15 +41,15 @@
         v-if='searchIsShown',
         v-if='searchIsShown',
         v-model='search',
         v-model='search',
         clearable,
         clearable,
-        color='blue',
+        color='white',
         label='Search...',
         label='Search...',
         single-line,
         single-line,
+        solo
+        flat
         hide-details,
         hide-details,
-        append-icon='search',
-        :append-icon-cb='searchEnter',
+        prepend-inner-icon='search',
         :loading='searchIsLoading',
         :loading='searchIsLoading',
-        @keyup.enter='searchEnter',
-        @keyup.esc='searchToggle'
+        @keyup.enter='searchEnter'
       )
       )
         v-progress-linear(
         v-progress-linear(
           indeterminate,
           indeterminate,
@@ -58,11 +58,9 @@
           color='blue'
           color='blue'
         )
         )
     v-spacer
     v-spacer
-    v-progress-circular.mr-3(indeterminate, color='blue', :size='22', :width='2' v-show='isLoading')
+    .navHeaderLoading.mr-3
+      v-progress-circular(indeterminate, color='blue', :size='22', :width='2' v-show='isLoading')
     slot(name='actions')
     slot(name='actions')
-    transition(name='navHeaderSearch')
-      v-btn(icon, @click='searchToggle', v-if='!searchIsShown')
-        v-icon(color='grey') search
     v-btn(icon, href='/a')
     v-btn(icon, href='/a')
       v-icon(color='grey') settings
       v-icon(color='grey') settings
     v-menu(offset-y, min-width='300')
     v-menu(offset-y, min-width='300')
@@ -90,11 +88,21 @@ import { mapGetters } from 'vuex'
 /* global siteConfig */
 /* global siteConfig */
 
 
 export default {
 export default {
+  props: {
+    dense: {
+      type: Boolean,
+      default: false
+    },
+    hideSearch: {
+      type: Boolean,
+      default: false
+    }
+  },
   data() {
   data() {
     return {
     return {
       menuIsShown: true,
       menuIsShown: true,
       searchIsLoading: false,
       searchIsLoading: false,
-      searchIsShown: false,
+      searchIsShown: true,
       search: ''
       search: ''
     }
     }
   },
   },
@@ -102,16 +110,12 @@ export default {
     ...mapGetters(['isLoading']),
     ...mapGetters(['isLoading']),
     title() { return siteConfig.title }
     title() { return siteConfig.title }
   },
   },
+  created() {
+    if (this.hideSearch || this.dense) {
+      this.searchIsShown = false
+    }
+  },
   methods: {
   methods: {
-    searchToggle() {
-      this.searchIsLoading = false
-      this.searchIsShown = !this.searchIsShown
-      if (this.searchIsShown) {
-        this.$nextTick(() => {
-          this.$refs.searchField.focus()
-        })
-      }
-    },
     searchEnter() {
     searchEnter() {
       this.searchIsLoading = true
       this.searchIsLoading = true
     }
     }
@@ -130,7 +134,10 @@ export default {
   }
   }
   &-enter, &-leave-to {
   &-enter, &-leave-to {
     opacity: 0;
     opacity: 0;
-    transform: translateY(-25px);
+    transform: scale(.7, .7);
   }
   }
 }
 }
+.navHeaderLoading { // To avoid search bar jumping
+  width: 22px;
+}
 </style>
 </style>

+ 1 - 1
client/components/editor.vue

@@ -1,6 +1,6 @@
 <template lang="pug">
 <template lang="pug">
   .editor
   .editor
-    nav-header
+    nav-header(dense)
       template(slot='actions')
       template(slot='actions')
         v-btn(outline, color='green', @click.native.stop='save')
         v-btn(outline, color='green', @click.native.stop='save')
           v-icon(color='green', left) check
           v-icon(color='green', left) check

+ 4 - 1
client/components/editor/editor-modal-properties.vue

@@ -57,6 +57,7 @@
                 :items='namespaces'
                 :items='namespaces'
                 v-model='locale'
                 v-model='locale'
                 hide-details
                 hide-details
+                :disabled='mode !== "create"'
               )
               )
             v-flex(xs12, md10)
             v-flex(xs12, md10)
               v-text-field(
               v-text-field(
@@ -68,6 +69,7 @@
                 hint='Do not include any leading or trailing slashes.'
                 hint='Do not include any leading or trailing slashes.'
                 persistent-hint
                 persistent-hint
                 @click:append='showPathSelector'
                 @click:append='showPathSelector'
+                :disabled='mode !== "create"'
                 )
                 )
         v-combobox(
         v-combobox(
           background-color='grey lighten-2'
           background-color='grey lighten-2'
@@ -186,7 +188,7 @@
 
 
 <script>
 <script>
 import _ from 'lodash'
 import _ from 'lodash'
-import { sync } from 'vuex-pathify'
+import { sync, get } from 'vuex-pathify'
 
 
 export default {
 export default {
   data() {
   data() {
@@ -204,6 +206,7 @@ export default {
     }
     }
   },
   },
   computed: {
   computed: {
+    mode: get('editor/mode'),
     title: sync('editor/title'),
     title: sync('editor/title'),
     description: sync('editor/description'),
     description: sync('editor/description'),
     locale: sync('editor/locale'),
     locale: sync('editor/locale'),

+ 1 - 1
client/scss/app.scss

@@ -3,7 +3,7 @@
 @import "base/base";
 @import "base/base";
 @import 'base/icons';
 @import 'base/icons';
 
 
-// @import "../libs/animate/animate";
+@import "../libs/animate/animate";
 
 
 @import 'components/markdown-content';
 @import 'components/markdown-content';
 @import 'components/btn';
 @import 'components/btn';

+ 18 - 1
client/scss/pages/_welcome.scss

@@ -33,11 +33,28 @@
     }
     }
   }
   }
 
 
+  &::after {
+    content: '';
+    display: block;
+    width: 95vw;
+    height: 95vh;
+    border: 3px dotted rgba(255,255,255,.2);
+    border-radius: 50px;
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    margin: auto;
+    z-index: 1;
+  }
+
   img {
   img {
     width: 500px;
     width: 500px;
     filter: grayscale(100%) brightness(160%);
     filter: grayscale(100%) brightness(160%);
     margin-bottom: 3rem;
     margin-bottom: 3rem;
     z-index: 2;
     z-index: 2;
+    animation-duration: 3s;
 
 
     @include until($tablet) {
     @include until($tablet) {
       width: 300px;
       width: 300px;
@@ -52,7 +69,7 @@
     margin-bottom: 3rem;
     margin-bottom: 3rem;
     z-index: 2;
     z-index: 2;
   }
   }
-  .button {
+  .v-btn {
     z-index: 2;
     z-index: 2;
   }
   }
 }
 }

+ 2 - 2
server/core/auth.js

@@ -17,7 +17,7 @@ module.exports = {
     })
     })
 
 
     passport.deserializeUser(function (id, done) {
     passport.deserializeUser(function (id, done) {
-      WIKI.db.users.query().findById(id).then((user) => {
+      WIKI.models.users.query().findById(id).then((user) => {
         if (user) {
         if (user) {
           done(null, user)
           done(null, user)
         } else {
         } else {
@@ -40,7 +40,7 @@ module.exports = {
       _.forEach(currentStrategies, stg => { passport.unuse(stg) })
       _.forEach(currentStrategies, stg => { passport.unuse(stg) })
 
 
       // Load enabled strategies
       // Load enabled strategies
-      const enabledStrategies = await WIKI.db.authentication.getStrategies()
+      const enabledStrategies = await WIKI.models.authentication.getStrategies()
       for (let idx in enabledStrategies) {
       for (let idx in enabledStrategies) {
         const stg = enabledStrategies[idx]
         const stg = enabledStrategies[idx]
         if (!stg.isEnabled) { continue }
         if (!stg.isEnabled) { continue }

+ 3 - 3
server/core/config.js

@@ -52,7 +52,7 @@ module.exports = {
    * Load config from DB
    * Load config from DB
    */
    */
   async loadFromDb() {
   async loadFromDb() {
-    let conf = await WIKI.db.settings.getConfig()
+    let conf = await WIKI.models.settings.getConfig()
     if (conf) {
     if (conf) {
       WIKI.config = _.defaultsDeep(conf, WIKI.config)
       WIKI.config = _.defaultsDeep(conf, WIKI.config)
     } else {
     } else {
@@ -73,9 +73,9 @@ module.exports = {
         if (!_.isPlainObject(value)) {
         if (!_.isPlainObject(value)) {
           value = { v: value }
           value = { v: value }
         }
         }
-        let affectedRows = await WIKI.db.settings.query().patch({ value }).where('key', key)
+        let affectedRows = await WIKI.models.settings.query().patch({ value }).where('key', key)
         if (affectedRows === 0 && value) {
         if (affectedRows === 0 && value) {
-          await WIKI.db.settings.query().insert({ key, value })
+          await WIKI.models.settings.query().insert({ key, value })
         }
         }
       }
       }
     } catch (err) {
     } catch (err) {

+ 1 - 1
server/core/db.js

@@ -74,7 +74,7 @@ module.exports = {
 
 
     // Load DB Models
     // Load DB Models
 
 
-    const models = autoload(path.join(WIKI.SERVERPATH, 'db/models'))
+    const models = autoload(path.join(WIKI.SERVERPATH, 'models'))
 
 
     // Set init tasks
     // Set init tasks
 
 

+ 3 - 2
server/core/kernel.js

@@ -6,7 +6,7 @@ module.exports = {
     WIKI.logger.info('= Wiki.js =============================')
     WIKI.logger.info('= Wiki.js =============================')
     WIKI.logger.info('=======================================')
     WIKI.logger.info('=======================================')
 
 
-    WIKI.db = require('./db').init()
+    WIKI.models = require('./db').init()
     WIKI.redis = require('./redis').init()
     WIKI.redis = require('./redis').init()
     WIKI.queue = require('./queue').init()
     WIKI.queue = require('./queue').init()
 
 
@@ -18,7 +18,7 @@ module.exports = {
    */
    */
   async preBootMaster() {
   async preBootMaster() {
     try {
     try {
-      await WIKI.db.onReady
+      await WIKI.models.onReady
       await WIKI.configSvc.loadFromDb()
       await WIKI.configSvc.loadFromDb()
       await WIKI.queue.clean()
       await WIKI.queue.clean()
     } catch (err) {
     } catch (err) {
@@ -48,6 +48,7 @@ module.exports = {
    */
    */
   async postBootMaster() {
   async postBootMaster() {
     await WIKI.auth.activateStrategies()
     await WIKI.auth.activateStrategies()
+    await WIKI.models.storage.refreshTargetsFromDisk()
     await WIKI.queue.start()
     await WIKI.queue.start()
   }
   }
 }
 }

+ 1 - 1
server/core/localization.js

@@ -52,7 +52,7 @@ module.exports = {
     }
     }
   },
   },
   async loadLocale(locale, opts = { silent: false }) {
   async loadLocale(locale, opts = { silent: false }) {
-    const res = await WIKI.db.locales.query().findOne('code', locale)
+    const res = await WIKI.models.locales.query().findOne('code', locale)
     if (res) {
     if (res) {
       if (_.isPlainObject(res.strings)) {
       if (_.isPlainObject(res.strings)) {
         _.forOwn(res.strings, (data, ns) => {
         _.forOwn(res.strings, (data, ns) => {

+ 1 - 1
server/core/system.js

@@ -45,7 +45,7 @@ module.exports = {
               $not: 'guest'
               $not: 'guest'
             }
             }
           }).toArray()
           }).toArray()
-          await WIKI.db.User.bulkCreate(_.map(userData, usr => {
+          await WIKI.models.User.bulkCreate(_.map(userData, usr => {
             return {
             return {
               email: usr.email,
               email: usr.email,
               name: usr.name || 'Imported User',
               name: usr.name || 'Imported User',

+ 2 - 0
server/db/migrations/2.0.0.js

@@ -79,6 +79,7 @@ exports.up = knex => {
       table.string('publishStartDate')
       table.string('publishStartDate')
       table.string('publishEndDate')
       table.string('publishEndDate')
       table.text('content')
       table.text('content')
+      table.string('contentType').notNullable()
       table.string('createdAt').notNullable()
       table.string('createdAt').notNullable()
     })
     })
     // PAGES -------------------------------
     // PAGES -------------------------------
@@ -92,6 +93,7 @@ exports.up = knex => {
       table.string('publishStartDate')
       table.string('publishStartDate')
       table.string('publishEndDate')
       table.string('publishEndDate')
       table.text('content')
       table.text('content')
+      table.string('contentType').notNullable()
       table.string('createdAt').notNullable()
       table.string('createdAt').notNullable()
       table.string('updatedAt').notNullable()
       table.string('updatedAt').notNullable()
     })
     })

+ 4 - 4
server/graph/resolvers/authentication.js

@@ -16,7 +16,7 @@ module.exports = {
   },
   },
   AuthenticationQuery: {
   AuthenticationQuery: {
     async strategies(obj, args, context, info) {
     async strategies(obj, args, context, info) {
-      let strategies = await WIKI.db.authentication.getStrategies()
+      let strategies = await WIKI.models.authentication.getStrategies()
       strategies = strategies.map(stg => ({
       strategies = strategies.map(stg => ({
         ...stg,
         ...stg,
         config: _.sortBy(_.transform(stg.config, (res, value, key) => {
         config: _.sortBy(_.transform(stg.config, (res, value, key) => {
@@ -31,7 +31,7 @@ module.exports = {
   AuthenticationMutation: {
   AuthenticationMutation: {
     async login(obj, args, context) {
     async login(obj, args, context) {
       try {
       try {
-        let authResult = await WIKI.db.users.login(args, context)
+        let authResult = await WIKI.models.users.login(args, context)
         return {
         return {
           ...authResult,
           ...authResult,
           responseResult: graphHelper.generateSuccess('Login success')
           responseResult: graphHelper.generateSuccess('Login success')
@@ -42,7 +42,7 @@ module.exports = {
     },
     },
     async loginTFA(obj, args, context) {
     async loginTFA(obj, args, context) {
       try {
       try {
-        let authResult = await WIKI.db.users.loginTFA(args, context)
+        let authResult = await WIKI.models.users.loginTFA(args, context)
         return {
         return {
           ...authResult,
           ...authResult,
           responseResult: graphHelper.generateSuccess('TFA success')
           responseResult: graphHelper.generateSuccess('TFA success')
@@ -54,7 +54,7 @@ module.exports = {
     async updateStrategies(obj, args, context) {
     async updateStrategies(obj, args, context) {
       try {
       try {
         for (let str of args.strategies) {
         for (let str of args.strategies) {
-          await WIKI.db.authentication.query().patch({
+          await WIKI.models.authentication.query().patch({
             isEnabled: str.isEnabled,
             isEnabled: str.isEnabled,
             config: _.reduce(str.config, (result, value, key) => {
             config: _.reduce(str.config, (result, value, key) => {
               _.set(result, `${value.key}.value`, value.value)
               _.set(result, `${value.key}.value`, value.value)

+ 4 - 4
server/graph/resolvers/comment.js

@@ -4,19 +4,19 @@
 module.exports = {
 module.exports = {
   Query: {
   Query: {
     comments(obj, args, context, info) {
     comments(obj, args, context, info) {
-      return WIKI.db.Comment.findAll({ where: args })
+      return WIKI.models.Comment.findAll({ where: args })
     }
     }
   },
   },
   Mutation: {
   Mutation: {
     createComment(obj, args) {
     createComment(obj, args) {
-      return WIKI.db.Comment.create({
+      return WIKI.models.Comment.create({
         content: args.content,
         content: args.content,
         author: args.userId,
         author: args.userId,
         document: args.documentId
         document: args.documentId
       })
       })
     },
     },
     deleteComment(obj, args) {
     deleteComment(obj, args) {
-      return WIKI.db.Comment.destroy({
+      return WIKI.models.Comment.destroy({
         where: {
         where: {
           id: args.id
           id: args.id
         },
         },
@@ -24,7 +24,7 @@ module.exports = {
       })
       })
     },
     },
     modifyComment(obj, args) {
     modifyComment(obj, args) {
-      return WIKI.db.Comment.update({
+      return WIKI.models.Comment.update({
         content: args.content
         content: args.content
       }, {
       }, {
         where: { id: args.id }
         where: { id: args.id }

+ 5 - 5
server/graph/resolvers/document.js

@@ -4,15 +4,15 @@
 module.exports = {
 module.exports = {
   Query: {
   Query: {
     documents(obj, args, context, info) {
     documents(obj, args, context, info) {
-      return WIKI.db.Document.findAll({ where: args })
+      return WIKI.models.Document.findAll({ where: args })
     }
     }
   },
   },
   Mutation: {
   Mutation: {
     createDocument(obj, args) {
     createDocument(obj, args) {
-      return WIKI.db.Document.create(args)
+      return WIKI.models.Document.create(args)
     },
     },
     deleteDocument(obj, args) {
     deleteDocument(obj, args) {
-      return WIKI.db.Document.destroy({
+      return WIKI.models.Document.destroy({
         where: {
         where: {
           id: args.id
           id: args.id
         },
         },
@@ -20,7 +20,7 @@ module.exports = {
       })
       })
     },
     },
     modifyDocument(obj, args) {
     modifyDocument(obj, args) {
-      return WIKI.db.Document.update({
+      return WIKI.models.Document.update({
         title: args.title,
         title: args.title,
         subtitle: args.subtitle
         subtitle: args.subtitle
       }, {
       }, {
@@ -28,7 +28,7 @@ module.exports = {
       })
       })
     },
     },
     moveDocument(obj, args) {
     moveDocument(obj, args) {
-      return WIKI.db.Document.update({
+      return WIKI.models.Document.update({
         path: args.path
         path: args.path
       }, {
       }, {
         where: { id: args.id }
         where: { id: args.id }

+ 6 - 6
server/graph/resolvers/file.js

@@ -6,16 +6,16 @@ const gql = require('graphql')
 module.exports = {
 module.exports = {
   Query: {
   Query: {
     files(obj, args, context, info) {
     files(obj, args, context, info) {
-      return WIKI.db.File.findAll({ where: args })
+      return WIKI.models.File.findAll({ where: args })
     }
     }
   },
   },
   Mutation: {
   Mutation: {
     uploadFile(obj, args) {
     uploadFile(obj, args) {
       // todo
       // todo
-      return WIKI.db.File.create(args)
+      return WIKI.models.File.create(args)
     },
     },
     deleteFile(obj, args) {
     deleteFile(obj, args) {
-      return WIKI.db.File.destroy({
+      return WIKI.models.File.destroy({
         where: {
         where: {
           id: args.id
           id: args.id
         },
         },
@@ -23,18 +23,18 @@ module.exports = {
       })
       })
     },
     },
     renameFile(obj, args) {
     renameFile(obj, args) {
-      return WIKI.db.File.update({
+      return WIKI.models.File.update({
         filename: args.filename
         filename: args.filename
       }, {
       }, {
         where: { id: args.id }
         where: { id: args.id }
       })
       })
     },
     },
     moveFile(obj, args) {
     moveFile(obj, args) {
-      return WIKI.db.File.findById(args.fileId).then(fl => {
+      return WIKI.models.File.findById(args.fileId).then(fl => {
         if (!fl) {
         if (!fl) {
           throw new gql.GraphQLError('Invalid File ID')
           throw new gql.GraphQLError('Invalid File ID')
         }
         }
-        return WIKI.db.Folder.findById(args.folderId).then(fld => {
+        return WIKI.models.Folder.findById(args.folderId).then(fld => {
           if (!fld) {
           if (!fld) {
             throw new gql.GraphQLError('Invalid Folder ID')
             throw new gql.GraphQLError('Invalid Folder ID')
           }
           }

+ 4 - 4
server/graph/resolvers/folder.js

@@ -4,15 +4,15 @@
 module.exports = {
 module.exports = {
   Query: {
   Query: {
     folders(obj, args, context, info) {
     folders(obj, args, context, info) {
-      return WIKI.db.Folder.findAll({ where: args })
+      return WIKI.models.Folder.findAll({ where: args })
     }
     }
   },
   },
   Mutation: {
   Mutation: {
     createFolder(obj, args) {
     createFolder(obj, args) {
-      return WIKI.db.Folder.create(args)
+      return WIKI.models.Folder.create(args)
     },
     },
     deleteFolder(obj, args) {
     deleteFolder(obj, args) {
-      return WIKI.db.Folder.destroy({
+      return WIKI.models.Folder.destroy({
         where: {
         where: {
           id: args.id
           id: args.id
         },
         },
@@ -20,7 +20,7 @@ module.exports = {
       })
       })
     },
     },
     renameFolder(obj, args) {
     renameFolder(obj, args) {
-      return WIKI.db.Folder.update({
+      return WIKI.models.Folder.update({
         name: args.name
         name: args.name
       }, {
       }, {
         where: { id: args.id }
         where: { id: args.id }

+ 10 - 10
server/graph/resolvers/group.js

@@ -13,22 +13,22 @@ module.exports = {
   },
   },
   GroupQuery: {
   GroupQuery: {
     async list(obj, args, context, info) {
     async list(obj, args, context, info) {
-      return WIKI.db.groups.query().select(
+      return WIKI.models.groups.query().select(
         'groups.*',
         'groups.*',
-        WIKI.db.groups.relatedQuery('users').count().as('userCount')
+        WIKI.models.groups.relatedQuery('users').count().as('userCount')
       )
       )
     },
     },
     async single(obj, args, context, info) {
     async single(obj, args, context, info) {
-      return WIKI.db.groups.query().findById(args.id)
+      return WIKI.models.groups.query().findById(args.id)
     }
     }
   },
   },
   GroupMutation: {
   GroupMutation: {
     async assignUser(obj, args) {
     async assignUser(obj, args) {
-      const grp = await WIKI.db.groups.query().findById(args.groupId)
+      const grp = await WIKI.models.groups.query().findById(args.groupId)
       if (!grp) {
       if (!grp) {
         throw new gql.GraphQLError('Invalid Group ID')
         throw new gql.GraphQLError('Invalid Group ID')
       }
       }
-      const usr = await WIKI.db.users.query().findById(args.userId)
+      const usr = await WIKI.models.users.query().findById(args.userId)
       if (!usr) {
       if (!usr) {
         throw new gql.GraphQLError('Invalid User ID')
         throw new gql.GraphQLError('Invalid User ID')
       }
       }
@@ -38,7 +38,7 @@ module.exports = {
       }
       }
     },
     },
     async create(obj, args) {
     async create(obj, args) {
-      const group = await WIKI.db.groups.query().insertAndFetch({
+      const group = await WIKI.models.groups.query().insertAndFetch({
         name: args.name
         name: args.name
       })
       })
       return {
       return {
@@ -47,17 +47,17 @@ module.exports = {
       }
       }
     },
     },
     async delete(obj, args) {
     async delete(obj, args) {
-      await WIKI.db.groups.query().deleteById(args.id)
+      await WIKI.models.groups.query().deleteById(args.id)
       return {
       return {
         responseResult: graphHelper.generateSuccess('Group has been deleted.')
         responseResult: graphHelper.generateSuccess('Group has been deleted.')
       }
       }
     },
     },
     async unassignUser(obj, args) {
     async unassignUser(obj, args) {
-      const grp = await WIKI.db.groups.query().findById(args.groupId)
+      const grp = await WIKI.models.groups.query().findById(args.groupId)
       if (!grp) {
       if (!grp) {
         throw new gql.GraphQLError('Invalid Group ID')
         throw new gql.GraphQLError('Invalid Group ID')
       }
       }
-      const usr = await WIKI.db.users.query().findById(args.userId)
+      const usr = await WIKI.models.users.query().findById(args.userId)
       if (!usr) {
       if (!usr) {
         throw new gql.GraphQLError('Invalid User ID')
         throw new gql.GraphQLError('Invalid User ID')
       }
       }
@@ -67,7 +67,7 @@ module.exports = {
       }
       }
     },
     },
     async update(obj, args) {
     async update(obj, args) {
-      await WIKI.db.groups.query().patch({ name: args.name }).where('id', args.id)
+      await WIKI.models.groups.query().patch({ name: args.name }).where('id', args.id)
       return {
       return {
         responseResult: graphHelper.generateSuccess('Group has been updated.')
         responseResult: graphHelper.generateSuccess('Group has been updated.')
       }
       }

+ 1 - 1
server/graph/resolvers/localization.js

@@ -13,7 +13,7 @@ module.exports = {
   LocalizationQuery: {
   LocalizationQuery: {
     async locales(obj, args, context, info) {
     async locales(obj, args, context, info) {
       let remoteLocales = await WIKI.redis.get('locales')
       let remoteLocales = await WIKI.redis.get('locales')
-      let localLocales = await WIKI.db.locales.query().select('id', 'code', 'isRTL', 'name', 'nativeName', 'createdAt', 'updatedAt')
+      let localLocales = await WIKI.models.locales.query().select('id', 'code', 'isRTL', 'name', 'nativeName', 'createdAt', 'updatedAt')
       remoteLocales = (remoteLocales) ? JSON.parse(remoteLocales) : localLocales
       remoteLocales = (remoteLocales) ? JSON.parse(remoteLocales) : localLocales
       return _.map(remoteLocales, rl => {
       return _.map(remoteLocales, rl => {
         let isInstalled = _.some(localLocales, ['code', rl.code])
         let isInstalled = _.some(localLocales, ['code', rl.code])

+ 6 - 6
server/graph/resolvers/page.js

@@ -11,18 +11,18 @@ module.exports = {
   },
   },
   PageQuery: {
   PageQuery: {
     async list(obj, args, context, info) {
     async list(obj, args, context, info) {
-      return WIKI.db.pages.query().select(
+      return WIKI.models.pages.query().select(
         'pages.*',
         'pages.*',
-        WIKI.db.pages.relatedQuery('users').count().as('userCount')
+        WIKI.models.pages.relatedQuery('users').count().as('userCount')
       )
       )
     },
     },
     async single(obj, args, context, info) {
     async single(obj, args, context, info) {
-      return WIKI.db.pages.query().findById(args.id)
+      return WIKI.models.pages.query().findById(args.id)
     }
     }
   },
   },
   PageMutation: {
   PageMutation: {
     async create(obj, args, context) {
     async create(obj, args, context) {
-      const page = await WIKI.db.pages.createPage({
+      const page = await WIKI.models.pages.createPage({
         ...args,
         ...args,
         authorId: context.req.user.id
         authorId: context.req.user.id
       })
       })
@@ -32,13 +32,13 @@ module.exports = {
       }
       }
     },
     },
     async delete(obj, args) {
     async delete(obj, args) {
-      await WIKI.db.groups.query().deleteById(args.id)
+      await WIKI.models.groups.query().deleteById(args.id)
       return {
       return {
         responseResult: graphHelper.generateSuccess('Page has been deleted.')
         responseResult: graphHelper.generateSuccess('Page has been deleted.')
       }
       }
     },
     },
     async update(obj, args, context) {
     async update(obj, args, context) {
-      const page = await WIKI.db.pages.updatePage({
+      const page = await WIKI.models.pages.updatePage({
         ...args,
         ...args,
         authorId: context.req.user.id
         authorId: context.req.user.id
       })
       })

+ 5 - 5
server/graph/resolvers/right.js

@@ -6,16 +6,16 @@ const gql = require('graphql')
 module.exports = {
 module.exports = {
   Query: {
   Query: {
     rights(obj, args, context, info) {
     rights(obj, args, context, info) {
-      return WIKI.db.Right.findAll({ where: args })
+      return WIKI.models.Right.findAll({ where: args })
     }
     }
   },
   },
   Mutation: {
   Mutation: {
     addRightToGroup(obj, args) {
     addRightToGroup(obj, args) {
-      return WIKI.db.Group.findById(args.groupId).then(grp => {
+      return WIKI.models.Group.findById(args.groupId).then(grp => {
         if (!grp) {
         if (!grp) {
           throw new gql.GraphQLError('Invalid Group ID')
           throw new gql.GraphQLError('Invalid Group ID')
         }
         }
-        return WIKI.db.Right.create({
+        return WIKI.models.Right.create({
           path: args.path,
           path: args.path,
           role: args.role,
           role: args.role,
           exact: args.exact,
           exact: args.exact,
@@ -25,7 +25,7 @@ module.exports = {
       })
       })
     },
     },
     removeRightFromGroup(obj, args) {
     removeRightFromGroup(obj, args) {
-      return WIKI.db.Right.destroy({
+      return WIKI.models.Right.destroy({
         where: {
         where: {
           id: args.rightId
           id: args.rightId
         },
         },
@@ -33,7 +33,7 @@ module.exports = {
       })
       })
     },
     },
     modifyRight(obj, args) {
     modifyRight(obj, args) {
-      return WIKI.db.Right.update({
+      return WIKI.models.Right.update({
         path: args.path,
         path: args.path,
         role: args.role,
         role: args.role,
         exact: args.exact,
         exact: args.exact,

+ 2 - 2
server/graph/resolvers/setting.js

@@ -6,7 +6,7 @@ const _ = require('lodash')
 module.exports = {
 module.exports = {
   Query: {
   Query: {
     settings(obj, args, context, info) {
     settings(obj, args, context, info) {
-      return WIKI.db.Setting.findAll({ where: args, raw: true }).then(entries => {
+      return WIKI.models.Setting.findAll({ where: args, raw: true }).then(entries => {
         return _.map(entries, entry => {
         return _.map(entries, entry => {
           entry.config = JSON.stringify(entry.config)
           entry.config = JSON.stringify(entry.config)
           return entry
           return entry
@@ -16,7 +16,7 @@ module.exports = {
   },
   },
   Mutation: {
   Mutation: {
     setConfigEntry(obj, args) {
     setConfigEntry(obj, args) {
-      return WIKI.db.Setting.update({
+      return WIKI.models.Setting.update({
         value: args.value
         value: args.value
       }, { where: { key: args.key } })
       }, { where: { key: args.key } })
     }
     }

+ 20 - 9
server/graph/resolvers/storage.js

@@ -12,13 +12,24 @@ module.exports = {
   },
   },
   StorageQuery: {
   StorageQuery: {
     async targets(obj, args, context, info) {
     async targets(obj, args, context, info) {
-      let targets = await WIKI.db.storage.getTargets()
-      targets = targets.map(tgt => ({
-        ...tgt,
-        config: _.sortBy(_.transform(tgt.config, (res, value, key) => {
-          res.push({ key, value: JSON.stringify(value) })
-        }, []), 'key')
-      }))
+      let targets = await WIKI.models.storage.getTargets()
+      targets = targets.map(tgt => {
+        const targetInfo = _.find(WIKI.data.storage, ['key', tgt.key]) || {}
+        console.info(targetInfo)
+        return {
+          ...tgt,
+          config: _.sortBy(_.transform(tgt.config, (res, value, key) => {
+            const configData = _.get(targetInfo.props, key, {})
+            res.push({
+              key,
+              value: JSON.stringify({
+                ...configData,
+                value
+              })
+            })
+          }, []), 'key')
+        }
+      })
       if (args.filter) { targets = graphHelper.filter(targets, args.filter) }
       if (args.filter) { targets = graphHelper.filter(targets, args.filter) }
       if (args.orderBy) { targets = graphHelper.orderBy(targets, args.orderBy) }
       if (args.orderBy) { targets = graphHelper.orderBy(targets, args.orderBy) }
       return targets
       return targets
@@ -28,11 +39,11 @@ module.exports = {
     async updateTargets(obj, args, context) {
     async updateTargets(obj, args, context) {
       try {
       try {
         for (let tgt of args.targets) {
         for (let tgt of args.targets) {
-          await WIKI.db.storage.query().patch({
+          await WIKI.models.storage.query().patch({
             isEnabled: tgt.isEnabled,
             isEnabled: tgt.isEnabled,
             mode: tgt.mode,
             mode: tgt.mode,
             config: _.reduce(tgt.config, (result, value, key) => {
             config: _.reduce(tgt.config, (result, value, key) => {
-              _.set(result, `${value.key}.value`, value.value)
+              _.set(result, `${value.key}`, value.value)
               return result
               return result
             }, {})
             }, {})
           }).where('key', tgt.key)
           }).where('key', tgt.key)

+ 1 - 1
server/graph/resolvers/system.js

@@ -33,7 +33,7 @@ module.exports = {
         configFile: path.join(process.cwd(), 'config.yml'),
         configFile: path.join(process.cwd(), 'config.yml'),
         currentVersion: WIKI.version,
         currentVersion: WIKI.version,
         dbType: _.get(dbTypes, WIKI.config.db.type, 'Unknown DB'),
         dbType: _.get(dbTypes, WIKI.config.db.type, 'Unknown DB'),
-        dbVersion: _.get(WIKI.db, 'knex.client.version', 'Unknown version'),
+        dbVersion: _.get(WIKI.models, 'knex.client.version', 'Unknown version'),
         dbHost: WIKI.config.db.host,
         dbHost: WIKI.config.db.host,
         latestVersion: WIKI.version, // TODO
         latestVersion: WIKI.version, // TODO
         latestVersionReleaseDate: new Date(), // TODO
         latestVersionReleaseDate: new Date(), // TODO

+ 8 - 8
server/graph/resolvers/tag.js

@@ -6,16 +6,16 @@ const gql = require('graphql')
 module.exports = {
 module.exports = {
   Query: {
   Query: {
     tags(obj, args, context, info) {
     tags(obj, args, context, info) {
-      return WIKI.db.Tag.findAll({ where: args })
+      return WIKI.models.Tag.findAll({ where: args })
     }
     }
   },
   },
   Mutation: {
   Mutation: {
     assignTagToDocument(obj, args) {
     assignTagToDocument(obj, args) {
-      return WIKI.db.Tag.findById(args.tagId).then(tag => {
+      return WIKI.models.Tag.findById(args.tagId).then(tag => {
         if (!tag) {
         if (!tag) {
           throw new gql.GraphQLError('Invalid Tag ID')
           throw new gql.GraphQLError('Invalid Tag ID')
         }
         }
-        return WIKI.db.Document.findById(args.documentId).then(doc => {
+        return WIKI.models.Document.findById(args.documentId).then(doc => {
           if (!doc) {
           if (!doc) {
             throw new gql.GraphQLError('Invalid Document ID')
             throw new gql.GraphQLError('Invalid Document ID')
           }
           }
@@ -24,10 +24,10 @@ module.exports = {
       })
       })
     },
     },
     createTag(obj, args) {
     createTag(obj, args) {
-      return WIKI.db.Tag.create(args)
+      return WIKI.models.Tag.create(args)
     },
     },
     deleteTag(obj, args) {
     deleteTag(obj, args) {
-      return WIKI.db.Tag.destroy({
+      return WIKI.models.Tag.destroy({
         where: {
         where: {
           id: args.id
           id: args.id
         },
         },
@@ -35,11 +35,11 @@ module.exports = {
       })
       })
     },
     },
     removeTagFromDocument(obj, args) {
     removeTagFromDocument(obj, args) {
-      return WIKI.db.Tag.findById(args.tagId).then(tag => {
+      return WIKI.models.Tag.findById(args.tagId).then(tag => {
         if (!tag) {
         if (!tag) {
           throw new gql.GraphQLError('Invalid Tag ID')
           throw new gql.GraphQLError('Invalid Tag ID')
         }
         }
-        return WIKI.db.Document.findById(args.documentId).then(doc => {
+        return WIKI.models.Document.findById(args.documentId).then(doc => {
           if (!doc) {
           if (!doc) {
             throw new gql.GraphQLError('Invalid Document ID')
             throw new gql.GraphQLError('Invalid Document ID')
           }
           }
@@ -48,7 +48,7 @@ module.exports = {
       })
       })
     },
     },
     renameTag(obj, args) {
     renameTag(obj, args) {
-      return WIKI.db.Group.update({
+      return WIKI.models.Group.update({
         key: args.key
         key: args.key
       }, {
       }, {
         where: { id: args.id }
         where: { id: args.id }

+ 6 - 6
server/graph/resolvers/user.js

@@ -10,18 +10,18 @@ module.exports = {
   },
   },
   UserQuery: {
   UserQuery: {
     async list(obj, args, context, info) {
     async list(obj, args, context, info) {
-      return WIKI.db.users.query()
+      return WIKI.models.users.query()
         .select('id', 'email', 'name', 'provider', 'role', 'createdAt', 'updatedAt')
         .select('id', 'email', 'name', 'provider', 'role', 'createdAt', 'updatedAt')
     },
     },
     async search(obj, args, context, info) {
     async search(obj, args, context, info) {
-      return WIKI.db.users.query()
+      return WIKI.models.users.query()
         .where('email', 'like', `%${args.query}%`)
         .where('email', 'like', `%${args.query}%`)
         .orWhere('name', 'like', `%${args.query}%`)
         .orWhere('name', 'like', `%${args.query}%`)
         .limit(10)
         .limit(10)
         .select('id', 'email', 'name', 'provider', 'role', 'createdAt', 'updatedAt')
         .select('id', 'email', 'name', 'provider', 'role', 'createdAt', 'updatedAt')
     },
     },
     async single(obj, args, context, info) {
     async single(obj, args, context, info) {
-      let usr = await WIKI.db.users.query().findById(args.id)
+      let usr = await WIKI.models.users.query().findById(args.id)
       usr.password = ''
       usr.password = ''
       usr.tfaSecret = ''
       usr.tfaSecret = ''
       return usr
       return usr
@@ -29,13 +29,13 @@ module.exports = {
   },
   },
   UserMutation: {
   UserMutation: {
     create(obj, args) {
     create(obj, args) {
-      return WIKI.db.users.query().insertAndFetch(args)
+      return WIKI.models.users.query().insertAndFetch(args)
     },
     },
     delete(obj, args) {
     delete(obj, args) {
-      return WIKI.db.users.query().deleteById(args.id)
+      return WIKI.models.users.query().deleteById(args.id)
     },
     },
     update(obj, args) {
     update(obj, args) {
-      return WIKI.db.users.query().patch({
+      return WIKI.models.users.query().patch({
         email: args.email,
         email: args.email,
         name: args.name,
         name: args.name,
         provider: args.provider,
         provider: args.provider,

+ 3 - 3
server/jobs/fetch-graph-locale.js

@@ -5,7 +5,7 @@ const { createApolloFetch } = require('apollo-fetch')
 /* global WIKI */
 /* global WIKI */
 
 
 WIKI.redis = require('../core/redis').init()
 WIKI.redis = require('../core/redis').init()
-WIKI.db = require('../core/db').init()
+WIKI.models = require('../core/db').init()
 
 
 module.exports = async (job) => {
 module.exports = async (job) => {
   WIKI.logger.info(`Fetching locale ${job.data.locale} from Graph endpoint...`)
   WIKI.logger.info(`Fetching locale ${job.data.locale} from Graph endpoint...`)
@@ -39,8 +39,8 @@ module.exports = async (job) => {
     const locales = await WIKI.redis.get('locales')
     const locales = await WIKI.redis.get('locales')
     if (locales) {
     if (locales) {
       const currentLocale = _.find(JSON.parse(locales), ['code', job.data.locale]) || {}
       const currentLocale = _.find(JSON.parse(locales), ['code', job.data.locale]) || {}
-      await WIKI.db.locales.query().delete().where('code', job.data.locale)
-      await WIKI.db.locales.query().insert({
+      await WIKI.models.locales.query().delete().where('code', job.data.locale)
+      await WIKI.models.locales.query().insert({
         code: job.data.locale,
         code: job.data.locale,
         strings: lcObj,
         strings: lcObj,
         isRTL: currentLocale.isRTL,
         isRTL: currentLocale.isRTL,

+ 2 - 2
server/jobs/sync-graph-locales.js

@@ -5,7 +5,7 @@ const { createApolloFetch } = require('apollo-fetch')
 /* global WIKI */
 /* global WIKI */
 
 
 WIKI.redis = require('../core/redis').init()
 WIKI.redis = require('../core/redis').init()
-WIKI.db = require('../core/db').init()
+WIKI.models = require('../core/db').init()
 
 
 module.exports = async (job) => {
 module.exports = async (job) => {
   WIKI.logger.info('Syncing locales with Graph endpoint...')
   WIKI.logger.info('Syncing locales with Graph endpoint...')
@@ -59,7 +59,7 @@ module.exports = async (job) => {
         _.set(lcObj, row.key.replace(':', '.'), row.value)
         _.set(lcObj, row.key.replace(':', '.'), row.value)
       })
       })
 
 
-      await WIKI.db.locales.query().update({
+      await WIKI.models.locales.query().update({
         code: WIKI.config.lang.code,
         code: WIKI.config.lang.code,
         strings: lcObj,
         strings: lcObj,
         isRTL: currentLocale.isRTL,
         isRTL: currentLocale.isRTL,

+ 4 - 4
server/db/models/authentication.js → server/models/authentication.js

@@ -3,7 +3,7 @@ const fs = require('fs-extra')
 const path = require('path')
 const path = require('path')
 const _ = require('lodash')
 const _ = require('lodash')
 const yaml = require('js-yaml')
 const yaml = require('js-yaml')
-const commonHelper = require('../../helpers/common')
+const commonHelper = require('../helpers/common')
 
 
 /* global WIKI */
 /* global WIKI */
 
 
@@ -33,7 +33,7 @@ module.exports = class Authentication extends Model {
   }
   }
 
 
   static async getStrategies() {
   static async getStrategies() {
-    const strategies = await WIKI.db.authentication.query()
+    const strategies = await WIKI.models.authentication.query()
     return strategies.map(str => ({
     return strategies.map(str => ({
       ...str,
       ...str,
       domainWhitelist: _.get(str.domainWhitelist, 'v', []),
       domainWhitelist: _.get(str.domainWhitelist, 'v', []),
@@ -43,7 +43,7 @@ module.exports = class Authentication extends Model {
 
 
   static async refreshStrategiesFromDisk() {
   static async refreshStrategiesFromDisk() {
     try {
     try {
-      const dbStrategies = await WIKI.db.authentication.query()
+      const dbStrategies = await WIKI.models.authentication.query()
 
 
       // -> Fetch definitions from disk
       // -> Fetch definitions from disk
       const authDirs = await fs.readdir(path.join(WIKI.SERVERPATH, 'modules/authentication'))
       const authDirs = await fs.readdir(path.join(WIKI.SERVERPATH, 'modules/authentication'))
@@ -86,7 +86,7 @@ module.exports = class Authentication extends Model {
         }
         }
       })
       })
       if (newStrategies.length > 0) {
       if (newStrategies.length > 0) {
-        await WIKI.db.authentication.query().insert(newStrategies)
+        await WIKI.models.authentication.query().insert(newStrategies)
         WIKI.logger.info(`Loaded ${newStrategies.length} new authentication strategies: [ OK ]`)
         WIKI.logger.info(`Loaded ${newStrategies.length} new authentication strategies: [ OK ]`)
       } else {
       } else {
         WIKI.logger.info(`No new authentication strategies found: [ SKIPPED ]`)
         WIKI.logger.info(`No new authentication strategies found: [ SKIPPED ]`)

+ 3 - 3
server/db/models/editors.js → server/models/editors.js

@@ -27,12 +27,12 @@ module.exports = class Editor extends Model {
   }
   }
 
 
   static async getEnabledEditors() {
   static async getEnabledEditors() {
-    return WIKI.db.editors.query().where({ isEnabled: true })
+    return WIKI.models.editors.query().where({ isEnabled: true })
   }
   }
 
 
   static async refreshEditorsFromDisk() {
   static async refreshEditorsFromDisk() {
     try {
     try {
-      const dbEditors = await WIKI.db.editors.query()
+      const dbEditors = await WIKI.models.editors.query()
       const diskEditors = autoload(path.join(WIKI.SERVERPATH, 'modules/editor'))
       const diskEditors = autoload(path.join(WIKI.SERVERPATH, 'modules/editor'))
       let newEditors = []
       let newEditors = []
       _.forOwn(diskEditors, (strategy, strategyKey) => {
       _.forOwn(diskEditors, (strategy, strategyKey) => {
@@ -49,7 +49,7 @@ module.exports = class Editor extends Model {
         }
         }
       })
       })
       if (newEditors.length > 0) {
       if (newEditors.length > 0) {
-        await WIKI.db.editors.query().insert(newEditors)
+        await WIKI.models.editors.query().insert(newEditors)
         WIKI.logger.info(`Loaded ${newEditors.length} new editors: [ OK ]`)
         WIKI.logger.info(`Loaded ${newEditors.length} new editors: [ OK ]`)
       } else {
       } else {
         WIKI.logger.info(`No new editors found: [ SKIPPED ]`)
         WIKI.logger.info(`No new editors found: [ SKIPPED ]`)

+ 0 - 0
server/db/models/groups.js → server/models/groups.js


+ 0 - 0
server/db/models/locales.js → server/models/locales.js


+ 1 - 1
server/db/models/pageHistory.js → server/models/pageHistory.js

@@ -82,7 +82,7 @@ module.exports = class PageHistory extends Model {
   }
   }
 
 
   static async addVersion(opts) {
   static async addVersion(opts) {
-    await WIKI.db.pageHistory.query().insert({
+    await WIKI.models.pageHistory.query().insert({
       pageId: opts.id,
       pageId: opts.id,
       authorId: opts.authorId,
       authorId: opts.authorId,
       content: opts.content,
       content: opts.content,

+ 14 - 7
server/db/models/pages.js → server/models/pages.js

@@ -22,6 +22,7 @@ module.exports = class Page extends Model {
         publishStartDate: {type: 'string'},
         publishStartDate: {type: 'string'},
         publishEndDate: {type: 'string'},
         publishEndDate: {type: 'string'},
         content: {type: 'string'},
         content: {type: 'string'},
+        contentType: {type: 'string'},
 
 
         createdAt: {type: 'string'},
         createdAt: {type: 'string'},
         updatedAt: {type: 'string'}
         updatedAt: {type: 'string'}
@@ -87,7 +88,7 @@ module.exports = class Page extends Model {
   }
   }
 
 
   static async createPage(opts) {
   static async createPage(opts) {
-    const page = await WIKI.db.pages.query().insertAndFetch({
+    const page = await WIKI.models.pages.query().insertAndFetch({
       authorId: opts.authorId,
       authorId: opts.authorId,
       content: opts.content,
       content: opts.content,
       creatorId: opts.authorId,
       creatorId: opts.authorId,
@@ -101,7 +102,7 @@ module.exports = class Page extends Model {
       publishStartDate: opts.publishStartDate,
       publishStartDate: opts.publishStartDate,
       title: opts.title
       title: opts.title
     })
     })
-    await WIKI.db.storage.pageEvent({
+    await WIKI.models.storage.pageEvent({
       event: 'created',
       event: 'created',
       page
       page
     })
     })
@@ -109,15 +110,21 @@ module.exports = class Page extends Model {
   }
   }
 
 
   static async updatePage(opts) {
   static async updatePage(opts) {
-    const ogPage = await WIKI.db.pages.query().findById(opts.id)
+    const ogPage = await WIKI.models.pages.query().findById(opts.id)
     if (!ogPage) {
     if (!ogPage) {
       throw new Error('Invalid Page Id')
       throw new Error('Invalid Page Id')
     }
     }
-    await WIKI.db.pageHistory.addVersion(ogPage)
-    const page = await WIKI.db.pages.query().patchAndFetch({
+    await WIKI.models.pageHistory.addVersion(ogPage)
+    const page = await WIKI.models.pages.query().patchAndFetchById(ogPage.id, {
+      authorId: opts.authorId,
+      content: opts.content,
+      description: opts.description,
+      isPublished: opts.isPublished,
+      publishEndDate: opts.publishEndDate,
+      publishStartDate: opts.publishStartDate,
       title: opts.title
       title: opts.title
-    }).where('id', opts.id)
-    await WIKI.db.storage.pageEvent({
+    })
+    await WIKI.models.storage.pageEvent({
       event: 'updated',
       event: 'updated',
       page
       page
     })
     })

+ 1 - 1
server/db/models/settings.js → server/models/settings.js

@@ -32,7 +32,7 @@ module.exports = class Setting extends Model {
   }
   }
 
 
   static async getConfig() {
   static async getConfig() {
-    const settings = await WIKI.db.settings.query()
+    const settings = await WIKI.models.settings.query()
     if (settings.length > 0) {
     if (settings.length > 0) {
       return _.reduce(settings, (res, val, key) => {
       return _.reduce(settings, (res, val, key) => {
         _.set(res, val.key, (val.value.v) ? val.value.v : val.value)
         _.set(res, val.key, (val.value.v) ? val.value.v : val.value)

+ 39 - 23
server/db/models/storage.js → server/models/storage.js

@@ -3,7 +3,7 @@ const path = require('path')
 const fs = require('fs-extra')
 const fs = require('fs-extra')
 const _ = require('lodash')
 const _ = require('lodash')
 const yaml = require('js-yaml')
 const yaml = require('js-yaml')
-const commonHelper = require('../../helpers/common')
+const commonHelper = require('../helpers/common')
 
 
 /* global WIKI */
 /* global WIKI */
 
 
@@ -30,12 +30,12 @@ module.exports = class Storage extends Model {
   }
   }
 
 
   static async getTargets() {
   static async getTargets() {
-    return WIKI.db.storage.query()
+    return WIKI.models.storage.query()
   }
   }
 
 
   static async refreshTargetsFromDisk() {
   static async refreshTargetsFromDisk() {
     try {
     try {
-      const dbTargets = await WIKI.db.storage.query()
+      const dbTargets = await WIKI.models.storage.query()
 
 
       // -> Fetch definitions from disk
       // -> Fetch definitions from disk
       const storageDirs = await fs.readdir(path.join(WIKI.SERVERPATH, 'modules/storage'))
       const storageDirs = await fs.readdir(path.join(WIKI.SERVERPATH, 'modules/storage'))
@@ -44,10 +44,29 @@ module.exports = class Storage extends Model {
         const def = await fs.readFile(path.join(WIKI.SERVERPATH, 'modules/storage', dir, 'definition.yml'), 'utf8')
         const def = await fs.readFile(path.join(WIKI.SERVERPATH, 'modules/storage', dir, 'definition.yml'), 'utf8')
         diskTargets.push(yaml.safeLoad(def))
         diskTargets.push(yaml.safeLoad(def))
       }
       }
+      WIKI.data.storage = diskTargets.map(target => ({
+        ...target,
+        props: _.transform(target.props, (result, value, key) => {
+          let defaultValue = ''
+          if (_.isPlainObject(value)) {
+            defaultValue = !_.isNil(value.default) ? value.default : commonHelper.getTypeDefaultValue(value.type)
+          } else {
+            defaultValue = commonHelper.getTypeDefaultValue(value)
+          }
+          _.set(result, key, {
+            default: defaultValue,
+            type: (value.type || value).toLowerCase(),
+            title: value.title || _.startCase(key),
+            hint: value.hint || false,
+            enum: value.enum || false
+          })
+          return result
+        }, {})
+      }))
 
 
       // -> Insert new targets
       // -> Insert new targets
       let newTargets = []
       let newTargets = []
-      _.forEach(diskTargets, target => {
+      for (let target of WIKI.data.storage) {
         if (!_.some(dbTargets, ['key', target.key])) {
         if (!_.some(dbTargets, ['key', target.key])) {
           newTargets.push({
           newTargets.push({
             key: target.key,
             key: target.key,
@@ -55,28 +74,25 @@ module.exports = class Storage extends Model {
             isEnabled: false,
             isEnabled: false,
             mode: 'push',
             mode: 'push',
             config: _.transform(target.props, (result, value, key) => {
             config: _.transform(target.props, (result, value, key) => {
-              if (_.isPlainObject(value)) {
-                let cfgValue = {
-                  type: value.type.toLowerCase(),
-                  value: !_.isNil(value.default) ? value.default : commonHelper.getTypeDefaultValue(value.type)
-                }
-                if (_.isArray(value.enum)) {
-                  cfgValue.enum = value.enum
-                }
-                _.set(result, key, cfgValue)
-              } else {
-                _.set(result, key, {
-                  type: value.toLowerCase(),
-                  value: commonHelper.getTypeDefaultValue(value)
-                })
-              }
+              _.set(result, key, value.default)
               return result
               return result
             }, {})
             }, {})
           })
           })
+        } else {
+          const targetConfig = _.get(_.find(dbTargets, ['key', target.key]), 'config', {})
+          await WIKI.models.storage.query().patch({
+            title: target.title,
+            config: _.transform(target.props, (result, value, key) => {
+              if (!_.has(result, key)) {
+                _.set(result, key, value.default)
+              }
+              return result
+            }, targetConfig)
+          }).where('key', target.key)
         }
         }
-      })
+      }
       if (newTargets.length > 0) {
       if (newTargets.length > 0) {
-        await WIKI.db.storage.query().insert(newTargets)
+        await WIKI.models.storage.query().insert(newTargets)
         WIKI.logger.info(`Loaded ${newTargets.length} new storage targets: [ OK ]`)
         WIKI.logger.info(`Loaded ${newTargets.length} new storage targets: [ OK ]`)
       } else {
       } else {
         WIKI.logger.info(`No new storage targets found: [ SKIPPED ]`)
         WIKI.logger.info(`No new storage targets found: [ SKIPPED ]`)
@@ -87,8 +103,8 @@ module.exports = class Storage extends Model {
     }
     }
   }
   }
 
 
-  static async pageEvent(event, page) {
-    const targets = await WIKI.db.storage.query().where('isEnabled', true)
+  static async pageEvent({ event, page }) {
+    const targets = await WIKI.models.storage.query().where('isEnabled', true)
     if (targets && targets.length > 0) {
     if (targets && targets.length > 0) {
       _.forEach(targets, target => {
       _.forEach(targets, target => {
         WIKI.queue.job.syncStorage.add({
         WIKI.queue.job.syncStorage.add({

+ 0 - 0
server/db/models/tags.js → server/models/tags.js


+ 5 - 5
server/db/models/users.js → server/models/users.js

@@ -3,7 +3,7 @@
 const bcrypt = require('bcryptjs-then')
 const bcrypt = require('bcryptjs-then')
 const _ = require('lodash')
 const _ = require('lodash')
 const tfa = require('node-2fa')
 const tfa = require('node-2fa')
-const securityHelper = require('../../helpers/security')
+const securityHelper = require('../helpers/security')
 const Model = require('objection').Model
 const Model = require('objection').Model
 
 
 const bcryptRegexp = /^\$2[ayb]\$[0-9]{2}\$[A-Za-z0-9./]{53}$/
 const bcryptRegexp = /^\$2[ayb]\$[0-9]{2}\$[A-Za-z0-9./]{53}$/
@@ -151,7 +151,7 @@ module.exports = class User extends Model {
     profile.provider = _.lowerCase(profile.provider)
     profile.provider = _.lowerCase(profile.provider)
     primaryEmail = _.toLower(primaryEmail)
     primaryEmail = _.toLower(primaryEmail)
 
 
-    let user = await WIKI.db.users.query().findOne({
+    let user = await WIKI.models.users.query().findOne({
       email: primaryEmail,
       email: primaryEmail,
       provider: profile.provider
       provider: profile.provider
     })
     })
@@ -163,7 +163,7 @@ module.exports = class User extends Model {
         name: profile.displayName || _.split(primaryEmail, '@')[0]
         name: profile.displayName || _.split(primaryEmail, '@')[0]
       })
       })
     } else {
     } else {
-      user = await WIKI.db.users.query().insertAndFetch({
+      user = await WIKI.models.users.query().insertAndFetch({
         email: primaryEmail,
         email: primaryEmail,
         provider: profile.provider,
         provider: profile.provider,
         providerId: profile.id,
         providerId: profile.id,
@@ -186,7 +186,7 @@ module.exports = class User extends Model {
     //       deny: false
     //       deny: false
     //     }]
     //     }]
     //   }
     //   }
-    //   return WIKI.db.users.query().insert(nUsr)
+    //   return WIKI.models.users.query().insert(nUsr)
     // }
     // }
 
 
     return user
     return user
@@ -238,7 +238,7 @@ module.exports = class User extends Model {
       if (result) {
       if (result) {
         let userId = _.toSafeInteger(result)
         let userId = _.toSafeInteger(result)
         if (userId && userId > 0) {
         if (userId && userId > 0) {
-          let user = await WIKI.db.users.query().findById(userId)
+          let user = await WIKI.models.users.query().findById(userId)
           if (user && user.verifyTFA(opts.securityCode)) {
           if (user && user.verifyTFA(opts.securityCode)) {
             return Promise.fromCallback(clb => {
             return Promise.fromCallback(clb => {
               context.req.logIn(user, clb)
               context.req.logIn(user, clb)

+ 1 - 1
server/modules/authentication/auth0/authentication.js

@@ -15,7 +15,7 @@ module.exports = {
         clientSecret: conf.clientSecret,
         clientSecret: conf.clientSecret,
         callbackURL: conf.callbackURL
         callbackURL: conf.callbackURL
       }, function (accessToken, refreshToken, profile, cb) {
       }, function (accessToken, refreshToken, profile, cb) {
-        WIKI.db.users.processProfile(profile).then((user) => {
+        WIKI.models.users.processProfile(profile).then((user) => {
           return cb(null, user) || true
           return cb(null, user) || true
         }).catch((err) => {
         }).catch((err) => {
           return cb(err, null) || true
           return cb(err, null) || true

+ 1 - 1
server/modules/authentication/azure/authentication.js

@@ -20,7 +20,7 @@ module.exports = {
         let waadProfile = jwt.decode(params.id_token)
         let waadProfile = jwt.decode(params.id_token)
         waadProfile.id = waadProfile.oid
         waadProfile.id = waadProfile.oid
         waadProfile.provider = 'azure'
         waadProfile.provider = 'azure'
-        WIKI.db.users.processProfile(waadProfile).then((user) => {
+        WIKI.models.users.processProfile(waadProfile).then((user) => {
           return cb(null, user) || true
           return cb(null, user) || true
         }).catch((err) => {
         }).catch((err) => {
           return cb(err, null) || true
           return cb(err, null) || true

+ 1 - 1
server/modules/authentication/cas/authentication.js

@@ -13,7 +13,7 @@ module.exports = {
         ssoBaseURL: conf.ssoBaseURL,
         ssoBaseURL: conf.ssoBaseURL,
         serverBaseURL: conf.serverBaseURL
         serverBaseURL: conf.serverBaseURL
       }, (profile, cb) => {
       }, (profile, cb) => {
-        WIKI.db.users.processProfile(profile).then((user) => {
+        WIKI.models.users.processProfile(profile).then((user) => {
           return cb(null, user) || true
           return cb(null, user) || true
         }).catch((err) => {
         }).catch((err) => {
           return cb(err, null) || true
           return cb(err, null) || true

+ 1 - 1
server/modules/authentication/discord/authentication.js

@@ -15,7 +15,7 @@ module.exports = {
         callbackURL: conf.callbackURL,
         callbackURL: conf.callbackURL,
         scope: 'identify email'
         scope: 'identify email'
       }, function (accessToken, refreshToken, profile, cb) {
       }, function (accessToken, refreshToken, profile, cb) {
-        WIKI.db.users.processProfile(profile).then((user) => {
+        WIKI.models.users.processProfile(profile).then((user) => {
           return cb(null, user) || true
           return cb(null, user) || true
         }).catch((err) => {
         }).catch((err) => {
           return cb(err, null) || true
           return cb(err, null) || true

+ 1 - 1
server/modules/authentication/dropbox/authentication.js

@@ -15,7 +15,7 @@ module.exports = {
         clientSecret: conf.clientSecret,
         clientSecret: conf.clientSecret,
         callbackURL: conf.callbackURL
         callbackURL: conf.callbackURL
       }, (accessToken, refreshToken, profile, cb) => {
       }, (accessToken, refreshToken, profile, cb) => {
-        WIKI.db.users.processProfile(profile).then((user) => {
+        WIKI.models.users.processProfile(profile).then((user) => {
           return cb(null, user) || true
           return cb(null, user) || true
         }).catch((err) => {
         }).catch((err) => {
           return cb(err, null) || true
           return cb(err, null) || true

+ 1 - 1
server/modules/authentication/facebook/authentication.js

@@ -15,7 +15,7 @@ module.exports = {
         callbackURL: conf.callbackURL,
         callbackURL: conf.callbackURL,
         profileFields: ['id', 'displayName', 'email']
         profileFields: ['id', 'displayName', 'email']
       }, function (accessToken, refreshToken, profile, cb) {
       }, function (accessToken, refreshToken, profile, cb) {
-        WIKI.db.users.processProfile(profile).then((user) => {
+        WIKI.models.users.processProfile(profile).then((user) => {
           return cb(null, user) || true
           return cb(null, user) || true
         }).catch((err) => {
         }).catch((err) => {
           return cb(err, null) || true
           return cb(err, null) || true

+ 1 - 1
server/modules/authentication/github/authentication.js

@@ -15,7 +15,7 @@ module.exports = {
         callbackURL: conf.callbackURL,
         callbackURL: conf.callbackURL,
         scope: ['user:email']
         scope: ['user:email']
       }, (accessToken, refreshToken, profile, cb) => {
       }, (accessToken, refreshToken, profile, cb) => {
-        WIKI.db.users.processProfile(profile).then((user) => {
+        WIKI.models.users.processProfile(profile).then((user) => {
           return cb(null, user) || true
           return cb(null, user) || true
         }).catch((err) => {
         }).catch((err) => {
           return cb(err, null) || true
           return cb(err, null) || true

+ 1 - 1
server/modules/authentication/google/authentication.js

@@ -14,7 +14,7 @@ module.exports = {
         clientSecret: conf.clientSecret,
         clientSecret: conf.clientSecret,
         callbackURL: conf.callbackURL
         callbackURL: conf.callbackURL
       }, (accessToken, refreshToken, profile, cb) => {
       }, (accessToken, refreshToken, profile, cb) => {
-        WIKI.db.users.processProfile(profile).then((user) => {
+        WIKI.models.users.processProfile(profile).then((user) => {
           return cb(null, user) || true
           return cb(null, user) || true
         }).catch((err) => {
         }).catch((err) => {
           return cb(err, null) || true
           return cb(err, null) || true

+ 1 - 1
server/modules/authentication/ldap/authentication.js

@@ -29,7 +29,7 @@ module.exports = {
       }, (profile, cb) => {
       }, (profile, cb) => {
         profile.provider = 'ldap'
         profile.provider = 'ldap'
         profile.id = profile.dn
         profile.id = profile.dn
-        WIKI.db.users.processProfile(profile).then((user) => {
+        WIKI.models.users.processProfile(profile).then((user) => {
           return cb(null, user) || true
           return cb(null, user) || true
         }).catch((err) => {
         }).catch((err) => {
           return cb(err, null) || true
           return cb(err, null) || true

+ 1 - 1
server/modules/authentication/local/authentication.js

@@ -13,7 +13,7 @@ module.exports = {
         usernameField: 'email',
         usernameField: 'email',
         passwordField: 'password'
         passwordField: 'password'
       }, (uEmail, uPassword, done) => {
       }, (uEmail, uPassword, done) => {
-        WIKI.db.users.query().findOne({
+        WIKI.models.users.query().findOne({
           email: uEmail,
           email: uEmail,
           providerKey: 'local'
           providerKey: 'local'
         }).then((user) => {
         }).then((user) => {

+ 1 - 1
server/modules/authentication/microsoft/authentication.js

@@ -14,7 +14,7 @@ module.exports = {
         clientSecret: conf.clientSecret,
         clientSecret: conf.clientSecret,
         callbackURL: conf.callbackURL
         callbackURL: conf.callbackURL
       }, function (accessToken, refreshToken, profile, cb) {
       }, function (accessToken, refreshToken, profile, cb) {
-        WIKI.db.users.processProfile(profile).then((user) => {
+        WIKI.models.users.processProfile(profile).then((user) => {
           return cb(null, user) || true
           return cb(null, user) || true
         }).catch((err) => {
         }).catch((err) => {
           return cb(err, null) || true
           return cb(err, null) || true

+ 1 - 1
server/modules/authentication/oauth2/authentication.js

@@ -16,7 +16,7 @@ module.exports = {
         clientSecret: conf.clientSecret,
         clientSecret: conf.clientSecret,
         callbackURL: conf.callbackURL
         callbackURL: conf.callbackURL
       }, (accessToken, refreshToken, profile, cb) => {
       }, (accessToken, refreshToken, profile, cb) => {
-        WIKI.db.users.processProfile(profile).then((user) => {
+        WIKI.models.users.processProfile(profile).then((user) => {
           return cb(null, user) || true
           return cb(null, user) || true
         }).catch((err) => {
         }).catch((err) => {
           return cb(err, null) || true
           return cb(err, null) || true

+ 1 - 1
server/modules/authentication/slack/authentication.js

@@ -14,7 +14,7 @@ module.exports = {
         clientSecret: conf.clientSecret,
         clientSecret: conf.clientSecret,
         callbackURL: conf.callbackURL
         callbackURL: conf.callbackURL
       }, (accessToken, refreshToken, profile, cb) => {
       }, (accessToken, refreshToken, profile, cb) => {
-        WIKI.db.users.processProfile(profile).then((user) => {
+        WIKI.models.users.processProfile(profile).then((user) => {
           return cb(null, user) || true
           return cb(null, user) || true
         }).catch((err) => {
         }).catch((err) => {
           return cb(err, null) || true
           return cb(err, null) || true

+ 1 - 1
server/modules/authentication/twitch/authentication.js

@@ -15,7 +15,7 @@ module.exports = {
         callbackURL: conf.callbackURL,
         callbackURL: conf.callbackURL,
         scope: 'user_read'
         scope: 'user_read'
       }, function (accessToken, refreshToken, profile, cb) {
       }, function (accessToken, refreshToken, profile, cb) {
-        WIKI.db.users.processProfile(profile).then((user) => {
+        WIKI.models.users.processProfile(profile).then((user) => {
           return cb(null, user) || true
           return cb(null, user) || true
         }).catch((err) => {
         }).catch((err) => {
           return cb(err, null) || true
           return cb(err, null) || true

+ 7 - 0
server/modules/storage/box/definition.yml

@@ -0,0 +1,7 @@
+key: box
+title: Box
+author: requarks.io
+props:
+  clientId: String
+  clientSecret: String
+  rootFolder: String

+ 23 - 0
server/modules/storage/box/storage.js

@@ -0,0 +1,23 @@
+module.exports = {
+  async activated() {
+
+  },
+  async deactivated() {
+
+  },
+  async init() {
+
+  },
+  async created() {
+
+  },
+  async updated() {
+
+  },
+  async deleted() {
+
+  },
+  async renamed() {
+
+  }
+}

+ 5 - 2
server/modules/storage/disk/definition.yml

@@ -1,5 +1,8 @@
 key: disk
 key: disk
-title: Local FS
+title: Local File System
 author: requarks.io
 author: requarks.io
 props:
 props:
-  path: String
+  path:
+    type: String
+    title: Path
+    hint: Absolute path without a trailing slash (e.g. /home/wiki/backup, C:\wiki\backup)

+ 52 - 7
server/modules/storage/disk/storage.js

@@ -1,23 +1,68 @@
+const fs = require('fs-extra')
+const path = require('path')
+
+/**
+ * Get file extension based on content type
+ */
+const getFileExtension = (contentType) => {
+  switch (contentType) {
+    case 'markdown':
+      return 'md'
+    case 'html':
+      return 'html'
+    default:
+      return 'txt'
+  }
+}
+
+/**
+ * Inject page metadata into contents
+ */
+const injectMetadata = (page) => {
+  let meta = [
+    ['title', page.title],
+    ['description', page.description]
+  ]
+  let metaFormatted = ''
+  switch (page.contentType) {
+    case 'markdown':
+      metaFormatted = meta.map(mt => `[//]: # ${mt[0]}: ${mt[1]}`).join('\n')
+      break
+    case 'html':
+      metaFormatted = meta.map(mt => `<!-- ${mt[0]}: ${mt[1]} -->`).join('\n')
+      break
+    default:
+      metaFormatted = meta.map(mt => `#WIKI ${mt[0]}: ${mt[1]}`).join('\n')
+      break
+  }
+  return `${metaFormatted}\n\n${page.content}`
+}
+
 module.exports = {
 module.exports = {
   async activated() {
   async activated() {
-
+    // not used
   },
   },
   async deactivated() {
   async deactivated() {
-
+    // not used
   },
   },
   async init() {
   async init() {
-
+    await fs.ensureDir(this.config.path)
   },
   },
   async created() {
   async created() {
-
+    const filePath = path.join(this.config.path, `${this.page.path}.${getFileExtension(this.page.contentType)}`)
+    await fs.outputFile(filePath, injectMetadata(this.page), 'utf8')
   },
   },
   async updated() {
   async updated() {
-
+    const filePath = path.join(this.config.path, `${this.page.path}.${getFileExtension(this.page.contentType)}`)
+    await fs.outputFile(filePath, injectMetadata(this.page), 'utf8')
   },
   },
   async deleted() {
   async deleted() {
-
+    const filePath = path.join(this.config.path, `${this.page.path}.${getFileExtension(this.page.contentType)}`)
+    await fs.unlink(filePath)
   },
   },
   async renamed() {
   async renamed() {
-
+    const sourceFilePath = path.join(this.config.path, `${this.page.sourcePath}.${getFileExtension(this.page.contentType)}`)
+    const destinationFilePath = path.join(this.config.path, `${this.page.destinationPath}.${getFileExtension(this.page.contentType)}`)
+    await fs.move(sourceFilePath, destinationFilePath, { overwrite: true })
   }
   }
 }
 }

+ 20 - 4
server/modules/storage/git/definition.yml

@@ -5,16 +5,32 @@ props:
   authType:
   authType:
     type: String
     type: String
     default: 'ssh'
     default: 'ssh'
+    title: Authentication Type
+    hint: Use SSH for maximum security.
     enum:
     enum:
       - 'basic'
       - 'basic'
       - 'ssh'
       - 'ssh'
-  repoUrl: String
+  repoUrl:
+    type: String
+    title: Repository URI
+    hint: Git-compliant URI (e.g. git@github.com:org/repo.git for ssh, https://github.com/org/repo.git for basic)
   branch:
   branch:
     type: String
     type: String
     default: 'master'
     default: 'master'
   verifySSL:
   verifySSL:
     type: Boolean
     type: Boolean
     default: true
     default: true
-  sshPrivateKeyPath: String
-  basicUsername: String
-  basicPassword: String
+    title: Verify SSL Certificate
+    hint: Some hosts requires SSL certificate checking to be disabled. Leave enabled for proper security.
+  sshPrivateKeyPath:
+    type: String
+    title: SSH Private Key Path
+    hint: SSH Authentication Only - Absolute path to the key. The key must NOT be passphrase-protected.
+  basicUsername:
+    type: String
+    title: Username
+    hint: Basic Authentication Only
+  basicPassword:
+    type: String
+    title: Password / PAT
+    hint: Basic Authentication Only

+ 10 - 10
server/setup.js

@@ -303,7 +303,7 @@ module.exports = () => {
 
 
       // Create default locale
       // Create default locale
       WIKI.logger.info('Installing default locale...')
       WIKI.logger.info('Installing default locale...')
-      await WIKI.db.locales.query().insert({
+      await WIKI.models.locales.query().insert({
         code: 'en',
         code: 'en',
         strings: require('./locales/default.json'),
         strings: require('./locales/default.json'),
         isRTL: false,
         isRTL: false,
@@ -312,23 +312,23 @@ module.exports = () => {
       })
       })
 
 
       // Load authentication strategies + enable local
       // Load authentication strategies + enable local
-      await WIKI.db.authentication.refreshStrategiesFromDisk()
-      await WIKI.db.authentication.query().patch({ isEnabled: true }).where('key', 'local')
+      await WIKI.models.authentication.refreshStrategiesFromDisk()
+      await WIKI.models.authentication.query().patch({ isEnabled: true }).where('key', 'local')
 
 
       // Load editors + enable default
       // Load editors + enable default
-      await WIKI.db.editors.refreshEditorsFromDisk()
-      await WIKI.db.editors.query().patch({ isEnabled: true }).where('key', 'markdown')
+      await WIKI.models.editors.refreshEditorsFromDisk()
+      await WIKI.models.editors.query().patch({ isEnabled: true }).where('key', 'markdown')
 
 
       // Load storage targets
       // Load storage targets
-      await WIKI.db.storage.refreshTargetsFromDisk()
+      await WIKI.models.storage.refreshTargetsFromDisk()
 
 
       // Create root administrator
       // Create root administrator
       WIKI.logger.info('Creating root administrator...')
       WIKI.logger.info('Creating root administrator...')
-      await WIKI.db.users.query().delete().where({
+      await WIKI.models.users.query().delete().where({
         providerKey: 'local',
         providerKey: 'local',
         email: req.body.adminEmail
         email: req.body.adminEmail
       })
       })
-      await WIKI.db.users.query().insert({
+      await WIKI.models.users.query().insert({
         email: req.body.adminEmail,
         email: req.body.adminEmail,
         provider: 'local',
         provider: 'local',
         password: req.body.adminPassword,
         password: req.body.adminPassword,
@@ -341,12 +341,12 @@ module.exports = () => {
 
 
       // Create Guest account
       // Create Guest account
       WIKI.logger.info('Creating guest account...')
       WIKI.logger.info('Creating guest account...')
-      const guestUsr = await WIKI.db.users.query().findOne({
+      const guestUsr = await WIKI.models.users.query().findOne({
         providerKey: 'local',
         providerKey: 'local',
         email: 'guest@example.com'
         email: 'guest@example.com'
       })
       })
       if (!guestUsr) {
       if (!guestUsr) {
-        await WIKI.db.users.query().insert({
+        await WIKI.models.users.query().insert({
           provider: 'local',
           provider: 'local',
           email: 'guest@example.com',
           email: 'guest@example.com',
           name: 'Guest',
           name: 'Guest',

+ 6 - 4
server/views/main/welcome.pug

@@ -4,7 +4,9 @@ block body
   #app.is-fullscreen
   #app.is-fullscreen
     v-app
     v-app
       .onboarding
       .onboarding
-        img(src='/svg/logo-wikijs.svg', alt='Wiki.js')
-        h1= t('welcome.title')
-        h2= t('welcome.subtitle')
-        v-btn(color='primary', href='/e/home')= t('welcome.createhome')
+        img.animated.zoomIn(src='/svg/logo-wikijs.svg', alt='Wiki.js')
+        .headline= t('welcome.title')
+        .subheading.mt-3= t('welcome.subtitle')
+        v-btn.mt-5(color='primary', href='/e/home', large)
+          v-icon(left) add
+          span= t('welcome.createhome')

+ 1 - 1
wiki.js

@@ -118,7 +118,7 @@ const init = {
   },
   },
   async reload() {
   async reload() {
     console.warn(chalk.yellow('--- Closing DB connections...'))
     console.warn(chalk.yellow('--- Closing DB connections...'))
-    await global.WIKI.db.knex.destroy()
+    await global.WIKI.models.knex.destroy()
     console.warn(chalk.yellow('--- Closing Redis connections...'))
     console.warn(chalk.yellow('--- Closing Redis connections...'))
     await global.WIKI.redis.quit()
     await global.WIKI.redis.quit()
     console.warn(chalk.yellow('--- Closing Server connections...'))
     console.warn(chalk.yellow('--- Closing Server connections...'))