diff --git a/locales/en.json b/locales/en.json
index cb0e4ec2..77ddeb0b 100644
--- a/locales/en.json
+++ b/locales/en.json
@@ -261,5 +261,11 @@
"scopes": {
"event:write": "Add and edit your events"
}
+ },
+ "setup": {
+ "check_db": "Check DB",
+ "completed": "Setup completed",
+ "completed_description": "Installation completed!
You can now login with the following user: {email} / {password}. Please write to us at gancio@cisti.org for any question.",
+ "start": "Start"
}
}
diff --git a/middleware/setup.js b/middleware/setup.js
new file mode 100644
index 00000000..350bede9
--- /dev/null
+++ b/middleware/setup.js
@@ -0,0 +1,11 @@
+export default function ({ req, redirect, route }) {
+ if (process.server) {
+ if (req.firstrun && route.path !== '/setup') {
+ return redirect('/setup')
+ }
+ if (!req.firstrun && route.path === '/setup') {
+ return redirect('/')
+ }
+ }
+
+}
diff --git a/pages/index.vue b/pages/index.vue
index 14dbab15..6a481840 100644
--- a/pages/index.vue
+++ b/pages/index.vue
@@ -36,6 +36,7 @@ import Calendar from '@/components/Calendar'
export default {
name: 'Index',
components: { Event, Search, Announcement, Calendar },
+ middleware: 'setup',
async asyncData ({ params, $api, store }) {
const events = await $api.getEvents({
start: dayjs().startOf('month').unix(),
diff --git a/pages/setup/Completed.vue b/pages/setup/Completed.vue
new file mode 100644
index 00000000..9c49e402
--- /dev/null
+++ b/pages/setup/Completed.vue
@@ -0,0 +1,40 @@
+
+ v-container
+ v-card-title.d-block.text-h5.text-center(v-text="$t('setup.completed')")
+ v-card-text
+ p(v-html="$t('setup.completed_description', user)")
+ v-card-actions
+ v-btn(text @click='next' color='primary' :loading='loading' :disabled='loading') {{$t('setup.start')}}
+ v-icon mdi-arrow-right
+
+
\ No newline at end of file
diff --git a/pages/setup/DbStep.vue b/pages/setup/DbStep.vue
new file mode 100644
index 00000000..e0a8f990
--- /dev/null
+++ b/pages/setup/DbStep.vue
@@ -0,0 +1,48 @@
+
+ v-container
+ v-card-title.text-h5 Database
+ v-card-text
+ v-form
+ v-btn-toggle(text color='primary' v-model='db.dialect')
+ v-btn(value='sqlite' text) sqlite
+ v-btn(value='postgres' text) postgres
+ template(v-if='db.dialect === "sqlite"')
+ v-text-field(v-model='db.storage' label='Path')
+ template(v-if='db.dialect === "postgres"')
+ v-text-field(v-model='db.hostname' label='Hostname' :rules="[$validators.required('hostname')]")
+ v-text-field(v-model='db.database' label='Database' :rules="[$validators.required('database')]")
+ v-text-field(v-model='db.username' label='Username' :rules="[$validators.required('username')]")
+ v-text-field(type='password' v-model='db.password' label='Password' :rules="[$validators.required('password')]")
+
+ v-card-actions
+ v-btn(text @click='checkDb' color='primary' :loading='loading' :disabled='loading') {{$t('setup.check_db')}}
+ v-icon mdi-arrow-right
+
+
\ No newline at end of file
diff --git a/pages/setup/index.vue b/pages/setup/index.vue
new file mode 100644
index 00000000..9ddf0696
--- /dev/null
+++ b/pages/setup/index.vue
@@ -0,0 +1,57 @@
+
+
+ v-container.pa-6
+ h2.mb-2.text-center Gancio Setup
+ v-stepper.grey.lighten-5(v-model='step')
+ v-stepper-header
+ v-stepper-step(:complete='step > 1' step='1') Database
+ v-divider
+ v-stepper-step(:complete='step > 2' step='2') Configuration
+ v-divider
+ v-stepper-step(:complete='step > 3' step='3') Finish
+
+ v-stepper-items
+ v-stepper-content(step='1')
+ DbStep(@complete='dbCompleted')
+ v-stepper-content(step='2')
+ Settings(setup, @complete='configCompleted')
+ v-stepper-content(step='3')
+ Completed(ref='completed')
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/server/api/controller/setup.js b/server/api/controller/setup.js
new file mode 100644
index 00000000..ab83d493
--- /dev/null
+++ b/server/api/controller/setup.js
@@ -0,0 +1,86 @@
+const log = require('../../log')
+const db = require('../models/index.js')
+const Umzug = require('umzug')
+const path = require('path')
+const config = require('../../config')
+const Sequelize = require('sequelize')
+
+const crypto = require('crypto')
+const { promisify } = require('util')
+const randomBytes = promisify(crypto.randomBytes)
+async function randomString (len = 16) {
+ const bytes = await randomBytes(len * 8)
+ return crypto
+ .createHash('sha1')
+ .update(bytes)
+ .digest('hex')
+}
+
+const setupController = {
+
+ async setupDb (req, res, next) {
+ log.debug('[SETUP] Check db')
+ const dbConf = req.body.db
+ if (!dbConf) {
+ return res.sendStatus(400)
+ }
+
+ try {
+ const sequelize = await db.connect(dbConf)
+ const users = await sequelize.query('SELECT * from users').catch(e => {})
+ config.db = dbConf
+ if (users && users.length) {
+ log.warn(' ⚠ Non empty db! Please move your current db elsewhere than retry.')
+ return res.status(400).send(' ⚠ Non empty db! Please move your current db elsewhere than retry.')
+ } else {
+ // run migrations...
+ const umzug = new Umzug({
+ storage: 'sequelize',
+ storageOptions: { sequelize },
+ logging: log.debug.bind(log),
+ migrations: {
+ wrap: fun => {
+ return () =>
+ fun(sequelize.queryInterface, Sequelize).catch(e => {
+ log.error(e)
+ return false
+ })
+ },
+ path: path.resolve(__dirname, '..', '..', 'migrations')
+ }
+ })
+ await umzug.up()
+ config.firstrun = false
+ config.db.logging = false
+ const settingsController = require('./settings')
+ await settingsController.load()
+ return res.sendStatus(200)
+ }
+ } catch (e) {
+ return res.status(400).send(String(e))
+ }
+ },
+
+ async restart (req, res) {
+ config.write()
+
+ // create admin user
+ const password = await randomString()
+ const email = `admin@${req.settings.hostname}`
+ const User = require('../models/user')
+ await User.create({
+ email,
+ password,
+ is_admin: true,
+ is_active: true
+ })
+
+ res.json({ password, email })
+
+ // exit process so pm2 || docker could restart me
+ process.exit()
+ }
+
+}
+
+module.exports = setupController
\ No newline at end of file