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

restructure directories

This commit is contained in:
Elliott Stoneham 2016-07-20 15:58:37 +01:00
parent 7e4ed6545b
commit a2ce777762
159 changed files with 320 additions and 323 deletions

148
core/api/request/account.go Normal file
View file

@ -0,0 +1,148 @@
// 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
package request
import (
"database/sql"
"fmt"
"github.com/documize/community/core/api/entity"
"github.com/documize/community/core/log"
"github.com/documize/community/core/utility"
"time"
)
// AddAccount inserts the given record into the datbase account table.
func (p *Persister) AddAccount(account entity.Account) (err error) {
account.Created = time.Now().UTC()
account.Revised = time.Now().UTC()
stmt, err := p.Context.Transaction.Preparex("INSERT INTO account (refid, orgid, userid, admin, editor, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?)")
defer utility.Close(stmt)
if err != nil {
log.Error("Unable to prepare insert for account", err)
return
}
_, err = stmt.Exec(account.RefID, account.OrgID, account.UserID, account.Admin, account.Editor, account.Created, account.Revised)
if err != nil {
log.Error("Unable to execute insert for account", err)
return
}
return
}
// GetUserAccount returns the databse account record corresponding to the given userID, using the client's current organizaion.
func (p *Persister) GetUserAccount(userID string) (account entity.Account, err error) {
err = nil
stmt, err := Db.Preparex("SELECT a.*, b.company, b.title, b.message, b.domain FROM account a, organization b WHERE b.refid=a.orgid and a.orgid=? and a.userid=? AND b.active=1")
defer utility.Close(stmt)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare select for account by user %s", userID), err)
return
}
err = stmt.Get(&account, p.Context.OrgID, userID)
if err != sql.ErrNoRows && err != nil {
log.Error(fmt.Sprintf("Unable to execute select for account by user %s", userID), err)
return
}
return
}
// GetUserAccounts returns a slice of database account records, for all organizations that the userID is a member of, in organization title order.
func (p *Persister) GetUserAccounts(userID string) (t []entity.Account, err error) {
err = nil
err = Db.Select(&t, "SELECT a.*, b.company, b.title, b.message, b.domain FROM account a, organization b WHERE a.userid=? AND a.orgid=b.refid AND b.active=1 ORDER BY b.title", userID)
if err != sql.ErrNoRows && err != nil {
log.Error(fmt.Sprintf("Unable to execute select account for user %s", userID), err)
}
return
}
// GetAccountsByOrg returns a slice of database account records, for all users in the client's organization.
func (p *Persister) GetAccountsByOrg() (t []entity.Account, err error) {
err = nil
err = Db.Select(&t, "SELECT a.*, b.company, b.title, b.message, b.domain FROM account a, organization b WHERE a.orgid=b.refid AND a.orgid=? AND b.active=1", p.Context.OrgID)
if err != sql.ErrNoRows && err != nil {
log.Error(fmt.Sprintf("Unable to execute select account for org %s", p.Context.OrgID), err)
}
return
}
// UpdateAccount updates the database record for the given account to the given values.
func (p *Persister) UpdateAccount(account entity.Account) (err error) {
err = nil
account.Revised = time.Now().UTC()
stmt, err := p.Context.Transaction.PrepareNamed("UPDATE account SET userid=:userid, admin=:admin, editor=:editor, revised=:revised WHERE orgid=:orgid AND refid=:refid")
defer utility.Close(stmt)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare update for account %s", account.RefID), err)
return
}
_, err = stmt.Exec(&account)
if err != sql.ErrNoRows && err != nil {
log.Error(fmt.Sprintf("Unable to execute update for account %s", account.RefID), err)
return
}
return
}
// HasOrgAccount returns if the given orgID has valid userID.
func (p *Persister) HasOrgAccount(orgID, userID string) bool {
row := Db.QueryRow("SELECT count(*) FROM account WHERE orgid=? and userid=?", orgID, userID)
var count int
err := row.Scan(&count)
if err != nil && err != sql.ErrNoRows {
log.Error(p.Base.SQLSelectError("HasOrgAccount", userID), err)
return false
}
if err == sql.ErrNoRows {
return false
}
if count == 0 {
return false
}
return true
}
// DeleteAccount deletes the database record in the account table for user ID.
func (p *Persister) DeleteAccount(ID string) (rows int64, err error) {
return p.Base.DeleteConstrained(p.Context.Transaction, "account", p.Context.OrgID, ID)
}

View file

@ -0,0 +1,139 @@
// 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
package request
/* TODO(Elliott)
import (
"github.com/documize/community/documize/api/entity"
"github.com/documize/community/core/environment"
"testing"
)
const testAcc = "TestAccount"
func testAddAccount(t *testing.T, p *Persister) entity.Account {
acc := entity.Account{
BaseEntity: entity.BaseEntity{RefID: testAcc},
Admin: true, // bool `json:"admin"`
Editor: true, // bool `json:"editor"`
UserID: p.Context.UserID, // string `json:"userId"`
OrgID: p.Context.OrgID, // string `json:"orgId"`
Company: "testCompany", // string `json:"company"`
Title: "testTitle", // string `json:"title"`
Message: "testMessage", // string `json:"message"`
Domain: "testDomain", // string `json:"domain"`
}
err := p.AddAccount(acc)
if err != nil {
t.Error(err)
t.Fail()
}
p.testCommit(t)
return acc
}
func testDeleteAccount(t *testing.T, p *Persister) {
p.testNewTx(t) // so that we can use it reliably in defer
rows, err := p.DeleteAccount(testAcc)
if err != nil {
t.Error(err)
t.Fail()
}
if rows != 1 {
t.Errorf("expected 1 row deleted got %d", rows)
t.Fail()
}
p.testCommit(t)
}
func TestAccount(t *testing.T) {
environment.Parse("db")
p := newTestPersister(t)
defer deleteTestAuditTrail(t, p)
org := testAddOrganization(t, p)
defer testDeleteOrganization(t, p)
user := testAddUser(t, p)
defer testDeleteUser(t, p)
acc := testAddAccount(t, p)
defer testDeleteAccount(t, p)
err := p.AddAccount(entity.Account{
BaseEntity: entity.BaseEntity{RefID: acc.RefID},
OrgID: org.RefID,
UserID: user.RefID,
})
if err == nil {
t.Error("did not error as expected on duplicate record")
}
p.testRollback(t)
acc2, err := p.GetUserAccount(user.RefID)
if err != nil {
t.Error(err)
}
if acc.Company != acc2.Company {
t.Errorf("bad data returned want: `%s` got: `%s`", acc.Company, acc2.Company)
}
p.testRollback(t)
gua, err := p.GetUserAccounts(user.RefID)
if err != nil {
t.Error(err)
}
if len(gua) != 1 {
t.Errorf("length is %d not 1 ", len(gua))
} else {
if acc.Company != gua[0].Company {
t.Errorf("bad data returned want: `%s` got: `%s`", acc.Company, gua[0].Company)
}
}
p.testRollback(t)
gabo, err := p.GetAccountsByOrg()
if err != nil {
t.Error(err)
}
if len(gabo) != 1 {
t.Errorf("length is %d not 1 ", len(gabo))
} else {
if acc.Company != gabo[0].Company {
t.Errorf("bad data returned want: `%s` got: `%s`", acc.Company, gabo[0].Company)
}
}
p.testRollback(t)
if p.HasOrgAccount("XXXXXXXX", "YYYYYY") {
t.Error("found account where there should not be one")
}
if !p.HasOrgAccount(org.RefID, user.RefID) {
t.Error("did not find account where there should be one")
}
p.testRollback(t)
acc.Admin = false
err = p.UpdateAccount(acc)
if err != nil {
t.Error(err)
}
p.testCommit(t)
acc3, err := p.GetUserAccount(user.RefID)
if err != nil {
t.Error(err)
}
if acc3.Admin {
t.Errorf("bad data returned")
}
p.testRollback(t)
}
*/

View file

@ -0,0 +1,106 @@
// 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
package request
import (
"fmt"
"strings"
"time"
"github.com/documize/community/core/api/entity"
"github.com/documize/community/core/log"
"github.com/documize/community/core/utility"
)
// AddAttachment inserts the given record into the database attachement table.
func (p *Persister) AddAttachment(a entity.Attachment) (err error) {
a.OrgID = p.Context.OrgID
a.Created = time.Now().UTC()
a.Revised = time.Now().UTC()
bits := strings.Split(a.Filename, ".")
a.Extension = bits[len(bits)-1]
stmt, err := p.Context.Transaction.Preparex("INSERT INTO attachment (refid, orgid, documentid, job, fileid, filename, data, extension, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
defer utility.Close(stmt)
if err != nil {
log.Error("Unable to prepare insert for attachment", err)
return
}
_, err = stmt.Exec(a.RefID, a.OrgID, a.DocumentID, a.Job, a.FileID, a.Filename, a.Data, a.Extension, a.Created, a.Revised)
if err != nil {
log.Error("Unable to execute insert for attachment", err)
return
}
return
}
// GetAttachmentByJobAndFileID returns the database attachment record specified by the parameters.
func (p *Persister) GetAttachmentByJobAndFileID(orgID, job, fileID string) (attachment entity.Attachment, err error) {
err = nil
stmt, err := Db.Preparex("SELECT id, refid, orgid, documentid, job, fileid, filename, data, extension, created, revised FROM attachment WHERE orgid=? and job=? and fileid=?")
defer utility.Close(stmt)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare select for attachment %s/%s", job, fileID), err)
return
}
err = stmt.Get(&attachment, orgID, job, fileID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute select for attachment %s/%s", job, fileID), err)
return
}
return
}
// GetAttachments returns a slice containing the attachement records (excluding their data) for document docID, ordered by filename.
func (p *Persister) GetAttachments(docID string) (attachments []entity.Attachment, err error) {
err = nil
err = Db.Select(&attachments, "SELECT id, refid, orgid, documentid, job, fileid, filename, extension, created, revised FROM attachment WHERE orgid=? and documentid=? order by filename", p.Context.OrgID, docID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute select attachments for org %s docID %s", p.Context.OrgID, docID), err)
return
}
return
}
// GetAttachmentsWithData returns a slice containing the attachement records (including their data) for document docID, ordered by filename.
func (p *Persister) GetAttachmentsWithData(docID string) (attachments []entity.Attachment, err error) {
err = nil
err = Db.Select(&attachments, "SELECT id, refid, orgid, documentid, job, fileid, filename, extension, data, created, revised FROM attachment WHERE orgid=? and documentid=? order by filename", p.Context.OrgID, docID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute select attachments for org %s docID %s", p.Context.OrgID, docID), err)
return
}
return
}
// DeleteAttachment deletes the id record from the database attachment table.
func (p *Persister) DeleteAttachment(id string) (rows int64, err error) {
return p.Base.DeleteConstrained(p.Context.Transaction, "attachment", p.Context.OrgID, id)
}

View file

@ -0,0 +1,106 @@
// 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
package request
/* TODO(Elliott)
import (
"testing"
"github.com/documize/community/documize/api/entity"
"github.com/documize/community/core/environment"
)
const testAtt = "TestAttachment"
const testFileID = "testFileID"
func TestAttachment(t *testing.T) {
environment.Parse("db")
p := newTestPersister(t)
defer deleteTestAuditTrail(t, p)
att := entity.Attachment{
BaseEntity: entity.BaseEntity{RefID: testAtt},
OrgID: p.Context.OrgID, // string `json:"orgId"`
DocumentID: testDocID, // string `json:"documentId"`
Job: testJobID, // string `json:"job"`
FileID: testFileID, // string `json:"fileId"`
Filename: "testFilename.test", // string `json:"filename"`
Data: []byte{1, 2, 3}, // `json:"-"`
Extension: "", // NOTE calculated by AddAttachment // string `json:"extension"`
}
err := p.AddAttachment(att)
if err != nil {
t.Error(err)
}
p.testCommit(t)
defer func() {
num, err := p.DeleteAttachment(testAtt)
if err != nil {
t.Error(err)
}
if num != 1 {
t.Error("one record not deleted:", num)
}
p.testCommit(t)
}()
list, err := p.GetAttachments(testDocID)
if err != nil {
t.Error(err)
}
if len(list) != 1 {
t.Errorf("wrong number of attachemnts %d", len(list))
} else {
if list[0].FileID != att.FileID {
t.Errorf("wanted %s got %s", att.FileID, list[0].FileID)
}
}
p.testRollback(t)
list, err = p.GetAttachmentsWithData(testDocID)
if err != nil {
t.Error(err)
}
if len(list) != 1 {
t.Errorf("wrong number of attachemnts %d", len(list))
} else {
if list[0].Data[1] != att.Data[1] {
t.Errorf("wanted %d got %d", att.Data[1], list[0].Data[1])
}
}
p.testRollback(t)
ga, err := p.GetAttachmentByJobAndFileID(p.Context.OrgID, testJobID, testFileID)
if err != nil {
t.Error(err)
}
if ga.FileID != att.FileID {
t.Errorf("wanted %s got %s", att.FileID, ga.FileID)
}
p.testRollback(t)
_, err = p.GetAttachmentByJobAndFileID("X", "Y", "Z")
if err == nil {
t.Error("did not error when it should have")
}
p.testRollback(t)
err = p.AddAttachment(att)
if err == nil {
t.Error("did not error on duplicate attachment")
}
p.testRollback(t)
}
*/

152
core/api/request/config.go Normal file
View file

@ -0,0 +1,152 @@
// 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
package request
import (
"bytes"
"errors"
"github.com/documize/community/core/utility"
)
/* NOT CURRENTLY USED
// FlagFromDB overrides the value in *target if it is set in the database configuration JSON.
// Function signaiture must map that in environment.
func FlagFromDB(target *string, name string) bool {
value := ConfigString(environment.Prefix, name)
//fmt.Println("DEBUG FlagFromDB " + value)
if value != `""` && value != "" {
*target = strings.TrimPrefix(strings.TrimSuffix(value, `"`), `"`)
return true
}
return false
}
*/
// ConfigString fetches a configuration JSON element from the config table.
func ConfigString(area, path string) (ret string) {
if Db == nil {
return ""
}
if path != "" {
path = "." + path
}
sql := "SELECT JSON_EXTRACT(`config`,'$" + path + "') FROM `config` WHERE `key` = '" + area + "';"
stmt, err := Db.Preparex(sql)
if err != nil {
//fmt.Printf("DEBUG: Unable to prepare select SQL for ConfigString: %s -- error: %v\n", sql, err)
return ""
}
defer utility.Close(stmt)
var item = make([]uint8, 0)
err = stmt.Get(&item)
if err != nil {
//fmt.Printf("DEBUG: Unable to prepare execute SQL for ConfigString: %s -- error: %v\n", sql, err)
return ""
}
if len(item) > 1 {
q := []byte(`"`)
ret = string(bytes.TrimPrefix(bytes.TrimSuffix(item, q), q))
}
//fmt.Println("DEBUG ConfigString " + sql + " => " + ret)
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) {
if Db == nil {
return ""
}
if path != "" {
path = "." + path
}
sql := "SELECT JSON_EXTRACT(`config`,'$" + path + "') FROM `userconfig` WHERE `key` = '" + area +
"' AND `orgid` = '" + orgid + "' AND `userid` = '" + userid + "';"
stmt, err := Db.Preparex(sql)
if err != nil {
//fmt.Printf("DEBUG: Unable to prepare select SQL for ConfigString: %s -- error: %v\n", sql, err)
return ""
}
defer utility.Close(stmt)
var item = make([]uint8, 0)
err = stmt.Get(&item)
if err != nil {
//fmt.Printf("DEBUG: Unable to prepare execute SQL for ConfigString: %s -- error: %v\n", sql, err)
return ""
}
if len(item) > 1 {
q := []byte(`"`)
ret = string(bytes.TrimPrefix(bytes.TrimSuffix(item, q), q))
}
//fmt.Println("DEBUG UserConfigString " + sql + " => " + ret)
return ret
}
// UserConfigSetJSON writes a configuration JSON element to the userconfig table for the current user.
func UserConfigSetJSON(orgid, userid, area, json string) error {
if Db == nil {
return errors.New("no database")
}
if area == "" {
return errors.New("no area")
}
sql := "INSERT INTO `userconfig` (`orgid`,`userid`,`key`,`config`) " +
"VALUES ('" + orgid + "','" + userid + "','" + 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 UserConfigSetJSON: %s -- error: %v\n", sql, err)
return err
}
defer utility.Close(stmt)
_, err = stmt.Exec()
return err
}

214
core/api/request/context.go Normal file
View file

@ -0,0 +1,214 @@
// 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
package request
import (
"database/sql"
"fmt"
"net/http"
"time"
"github.com/gorilla/context"
"github.com/jmoiron/sqlx"
"github.com/documize/community/core/log"
)
var rc = Context{}
// Context holds the context in which the client is dealing with Documize.
type Context struct {
AllowAnonymousAccess bool
Authenticated bool
Administrator bool
Guest bool
Editor bool
UserID string
OrgID string
OrgName string
SSL bool
AppURL string // e.g. https://{url}.documize.com
Subdomain string
Expires time.Time
Transaction *sqlx.Tx
}
// NewContext simply returns a blank Context type.
func NewContext() Context {
return Context{}
}
func getContext(r *http.Request) Context {
if value := context.Get(r, rc); value != nil {
return value.(Context)
}
return Context{}
}
// SetContext simply calls the Set method on the passed context, using the empty context stored in rc as an extra parameter.
func SetContext(r *http.Request, c Context) {
c.AppURL = r.Host
c.Subdomain = GetSubdomainFromHost(r)
c.SSL = r.TLS != nil
context.Set(r, rc, c)
}
// Persister stores the Context of the client along with a baseManager instance.
type Persister struct {
Context Context
Base baseManager
}
// GetPersister reurns a Persister which contains a Context which is based on the incoming request.
func GetPersister(r *http.Request) Persister {
var p = Persister{}
p.Context = getContext(r)
p.Context.AppURL = r.Host
p.Context.SSL = r.TLS != nil
return p
}
// CanViewDocumentInFolder returns if the user has permission to view a document within the specified folder.
func (p *Persister) CanViewDocumentInFolder(labelID string) (hasPermission bool) {
roles, err := p.GetUserLabelRoles()
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
log.Error(fmt.Sprintf("Unable to check folder %s for permission check", labelID), err)
return false
}
for _, role := range roles {
if role.LabelID == labelID && (role.CanView || role.CanEdit) {
return true
}
}
return false
}
// CanViewDocument returns if the clinet has permission to view a given document.
func (p *Persister) CanViewDocument(documentID string) (hasPermission bool) {
document, err := p.GetDocument(documentID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
log.Error(fmt.Sprintf("Unable to get document %s for permission check", documentID), err)
return false
}
roles, err := p.GetUserLabelRoles()
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
log.Error(fmt.Sprintf("Unable to get document %s for permission check", documentID), err)
return false
}
for _, role := range roles {
if role.LabelID == document.LabelID && (role.CanView || role.CanEdit) {
return true
}
}
return false
}
// CanChangeDocument returns if the clinet has permission to change a given document.
func (p *Persister) CanChangeDocument(documentID string) (hasPermission bool) {
document, err := p.GetDocument(documentID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
log.Error(fmt.Sprintf("Unable to get document %s for permission check", documentID), err)
return false
}
roles, err := p.GetUserLabelRoles()
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
log.Error(fmt.Sprintf("Unable to get document %s for permission check", documentID), err)
return false
}
for _, role := range roles {
if role.LabelID == document.LabelID && role.CanEdit {
return true
}
}
return false
}
// CanUploadDocument returns if the client has permission to upload documents to the given folderID.
func (p *Persister) CanUploadDocument(folderID string) (hasPermission bool) {
roles, err := p.GetUserLabelRoles()
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
log.Error(fmt.Sprintf("Unable to check permission for folder %s", folderID), err)
return false
}
for _, role := range roles {
if role.LabelID == folderID && role.CanEdit {
return true
}
}
return false
}
// CanViewFolder returns if the user has permission to view the given folderID.
func (p *Persister) CanViewFolder(folderID string) (hasPermission bool) {
roles, err := p.GetUserLabelRoles()
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
log.Error(fmt.Sprintf("Unable to check permission for folder %s", folderID), err)
return false
}
for _, role := range roles {
if role.LabelID == folderID && (role.CanView || role.CanEdit) {
return true
}
}
return false
}

View file

@ -0,0 +1,80 @@
// 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
package request
/* TODO(Elliott)
import (
"github.com/documize/community/core/environment"
"net/http"
"testing"
)
func newTestPersister(t *testing.T) *Persister {
p, err := SetupPersister()
if err != nil {
t.Error(err)
t.Fail()
}
return p
}
func (p *Persister) testNewTx(t *testing.T) {
tx, err := Db.Beginx()
if err != nil {
t.Error(err)
t.Fail()
}
p.Context.Transaction = tx
}
func (p *Persister) testCommit(t *testing.T) {
err := p.Context.Transaction.Commit()
if err != nil {
t.Error(err)
t.Fail()
}
p.testNewTx(t)
}
func (p *Persister) testRollback(t *testing.T) {
err := p.Context.Transaction.Rollback()
if err != nil {
t.Error(err)
t.Fail()
}
p.testNewTx(t)
}
func TestContext(t *testing.T) {
environment.Parse("db")
req, err := http.NewRequest("GET", "http://example.com", nil)
if err != nil {
t.Error(err)
t.Fail()
}
zgp := GetPersister(req)
if zgp.Context.OrgID != "" {
t.Error("wrong data retrieved")
}
ctx := NewContext()
tp := newTestPersister(t)
SetContext(req, tp.Context)
gp := GetPersister(req)
ctx = gp.Context
if ctx.OrgID != tp.Context.OrgID {
t.Error("wrong data retrieved")
}
}
*/

13
core/api/request/doc.go Normal file
View file

@ -0,0 +1,13 @@
// 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
// Package request handles database requests for Documize, mostly from the endpoints package.
package request

View file

@ -0,0 +1,402 @@
// 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
package request
import (
"database/sql"
"fmt"
"regexp"
"strings"
"time"
"github.com/documize/community/core/api/entity"
"github.com/documize/community/core/log"
"github.com/documize/community/core/utility"
)
// AddDocument inserts the given document record into the document table and audits that it has been done.
func (p *Persister) AddDocument(document entity.Document) (err error) {
document.OrgID = p.Context.OrgID
document.Created = time.Now().UTC()
document.Revised = document.Created // put same time in both fields
stmt, err := p.Context.Transaction.Preparex("INSERT INTO document (refId, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
defer utility.Close(stmt)
if err != nil {
log.Error("Unable to prepare insert for document", err)
return
}
_, err = stmt.Exec(document.RefID, document.OrgID, document.LabelID, document.UserID, document.Job, document.Location, document.Title, document.Excerpt, document.Slug, document.Tags, document.Template, document.Created, document.Revised)
if err != nil {
log.Error("Unable to execute insert for document", err)
return
}
p.Base.Audit(p.Context, "add-document", document.RefID, "")
return
}
// GetDocument fetches the document record with the given id fromt the document table and audits that it has been got.
func (p *Persister) GetDocument(id string) (document entity.Document, err error) {
err = nil
stmt, err := Db.Preparex("SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, created, revised FROM document WHERE orgid=? and refid=?")
defer utility.Close(stmt)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare select for document %s", id), err)
return
}
err = stmt.Get(&document, p.Context.OrgID, id)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute select for document %s", id), err)
return
}
p.Base.Audit(p.Context, "get-document", id, "")
return
}
// GetDocumentMeta returns the metadata for a specified document.
func (p *Persister) GetDocumentMeta(id string) (meta entity.DocumentMeta, err error) {
err = nil
sqlViewers := `SELECT CONVERT_TZ(MAX(a.created), @@session.time_zone, '+00:00') as created, a.userid, u.firstname, u.lastname
FROM audit a LEFT JOIN user u ON a.userid=u.refid
WHERE a.orgid=? AND a.documentid=? AND a.userid != '0' AND action='get-document'
GROUP BY a.userid ORDER BY MAX(a.created) DESC`
err = Db.Select(&meta.Viewers, sqlViewers, p.Context.OrgID, id)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute select GetDocumentMeta.viewers %s", id), err)
return
}
//SELECT CONVERT_TZ(a.created, @@session.time_zone, '+00:00') as
sqlEdits := `SELECT CONVERT_TZ(a.created, @@session.time_zone, '+00:00') as created,
a.action, a.userid, u.firstname, u.lastname, a.pageid
FROM audit a LEFT JOIN user u ON a.userid=u.refid
WHERE a.orgid=? AND a.documentid=? AND a.userid != '0'
AND (a.action='update-page' OR a.action='add-page' OR a.action='remove-page')
ORDER BY a.created DESC;`
err = Db.Select(&meta.Editors, sqlEdits, p.Context.OrgID, id)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute select GetDocumentMeta.edits %s", id), err)
return
}
return
}
// GetDocuments returns a slice containg all of the the documents for the client's organisation, with the most recient first.
func (p *Persister) GetDocuments() (documents []entity.Document, err error) {
err = Db.Select(&documents, "SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, created, revised FROM document WHERE orgid=? AND template=0 ORDER BY revised DESC", p.Context.OrgID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute select documents for org %s", p.Context.OrgID), err)
return
}
return
}
// GetDocumentsByFolder returns a slice containing the documents for a given folder, most recient first.
func (p *Persister) GetDocumentsByFolder(folderID string) (documents []entity.Document, err error) {
err = nil
err = Db.Select(&documents, "SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, created, revised FROM document WHERE orgid=? AND template=0 AND labelid=? ORDER BY revised DESC", p.Context.OrgID, folderID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute select documents for org %s", p.Context.OrgID), err)
return
}
return
}
// GetDocumentsByTag returns a slice containing the documents with the specified tag, in title order.
func (p *Persister) GetDocumentsByTag(tag string) (documents []entity.Document, err error) {
tagQuery := "tags LIKE '%#" + tag + "#%'"
err = Db.Select(&documents,
`SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, created, revised FROM document WHERE orgid=? AND template=0 AND `+tagQuery+` AND labelid IN
(SELECT refid from label WHERE orgid=? AND type=2 AND userid=?
UNION ALL SELECT refid FROM label a where orgid=? AND type=1 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid='' AND (canedit=1 OR canview=1))
UNION ALL SELECT refid FROM label a where orgid=? AND type=3 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid=? AND (canedit=1 OR canview=1)))
ORDER BY title`,
p.Context.OrgID,
p.Context.OrgID,
p.Context.UserID,
p.Context.OrgID,
p.Context.OrgID,
p.Context.OrgID,
p.Context.OrgID,
p.Context.UserID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute select document by tag for org %s", p.Context.OrgID), err)
return
}
return
}
// GetDocumentTemplates returns a slice containing the documents available as templates to the client's organisation, in title order.
func (p *Persister) GetDocumentTemplates() (documents []entity.Document, err error) {
err = Db.Select(&documents,
`SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, created, revised FROM document WHERE orgid=? AND template=1 AND labelid IN
(SELECT refid from label WHERE orgid=? AND type=2 AND userid=?
UNION ALL SELECT refid FROM label a where orgid=? AND type=1 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid='' AND (canedit=1 OR canview=1))
UNION ALL SELECT refid FROM label a where orgid=? AND type=3 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid=? AND (canedit=1 OR canview=1)))
ORDER BY title`,
p.Context.OrgID,
p.Context.OrgID,
p.Context.UserID,
p.Context.OrgID,
p.Context.OrgID,
p.Context.OrgID,
p.Context.OrgID,
p.Context.UserID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute select document templates for org %s", p.Context.OrgID), err)
return
}
return
}
// GetPublicDocuments returns a slice of SitemapDocument records, holding documents in folders of type 1 (entity.TemplateTypePublic).
func (p *Persister) GetPublicDocuments(orgID string) (documents []entity.SitemapDocument, err error) {
err = Db.Select(&documents,
`SELECT d.refid as documentid, d.title as document, d.revised as revised, l.refid as folderid, l.label as folder
FROM document d LEFT JOIN label l ON l.refid=d.labelid
WHERE d.orgid=?
AND l.type=1
AND d.template=0`, orgID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute GetPublicDocuments for org %s", orgID), err)
return
}
return
}
// SearchDocument searches the documents that the client is allowed to see, using the keywords search string, then audits that search.
// Visible documents include both those in the client's own organisation and those that are public, or whose visibility includes the client.
func (p *Persister) SearchDocument(keywords string) (results []entity.DocumentSearch, err error) {
if len(keywords) == 0 {
return
}
var tagQuery, keywordQuery string
r, _ := regexp.Compile(`(#[a-z0-9][a-z0-9\-_]*)`)
res := r.FindAllString(keywords, -1)
if len(res) == 0 {
tagQuery = " "
} else {
if len(res) == 1 {
tagQuery = " AND document.tags LIKE '%" + res[0] + "#%' "
} else {
fmt.Println("lots of tags!")
tagQuery = " AND ("
for i := 0; i < len(res); i++ {
tagQuery += "document.tags LIKE '%" + res[i] + "#%'"
if i < len(res)-1 {
tagQuery += " OR "
}
}
tagQuery += ") "
}
keywords = r.ReplaceAllString(keywords, "")
keywords = strings.Replace(keywords, " ", "", -1)
}
keywords = strings.TrimSpace(keywords)
if len(keywords) > 0 {
keywordQuery = "AND MATCH(pagetitle,body) AGAINST('" + keywords + "' in boolean mode)"
}
sql := `SELECT search.id, documentid, pagetitle, document.labelid, document.title as documenttitle, document.tags,
COALESCE(label.label,'Unknown') AS labelname, document.excerpt as documentexcerpt
FROM search, document LEFT JOIN label ON label.orgid=document.orgid AND label.refid = document.labelid
WHERE search.documentid = document.refid AND search.orgid=? AND document.template=0 ` + tagQuery +
`AND document.labelid IN
(SELECT refid from label WHERE orgid=? AND type=2 AND userid=?
UNION ALL SELECT refid FROM label a where orgid=? AND type=1 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid='' AND (canedit=1 OR canview=1))
UNION ALL SELECT refid FROM label a where orgid=? AND type=3 AND refid IN (SELECT labelid from labelrole WHERE orgid=? AND userid=? AND (canedit=1 OR canview=1))) ` + keywordQuery
// AND MATCH(pagetitle,body)
// AGAINST('` + keywords + "' in boolean mode)"
err = Db.Select(&results,
sql,
p.Context.OrgID,
p.Context.OrgID,
p.Context.UserID,
p.Context.OrgID,
p.Context.OrgID,
p.Context.OrgID,
p.Context.OrgID,
p.Context.UserID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute search documents for org %s looking for %s", p.Context.OrgID, keywords), err)
return
}
p.Base.Audit(p.Context, "search", "", "")
return
}
// UpdateDocument changes the given document record to the new values, updates search information and audits the action.
func (p *Persister) UpdateDocument(document entity.Document) (err error) {
document.Revised = time.Now().UTC()
stmt, err := p.Context.Transaction.PrepareNamed("UPDATE document SET labelid=:labelid, userid=:userid, job=:job, location=:location, title=:title, excerpt=:excerpt, slug=:slug, tags=:tags, template=:template, revised=:revised WHERE orgid=:orgid AND refid=:refid")
defer utility.Close(stmt)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare update for document %s", document.RefID), err)
return
}
var res sql.Result
res, err = stmt.Exec(&document)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute update for document %s", document.RefID), err)
return
}
rows, rerr := res.RowsAffected()
if rerr == nil && rows > 1 { // zero rows occur where the update is done with exactly the same data as in the record
re := fmt.Errorf("Update for document %s affected %d rows", document.RefID, rows)
log.Error("", re)
return re
}
err = searches.UpdateDocument(&databaseRequest{OrgID: p.Context.OrgID}, document)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute search update for document %s", document.RefID), err)
return
}
p.Base.Audit(p.Context, "update-document", document.RefID, "")
return
}
// ChangeDocumentLabel assigns the specified folder to the document.
func (p *Persister) ChangeDocumentLabel(document, label string) (err error) {
revised := time.Now().UTC()
stmt, err := p.Context.Transaction.Preparex("UPDATE document SET labelid=?, revised=? WHERE orgid=? AND refid=?")
defer utility.Close(stmt)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare update for document label change %s", document), err)
return
}
var res sql.Result
res, err = stmt.Exec(label, revised, p.Context.OrgID, document)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute update for document label change %s", document), err)
return
}
rows, rerr := res.RowsAffected()
if rerr == nil && rows != 1 {
re := fmt.Errorf("Update for document %s affected %d rows", document, rows)
log.Error("", re)
return re
}
p.Base.Audit(p.Context, "update-document-label", document, "")
return
}
// MoveDocumentLabel changes the label for client's organization's documents which have label "id", to "move".
// Then audits that move.
func (p *Persister) MoveDocumentLabel(id, move string) (err error) {
stmt, err := p.Context.Transaction.Preparex("UPDATE document SET labelid=? WHERE orgid=? AND labelid=?")
defer utility.Close(stmt)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare update for document label move %s", id), err)
return
}
_, err = stmt.Exec(move, p.Context.OrgID, id)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute update for document label move %s", id), err)
return
}
p.Base.Audit(p.Context, "move-document-label", "", "")
return
}
// DeleteDocument delete the document pages in the database, updates the search subsystem, deletes the associated revisions and attachments,
// audits the deletion, then finally deletes the document itself.
func (p *Persister) DeleteDocument(documentID string) (rows int64, err error) {
rows, err = p.Base.DeleteWhere(p.Context.Transaction, fmt.Sprintf("DELETE from page WHERE documentid=\"%s\" AND orgid=\"%s\"", documentID, p.Context.OrgID))
if err != nil {
return
}
err = searches.DeleteDocument(&databaseRequest{OrgID: p.Context.OrgID}, documentID)
if err != nil {
return
}
_ /*revision rows*/, err = p.Base.DeleteWhere(p.Context.Transaction, fmt.Sprintf("DELETE from revision WHERE documentid=\"%s\" AND orgid=\"%s\"", documentID, p.Context.OrgID))
if err != nil {
return
}
_ /*attachment rows*/, err = p.Base.DeleteWhere(p.Context.Transaction, fmt.Sprintf("DELETE from attachment WHERE documentid=\"%s\" AND orgid=\"%s\"", documentID, p.Context.OrgID))
if err != nil {
return
}
p.Base.Audit(p.Context, "delete-document", documentID, "")
return p.Base.DeleteConstrained(p.Context.Transaction, "document", p.Context.OrgID, documentID)
}

View file

@ -0,0 +1,256 @@
// 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
package request
/* TODO(Elliott)
import (
"github.com/documize/community/documize/api/entity"
"github.com/documize/community/core/environment"
"testing"
"time"
)
const testDocID = "testDocID"
const testLabelID = "testLabelID"
const testJobID = "testJobID"
func testAddDocument(t *testing.T, p *Persister) entity.Document {
doc := entity.Document{
BaseEntity: entity.BaseEntity{RefID: testDocID},
OrgID: p.Context.OrgID, // string `json:"orgId"`
LabelID: testLabelID, // string `json:"folderId"`
Job: testJobID, // string `json:"job"`
Location: "testLocation", // string `json:"location"`
Title: "testTitle", // string `json:"name"`
Excerpt: "testExcerpt", // string `json:"excerpt"`
Slug: "testSlig", // string `json:"-"`
Tags: "", // string `json:"-"`
Template: false, // bool `json:"template"`
}
err := p.AddDocument(doc)
if err != nil {
t.Error(err)
t.Fail()
}
p.testCommit(t)
return doc
}
func testDeleteDocument(t *testing.T, p *Persister) {
p.testNewTx(t) // so that we can use it reliably in defer
rows, err := p.DeleteDocument(testDocID)
if err != nil {
t.Error(err)
t.Fail()
}
if rows != 1 {
t.Errorf("expected 1 row deleted got %d", rows)
t.Fail()
}
p.testCommit(t)
}
func TestDocument(t *testing.T) {
environment.Parse("db")
p := newTestPersister(t)
defer deleteTestAuditTrail(t, p)
org := testAddOrganization(t, p)
defer testDeleteOrganization(t, p)
user := testAddUser(t, p)
defer testDeleteUser(t, p)
acc := testAddAccount(t, p)
defer testDeleteAccount(t, p)
doc := testAddDocument(t, p)
defer testDeleteDocument(t, p)
pages := testAddPages(t, p)
defer testDeletePages(t, p, pages)
lab := testAddLabel(t, p)
defer testDeleteLabel(t, p)
labrole := testAddLabelRole(t, p)
defer testDeleteLabelRole(t, p)
// keep vars
_ = org
_ = user
_ = acc
_ = doc
_ = lab
_ = labrole
publ, err := p.GetPublicDocuments(org.RefID)
if err != nil {
t.Error(err)
}
if len(publ) != 1 || publ[0].Document != "testTitle" {
t.Errorf("wrong data found: %#v", publ)
}
p.testRollback(t)
lab.Type = entity.FolderTypePrivate
err = p.UpdateLabel(lab)
if err != nil {
t.Error(err)
}
p.testCommit(t)
for len(searches.queue) > 0 {
time.Sleep(time.Second) // let the search indexing happen
}
dss, err := p.SearchDocument("manifestations")
if err != nil {
t.Error(err)
}
if len(dss) != 1 {
t.Error("wrong number of documents found:", len(dss))
} else {
if dss[0].DocumentID != testDocID {
t.Error("wrong document found:", dss[0])
}
}
p.testRollback(t)
dss, err = p.SearchDocument("XXXXXXXXX")
if err != nil {
t.Error(err)
}
if len(dss) != 0 {
t.Error("wrong number of documents found:", len(dss))
}
p.testRollback(t)
err = p.AddDocument(entity.Document{BaseEntity: entity.BaseEntity{RefID: testDocID}})
if err == nil {
t.Error("add duplicate document did not error")
}
p.testRollback(t)
rows, err := p.DeleteDocument("XXXXXXXXXXXX")
if rows != 0 || err != nil {
t.Error("delete unknown document did not affect 0 rows or had an error ", rows, err)
}
p.testRollback(t)
doc1, err := p.GetDocument(testDocID)
if err != nil {
t.Error(err)
}
if doc.Job != doc1.Job {
t.Error("wrong data found:", doc.Job, doc1.Job)
}
p.testRollback(t)
_, err = p.GetDocument("XXXXXXXXXXXXXXX")
if err == nil {
t.Error("get unknown document did not error")
}
p.testRollback(t)
doc1m, err := p.GetDocumentMeta(testDocID)
if err != nil {
t.Error(err)
}
if len(doc1m.Viewers) != 1 {
t.Errorf("wrong data found, len(viewers) != 1 : %#v", doc1m)
} else {
if doc1m.Viewers[0].UserID != user.RefID {
t.Errorf("wrong data found, userid != `%s`: %#v", user.RefID, doc1m)
}
}
p.testRollback(t)
docs, err := p.GetDocuments()
if err != nil {
t.Error(err)
}
if len(docs) != 1 {
t.Errorf("wrong data found, wrong number of records: %#v", docs)
} else {
if docs[0].Job != doc.Job {
t.Errorf("wrong data found: %#v", docs)
}
}
p.testRollback(t)
docs2, err := p.GetDocumentsByFolder(testLabelID)
if err != nil {
t.Error(err)
}
if len(docs2) != 1 {
t.Errorf("wrong data found, wrong number of records: %#v", docs2)
} else {
if docs2[0].Job != doc.Job {
t.Errorf("wrong data found: %#v", docs2)
}
}
p.testRollback(t)
templ, err := p.GetDocumentTemplates()
if err != nil {
t.Error(err)
}
if len(templ) != 0 {
t.Errorf("wrong data found, should be no templates in test DB for org: %#v", templ)
}
p.testRollback(t)
doc1.Job += "42"
err = p.UpdateDocument(doc1)
if err != nil {
t.Error(err)
}
p.testCommit(t)
doc2, err := p.GetDocument(testDocID)
if err != nil {
t.Error(err)
}
if doc2.Job != doc1.Job {
t.Error("wrong data for job, wanted:", doc1.Job, "got:", doc2.Job)
}
p.testRollback(t)
_, err = p.GetDocument("XXXXXXXXXXXXXXXXXXX")
if err == nil {
t.Error("did not error when getting unknown document")
}
p.testRollback(t)
err = p.ChangeDocumentLabel("XXXXXXXXXXXXXX", "YYYYYYYYYYYYYYYY")
if err == nil {
t.Error("did not error when updating unknown document label")
}
p.testRollback(t)
err = p.ChangeDocumentLabel(testDocID, "Dickens")
if err != nil {
t.Error(err)
}
p.testCommit(t)
err = p.MoveDocumentLabel("Dickens", "Asimov")
if err != nil {
t.Error(err)
}
p.testCommit(t)
doc3, err := p.GetDocument(testDocID)
if err != nil {
t.Error(err)
}
if doc3.LabelID != "Asimov" {
t.Error("wrong data for LabelID:", doc3.LabelID)
}
p.testRollback(t)
err = p.ChangeDocumentLabel(testDocID, testLabelID) // put it back
if err != nil {
t.Error(err)
}
p.testCommit(t)
}
*/

View file

@ -0,0 +1,44 @@
// 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
package request
import (
"net/http"
"strings"
)
// find the subdomain (which is actually the organisation )
func urlSubdomain(url string) string {
url = strings.ToLower(url)
url = strings.Replace(url, "https://", "", 1)
url = strings.Replace(url, "http://", "", 1)
parts := strings.Split(url, ".")
if len(parts) >= 2 {
url = parts[0]
} else {
url = ""
}
return CheckDomain(url)
}
// GetRequestSubdomain extracts subdomain from referring URL.
func GetRequestSubdomain(r *http.Request) string {
return urlSubdomain(r.Referer())
}
// GetSubdomainFromHost extracts the subdomain from the requesting URL.
func GetSubdomainFromHost(r *http.Request) string {
return urlSubdomain(r.Host)
}

View file

@ -0,0 +1,42 @@
// 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
package request
/* TODO(Elliott)
import "testing"
import "net/http"
func TestDomain(t *testing.T) {
ds(t, "doodahday.documize.com", "doodahday", "doodahday")
ds(t, "crud.com", "crud", "crud")
ds(t, "badbadbad", "", "")
}
func ds(t *testing.T, in, out1, out2 string) {
r, e := http.NewRequest("", in, nil)
if e != nil {
t.Fatal(e)
}
r.Host = in
r.Header.Set("Referer", in)
got1 := GetRequestSubdomain(r)
out1 = CheckDomain(out1)
if got1 != out1 {
t.Errorf("GetRequestSubdomain input `%s` got `%s` expected `%s`\n", in, got1, out1)
}
got2 := GetSubdomainFromHost(r)
out2 = CheckDomain(out2)
if got2 != out2 {
t.Errorf("GetSubdomainFromHost input `%s` got `%s` expected `%s`\n", in, got2, out2)
}
}
*/

221
core/api/request/init.go Normal file
View file

@ -0,0 +1,221 @@
// 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
package request
import (
"fmt"
"os"
"strings"
"github.com/jmoiron/sqlx"
"github.com/documize/community/core/database"
"github.com/documize/community/core/web"
"github.com/documize/community/core/environment"
"github.com/documize/community/core/log"
"github.com/documize/community/core/utility"
)
var connectionString string
// Db is the central connection to the database, used by all database requests.
var Db *sqlx.DB
var searches *SearchManager
type databaseRequest struct {
Transaction *sqlx.Tx
OrgID string
}
func (dr *databaseRequest) MakeTx() (err error) {
if dr.Transaction != nil {
return nil
}
dr.Transaction, err = Db.Beginx()
return err
}
func init() {
var err error
environment.GetString(&connectionString, "db", true,
`"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 {
log.Error("Unable to setup database", err)
}
Db.SetMaxIdleConns(30)
Db.SetMaxOpenConns(100)
err = Db.Ping()
if err != nil {
log.Error("Unable to connect to database, connection string should be of the form: '"+
"username:password@tcp(host:3306)/database'", err)
os.Exit(2)
}
// go into setup mode if required
if web.SiteMode != web.SiteModeOffline {
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)
}
} else {
log.Info("database.Check(Db) !OK, going into setup mode")
}
}
return false // value not changed
})
}
var stdParams = map[string]string{
"charset": "utf8",
"parseTime": "True",
}
func stdConn(cs string) string {
queryBits := strings.Split(cs, "?")
ret := queryBits[0] + "?"
retFirst := true
if len(queryBits) == 2 {
paramBits := strings.Split(queryBits[1], "&")
for _, pb := range paramBits {
found := false
if assignBits := strings.Split(pb, "="); len(assignBits) == 2 {
_, found = stdParams[strings.TrimSpace(assignBits[0])]
}
if !found { // if we can't work out what it is, put it through
if retFirst {
retFirst = false
} else {
ret += "&"
}
ret += pb
}
}
}
for k, v := range stdParams {
if retFirst {
retFirst = false
} else {
ret += "&"
}
ret += k + "=" + v
}
return ret
}
type baseManager struct {
}
func (m *baseManager) Delete(tx *sqlx.Tx, table string, id string) (rows int64, err error) {
err = nil
stmt, err := tx.Preparex("DELETE FROM " + table + " WHERE refid=?")
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare delete of row in table %s", table), err)
return
}
defer utility.Close(stmt)
result, err := stmt.Exec(id)
if err != nil {
log.Error(fmt.Sprintf("Unable to delete row in table %s", table), err)
return
}
rows, err = result.RowsAffected()
return
}
func (m *baseManager) DeleteConstrained(tx *sqlx.Tx, table string, orgID, id string) (rows int64, err error) {
stmt, err := tx.Preparex("DELETE FROM " + table + " WHERE orgid=? AND refid=?")
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare constrained delete of row in table %s", table), err)
return
}
defer utility.Close(stmt)
result, err := stmt.Exec(orgID, id)
if err != nil {
log.Error(fmt.Sprintf("Unable to delete row in table %s", table), err)
return
}
rows, err = result.RowsAffected()
return
}
func (m *baseManager) DeleteConstrainedWithID(tx *sqlx.Tx, table string, orgID, id string) (rows int64, err error) {
stmt, err := tx.Preparex("DELETE FROM " + table + " WHERE orgid=? AND id=?")
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare ConstrainedWithID delete of row in table %s", table), err)
return
}
defer utility.Close(stmt)
result, err := stmt.Exec(orgID, id)
if err != nil {
log.Error(fmt.Sprintf("Unable to delete row in table %s", table), err)
return
}
rows, err = result.RowsAffected()
return
}
func (m *baseManager) DeleteWhere(tx *sqlx.Tx, statement string) (rows int64, err error) {
result, err := tx.Exec(statement)
if err != nil {
log.Error(fmt.Sprintf("Unable to delete rows: %s", statement), err)
return
}
rows, err = result.RowsAffected()
return
}
// Audit inserts a record into the audit table.
func (m *baseManager) Audit(c Context, action, document, page string) {
_, err := Db.Exec("INSERT INTO audit (orgid, userid, documentid, pageid, action) VALUES (?,?,?,?,?)", c.OrgID, c.UserID, document, page, action)
if err != nil {
log.Error(fmt.Sprintf("Unable record audit for action %s, user %s, customer %s", action, c.UserID, c.OrgID), err)
}
}
// SQLPrepareError returns a string detailing the location of the error.
func (m *baseManager) SQLPrepareError(method string, id string) string {
return fmt.Sprintf("Unable to prepare SQL for %s, ID %s", method, id)
}
// SQLSelectError returns a string detailing the location of the error.
func (m *baseManager) SQLSelectError(method string, id string) string {
return fmt.Sprintf("Unable to execute SQL for %s, ID %s", method, id)
}

View file

@ -0,0 +1,37 @@
// 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
package request
/* TODO(Elliott)
import (
"fmt"
_ "github.com/go-sql-driver/mysql" // this must be somewhere...
"testing"
)
func deleteTestAuditTrail(t *testing.T, p *Persister) {
c := p.Context
_, err := Db.Exec("DELETE FROM audit WHERE orgid=? AND userid=?", c.OrgID, c.UserID)
if err != nil {
t.Error(fmt.Sprintf("Unable delete audit trail for user %s, customer %s", c.UserID, c.OrgID), err)
}
p.testCommit(t)
}
func TestInit(t *testing.T) {
p := newTestPersister(t)
defer deleteTestAuditTrail(t, p)
_ = p.Base.SQLPrepareError("method", "id") // noting to test, just for coverage stats
_ = p.Base.SQLSelectError("method", "id") // noting to test, just for coverage stats
}
*/

190
core/api/request/label.go Normal file
View file

@ -0,0 +1,190 @@
// 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
package request
import (
"fmt"
"time"
"github.com/documize/community/core/api/entity"
"github.com/documize/community/core/log"
"github.com/documize/community/core/utility"
)
// AddLabel adds new folder into the store.
func (p *Persister) AddLabel(l entity.Label) (err error) {
l.UserID = p.Context.UserID
l.Created = time.Now().UTC()
l.Revised = time.Now().UTC()
stmt, err := p.Context.Transaction.Preparex("INSERT INTO label (refid, label, orgid, userid, type, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?)")
defer utility.Close(stmt)
if err != nil {
log.Error("Unable to prepare insert for label", err)
return
}
_, err = stmt.Exec(l.RefID, l.Name, l.OrgID, l.UserID, l.Type, l.Created, l.Revised)
if err != nil {
log.Error("Unable to execute insert for label", err)
return
}
return
}
// GetLabel returns a folder from the store.
func (p *Persister) GetLabel(id string) (label entity.Label, err error) {
err = nil
stmt, err := Db.Preparex("SELECT id,refid,label as name,orgid,userid,type,created,revised FROM label WHERE orgid=? and refid=?")
defer utility.Close(stmt)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare select for label %s", id), err)
return
}
err = stmt.Get(&label, p.Context.OrgID, id)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute select for label %s", id), err)
return
}
return
}
// GetPublicFolders returns folders that anyone can see.
func (p *Persister) GetPublicFolders(orgID string) (labels []entity.Label, err error) {
err = nil
sql := "SELECT id,refid,label as name,orgid,userid,type,created,revised FROM label a where orgid=? AND type=1"
err = Db.Select(&labels, sql, orgID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute GetPublicFolders for org %s", p.Context.OrgID), err)
return
}
return
}
// GetLabels returns folders that the user can see.
// Also handles which folders can be seen by anonymous users.
func (p *Persister) GetLabels() (labels []entity.Label, err error) {
err = nil
sql := `
(SELECT id,refid,label as name,orgid,userid,type,created,revised from label WHERE orgid=? AND type=2 AND userid=?)
UNION ALL
(SELECT id,refid,label as name,orgid,userid,type,created,revised FROM label a where orgid=? AND type=1 AND refid in
(SELECT labelid from labelrole WHERE orgid=? AND userid='' AND (canedit=1 OR canview=1)))
UNION ALL
(SELECT id,refid,label as name,orgid,userid,type,created,revised FROM label a where orgid=? AND type=3 AND refid in
(SELECT labelid from labelrole WHERE orgid=? AND userid=? AND (canedit=1 OR canview=1)))
ORDER BY name`
err = Db.Select(&labels, sql,
p.Context.OrgID,
p.Context.UserID,
p.Context.OrgID,
p.Context.OrgID,
p.Context.OrgID,
p.Context.OrgID,
p.Context.UserID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute select labels for org %s", p.Context.OrgID), err)
return
}
return
}
// UpdateLabel saves folder changes.
func (p *Persister) UpdateLabel(label entity.Label) (err error) {
err = nil
label.Revised = time.Now().UTC()
stmt, err := p.Context.Transaction.PrepareNamed("UPDATE label SET label=:name, type=:type, userid=:userid, revised=:revised WHERE orgid=:orgid AND refid=:refid")
defer utility.Close(stmt)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare update for label %s", label.RefID), err)
return
}
_, err = stmt.Exec(&label)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute update for label %s", label.RefID), err)
return
}
return
}
// ChangeLabelOwner transfer folder ownership.
func (p *Persister) ChangeLabelOwner(currentOwner, newOwner string) (err error) {
err = nil
stmt, err := p.Context.Transaction.Preparex("UPDATE label SET userid=? WHERE userid=? AND orgid=?")
defer utility.Close(stmt)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare change label owner for %s", currentOwner), err)
return
}
_, err = stmt.Exec(newOwner, currentOwner, p.Context.OrgID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute change label owner for %s", currentOwner), err)
return
}
return
}
// GetFolderVisibility returns the list of people who can see shared folders.
func (p *Persister) GetFolderVisibility() (visibleTo []entity.FolderVisibility, err error) {
err = nil
sql := `
SELECT a.userid,
COALESCE(u.firstname, '') as firstname,
COALESCE(u.lastname, '') as lastname,
COALESCE(u.email, '') as email,
a.labelid,
b.label as name,
b.type
FROM labelrole a
LEFT JOIN label b ON b.refid=a.labelid
LEFT JOIN user u ON u.refid=a.userid
WHERE a.orgid=? AND b.type != 2
GROUP BY a.labelid,a.userid
ORDER BY u.firstname,u.lastname`
err = Db.Select(&visibleTo, sql, p.Context.OrgID)
return
}
// DeleteLabel removes folder from the store.
func (p *Persister) DeleteLabel(labelID string) (rows int64, err error) {
return p.Base.DeleteConstrained(p.Context.Transaction, "label", p.Context.OrgID, labelID)
}

View file

@ -0,0 +1,149 @@
// 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
package request
/* TODO(Elliott)
import (
"testing"
"github.com/documize/community/documize/api/entity"
)
const testName = "testLabelName"
func testAddLabel(t *testing.T, p *Persister) entity.Label {
lab := entity.Label{
BaseEntity: entity.BaseEntity{RefID: testLabelID},
Name: testName, // string `json:"name"`
OrgID: p.Context.OrgID, // string `json:"orgId"`
UserID: p.Context.UserID, // string `json:"userId"`
Type: entity.FolderTypePublic, //FolderType `json:"folderType"`
}
err := p.AddLabel(lab)
if err != nil {
t.Error(err)
}
p.testCommit(t)
return lab
}
func testDeleteLabel(t *testing.T, p *Persister) {
num, err := p.DeleteLabel(testLabelID)
if err != nil {
t.Error(err)
}
if num != 1 {
t.Error("one record not deleted:", num)
}
p.testCommit(t)
}
func TestLabel(t *testing.T) {
p := newTestPersister(t)
defer deleteTestAuditTrail(t, p)
lab := testAddLabel(t, p)
defer testDeleteLabel(t, p)
testAddLabelRole(t, p)
defer testDeleteLabelRole(t, p)
testAddUser(t, p)
defer testDeleteUser(t, p)
vis, err := p.GetFolderVisibility()
if err != nil {
t.Error(err)
}
for _, v := range vis {
if v.LabelID == testLabelID {
goto foundVis
}
}
t.Error("test label not found in GetFolderVisibility()")
foundVis:
p.testRollback(t)
err = p.AddLabel(lab)
if err == nil {
t.Error("did not error on duplicate label")
}
p.testRollback(t)
lab2, err := p.GetLabel(testLabelID)
if err != nil {
t.Error(err)
}
if lab.Name != lab2.Name {
t.Error("wrong data returned")
}
p.testRollback(t)
_, err = p.GetLabel("XXXXXXXXX")
if err == nil {
t.Error("did not error when it should have")
}
p.testRollback(t)
list, err := p.GetPublicFolders(p.Context.OrgID)
if err != nil {
t.Error(err)
}
if len(list) != 1 {
t.Errorf("wrong number of public folders %d", len(list))
} else {
if list[0].Name != lab.Name {
t.Errorf("wanted %s got %s", lab.Name, list[0].Name)
}
}
p.testRollback(t)
lab.Type = entity.FolderTypePrivate
err = p.UpdateLabel(lab)
if err != nil {
t.Error(err)
}
p.testCommit(t)
labels, err := p.GetLabels()
if err != nil {
t.Error(err)
}
for _, l := range labels {
if l.BaseEntity.RefID == testLabelID {
goto foundLabel
}
}
t.Error("test label not found in GetLabels()")
foundLabel:
p.testRollback(t)
gonzo := "Gonzo"
err = p.ChangeLabelOwner(p.Context.UserID, gonzo)
if err != nil {
t.Error(err)
}
p.testCommit(t)
u := p.Context.UserID
p.Context.UserID = gonzo
_, err = p.GetLabel(testLabelID)
if err != nil {
t.Error(err)
}
p.testRollback(t)
err = p.ChangeLabelOwner(gonzo, p.Context.UserID) // change it back for deletion
if err != nil {
t.Error(err)
}
p.testCommit(t)
p.Context.UserID = u // put back the right one, so that we delete correctly on tidy-up
}
*/

View file

@ -0,0 +1,136 @@
// 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
package request
import (
"database/sql"
"fmt"
"time"
"github.com/documize/community/core/api/entity"
"github.com/documize/community/core/log"
"github.com/documize/community/core/utility"
)
// AddLabelRole inserts the given record into the labelrole database table.
func (p *Persister) AddLabelRole(l entity.LabelRole) (err error) {
l.Created = time.Now().UTC()
l.Revised = time.Now().UTC()
stmt, err := p.Context.Transaction.Preparex("INSERT INTO labelrole (refid, labelid, orgid, userid, canview, canedit, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?)")
defer utility.Close(stmt)
if err != nil {
log.Error("Unable to prepare insert for label role", err)
return
}
_, err = stmt.Exec(l.RefID, l.LabelID, l.OrgID, l.UserID, l.CanView, l.CanEdit, l.Created, l.Revised)
if err != nil {
log.Error("Unable to execute insert for label role", err)
return
}
return
}
// GetLabelRoles returns a slice of labelrole records, for the given labelID in the client's organization, grouped by user.
func (p *Persister) GetLabelRoles(labelID string) (roles []entity.LabelRole, err error) {
err = nil
query := `SELECT id, refid, labelid, orgid, userid, canview, canedit, created, revised FROM labelrole WHERE orgid=? AND labelid=?` // was + "GROUP BY userid"
err = Db.Select(&roles, query, p.Context.OrgID, labelID)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare select for label roles %s", labelID), err)
return
}
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
log.Error(fmt.Sprintf("Unable to execute select for label role %s", labelID), err)
return
}
return
}
// GetUserLabelRoles returns a slice of labelrole records, for both the client's user and organization, and
// those label roles that exist for all users in the client's organization.
func (p *Persister) GetUserLabelRoles() (roles []entity.LabelRole, err error) {
err = Db.Select(&roles, `
SELECT id, refid, labelid, orgid, userid, canview, canedit, created, revised FROM labelrole WHERE orgid=? and userid=?
UNION ALL
SELECT id, refid, labelid, orgid, userid, canview, canedit, created, revised FROM labelrole WHERE orgid=? AND userid=''`,
p.Context.OrgID, p.Context.UserID, p.Context.OrgID)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare select for user label roles %s", p.Context.UserID), err)
return
}
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
log.Error(fmt.Sprintf("Unable to execute select for user label roles %s", p.Context.UserID), err)
return
}
return
}
// DeleteLabelRole deletes the labelRoleID record from the labelrole table.
func (p *Persister) DeleteLabelRole(labelRoleID string) (rows int64, err error) {
sql := fmt.Sprintf("DELETE FROM labelrole WHERE orgid='%s' AND refid='%s'", p.Context.OrgID, labelRoleID)
return p.Base.DeleteWhere(p.Context.Transaction, sql)
}
// DeleteLabelRoles deletes records from the labelrole table which have the given labelID.
func (p *Persister) DeleteLabelRoles(labelID string) (rows int64, err error) {
sql := fmt.Sprintf("DELETE FROM labelrole WHERE orgid='%s' AND labelid='%s'", p.Context.OrgID, labelID)
return p.Base.DeleteWhere(p.Context.Transaction, sql)
}
// DeleteUserFolderRoles removes all roles for the specified user, for the specified folder.
func (p *Persister) DeleteUserFolderRoles(labelID, userID string) (rows int64, err error) {
sql := fmt.Sprintf("DELETE FROM labelrole WHERE orgid='%s' AND labelid='%s' AND userid='%s'",
p.Context.OrgID, labelID, userID)
return p.Base.DeleteWhere(p.Context.Transaction, sql)
}
// MoveLabelRoles changes the labelid for an organization's labelrole records from previousLabel to newLabel.
func (p *Persister) MoveLabelRoles(previousLabel, newLabel string) (err error) {
stmt, err := p.Context.Transaction.Preparex("UPDATE labelrole SET labelid=? WHERE labelid=? AND orgid=?")
defer utility.Close(stmt)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare move label roles for label %s", previousLabel), err)
return
}
_, err = stmt.Exec(newLabel, previousLabel, p.Context.OrgID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute move label roles for label %s", previousLabel), err)
}
return
}

View file

@ -0,0 +1,236 @@
// 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
package request
/* TODO(Elliott)
import (
"testing"
"github.com/documize/community/documize/api/entity"
)
const testLabelRoleID = "testLabelRoleID"
func testAddLabelRole(t *testing.T, p *Persister) entity.LabelRole {
labrole := entity.LabelRole{
BaseEntityObfuscated: entity.BaseEntityObfuscated{RefID: testLabelRoleID},
OrgID: p.Context.OrgID, // string `json:"orgId"`
LabelID: testLabelID, // string `json:"userId"`
UserID: p.Context.UserID, // string `json:"userId"`
CanView: true, // bool `json:"canView"`
CanEdit: true, // bool `json:"canEdit"`
}
err := p.AddLabelRole(labrole)
if err != nil {
t.Error(err)
}
p.testCommit(t)
return labrole
}
func testDeleteLabelRole(t *testing.T, p *Persister) {
num, err := p.DeleteLabelRole(testLabelRoleID)
if err != nil {
t.Error(err)
}
if num != 1 {
t.Error("one record not deleted:", num)
}
p.testCommit(t)
}
func TestLabelRole(t *testing.T) {
p := newTestPersister(t)
defer deleteTestAuditTrail(t, p)
labRole := testAddLabelRole(t, p)
defer testDeleteLabelRole(t, p)
testAddDocument(t, p)
defer testDeleteDocument(t, p)
err := p.AddLabelRole(labRole)
if err == nil {
t.Error("did not error on duplicate label")
}
p.testRollback(t)
labs, err := p.GetLabelRoles(testLabelID)
if err != nil {
t.Error(err)
}
if len(labs) != 1 {
t.Errorf("wrong number of labels %d", len(labs))
} else {
if labs[0].LabelID != testLabelID {
t.Errorf("wrong data")
}
}
p.testRollback(t)
labs, err = p.GetLabelRoles("XXXXXXXXX")
if err != nil {
t.Error(err)
}
if len(labs) != 0 {
t.Errorf("wrong number of labels %d", len(labs))
}
p.testRollback(t)
labs, err = p.GetUserLabelRoles()
if err != nil {
t.Error(err)
}
if len(labs) != 1 {
t.Errorf("wrong number of labels %d", len(labs))
} else {
if labs[0].LabelID != testLabelID {
t.Errorf("wrong data")
}
}
p.testRollback(t)
if !p.CanUploadDocument(testLabelID) {
t.Error("unexpected result for can upload document")
}
if !p.CanChangeDocument(testDocID) {
t.Error("unexpected result for can change document")
}
_, err = p.DeleteLabelRoles(testLabelID)
if err != nil {
t.Error(err)
}
p.testCommit(t)
labs, err = p.GetUserLabelRoles()
if err != nil {
t.Error(err)
}
if len(labs) != 0 {
t.Errorf("wrong number of labels %d, record not deleted", len(labs))
}
p.testRollback(t)
testAddLabelRole(t, p) // reset
_, err = p.DeleteUserFolderRoles(testLabelID, p.Context.UserID)
if err != nil {
t.Error(err)
}
p.testCommit(t)
labs, err = p.GetUserLabelRoles()
if err != nil {
t.Error(err)
}
if len(labs) != 0 {
t.Errorf("wrong number of labels %d, record not deleted", len(labs))
}
p.testRollback(t)
testAddLabelRole(t, p) // reset
g := "Gonzo"
err = p.MoveLabelRoles(testLabelID, g)
if err != nil {
t.Error(err)
}
p.testCommit(t)
labs, err = p.GetLabelRoles(g)
if err != nil {
t.Error(err)
}
if len(labs) != 1 {
t.Errorf("wrong number of labels %d", len(labs))
} else {
if labs[0].LabelID != g {
t.Errorf("wrong data")
}
}
p.testRollback(t)
if p.CanUploadDocument(testLabelID) { // Gonzo
t.Error("unexpected result for can upload document")
}
if p.CanChangeDocument(testDocID) { // Gonzo
t.Error("unexpected result for can change document")
}
if p.CanChangeDocument("XXXXXXX") { // unknown docID
t.Error("unexpected result for can change document")
}
/*
lab2, err := p.GetLabelRoles(testLabelID)
if err != nil {
t.Error(err)
}
if lab.Name != lab2.Name {
t.Error("wrong data returned")
}
p.testRollback(t)
_, err = p.GetLabel("XXXXXXXXX")
if err == nil {
t.Error("did not error when it should have")
}
p.testRollback(t)
list, err := p.GetPublicFolders(p.Context.OrgID)
if err != nil {
t.Error(err)
}
if len(list) != 1 {
t.Errorf("wrong number of public folders %d", len(list))
} else {
if list[0].Name != lab.Name {
t.Errorf("wanted %s got %s", lab.Name, list[0].Name)
}
}
p.testRollback(t)
t.Log("TODO p.GetFolderVisibility() requires user and labelrole recorde")
lab.Type = entity.FolderTypePrivate
err = p.UpdateLabel(lab)
if err != nil {
t.Error(err)
}
p.testCommit(t)
labels, err := p.GetLabels()
if err != nil {
t.Error(err)
}
for _, l := range labels {
if l.BaseEntity.RefID == testLabelID {
goto foundLabel
}
}
t.Error("test label not found in GetLabels()")
foundLabel:
p.testRollback(t)
gonzo := "Gonzo"
err = p.ChangeLabelOwner(p.Context.UserID, gonzo)
if err != nil {
t.Error(err)
}
p.testCommit(t)
p.Context.UserID = gonzo
_, err = p.GetLabel(testLabelID)
if err != nil {
t.Error(err)
}
p.testRollback(t)
*/
//}

View file

@ -0,0 +1,193 @@
// 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
package request
import (
"database/sql"
"fmt"
"strings"
"time"
"github.com/documize/community/core/api/entity"
"github.com/documize/community/core/web"
"github.com/documize/community/core/log"
"github.com/documize/community/core/utility"
"github.com/jmoiron/sqlx"
)
// AddOrganization inserts the passed organization record into the organization table.
func (p *Persister) AddOrganization(org entity.Organization) error {
org.Created = time.Now().UTC()
org.Revised = time.Now().UTC()
stmt, err := p.Context.Transaction.Preparex(
"INSERT INTO organization (refid, company, title, message, url, domain, email, allowanonymousaccess, serial, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
defer utility.Close(stmt)
if err != nil {
log.Error("Unable to prepare insert for org", err)
return err
}
res, err := stmt.Exec(org.RefID, org.Company, org.Title, org.Message, strings.ToLower(org.URL), strings.ToLower(org.Domain),
strings.ToLower(org.Email), org.AllowAnonymousAccess, org.Serial, org.Created, org.Revised)
if err != nil {
log.Error("Unable to execute insert for org", err)
return err
}
if num, e := res.RowsAffected(); e == nil {
if num != 1 {
e := fmt.Errorf("expecting to insert one row, but inserted %d", num)
log.Error("Wrong numer of rows inserted for org:", e)
return e
}
}
return nil
}
// GetOrganization returns the Organization reocrod from the organization database table with the given id.
func (p *Persister) GetOrganization(id string) (org entity.Organization, err error) {
err = nil
stmt, err := Db.Preparex("SELECT id, refid, company, title, message, url, domain, email, serial, active, allowanonymousaccess, created, revised FROM organization WHERE refid=?")
defer utility.Close(stmt)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare select for org %s", id), err)
return
}
err = stmt.Get(&org, id)
if err != nil {
log.Error(fmt.Sprintf("Unable to get org %s", id), err)
return
}
return
}
// CheckDomain makes sure there is an organisation with the correct domain
func CheckDomain(domain string) string {
row := Db.QueryRow("SELECT COUNT(*) FROM organization WHERE domain=? AND active=1", domain)
var count int
err := row.Scan(&count)
if err != nil {
return ""
}
if count == 1 {
return domain
}
return ""
}
// GetOrganizationByDomain returns the organization matching a given URL subdomain.
func (p *Persister) GetOrganizationByDomain(subdomain string) (org entity.Organization, err error) {
err = nil
subdomain = strings.ToLower(subdomain)
if web.SiteMode == web.SiteModeNormal { // only return an organization when running normally
var stmt *sqlx.Stmt
stmt, err = Db.Preparex("SELECT id, refid, company, title, message, url, domain, email, serial, active, allowanonymousaccess, created, revised FROM organization WHERE domain=? AND active=1")
defer utility.Close(stmt)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare select for subdomain %s", subdomain), err)
return
}
err = stmt.Get(&org, subdomain)
if err != nil && err != sql.ErrNoRows {
log.Error(fmt.Sprintf("Unable to execute select for subdomain %s", subdomain), err)
return
}
}
return
}
// UpdateOrganization updates the given organization record in the database to the values supplied.
func (p *Persister) UpdateOrganization(org entity.Organization) (err error) {
err = nil
org.Revised = time.Now().UTC()
stmt, err := p.Context.Transaction.PrepareNamed("UPDATE organization SET title=:title, message=:message, email=:email, allowanonymousaccess=:allowanonymousaccess, revised=:revised WHERE refid=:refid")
defer utility.Close(stmt)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare update for org %s", org.RefID), err)
return
}
res, err := stmt.Exec(&org)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute update for org %s", org.RefID), err)
return
}
if num, e := res.RowsAffected(); e == nil {
if num != 1 {
e := fmt.Errorf("expecting to update one row, but updated %d", num)
log.Error("Wrong numer of rows updated for org:", e)
return e
}
}
return
}
// DeleteOrganization deletes the orgID organization from the organization table.
func (p *Persister) DeleteOrganization(orgID string) (rows int64, err error) {
return p.Base.Delete(p.Context.Transaction, "organization", orgID)
}
// RemoveOrganization sets the orgID organization to be inactive, thus executing a "soft delete" operation.
func (p *Persister) RemoveOrganization(orgID string) (err error) {
err = nil
stmt, err := p.Context.Transaction.Preparex("UPDATE organization SET active=0 WHERE refid=?")
defer utility.Close(stmt)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare soft delete for org %s", orgID), err)
return
}
res, err := stmt.Exec(orgID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute soft delete for org %s", orgID), err)
return
}
if num, e := res.RowsAffected(); e == nil {
if num != 1 {
e := fmt.Errorf("expecting to update one row to remove an organization, but updated %d", num)
log.Error("Wrong numer of rows updated for org:", e)
return e
}
}
return
}

View file

@ -0,0 +1,121 @@
// 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
package request
/* TODO(Elliott)
import (
"database/sql"
"reflect"
"testing"
"github.com/documize/community/documize/api/entity"
)
func testAddOrganization(t *testing.T, p *Persister) entity.Organization {
org, err := p.SetupOrganization("testCompany", "testTitle", "testMessage", "testdomain", "mail@request.test.org")
if err != nil {
t.Error(err)
t.Fail()
}
return org
}
func testDeleteOrganization(t *testing.T, p *Persister) {
p.testNewTx(t) // so that we can use it reliably in defer
rows, err := p.DeleteOrganization(p.Context.OrgID)
if err != nil {
t.Error(err)
t.Fail()
}
if rows != 1 {
t.Errorf("expected 1 row deleted got %d", rows)
t.Fail()
}
p.testCommit(t)
}
func TestOrganization(t *testing.T) {
p := newTestPersister(t)
defer deleteTestAuditTrail(t, p)
org := testAddOrganization(t, p)
defer testDeleteOrganization(t, p)
org2, err := p.GetOrganization(org.RefID)
if err != nil {
t.Error(err)
return
}
org.BaseEntity = org2.BaseEntity
if !reflect.DeepEqual(org, org2) {
t.Error("wrong data returned", org, org2)
}
org2.Email += "42"
err = p.UpdateOrganization(org2)
if err != nil {
t.Error(err)
return
}
p.testCommit(t)
org3, err := p.GetOrganizationByDomain(org.Domain)
if err != nil {
t.Error(err)
return
}
if org3.Email != org2.Email {
t.Error("wrong data returned", org3.Email, org2.Email)
}
err = p.RemoveOrganization(org.RefID)
if err != nil {
t.Error(err)
return
}
p.testCommit(t)
_, err = p.GetOrganizationByDomain(org.Domain)
if err != sql.ErrNoRows {
t.Error("should have no rows returned here, error:", err)
return
}
p.testRollback(t)
// now errors
err = p.AddOrganization(org)
if err == nil {
t.Error("no error adding duplicate organization", err)
}
p.testRollback(t)
_, err = p.GetOrganization("XXXXXXXXX")
if err == nil {
t.Error("no error getting non-existent organization", err)
}
p.testRollback(t)
err = p.UpdateOrganization(entity.Organization{BaseEntity: entity.BaseEntity{RefID: "XXXXXXXXX"}})
if err == nil {
t.Error("no error updating non-existent organization", err)
}
p.testRollback(t)
err = p.RemoveOrganization("XXXXXXXXX")
if err == nil {
t.Error("no error removing non-existent organization", err)
}
p.testRollback(t)
}
*/

424
core/api/request/page.go Normal file
View file

@ -0,0 +1,424 @@
// 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
package request
import (
"fmt"
"strings"
"time"
"github.com/jmoiron/sqlx"
"github.com/documize/community/core/api/endpoint/models"
"github.com/documize/community/core/api/entity"
"github.com/documize/community/core/log"
"github.com/documize/community/core/utility"
)
// AddPage inserts the given page into the page table, adds that page to the queue of pages to index and audits that the page has been added.
func (p *Persister) AddPage(model models.PageModel) (err error) {
err = nil
model.Page.OrgID = p.Context.OrgID
model.Page.Created = time.Now().UTC()
model.Page.Revised = time.Now().UTC()
model.Page.UserID = p.Context.UserID
model.Page.SetDefaults()
model.Meta.OrgID = p.Context.OrgID
model.Meta.UserID = p.Context.UserID
model.Meta.DocumentID = model.Page.DocumentID
model.Meta.Created = time.Now().UTC()
model.Meta.Revised = time.Now().UTC()
model.Meta.SetDefaults()
// Get maximum page sequence number and increment
row := Db.QueryRow("SELECT max(sequence) FROM page WHERE orgid=? and documentid=?", p.Context.OrgID, model.Page.DocumentID)
var maxSeq float64
err = row.Scan(&maxSeq)
if err == nil {
model.Page.Sequence = maxSeq * 2
}
stmt, err := p.Context.Transaction.Preparex("INSERT INTO page (refid, orgid, documentid, userid, contenttype, level, title, body, revisions, sequence, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
defer utility.Close(stmt)
if err != nil {
log.Error("Unable to prepare insert for page", err)
return
}
_, err = stmt.Exec(model.Page.RefID, model.Page.OrgID, model.Page.DocumentID, model.Page.UserID, model.Page.ContentType, model.Page.Level, model.Page.Title, model.Page.Body, model.Page.Revisions, model.Page.Sequence, model.Page.Created, model.Page.Revised)
if err != nil {
log.Error("Unable to execute insert for page", err)
return
}
err = searches.Add(&databaseRequest{OrgID: p.Context.OrgID}, model.Page, model.Page.RefID)
stmt2, err := p.Context.Transaction.Preparex("INSERT INTO pagemeta (pageid, orgid, userid, documentid, rawbody, config, externalsource, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)")
defer utility.Close(stmt2)
if err != nil {
log.Error("Unable to prepare insert for page meta", err)
return
}
_, err = stmt2.Exec(model.Meta.PageID, model.Meta.OrgID, model.Meta.UserID, model.Meta.DocumentID, model.Meta.RawBody, model.Meta.Config, model.Meta.ExternalSource, model.Meta.Created, model.Meta.Revised)
if err != nil {
log.Error("Unable to execute insert for page meta", err)
return
}
p.Base.Audit(p.Context, "add-page", model.Page.DocumentID, model.Page.RefID)
return
}
// GetPage returns the pageID page record from the page table.
func (p *Persister) GetPage(pageID string) (page entity.Page, err error) {
err = nil
stmt, err := Db.Preparex("SELECT a.id, a.refid, a.orgid, a.documentid, a.userid, a.contenttype, a.level, a.sequence, a.title, a.body, a.revisions, a.created, a.revised FROM page a WHERE a.orgid=? AND a.refid=?")
defer utility.Close(stmt)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare select for page %s", pageID), err)
return
}
err = stmt.Get(&page, p.Context.OrgID, pageID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute select for page %s", pageID), err)
return
}
return
}
// GetPages returns a slice containing all the page records for a given documentID, in presentation sequence.
func (p *Persister) GetPages(documentID string) (pages []entity.Page, err error) {
err = nil
err = Db.Select(&pages, "SELECT a.id, a.refid, a.orgid, a.documentid, a.userid, a.contenttype, a.level, a.sequence, a.title, a.body, a.revisions, a.created, a.revised FROM page a WHERE a.orgid=? AND a.documentid=? ORDER BY a.sequence", p.Context.OrgID, documentID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute select pages for org %s and document %s", p.Context.OrgID, documentID), err)
return
}
return
}
// GetPagesWhereIn returns a slice, in presentation sequence, containing those page records for a given documentID
// where their refid is in the comma-separated list passed as inPages.
func (p *Persister) GetPagesWhereIn(documentID, inPages string) (pages []entity.Page, err error) {
err = nil
args := []interface{}{p.Context.OrgID, documentID}
tempValues := strings.Split(inPages, ",")
sql := "SELECT a.id, a.refid, a.orgid, a.documentid, a.userid, a.contenttype, a.level, a.sequence, a.title, a.body, a.revisions, a.created, a.revised FROM page a WHERE a.orgid=? AND a.documentid=? AND a.refid IN (?" + strings.Repeat(",?", len(tempValues)-1) + ") ORDER BY sequence"
inValues := make([]interface{}, len(tempValues))
for i, v := range tempValues {
inValues[i] = interface{}(v)
}
args = append(args, inValues...)
stmt, err := Db.Preparex(sql)
defer utility.Close(stmt)
if err != nil {
log.Error(fmt.Sprintf("Failed to prepare select pages for org %s and document %s where in %s", p.Context.OrgID, documentID, inPages), err)
return
}
rows, err := stmt.Queryx(args...)
if err != nil {
log.Error(fmt.Sprintf("Failed to execute select pages for org %s and document %s where in %s", p.Context.OrgID, documentID, inPages), err)
return
}
defer utility.Close(rows)
for rows.Next() {
page := entity.Page{}
err = rows.StructScan(&page)
if err != nil {
log.Error(fmt.Sprintf("Failed to scan row: select pages for org %s and document %s where in %s", p.Context.OrgID, documentID, inPages), err)
return
}
pages = append(pages, page)
}
if err != nil {
log.Error(fmt.Sprintf("Failed to execute select pages for org %s and document %s where in %s", p.Context.OrgID, documentID, inPages), err)
return
}
return
}
// GetPagesWithoutContent returns a slice containing all the page records for a given documentID, in presentation sequence,
// but without the body field (which holds the HTML content).
func (p *Persister) GetPagesWithoutContent(documentID string) (pages []entity.Page, err error) {
err = Db.Select(&pages, "SELECT id, refid, orgid, documentid, userid, contenttype, sequence, level, title, revisions, created, revised FROM page WHERE orgid=? AND documentid=? ORDER BY sequence", p.Context.OrgID, documentID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute select pages for org %s and document %s", p.Context.OrgID, documentID), err)
return
}
return
}
// UpdatePage saves changes to the database and handles recording of revisions.
// Not all updates result in a revision being recorded hence the parameter.
func (p *Persister) UpdatePage(page entity.Page, refID, userID string, skipRevision bool) (err error) {
err = nil
page.Revised = time.Now().UTC()
// Store revision history
if !skipRevision {
var stmt *sqlx.Stmt
stmt, err = p.Context.Transaction.Preparex("INSERT INTO revision (refid, orgid, documentid, ownerid, pageid, userid, contenttype, title, body, rawbody, config, created, revised) SELECT ? as refid, a.orgid, a.documentid, a.userid as ownerid, a.refid as pageid, ? as userid, a.contenttype, a.title, a.body, b.rawbody, b.config, ? as created, ? as revised FROM page a, pagemeta b WHERE a.refid=? AND a.refid=b.pageid")
defer utility.Close(stmt)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare insert for page revision %s", page.RefID), err)
return err
}
_, err = stmt.Exec(refID, userID, time.Now().UTC(), time.Now().UTC(), page.RefID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute insert for page revision %s", page.RefID), err)
return err
}
}
// Update page
var stmt2 *sqlx.NamedStmt
stmt2, err = p.Context.Transaction.PrepareNamed("UPDATE page SET documentid=:documentid, level=:level, title=:title, body=:body, revisions=:revisions, sequence=:sequence, revised=:revised WHERE orgid=:orgid AND refid=:refid")
defer utility.Close(stmt2)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare update for page %s", page.RefID), err)
return
}
_, err = stmt2.Exec(&page)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute update for page %s", page.RefID), err)
return
}
err = searches.Update(&databaseRequest{OrgID: p.Context.OrgID}, page)
if err != nil {
log.Error("Unable to update for searching", err)
return
}
// Update revisions counter
if !skipRevision {
stmt3, err := p.Context.Transaction.Preparex("UPDATE page SET revisions=revisions+1 WHERE orgid=? AND refid=?")
defer utility.Close(stmt3)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare revisions counter update for page %s", page.RefID), err)
return err
}
_, err = stmt3.Exec(p.Context.OrgID, page.RefID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute revisions counter update for page %s", page.RefID), err)
return err
}
}
//if page.Level == 1 { // may need to update the document name
//var doc entity.Document
//stmt4, err := p.Context.Transaction.Preparex("SELECT id, refid, orgid, labelid, job, location, title, excerpt, slug, tags, template, created, revised FROM document WHERE refid=?")
//defer utility.Close(stmt4)
//if err != nil {
//log.Error(fmt.Sprintf("Unable to prepare pagemanager doc query for Id %s", page.DocumentID), err)
//return err
//}
//err = stmt4.Get(&doc, page.DocumentID)
//if err != nil {
//log.Error(fmt.Sprintf("Unable to execute pagemanager document query for Id %s", page.DocumentID), err)
//return err
//}
//if doc.Title != page.Title {
//doc.Title = page.Title
//doc.Revised = page.Revised
//err = p.UpdateDocument(doc)
//if err != nil {
//log.Error(fmt.Sprintf("Unable to update document when page 1 altered DocumentId %s", page.DocumentID), err)
//return err
//}
//}
//}
p.Base.Audit(p.Context, "update-page", page.DocumentID, page.RefID)
return
}
// UpdatePageMeta persists meta information associated with a document page.
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
}
var stmt *sqlx.NamedStmt
stmt, err = p.Context.Transaction.PrepareNamed("UPDATE pagemeta SET userid=:userid, documentid=:documentid, rawbody=:rawbody, config=:config, externalsource=:externalsource, revised=:revised WHERE orgid=:orgid AND pageid=:pageid")
defer utility.Close(stmt)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare update for page meta %s", meta.PageID), err)
return
}
_, err = stmt.Exec(&meta)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute update for page meta %s", meta.PageID), err)
return
}
return
}
// UpdatePageSequence changes the presentation sequence of the pageID page in the document.
// It then propagates that change into the search table and audits that it has occurred.
func (p *Persister) UpdatePageSequence(documentID, pageID string, sequence float64) (err error) {
stmt, err := p.Context.Transaction.Preparex("UPDATE page SET sequence=? WHERE orgid=? AND refid=?")
defer utility.Close(stmt)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare update for page %s", pageID), err)
return
}
_, err = stmt.Exec(sequence, p.Context.OrgID, pageID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute update for page %s", pageID), err)
return
}
err = searches.UpdateSequence(&databaseRequest{OrgID: p.Context.OrgID}, documentID, pageID, sequence)
p.Base.Audit(p.Context, "re-sequence-page", "", pageID)
return
}
// UpdatePageLevel changes the heading level of the pageID page in the document.
// It then propagates that change into the search table and audits that it has occurred.
func (p *Persister) UpdatePageLevel(documentID, pageID string, level int) (err error) {
stmt, err := p.Context.Transaction.Preparex("UPDATE page SET level=? WHERE orgid=? AND refid=?")
defer utility.Close(stmt)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare update for page %s", pageID), err)
return
}
_, err = stmt.Exec(level, p.Context.OrgID, pageID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute update for page %s", pageID), err)
return
}
err = searches.UpdateLevel(&databaseRequest{OrgID: p.Context.OrgID}, documentID, pageID, level)
p.Base.Audit(p.Context, "re-level-page", "", pageID)
return
}
// DeletePage deletes the pageID page in the document.
// It then propagates that change into the search table, adds a delete the page revisions history, and audits that the page has been removed.
func (p *Persister) DeletePage(documentID, pageID string) (rows int64, err error) {
rows, err = p.Base.DeleteConstrained(p.Context.Transaction, "page", p.Context.OrgID, pageID)
if err == nil {
_, err = p.Base.DeleteWhere(p.Context.Transaction, fmt.Sprintf("DELETE FROM pagemeta WHERE orgid='%s' AND pageid='%s'", p.Context.OrgID, pageID))
_, err = searches.Delete(&databaseRequest{OrgID: p.Context.OrgID}, documentID, pageID)
p.Base.Audit(p.Context, "remove-page", documentID, pageID)
}
return
}
// GetPageMeta returns the meta information associated with the page.
func (p *Persister) GetPageMeta(pageID string) (meta entity.PageMeta, err error) {
err = nil
stmt, err := Db.Preparex("SELECT id, pageid, orgid, userid, documentid, rawbody, coalesce(config,JSON_UNQUOTE('{}')) as config, externalsource, created, revised FROM pagemeta WHERE orgid=? AND pageid=?")
defer utility.Close(stmt)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare select for pagemeta %s", pageID), err)
return
}
err = stmt.Get(&meta, p.Context.OrgID, pageID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute select for pagemeta %s", pageID), err)
return
}
return
}
// GetDocumentPageMeta returns the meta information associated with a document.
func (p *Persister) GetDocumentPageMeta(documentID string, externalSourceOnly bool) (meta []entity.PageMeta, err error) {
err = nil
filter := ""
if externalSourceOnly {
filter = " AND externalsource=1"
}
err = Db.Select(&meta, "SELECT id, pageid, orgid, userid, documentid, rawbody, coalesce(config,JSON_UNQUOTE('{}')) as config, externalsource, created, revised FROM pagemeta WHERE orgid=? AND documentid=?"+filter, p.Context.OrgID, documentID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute select document page meta for org %s and document %s", p.Context.OrgID, documentID), err)
return
}
return
}

View file

@ -0,0 +1,278 @@
// 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
package request
/* TODO(Elliott)
import (
"strings"
"testing"
"github.com/documize/community/documize/api/endpoint/models"
"github.com/documize/community/documize/api/entity"
)
func testAddPages(t *testing.T, p *Persister) []entity.Page {
testPages := []entity.Page{
{
BaseEntity: entity.BaseEntity{RefID: "testPage1"},
OrgID: p.Context.OrgID, // string `json:"orgId"`
DocumentID: testDocID, // string `json:"documentId"`
Level: 1, // uint64 `json:"level"`
Title: "Document title", // string `json:"title"`
Body: "The quick brown fox jumps over the lazy dog", // string `json:"body"`
Sequence: 1.0, // float64 `json:"sequence"`
Revisions: 0, // uint64 `json:"revisions"`
},
{
BaseEntity: entity.BaseEntity{RefID: "testPage2"},
OrgID: p.Context.OrgID, // string `json:"orgId"`
DocumentID: testDocID, // string `json:"documentId"`
Level: 2, // uint64 `json:"level"`
Title: "Document sub-title one", // string `json:"title"`
Body: `
The Tao that can be spoken is not the eternal Tao
The name that can be named is not the eternal name
The nameless is the origin of Heaven and Earth
The named is the mother of myriad things
Thus, constantly without desire, one observes its essence
Constantly with desire, one observes its manifestations
These two emerge together but differ in name
The unity is said to be the mystery
Mystery of mysteries, the door to all wonders
`, // string `json:"body"`
Sequence: 2.0, // float64 `json:"sequence"`
Revisions: 0, // uint64 `json:"revisions"`
},
{
BaseEntity: entity.BaseEntity{RefID: "testPage3"},
OrgID: p.Context.OrgID, // string `json:"orgId"`
DocumentID: testDocID, // string `json:"documentId"`
Level: 2, // uint64 `json:"level"`
Title: "Document sub-title two", // string `json:"title"`
Body: `
Bent double, like old beggars under sacks,
Knock-kneed, coughing like hags, we cursed through sludge,
Till on the haunting flares we turned our backs,
And towards our distant rest began to trudge.
Men marched asleep. Many had lost their boots,
But limped on, blood-shod. All went lame; all blind;
Drunk with fatigue; deaf even to the hoots
Of gas-shells dropping softly behind.
Gas! GAS! Quick, boys!An ecstasy of fumbling
Fitting the clumsy helmets just in time,
But someone still was yelling out and stumbling
And floundring like a man in fire or lime.
Dim through the misty panes and thick green light,
As under a green sea, I saw him drowning.
In all my dreams before my helpless sight,
He plunges at me, guttering, choking, drowning.
If in some smothering dreams, you too could pace
Behind the wagon that we flung him in,
And watch the white eyes writhing in his face,
His hanging face, like a devils sick of sin;
If you could hear, at every jolt, the blood
Come gargling from the froth-corrupted lungs,
Obscene as cancer, bitter as the cud
Of vile, incurable sores on innocent tongues,
My friend, you would not tell with such high zest
To children ardent for some desperate glory,
The old Lie: Dulce et decorum est
Pro patria mori.
`, // string `json:"body"`
Sequence: 3.0, // float64 `json:"sequence"`
Revisions: 0, // uint64 `json:"revisions"`
},
}
for _, page := range testPages {
err := p.AddPage(models.PageModel{Page: page})
if err != nil {
t.Error(err)
t.Fail()
}
p.testCommit(t)
}
return testPages
}
func testDeletePages(t *testing.T, p *Persister, pages []entity.Page) {
p.testNewTx(t) // so that we can use it reliably in defer
for _, pg := range pages {
_, err := p.DeletePage(testDocID, pg.RefID)
if err != nil {
t.Error(err)
//t.Fail()
}
// this code is belt-and-braces, as document delete should also delete any pages
//if rows != 1 {
// t.Errorf("expected 1 page row deleted got %d", rows)
// //t.Fail()
//}
p.testCommit(t)
}
}
func TestPage(t *testing.T) {
p := newTestPersister(t)
defer deleteTestAuditTrail(t, p)
org := testAddOrganization(t, p)
defer testDeleteOrganization(t, p)
user := testAddUser(t, p)
defer testDeleteUser(t, p)
acc := testAddAccount(t, p)
defer testDeleteAccount(t, p)
doc := testAddDocument(t, p)
defer testDeleteDocument(t, p)
pages := testAddPages(t, p)
defer testDeletePages(t, p, pages)
// keep vars
_ = org
_ = user
_ = acc
_ = doc
err := p.AddPage(models.PageModel{Page: pages[0]})
if err == nil {
t.Error("did not error on add of duplicate record")
}
p.testRollback(t)
retpgs, err := p.GetPages(doc.RefID) // a bad ID just brings back 0 pages, so not tested
if err != nil {
t.Error(err)
}
if len(retpgs) != len(pages) {
t.Errorf("wrong number of pages returned, expected %d got %d", len(pages), len(retpgs))
} else {
for l := range retpgs {
if retpgs[l].Body != pages[l].Body {
t.Errorf("wrong body content")
}
}
}
p.testRollback(t)
retpgswoc, err := p.GetPagesWithoutContent(doc.RefID) // a bad ID just brings back 0 pages, so not tested
if err != nil {
t.Error(err)
}
if len(retpgswoc) != len(pages) {
t.Errorf("wrong number of pages returned, expected %d got %d", len(pages), len(retpgswoc))
} else {
for l := range retpgswoc {
if retpgswoc[l].Title != pages[l].Title {
t.Errorf("wrong title content")
}
}
}
p.testRollback(t)
retpgswi, err := p.GetPagesWhereIn(doc.RefID, pages[0].BaseEntity.RefID+","+pages[2].BaseEntity.RefID)
if err != nil {
t.Error(err)
}
if len(retpgswi) != 2 {
t.Errorf("wrong number of pages returned, expected %d got %d", 2, len(retpgswi))
} else {
if retpgswi[1].Body != pages[2].Body {
t.Errorf("wrong WhereIn content")
}
}
p.testRollback(t)
retpg, err := p.GetPage(pages[0].BaseEntity.RefID)
if err != nil {
t.Error(err)
}
if retpg.Body != pages[0].Body {
t.Errorf("wrong page returned, expected body of `%s` got `%s`", pages[0].Body, retpg.Body)
}
p.testRollback(t)
_, err = p.GetPage("XXXXXXXXXXX")
if err == nil {
t.Error("no error on unknown page")
}
p.testRollback(t)
meaningOfLife := 42.0
err = p.UpdatePageSequence(doc.RefID, "testPage3", meaningOfLife)
if err != nil {
t.Error(err)
}
p.testCommit(t)
retpg, err = p.GetPage("testPage3")
if err != nil {
t.Error(err)
}
if retpg.Sequence != meaningOfLife {
t.Errorf("wrong page returned, expected sequence of `%g` got `%g`", meaningOfLife, retpg.Sequence)
}
p.testRollback(t)
err = p.UpdatePageLevel(doc.RefID, "testPage3", 3)
if err != nil {
t.Error(err)
}
p.testCommit(t)
retpg, err = p.GetPage("testPage3")
if err != nil {
t.Error(err)
}
if retpg.Level != 3 {
t.Errorf("wrong page returned, expected level of `3` got `%d`", retpg.Level)
}
p.testRollback(t)
newPg := pages[0]
newPg.Body += "!"
err = p.UpdatePage(newPg, pages[0].BaseEntity.RefID, p.Context.UserID, false)
if err != nil {
t.Error(err)
}
p.testCommit(t)
retpg, err = p.GetPage(pages[0].BaseEntity.RefID)
if err != nil {
t.Error(err)
}
if retpg.Body[len(retpg.Body)-1] != byte('!') {
t.Errorf("wrong page returned, expected string ending in '!' got `%s`", retpg.Body)
}
p.testRollback(t)
revs, err := p.GetPageRevisions(pages[0].BaseEntity.RefID)
if err != nil {
t.Error(err)
}
if len(revs) != 1 {
t.Error("wrong number of page revisions")
t.Fail()
}
if revs[0].Body != strings.TrimSuffix(pages[0].Body, "!") {
t.Error("wrong revision data:", revs[0].Body)
}
p.testRollback(t)
rev, err := p.GetPageRevision(revs[0].BaseEntity.RefID)
if err != nil {
t.Error(err)
}
if revs[0].Body != rev.Body {
t.Error("wrong revision data:", revs[0].Body, rev.Body)
}
p.testRollback(t)
}
*/

468
core/api/request/search.go Normal file
View file

@ -0,0 +1,468 @@
// 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
package request
import (
"errors"
"fmt"
"strings"
"sync"
"time"
_ "github.com/go-sql-driver/mysql" // required for sqlx but not directly called
"github.com/jmoiron/sqlx"
"github.com/documize/community/core/api/entity"
"github.com/documize/community/core/log"
"github.com/documize/community/core/utility"
)
// SearchManager type provides the datastructure for the queues of activity to be serialized through a single background goroutine.
// NOTE if the queue becomes full, the system will trigger the rebuilding entire files in order to clear the backlog.
type SearchManager struct {
queue chan queueEntry
rebuild map[string]bool
rebuildLock sync.RWMutex
givenWarning bool
}
const searchQueueLength = 2048 // NOTE the largest 15Mb docx in the test set generates 2142 queue entries, but the queue is constantly emptied
type queueEntry struct {
action func(*databaseRequest, entity.Page) error
isRebuild bool
entity.Page
}
func init() {
searches = &SearchManager{}
searches.queue = make(chan queueEntry, searchQueueLength) // provide some decoupling
searches.rebuild = make(map[string]bool)
go searches.searchProcessQueue()
}
// searchProcessQueue is run as a goroutine, it processes the queue of search index update requests.
func (m *SearchManager) searchProcessQueue() {
for {
//fmt.Println("DEBUG queue length=", len(Searches.queue))
if len(m.queue) <= searchQueueLength/20 { // on a busy server, the queue may never get to zero - so use 5%
m.rebuildLock.Lock()
for docid := range m.rebuild {
m.queue <- queueEntry{
action: searchRebuild,
isRebuild: true,
Page: entity.Page{DocumentID: docid},
}
delete(m.rebuild, docid)
}
m.rebuildLock.Unlock()
}
qe := <-m.queue
doit := true
if len(qe.DocumentID) > 0 {
m.rebuildLock.RLock()
if m.rebuild[qe.DocumentID] {
doit = false // don't execute an action on a document queued to be rebuilt
}
m.rebuildLock.RUnlock()
}
if doit {
tx, err := Db.Beginx()
if err != nil {
log.Error("Search Queue Beginx()", err)
} else {
dbRequest := &databaseRequest{Transaction: tx, OrgID: qe.Page.OrgID}
err = qe.action(dbRequest, qe.Page)
if err != nil {
log.Error("Search Queue action()", err)
log.IfErr(tx.Rollback())
// This action has failed, so re-build indexes for the entire document,
// provided it was not a re-build command that failed and we know the documentId.
if !qe.isRebuild && len(qe.DocumentID) > 0 {
m.rebuildLock.Lock()
m.rebuild[qe.DocumentID] = true
m.rebuildLock.Unlock()
}
} else {
log.IfErr(tx.Commit())
}
}
}
}
}
func (m *SearchManager) addQueue(request *databaseRequest, qe queueEntry) error {
lsq := len(m.queue)
if lsq >= (searchQueueLength - 1) {
if qe.DocumentID != "" {
m.rebuildLock.Lock()
if !m.rebuild[qe.DocumentID] {
log.Info(fmt.Sprintf("WARNING: Search Queue Has No Space! Marked rebuild index for document id %s", qe.DocumentID))
}
m.rebuild[qe.DocumentID] = true
m.rebuildLock.Unlock()
} else {
log.Error("addQueue", errors.New("WARNING: Search Queue Has No Space! But unable to index unknown document id"))
}
return nil
}
if lsq > ((8 * searchQueueLength) / 10) {
if !m.givenWarning {
log.Info(fmt.Sprintf("WARNING: Searches.queue length %d exceeds 80%% of capacity", lsq))
m.givenWarning = true
}
} else {
if m.givenWarning {
log.Info(fmt.Sprintf("INFO: Searches.queue length %d now below 80%% of capacity", lsq))
m.givenWarning = false
}
}
m.queue <- qe
return nil
}
// Add should be called when a new page is added to a document.
func (m *SearchManager) Add(request *databaseRequest, page entity.Page, id string) (err error) {
page.RefID = id
err = m.addQueue(request, queueEntry{
action: searchAdd,
Page: page,
})
return
}
func searchAdd(request *databaseRequest, page entity.Page) (err error) {
id := page.RefID
// translate the html into text for the search
nonHTML, err := utility.HTML(page.Body).Text(false)
if err != nil {
log.Error("Unable to decode the html for searching", err)
return
}
// insert into the search table, getting the document title along the way
var stmt *sqlx.Stmt
stmt, err = request.Transaction.Preparex(
"INSERT INTO search (id, orgid, documentid, level, sequence, documenttitle, slug, pagetitle, body, created, revised) " +
" SELECT page.refid,page.orgid,document.refid,page.level,page.sequence,document.title,document.slug,page.title,?,page.created,page.revised " +
" FROM document,page WHERE page.refid=? AND document.refid=page.documentid")
if err != nil {
log.Error("Unable to prepare insert for search", err)
return
}
defer utility.Close(stmt)
_, err = stmt.Exec(nonHTML, id)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute insert for search"), err)
return
}
return
}
// Update should be called after a page record has been updated.
func (m *SearchManager) Update(request *databaseRequest, page entity.Page) (err error) {
err = m.addQueue(request, queueEntry{
action: searchUpdate,
Page: page,
})
return
}
func searchUpdate(request *databaseRequest, page entity.Page) (err error) {
// translate the html into text for the search
nonHTML, err := utility.HTML(page.Body).Text(false)
if err != nil {
log.Error("Unable to decode the html for searching", err)
return
}
su, err := request.Transaction.Preparex(
"UPDATE search SET pagetitle=?,body=?,sequence=?,level=?,revised=? WHERE id=?")
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare search update for page %s", page.RefID), err)
return err // could have been redefined
}
defer utility.Close(su)
_, err = su.Exec(page.Title, nonHTML, page.Sequence, page.Level, page.Revised, page.RefID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute search update for page %s", page.RefID), err)
return
}
return
}
// UpdateDocument should be called after a document record has been updated.
func (m *SearchManager) UpdateDocument(request *databaseRequest, document entity.Document) (err error) {
err = m.addQueue(request, queueEntry{
action: searchUpdateDocument,
Page: entity.Page{
DocumentID: document.RefID,
Title: document.Title,
Body: document.Slug, // NOTE body==slug in this context
},
})
return
}
func searchUpdateDocument(request *databaseRequest, page entity.Page) (err error) {
searchstmt, err := request.Transaction.Preparex(
"UPDATE search SET documenttitle=?, slug=?, revised=? WHERE documentid=?")
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare search update for document %s", page.DocumentID), err)
return err // may have been redefined
}
defer utility.Close(searchstmt)
_, err = searchstmt.Exec(page.Title, page.Body, time.Now().UTC(), page.DocumentID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute search update for document %s", page.DocumentID), err)
return err
}
return nil
}
// DeleteDocument should be called after a document record has been deleted.
func (m *SearchManager) DeleteDocument(request *databaseRequest, documentID string) (err error) {
if len(documentID) > 0 {
m.queue <- queueEntry{
action: searchDeleteDocument,
Page: entity.Page{DocumentID: documentID},
}
}
return
}
func searchDeleteDocument(request *databaseRequest, page entity.Page) (err error) {
var bm = baseManager{}
_, err = bm.DeleteWhere(request.Transaction,
fmt.Sprintf("DELETE from search WHERE documentid='%s'", page.DocumentID))
if err != nil {
log.Error(fmt.Sprintf("Unable to delete search entries for docId %s", page.DocumentID), err)
}
return
}
func searchRebuild(request *databaseRequest, page entity.Page) (err error) {
log.Info(fmt.Sprintf("SearchRebuild begin for docId %s", page.DocumentID))
start := time.Now()
var bm = baseManager{}
_, err = bm.DeleteWhere(request.Transaction, fmt.Sprintf("DELETE from search WHERE documentid='%s'", page.DocumentID))
if err != nil {
log.Error(fmt.Sprintf("Unable to delete search entries for docId %s prior to rebuild",
page.DocumentID), err)
return err
}
var pages []struct{ ID string }
stmt2, err := request.Transaction.Preparex("SELECT refid as id FROM page WHERE documentid=? ")
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare searchRebuild select for docId %s", page.DocumentID), err)
return err
}
defer utility.Close(stmt2)
err = stmt2.Select(&pages, page.DocumentID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute searchRebuild select for docId %s", page.DocumentID), err)
return err
}
if len(pages) > 0 {
for _, pg := range pages {
err = searchAdd(request, entity.Page{BaseEntity: entity.BaseEntity{RefID: pg.ID}})
if err != nil {
log.Error(fmt.Sprintf("Unable to execute searchAdd from searchRebuild for docId %s pageID %s",
page.DocumentID, pg.ID), err)
return err
}
}
// rebuild doc-level tags & excerpts
// get the 0'th page data and rewrite it
target := entity.Page{}
stmt1, err := request.Transaction.Preparex("SELECT * FROM page WHERE refid=?")
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare select from searchRebuild for pageId %s", pages[0].ID), err)
return err
}
defer utility.Close(stmt1)
err = stmt1.Get(&target, pages[0].ID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute select from searchRebuild for pageId %s", pages[0].ID), err)
return err
}
err = searchUpdate(request, target) // to rebuild the document-level tags + excerpt
if err != nil {
log.Error(fmt.Sprintf("Unable to run searchUpdate in searchRebuild for docId %s", target.DocumentID), err)
return err
}
}
log.Info(fmt.Sprintf("Time to rebuild all search data for documentId %s = %v", page.DocumentID,
time.Since(start)))
return
}
// UpdateSequence should be called after a page record has been resequenced.
func (m *SearchManager) UpdateSequence(request *databaseRequest, documentID, pageID string, sequence float64) (err error) {
err = m.addQueue(request, queueEntry{
action: searchUpdateSequence,
Page: entity.Page{
BaseEntity: entity.BaseEntity{RefID: pageID},
Sequence: sequence,
DocumentID: documentID,
},
})
return
}
func searchUpdateSequence(request *databaseRequest, page entity.Page) (err error) {
supdate, err := request.Transaction.Preparex(
"UPDATE search SET sequence=?,revised=? WHERE id=?")
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare search sequence update for page %s", page.RefID), err)
return err
}
defer utility.Close(supdate)
_, err = supdate.Exec(page.Sequence, time.Now().UTC(), page.RefID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute search sequence update for page %s", page.RefID), err)
return
}
return
}
// UpdateLevel should be called after the level of a page has been changed.
func (m *SearchManager) UpdateLevel(request *databaseRequest, documentID, pageID string, level int) (err error) {
err = m.addQueue(request, queueEntry{
action: searchUpdateLevel,
Page: entity.Page{
BaseEntity: entity.BaseEntity{RefID: pageID},
Level: uint64(level),
DocumentID: documentID,
},
})
return
}
func searchUpdateLevel(request *databaseRequest, page entity.Page) (err error) {
pageID := page.RefID
level := page.Level
supdate, err := request.Transaction.Preparex(
"UPDATE search SET level=?,revised=? WHERE id=?")
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare search level update for page %s", pageID), err)
return err
}
defer utility.Close(supdate)
_, err = supdate.Exec(level, time.Now().UTC(), pageID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute search level update for page %s", pageID), err)
return
}
return
}
// Delete should be called after a page has been deleted.
func (m *SearchManager) Delete(request *databaseRequest, documentID, pageID string) (rows int64, err error) {
err = m.addQueue(request, queueEntry{
action: searchDelete,
Page: entity.Page{
BaseEntity: entity.BaseEntity{RefID: pageID},
DocumentID: documentID,
},
})
return
}
func searchDelete(request *databaseRequest, page entity.Page) (err error) {
var bm = baseManager{}
//_, err = bm.DeleteWhere(request.Transaction, fmt.Sprintf("DELETE FROM search WHERE orgid=\"%s\" AND pageid=\"%s\"", request.OrgID, page.RefID))
_, err = bm.DeleteConstrainedWithID(request.Transaction, "search", request.OrgID, page.RefID)
return
}
/******************
* Sort Page Context
*******************/
// GetPageContext is called to get the context of a page in terms of an headings hierarchy.
func (m *SearchManager) GetPageContext(request *databaseRequest, pageID string, existingContext []string) ([]string, error) {
err := request.MakeTx()
if err != nil {
return nil, err
}
target := entity.Search{}
stmt1, err := request.Transaction.Preparex("SELECT * FROM search WHERE id=?")
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare setPageContext select for pageId %s", pageID), err)
return nil, err
}
defer utility.Close(stmt1)
err = stmt1.Get(&target, pageID)
if err != nil {
return existingContext, nil
}
context := append([]string{target.PageTitle}, existingContext...)
if target.Level > 1 { // more levels to process
var next struct{ ID string }
// process the lower levels
stmt2, err := request.Transaction.Preparex("SELECT id FROM search WHERE documentid=? " +
"AND sequence=(SELECT max(sequence) FROM search " +
"WHERE documentid=? AND sequence<? AND level=?)")
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare GetPageContext next select for pageId %s", pageID), err)
return nil, err
}
defer utility.Close(stmt2)
err = stmt2.Get(&next, target.DocumentID, target.DocumentID, target.Sequence, target.Level-1)
if err != nil {
if strings.Contains(err.Error(), "no rows in result set") {
return context, nil
}
log.Error(fmt.Sprintf("Unable to execute GetPageContext next select for pageId %s", pageID), err)
return nil, err
}
if len(next.ID) > 0 {
context, err = m.GetPageContext(request, next.ID, context)
if err != nil {
log.Error(fmt.Sprintf("Error calling recursive GetPageContext for pageId %s", pageID), err)
return nil, err
}
} else {
err = fmt.Errorf("search.ID<=0 : %s", next.ID)
log.Error(fmt.Sprintf("Unexpected higher level ID in GetPageContext for pageId %s", pageID), err)
return nil, err
}
}
return context, nil
}

66
core/api/request/setup.go Normal file
View file

@ -0,0 +1,66 @@
// 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
package request
// This file contains the code for initial set-up of a database
import (
"github.com/documize/community/core/api/entity"
"github.com/documize/community/core/api/util"
)
func SetupPersister() (*Persister, error) {
var err error
c := Context{
Authenticated: true, // bool
Guest: false, // bool
Administrator: true, // bool
Editor: true, // bool
UserID: util.UniqueID(), // string
OrgID: util.UniqueID(), // string
//OrgURL: "http://wwww.test.org", // string
//OrgName: "TestOrgName", // string
AllowAnonymousAccess: false, // bool
//AppURL: "https://documize.com", // string // e.g. https://{url}.documize.com
//Expires time.Time
//Transaction: &sqlx.Tx{},
}
p := &Persister{Context: c}
p.Context.Transaction, err = Db.Beginx()
return p, err
}
func (p *Persister) SetupOrganization(company, title, message, domain, email string) (entity.Organization, error) {
org := entity.Organization{
BaseEntity: entity.BaseEntity{RefID: p.Context.OrgID},
Company: company, // string `json:"-"`
Title: title, // string `json:"title"`
Message: message, // string `json:"message"`
//URL: "test.domain", // string `json:"url"`
Domain: domain, // string `json:"domain"`
Email: email, // string `json:"email"`
AllowAnonymousAccess: false, // bool `json:"allowAnonymousAccess"`
//Serial: "123", // string `json:"-"`
Active: true, // bool `json:"-"`
}
err := p.AddOrganization(org)
if err != nil {
return org, err
}
err = p.Context.Transaction.Commit()
if err != nil {
return org, err
}
p.Context.Transaction, err = Db.Beginx()
return org, err
}

291
core/api/request/user.go Normal file
View file

@ -0,0 +1,291 @@
// 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
package request
import (
"fmt"
"strings"
"time"
"database/sql"
"github.com/documize/community/core/api/entity"
"github.com/documize/community/core/log"
"github.com/documize/community/core/utility"
)
// AddUser adds the given user record to the user table.
func (p *Persister) AddUser(user entity.User) (err error) {
user.Created = time.Now().UTC()
user.Revised = time.Now().UTC()
stmt, err := p.Context.Transaction.Preparex("INSERT INTO user (refid, firstname, lastname, email, initials, password, salt, reset, active, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
defer utility.Close(stmt)
if err != nil {
log.Error("Unable to prepare insert for user", err)
return
}
res, err := stmt.Exec(user.RefID, user.Firstname, user.Lastname, strings.ToLower(user.Email), user.Initials, user.Password, user.Salt, "", true, user.Created, user.Revised)
if err != nil {
log.Error("Unable insert for user", err)
return
}
if num, e := res.RowsAffected(); e == nil && num != 1 {
er := fmt.Errorf("expected to insert 1 record, but inserted %d", num)
log.Error("AddUser", er)
return er
}
p.Base.Audit(p.Context, "add-user", "", "")
return
}
// GetUser returns the user record for the given id.
func (p *Persister) GetUser(id string) (user entity.User, err error) {
stmt, err := Db.Preparex("SELECT id, refid, firstname, lastname, email, initials, password, salt, reset, active, created, revised FROM user WHERE refid=?")
defer utility.Close(stmt)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare select for user %s", id), err)
return
}
err = stmt.Get(&user, id)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute select for user %s", id), err)
return
}
return
}
// GetUserByEmail returns a single row match on email.
func (p *Persister) GetUserByEmail(email string) (user entity.User, err error) {
email = strings.TrimSpace(strings.ToLower(email))
stmt, err := Db.Preparex("SELECT id, refid, firstname, lastname, email, initials, password, salt, reset, active, created, revised FROM user WHERE TRIM(LOWER(email))=?")
defer utility.Close(stmt)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare select for user by email %s", email), err)
return
}
err = stmt.Get(&user, email)
if err != nil && err != sql.ErrNoRows {
log.Error(fmt.Sprintf("Unable to execute select for user by email %s", email), err)
return
}
return
}
// GetUserByDomain matches user by email and domain.
func (p *Persister) GetUserByDomain(domain, email string) (user entity.User, err error) {
email = strings.TrimSpace(strings.ToLower(email))
stmt, err := Db.Preparex("SELECT u.id, u.refid, u.firstname, u.lastname, u.email, u.initials, u.password, u.salt, u.reset, u.active, u.created, u.revised FROM user u, account a, organization o WHERE TRIM(LOWER(u.email))=? AND u.refid=a.userid AND a.orgid=o.refid AND TRIM(LOWER(o.domain))=?")
defer utility.Close(stmt)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare GetUserByDomain %s %s", domain, email), err)
return
}
err = stmt.Get(&user, email, domain)
if err != nil && err != sql.ErrNoRows {
log.Error(fmt.Sprintf("Unable to execute GetUserByDomain %s %s", domain, email), err)
return
}
return
}
// GetUserByToken returns a user record given a reset token value.
func (p *Persister) GetUserByToken(token string) (user entity.User, err error) {
stmt, err := Db.Preparex("SELECT id, refid, firstname, lastname, email, initials, password, salt, reset, active, created, revised FROM user WHERE reset=?")
defer utility.Close(stmt)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare select for user by token %s", token), err)
return
}
err = stmt.Get(&user, token)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute select for user by token %s", token), err)
return
}
return
}
// GetUserBySerial is used to retrieve a user via their temporary password salt value!
// This occurs when we you share a folder with a new user and they have to complete
// the onboarding process.
func (p *Persister) GetUserBySerial(serial string) (user entity.User, err error) {
stmt, err := Db.Preparex("SELECT id, refid, firstname, lastname, email, initials, password, salt, reset, active, created, revised FROM user WHERE salt=?")
defer utility.Close(stmt)
if err != nil {
return
}
err = stmt.Get(&user, serial)
if err != nil {
return
}
return
}
// GetUsersForOrganization returns a slice containing all of the user records for the organizaiton
// identified in the Persister.
func (p *Persister) GetUsersForOrganization() (users []entity.User, err error) {
err = Db.Select(&users,
"SELECT id, refid, firstname, lastname, email, initials, password, salt, reset, active, created, revised FROM user WHERE refid IN (SELECT userid FROM account where orgid = ?) ORDER BY firstname,lastname", p.Context.OrgID)
if err != nil {
log.Error(fmt.Sprintf("Unable to get all users for org %s", p.Context.OrgID), err)
return
}
return
}
// GetFolderUsers returns a slice containing all user records for given folder.
func (p *Persister) GetFolderUsers(folderID string) (users []entity.User, err error) {
err = Db.Select(&users,
"SELECT id, refid, firstname, lastname, email, initials, password, salt, reset, active, created, revised FROM user WHERE refid IN (SELECT userid from labelrole WHERE orgid=? AND labelid=?) ORDER BY firstname,lastname", p.Context.OrgID, folderID)
if err != nil {
log.Error(fmt.Sprintf("Unable to get all users for org %s", p.Context.OrgID), err)
return
}
return
}
// UpdateUser updates the user table using the given replacement user record.
func (p *Persister) UpdateUser(user entity.User) (err error) {
user.Revised = time.Now().UTC()
user.Email = strings.ToLower(user.Email)
stmt, err := p.Context.Transaction.PrepareNamed(
"UPDATE user SET firstname=:firstname, lastname=:lastname, email=:email, revised=:revised, active=:active, initials=:initials WHERE refid=:refid")
defer utility.Close(stmt)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare update for user %s", user.RefID), err)
return
}
_, err = stmt.Exec(&user)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare update for user %s", user.RefID), err)
return
}
p.Base.Audit(p.Context, "update-user", "", "")
return
}
// UpdateUserPassword updates a user record with new password and salt values.
func (p *Persister) UpdateUserPassword(userID, salt, password string) (err error) {
stmt, err := p.Context.Transaction.Preparex("UPDATE user SET salt=?, password=?, reset='' WHERE refid=?")
defer utility.Close(stmt)
if err != nil {
log.Error("Unable to prepare update for user", err)
return
}
res, err := stmt.Exec(salt, password, userID)
if err != nil {
log.Error(fmt.Sprintf("Unable to update password for user %s", userID), err)
return
}
if num, e := res.RowsAffected(); e == nil && num != 1 {
er := fmt.Errorf("expected to update 1 record, but updated %d", num)
log.Error("UpdateUserPassword", er)
return er
}
p.Base.Audit(p.Context, "change-password", "", "")
return
}
// DeactiveUser deletes the account record for the given userID and persister.Context.OrgID.
func (p *Persister) DeactiveUser(userID string) (err error) {
stmt, err := p.Context.Transaction.Preparex("DELETE FROM account WHERE userid=? and orgid=?")
defer utility.Close(stmt)
if err != nil {
log.Error("Unable to prepare update for user", err)
return
}
_ /* deleting 0 records is OK */, err = stmt.Exec(userID, p.Context.OrgID)
if err != nil {
log.Error(fmt.Sprintf("Unable to deactivate user %s", userID), err)
return
}
p.Base.Audit(p.Context, "deactivate-user", "", "")
return
}
// ForgotUserPassword sets the password to '' and the reset field to token, for a user identified by email.
func (p *Persister) ForgotUserPassword(email, token string) (err error) {
stmt, err := p.Context.Transaction.Preparex("UPDATE user SET reset=?, password='' WHERE LOWER(email)=?")
defer utility.Close(stmt)
if err != nil {
log.Error("Unable to prepare update for reset password", err)
return
}
result, err := stmt.Exec(token, strings.ToLower(email))
if err != nil {
log.Error(fmt.Sprintf("Unable to update password for reset password %s", email), err)
return
}
rows, err := result.RowsAffected()
log.IfErr(err)
if rows == 0 {
err = sql.ErrNoRows
return
}
p.Base.Audit(p.Context, "forgot-password", "", "")
return
}

View file

@ -0,0 +1,218 @@
// 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
package request
/* TODO(Elliott)
import (
"database/sql"
"testing"
"github.com/documize/community/documize/api/entity"
"github.com/documize/community/documize/api/util"
)
func testAddUser(t *testing.T, p *Persister) entity.User {
user := entity.User{
BaseEntity: entity.BaseEntity{RefID: p.Context.UserID},
Firstname: "testFirstname", // string `json:"firstname"`
Lastname: "testLastname", // string `json:"lastname"`
Email: "testuser@somecompany.zit", // string `json:"email"`
Active: true, // bool `json:"active"`
Editor: true, // bool `json:"editor"`
Admin: true, // bool `json:"admin"`
//Password: "testpassword", // string `json:"-"`
//Salt: "testsalt", // string `json:"-"`
//Reset: "testreset", // string `json:"-"`
Accounts: nil, // []Account `json:"accounts"`
}
user.Salt = util.GenerateSalt()
requestedPassword := util.GenerateRandomPassword()
user.Password = util.GeneratePassword(requestedPassword, user.Salt)
err := p.AddUser(user)
if err != nil {
t.Error(err)
p.testRollback(t)
testDeleteUser(t, p)
t.Fail()
}
p.testCommit(t)
return user
}
func testDeleteUser(t *testing.T, p *Persister) {
p.testNewTx(t) // so that we can use it reliably in defer
rows, err := p.Base.Delete(p.Context.Transaction, "user", p.Context.UserID)
if err != nil {
t.Error(err)
t.Fail()
}
if rows != 1 {
t.Errorf("expected 1 row deleted got %d", rows)
t.Fail()
}
p.testCommit(t)
}
func TestUser(t *testing.T) {
p := newTestPersister(t)
defer deleteTestAuditTrail(t, p)
org := testAddOrganization(t, p)
defer testDeleteOrganization(t, p)
user := testAddUser(t, p)
defer testDeleteUser(t, p)
testAddAccount(t, p)
//defer testDeleteAccount(t, p) // done by p.DeactiveUser()
//t.Log(user)
err := p.AddUser(user)
if err == nil {
t.Error("should have errored on duplicate user", err)
}
p.testRollback(t)
usr1, err := p.GetUser(p.Context.UserID)
if err != nil {
t.Error(err)
}
if usr1.Firstname != user.Firstname {
t.Error("wrong data returned")
}
p.testRollback(t)
_, err = p.GetUser("XXXXXXXXXXXXX")
if err == nil {
t.Error("should have errored on get unknown user", err)
}
p.testRollback(t)
usr2, err := p.GetUserByEmail(user.Email)
if err != nil {
t.Error(err)
}
if usr2.Firstname != user.Firstname {
t.Error("wrong data returned")
}
p.testRollback(t)
_, err = p.GetUserByEmail("XXXXXXXXXXXXX")
if err != sql.ErrNoRows {
t.Error("should have errored with sql.ErrNoRows on get user by unknown email", err)
}
p.testRollback(t)
usr3, err := p.GetUserByDomain(org.Domain, user.Email)
if err != nil {
t.Error(err)
}
if usr3.Firstname != user.Firstname {
t.Error("wrong data returned")
}
p.testRollback(t)
_, err = p.GetUserByDomain("XXXXXXXXXXXXX", "YYYYYYYYYYYYY")
if err != sql.ErrNoRows {
t.Error("should have errored with sql.ErrNoRows on get user by unknown email", err)
}
p.testRollback(t)
usr4, err := p.GetUserBySerial(usr3.Salt)
if err != nil {
t.Error(err)
}
if usr4.Firstname != usr3.Firstname {
t.Error("wrong data returned", user, usr4)
}
p.testRollback(t)
_, err = p.GetUserBySerial("XXXXXXXXXXXXX")
if err != sql.ErrNoRows {
t.Error("should have errored with sql.ErrNoRows on get user by unknown serial", err)
}
p.testRollback(t)
uu := user
uu.Lastname = "Smith"
err = p.UpdateUser(uu)
if err != nil {
t.Error(err)
}
p.testCommit(t)
users, err := p.GetUsersForOrganization()
if err != nil {
t.Error(err)
}
if len(users) != 1 {
t.Error("wrong number of users returned", len(users))
} else {
if users[0].Lastname != "Smith" {
t.Error("wrong data returned", users[0], user)
}
}
p.testRollback(t)
err = p.UpdateUserPassword(user.RefID, "salt", "password")
if err != nil {
t.Error(err)
}
p.testCommit(t)
err = p.UpdateUserPassword("XXXXXXXXXX", "salt", "password")
if err == nil {
t.Error("did not error when expected")
}
p.testRollback(t)
err = p.ForgotUserPassword("XXXXXXXXXXX", "token")
if err != sql.ErrNoRows {
t.Error("should have errored with sql.ErrNoRows ForgotUserPassword with unknown ID", err)
}
p.testRollback(t)
err = p.ForgotUserPassword(user.Email, "token")
if err != nil {
t.Error(err)
}
p.testCommit(t)
usrT, err := p.GetUserByToken("token")
if err != nil {
t.Error(err)
}
if usrT.Lastname != "Smith" {
t.Error("wrong data returned", usrT)
}
p.testRollback(t)
_, err = p.GetUserByToken("XXXXXXXXXX")
if err == nil {
t.Error("did not error when expected")
}
p.testRollback(t)
err = p.DeactiveUser(user.RefID) // does not error on bad ID
if err != nil {
t.Error(err)
}
p.testCommit(t)
_, err = p.GetUserByDomain(org.Domain, user.Email)
if err == nil {
t.Error("did not error when expected")
}
p.testRollback(t)
}
*/