mirror of
https://github.com/documize/community.git
synced 2025-07-24 07:39:43 +02:00
restructure directories
This commit is contained in:
parent
7e4ed6545b
commit
a2ce777762
159 changed files with 320 additions and 323 deletions
148
core/api/request/account.go
Normal file
148
core/api/request/account.go
Normal 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)
|
||||
}
|
139
core/api/request/account_test.go
Normal file
139
core/api/request/account_test.go
Normal 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)
|
||||
|
||||
}
|
||||
*/
|
106
core/api/request/attachment.go
Normal file
106
core/api/request/attachment.go
Normal 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)
|
||||
}
|
106
core/api/request/attachment_test.go
Normal file
106
core/api/request/attachment_test.go
Normal 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
152
core/api/request/config.go
Normal 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
214
core/api/request/context.go
Normal 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
|
||||
}
|
80
core/api/request/context_test.go
Normal file
80
core/api/request/context_test.go
Normal 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
13
core/api/request/doc.go
Normal 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
|
402
core/api/request/document.go
Normal file
402
core/api/request/document.go
Normal 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)
|
||||
}
|
256
core/api/request/document_test.go
Normal file
256
core/api/request/document_test.go
Normal 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)
|
||||
|
||||
}
|
||||
*/
|
44
core/api/request/domain.go
Normal file
44
core/api/request/domain.go
Normal 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)
|
||||
}
|
42
core/api/request/domain_test.go
Normal file
42
core/api/request/domain_test.go
Normal 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
221
core/api/request/init.go
Normal 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)
|
||||
}
|
37
core/api/request/init_test.go
Normal file
37
core/api/request/init_test.go
Normal 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
190
core/api/request/label.go
Normal 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)
|
||||
}
|
149
core/api/request/label_test.go
Normal file
149
core/api/request/label_test.go
Normal 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
|
||||
|
||||
}
|
||||
*/
|
136
core/api/request/labelrole.go
Normal file
136
core/api/request/labelrole.go
Normal 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
|
||||
}
|
236
core/api/request/labelrole_test.go
Normal file
236
core/api/request/labelrole_test.go
Normal 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)
|
||||
|
||||
*/
|
||||
//}
|
193
core/api/request/organization.go
Normal file
193
core/api/request/organization.go
Normal 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
|
||||
}
|
121
core/api/request/organization_test.go
Normal file
121
core/api/request/organization_test.go
Normal 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
424
core/api/request/page.go
Normal 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
|
||||
}
|
278
core/api/request/page_test.go
Normal file
278
core/api/request/page_test.go
Normal 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 flound’ring 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 devil’s 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
468
core/api/request/search.go
Normal 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
66
core/api/request/setup.go
Normal 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
291
core/api/request/user.go
Normal 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
|
||||
}
|
218
core/api/request/user_test.go
Normal file
218
core/api/request/user_test.go
Normal 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)
|
||||
|
||||
}
|
||||
*/
|
Loading…
Add table
Add a link
Reference in a new issue