1
0
Fork 0
mirror of https://github.com/documize/community.git synced 2025-07-28 01:29:43 +02:00

Merge branch 'master' into github-tidy-up

# Conflicts:
#	embed/bindata_assetfs.go
This commit is contained in:
Elliott Stoneham 2016-08-15 16:32:06 +01:00
commit 6f0d503cb5
34 changed files with 792 additions and 449 deletions

5
.gitattributes vendored Normal file
View file

@ -0,0 +1,5 @@
# Set the default behavior, in case people don't have core.autocrlf set.
* text=auto
# These files are text and should be normalized (convert crlf > lf)
*.hbs text

5
.gitconfig Normal file
View file

@ -0,0 +1,5 @@
[core]
whitespace = trailing-space,space-before-tab
autocrlf = input
[apply]
whitespace = fix

2
.gitignore vendored
View file

@ -61,3 +61,5 @@ debug
Dockerfile
container.sh
make.sh
embed/bindata_assetfs.go

View file

@ -30,6 +30,12 @@ v0.15.0
![Alt text](screenshot.png "Documize")
## Auth0 Integration
Documize is compatible with Auth0 identity as a service.
<a width="150" height="50" href="https://auth0.com/?utm_source=oss&utm_medium=gp&utm_campaign=oss" target="_blank" alt="Single Sign On & Token Based Authentication - Auth0"><img width="150" height="50" alt="JWT Auth for open source projects" src="https://cdn.auth0.com/oss/badges/a0-badge-dark.png"/></a>
## Legal
https://documize.com

View file

@ -0,0 +1,71 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
import Ember from 'ember';
const {
isEmpty,
computed,
set
} = Ember;
export default Ember.Component.extend({
titleEmpty: computed.empty('model.title'),
firstnameEmpty: computed.empty('model.firstname'),
lastnameEmpty: computed.empty('model.lastname'),
emailEmpty: computed.empty('model.email'),
passwordEmpty: computed.empty('model.password'),
hasEmptyTitleError: computed.and('titleEmpty', 'titleError'),
hasEmptyFirstnameError: computed.and('firstnameEmpty', 'adminFirstnameError'),
hasEmptyLastnameError: computed.and('lastnameEmpty', 'adminLastnameError'),
hasEmptyEmailError: computed.and('emailEmpty', 'adminEmailError'),
hasEmptyPasswordError: computed.and('passwordEmpty', 'adminPasswordError'),
actions: {
save() {
if (isEmpty(this.get('model.title'))) {
set(this, 'titleError', true);
return $("#siteTitle").focus();
}
if (isEmpty(this.get('model.firstname'))) {
set(this, 'adminFirstnameError', true);
return $("#adminFirstname").focus();
}
if (isEmpty(this.get('model.lastname'))) {
set(this, 'adminLastnameError', true);
return $("#adminLastname").focus();
}
if (isEmpty(this.get('model.email')) || !is.email(this.get('model.email'))) {
set(this, 'adminEmailError', true);
return $("#adminEmail").focus();
}
if (isEmpty(this.get('model.password'))) {
set(this, 'adminPasswordError', true);
return $("#adminPassword").focus();
}
this.model.allowAnonymousAccess = Ember.$("#allowAnonymousAccess").prop('checked');
this.get('save')().then(() => {
set(this, 'titleError', false);
set(this, 'adminFirstnameError', false);
set(this, 'adminLastnameError', false);
set(this, 'adminEmailError', false);
set(this, 'adminPasswordError', false);
});
}
}
});

View file

@ -0,0 +1,41 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
import Ember from 'ember';
const {
computed,
isEmpty
} = Ember;
export default Ember.Component.extend({
email: "",
sayThanks: false,
emailEmpty: computed.empty('email'),
hasEmptyEmailError: computed.and('emailEmpty', 'emailIsEmpty'),
actions: {
forgot() {
let email = this.get('email');
if (isEmpty(email)) {
Ember.set(this, 'emailIsEmpty', true);
return $("#email").focus();
}
this.get('forgot')(email).then(() => {
Ember.set(this, 'sayThanks', true);
Ember.set(this, 'email', '');
Ember.set(this, 'emailIsEmpty', false);
});
}
}
});

View file

@ -0,0 +1,45 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
import Ember from 'ember';
const {
isEmpty,
computed,
set
} = Ember;
export default Ember.Component.extend({
titleEmpty: computed.empty('model.title'),
messageEmpty: computed.empty('model.message'),
hasTitleInputError: computed.and('titleEmpty', 'titleError'),
hasMessageInputError: computed.and('messageEmpty', 'messageError'),
actions: {
save() {
if (isEmpty(this.get('model.title'))) {
set(this, 'titleError', true);
return $("#siteTitle").focus();
}
if (isEmpty(this.get('model.message'))) {
set(this, 'messageError', true);
return $("#siteMessage").focus();
}
this.model.set('allowAnonymousAccess', Ember.$("#allowAnonymousAccess").prop('checked'));
this.get('save')().then(() => {
set(this, 'titleError', false);
set(this, 'messageError', false);
});
}
}
});

View file

@ -0,0 +1,59 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
import Ember from 'ember';
const {
isEmpty,
isEqual,
computed,
set
} = Ember;
export default Ember.Component.extend({
password: "",
passwordConfirm: "",
mustMatch: false,
passwordEmpty: computed.empty('password'),
confirmEmpty: computed.empty('passwordConfirm'),
hasPasswordError: computed.and('passwordEmpty', 'passwordIsEmpty'),
hasConfirmError: computed.and('confirmEmpty', 'passwordConfirmIsEmpty'),
actions: {
reset() {
let password = this.get('password');
let passwordConfirm = this.get('passwordConfirm');
if (isEmpty(password)) {
set(this, 'passwordIsEmpty', true);
return $("#newPassword").focus();
}
if (isEmpty(passwordConfirm)) {
set(this, 'passwordConfirmIsEmpty', true);
return $("#passwordConfirm").focus();
}
if (!isEqual(password, passwordConfirm)) {
set(this, 'hasPasswordError', true);
set(this, 'hasConfirmError', true);
set(this, 'mustMatch', true);
return;
}
this.get('reset')(password).then(() => {
set(this, 'passwordIsEmpty', false);
set(this, 'passwordConfirmIsEmpty', false);
});
}
}
});

View file

@ -77,7 +77,7 @@ export default Ember.Component.extend({
};
if (typeof tinymce === 'undefined') {
$.getScript(this.get("appMeta").getBaseUrl("tinymce/tinymce.min.js?v=430"), function() {
$.getScript("tinymce/tinymce.min.js?v=430", function () {
window.tinymce.dom.Event.domLoaded = true;
tinymce.baseURL = "//" + window.location.host + "/tinymce";
tinymce.suffix = ".min";

View file

@ -0,0 +1,83 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
import Ember from 'ember';
const {
computed,
isEmpty,
isEqual,
isPresent
} = Ember;
export default Ember.Component.extend({
password: { password: "", confirmation: "" },
hasFirstnameError: computed.empty('model.firstname'),
hasLastnameError: computed.empty('model.lastname'),
hasEmailError: computed.empty('model.email'),
hasPasswordError: computed('passwordError', 'password.password', {
get() {
if (isPresent(this.get('passwordError'))) {
return `error`;
}
if (isEmpty(this.get('password.password'))) {
return null;
}
}
}),
hasConfirmPasswordError: computed('confirmPasswordError', {
get() {
if (isPresent(this.get("confirmPasswordError"))) {
return `error`;
}
return;
}
}),
actions: {
save() {
let password = this.get('password.password');
let confirmation = this.get('password.confirmation');
if (isEmpty(this.get('model.firstname'))) {
return $("#firstname").focus();
}
if (isEmpty(this.get('model.lastname'))) {
return $("#lastname").focus();
}
if (isEmpty(this.get('model.email'))) {
return $("#email").focus();
}
if (isPresent(password) && isEmpty(confirmation)) {
Ember.set(this, 'confirmPasswordError', 'error');
return $("#confirmPassword").focus();
}
if (isEmpty(password) && isPresent(confirmation)) {
Ember.set(this, 'passwordError', 'error');
return $("#password").focus();
}
if (!isEqual(password, confirmation)) {
Ember.set(this, 'passwordError', 'error');
return $("#password").focus();
}
let passwords = this.get('password');
this.get('save')(passwords).finally(() => {
Ember.set(this, 'password.password', '');
Ember.set(this, 'password.confirmation', '');
});
}
}
});

View file

@ -0,0 +1,56 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
import Ember from 'ember';
const {
isEmpty,
computed,
set,
get
} = Ember;
export default Ember.Component.extend({
newUser: { firstname: "", lastname: "", email: "", active: true },
firstnameEmpty: computed.empty('newUser.firstname'),
lastnameEmpty: computed.empty('newUser.lastname'),
emailEmpty: computed.empty('newUser.email'),
hasFirstnameEmptyError: computed.and('firstnameEmpty', 'firstnameError'),
hasLastnameEmptyError: computed.and('lastnameEmpty', 'lastnameError'),
hasEmailEmptyError: computed.and('emailEmpty', 'emailError'),
actions: {
add() {
if (isEmpty(this.get('newUser.firstname'))) {
set(this, 'firstnameError', true);
return $("#newUserFirstname").focus();
}
if (isEmpty(this.get('newUser.lastname'))) {
set(this, 'lastnameError', true);
return $("#newUserLastname").focus();
}
if (isEmpty(this.get('newUser.email')) || is.not.email(this.get('newUser.email'))) {
set(this, 'emailError', true);
return $("#newUserEmail").focus();
}
let user = get(this, 'newUser');
get(this, 'add')(user).then(() => {
this.set('newUser', { firstname: "", lastname: "", email: "", active: true });
set(this, 'firstnameError', false);
set(this, 'lastnameError', false);
set(this, 'emailError', false);
$("#newUserFirstname").focus();
});
}
}
});

View file

@ -13,23 +13,10 @@ import Ember from 'ember';
export default Ember.Controller.extend({
userService: Ember.inject.service('user'),
email: "",
sayThanks: false,
actions: {
forgot: function () {
var self = this;
var email = this.get('email');
if (is.empty(email)) {
$("#email").addClass("error").focus();
return;
}
self.set('sayThanks', true);
this.set('email', '');
this.get('userService').forgotPassword(email);
forgot: function (email) {
return this.get('userService').forgotPassword(email);
}
}
});

View file

@ -3,20 +3,6 @@
<img src="assets/img/logo-color.png" title="Documize" alt="Documize" class="responsive-img" />
</div>
<div class="login-form">
<form {{action 'forgot' on="submit"}}>
{{#if sayThanks}}
<div class="reset-thanks margin-bottom-30">Thanks. Check your email for instructions.</div>
{{else}}
<div class="input-control">
<label>Email</label>
{{focus-input type="email" value=email id="email"}}
</div>
<div class="clearfix" />
<div class="margin-top-10 margin-bottom-20">
<button type="submit" class="regular-button button-blue">Reset</button>
</div>
{{/if}}
{{#link-to 'auth.login'}}Sign In{{/link-to}}
</form>
{{forgot-password forgot=(action 'forgot')}}
</div>
</div>

View file

@ -18,30 +18,9 @@ export default Ember.Controller.extend({
mustMatch: false,
actions: {
reset() {
let self = this;
let password = this.get('password');
let passwordConfirm = this.get('passwordConfirm');
if (is.empty(password)) {
$("#newPassword").addClass("error").focus();
return;
}
if (is.empty(passwordConfirm)) {
$("#passwordConfirm").addClass("error").focus();
return;
}
if (is.not.equal(password, passwordConfirm)) {
$("#newPassword").addClass("error").focus();
$("#passwordConfirm").addClass("error");
self.set('mustMatch', true);
return;
}
this.get('userService').resetPassword(self.model, password).then(function (response) { /* jshint ignore:line */
self.transitionToRoute('auth.login');
reset(password) {
return this.get('userService').resetPassword(this.model, password).then(() => { /* jshint ignore:line */
this.transitionToRoute('auth.login');
});
}
}

View file

@ -2,23 +2,5 @@
<div class="logo">
<img src="assets/img/logo-color.png" title="Documize" alt="Documize" class="responsive-img" />
</div>
<div class="login-form">
<form {{action 'reset' on="submit"}}>
<div class="input-control">
<label>New Password</label>
<div class="tip">Choose a strong password</div>
{{focus-input type="password" value=password id="newPassword"}}
</div>
<div class="input-control">
<label>Confirm Password</label>
<div class="tip">Please type your new password again</div>
{{input type="password" value=passwordConfirm id="passwordConfirm"}}
</div>
<div class="clearfix" />
<div class="margin-top-10 margin-bottom-20">
<button type="submit" class="regular-button button-blue">Reset</button>
<span class="{{unless mustMatch "hide"}} color-red margin-left-20">Passwords must match</span>
</div>
</form>
</div>
{{password-reset reset=(action 'reset')}}
</div>

View file

@ -17,19 +17,9 @@ export default Ember.Controller.extend(NotifierMixin, {
actions: {
save() {
if (is.empty(this.model.get('title'))) {
$("#siteTitle").addClass("error").focus();
return;
}
if (is.empty(this.model.get('message'))) {
$("#siteMessage").addClass("error").focus();
return;
}
this.model.set('allowAnonymousAccess', Ember.$("#allowAnonymousAccess").prop('checked'));
this.get('orgService').save(this.model);
return this.get('orgService').save(this.model).then(() => {
this.showNotification('Saved');
});
}
}
});

View file

@ -1,29 +1,3 @@
<div class="input-form form-borderless">
<form>
<div class="heading">
<div class="title">General Settings</div>
<div class="tip">Tell people about this Documize instance</div>
</div>
<div>
<div class="input-control">
<label>Title</label>
<div class="tip">Describe the title of this Documize instance</div>
{{focus-input id="siteTitle" type="text" value=model.title}}
</div>
<div class="input-control">
<label>Message</label>
<div class="tip">Describe the purpose of this Documize instance</div>
{{textarea id="siteMessage" rows="3" value=model.message}}
</div>
<div class="input-control">
<label>Anonymous Access</label>
<div class="tip">Content within "Everyone" will be made available to anonymous users</div>
<div class="checkbox">
<input type="checkbox" id="allowAnonymousAccess" checked= {{model.allowAnonymousAccess}} />
<label for="allowAnonymousAccess">Allow anyone to access this Documize instance</label>
</div>
</div>
<div class="regular-button button-blue" {{ action 'save' }}>save</div>
</div>
</form>
{{general-settings model=model save=(action 'save')}}
</div>

View file

@ -17,30 +17,13 @@ export default Ember.Controller.extend(NotifierMixin, {
newUser: { firstname: "", lastname: "", email: "", active: true },
actions: {
add: function () {
if (is.empty(this.newUser.firstname)) {
$("#newUserFirstname").addClass("error").focus();
return;
}
if (is.empty(this.newUser.lastname)) {
$("#newUserLastname").addClass("error").focus();
return;
}
if (is.empty(this.newUser.email) || is.not.email(this.newUser.email)) {
$("#newUserEmail").addClass("error").focus();
return;
}
add(user) {
Ember.set(this, 'newUser', user);
$("#newUserFirstname").removeClass("error");
$("#newUserLastname").removeClass("error");
$("#newUserEmail").removeClass("error");
this.get('userService')
return this.get('userService')
.add(this.get('newUser'))
.then((user) => {
this.showNotification('Added');
this.set('newUser', { firstname: "", lastname: "", email: "", active: true });
$("#newUserFirstname").focus();
this.get('model').pushObject(user);
})
.catch(function (error) {

View file

@ -1,23 +1,5 @@
<div class="input-form form-borderless">
<form>
<div class="heading">
<div class="title">Add User</div>
<div class="tip">New users receive an invitation email with a random password</div>
</div>
<div class="input-control">
<label>Firstname</label>
{{focus-input id="newUserFirstname" type="text" value=newUser.firstname}}
</div>
<div class="input-control">
<label>Lastname</label>
{{input id="newUserLastname" type="text" value=newUser.lastname}}
</div>
<div class="input-control">
<label>Email</label>
{{input id="newUserEmail" type="text" value=newUser.email}}
</div>
<div class="regular-button button-blue" {{ action 'add' }}>Add</div>
</form>
{{user-settings add=(action 'add')}}
</div>
<div class="clearfix" /> {{settings/user-list users=model onDelete=(action "onDelete") onSave=(action "onSave") onPassword=(action "onPassword")}}

View file

@ -11,52 +11,27 @@
import Ember from 'ember';
const {
isPresent
} = Ember;
export default Ember.Controller.extend({
userService: Ember.inject.service('user'),
password: { password: "", confirmation: "" },
session: Ember.inject.service(),
actions: {
save: function () {
if (is.empty(this.model.get('firstname'))) {
$("#firstname").addClass("error").focus();
return;
}
if (is.empty(this.model.get('lastname'))) {
$("#lastname").addClass("error").focus();
return;
}
if (is.empty(this.model.get('email'))) {
$("#email").addClass("error").focus();
return;
}
if (is.not.empty(this.password.password) && is.empty(this.password.confirmation)) {
$("#confirmPassword").addClass("error").focus();
return;
}
if (is.empty(this.password.password) && is.not.empty(this.password.confirmation)) {
$("#password").addClass("error").focus();
return;
}
if (is.not.equal(this.password.password, this.password.confirmation)) {
$("#password").addClass("error").focus();
return;
}
save(passwords) {
let password = passwords.password;
let confirmation = passwords.confirmation;
let self = this;
this.get('userService').save(this.model).then(function () {
if (is.not.empty(self.password.password) && is.not.empty(self.password.confirmation)) {
self.get('userService').updatePassword(self.model.get('id'), self.password.password).then(function () {
self.password.password = "";
self.password.confirmation = "";
});
return this.get('userService').save(this.model).then(() => {
if (isPresent(password) && isPresent(confirmation)) {
this.get('userService').updatePassword(this.get('model.id'), password);
}
self.model.generateInitials();
self.get('session').set('user', self.model);
});
this.model.generateInitials();
this.get('session').set('user', this.model);
this.transitionToRoute('folders');
});
}
}
});

View file

@ -13,33 +13,7 @@
{{/layout/zone-sidebar}}
{{#layout/zone-content}}
<div class="input-form form-borderless">
<form>
<div class="input-control">
<label>Firstname</label>
{{focus-input id="firstname" type="text" value=model.firstname}}
</div>
<div class="input-control">
<label>Lastname</label>
{{input id="lastname" type="text" value=model.lastname}}
</div>
<div class="input-control">
<label>Email</label>
{{input id="email" type="text" value=model.email}}
</div>
<div class="input-control">
<label>Password</label>
<div class="tip">Optional change your password</div>
{{input id="password" type="password" value=password.password}}
</div>
<div class="input-control">
<label>Confirm Password</label>
<div class="tip">Confirm your new password</div>
{{input id="confirmPassword" type="password" value=password.confirmation}}
</div>
<div class="regular-button button-blue" {{ action 'save' }}>save</div>
</form>
</div>
{{user-profile model=model save=(action 'save')}}
{{/layout/zone-content}}
{{/layout/zone-container}}

View file

@ -19,39 +19,7 @@ export default Ember.Controller.extend(NotifierMixin, {
actions: {
save() {
if (is.empty(this.model.title)) {
$("#siteTitle").addClass("error").focus();
return;
}
if (is.empty(this.model.firstname)) {
$("#adminFirstname").addClass("error").focus();
return;
}
if (is.empty(this.model.lastname)) {
$("#adminLastname").addClass("error").focus();
return;
}
if (is.empty(this.model.email)) {
$("#adminEmail").addClass("error").focus();
return;
}
if (!is.email(this.model.email)) {
$("#adminEmail").addClass("error").focus();
return;
}
if (is.empty(this.model.password)) {
$("#adminPassword").addClass("error").focus();
return;
}
this.model.allowAnonymousAccess = Ember.$("#allowAnonymousAccess").prop('checked');
this.get('ajax').request("/setup", {
return this.get('ajax').request("/setup", {
method: 'POST',
data: this.model,
dataType: "text",

View file

@ -9,46 +9,7 @@
<img src="/assets/img/setup/cogs.png" width="157" height="187" alt="Setup new database" class="no-select no-outline" />
</div>
</div>
<div class="col-lg-9 col-md-9 col-sm-9">
<div class="input-form">
<form>
<div class="heading">
<div class="title">Let's setup Documize</div>
<div class="tip">Database name is <em>{{model.dbname}}</em></div>
</div>
<div>
<div class="input-control">
<label>Team</label>
<div class="tip">What's your tribe called?</div>
{{focus-input id="siteTitle" type="text" value=model.title}}
</div>
<div class="input-control">
<label>Firstname</label>
<div class="tip">What do people call you?</div>
{{input id="adminFirstname" type="text" value=model.firstname}}
</div>
<div class="input-control">
<label>Lastname</label>
<div class="tip">How the government refers to you.</div>
{{input id="adminLastname" type="text" value=model.lastname}}
</div>
<div class="input-control">
<label>Email</label>
<div class="tip">No spam. Ever!</div>
{{input id="adminEmail" type="email" value=model.email}}
</div>
<div class="input-control">
<label>Password</label>
<div class="tip">Something you can remember without writing it down.</div>
{{input id="adminPassword" type="text" value=model.password}}
</div>
<div class="regular-button button-green" {{ action 'save' }}>Go Setup</div>
</div>
</form>
</div>
</div>
{{documize-setup model=model save=(action 'save')}}
</div>
</div>

View file

@ -0,0 +1,38 @@
<div class="col-lg-9 col-md-9 col-sm-9">
<div class="input-form">
<form>
<div class="heading">
<div class="title">Let's setup Documize</div>
<div class="tip">Database name is <em>{{model.dbname}}</em></div>
</div>
<div>
<div class="input-control">
<label>Team</label>
<div class="tip">What's your tribe called?</div>
{{focus-input id="siteTitle" type="text" value=model.title class=(if hasEmptyTitleError 'error')}}
</div>
<div class="input-control">
<label>Firstname</label>
<div class="tip">What do people call you?</div>
{{input id="adminFirstname" type="text" value=model.firstname class=(if hasEmptyFirstnameError 'error')}}
</div>
<div class="input-control">
<label>Lastname</label>
<div class="tip">How the government refers to you.</div>
{{input id="adminLastname" type="text" value=model.lastname class=(if hasEmptyLastnameError 'error')}}
</div>
<div class="input-control">
<label>Email</label>
<div class="tip">No spam. Ever!</div>
{{input id="adminEmail" type="email" value=model.email class=(if hasEmptyEmailError 'error')}}
</div>
<div class="input-control">
<label>Password</label>
<div class="tip">Something you can remember without writing it down.</div>
{{input id="adminPassword" type="text" value=model.password class=(if hasEmptyPasswordError 'error')}}
</div>
<div class="regular-button button-green" {{ action 'save' }}>Go Setup</div>
</div>
</form>
</div>
</div>

View file

@ -0,0 +1,15 @@
<form {{action 'forgot' on="submit"}}>
{{#if sayThanks}}
<div class="reset-thanks margin-bottom-30">Thanks. Check your email for instructions.</div>
{{else}}
<div class="input-control">
<label>Email</label>
{{focus-input type="email" value=email id="email" class=(if hasEmptyEmailError 'error')}}
</div>
<div class="clearfix" />
<div class="margin-top-10 margin-bottom-20">
<button type="submit" class="regular-button button-blue">Reset</button>
</div>
{{/if}}
{{#link-to 'auth.login'}}Sign In{{/link-to}}
</form>

View file

@ -0,0 +1,27 @@
<form>
<div class="heading">
<div class="title">General Settings</div>
<div class="tip">Tell people about this Documize instance</div>
</div>
<div>
<div class="input-control">
<label>Title</label>
<div class="tip">Describe the title of this Documize instance</div>
{{focus-input id="siteTitle" type="text" value=model.title class=(if hasTitleInputError 'error')}}
</div>
<div class="input-control">
<label>Message</label>
<div class="tip">Describe the purpose of this Documize instance</div>
{{textarea id="siteMessage" rows="3" value=model.message class=(if hasMessageInputError 'error')}}
</div>
<div class="input-control">
<label>Anonymous Access</label>
<div class="tip">Content within "Everyone" will be made available to anonymous users</div>
<div class="checkbox">
<input type="checkbox" id="allowAnonymousAccess" checked= {{model.allowAnonymousAccess}} />
<label for="allowAnonymousAccess">Allow anyone to access this Documize instance</label>
</div>
</div>
<div class="regular-button button-blue" {{ action 'save' }}>save</div>
</div>
</form>

View file

@ -0,0 +1,19 @@
<div class="login-form">
<form {{action 'reset' on="submit"}}>
<div class="input-control">
<label>New Password</label>
<div class="tip">Choose a strong password</div>
{{focus-input type="password" value=password id="newPassword" class=(if hasPasswordError 'error')}}
</div>
<div class="input-control">
<label>Confirm Password</label>
<div class="tip">Please type your new password again</div>
{{input type="password" value=passwordConfirm id="passwordConfirm" class=(if hasConfirmError 'error')}}
</div>
<div class="clearfix" />
<div class="margin-top-10 margin-bottom-20">
<button type="submit" class="regular-button button-blue">Reset</button>
<span class="{{unless mustMatch "hide"}} color-red margin-left-20">Passwords must match</span>
</div>
</form>
</div>

View file

@ -0,0 +1,27 @@
<div class="input-form form-borderless">
<form>
<div class="input-control">
<label>Firstname</label>
{{focus-input id="firstname" type="text" value=model.firstname class=(if hasFirstnameError 'error')}}
</div>
<div class="input-control">
<label>Lastname</label>
{{input id="lastname" type="text" value=model.lastname class=(if hasLastnameError 'error')}}
</div>
<div class="input-control">
<label>Email</label>
{{input id="email" type="text" value=model.email class=(if hasEmailError 'error')}}
</div>
<div class="input-control">
<label>Password</label>
<div class="tip">Optional change your password</div>
{{input id="password" type="password" value=password.password class=hasPasswordError}}
</div>
<div class="input-control">
<label>Confirm Password</label>
<div class="tip">Confirm your new password</div>
{{input id="confirmPassword" type="password" value=password.confirmation class=hasConfirmPasswordError}}
</div>
<div class="regular-button button-blue" {{ action 'save' }}>save</div>
</form>
</div>

View file

@ -0,0 +1,19 @@
<form>
<div class="heading">
<div class="title">Add User</div>
<div class="tip">New users receive an invitation email with a random password</div>
</div>
<div class="input-control">
<label>Firstname</label>
{{focus-input id="newUserFirstname" type="text" value=newUser.firstname class=(if hasFirstnameEmptyError 'error')}}
</div>
<div class="input-control">
<label>Lastname</label>
{{input id="newUserLastname" type="text" value=newUser.lastname class=(if hasLastnameEmptyError 'error')}}
</div>
<div class="input-control">
<label>Email</label>
{{input id="newUserEmail" type="text" value=newUser.email class=(if hasEmailEmptyError 'error')}}
</div>
<div class="regular-button button-blue" {{ action 'add' }}>Add</div>
</form>

View file

@ -50,8 +50,9 @@ func init() {
var err error
environment.GetString(&connectionString, "db", true,
`"username:password@protocol(hostname:port)/databasename" for example "fred:bloggs@tcp(localhost:3306)/documize"`,
`'username:password@protocol(hostname:port)/databasename" for example "fred:bloggs@tcp(localhost:3306)/documize"`,
func(*string, string) bool {
Db, err = sqlx.Open("mysql", stdConn(connectionString))
if err != nil {

View file

@ -19,9 +19,9 @@ import (
"time"
"github.com/documize/community/core/api/util"
"github.com/documize/community/core/web"
"github.com/documize/community/core/log"
"github.com/documize/community/core/utility"
"github.com/documize/community/core/web"
)
// runSQL creates a transaction per call

View file

@ -13,16 +13,20 @@ package database
import (
"bytes"
"crypto/rand"
"database/sql"
"fmt"
"os"
"regexp"
"sort"
"strings"
"time"
"github.com/jmoiron/sqlx"
"github.com/documize/community/core/web"
"github.com/documize/community/core/log"
"github.com/documize/community/core/utility"
"github.com/documize/community/core/web"
)
const migrationsDir = "bindata/scripts"
@ -69,35 +73,86 @@ func migrations(lastMigration string) (migrationsT, 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
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)
_, err = tx.Exec(sql) // add a record in the config file to say we have done the upgrade
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 {
func lockDB() (bool, error) {
b := make([]byte, 2)
_, err := rand.Read(b)
if err != nil {
return false, err
}
wait := ((time.Duration(b[0]) << 8) | time.Duration(b[1])) * time.Millisecond / 10 // up to 6.5 secs wait
time.Sleep(wait)
tx, err := (*dbPtr).Beginx()
if err != nil {
return false, err
}
_, err = tx.Exec("LOCK TABLE `config` WRITE;")
if err != nil {
return false, err
}
defer func() {
_, err = tx.Exec("UNLOCK TABLES;")
log.IfErr(err)
log.IfErr(tx.Commit())
}()
_, err = tx.Exec("INSERT INTO `config` (`key`,`config`) " +
fmt.Sprintf(`VALUES ('DBLOCK','{"pid": "%d"}');`, os.Getpid()))
if err != nil {
// good error would be "Error 1062: Duplicate entry 'DBLOCK' for key 'idx_config_area'"
if strings.HasPrefix(err.Error(), "Error 1062:") {
log.Info("Database locked by annother Documize instance")
return false, nil
}
return false, err
}
log.Info("Database locked by this Documize instance")
return true, err // success!
}
func unlockDB() error {
tx, err := (*dbPtr).Beginx()
if err != nil {
return err
}
_, err = tx.Exec("DELETE FROM `config` WHERE `key`='DBLOCK';")
if err != nil {
return err
}
return tx.Commit()
}
func migrateEnd(tx *sqlx.Tx, err error, amLeader bool) error {
if amLeader {
defer func() { log.IfErr(unlockDB()) }()
if tx != nil {
_, ulerr := tx.Exec("UNLOCK TABLES;")
log.IfErr(ulerr)
if err == nil {
log.IfErr(tx.Commit())
log.Info("Database checks: completed")
@ -108,25 +163,10 @@ func migrateEnd(tx *sqlx.Tx, err error) error {
log.Error("Database checks: failed: ", err)
return err
}
// Migrate the database as required, consolidated action.
func Migrate(ConfigTableExists bool) error {
lastMigration := ""
tx, err := (*dbPtr).Beginx()
if err != nil {
return migrateEnd(tx, err)
return nil // not the leader, so ignore errors
}
if ConfigTableExists {
_, err = tx.Exec("LOCK TABLE `config` WRITE;")
if err != nil {
return migrateEnd(tx, err)
}
log.Info("Database checks: lock taken")
func getLastMigration(tx *sqlx.Tx) (lastMigration string, err error) {
var stmt *sql.Stmt
stmt, err = tx.Prepare("SELECT JSON_EXTRACT(`config`,'$.database') FROM `config` WHERE `key` = 'META';")
if err == nil {
@ -136,30 +176,73 @@ func Migrate(ConfigTableExists bool) error {
row := stmt.QueryRow()
err = row.Scan(&item)
if err != nil {
return migrateEnd(tx, err)
}
if err == nil {
if len(item) > 1 {
q := []byte(`"`)
lastMigration = string(bytes.TrimPrefix(bytes.TrimSuffix(item, q), q))
}
}
}
return
}
// Migrate the database as required, consolidated action.
func Migrate(ConfigTableExists bool) error {
amLeader := false
if ConfigTableExists {
var err error
amLeader, err = lockDB()
log.IfErr(err)
} else {
amLeader = true // what else can you do?
}
tx, err := (*dbPtr).Beginx()
if err != nil {
return migrateEnd(tx, err, amLeader)
}
lastMigration := ""
if ConfigTableExists {
lastMigration, err = getLastMigration(tx)
if err != nil {
return migrateEnd(tx, err, amLeader)
}
log.Info("Database checks: last previously applied file was " + lastMigration)
}
mig, err := migrations(lastMigration)
if err != nil {
return migrateEnd(tx, err)
return migrateEnd(tx, err, amLeader)
}
if len(mig) == 0 {
log.Info("Database checks: no updates to perform")
return migrateEnd(tx, nil) // no migrations to perform
return migrateEnd(tx, nil, amLeader) // no migrations to perform
}
log.Info("Database checks: will execute the following update files: " + strings.Join([]string(mig), ", "))
return migrateEnd(tx, mig.migrate(tx))
if amLeader {
log.Info("Database checks: will execute the following update files: " + strings.Join([]string(mig), ", "))
return migrateEnd(tx, mig.migrate(tx), amLeader)
}
// a follower instance
targetMigration := string(mig[len(mig)-1])
for targetMigration != lastMigration {
time.Sleep(time.Second)
log.Info("Waiting for database migration process to complete")
tx.Rollback() // ignore error
tx, err := (*dbPtr).Beginx() // need this in order to see the changed situation since last tx
if err != nil {
return migrateEnd(tx, err, amLeader)
}
lastMigration, _ = getLastMigration(tx)
}
return migrateEnd(tx, nil, amLeader)
}
func processSQLfile(tx *sqlx.Tx, buf []byte) error {

View file

@ -27,7 +27,7 @@ type ProdInfo struct {
func Product() (p ProdInfo) {
p.Major = "0"
p.Minor = "16"
p.Patch = "0"
p.Patch = "1"
p.Version = fmt.Sprintf("%s.%s.%s", p.Major, p.Minor, p.Patch)
p.Edition = "Community"
p.Title = fmt.Sprintf("%s Edition", p.Edition)