mirror of
https://github.com/documize/community.git
synced 2025-08-04 13:05:23 +02:00
commit
e0e6aeb021
37 changed files with 414 additions and 333 deletions
|
@ -11,6 +11,7 @@
|
|||
|
||||
import Ember from 'ember';
|
||||
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
|
||||
import { isNotFoundError } from 'ember-ajax/errors';
|
||||
|
||||
const {
|
||||
isPresent
|
||||
|
@ -31,20 +32,23 @@ export default Ember.Route.extend(AuthenticatedRouteMixin, {
|
|||
|
||||
if (is.empty(params)) {
|
||||
let lastFolder = this.get('localStorage').getSessionItem("folder");
|
||||
let self = this;
|
||||
|
||||
//If folder lastFolder is defined
|
||||
if (isPresent(lastFolder)) {
|
||||
return this.get('folderService').getFolder(lastFolder).then((folder) => {
|
||||
//if Response is null or undefined redirect to login else transitionTo dashboard
|
||||
if (Ember.isNone(folder)) {
|
||||
this.transitionTo('auth.login');
|
||||
self.get('localStorage').clearSessionItem("folder");
|
||||
this.transitionTo('application');
|
||||
}
|
||||
|
||||
Ember.set(this, 'folder', folder);
|
||||
this.transitionTo('folders.folder', folder.get('id'), folder.get('slug'));
|
||||
}).catch(() => {
|
||||
//if there was an error redirect to login
|
||||
this.transitionTo('auth.login');
|
||||
self.get('localStorage').clearSessionItem("folder");
|
||||
this.transitionTo('application');
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -64,11 +68,16 @@ export default Ember.Route.extend(AuthenticatedRouteMixin, {
|
|||
|
||||
//If folder route has params
|
||||
if (isPresent(params)) {
|
||||
|
||||
let self = this;
|
||||
let folderId = this.paramsFor('folders.folder').folder_id;
|
||||
|
||||
return this.get('folderService').getFolder(folderId).then((folder) => {
|
||||
Ember.set(this, 'folder', folder);
|
||||
}).catch(function (error) {
|
||||
if (isNotFoundError(error)) {
|
||||
// handle 404 errors here
|
||||
self.transitionTo('application');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -17,8 +17,6 @@ export default Ember.Route.extend({
|
|||
if (pwd.length === 0 || pwd === "{{.DBhash}}") {
|
||||
this.transitionTo('auth.login'); // don't allow access to this page if we are not in setup mode, kick them out altogether
|
||||
}
|
||||
|
||||
this.session.clearSession();
|
||||
},
|
||||
|
||||
model() {
|
||||
|
|
|
@ -20,6 +20,7 @@ const {
|
|||
|
||||
export default Ember.Service.extend({
|
||||
ajax: service(),
|
||||
localStorage: service(),
|
||||
|
||||
endpoint: `${config.apiHost}/${config.apiNamespace}`,
|
||||
orgId: '',
|
||||
|
@ -27,6 +28,7 @@ export default Ember.Service.extend({
|
|||
version: '',
|
||||
message: '',
|
||||
allowAnonymousAccess: false,
|
||||
setupMode: false,
|
||||
|
||||
getBaseUrl(endpoint) {
|
||||
return [this.get('host'), endpoint].join('/');
|
||||
|
@ -40,12 +42,14 @@ export default Ember.Service.extend({
|
|||
|
||||
let isInSetupMode = dbhash && dbhash !== "{{.DBhash}}";
|
||||
if (isInSetupMode) {
|
||||
this.setProperites({
|
||||
this.setProperties({
|
||||
title: htmlSafe("Documize Setup"),
|
||||
allowAnonymousAccess: false
|
||||
allowAnonymousAccess: true,
|
||||
setupMode: true
|
||||
});
|
||||
this.get('localStorage').clearAll();
|
||||
|
||||
return resolve();
|
||||
return resolve(this);
|
||||
}
|
||||
|
||||
return this.get('ajax').request('public/meta').then((response) => {
|
||||
|
|
|
@ -14,194 +14,193 @@ import models from '../utils/model';
|
|||
import BaseService from '../services/base';
|
||||
|
||||
const {
|
||||
get
|
||||
get
|
||||
} = Ember;
|
||||
|
||||
export default BaseService.extend({
|
||||
sessionService: Ember.inject.service('session'),
|
||||
ajax: Ember.inject.service(),
|
||||
localStorage: Ember.inject.service(),
|
||||
sessionService: Ember.inject.service('session'),
|
||||
ajax: Ember.inject.service(),
|
||||
localStorage: Ember.inject.service(),
|
||||
|
||||
// selected folder
|
||||
currentFolder: null,
|
||||
canEditCurrentFolder: false,
|
||||
|
||||
// selected folder
|
||||
currentFolder: null,
|
||||
canEditCurrentFolder: false,
|
||||
// Add a new folder.
|
||||
add(folder) {
|
||||
|
||||
// Add a new folder.
|
||||
add(folder) {
|
||||
return this.get('ajax').post(`folders`, {
|
||||
contentType: 'json',
|
||||
data: JSON.stringify(folder)
|
||||
}).then((folder) => {
|
||||
let folderModel = models.FolderModel.create(folder);
|
||||
return folderModel;
|
||||
});
|
||||
},
|
||||
|
||||
return this.get('ajax').post(`folders`, {
|
||||
contentType: 'json',
|
||||
data: JSON.stringify(folder)
|
||||
}).then((folder)=>{
|
||||
let folderModel = models.FolderModel.create(folder);
|
||||
return folderModel;
|
||||
});
|
||||
},
|
||||
// Returns folder model for specified folder id.
|
||||
getFolder(id) {
|
||||
|
||||
// Returns folder model for specified folder id.
|
||||
getFolder(id) {
|
||||
return this.get('ajax').request(`folders/${id}`, {
|
||||
method: 'GET'
|
||||
}).then((response) => {
|
||||
let folder = models.FolderModel.create(response);
|
||||
return folder;
|
||||
});
|
||||
},
|
||||
|
||||
return this.get('ajax').request(`folders/${id}`, {
|
||||
method: 'GET'
|
||||
}).then((response)=>{
|
||||
let folder = models.FolderModel.create(response);
|
||||
return folder;
|
||||
});
|
||||
},
|
||||
// Returns all folders that user can see.
|
||||
getAll() {
|
||||
let self = this;
|
||||
|
||||
// Returns all folders that user can see.
|
||||
getAll() {
|
||||
let self = this;
|
||||
if (this.get('folders') != null) {
|
||||
return new Ember.RSVP.Promise(function (resolve) {
|
||||
resolve(self.get('folders'));
|
||||
});
|
||||
} else {
|
||||
return this.reload();
|
||||
}
|
||||
},
|
||||
|
||||
if (this.get('folders') != null) {
|
||||
return new Ember.RSVP.Promise(function(resolve) {
|
||||
resolve(self.get('folders'));
|
||||
});
|
||||
} else {
|
||||
return this.reload();
|
||||
}
|
||||
},
|
||||
// Updates an existing folder record.
|
||||
save(folder) {
|
||||
let id = folder.get('id');
|
||||
|
||||
// Updates an existing folder record.
|
||||
save(folder) {
|
||||
let id = folder.get('id');
|
||||
return this.get('ajax').request(`folders/${id}`, {
|
||||
method: 'PUT',
|
||||
contentType: 'json',
|
||||
data: JSON.stringify(folder)
|
||||
});
|
||||
},
|
||||
|
||||
return this.get('ajax').request(`folders/${id}`, {
|
||||
method: 'PUT',
|
||||
contentType: 'json',
|
||||
data: JSON.stringify(folder)
|
||||
});
|
||||
},
|
||||
remove: function (folderId, moveToId) {
|
||||
let url = `folders/${folderId}/move/${moveToId}`;
|
||||
|
||||
remove: function(folderId, moveToId) {
|
||||
let url = `folders/${folderId}/move/${moveToId}`;
|
||||
return this.get('ajax').request(url, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
},
|
||||
|
||||
return this.get('ajax').request(url, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
},
|
||||
onboard: function (folderId, payload) {
|
||||
let url = `public/share/${folderId}`;
|
||||
|
||||
onboard: function(folderId, payload) {
|
||||
let url = `public/share/${folderId}`;
|
||||
return this.get('ajax').post(url, {
|
||||
contentType: "application/json",
|
||||
data: payload
|
||||
});
|
||||
},
|
||||
|
||||
return this.get('ajax').post(url, {
|
||||
contentType: "application/json",
|
||||
data: payload
|
||||
});
|
||||
},
|
||||
// getProtectedFolderInfo returns non-private folders and who has access to them.
|
||||
getProtectedFolderInfo: function () {
|
||||
return this.get('ajax').request(`folders?filter=viewers`, {
|
||||
method: "GET"
|
||||
}).then((response) => {
|
||||
let data = [];
|
||||
_.each(response, function (obj) {
|
||||
data.pushObject(models.ProtectedFolderParticipant.create(obj));
|
||||
});
|
||||
|
||||
// getProtectedFolderInfo returns non-private folders and who has access to them.
|
||||
getProtectedFolderInfo: function() {
|
||||
return this.get('ajax').request(`folders?filter=viewers`, {
|
||||
method: "GET"
|
||||
}).then((response)=>{
|
||||
let data = [];
|
||||
_.each(response, function(obj) {
|
||||
data.pushObject(models.ProtectedFolderParticipant.create(obj));
|
||||
});
|
||||
return data;
|
||||
});
|
||||
},
|
||||
|
||||
return data;
|
||||
});
|
||||
},
|
||||
// reloads and caches folders.
|
||||
reload() {
|
||||
|
||||
// reloads and caches folders.
|
||||
reload() {
|
||||
return this.get('ajax').request(`folders`, {
|
||||
method: "GET"
|
||||
}).then((response) => {
|
||||
let data = [];
|
||||
_.each(response, function (obj) {
|
||||
data.pushObject(models.FolderModel.create(obj));
|
||||
});
|
||||
|
||||
return this.get('ajax').request(`folders`, {
|
||||
method: "GET"
|
||||
}).then((response)=>{
|
||||
let data = [];
|
||||
_.each(response, function(obj) {
|
||||
data.pushObject(models.FolderModel.create(obj));
|
||||
});
|
||||
return data;
|
||||
});
|
||||
},
|
||||
|
||||
return data;
|
||||
});
|
||||
},
|
||||
// so who can see/edit this folder?
|
||||
getPermissions(folderId) {
|
||||
|
||||
// so who can see/edit this folder?
|
||||
getPermissions(folderId) {
|
||||
return this.get('ajax').request(`folders/${folderId}/permissions`, {
|
||||
method: "GET"
|
||||
}).then((response) => {
|
||||
let data = [];
|
||||
_.each(response, function (obj) {
|
||||
data.pushObject(models.FolderPermissionModel.create(obj));
|
||||
});
|
||||
|
||||
return this.get('ajax').request(`folders/${folderId}/permissions`, {
|
||||
method: "GET"
|
||||
}).then((response)=>{
|
||||
let data = [];
|
||||
_.each(response, function(obj) {
|
||||
data.pushObject(models.FolderPermissionModel.create(obj));
|
||||
});
|
||||
return data;
|
||||
});
|
||||
},
|
||||
|
||||
return data;
|
||||
});
|
||||
},
|
||||
// persist folder permissions
|
||||
savePermissions(folderId, payload) {
|
||||
|
||||
// persist folder permissions
|
||||
savePermissions(folderId, payload) {
|
||||
return this.get('ajax').request(`folders/${folderId}/permissions`, {
|
||||
method: 'PUT',
|
||||
contentType: 'json',
|
||||
data: JSON.stringify(payload)
|
||||
});
|
||||
},
|
||||
|
||||
return this.get('ajax').request(`folders/${folderId}/permissions`, {
|
||||
method: 'PUT',
|
||||
contentType: 'json',
|
||||
data: JSON.stringify(payload)
|
||||
});
|
||||
},
|
||||
// share this folder with new users!
|
||||
share(folderId, invitation) {
|
||||
|
||||
// share this folder with new users!
|
||||
share(folderId, invitation) {
|
||||
return this.get('ajax').post(`folders/${folderId}/invitation`, {
|
||||
contentType: 'json',
|
||||
data: JSON.stringify(invitation)
|
||||
});
|
||||
},
|
||||
|
||||
return this.get('ajax').post(`folders/${folderId}/invitation`, {
|
||||
contentType: 'json',
|
||||
data: JSON.stringify(invitation)
|
||||
});
|
||||
},
|
||||
// Current folder caching
|
||||
setCurrentFolder(folder) {
|
||||
if (is.undefined(folder) || is.null(folder)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Current folder caching
|
||||
setCurrentFolder(folder) {
|
||||
if (is.undefined(folder) || is.null(folder)) {
|
||||
return;
|
||||
}
|
||||
this.set('currentFolder', folder);
|
||||
this.get('localStorage').storeSessionItem("folder", get(folder, 'id'));
|
||||
this.set('canEditCurrentFolder', false);
|
||||
|
||||
this.set('currentFolder', folder);
|
||||
this.get('localStorage').storeSessionItem("folder", get(folder, 'id'));
|
||||
this.set('canEditCurrentFolder', false);
|
||||
let userId = this.get('sessionService.user.id');
|
||||
if (userId === "") {
|
||||
userId = "0";
|
||||
}
|
||||
|
||||
let userId = this.get('sessionService.user.id');
|
||||
if (userId === "") {
|
||||
userId = "0";
|
||||
}
|
||||
let url = `users/${userId}/permissions`;
|
||||
|
||||
let url = `users/${userId}/permissions`;
|
||||
return this.get('ajax').request(url).then((folderPermissions) => {
|
||||
// safety check
|
||||
this.set('canEditCurrentFolder', false);
|
||||
|
||||
return this.get('ajax').request(url).then((folderPermissions) => {
|
||||
// safety check
|
||||
this.set('canEditCurrentFolder', false);
|
||||
if (folderPermissions.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (folderPermissions.length === 0) {
|
||||
return;
|
||||
}
|
||||
let result = [];
|
||||
let folderId = folder.get('id');
|
||||
|
||||
let result = [];
|
||||
let folderId = folder.get('id');
|
||||
folderPermissions.forEach(function (item) {
|
||||
if (item.folderId === folderId) {
|
||||
result.push(item);
|
||||
}
|
||||
});
|
||||
|
||||
folderPermissions.forEach(function(item) {
|
||||
if (item.folderId === folderId) {
|
||||
result.push(item);
|
||||
}
|
||||
});
|
||||
let canEdit = false;
|
||||
|
||||
let canEdit = false;
|
||||
result.forEach(function (permission) {
|
||||
if (permission.userId === userId) {
|
||||
canEdit = permission.canEdit;
|
||||
}
|
||||
|
||||
result.forEach(function(permission) {
|
||||
if (permission.userId === userId) {
|
||||
canEdit = permission.canEdit;
|
||||
}
|
||||
|
||||
if (permission.userId === "" && !canEdit) {
|
||||
canEdit = permission.canEdit;
|
||||
}
|
||||
});
|
||||
Ember.run(() => {
|
||||
this.set('canEditCurrentFolder', canEdit && this.get('sessionService.authenticated'));
|
||||
});
|
||||
});
|
||||
},
|
||||
if (permission.userId === "" && !canEdit) {
|
||||
canEdit = permission.canEdit;
|
||||
}
|
||||
});
|
||||
Ember.run(() => {
|
||||
this.set('canEditCurrentFolder', canEdit && this.get('sessionService.authenticated'));
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
|
@ -22,5 +22,9 @@ export default Ember.Service.extend({
|
|||
|
||||
clearSessionItem: function (key) {
|
||||
delete localStorage[key];
|
||||
},
|
||||
|
||||
clearAll() {
|
||||
localStorage.clear();
|
||||
}
|
||||
});
|
2
build.sh
2
build.sh
|
@ -37,7 +37,7 @@ go generate
|
|||
|
||||
echo "Compiling app..."
|
||||
cd ../..
|
||||
for arch in amd64 386 ; do
|
||||
for arch in amd64 ; do
|
||||
for os in darwin linux windows ; do
|
||||
if [ "$os" == "windows" ] ; then
|
||||
echo "Compiling documize-$os-$arch.exe"
|
||||
|
|
|
@ -28,6 +28,7 @@ import (
|
|||
"github.com/documize/community/documize/api/request"
|
||||
"github.com/documize/community/documize/api/util"
|
||||
"github.com/documize/community/documize/section/provider"
|
||||
"github.com/documize/community/documize/web"
|
||||
"github.com/documize/community/wordsmith/environment"
|
||||
"github.com/documize/community/wordsmith/log"
|
||||
"github.com/documize/community/wordsmith/utility"
|
||||
|
@ -293,7 +294,8 @@ func preAuthorizeStaticAssets(r *http.Request) bool {
|
|||
strings.ToLower(r.URL.Path) == "/favicon.ico" ||
|
||||
strings.ToLower(r.URL.Path) == "/robots.txt" ||
|
||||
strings.ToLower(r.URL.Path) == "/version" ||
|
||||
strings.HasPrefix(strings.ToLower(r.URL.Path), "/api/public/") {
|
||||
strings.HasPrefix(strings.ToLower(r.URL.Path), "/api/public/") ||
|
||||
((web.SiteMode == web.SiteModeSetup) && (strings.ToLower(r.URL.Path) == "/api/setup")) {
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -134,6 +134,10 @@ func buildUnsecureRoutes() *mux.Router {
|
|||
func buildSecureRoutes() *mux.Router {
|
||||
router := mux.NewRouter()
|
||||
|
||||
if web.SiteMode == web.SiteModeSetup {
|
||||
router.HandleFunc("/api/setup", database.Create).Methods("POST", "OPTIONS")
|
||||
}
|
||||
|
||||
// Import & Convert Document
|
||||
router.HandleFunc("/api/import/folder/{folderID}", UploadConvertDocument).Methods("POST", "OPTIONS")
|
||||
|
||||
|
@ -254,7 +258,6 @@ func AppRouter() *mux.Router {
|
|||
log.Info("Serving OFFLINE web app")
|
||||
case web.SiteModeSetup:
|
||||
log.Info("Serving SETUP web app")
|
||||
router.HandleFunc("/setup", database.Create).Methods("POST", "OPTIONS")
|
||||
case web.SiteModeBadDB:
|
||||
log.Info("Serving BAD DATABASE web app")
|
||||
default:
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
// https://documize.com
|
||||
|
||||
package request
|
||||
|
||||
/* TODO(Elliott)
|
||||
import (
|
||||
"github.com/documize/community/documize/api/entity"
|
||||
|
|
|
@ -67,6 +67,29 @@ func ConfigString(area, path string) (ret string) {
|
|||
return ret
|
||||
}
|
||||
|
||||
// ConfigSet writes a configuration JSON element to the config table.
|
||||
func ConfigSet(area, json string) error {
|
||||
if Db == nil {
|
||||
return errors.New("no database")
|
||||
}
|
||||
if area == "" {
|
||||
return errors.New("no area")
|
||||
}
|
||||
sql := "INSERT INTO `config` (`key`,`config`) " +
|
||||
"VALUES ('" + area + "','" + json +
|
||||
"') ON DUPLICATE KEY UPDATE `config`='" + json + "';"
|
||||
|
||||
stmt, err := Db.Preparex(sql)
|
||||
if err != nil {
|
||||
//fmt.Printf("DEBUG: Unable to prepare select SQL for ConfigSet: %s -- error: %v\n", sql, err)
|
||||
return err
|
||||
}
|
||||
defer utility.Close(stmt)
|
||||
|
||||
_, err = stmt.Exec()
|
||||
return err
|
||||
}
|
||||
|
||||
// UserConfigGetJSON fetches a configuration JSON element from the userconfig table for a given orgid/userid combination.
|
||||
// Errors return the empty string. A blank path returns the whole JSON object, as JSON.
|
||||
func UserConfigGetJSON(orgid, userid, area, path string) (ret string) {
|
||||
|
@ -124,6 +147,6 @@ func UserConfigSetJSON(orgid, userid, area, json string) error {
|
|||
}
|
||||
defer utility.Close(stmt)
|
||||
|
||||
_,err= stmt.Exec()
|
||||
_, err = stmt.Exec()
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
// https://documize.com
|
||||
|
||||
package request
|
||||
|
||||
/* TODO(Elliott)
|
||||
import (
|
||||
"github.com/documize/community/wordsmith/environment"
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
// https://documize.com
|
||||
|
||||
package request
|
||||
|
||||
/* TODO(Elliott)
|
||||
import (
|
||||
"github.com/documize/community/documize/api/entity"
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
// https://documize.com
|
||||
|
||||
package request
|
||||
|
||||
/* TODO(Elliott)
|
||||
import "testing"
|
||||
import "net/http"
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
package request
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
@ -69,18 +68,8 @@ func init() {
|
|||
}
|
||||
|
||||
// go into setup mode if required
|
||||
if database.Check(Db, connectionString,
|
||||
func() (bool, error) {
|
||||
// LockDB locks the database for migrations, returning if locked and an error.
|
||||
// TODO, and if lock fails, wait here until it unlocks
|
||||
return false, errors.New("LockDB TODO")
|
||||
},
|
||||
func() {
|
||||
// UnlockDB unlocks the database for migrations.
|
||||
// Reports errors in the log.
|
||||
// TODO
|
||||
}) {
|
||||
if err := database.Migrate(ConfigString("META", "database")); err != nil {
|
||||
if database.Check(Db, connectionString) {
|
||||
if err := database.Migrate(true /* the config table exists */); err != nil {
|
||||
log.Error("Unable to run database migration: ", err)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
// https://documize.com
|
||||
|
||||
package request
|
||||
|
||||
/* TODO(Elliott)
|
||||
import (
|
||||
"fmt"
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
// https://documize.com
|
||||
|
||||
package request
|
||||
|
||||
/* TODO(Elliott)
|
||||
import (
|
||||
"testing"
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
// https://documize.com
|
||||
|
||||
package request
|
||||
|
||||
/* TODO(Elliott)
|
||||
import (
|
||||
"testing"
|
||||
|
@ -231,5 +232,5 @@ func TestLabelRole(t *testing.T) {
|
|||
}
|
||||
p.testRollback(t)
|
||||
|
||||
*/
|
||||
*/
|
||||
//}
|
||||
|
|
|
@ -292,11 +292,11 @@ func (p *Persister) UpdatePage(page entity.Page, refID, userID string, skipRevis
|
|||
}
|
||||
|
||||
// UpdatePageMeta persists meta information associated with a document page.
|
||||
func (p *Persister) UpdatePageMeta(meta entity.PageMeta,updateUserID bool) (err error) {
|
||||
func (p *Persister) UpdatePageMeta(meta entity.PageMeta, updateUserID bool) (err error) {
|
||||
err = nil
|
||||
meta.Revised = time.Now().UTC()
|
||||
if updateUserID {
|
||||
meta.UserID=p.Context.UserID
|
||||
meta.UserID = p.Context.UserID
|
||||
}
|
||||
|
||||
var stmt *sqlx.NamedStmt
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
// https://documize.com
|
||||
|
||||
package request
|
||||
|
||||
/* TODO(Elliott)
|
||||
import (
|
||||
"strings"
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
|
||||
"github.com/documize/community/documize/web"
|
||||
"github.com/documize/community/wordsmith/log"
|
||||
"github.com/documize/community/wordsmith/utility"
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
|
@ -27,18 +28,12 @@ var dbCheckOK bool // default false
|
|||
// dbPtr is a pointer to the central connection to the database, used by all database requests.
|
||||
var dbPtr **sqlx.DB
|
||||
|
||||
// lockDB locks the database
|
||||
var lockDB func() (bool, error)
|
||||
|
||||
// unlockDB unlocks the database
|
||||
var unlockDB func()
|
||||
|
||||
// Check that the database is configured correctly and that all the required tables exist.
|
||||
// It must be the first function called in the
|
||||
func Check(Db *sqlx.DB, connectionString string, lDB func() (bool, error), ulDB func()) bool {
|
||||
// It must be the first function called in this package.
|
||||
func Check(Db *sqlx.DB, connectionString string) bool {
|
||||
dbPtr = &Db
|
||||
lockDB = lDB
|
||||
unlockDB = ulDB
|
||||
|
||||
log.Info("Running database checks, this may take a while...")
|
||||
|
||||
csBits := strings.Split(connectionString, "/")
|
||||
if len(csBits) > 1 {
|
||||
|
@ -52,7 +47,7 @@ func Check(Db *sqlx.DB, connectionString string, lDB func() (bool, error), ulDB
|
|||
web.SiteMode = web.SiteModeBadDB
|
||||
return false
|
||||
}
|
||||
defer rows.Close() // ignore error
|
||||
defer utility.Close(rows)
|
||||
var version, charset, collation string
|
||||
if rows.Next() {
|
||||
err = rows.Scan(&version, &charset, &collation)
|
||||
|
|
|
@ -15,7 +15,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -25,6 +24,7 @@ import (
|
|||
"github.com/documize/community/wordsmith/utility"
|
||||
)
|
||||
|
||||
// runSQL creates a transaction per call
|
||||
func runSQL(sql string) (id uint64, err error) {
|
||||
|
||||
if strings.TrimSpace(sql) == "" {
|
||||
|
@ -41,7 +41,7 @@ func runSQL(sql string) (id uint64, err error) {
|
|||
result, err := tx.Exec(sql)
|
||||
|
||||
if err != nil {
|
||||
tx.Rollback() // ignore error as already in an error state
|
||||
log.IfErr(tx.Rollback())
|
||||
log.Error("runSql - unable to run sql", err)
|
||||
return
|
||||
}
|
||||
|
@ -59,14 +59,6 @@ func runSQL(sql string) (id uint64, err error) {
|
|||
|
||||
// Create the tables in a blank database
|
||||
func Create(w http.ResponseWriter, r *http.Request) {
|
||||
txt := "database.Create()"
|
||||
//defer func(){fmt.Println("DEBUG"+txt)}()
|
||||
|
||||
if dbCheckOK {
|
||||
txt += " Check OK"
|
||||
} else {
|
||||
txt += " Check not OK"
|
||||
}
|
||||
|
||||
defer func() {
|
||||
target := "/setup"
|
||||
|
@ -92,14 +84,9 @@ func Create(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
txt += fmt.Sprintf("\n%#v\n", r.Form)
|
||||
|
||||
dbname := r.Form.Get("dbname")
|
||||
dbhash := r.Form.Get("dbhash")
|
||||
|
||||
txt += fmt.Sprintf("DBname:%s (want:%s) DBhash: %s (want:%s)\n",
|
||||
dbname, web.SiteInfo.DBname, dbhash, web.SiteInfo.DBhash)
|
||||
|
||||
if dbname != web.SiteInfo.DBname || dbhash != web.SiteInfo.DBhash {
|
||||
log.Error("database.Create()'s security credentials error ", errors.New("bad db name or validation code"))
|
||||
return
|
||||
|
@ -117,8 +104,6 @@ func Create(w http.ResponseWriter, r *http.Request) {
|
|||
Revised: time.Now(),
|
||||
}
|
||||
|
||||
txt += fmt.Sprintf("\n%#v\n", details)
|
||||
|
||||
if details.Company == "" ||
|
||||
details.CompanyLong == "" ||
|
||||
details.Message == "" ||
|
||||
|
@ -126,43 +111,12 @@ func Create(w http.ResponseWriter, r *http.Request) {
|
|||
details.Password == "" ||
|
||||
details.Firstname == "" ||
|
||||
details.Lastname == "" {
|
||||
txt += "ERROR: required field blank"
|
||||
log.Error("database.Create() error ",
|
||||
errors.New("required field in database set-up form blank"))
|
||||
return
|
||||
}
|
||||
|
||||
firstSQL := "db_00000.sql"
|
||||
|
||||
buf, err := web.ReadFile("scripts/" + firstSQL)
|
||||
if err != nil {
|
||||
log.Error("database.Create()'s web.ReadFile()", err)
|
||||
return
|
||||
}
|
||||
|
||||
tx, err := (*dbPtr).Beginx()
|
||||
if err != nil {
|
||||
log.Error(" failed to get transaction", err)
|
||||
return
|
||||
}
|
||||
|
||||
stmts := getStatements(buf)
|
||||
|
||||
for i, stmt := range stmts {
|
||||
_, err = tx.Exec(stmt)
|
||||
txt += fmt.Sprintf("%d: %s\nResult: %v\n\n", i, stmt, err)
|
||||
if err != nil {
|
||||
tx.Rollback() // ignore error as already in an error state
|
||||
log.Error("database.Create() unable to run table create sql", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
log.Error("database.Create()", err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := Migrate(firstSQL); err != nil {
|
||||
if err = Migrate(false /* no tables exist yet */); err != nil {
|
||||
log.Error("database.Create()", err)
|
||||
return
|
||||
}
|
||||
|
@ -174,7 +128,6 @@ func Create(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
web.SiteMode = web.SiteModeNormal
|
||||
txt += "\n Success!\n"
|
||||
}
|
||||
|
||||
// The result of completing the onboarding process.
|
||||
|
@ -219,7 +172,6 @@ func setupAccount(completion onboardRequest, serial string) (err error) {
|
|||
log.Error("Failed with error", err)
|
||||
return err
|
||||
}
|
||||
//}
|
||||
|
||||
// Link user to organization.
|
||||
accountID := util.UniqueID()
|
||||
|
@ -250,19 +202,3 @@ func setupAccount(completion onboardRequest, serial string) (err error) {
|
|||
|
||||
return
|
||||
}
|
||||
|
||||
// getStatement strips out the comments and returns all the individual SQL commands (apart from "USE") as a []string.
|
||||
func getStatements(bytes []byte) []string {
|
||||
/* Strip comments of the form '-- comment' or like this one */
|
||||
stripped := regexp.MustCompile("(?s)--.*?\n|/\\*.*?\\*/").ReplaceAll(bytes, []byte("\n"))
|
||||
sqls := strings.Split(string(stripped), ";")
|
||||
ret := make([]string, 0, len(sqls))
|
||||
for _, v := range sqls {
|
||||
trimmed := strings.TrimSpace(v)
|
||||
if len(trimmed) > 0 &&
|
||||
!strings.HasPrefix(strings.ToUpper(trimmed), "USE ") { // make sure we don't USE the wrong database
|
||||
ret = append(ret, trimmed+";")
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
|
|
@ -12,11 +12,17 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"bytes"
|
||||
"database/sql"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
|
||||
"github.com/documize/community/documize/web"
|
||||
"github.com/documize/community/wordsmith/log"
|
||||
"github.com/documize/community/wordsmith/utility"
|
||||
)
|
||||
|
||||
const migrationsDir = "bindata/scripts"
|
||||
|
@ -41,6 +47,10 @@ func migrations(lastMigration string) (migrationsT, error) {
|
|||
|
||||
hadLast := false
|
||||
|
||||
if len(lastMigration) == 0 {
|
||||
hadLast = true
|
||||
}
|
||||
|
||||
for _, v := range files {
|
||||
if v == lastMigration {
|
||||
hadLast = true
|
||||
|
@ -56,35 +66,130 @@ func migrations(lastMigration string) (migrationsT, error) {
|
|||
}
|
||||
|
||||
// migrate the database as required, by applying the migrations.
|
||||
func (m migrationsT) migrate() error {
|
||||
func (m migrationsT) migrate(tx *sqlx.Tx) error {
|
||||
for _, v := range m {
|
||||
log.Info("Processing migration file: " + v)
|
||||
buf, err := web.Asset(migrationsDir + "/" + v)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("DEBUG database.Migrate() ", v, ":\n", string(buf)) // TODO actually run the SQL
|
||||
//fmt.Println("DEBUG database.Migrate() ", v, ":\n", string(buf)) // TODO actually run the SQL
|
||||
err = processSQLfile(tx, buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
json := `{"database":"` + v + `"}`
|
||||
sql := "INSERT INTO `config` (`key`,`config`) " +
|
||||
"VALUES ('META','" + json +
|
||||
"') ON DUPLICATE KEY UPDATE `config`='" + json + "';"
|
||||
|
||||
_, err = tx.Exec(sql)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//fmt.Println("DEBUG insert 10s wait for testing")
|
||||
//time.Sleep(10 * time.Second)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func migrateEnd(tx *sqlx.Tx, err error) error {
|
||||
if tx != nil {
|
||||
_, ulerr := tx.Exec("UNLOCK TABLES;")
|
||||
log.IfErr(ulerr)
|
||||
if err == nil {
|
||||
log.IfErr(tx.Commit())
|
||||
log.Info("Database migration completed.")
|
||||
return nil
|
||||
}
|
||||
log.IfErr(tx.Rollback())
|
||||
}
|
||||
log.Error("Database migration failed: ", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Migrate the database as required, consolidated action.
|
||||
func Migrate(lastMigration string) error {
|
||||
func Migrate(ConfigTableExists bool) error {
|
||||
|
||||
lastMigration := ""
|
||||
|
||||
tx, err := (*dbPtr).Beginx()
|
||||
if err != nil {
|
||||
return migrateEnd(tx, err)
|
||||
}
|
||||
|
||||
if ConfigTableExists {
|
||||
_, err = tx.Exec("LOCK TABLE `config` WRITE;")
|
||||
if err != nil {
|
||||
return migrateEnd(tx, err)
|
||||
}
|
||||
|
||||
log.Info("Database migration lock taken.")
|
||||
|
||||
var stmt *sql.Stmt
|
||||
stmt, err = tx.Prepare("SELECT JSON_EXTRACT(`config`,'$.database') FROM `config` WHERE `key` = 'META';")
|
||||
if err == nil {
|
||||
defer utility.Close(stmt)
|
||||
var item = make([]uint8, 0)
|
||||
|
||||
row := stmt.QueryRow()
|
||||
|
||||
err = row.Scan(&item)
|
||||
if err != nil {
|
||||
return migrateEnd(tx, err)
|
||||
}
|
||||
|
||||
if len(item) > 1 {
|
||||
q := []byte(`"`)
|
||||
lastMigration = string(bytes.TrimPrefix(bytes.TrimSuffix(item, q), q))
|
||||
}
|
||||
}
|
||||
log.Info("Database migration last previously applied file was: " + lastMigration)
|
||||
}
|
||||
|
||||
mig, err := migrations(lastMigration)
|
||||
if err != nil {
|
||||
return err
|
||||
return migrateEnd(tx, err)
|
||||
}
|
||||
|
||||
if len(mig) == 0 {
|
||||
return nil // no migrations to perform
|
||||
log.Info("Database migration no updates to perform.")
|
||||
return migrateEnd(tx, nil) // no migrations to perform
|
||||
}
|
||||
locked, err := lockDB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if locked {
|
||||
defer unlockDB()
|
||||
if err := mig.migrate(); err != nil {
|
||||
log.Info("Database migration will execute the following update files: " + strings.Join([]string(mig), ", "))
|
||||
|
||||
return migrateEnd(tx, mig.migrate(tx))
|
||||
}
|
||||
|
||||
func processSQLfile(tx *sqlx.Tx, buf []byte) error {
|
||||
|
||||
stmts := getStatements(buf)
|
||||
|
||||
for _, stmt := range stmts {
|
||||
|
||||
_, err := tx.Exec(stmt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getStatement strips out the comments and returns all the individual SQL commands (apart from "USE") as a []string.
|
||||
func getStatements(bytes []byte) []string {
|
||||
/* Strip comments of the form '-- comment' or like this one */
|
||||
stripped := regexp.MustCompile("(?s)--.*?\n|/\\*.*?\\*/").ReplaceAll(bytes, []byte("\n"))
|
||||
sqls := strings.Split(string(stripped), ";")
|
||||
ret := make([]string, 0, len(sqls))
|
||||
for _, v := range sqls {
|
||||
trimmed := strings.TrimSpace(v)
|
||||
if len(trimmed) > 0 &&
|
||||
!strings.HasPrefix(strings.ToUpper(trimmed), "USE ") { // make sure we don't USE the wrong database
|
||||
ret = append(ret, trimmed+";")
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import (
|
|||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// CallbackT is the type signature of the callback function of GetString().
|
||||
|
@ -36,6 +37,7 @@ type varsT struct {
|
|||
}
|
||||
|
||||
var vars varsT
|
||||
var varsMutex sync.Mutex
|
||||
|
||||
// Len is part of sort.Interface.
|
||||
func (v *varsT) Len() int {
|
||||
|
@ -59,6 +61,8 @@ const goInit = "(default)"
|
|||
|
||||
// GetString sets-up the flag for later use, it must be called before ParseOK(), usually in an init().
|
||||
func GetString(target *string, name string, required bool, usage string, callback CallbackT) {
|
||||
varsMutex.Lock()
|
||||
defer varsMutex.Unlock()
|
||||
name = strings.ToLower(strings.TrimSpace(name))
|
||||
setter := Prefix + strings.ToUpper(name)
|
||||
value := os.Getenv(setter)
|
||||
|
@ -76,6 +80,8 @@ var showSettings = flag.Bool("showsettings", false, "if true, show settings in t
|
|||
// It should be the first thing called by any main() that uses this library.
|
||||
// If all the required variables are not present, it prints an error and calls os.Exit(2) like flag.Parse().
|
||||
func Parse(doFirst string) {
|
||||
varsMutex.Lock()
|
||||
defer varsMutex.Unlock()
|
||||
flag.Parse()
|
||||
sort.Sort(&vars)
|
||||
for pass := 1; pass <= 2; pass++ {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue