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

removed old API

This commit is contained in:
Harvey Kandola 2017-08-02 15:26:31 +01:00
parent e284a46f86
commit 562872a4a8
105 changed files with 337 additions and 14496 deletions

View file

@ -19,7 +19,7 @@ cp gui/dist-prod/*.* embed/bindata
cp gui/dist-prod/favicon.ico embed/bindata/public cp gui/dist-prod/favicon.ico embed/bindata/public
rm -rf embed/bindata/mail rm -rf embed/bindata/mail
mkdir -p embed/bindata/mail mkdir -p embed/bindata/mail
cp core/api/mail/*.html embed/bindata/mail cp domain/mail/*.html embed/bindata/mail
cp core/database/templates/*.html embed/bindata cp core/database/templates/*.html embed/bindata
rm -rf embed/bindata/scripts rm -rf embed/bindata/scripts
mkdir -p embed/bindata/scripts mkdir -p embed/bindata/scripts

View file

@ -1,226 +0,0 @@
// 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 endpoint
import (
"bytes"
"database/sql"
"encoding/json"
"fmt"
"io"
"mime"
"net/http"
"github.com/documize/community/core/api/entity"
"github.com/documize/community/core/api/request"
"github.com/documize/community/core/log"
"github.com/documize/community/core/secrets"
"github.com/documize/community/core/uniqueid"
"github.com/gorilla/mux"
_ "github.com/mytrile/mime-ext" // this adds a large number of mime extensions
uuid "github.com/nu7hatch/gouuid"
)
// AttachmentDownload is the end-point that responds to a request for a particular attachment
// by sending the requested file to the client.
func AttachmentDownload(w http.ResponseWriter, r *http.Request) {
method := "AttachmentDownload"
p := request.GetPersister(r)
params := mux.Vars(r)
attachment, err := p.GetAttachment(params["orgID"], params["attachmentID"])
if err == sql.ErrNoRows {
writeNotFoundError(w, method, params["fileID"])
return
}
if err != nil {
writeGeneralSQLError(w, method, err)
return
}
typ := mime.TypeByExtension("." + attachment.Extension)
if typ == "" {
typ = "application/octet-stream"
}
w.Header().Set("Content-Type", typ)
w.Header().Set("Content-Disposition", `Attachment; filename="`+attachment.Filename+`" ; `+`filename*="`+attachment.Filename+`"`)
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(attachment.Data)))
w.WriteHeader(http.StatusOK)
_, err = w.Write(attachment.Data)
log.IfErr(err)
p.RecordEvent(entity.EventTypeAttachmentDownload)
}
// GetAttachments is an end-point that returns all of the attachments of a particular documentID.
func GetAttachments(w http.ResponseWriter, r *http.Request) {
method := "GetAttachments"
p := request.GetPersister(r)
params := mux.Vars(r)
documentID := params["documentID"]
if len(documentID) == 0 {
writeMissingDataError(w, method, "documentID")
return
}
if !p.CanViewDocument(documentID) {
writeForbiddenError(w)
return
}
a, err := p.GetAttachments(documentID)
if err != nil && err != sql.ErrNoRows {
writeGeneralSQLError(w, method, err)
return
}
if len(a) == 0 {
a = []entity.Attachment{}
}
json, err := json.Marshal(a)
if err != nil {
writeJSONMarshalError(w, method, "attachments", err)
return
}
writeSuccessBytes(w, json)
}
// DeleteAttachment is an endpoint that deletes a particular document attachment.
func DeleteAttachment(w http.ResponseWriter, r *http.Request) {
method := "DeleteAttachment"
p := request.GetPersister(r)
params := mux.Vars(r)
documentID := params["documentID"]
attachmentID := params["attachmentID"]
if len(documentID) == 0 || len(attachmentID) == 0 {
writeMissingDataError(w, method, "documentID, attachmentID")
return
}
if !p.CanChangeDocument(documentID) {
writeForbiddenError(w)
return
}
tx, err := request.Db.Beginx()
if err != nil {
writeTransactionError(w, method, err)
return
}
p.Context.Transaction = tx
_, err = p.DeleteAttachment(attachmentID)
if err != nil {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err)
return
}
p.RecordEvent(entity.EventTypeAttachmentDelete)
log.IfErr(tx.Commit())
writeSuccessEmptyJSON(w)
}
// AddAttachments stores files against a document.
func AddAttachments(w http.ResponseWriter, r *http.Request) {
method := "AddAttachments"
p := request.GetPersister(r)
params := mux.Vars(r)
documentID := params["documentID"]
if len(documentID) == 0 {
writeMissingDataError(w, method, "documentID")
return
}
if !p.CanChangeDocument(documentID) {
w.WriteHeader(http.StatusForbidden)
return
}
filedata, filename, err := r.FormFile("attachment")
if err != nil {
writeMissingDataError(w, method, "attachment")
return
}
b := new(bytes.Buffer)
_, err = io.Copy(b, filedata)
if err != nil {
writeServerError(w, method, err)
return
}
var job = "some-uuid"
newUUID, err := uuid.NewV4()
if err != nil {
writeServerError(w, method, err)
return
}
job = newUUID.String()
var a entity.Attachment
refID := uniqueid.Generate()
a.RefID = refID
a.DocumentID = documentID
a.Job = job
random := secrets.GenerateSalt()
a.FileID = random[0:9]
a.Filename = filename.Filename
a.Data = b.Bytes()
tx, err := request.Db.Beginx()
if err != nil {
writeTransactionError(w, method, err)
return
}
p.Context.Transaction = tx
err = p.AddAttachment(a)
if err != nil {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err)
return
}
p.RecordEvent(entity.EventTypeAttachmentAdd)
log.IfErr(tx.Commit())
writeSuccessEmptyJSON(w)
}

View file

@ -1,356 +0,0 @@
// 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 endpoint
import (
"database/sql"
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
"github.com/documize/community/core/api"
"github.com/documize/community/core/api/endpoint/models"
"github.com/documize/community/core/api/entity"
"github.com/documize/community/core/api/request"
"github.com/documize/community/core/api/util"
"github.com/documize/community/core/env"
"github.com/documize/community/core/log"
"github.com/documize/community/core/secrets"
"github.com/documize/community/domain/section/provider"
)
// Authenticate user based up HTTP Authorization header.
// An encrypted authentication token is issued with an expiry date.
func Authenticate(w http.ResponseWriter, r *http.Request) {
method := "Authenticate"
p := request.GetPersister(r)
authHeader := r.Header.Get("Authorization")
// check for http header
if len(authHeader) == 0 {
writeBadRequestError(w, method, "Missing Authorization header")
return
}
// decode what we received
data := strings.Replace(authHeader, "Basic ", "", 1)
decodedBytes, err := secrets.DecodeBase64([]byte(data))
if err != nil {
writeBadRequestError(w, method, "Unable to decode authentication token")
return
}
decoded := string(decodedBytes)
// check that we have domain:email:password (but allow for : in password field!)
credentials := strings.SplitN(decoded, ":", 3)
if len(credentials) != 3 {
writeBadRequestError(w, method, "Bad authentication token, expecting domain:email:password")
return
}
domain := strings.TrimSpace(strings.ToLower(credentials[0]))
domain = request.CheckDomain(domain) // TODO optimize by removing this once js allows empty domains
email := strings.TrimSpace(strings.ToLower(credentials[1]))
password := credentials[2]
log.Info("logon attempt " + email + " @ " + domain)
user, err := p.GetUserByDomain(domain, email)
if err == sql.ErrNoRows {
writeUnauthorizedError(w)
return
}
if err != nil {
writeServerError(w, method, err)
return
}
if len(user.Reset) > 0 || len(user.Password) == 0 {
writeUnauthorizedError(w)
return
}
// Password correct and active user
if email != strings.TrimSpace(strings.ToLower(user.Email)) || !secrets.MatchPassword(user.Password, password, user.Salt) {
writeUnauthorizedError(w)
return
}
org, err := p.GetOrganizationByDomain(domain)
if err != nil {
writeUnauthorizedError(w)
return
}
// Attach user accounts and work out permissions
AttachUserAccounts(p, org.RefID, &user)
// active check
if len(user.Accounts) == 0 {
writeUnauthorizedError(w)
return
}
authModel := models.AuthenticationModel{}
authModel.Token = generateJWT(user.RefID, org.RefID, domain)
authModel.User = user
json, err := json.Marshal(authModel)
if err != nil {
writeJSONMarshalError(w, method, "user", err)
return
}
writeSuccessBytes(w, json)
}
// Authorize secure API calls by inspecting authentication token.
// request.Context provides caller user information.
// Site meta sent back as HTTP custom headers.
func Authorize(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
method := "Authorize"
// Let certain requests pass straight through
authenticated := preAuthorizeStaticAssets(r)
if !authenticated {
token := findJWT(r)
hasToken := len(token) > 1
context, _, tokenErr := decodeJWT(token)
var org = entity.Organization{}
var err = errors.New("")
// We always grab the org record regardless of token status.
// Why? If bad token we might be OK to alow anonymous access
// depending upon the domain in question.
p := request.GetPersister(r)
if len(context.OrgID) == 0 {
org, err = p.GetOrganizationByDomain(request.GetRequestSubdomain(r))
} else {
org, err = p.GetOrganization(context.OrgID)
}
context.Subdomain = org.Domain
// Inability to find org record spells the end of this request.
if err != nil {
writeForbiddenError(w)
return
}
// If we have bad auth token and the domain does not allow anon access
if !org.AllowAnonymousAccess && tokenErr != nil {
writeUnauthorizedError(w)
return
}
domain := request.GetSubdomainFromHost(r)
domain2 := request.GetRequestSubdomain(r)
if org.Domain != domain && org.Domain != domain2 {
log.Info(fmt.Sprintf("domain mismatch %s vs. %s vs. %s", domain, domain2, org.Domain))
writeUnauthorizedError(w)
return
}
// If we have bad auth token and the domain allows anon access
// then we generate guest context.
if org.AllowAnonymousAccess {
// So you have a bad token
if hasToken {
if tokenErr != nil {
writeUnauthorizedError(w)
return
}
} else {
// Just grant anon user guest access
context.UserID = "0"
context.OrgID = org.RefID
context.Authenticated = false
context.Guest = true
}
}
// Refresh context and persister
request.SetContext(r, context)
p = request.GetPersister(r)
context.AllowAnonymousAccess = org.AllowAnonymousAccess
context.OrgName = org.Title
context.Administrator = false
context.Editor = false
context.Global = false
// Fetch user permissions for this org
if context.Authenticated {
user, err := GetSecuredUser(p, org.RefID, context.UserID)
if err != nil {
writeServerError(w, method, err)
return
}
context.Administrator = user.Admin
context.Editor = user.Editor
context.Global = user.Global
var state struct {
Active bool `json:"active"`
Admin bool `json:"admin"`
Editor bool `json:"editor"`
}
state.Active = user.Active
state.Admin = user.Admin
state.Editor = user.Editor
sb, err := json.Marshal(state)
w.Header().Add("X-Documize-Status", string(sb))
}
request.SetContext(r, context)
p = request.GetPersister(r)
// Middleware moves on if we say 'yes' -- authenticated or allow anon access.
authenticated = context.Authenticated || org.AllowAnonymousAccess
}
if authenticated {
next(w, r)
} else {
w.WriteHeader(http.StatusUnauthorized)
}
}
// ValidateAuthToken finds and validates authentication token.
func ValidateAuthToken(w http.ResponseWriter, r *http.Request) {
// TODO should this go after token validation?
if s := r.URL.Query().Get("section"); s != "" {
if err := provider.Callback(s, w, r); err != nil {
log.Error("section validation failure", err)
w.WriteHeader(http.StatusUnauthorized)
}
return
}
token := findJWT(r)
hasToken := len(token) > 1
context, _, tokenErr := decodeJWT(token)
var org = entity.Organization{}
var err = errors.New("")
p := request.GetPersister(r)
// We always grab the org record regardless of token status.
// Why? If bad token we might be OK to alow anonymous access
// depending upon the domain in question.
if len(context.OrgID) == 0 {
org, err = p.GetOrganizationByDomain(request.GetRequestSubdomain(r))
} else {
org, err = p.GetOrganization(context.OrgID)
}
context.Subdomain = org.Domain
// Inability to find org record spells the end of this request.
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
return
}
// If we have bad auth token and the domain does not allow anon access
if !org.AllowAnonymousAccess && tokenErr != nil {
return
}
domain := request.GetSubdomainFromHost(r)
domain2 := request.GetRequestSubdomain(r)
if org.Domain != domain && org.Domain != domain2 {
w.WriteHeader(http.StatusUnauthorized)
return
}
// If we have bad auth token and the domain allows anon access
// then we generate guest context.
if org.AllowAnonymousAccess {
// So you have a bad token
if hasToken {
if tokenErr != nil {
w.WriteHeader(http.StatusUnauthorized)
return
}
} else {
// Just grant anon user guest access
context.UserID = "0"
context.OrgID = org.RefID
context.Authenticated = false
context.Guest = true
}
}
// Refresh context and persister
request.SetContext(r, context)
p = request.GetPersister(r)
context.AllowAnonymousAccess = org.AllowAnonymousAccess
context.OrgName = org.Title
context.Administrator = false
context.Editor = false
context.Global = false
// Fetch user permissions for this org
if !context.Authenticated {
w.WriteHeader(http.StatusUnauthorized)
return
}
user, err := GetSecuredUser(p, org.RefID, context.UserID)
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
return
}
context.Administrator = user.Admin
context.Editor = user.Editor
context.Global = user.Global
util.WriteJSON(w, user)
return
}
// Certain assets/URL do not require authentication.
// Just stops the log files being clogged up with failed auth errors.
func preAuthorizeStaticAssets(r *http.Request) bool {
if strings.ToLower(r.URL.Path) == "/" ||
strings.ToLower(r.URL.Path) == "/validate" ||
strings.ToLower(r.URL.Path) == "/favicon.ico" ||
strings.ToLower(r.URL.Path) == "/robots.txt" ||
strings.ToLower(r.URL.Path) == "/version" ||
strings.HasPrefix(strings.ToLower(r.URL.Path), "/api/public/") ||
((api.Runtime.Flags.SiteMode == env.SiteModeSetup) && (strings.ToLower(r.URL.Path) == "/api/setup")) {
return true
}
return false
}

View file

@ -1,263 +0,0 @@
// 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 endpoint
import (
"bytes"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strings"
"github.com/documize/community/core/api/endpoint/models"
"github.com/documize/community/core/api/entity"
"github.com/documize/community/core/api/request"
"github.com/documize/community/core/api/store"
api "github.com/documize/community/core/convapi"
"github.com/documize/community/core/log"
"github.com/documize/community/core/uniqueid"
"github.com/gorilla/mux"
uuid "github.com/nu7hatch/gouuid"
)
// UploadConvertDocument is an endpoint to both upload and convert a document
func UploadConvertDocument(w http.ResponseWriter, r *http.Request) {
job, folderID, orgID := uploadDocument(w, r)
if job == "" {
return // error already handled
}
convertDocument(w, r, job, folderID, api.ConversionJobRequest{
Job: job,
IndexDepth: 4,
OrgID: orgID,
})
}
func uploadDocument(w http.ResponseWriter, r *http.Request) (string, string, string) {
method := "uploadDocument"
p := request.GetPersister(r)
params := mux.Vars(r)
folderID := params["folderID"]
if !p.CanUploadDocument(folderID) {
writeForbiddenError(w)
return "", "", ""
}
// grab file
filedata, filename, err := r.FormFile("attachment")
if err != nil {
writeMissingDataError(w, method, "attachment")
return "", "", ""
}
b := new(bytes.Buffer)
_, err = io.Copy(b, filedata)
if err != nil {
writeServerError(w, method, err)
return "", "", ""
}
// generate job id
newUUID, err := uuid.NewV4()
if err != nil {
writeServerError(w, method, err)
return "", "", ""
}
job := newUUID.String()
err = storageProvider.Upload(job, filename.Filename, b.Bytes())
if err != nil {
writeServerError(w, method, err)
return "", "", ""
}
log.Info(fmt.Sprintf("Org %s (%s) [Uploaded] %s", p.Context.OrgName, p.Context.OrgID, filename.Filename))
return job, folderID, p.Context.OrgID
}
func convertDocument(w http.ResponseWriter, r *http.Request, job, folderID string, conversion api.ConversionJobRequest) {
method := "convertDocument"
p := request.GetPersister(r)
licenseKey := request.ConfigString("EDITION-LICENSE", "key")
licenseSignature := request.ConfigString("EDITION-LICENSE", "signature")
k, _ := hex.DecodeString(licenseKey)
s, _ := hex.DecodeString(licenseSignature)
conversion.LicenseKey = k
conversion.LicenseSignature = s
org, err := p.GetOrganization(p.Context.OrgID)
if err != nil {
writePayloadError(w, method, err)
return
}
conversion.ServiceEndpoint = org.ConversionEndpoint
var fileResult *api.DocumentConversionResponse
var filename string
filename, fileResult, err = storageProvider.Convert(conversion)
if err != nil {
writePayloadError(w, method, err)
return
}
if fileResult.Err != "" {
writeGeneralSQLError(w, method, errors.New(fileResult.Err))
return
}
// NOTE: empty .docx documents trigger this error
if len(fileResult.Pages) == 0 {
writeMissingDataError(w, method, "no pages in document")
return
}
// All the commented-out code below should be in following function call
newDocument, err := processDocument(p, filename, job, folderID, fileResult)
if err != nil {
writeServerError(w, method, err)
return
}
json, err := json.Marshal(newDocument)
if err != nil {
writeJSONMarshalError(w, method, "conversion", err)
return
}
writeSuccessBytes(w, json)
}
func processDocument(p request.Persister, filename, job, folderID string, fileResult *api.DocumentConversionResponse) (newDocument entity.Document, err error) {
// Convert into database objects
document := store.ConvertFileResult(filename, fileResult)
document.Job = job
document.OrgID = p.Context.OrgID
document.LabelID = folderID
document.UserID = p.Context.UserID
documentID := uniqueid.Generate()
document.RefID = documentID
tx, err := request.Db.Beginx()
log.IfErr(err)
p.Context.Transaction = tx
err = p.AddDocument(document)
if err != nil {
log.IfErr(tx.Rollback())
log.Error("Cannot insert new document", err)
return
}
//err = processPage(documentID, fileResult.PageFiles, fileResult.Pages.Children[0], 1, p)
for k, v := range fileResult.Pages {
var page entity.Page
page.OrgID = p.Context.OrgID
page.DocumentID = documentID
page.Level = v.Level
page.Title = v.Title
page.Body = string(v.Body)
page.Sequence = float64(k+1) * 1024.0 // need to start above 0 to allow insertion before the first item
pageID := uniqueid.Generate()
page.RefID = pageID
page.ContentType = "wysiwyg"
page.PageType = "section"
meta := entity.PageMeta{}
meta.PageID = pageID
meta.RawBody = page.Body
meta.Config = "{}"
model := models.PageModel{}
model.Page = page
model.Meta = meta
err = p.AddPage(model)
if err != nil {
log.IfErr(tx.Rollback())
log.Error("Cannot process page newly added document", err)
return
}
}
for _, e := range fileResult.EmbeddedFiles {
//fmt.Println("DEBUG embedded file info", document.OrgId, document.Job, e.Name, len(e.Data), e.ID)
var a entity.Attachment
a.DocumentID = documentID
a.Job = document.Job
a.FileID = e.ID
a.Filename = strings.Replace(e.Name, "embeddings/", "", 1)
a.Data = e.Data
refID := uniqueid.Generate()
a.RefID = refID
err = p.AddAttachment(a)
if err != nil {
log.IfErr(tx.Rollback())
log.Error("Cannot add attachment for newly added document", err)
return
}
}
log.IfErr(tx.Commit())
newDocument, err = p.GetDocument(documentID)
if err != nil {
log.Error("Cannot fetch newly added document", err)
return
}
tx, err = request.Db.Beginx()
if err != nil {
log.Error("Cannot begin a transatcion", err)
return
}
p.Context.Transaction = tx
err = p.UpdateDocument(newDocument) // TODO review - this seems to write-back an unaltered record from that read above, but within that it calls searches.UpdateDocument() to reindex the doc.
if err != nil {
log.IfErr(tx.Rollback())
log.Error("Cannot update an imported document", err)
return
}
p.RecordUserActivity(entity.UserActivity{
LabelID: newDocument.LabelID,
SourceID: newDocument.RefID,
SourceType: entity.ActivitySourceTypeDocument,
ActivityType: entity.ActivityTypeCreated})
p.RecordEvent(entity.EventTypeDocumentUpload)
log.IfErr(tx.Commit())
return
}

View file

@ -1,13 +0,0 @@
// 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 endpoint provides API endpoints for Documize.
package endpoint

View file

@ -1,447 +0,0 @@
// 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 endpoint
import (
"database/sql"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"github.com/documize/community/core/api/entity"
"github.com/documize/community/core/api/plugins"
"github.com/documize/community/core/api/request"
"github.com/documize/community/core/api/store"
"github.com/documize/community/core/api/util"
"github.com/documize/community/core/log"
"github.com/documize/community/core/streamutil"
"github.com/documize/community/core/stringutil"
"github.com/gorilla/mux"
)
// SearchDocuments endpoint takes a list of keywords and returns a list of document references matching those keywords.
func SearchDocuments(w http.ResponseWriter, r *http.Request) {
method := "SearchDocuments"
p := request.GetPersister(r)
query := r.URL.Query()
keywords := query.Get("keywords")
decoded, err := url.QueryUnescape(keywords)
log.IfErr(err)
results, err := p.SearchDocument(decoded)
if err != nil {
writeServerError(w, method, err)
return
}
// Put in slugs for easy UI display of search URL
for key, result := range results {
result.DocumentSlug = stringutil.MakeSlug(result.DocumentTitle)
result.FolderSlug = stringutil.MakeSlug(result.LabelName)
results[key] = result
}
if len(results) == 0 {
results = []entity.DocumentSearch{}
}
data, err := json.Marshal(results)
if err != nil {
writeJSONMarshalError(w, method, "search", err)
return
}
p.RecordEvent(entity.EventTypeSearch)
writeSuccessBytes(w, data)
}
// GetDocument is an endpoint that returns the document-level information for a given documentID.
func GetDocument(w http.ResponseWriter, r *http.Request) {
method := "GetDocument"
p := request.GetPersister(r)
params := mux.Vars(r)
id := params["documentID"]
if len(id) == 0 {
writeMissingDataError(w, method, "documentID")
return
}
document, err := p.GetDocument(id)
if err == sql.ErrNoRows {
writeNotFoundError(w, method, id)
return
}
if err != nil {
writeGeneralSQLError(w, method, err)
return
}
if !p.CanViewDocumentInFolder(document.LabelID) {
writeForbiddenError(w)
return
}
json, err := json.Marshal(document)
if err != nil {
writeJSONMarshalError(w, method, "document", err)
return
}
p.Context.Transaction, err = request.Db.Beginx()
if err != nil {
writeTransactionError(w, method, err)
return
}
_ = p.RecordUserActivity(entity.UserActivity{
LabelID: document.LabelID,
SourceID: document.RefID,
SourceType: entity.ActivitySourceTypeDocument,
ActivityType: entity.ActivityTypeRead})
p.RecordEvent(entity.EventTypeDocumentView)
log.IfErr(p.Context.Transaction.Commit())
writeSuccessBytes(w, json)
}
// GetDocumentActivity is an endpoint returning the activity logs for specified document.
func GetDocumentActivity(w http.ResponseWriter, r *http.Request) {
method := "GetDocumentActivity"
p := request.GetPersister(r)
params := mux.Vars(r)
id := params["documentID"]
if len(id) == 0 {
writeMissingDataError(w, method, "documentID")
return
}
a, err := p.GetDocumentActivity(id)
if err != nil && err != sql.ErrNoRows {
writeGeneralSQLError(w, method, err)
return
}
util.WriteJSON(w, a)
}
// GetDocumentLinks is an endpoint returning the links for a document.
func GetDocumentLinks(w http.ResponseWriter, r *http.Request) {
method := "GetDocumentLinks"
p := request.GetPersister(r)
params := mux.Vars(r)
id := params["documentID"]
if len(id) == 0 {
writeMissingDataError(w, method, "documentID")
return
}
oLinks, err := p.GetDocumentOutboundLinks(id)
if len(oLinks) == 0 {
oLinks = []entity.Link{}
}
if err != nil && err != sql.ErrNoRows {
writeGeneralSQLError(w, method, err)
return
}
json, err := json.Marshal(oLinks)
if err != nil {
writeJSONMarshalError(w, method, "link", err)
return
}
writeSuccessBytes(w, json)
}
// GetDocumentsByFolder is an endpoint that returns the documents in a given folder.
func GetDocumentsByFolder(w http.ResponseWriter, r *http.Request) {
method := "GetDocumentsByFolder"
p := request.GetPersister(r)
query := r.URL.Query()
folderID := query.Get("folder")
if len(folderID) == 0 {
writeMissingDataError(w, method, "folder")
return
}
if !p.CanViewFolder(folderID) {
writeForbiddenError(w)
return
}
documents, err := p.GetDocumentsByFolder(folderID)
if err != nil && err != sql.ErrNoRows {
writeServerError(w, method, err)
return
}
if len(documents) == 0 {
documents = []entity.Document{}
}
json, err := json.Marshal(documents)
if err != nil {
writeJSONMarshalError(w, method, "document", err)
return
}
writeSuccessBytes(w, json)
}
// GetDocumentsByTag is an endpoint that returns the documents with a given tag.
func GetDocumentsByTag(w http.ResponseWriter, r *http.Request) {
method := "GetDocumentsByTag"
p := request.GetPersister(r)
query := r.URL.Query()
tag := query.Get("tag")
if len(tag) == 0 {
writeMissingDataError(w, method, "tag")
return
}
documents, err := p.GetDocumentsByTag(tag)
if err != nil && err != sql.ErrNoRows {
writeServerError(w, method, err)
return
}
json, err := json.Marshal(documents)
if err != nil {
writeJSONMarshalError(w, method, "document", err)
return
}
writeSuccessBytes(w, json)
}
// DeleteDocument is an endpoint that deletes a document specified by documentID.
func DeleteDocument(w http.ResponseWriter, r *http.Request) {
method := "DeleteDocument"
p := request.GetPersister(r)
params := mux.Vars(r)
documentID := params["documentID"]
if len(documentID) == 0 {
writeMissingDataError(w, method, "documentID")
return
}
if !p.CanChangeDocument(documentID) {
writeForbiddenError(w)
return
}
doc, err := p.GetDocument(documentID)
if err != nil {
writeGeneralSQLError(w, method, err)
return
}
tx, err := request.Db.Beginx()
if err != nil {
writeTransactionError(w, method, err)
return
}
p.Context.Transaction = tx
_, err = p.DeleteDocument(documentID)
if err != nil {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err)
return
}
_, err = p.DeletePinnedDocument(documentID)
if err != nil && err != sql.ErrNoRows {
log.IfErr(tx.Rollback())
writeServerError(w, method, err)
return
}
_ = p.RecordUserActivity(entity.UserActivity{
LabelID: doc.LabelID,
SourceID: documentID,
SourceType: entity.ActivitySourceTypeDocument,
ActivityType: entity.ActivityTypeDeleted})
p.RecordEvent(entity.EventTypeDocumentDelete)
log.IfErr(tx.Commit())
writeSuccessEmptyJSON(w)
}
// GetDocumentAsDocx returns a Word document.
func GetDocumentAsDocx(w http.ResponseWriter, r *http.Request) {
method := "GetDocumentAsDocx"
p := request.GetPersister(r)
params := mux.Vars(r)
documentID := params["documentID"]
if len(documentID) == 0 {
writeMissingDataError(w, method, "documentID")
return
}
document, err := p.GetDocument(documentID)
if err == sql.ErrNoRows {
writeNotFoundError(w, method, documentID)
return
}
if err != nil {
writeServerError(w, method, err)
return
}
if !p.CanViewDocumentInFolder(document.LabelID) {
writeForbiddenError(w)
return
}
pages, err := p.GetPages(documentID)
if err != nil {
writeServerError(w, method, err)
return
}
xtn := "html"
actions, err := plugins.Lib.Actions("Export")
if err == nil {
for _, x := range actions {
if x == "docx" { // only actually export a docx if we have the plugin
xtn = x
break
}
}
}
estLen := 0
for _, page := range pages {
estLen += len(page.Title) + len(page.Body)
}
html := make([]byte, 0, estLen*2) // should be far bigger than we need
html = append(html, []byte("<html><head></head><body>")...)
for _, page := range pages {
html = append(html, []byte(fmt.Sprintf("<h%d>", page.Level))...)
html = append(html, stringutil.EscapeHTMLcomplexCharsByte([]byte(page.Title))...)
html = append(html, []byte(fmt.Sprintf("</h%d>", page.Level))...)
html = append(html, stringutil.EscapeHTMLcomplexCharsByte([]byte(page.Body))...)
}
html = append(html, []byte("</body></html>")...)
export, err := store.ExportAs(xtn, string(html))
log.Error("store.ExportAs()", err)
w.Header().Set("Content-Disposition", "attachment; filename="+stringutil.MakeSlug(document.Title)+"."+xtn)
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(export.File)))
writeSuccessBytes(w, export.File)
}
// UpdateDocument updates an existing document using the
// format described in NewDocumentModel() encoded as JSON in the request.
func UpdateDocument(w http.ResponseWriter, r *http.Request) {
method := "UpdateDocument"
p := request.GetPersister(r)
if !p.Context.Editor {
w.WriteHeader(http.StatusForbidden)
return
}
params := mux.Vars(r)
documentID := params["documentID"]
if len(documentID) == 0 {
writeMissingDataError(w, method, "documentID")
return
}
if !p.CanChangeDocument(documentID) {
writeForbiddenError(w)
return
}
defer streamutil.Close(r.Body)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
writePayloadError(w, method, err)
return
}
d := entity.Document{}
err = json.Unmarshal(body, &d)
if err != nil {
writeBadRequestError(w, method, "document")
return
}
d.RefID = documentID
tx, err := request.Db.Beginx()
if err != nil {
writeTransactionError(w, method, err)
return
}
p.Context.Transaction = tx
err = p.UpdateDocument(d)
if err != nil {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err)
return
}
p.RecordEvent(entity.EventTypeDocumentUpdate)
log.IfErr(tx.Commit())
writeSuccessEmptyJSON(w)
}

View file

@ -1,110 +0,0 @@
// 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 endpoint
// TestEndpoint is the entrypoint for all testing unit testing of this package.
// The actual tests are in "github.com/documize/documize-sdk/exttest".
/* The tests require an environment specified by two environment variables:
"DOCUMIZEAPI" e.g. "http://localhost:5002"
"DOCUMIZEAUTH" e.g. "demo1:jim@davidson.com:demo123"
- the user for testing must have admin privilidges and a folder called 'TEST'.
*/
/* NOTE currently excluded from SDK and testing are endpoints requiring e-mail interaction:
InviteToFolder()
inviteNewUserToSharedFolder()
AcceptSharedFolder()
ForgotUserPassword()
ResetUserPassword()
ChangeUserPassword()
*/
/* TODO (Elliott) make tests work on an empty database
import (
"os"
"strings"
"testing"
"time"
"github.com/documize/community/documize/api/plugins"
"github.com/documize/community/core/environment"
"github.com/documize/community/core/log"
"github.com/documize/community/sdk/exttest"
)
func TestMain(m *testing.M) {
environment.Parse("db") // the database environment variables must be set
port = "5002"
testHost = "localhost"
testSetup()
x := m.Run()
testTeardown()
os.Exit(x)
}
func testSetup() {
path, err := os.Getwd()
if err != nil {
log.IfErr(err)
return
}
switch {
case strings.HasSuffix(path, "endpoint"):
err = os.Chdir("../../..") // everything needs to be run from the top level documize directory, as plugin paths are relative
if err != nil {
log.IfErr(err)
return
}
case strings.HasSuffix(path, "api"):
err = os.Chdir("../..") // everything needs to be run from the top level documize directory, as plugin paths are relative
if err != nil {
log.IfErr(err)
return
}
case strings.HasSuffix(path, "documize"):
err = os.Chdir("..") // everything needs to be run from the top level documize directory, as plugin paths are relative
if err != nil {
log.IfErr(err)
return
}
case strings.HasSuffix(path, "community"):
// in the right place
default:
log.Error("wrong directory? "+path, nil)
return
}
ready := make(chan struct{}, 1)
go Serve(ready)
<-ready
time.Sleep(time.Second) // just to let everything settle down
}
func testTeardown() {
log.IfErr(plugins.Lib.KillSubProcs())
}
func TestEndpoint(t *testing.T) {
exttest.APItest(t)
}
func BenchmarkEndpoint(b *testing.B) {
for n := 0; n < b.N; n++ {
err := exttest.APIbenchmark()
if err != nil {
b.Error(err)
b.Fail()
}
}
}
*/

View file

@ -1,271 +0,0 @@
// 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 endpoint
import (
"encoding/json"
"io/ioutil"
"net/http"
"encoding/xml"
"fmt"
"github.com/documize/community/core/api/entity"
"github.com/documize/community/core/api/request"
"github.com/documize/community/core/api/util"
"github.com/documize/community/core/event"
"github.com/documize/community/core/log"
)
// GetSMTPConfig returns installation-wide SMTP settings
func GetSMTPConfig(w http.ResponseWriter, r *http.Request) {
method := "GetSMTPConfig"
p := request.GetPersister(r)
if !p.Context.Global {
writeForbiddenError(w)
return
}
// SMTP settings
config := request.ConfigString("SMTP", "")
// marshall as JSON
var y map[string]interface{}
json.Unmarshal([]byte(config), &y)
json, err := json.Marshal(y)
if err != nil {
writeJSONMarshalError(w, method, "SMTP", err)
return
}
util.WriteSuccessBytes(w, json)
}
// SaveSMTPConfig persists global SMTP configuration.
func SaveSMTPConfig(w http.ResponseWriter, r *http.Request) {
method := "SaveSMTPConfig"
p := request.GetPersister(r)
if !p.Context.Global {
writeForbiddenError(w)
return
}
defer r.Body.Close()
body, err := ioutil.ReadAll(r.Body)
if err != nil {
writePayloadError(w, method, err)
return
}
var config string
config = string(body)
tx, err := request.Db.Beginx()
if err != nil {
writeTransactionError(w, method, err)
return
}
p.Context.Transaction = tx
request.ConfigSet("SMTP", config)
util.WriteSuccessEmptyJSON(w)
p.RecordEvent(entity.EventTypeSystemSMTP)
}
// GetLicense returns product license
func GetLicense(w http.ResponseWriter, r *http.Request) {
// method := "GetLicense"
p := request.GetPersister(r)
if !p.Context.Global {
writeForbiddenError(w)
return
}
// SMTP settings
config := request.ConfigString("EDITION-LICENSE", "")
if len(config) == 0 {
config = "{}"
}
x := &licenseXML{Key: "", Signature: ""}
lj := licenseJSON{}
err := json.Unmarshal([]byte(config), &lj)
if err == nil {
x.Key = lj.Key
x.Signature = lj.Signature
} else {
fmt.Println(err)
}
output, err := xml.Marshal(x)
if err != nil {
fmt.Printf("error: %v\n", err)
}
w.WriteHeader(http.StatusOK)
w.Write(output)
}
// SaveLicense persists product license
func SaveLicense(w http.ResponseWriter, r *http.Request) {
method := "SaveLicense"
p := request.GetPersister(r)
if !p.Context.Global {
writeForbiddenError(w)
return
}
defer r.Body.Close()
body, err := ioutil.ReadAll(r.Body)
if err != nil {
writePayloadError(w, method, err)
return
}
var config string
config = string(body)
lj := licenseJSON{}
x := licenseXML{Key: "", Signature: ""}
err = xml.Unmarshal([]byte(config), &x)
if err == nil {
lj.Key = x.Key
lj.Signature = x.Signature
} else {
fmt.Println(err)
}
j, err := json.Marshal(lj)
js := "{}"
if err == nil {
js = string(j)
}
request.ConfigSet("EDITION-LICENSE", js)
event.Handler().Publish(string(event.TypeSystemLicenseChange))
p.Context.Transaction, err = request.Db.Beginx()
if err != nil {
writeTransactionError(w, method, err)
return
}
p.RecordEvent(entity.EventTypeSystemLicense)
log.IfErr(p.Context.Transaction.Commit())
util.WriteSuccessEmptyJSON(w)
}
type licenseXML struct {
XMLName xml.Name `xml:"DocumizeLicense"`
Key string
Signature string
}
type licenseJSON struct {
Key string `json:"key"`
Signature string `json:"signature"`
}
/*
<DocumizeLicense>
<Key>some key</Key>
<Signature>some signature</Signature>
</DocumizeLicense>
*/
// SaveAuthConfig returns installation-wide authentication configuration
func SaveAuthConfig(w http.ResponseWriter, r *http.Request) {
method := "SaveAuthConfig"
p := request.GetPersister(r)
if !p.Context.Global {
writeForbiddenError(w)
return
}
defer r.Body.Close()
body, err := ioutil.ReadAll(r.Body)
if err != nil {
writePayloadError(w, method, err)
return
}
var data authData
err = json.Unmarshal(body, &data)
if err != nil {
writePayloadError(w, method, err)
return
}
org, err := p.GetOrganization(p.Context.OrgID)
if err != nil {
util.WriteGeneralSQLError(w, method, err)
return
}
org.AuthProvider = data.AuthProvider
org.AuthConfig = data.AuthConfig
p.Context.Transaction, err = request.Db.Beginx()
if err != nil {
writeTransactionError(w, method, err)
return
}
err = p.UpdateAuthConfig(org)
if err != nil {
util.WriteGeneralSQLError(w, method, err)
p.Context.Transaction.Rollback()
return
}
p.RecordEvent(entity.EventTypeSystemAuth)
p.Context.Transaction.Commit()
util.WriteSuccessEmptyJSON(w)
}
type authData struct {
AuthProvider string `json:"authProvider"`
AuthConfig string `json:"authConfig"`
}
// GetAuthConfig returns installation-wide auth configuration
func GetAuthConfig(w http.ResponseWriter, r *http.Request) {
p := request.GetPersister(r)
if !p.Context.Global {
writeForbiddenError(w)
return
}
org, err := p.GetOrganization(p.Context.OrgID)
if err != nil {
writeForbiddenError(w)
return
}
util.WriteJSON(w, org.AuthConfig)
}

View file

@ -1,173 +0,0 @@
// 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 endpoint
import (
"fmt"
"net/http"
"github.com/documize/community/core/api"
"github.com/documize/community/core/api/store"
"github.com/documize/community/core/log"
)
var storageProvider store.StorageProvider
func init() {
storageProvider = new(store.LocalStorageProvider)
}
func writePayloadError(w http.ResponseWriter, method string, err error) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusBadRequest)
_, err2 := w.Write([]byte("{Error: 'Bad payload'}"))
log.IfErr(err2)
log.Error(fmt.Sprintf("Unable to decode HTML request body for method %s", method), err)
}
func writeTransactionError(w http.ResponseWriter, method string, err error) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusInternalServerError)
_, err2 := w.Write([]byte("{Error: 'No transaction'}"))
log.IfErr(err2)
log.Error(fmt.Sprintf("Unable to get database transaction for method %s", method), err)
}
// IsInvalidLicense returns true if license is invalid
func IsInvalidLicense() bool {
return api.Runtime.Product.License.Valid == false
}
/*
func WriteAddRecordError(w http.ResponseWriter, method string, err error) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusInternalServerError)
_, err2 := w.Write([]byte("{Error: 'Add error'}"))
log.IfErr(err2)
log.Error(fmt.Sprintf("Unable to insert new database record for method %s", method), err)
}
func WriteGetRecordError(w http.ResponseWriter, method, entity string, err error) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusInternalServerError)
_, err2 := w.Write([]byte("{Error: 'Get error'}"))
log.IfErr(err2)
log.Error(fmt.Sprintf("Unable to get %s record for method %s", entity, method), err)
}
func WriteUpdateRecordError(w http.ResponseWriter, method string, id string, err error) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusInternalServerError)
_, err2 := w.Write([]byte("{Error: 'Add error'}"))
log.IfErr(err2)
log.Error(fmt.Sprintf("Unable to update database record ID %s for method %s", id, method), err)
}
func WriteParameterParsingError(w http.ResponseWriter, method, parameter string, err error) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusBadRequest)
_, err2 := w.Write([]byte("{Error: 'Bad parameter'}"))
log.IfErr(err2)
log.Error(fmt.Sprintf("Unable to parse API parameter %s for method %s", parameter, method), err)
}
*/
func writeMissingDataError(w http.ResponseWriter, method, parameter string) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusBadRequest)
_, err := w.Write([]byte("{Error: 'Missing data'}"))
log.IfErr(err)
log.Info(fmt.Sprintf("Missing data %s for method %s", parameter, method))
}
func writeNotFoundError(w http.ResponseWriter, method string, id string) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusNotFound)
_, err := w.Write([]byte("{Error: 'Not found'}"))
log.IfErr(err)
log.Info(fmt.Sprintf("Not found ID %s for method %s", id, method))
}
func writeGeneralSQLError(w http.ResponseWriter, method string, err error) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusBadRequest)
_, err2 := w.Write([]byte("{Error: 'SQL error'}"))
log.IfErr(err2)
log.Error(fmt.Sprintf("General SQL error for method %s", method), err)
}
func writeJSONMarshalError(w http.ResponseWriter, method, entity string, err error) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusBadRequest)
_, err2 := w.Write([]byte("{Error: 'JSON marshal failed'}"))
log.IfErr(err2)
log.Error(fmt.Sprintf("Failed to JSON marshal %s for method %s", entity, method), err)
}
func writeServerError(w http.ResponseWriter, method string, err error) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusBadRequest)
_, err2 := w.Write([]byte("{Error: 'Internal server error'}"))
log.IfErr(err2)
log.Error(fmt.Sprintf("Internal server error for method %s", method), err)
}
func writeDuplicateError(w http.ResponseWriter, method, entity string) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusConflict)
_, err := w.Write([]byte("{Error: 'Duplicate record'}"))
log.IfErr(err)
log.Info(fmt.Sprintf("Duplicate %s record detected for method %s", entity, method))
}
func writeUnauthorizedError(w http.ResponseWriter) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusUnauthorized)
_, err := w.Write([]byte("{Error: 'Unauthorized'}"))
log.IfErr(err)
}
func writeForbiddenError(w http.ResponseWriter) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusForbidden)
_, err := w.Write([]byte("{Error: 'Forbidden'}"))
log.IfErr(err)
}
func writeBadRequestError(w http.ResponseWriter, method, message string) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusBadRequest)
_, err := w.Write([]byte("{Error: 'Bad Request'}"))
log.IfErr(err)
log.Info(fmt.Sprintf("Bad Request %s for method %s", message, method))
}
func writeSuccessBytes(w http.ResponseWriter, data []byte) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusOK)
_, err := w.Write(data)
log.IfErr(err)
}
func writeSuccessString(w http.ResponseWriter, data string) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusOK)
_, err := w.Write([]byte(data))
log.IfErr(err)
}
func writeSuccessEmptyJSON(w http.ResponseWriter) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusOK)
_, err := w.Write([]byte("{}"))
log.IfErr(err)
}

View file

@ -1,141 +0,0 @@
// 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 endpoint
import (
"fmt"
"net/http"
"strings"
"time"
jwt "github.com/dgrijalva/jwt-go"
"github.com/documize/community/core/api"
"github.com/documize/community/core/api/request"
"github.com/documize/community/core/log"
)
// Generates JSON Web Token (http://jwt.io)
func generateJWT(user, org, domain string) string {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"iss": "Documize",
"sub": "webapp",
"exp": time.Now().Add(time.Hour * 168).Unix(),
"user": user,
"org": org,
"domain": domain,
})
tokenString, _ := token.SignedString([]byte(api.Runtime.Flags.Salt))
return tokenString
}
// Check for authorization token.
// We look for 'Authorization' request header OR query string "?token=XXX".
func findJWT(r *http.Request) (token string) {
header := r.Header.Get("Authorization")
if header != "" {
header = strings.Replace(header, "Bearer ", "", 1)
}
if len(header) > 1 {
token = header
} else {
query := r.URL.Query()
token = query.Get("token")
}
if token == "null" {
token = ""
}
return
}
// We take in raw token string and decode it.
func decodeJWT(tokenString string) (c request.Context, claims jwt.Claims, err error) {
method := "decodeJWT"
// sensible defaults
c.UserID = ""
c.OrgID = ""
c.Authenticated = false
c.Guest = false
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
return []byte(api.Runtime.Flags.Salt), nil
})
if err != nil {
err = fmt.Errorf("bad authorization token")
return
}
if !token.Valid {
if ve, ok := err.(*jwt.ValidationError); ok {
if ve.Errors&jwt.ValidationErrorMalformed != 0 {
log.Error("invalid token", err)
err = fmt.Errorf("bad token")
return
} else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 {
log.Error("expired token", err)
err = fmt.Errorf("expired token")
return
} else {
log.Error("invalid token", err)
err = fmt.Errorf("bad token")
return
}
} else {
log.Error("invalid token", err)
err = fmt.Errorf("bad token")
return
}
}
c = request.NewContext()
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
c.UserID = claims["user"].(string)
c.OrgID = claims["org"].(string)
} else {
fmt.Println(err)
}
if len(c.UserID) == 0 || len(c.OrgID) == 0 {
err = fmt.Errorf("%s : unable parse token data", method)
return
}
c.Authenticated = true
c.Guest = false
return c, token.Claims, nil
}
// We take in Keycloak token string and decode it.
func decodeKeycloakJWT(t, pk string) (c jwt.MapClaims, err error) {
token, err := jwt.Parse(t, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}
return jwt.ParseRSAPublicKeyFromPEM([]byte(pk))
})
if c, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
return c, nil
}
return nil, err
}

View file

@ -1,514 +0,0 @@
// 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 endpoint
import (
"bytes"
"database/sql"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"obiwan/utility"
"sort"
"strconv"
"strings"
"github.com/documize/community/core/api/endpoint/models"
"github.com/documize/community/core/api/entity"
"github.com/documize/community/core/api/request"
"github.com/documize/community/core/api/util"
"github.com/documize/community/core/log"
"github.com/documize/community/core/secrets"
"github.com/documize/community/core/streamutil"
"github.com/documize/community/core/stringutil"
"github.com/documize/community/core/uniqueid"
)
// AuthenticateKeycloak checks Keycloak authentication credentials.
func AuthenticateKeycloak(w http.ResponseWriter, r *http.Request) {
method := "AuthenticateKeycloak"
p := request.GetPersister(r)
defer streamutil.Close(r.Body)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
writeBadRequestError(w, method, "Bad payload")
return
}
a := keycloakAuthRequest{}
err = json.Unmarshal(body, &a)
if err != nil {
writePayloadError(w, method, err)
return
}
a.Domain = strings.TrimSpace(strings.ToLower(a.Domain))
a.Domain = request.CheckDomain(a.Domain) // TODO optimize by removing this once js allows empty domains
a.Email = strings.TrimSpace(strings.ToLower(a.Email))
// Check for required fields.
if len(a.Email) == 0 {
writeUnauthorizedError(w)
return
}
org, err := p.GetOrganizationByDomain(a.Domain)
if err != nil {
writeUnauthorizedError(w)
return
}
p.Context.OrgID = org.RefID
// Fetch Keycloak auth provider config
ac := keycloakConfig{}
err = json.Unmarshal([]byte(org.AuthConfig), &ac)
if err != nil {
writeBadRequestError(w, method, "Unable to unmarshall Keycloak Public Key")
return
}
// Decode and prepare RSA Public Key used by keycloak to sign JWT.
pkb, err := utility.DecodeBase64([]byte(ac.PublicKey))
if err != nil {
writeBadRequestError(w, method, "Unable to base64 decode Keycloak Public Key")
return
}
pk := string(pkb)
pk = fmt.Sprintf("-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----", pk)
// Decode and verify Keycloak JWT
claims, err := decodeKeycloakJWT(a.Token, pk)
if err != nil {
log.Info("decodeKeycloakJWT failed")
log.Info(pk)
util.WriteRequestError(w, err.Error())
return
}
// Compare the contents from JWT with what we have.
// Guards against MITM token tampering.
if a.Email != claims["email"].(string) || claims["sub"].(string) != a.RemoteID {
writeUnauthorizedError(w)
return
}
log.Info("keycloak logon attempt " + a.Email + " @ " + a.Domain)
user, err := p.GetUserByDomain(a.Domain, a.Email)
if err != nil && err != sql.ErrNoRows {
writeServerError(w, method, err)
return
}
// Create user account if not found
if err == sql.ErrNoRows {
log.Info("keycloak add user " + a.Email + " @ " + a.Domain)
user = entity.User{}
user.Firstname = a.Firstname
user.Lastname = a.Lastname
user.Email = a.Email
user.Initials = stringutil.MakeInitials(user.Firstname, user.Lastname)
user.Salt = secrets.GenerateSalt()
user.Password = secrets.GeneratePassword(secrets.GenerateRandomPassword(), user.Salt)
err = addUser(p, &user, ac.DefaultPermissionAddSpace)
if err != nil {
writeServerError(w, method, err)
return
}
}
// Password correct and active user
if a.Email != strings.TrimSpace(strings.ToLower(user.Email)) {
writeUnauthorizedError(w)
return
}
// Attach user accounts and work out permissions.
AttachUserAccounts(p, org.RefID, &user)
// No accounts signals data integrity problem
// so we reject login request.
if len(user.Accounts) == 0 {
writeUnauthorizedError(w)
return
}
// Abort login request if account is disabled.
for _, ac := range user.Accounts {
if ac.OrgID == org.RefID {
if ac.Active == false {
writeUnauthorizedError(w)
return
}
break
}
}
// Generate JWT token
authModel := models.AuthenticationModel{}
authModel.Token = generateJWT(user.RefID, org.RefID, a.Domain)
authModel.User = user
json, err := json.Marshal(authModel)
if err != nil {
writeJSONMarshalError(w, method, "user", err)
return
}
writeSuccessBytes(w, json)
}
// SyncKeycloak gets list of Keycloak users and inserts new users into Documize
// and marks Keycloak disabled users as inactive.
func SyncKeycloak(w http.ResponseWriter, r *http.Request) {
p := request.GetPersister(r)
if !p.Context.Administrator {
writeForbiddenError(w)
return
}
var result struct {
Message string `json:"message"`
IsError bool `json:"isError"`
}
// Org contains raw auth provider config
org, err := p.GetOrganization(p.Context.OrgID)
if err != nil {
result.Message = "Error: unable to get organization record"
result.IsError = true
log.Error(result.Message, err)
util.WriteJSON(w, result)
return
}
// Exit if not using Keycloak
if org.AuthProvider != "keycloak" {
result.Message = "Error: skipping user sync with Keycloak as it is not the configured option"
result.IsError = true
log.Info(result.Message)
util.WriteJSON(w, result)
return
}
// Make Keycloak auth provider config
c := keycloakConfig{}
err = json.Unmarshal([]byte(org.AuthConfig), &c)
if err != nil {
result.Message = "Error: unable read Keycloak configuration data"
result.IsError = true
log.Error(result.Message, err)
util.WriteJSON(w, result)
return
}
// User list from Keycloak
kcUsers, err := KeycloakUsers(c)
if err != nil {
result.Message = "Error: unable to fetch Keycloak users: " + err.Error()
result.IsError = true
log.Error(result.Message, err)
util.WriteJSON(w, result)
return
}
// User list from Documize
dmzUsers, err := p.GetUsersForOrganization()
if err != nil {
result.Message = "Error: unable to fetch Documize users"
result.IsError = true
log.Error(result.Message, err)
util.WriteJSON(w, result)
return
}
sort.Slice(kcUsers, func(i, j int) bool { return kcUsers[i].Email < kcUsers[j].Email })
sort.Slice(dmzUsers, func(i, j int) bool { return dmzUsers[i].Email < dmzUsers[j].Email })
insert := []entity.User{}
for _, k := range kcUsers {
exists := false
for _, d := range dmzUsers {
if k.Email == d.Email {
exists = true
}
}
if !exists {
insert = append(insert, k)
}
}
// Insert new users into Documize
for _, u := range insert {
err = addUser(p, &u, c.DefaultPermissionAddSpace)
}
result.Message = fmt.Sprintf("Keycloak sync'ed %d users, %d new additions", len(kcUsers), len(insert))
log.Info(result.Message)
util.WriteJSON(w, result)
}
// Helper method to setup user account in Documize using Keycloak provided user data.
func addUser(p request.Persister, u *entity.User, addSpace bool) (err error) {
// only create account if not dupe
addUser := true
addAccount := true
var userID string
userDupe, err := p.GetUserByEmail(u.Email)
if err != nil && err != sql.ErrNoRows {
return err
}
if u.Email == userDupe.Email {
addUser = false
userID = userDupe.RefID
}
p.Context.Transaction, err = request.Db.Beginx()
if err != nil {
return err
}
if addUser {
userID = uniqueid.Generate()
u.RefID = userID
err = p.AddUser(*u)
if err != nil {
log.IfErr(p.Context.Transaction.Rollback())
return err
}
} else {
AttachUserAccounts(p, p.Context.OrgID, &userDupe)
for _, a := range userDupe.Accounts {
if a.OrgID == p.Context.OrgID {
addAccount = false
break
}
}
}
// set up user account for the org
if addAccount {
var a entity.Account
a.UserID = userID
a.OrgID = p.Context.OrgID
a.Editor = addSpace
a.Admin = false
accountID := uniqueid.Generate()
a.RefID = accountID
a.Active = true
err = p.AddAccount(a)
if err != nil {
log.IfErr(p.Context.Transaction.Rollback())
return err
}
}
log.IfErr(p.Context.Transaction.Commit())
nu, err := p.GetUser(userID)
u = &nu
return err
}
// KeycloakUsers gets list of Keycloak users for specified Realm, Client Id
func KeycloakUsers(c keycloakConfig) (users []entity.User, err error) {
users = []entity.User{}
form := url.Values{}
form.Add("username", c.AdminUser)
form.Add("password", c.AdminPassword)
form.Add("client_id", "admin-cli")
form.Add("grant_type", "password")
req, err := http.NewRequest("POST",
fmt.Sprintf("%s/realms/master/protocol/openid-connect/token", c.URL),
bytes.NewBufferString(form.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Content-Length", strconv.Itoa(len(form.Encode())))
client := &http.Client{}
res, err := client.Do(req)
if err != nil {
log.Info("Keycloak: cannot connect to auth URL")
return users, err
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
log.Info("Keycloak: cannot read response from auth request")
log.Info(string(body))
return users, err
}
if res.StatusCode != http.StatusOK {
if res.StatusCode == http.StatusUnauthorized {
return users, errors.New("Check Keycloak username/password")
}
return users, errors.New("Keycloak authentication failed " + res.Status)
}
ka := keycloakAPIAuth{}
err = json.Unmarshal(body, &ka)
if err != nil {
return users, err
}
url := fmt.Sprintf("%s/admin/realms/%s/users?max=500", c.URL, c.Realm)
c.Group = strings.TrimSpace(c.Group)
if len(c.Group) > 0 {
log.Info("Keycloak: filtering by Group members")
url = fmt.Sprintf("%s/admin/realms/%s/groups/%s/members?max=500", c.URL, c.Realm, c.Group)
}
req, err = http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", ka.AccessToken))
client = &http.Client{}
res, err = client.Do(req)
if err != nil {
log.Info("Keycloak: unable to fetch users")
return users, err
}
defer res.Body.Close()
body, err = ioutil.ReadAll(res.Body)
if err != nil {
log.Info("Keycloak: unable to read user list response")
return users, err
}
if res.StatusCode != http.StatusOK {
if res.StatusCode == http.StatusNotFound {
if c.Group != "" {
return users, errors.New("Keycloak Realm/Client/Group ID not found")
}
return users, errors.New("Keycloak Realm/Client Id not found")
}
return users, errors.New("Keycloak users list call failed " + res.Status)
}
kcUsers := []keycloakUser{}
err = json.Unmarshal(body, &kcUsers)
if err != nil {
log.Info("Keycloak: unable to unmarshal user list response")
return users, err
}
for _, kc := range kcUsers {
u := entity.User{}
u.Email = kc.Email
u.Firstname = kc.Firstname
u.Lastname = kc.Lastname
u.Initials = stringutil.MakeInitials(u.Firstname, u.Lastname)
u.Active = kc.Enabled
u.Editor = false
users = append(users, u)
}
return users, nil
}
// StripAuthSecrets removes sensitive data from auth provider configuration
func StripAuthSecrets(provider, config string) string {
switch provider {
case "documize":
return config
break
case "keycloak":
c := keycloakConfig{}
err := json.Unmarshal([]byte(config), &c)
if err != nil {
log.Error("StripAuthSecrets", err)
return config
}
c.AdminPassword = ""
c.AdminUser = ""
c.PublicKey = ""
j, err := json.Marshal(c)
if err != nil {
log.Error("StripAuthSecrets", err)
return config
}
return string(j)
break
}
return config
}
// Data received via Keycloak client library
type keycloakAuthRequest struct {
Domain string `json:"domain"`
Token string `json:"token"`
RemoteID string `json:"remoteId"`
Email string `json:"email"`
Username string `json:"username"`
Firstname string `json:"firstname"`
Lastname string `json:"lastname"`
Enabled bool `json:"enabled"`
}
// Keycloak server configuration
type keycloakConfig struct {
URL string `json:"url"`
Realm string `json:"realm"`
ClientID string `json:"clientId"`
PublicKey string `json:"publicKey"`
AdminUser string `json:"adminUser"`
AdminPassword string `json:"adminPassword"`
Group string `json:"group"`
DisableLogout bool `json:"disableLogout"`
DefaultPermissionAddSpace bool `json:"defaultPermissionAddSpace"`
}
// keycloakAPIAuth is returned when authenticating with Keycloak REST API.
type keycloakAPIAuth struct {
AccessToken string `json:"access_token"`
}
// keycloakUser details user record returned by Keycloak
type keycloakUser struct {
ID string `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
Firstname string `json:"firstName"`
Lastname string `json:"lastName"`
Enabled bool `json:"enabled"`
}

View file

@ -1,935 +0,0 @@
// 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 endpoint
import (
"database/sql"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"github.com/gorilla/mux"
"github.com/documize/community/core/api/endpoint/models"
"github.com/documize/community/core/api/entity"
"github.com/documize/community/core/api/mail"
"github.com/documize/community/core/api/request"
"github.com/documize/community/core/api/util"
"github.com/documize/community/core/log"
"github.com/documize/community/core/secrets"
"github.com/documize/community/core/streamutil"
"github.com/documize/community/core/stringutil"
"github.com/documize/community/core/uniqueid"
)
// AddFolder creates a new folder.
func AddFolder(w http.ResponseWriter, r *http.Request) {
if IsInvalidLicense() {
util.WriteBadLicense(w)
return
}
method := "AddFolder"
p := request.GetPersister(r)
if !p.Context.Editor {
writeForbiddenError(w)
return
}
defer streamutil.Close(r.Body)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
writePayloadError(w, method, err)
return
}
var folder = entity.Label{}
err = json.Unmarshal(body, &folder)
if len(folder.Name) == 0 {
writeJSONMarshalError(w, method, "folder", err)
return
}
tx, err := request.Db.Beginx()
if err != nil {
writeTransactionError(w, method, err)
return
}
p.Context.Transaction = tx
id := uniqueid.Generate()
folder.RefID = id
folder.OrgID = p.Context.OrgID
err = addFolder(p, &folder)
if err != nil {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err)
return
}
p.RecordEvent(entity.EventTypeSpaceAdd)
log.IfErr(tx.Commit())
folder, _ = p.GetLabel(id)
json, err := json.Marshal(folder)
if err != nil {
writeJSONMarshalError(w, method, "folder", err)
return
}
writeSuccessBytes(w, json)
}
func addFolder(p request.Persister, label *entity.Label) (err error) {
label.Type = entity.FolderTypePrivate
label.UserID = p.Context.UserID
err = p.AddLabel(*label)
if err != nil {
return
}
role := entity.LabelRole{}
role.LabelID = label.RefID
role.OrgID = label.OrgID
role.UserID = p.Context.UserID
role.CanEdit = true
role.CanView = true
refID := uniqueid.Generate()
role.RefID = refID
err = p.AddLabelRole(role)
return
}
// GetFolder returns the requested folder.
func GetFolder(w http.ResponseWriter, r *http.Request) {
method := "GetFolder"
p := request.GetPersister(r)
params := mux.Vars(r)
id := params["folderID"]
if len(id) == 0 {
writeMissingDataError(w, method, "folderID")
return
}
folder, err := p.GetLabel(id)
if err != nil && err != sql.ErrNoRows {
writeServerError(w, method, err)
return
}
if err == sql.ErrNoRows {
writeNotFoundError(w, method, id)
return
}
json, err := json.Marshal(folder)
if err != nil {
writeJSONMarshalError(w, method, "folder", err)
return
}
writeSuccessBytes(w, json)
}
// GetFolders returns the folders the user can see.
func GetFolders(w http.ResponseWriter, r *http.Request) {
method := "GetFolders"
p := request.GetPersister(r)
folders, err := p.GetLabels()
if err != nil && err != sql.ErrNoRows {
writeServerError(w, method, err)
return
}
if len(folders) == 0 {
folders = []entity.Label{}
}
json, err := json.Marshal(folders)
if err != nil {
writeJSONMarshalError(w, method, "folder", err)
return
}
writeSuccessBytes(w, json)
}
// GetFolderVisibility returns the users that can see the shared folders.
func GetFolderVisibility(w http.ResponseWriter, r *http.Request) {
method := "GetFolderVisibility"
p := request.GetPersister(r)
folders, err := p.GetFolderVisibility()
if err != nil && err != sql.ErrNoRows {
writeServerError(w, method, err)
return
}
json, err := json.Marshal(folders)
if err != nil {
writeJSONMarshalError(w, method, "folder", err)
return
}
writeSuccessBytes(w, json)
}
// UpdateFolder processes request to save folder object to the database
func UpdateFolder(w http.ResponseWriter, r *http.Request) {
method := "UpdateFolder"
p := request.GetPersister(r)
if !p.Context.Editor {
writeForbiddenError(w)
return
}
params := mux.Vars(r)
folderID := params["folderID"]
if len(folderID) == 0 {
writeMissingDataError(w, method, "folderID")
return
}
defer streamutil.Close(r.Body)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
writePayloadError(w, method, err)
return
}
var folder = entity.Label{}
err = json.Unmarshal(body, &folder)
if len(folder.Name) == 0 {
writeJSONMarshalError(w, method, "folder", err)
return
}
folder.RefID = folderID
tx, err := request.Db.Beginx()
if err != nil {
writeTransactionError(w, method, err)
return
}
p.Context.Transaction = tx
err = p.UpdateLabel(folder)
if err != nil {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err)
return
}
p.RecordEvent(entity.EventTypeSpaceUpdate)
log.IfErr(tx.Commit())
json, err := json.Marshal(folder)
if err != nil {
writeJSONMarshalError(w, method, "folder", err)
return
}
writeSuccessBytes(w, json)
}
// RemoveFolder moves documents to another folder before deleting it
func RemoveFolder(w http.ResponseWriter, r *http.Request) {
if IsInvalidLicense() {
util.WriteBadLicense(w)
return
}
method := "RemoveFolder"
p := request.GetPersister(r)
if !p.Context.Editor {
writeForbiddenError(w)
return
}
params := mux.Vars(r)
id := params["folderID"]
move := params["moveToId"]
if len(id) == 0 {
writeMissingDataError(w, method, "folderID")
return
}
if len(move) == 0 {
writeMissingDataError(w, method, "moveToId")
return
}
tx, err := request.Db.Beginx()
if err != nil {
writeTransactionError(w, method, err)
return
}
p.Context.Transaction = tx
_, err = p.DeleteLabel(id)
if err != nil {
log.IfErr(tx.Rollback())
writeServerError(w, method, err)
return
}
err = p.MoveDocumentLabel(id, move)
if err != nil {
log.IfErr(tx.Rollback())
writeServerError(w, method, err)
return
}
err = p.MoveLabelRoles(id, move)
if err != nil {
log.IfErr(tx.Rollback())
writeServerError(w, method, err)
return
}
_, err = p.DeletePinnedSpace(id)
if err != nil && err != sql.ErrNoRows {
log.IfErr(tx.Rollback())
writeServerError(w, method, err)
return
}
p.RecordEvent(entity.EventTypeSpaceDelete)
log.IfErr(tx.Commit())
writeSuccessString(w, "{}")
}
// DeleteFolder deletes empty folder.
func DeleteFolder(w http.ResponseWriter, r *http.Request) {
if IsInvalidLicense() {
util.WriteBadLicense(w)
return
}
method := "DeleteFolder"
p := request.GetPersister(r)
if !p.Context.Editor {
writeForbiddenError(w)
return
}
params := mux.Vars(r)
id := params["folderID"]
if len(id) == 0 {
writeMissingDataError(w, method, "folderID")
return
}
tx, err := request.Db.Beginx()
if err != nil {
writeTransactionError(w, method, err)
return
}
p.Context.Transaction = tx
_, err = p.DeleteLabel(id)
if err != nil {
log.IfErr(tx.Rollback())
writeServerError(w, method, err)
return
}
_, err = p.DeleteLabelRoles(id)
if err != nil {
log.IfErr(tx.Rollback())
writeServerError(w, method, err)
return
}
_, err = p.DeletePinnedSpace(id)
if err != nil && err != sql.ErrNoRows {
log.IfErr(tx.Rollback())
writeServerError(w, method, err)
return
}
p.RecordEvent(entity.EventTypeSpaceDelete)
log.IfErr(tx.Commit())
writeSuccessString(w, "{}")
}
// SetFolderPermissions persists specified folder permissions
func SetFolderPermissions(w http.ResponseWriter, r *http.Request) {
method := "SetFolderPermissions"
p := request.GetPersister(r)
params := mux.Vars(r)
id := params["folderID"]
if len(id) == 0 {
writeMissingDataError(w, method, "folderID")
return
}
label, err := p.GetLabel(id)
if err != nil {
writeBadRequestError(w, method, "No such folder")
return
}
if label.UserID != p.Context.UserID {
writeForbiddenError(w)
return
}
defer streamutil.Close(r.Body)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
writePayloadError(w, method, err)
return
}
var model = models.FolderRolesModel{}
err = json.Unmarshal(body, &model)
tx, err := request.Db.Beginx()
if err != nil {
writeTransactionError(w, method, err)
return
}
p.Context.Transaction = tx
// We compare new permisions to what we had before.
// Why? So we can send out folder invitation emails.
previousRoles, err := p.GetLabelRoles(id)
if err != nil {
writeGeneralSQLError(w, method, err)
return
}
// Store all previous roles as map for easy querying
previousRoleUsers := make(map[string]bool)
for _, v := range previousRoles {
previousRoleUsers[v.UserID] = true
}
// Who is sharing this folder?
inviter, err := p.GetUser(p.Context.UserID)
if err != nil {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err)
return
}
// Nuke all previous permissions for this folder
_, err = p.DeleteLabelRoles(id)
if err != nil {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err)
return
}
me := false
hasEveryoneRole := false
roleCount := 0
url := p.Context.GetAppURL(fmt.Sprintf("s/%s/%s", label.RefID, stringutil.MakeSlug(label.Name)))
for _, role := range model.Roles {
role.OrgID = p.Context.OrgID
role.LabelID = id
// Ensure the folder owner always has access!
if role.UserID == p.Context.UserID {
me = true
role.CanView = true
role.CanEdit = true
}
if len(role.UserID) == 0 && (role.CanView || role.CanEdit) {
hasEveryoneRole = true
}
// Only persist if there is a role!
if role.CanView || role.CanEdit {
roleID := uniqueid.Generate()
role.RefID = roleID
err = p.AddLabelRole(role)
roleCount++
log.IfErr(err)
// We send out folder invitation emails to those users
// that have *just* been given permissions.
if _, isExisting := previousRoleUsers[role.UserID]; !isExisting {
// we skip 'everyone' (user id != empty string)
if len(role.UserID) > 0 {
var existingUser entity.User
existingUser, err = p.GetUser(role.UserID)
if err == nil {
go mail.ShareFolderExistingUser(existingUser.Email, inviter.Fullname(), url, label.Name, model.Message)
log.Info(fmt.Sprintf("%s is sharing space %s with existing user %s", inviter.Email, label.Name, existingUser.Email))
} else {
writeServerError(w, method, err)
}
}
}
}
}
// Do we need to ensure permissions for folder owner when shared?
if !me {
role := entity.LabelRole{}
role.LabelID = id
role.OrgID = p.Context.OrgID
role.UserID = p.Context.UserID
role.CanEdit = true
role.CanView = true
roleID := uniqueid.Generate()
role.RefID = roleID
err = p.AddLabelRole(role)
log.IfErr(err)
}
// Mark up folder type as either public, private or restricted access.
if hasEveryoneRole {
label.Type = entity.FolderTypePublic
} else {
if roleCount > 1 {
label.Type = entity.FolderTypeRestricted
} else {
label.Type = entity.FolderTypePrivate
}
}
log.Error("p.UpdateLabel()", p.UpdateLabel(label))
p.RecordEvent(entity.EventTypeSpacePermission)
log.Error("tx.Commit()", tx.Commit())
writeSuccessEmptyJSON(w)
}
// GetFolderPermissions returns user permissions for the requested folder.
func GetFolderPermissions(w http.ResponseWriter, r *http.Request) {
method := "GetFolderPermissions"
p := request.GetPersister(r)
params := mux.Vars(r)
folderID := params["folderID"]
if len(folderID) == 0 {
writeMissingDataError(w, method, "folderID")
return
}
roles, err := p.GetLabelRoles(folderID)
if err != nil && err != sql.ErrNoRows {
writeGeneralSQLError(w, method, err)
return
}
if len(roles) == 0 {
roles = []entity.LabelRole{}
}
json, err := json.Marshal(roles)
if err != nil {
writeJSONMarshalError(w, method, "folder-permissions", err)
return
}
writeSuccessBytes(w, json)
}
// AcceptSharedFolder records the fact that a user has completed folder onboard process.
func AcceptSharedFolder(w http.ResponseWriter, r *http.Request) {
method := "AcceptSharedFolder"
p := request.GetPersister(r)
params := mux.Vars(r)
folderID := params["folderID"]
if len(folderID) == 0 {
writeMissingDataError(w, method, "folderID")
return
}
org, err := p.GetOrganizationByDomain(p.Context.Subdomain)
if err != nil {
writeGeneralSQLError(w, method, err)
return
}
p.Context.OrgID = org.RefID
defer streamutil.Close(r.Body)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
writePayloadError(w, method, err)
return
}
var model = models.AcceptSharedFolderModel{}
err = json.Unmarshal(body, &model)
if err != nil {
writePayloadError(w, method, err)
return
}
if len(model.Serial) == 0 || len(model.Firstname) == 0 || len(model.Lastname) == 0 || len(model.Password) == 0 {
writeJSONMarshalError(w, method, "missing field data", err)
return
}
if err != nil {
writeGeneralSQLError(w, method, err)
return
}
user, err := p.GetUserBySerial(model.Serial)
// User has already on-boarded.
if err != nil && err == sql.ErrNoRows {
writeDuplicateError(w, method, "user")
return
}
if err != nil {
writeGeneralSQLError(w, method, err)
return
}
user.Firstname = model.Firstname
user.Lastname = model.Lastname
user.Initials = stringutil.MakeInitials(user.Firstname, user.Lastname)
tx, err := request.Db.Beginx()
if err != nil {
writeTransactionError(w, method, err)
return
}
p.Context.Transaction = tx
err = p.UpdateUser(user)
if err != nil {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err)
return
}
p.Context.UserID = user.RefID
salt := secrets.GenerateSalt()
log.IfErr(p.UpdateUserPassword(user.RefID, salt, secrets.GeneratePassword(model.Password, salt)))
if err != nil {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err)
return
}
p.RecordEvent(entity.EventTypeSpaceJoin)
log.IfErr(tx.Commit())
data, err := json.Marshal(user)
if err != nil {
writeJSONMarshalError(w, method, "user", err)
return
}
writeSuccessBytes(w, data)
}
// InviteToFolder sends users folder invitation emails.
func InviteToFolder(w http.ResponseWriter, r *http.Request) {
method := "InviteToFolder"
p := request.GetPersister(r)
params := mux.Vars(r)
id := params["folderID"]
if len(id) == 0 {
writeMissingDataError(w, method, "folderID")
return
}
label, err := p.GetLabel(id)
if err != nil {
writeBadRequestError(w, method, "folder not found")
return
}
if label.UserID != p.Context.UserID {
writeForbiddenError(w)
return
}
defer streamutil.Close(r.Body)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
writePayloadError(w, method, err)
return
}
var model = models.FolderInvitationModel{}
err = json.Unmarshal(body, &model)
tx, err := request.Db.Beginx()
if err != nil {
writeTransactionError(w, method, err)
return
}
p.Context.Transaction = tx
inviter, err := p.GetUser(p.Context.UserID)
if err != nil {
writeGeneralSQLError(w, method, err)
return
}
for _, email := range model.Recipients {
var user entity.User
user, err = p.GetUserByEmail(email)
if err != nil && err != sql.ErrNoRows {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err)
return
}
if len(user.RefID) > 0 {
// Ensure they have access to this organization
accounts, err2 := p.GetUserAccounts(user.RefID)
if err2 != nil {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err2)
return
}
// we create if they c
hasAccess := false
for _, a := range accounts {
if a.OrgID == p.Context.OrgID {
hasAccess = true
}
}
if !hasAccess {
var a entity.Account
a.UserID = user.RefID
a.OrgID = p.Context.OrgID
a.Admin = false
a.Editor = false
a.Active = true
accountID := uniqueid.Generate()
a.RefID = accountID
err = p.AddAccount(a)
if err != nil {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err)
return
}
}
// Ensure they have folder roles
_, err = p.DeleteUserFolderRoles(label.RefID, user.RefID)
log.IfErr(err)
role := entity.LabelRole{}
role.LabelID = label.RefID
role.OrgID = p.Context.OrgID
role.UserID = user.RefID
role.CanEdit = false
role.CanView = true
roleID := uniqueid.Generate()
role.RefID = roleID
err = p.AddLabelRole(role)
if err != nil {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err)
return
}
url := p.Context.GetAppURL(fmt.Sprintf("s/%s/%s", label.RefID, stringutil.MakeSlug(label.Name)))
go mail.ShareFolderExistingUser(email, inviter.Fullname(), url, label.Name, model.Message)
log.Info(fmt.Sprintf("%s is sharing space %s with existing user %s", inviter.Email, label.Name, email))
} else {
// On-board new user
if strings.Contains(email, "@") {
url := p.Context.GetAppURL(fmt.Sprintf("auth/share/%s/%s", label.RefID, stringutil.MakeSlug(label.Name)))
err = inviteNewUserToSharedFolder(p, email, inviter, url, label, model.Message)
if err != nil {
log.IfErr(tx.Rollback())
writeServerError(w, method, err)
return
}
log.Info(fmt.Sprintf("%s is sharing space %s with new user %s", inviter.Email, label.Name, email))
}
}
}
// We ensure that the folder is marked as restricted as a minimum!
if len(model.Recipients) > 0 && label.Type == entity.FolderTypePrivate {
label.Type = entity.FolderTypeRestricted
err = p.UpdateLabel(label)
if err != nil {
log.IfErr(tx.Rollback())
writeServerError(w, method, err)
return
}
}
p.RecordEvent(entity.EventTypeSpaceInvite)
log.IfErr(tx.Commit())
_, err = w.Write([]byte("{}"))
log.IfErr(err)
}
// Invite new user to a folder that someone has shared with them.
// We create the user account with default values and then take them
// through a welcome process designed to capture profile data.
// We add them to the organization and grant them view-only folder access.
func inviteNewUserToSharedFolder(p request.Persister, email string, invitedBy entity.User,
baseURL string, label entity.Label, invitationMessage string) (err error) {
var user = entity.User{}
user.Email = email
user.Firstname = email
user.Lastname = ""
user.Salt = secrets.GenerateSalt()
requestedPassword := secrets.GenerateRandomPassword()
user.Password = secrets.GeneratePassword(requestedPassword, user.Salt)
userID := uniqueid.Generate()
user.RefID = userID
err = p.AddUser(user)
if err != nil {
return
}
// Let's give this user access to the organization
var a entity.Account
a.UserID = userID
a.OrgID = p.Context.OrgID
a.Admin = false
a.Editor = false
a.Active = true
accountID := uniqueid.Generate()
a.RefID = accountID
err = p.AddAccount(a)
if err != nil {
return
}
role := entity.LabelRole{}
role.LabelID = label.RefID
role.OrgID = p.Context.OrgID
role.UserID = userID
role.CanEdit = false
role.CanView = true
roleID := uniqueid.Generate()
role.RefID = roleID
err = p.AddLabelRole(role)
if err != nil {
return
}
url := fmt.Sprintf("%s/%s", baseURL, user.Salt)
go mail.ShareFolderNewUser(user.Email, invitedBy.Fullname(), url, label.Name, invitationMessage)
return
}

View file

@ -1,168 +0,0 @@
// 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 endpoint
import (
"database/sql"
"encoding/json"
"net/http"
"net/url"
"github.com/gorilla/mux"
"github.com/documize/community/core/api/entity"
"github.com/documize/community/core/api/request"
"github.com/documize/community/core/api/util"
"github.com/documize/community/core/log"
"github.com/documize/community/core/uniqueid"
)
// GetLinkCandidates returns references to documents/sections/attachments.
func GetLinkCandidates(w http.ResponseWriter, r *http.Request) {
method := "GetLinkCandidates"
p := request.GetPersister(r)
params := mux.Vars(r)
folderID := params["folderID"]
documentID := params["documentID"]
pageID := params["pageID"]
// parameter check
if len(documentID) == 0 {
util.WriteMissingDataError(w, method, "documentID")
return
}
if len(pageID) == 0 {
util.WriteMissingDataError(w, method, "pageID")
return
}
// permission check
if !p.CanViewDocument(documentID) {
util.WriteForbiddenError(w)
return
}
// We can link to a section within the same document so
// let's get all pages for the document and remove "us".
pages, err := p.GetPagesWithoutContent(documentID)
if err != nil && err != sql.ErrNoRows {
util.WriteServerError(w, method, err)
return
}
if len(pages) == 0 {
pages = []entity.Page{}
}
pc := []entity.LinkCandidate{}
for _, p := range pages {
if p.RefID != pageID {
c := entity.LinkCandidate{
RefID: uniqueid.Generate(),
FolderID: folderID,
DocumentID: documentID,
TargetID: p.RefID,
LinkType: p.PageType,
Title: p.Title,
}
pc = append(pc, c)
}
}
// We can link to attachment within the same document so
// let's get all attachments for the document.
files, err := p.GetAttachments(documentID)
if err != nil && err != sql.ErrNoRows {
util.WriteServerError(w, method, err)
return
}
if len(files) == 0 {
files = []entity.Attachment{}
}
fc := []entity.LinkCandidate{}
for _, f := range files {
c := entity.LinkCandidate{
RefID: uniqueid.Generate(),
FolderID: folderID,
DocumentID: documentID,
TargetID: f.RefID,
LinkType: "file",
Title: f.Filename,
Context: f.Extension,
}
fc = append(fc, c)
}
// send back the payload
var payload struct {
Pages []entity.LinkCandidate `json:"pages"`
Attachments []entity.LinkCandidate `json:"attachments"`
}
payload.Pages = pc
payload.Attachments = fc
json, err := json.Marshal(payload)
if err != nil {
util.WriteMarshalError(w, err)
return
}
util.WriteSuccessBytes(w, json)
}
// SearchLinkCandidates endpoint takes a list of keywords and returns a list of document references matching those keywords.
func SearchLinkCandidates(w http.ResponseWriter, r *http.Request) {
method := "SearchLinkCandidates"
p := request.GetPersister(r)
query := r.URL.Query()
keywords := query.Get("keywords")
decoded, err := url.QueryUnescape(keywords)
log.IfErr(err)
docs, pages, attachments, err := p.SearchLinkCandidates(decoded)
if err != nil {
util.WriteServerError(w, method, err)
return
}
var payload struct {
Documents []entity.LinkCandidate `json:"documents"`
Pages []entity.LinkCandidate `json:"pages"`
Attachments []entity.LinkCandidate `json:"attachments"`
}
payload.Documents = docs
payload.Pages = pages
payload.Attachments = attachments
json, err := json.Marshal(payload)
if err != nil {
util.WriteMarshalError(w, err)
return
}
util.WriteSuccessBytes(w, json)
}

View file

@ -1,179 +0,0 @@
// 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 endpoint
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"text/template"
"github.com/documize/community/core/api"
"github.com/documize/community/core/api/entity"
"github.com/documize/community/core/api/request"
"github.com/documize/community/core/log"
"github.com/documize/community/core/stringutil"
)
// GetMeta provides org meta data based upon request domain (e.g. acme.documize.com).
func GetMeta(w http.ResponseWriter, r *http.Request) {
method := "GetMeta"
p := request.GetPersister(r)
data := entity.SiteMeta{}
data.URL = request.GetSubdomainFromHost(r)
org, err := p.GetOrganizationByDomain(data.URL)
if err != nil {
log.Info(fmt.Sprintf("%s URL not found", data.URL))
writeForbiddenError(w)
return
}
data.OrgID = org.RefID
data.Title = org.Title
data.Message = org.Message
data.AllowAnonymousAccess = org.AllowAnonymousAccess
data.AuthProvider = org.AuthProvider
data.AuthConfig = org.AuthConfig
data.Version = api.Runtime.Product.Version
data.Edition = api.Runtime.Product.License.Edition
data.Valid = api.Runtime.Product.License.Valid
data.ConversionEndpoint = org.ConversionEndpoint
// Strip secrets
data.AuthConfig = StripAuthSecrets(org.AuthProvider, org.AuthConfig)
json, err := json.Marshal(data)
if err != nil {
writeJSONMarshalError(w, method, "meta", err)
return
}
writeSuccessBytes(w, json)
}
// GetRobots returns robots.txt depending on site configuration.
// Did we allow anonymouse access?
func GetRobots(w http.ResponseWriter, r *http.Request) {
method := "GetRobots"
p := request.GetPersister(r)
domain := request.GetSubdomainFromHost(r)
org, err := p.GetOrganizationByDomain(domain)
// default is to deny
robots :=
`User-agent: *
Disallow: /
`
if err != nil {
log.Error(fmt.Sprintf("%s failed to get Organization for domain %s", method, domain), err)
}
// Anonymous access would mean we allow bots to crawl.
if org.AllowAnonymousAccess {
sitemap := p.Context.GetAppURL("sitemap.xml")
robots = fmt.Sprintf(
`User-agent: *
Disallow: /settings/
Disallow: /settings/*
Disallow: /profile/
Disallow: /profile/*
Disallow: /auth/login/
Disallow: /auth/login/
Disallow: /auth/logout/
Disallow: /auth/logout/*
Disallow: /auth/reset/*
Disallow: /auth/reset/*
Disallow: /auth/sso/
Disallow: /auth/sso/*
Disallow: /share
Disallow: /share/*
Sitemap: %s`, sitemap)
}
writeSuccessBytes(w, []byte(robots))
}
// GetSitemap returns URLs that can be indexed.
// We only include public folders and documents (e.g. can be seen by everyone).
func GetSitemap(w http.ResponseWriter, r *http.Request) {
method := "GetSitemap"
p := request.GetPersister(r)
domain := request.GetSubdomainFromHost(r)
org, err := p.GetOrganizationByDomain(domain)
if err != nil {
log.Error(fmt.Sprintf("%s failed to get Organization for domain %s", method, domain), err)
}
sitemap :=
`<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
{{range .}}<url>
<loc>{{ .URL }}</loc>
<lastmod>{{ .Date }}</lastmod>
</url>{{end}}
</urlset>`
var items []sitemapItem
// Anonymous access means we announce folders/documents shared with 'Everyone'.
if org.AllowAnonymousAccess {
// Grab shared folders
folders, err := p.GetPublicFolders(org.RefID)
if err != nil {
log.Error(fmt.Sprintf("%s failed to get folders for domain %s", method, domain), err)
}
for _, folder := range folders {
var item sitemapItem
item.URL = p.Context.GetAppURL(fmt.Sprintf("s/%s/%s", folder.RefID, stringutil.MakeSlug(folder.Name)))
item.Date = folder.Revised.Format("2006-01-02T15:04:05.999999-07:00")
items = append(items, item)
}
// Grab documents from shared folders
documents, err := p.GetPublicDocuments(org.RefID)
if err != nil {
log.Error(fmt.Sprintf("%s failed to get documents for domain %s", method, domain), err)
}
for _, document := range documents {
var item sitemapItem
item.URL = p.Context.GetAppURL(fmt.Sprintf("s/%s/%s/d/%s/%s",
document.FolderID, stringutil.MakeSlug(document.Folder), document.DocumentID, stringutil.MakeSlug(document.Document)))
item.Date = document.Revised.Format("2006-01-02T15:04:05.999999-07:00")
items = append(items, item)
}
}
buffer := new(bytes.Buffer)
t := template.Must(template.New("tmp").Parse(sitemap))
log.IfErr(t.Execute(buffer, &items))
writeSuccessBytes(w, buffer.Bytes())
}
// sitemapItem provides a means to teleport somewhere else for free.
// What did you think it did?
type sitemapItem struct {
URL string
Date string
}

View file

@ -1,82 +0,0 @@
// 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 models describes the communication format between JS snd Go.
// Models are not persisted entities - they are object models that are marshalled between the
// backend and the consumer (UI).
package models
import (
"github.com/documize/community/core/api/entity"
"time"
)
// PageSequenceRequestModel details a page ID and its sequence within the document.
type PageSequenceRequestModel struct {
PageID string `json:"pageId"`
Sequence float64 `json:"sequence"`
}
// PageLevelRequestModel details a page ID and level.
type PageLevelRequestModel struct {
PageID string `json:"pageId"`
Level int `json:"level"`
}
// AuthenticationModel details authentication token and user details.
type AuthenticationModel struct {
Token string `json:"token"`
User entity.User `json:"user"`
}
// DocumentUploadModel details the job ID of an uploaded document.
type DocumentUploadModel struct {
JobID string `json:"jobId"`
}
// FolderInvitationModel details which users have been invited to a folder.
type FolderInvitationModel struct {
Message string
Recipients []string
}
// FolderRolesModel details which users have what permissions on a given folder.
type FolderRolesModel struct {
Message string
Roles []entity.LabelRole
}
// AcceptSharedFolderModel is used to setup a user who has accepted a shared folder.
type AcceptSharedFolderModel struct {
Serial string `json:"serial"`
Firstname string `json:"firstname"`
Lastname string `json:"lastname"`
Password string `json:"password"`
}
// PageModel contains the page and associated meta.
type PageModel struct {
Page entity.Page `json:"page"`
Meta entity.PageMeta `json:"meta"`
}
// DocumentActivity represents an activity taken against a document.
type DocumentActivity struct {
ID int `json:"id"`
OrgID string `json:"orgId"`
LabelID string `json:"folderId"`
DocumentID string `json:"documentId"`
UserID string `json:"userId"`
Firstname string `json:"firstname"`
Lastname string `json:"lastname"`
ActivityType int `json:"activityType"`
Created time.Time `json:"created"`
}

View file

@ -1,108 +0,0 @@
// 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 endpoint
import (
// "bytes"
"database/sql"
"encoding/json"
"io/ioutil"
"net/http"
"github.com/documize/community/core/api/entity"
"github.com/documize/community/core/api/request"
"github.com/documize/community/core/log"
"github.com/documize/community/core/streamutil"
"github.com/gorilla/mux"
)
// GetOrganization returns the requested organization.
func GetOrganization(w http.ResponseWriter, r *http.Request) {
method := "GetOrganization"
p := request.GetPersister(r)
params := mux.Vars(r)
orgID := params["orgID"]
if orgID != p.Context.OrgID {
writeForbiddenError(w)
return
}
org, err := p.GetOrganization(p.Context.OrgID)
if err != nil && err != sql.ErrNoRows {
writeServerError(w, method, err)
return
}
json, err := json.Marshal(org)
if err != nil {
writeJSONMarshalError(w, method, "organization", err)
return
}
writeSuccessBytes(w, json)
}
// UpdateOrganization saves organization amends.
func UpdateOrganization(w http.ResponseWriter, r *http.Request) {
method := "UpdateOrganization"
p := request.GetPersister(r)
if !p.Context.Administrator {
writeForbiddenError(w)
return
}
defer streamutil.Close(r.Body)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
writePayloadError(w, method, err)
return
}
var org = entity.Organization{}
err = json.Unmarshal(body, &org)
org.RefID = p.Context.OrgID
tx, err := request.Db.Beginx()
if err != nil {
writeTransactionError(w, method, err)
return
}
p.Context.Transaction = tx
err = p.UpdateOrganization(org)
if err != nil {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err)
return
}
log.IfErr(tx.Commit())
json, err := json.Marshal(org)
if err != nil {
writeJSONMarshalError(w, method, "organization", err)
return
}
writeSuccessBytes(w, json)
}

File diff suppressed because it is too large Load diff

View file

@ -1,256 +0,0 @@
// 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 endpoint
import (
"database/sql"
"encoding/json"
"io/ioutil"
"net/http"
"strings"
"github.com/documize/community/core/api/entity"
"github.com/documize/community/core/api/request"
"github.com/documize/community/core/api/util"
"github.com/documize/community/core/log"
"github.com/documize/community/core/uniqueid"
"github.com/gorilla/mux"
)
// AddPin saves pinned item.
func AddPin(w http.ResponseWriter, r *http.Request) {
if IsInvalidLicense() {
util.WriteBadLicense(w)
return
}
method := "AddPin"
p := request.GetPersister(r)
params := mux.Vars(r)
userID := params["userID"]
if !p.Context.Authenticated {
writeForbiddenError(w)
return
}
if len(userID) == 0 {
writeMissingDataError(w, method, "userID")
return
}
defer r.Body.Close()
body, err := ioutil.ReadAll(r.Body)
if err != nil {
writePayloadError(w, method, err)
return
}
var pin entity.Pin
err = json.Unmarshal(body, &pin)
if err != nil {
writePayloadError(w, method, err)
return
}
pin.RefID = uniqueid.Generate()
pin.OrgID = p.Context.OrgID
pin.UserID = p.Context.UserID
pin.Pin = strings.TrimSpace(pin.Pin)
if len(pin.Pin) > 20 {
pin.Pin = pin.Pin[0:20]
}
tx, err := request.Db.Beginx()
if err != nil {
writeTransactionError(w, method, err)
return
}
p.Context.Transaction = tx
err = p.AddPin(pin)
if err != nil {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err)
return
}
p.RecordEvent(entity.EventTypePinAdd)
log.IfErr(tx.Commit())
newPin, err := p.GetPin(pin.RefID)
if err != nil {
writeGeneralSQLError(w, method, err)
return
}
util.WriteJSON(w, newPin)
}
// GetUserPins returns users' pins.
func GetUserPins(w http.ResponseWriter, r *http.Request) {
method := "GetUserPins"
p := request.GetPersister(r)
params := mux.Vars(r)
userID := params["userID"]
if len(userID) == 0 {
writeMissingDataError(w, method, "userID")
return
}
if p.Context.UserID != userID {
writeForbiddenError(w)
return
}
pins, err := p.GetUserPins(userID)
if err != nil && err != sql.ErrNoRows {
writeGeneralSQLError(w, method, err)
return
}
if err == sql.ErrNoRows {
pins = []entity.Pin{}
}
json, err := json.Marshal(pins)
if err != nil {
writeJSONMarshalError(w, method, "pin", err)
return
}
writeSuccessBytes(w, json)
}
// DeleteUserPin removes saved user pin.
func DeleteUserPin(w http.ResponseWriter, r *http.Request) {
if IsInvalidLicense() {
util.WriteBadLicense(w)
return
}
method := "DeleteUserPin"
p := request.GetPersister(r)
params := mux.Vars(r)
userID := params["userID"]
pinID := params["pinID"]
if len(userID) == 0 {
writeMissingDataError(w, method, "userID")
return
}
if len(pinID) == 0 {
writeMissingDataError(w, method, "pinID")
return
}
if p.Context.UserID != userID {
writeForbiddenError(w)
return
}
tx, err := request.Db.Beginx()
if err != nil {
writeTransactionError(w, method, err)
return
}
p.Context.Transaction = tx
_, err = p.DeletePin(pinID)
if err != nil && err != sql.ErrNoRows {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err)
return
}
p.RecordEvent(entity.EventTypePinDelete)
log.IfErr(tx.Commit())
util.WriteSuccessEmptyJSON(w)
}
// UpdatePinSequence records order of pinned items.
func UpdatePinSequence(w http.ResponseWriter, r *http.Request) {
if IsInvalidLicense() {
util.WriteBadLicense(w)
return
}
method := "UpdatePinSequence"
p := request.GetPersister(r)
params := mux.Vars(r)
userID := params["userID"]
if !p.Context.Authenticated {
writeForbiddenError(w)
return
}
if len(userID) == 0 {
writeMissingDataError(w, method, "userID")
return
}
defer r.Body.Close()
body, err := ioutil.ReadAll(r.Body)
if err != nil {
writePayloadError(w, method, err)
return
}
var pins []string
err = json.Unmarshal(body, &pins)
if err != nil {
writePayloadError(w, method, err)
return
}
tx, err := request.Db.Beginx()
if err != nil {
writeTransactionError(w, method, err)
return
}
p.Context.Transaction = tx
for k, v := range pins {
err = p.UpdatePinSequence(v, k+1)
if err != nil {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err)
return
}
}
p.RecordEvent(entity.EventTypePinResequence)
log.IfErr(tx.Commit())
newPins, err := p.GetUserPins(userID)
if err != nil {
writeGeneralSQLError(w, method, err)
return
}
util.WriteJSON(w, newPins)
}

View file

@ -1,416 +0,0 @@
// 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 endpoint
import (
"database/sql"
"encoding/json"
"io/ioutil"
"net/http"
"github.com/documize/community/core/api/entity"
"github.com/documize/community/core/api/request"
"github.com/documize/community/core/api/util"
"github.com/documize/community/core/log"
"github.com/documize/community/core/streamutil"
"github.com/documize/community/core/uniqueid"
"github.com/documize/community/domain/section/provider"
"github.com/gorilla/mux"
)
// GetSections returns available smart sections.
func GetSections(w http.ResponseWriter, r *http.Request) {
method := "GetSections"
json, err := json.Marshal(provider.GetSectionMeta())
if err != nil {
writeJSONMarshalError(w, method, "section", err)
return
}
writeSuccessBytes(w, json)
}
// RunSectionCommand passes UI request to section handler.
func RunSectionCommand(w http.ResponseWriter, r *http.Request) {
method := "WebCommand"
p := request.GetPersister(r)
query := r.URL.Query()
documentID := query.Get("documentID")
sectionName := query.Get("section")
// Missing value checks
if len(documentID) == 0 {
writeMissingDataError(w, method, "documentID")
return
}
if len(sectionName) == 0 {
writeMissingDataError(w, method, "section")
return
}
// Note that targetMethod query item can be empty --
// it's up to the section handler to parse if required.
// Permission checks
if !p.CanChangeDocument(documentID) {
writeForbiddenError(w)
return
}
if !p.Context.Editor {
writeForbiddenError(w)
return
}
if !provider.Command(sectionName, provider.NewContext(p.Context.OrgID, p.Context.UserID), w, r) {
log.ErrorString("Unable to run provider.Command() for: " + sectionName)
writeNotFoundError(w, "RunSectionCommand", sectionName)
}
}
// RefreshSections updates document sections where the data is externally sourced.
func RefreshSections(w http.ResponseWriter, r *http.Request) {
method := "RefreshSections"
p := request.GetPersister(r)
query := r.URL.Query()
documentID := query.Get("documentID")
if len(documentID) == 0 {
writeMissingDataError(w, method, "documentID")
return
}
if !p.CanViewDocument(documentID) {
writeForbiddenError(w)
return
}
// Return payload
var pages []entity.Page
// Let's see what sections are reliant on external sources
meta, err := p.GetDocumentPageMeta(documentID, true)
if err != nil {
writeGeneralSQLError(w, method, err)
return
}
tx, err := request.Db.Beginx()
if err != nil {
writeTransactionError(w, method, err)
return
}
p.Context.Transaction = tx
for _, pm := range meta {
// Grab the page because we need content type and
page, err2 := p.GetPage(pm.PageID)
if err2 == sql.ErrNoRows {
continue
}
if err2 != nil {
writeGeneralSQLError(w, method, err2)
log.IfErr(tx.Rollback())
return
}
pcontext := provider.NewContext(pm.OrgID, pm.UserID)
// Ask for data refresh
data, ok := provider.Refresh(page.ContentType, pcontext, pm.Config, pm.RawBody)
if !ok {
log.ErrorString("provider.Refresh could not find: " + page.ContentType)
}
// Render again
body, ok := provider.Render(page.ContentType, pcontext, pm.Config, data)
if !ok {
log.ErrorString("provider.Render could not find: " + page.ContentType)
}
// Compare to stored render
if body != page.Body {
// Persist latest data
page.Body = body
pages = append(pages, page)
refID := uniqueid.Generate()
err = p.UpdatePage(page, refID, p.Context.UserID, false)
if err != nil {
writeGeneralSQLError(w, method, err)
log.IfErr(tx.Rollback())
return
}
err = p.UpdatePageMeta(pm, false) // do not change the UserID on this PageMeta
if err != nil {
writeGeneralSQLError(w, method, err)
log.IfErr(tx.Rollback())
return
}
}
}
log.IfErr(tx.Commit())
json, err := json.Marshal(pages)
if err != nil {
writeJSONMarshalError(w, method, "pages", err)
return
}
writeSuccessBytes(w, json)
}
/**************************************************
* Reusable Content Blocks
**************************************************/
// AddBlock inserts new reusable content block into database.
func AddBlock(w http.ResponseWriter, r *http.Request) {
if IsInvalidLicense() {
util.WriteBadLicense(w)
return
}
method := "AddBlock"
p := request.GetPersister(r)
defer streamutil.Close(r.Body)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
writeBadRequestError(w, method, "Bad payload")
return
}
b := entity.Block{}
err = json.Unmarshal(body, &b)
if err != nil {
writePayloadError(w, method, err)
return
}
if !p.CanUploadDocument(b.LabelID) {
writeForbiddenError(w)
return
}
b.RefID = uniqueid.Generate()
tx, err := request.Db.Beginx()
if err != nil {
writeTransactionError(w, method, err)
return
}
p.Context.Transaction = tx
err = p.AddBlock(b)
if err != nil {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err)
return
}
p.RecordEvent(entity.EventTypeBlockAdd)
log.IfErr(tx.Commit())
b, err = p.GetBlock(b.RefID)
if err != nil {
writeGeneralSQLError(w, method, err)
return
}
json, err := json.Marshal(b)
if err != nil {
writeJSONMarshalError(w, method, "block", err)
return
}
writeSuccessBytes(w, json)
}
// GetBlock returns requested reusable content block.
func GetBlock(w http.ResponseWriter, r *http.Request) {
method := "GetBlock"
p := request.GetPersister(r)
params := mux.Vars(r)
blockID := params["blockID"]
if len(blockID) == 0 {
writeMissingDataError(w, method, "blockID")
return
}
b, err := p.GetBlock(blockID)
if err != nil {
writeGeneralSQLError(w, method, err)
return
}
json, err := json.Marshal(b)
if err != nil {
writeJSONMarshalError(w, method, "block", err)
return
}
writeSuccessBytes(w, json)
}
// GetBlocksForSpace returns available reusable content blocks for the space.
func GetBlocksForSpace(w http.ResponseWriter, r *http.Request) {
method := "GetBlocksForSpace"
p := request.GetPersister(r)
params := mux.Vars(r)
folderID := params["folderID"]
if len(folderID) == 0 {
writeMissingDataError(w, method, "folderID")
return
}
var b []entity.Block
var err error
b, err = p.GetBlocksForSpace(folderID)
if len(b) == 0 {
b = []entity.Block{}
}
if err != nil {
writeGeneralSQLError(w, method, err)
return
}
json, err := json.Marshal(b)
if err != nil {
writeJSONMarshalError(w, method, "block", err)
return
}
writeSuccessBytes(w, json)
}
// UpdateBlock inserts new reusable content block into database.
func UpdateBlock(w http.ResponseWriter, r *http.Request) {
method := "UpdateBlock"
p := request.GetPersister(r)
params := mux.Vars(r)
blockID := params["blockID"]
if len(blockID) == 0 {
writeMissingDataError(w, method, "blockID")
return
}
defer streamutil.Close(r.Body)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
writeBadRequestError(w, method, "Bad payload")
return
}
b := entity.Block{}
err = json.Unmarshal(body, &b)
if err != nil {
writePayloadError(w, method, err)
return
}
b.RefID = blockID
if !p.CanUploadDocument(b.LabelID) {
writeForbiddenError(w)
return
}
tx, err := request.Db.Beginx()
if err != nil {
writeTransactionError(w, method, err)
return
}
p.Context.Transaction = tx
err = p.UpdateBlock(b)
if err != nil {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err)
return
}
p.RecordEvent(entity.EventTypeBlockUpdate)
log.IfErr(tx.Commit())
writeSuccessEmptyJSON(w)
}
// DeleteBlock removes requested reusable content block.
func DeleteBlock(w http.ResponseWriter, r *http.Request) {
method := "DeleteBlock"
p := request.GetPersister(r)
params := mux.Vars(r)
blockID := params["blockID"]
if len(blockID) == 0 {
writeMissingDataError(w, method, "blockID")
return
}
tx, err := request.Db.Beginx()
if err != nil {
writeTransactionError(w, method, err)
return
}
p.Context.Transaction = tx
_, err = p.DeleteBlock(blockID)
if err != nil {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err)
return
}
err = p.RemoveBlockReference(blockID)
if err != nil {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err)
return
}
p.RecordEvent(entity.EventTypeBlockDelete)
log.IfErr(tx.Commit())
writeSuccessEmptyJSON(w)
}

View file

@ -1,572 +0,0 @@
// 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 endpoint
import (
"database/sql"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"github.com/documize/community/core/api/convert"
"github.com/documize/community/core/api/endpoint/models"
"github.com/documize/community/core/api/entity"
"github.com/documize/community/core/api/request"
"github.com/documize/community/core/api/util"
api "github.com/documize/community/core/convapi"
"github.com/documize/community/core/event"
"github.com/documize/community/core/log"
"github.com/documize/community/core/secrets"
"github.com/documize/community/core/streamutil"
"github.com/documize/community/core/stringutil"
"github.com/documize/community/core/uniqueid"
"github.com/gorilla/mux"
uuid "github.com/nu7hatch/gouuid"
)
// SaveAsTemplate saves existing document as a template.
func SaveAsTemplate(w http.ResponseWriter, r *http.Request) {
if IsInvalidLicense() {
util.WriteBadLicense(w)
return
}
method := "SaveAsTemplate"
p := request.GetPersister(r)
var model struct {
DocumentID string
Name string
Excerpt string
}
defer streamutil.Close(r.Body)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
writeBadRequestError(w, method, "Bad payload")
return
}
err = json.Unmarshal(body, &model)
if err != nil {
writePayloadError(w, method, err)
return
}
if !p.CanChangeDocument(model.DocumentID) {
writeForbiddenError(w)
return
}
// DB transaction
tx, err := request.Db.Beginx()
if err != nil {
writeTransactionError(w, method, err)
return
}
p.Context.Transaction = tx
// Duplicate document
doc, err := p.GetDocument(model.DocumentID)
if err != nil {
writeServerError(w, method, err)
return
}
docID := uniqueid.Generate()
doc.Template = true
doc.Title = model.Name
doc.Excerpt = model.Excerpt
doc.RefID = docID
doc.ID = 0
doc.Template = true
// Duplicate pages and associated meta
pages, err := p.GetPages(model.DocumentID)
var pageModel []models.PageModel
if err != nil {
writeServerError(w, method, err)
return
}
for _, page := range pages {
page.DocumentID = docID
page.ID = 0
meta, err2 := p.GetPageMeta(page.RefID)
if err2 != nil {
writeServerError(w, method, err2)
return
}
pageID := uniqueid.Generate()
page.RefID = pageID
meta.PageID = pageID
meta.DocumentID = docID
m := models.PageModel{}
m.Page = page
m.Meta = meta
pageModel = append(pageModel, m)
}
// Duplicate attachments
attachments, _ := p.GetAttachments(model.DocumentID)
for i, a := range attachments {
a.DocumentID = docID
a.RefID = uniqueid.Generate()
a.ID = 0
attachments[i] = a
}
// Now create the template: document, attachments, pages and their meta
err = p.AddDocument(doc)
if err != nil {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err)
return
}
for _, a := range attachments {
err = p.AddAttachment(a)
if err != nil {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err)
return
}
}
for _, m := range pageModel {
err = p.AddPage(m)
if err != nil {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err)
return
}
}
p.RecordEvent(entity.EventTypeTemplateAdd)
// Commit and return new document template
log.IfErr(tx.Commit())
doc, err = p.GetDocument(docID)
if err != nil {
writeGeneralSQLError(w, method, err)
return
}
d, err := json.Marshal(doc)
if err != nil {
writeJSONMarshalError(w, method, "document", err)
return
}
writeSuccessBytes(w, d)
}
// GetSavedTemplates returns all templates saved by the user
func GetSavedTemplates(w http.ResponseWriter, r *http.Request) {
method := "GetSavedTemplates"
p := request.GetPersister(r)
documents, err := p.GetDocumentTemplates()
if err != nil {
writeGeneralSQLError(w, method, err)
return
}
templates := []entity.Template{}
for _, d := range documents {
var template = entity.Template{}
template.ID = d.RefID
template.Title = d.Title
template.Description = d.Excerpt
template.Author = ""
template.Dated = d.Created
template.Type = entity.TemplateTypePrivate
templates = append(templates, template)
}
data, err := json.Marshal(templates)
if err != nil {
writeJSONMarshalError(w, method, "template", err)
return
}
writeSuccessBytes(w, data)
}
// GetStockTemplates returns available templates from the public Documize repository.
func GetStockTemplates(w http.ResponseWriter, r *http.Request) {
method := "GetStockTemplates"
var templates = fetchStockTemplates()
json, err := json.Marshal(templates)
if err != nil {
writeJSONMarshalError(w, method, "template", err)
return
}
writeSuccessBytes(w, json)
}
// StartDocumentFromStockTemplate creates new document using one of the stock templates
func StartDocumentFromStockTemplate(w http.ResponseWriter, r *http.Request) {
method := "StartDocumentFromStockTemplate"
p := request.GetPersister(r)
if !p.Context.Editor {
writeForbiddenError(w)
return
}
params := mux.Vars(r)
folderID := params["folderID"]
if len(folderID) == 0 {
writeMissingDataError(w, method, "folderID")
return
}
templateID := params["templateID"]
if len(templateID) == 0 {
writeMissingDataError(w, method, "templateID")
return
}
filename, template, err := fetchStockTemplate(templateID)
if err != nil {
writeServerError(w, method, err)
return
}
if len(template) == 0 {
writeBadRequestError(w, method, "No data found in template")
}
fileRequest := api.DocumentConversionRequest{}
fileRequest.Filedata = template
fileRequest.Filename = fmt.Sprintf("%s.docx", filename)
fileRequest.PageBreakLevel = 4
//fileRequest.Job = templateID
//fileRequest.OrgID = p.Context.OrgID
// fileResult, err := store.RunConversion(fileRequest)
//fileResultI, err := plugins.Lib.Run(nil, "Convert", "docx", fileRequest)
fileResult, err := convert.Convert(nil, "docx", &fileRequest)
if err != nil {
writeServerError(w, method, err)
return
}
model, err := processDocument(p, fileRequest.Filename, templateID, folderID, fileResult)
if err != nil {
writeServerError(w, method, err)
return
}
json, err := json.Marshal(model)
if err != nil {
writeJSONMarshalError(w, method, "stockTemplate", err)
return
}
writeSuccessBytes(w, json)
}
// StartDocumentFromSavedTemplate creates new document using a saved document as a template.
// If template ID is ZERO then we provide an Empty Document as the new document.
func StartDocumentFromSavedTemplate(w http.ResponseWriter, r *http.Request) {
method := "StartDocumentFromSavedTemplate"
p := request.GetPersister(r)
params := mux.Vars(r)
folderID := params["folderID"]
if len(folderID) == 0 {
writeMissingDataError(w, method, "folderID")
return
}
templateID := params["templateID"]
if len(templateID) == 0 {
writeMissingDataError(w, method, "templateID")
return
}
defer streamutil.Close(r.Body)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
writeBadRequestError(w, method, "Bad payload")
return
}
docTitle := string(body)
// Define an empty document just in case user wanted one.
var d = entity.Document{}
d.Title = docTitle
d.Location = fmt.Sprintf("template-%s", templateID)
d.Excerpt = "A new document"
d.Slug = stringutil.MakeSlug(d.Title)
d.Tags = ""
d.LabelID = folderID
documentID := uniqueid.Generate()
d.RefID = documentID
var pages = []entity.Page{}
var attachments = []entity.Attachment{}
// Fetch document and associated pages, attachments if we have template ID
if templateID != "0" {
d, err = p.GetDocument(templateID)
if err == sql.ErrNoRows {
api.WriteError(w, errors.New("NotFound"))
return
}
if err != nil {
api.WriteError(w, err)
return
}
pages, _ = p.GetPages(templateID)
attachments, _ = p.GetAttachmentsWithData(templateID)
}
// create new document
tx, err := request.Db.Beginx()
if err != nil {
writeTransactionError(w, method, err)
return
}
p.Context.Transaction = tx
// Prepare new document
documentID = uniqueid.Generate()
d.RefID = documentID
d.Template = false
d.LabelID = folderID
d.UserID = p.Context.UserID
d.Title = docTitle
err = p.AddDocument(d)
if err != nil {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err)
return
}
for _, page := range pages {
meta, err2 := p.GetPageMeta(page.RefID)
if err2 != nil {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err)
return
}
page.DocumentID = documentID
pageID := uniqueid.Generate()
page.RefID = pageID
// meta := entity.PageMeta{}
meta.PageID = pageID
meta.DocumentID = documentID
// meta.RawBody = page.Body
model := models.PageModel{}
model.Page = page
model.Meta = meta
err = p.AddPage(model)
if err != nil {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err)
return
}
}
newUUID, err := uuid.NewV4()
if err != nil {
log.IfErr(tx.Rollback())
writeServerError(w, method, err)
return
}
for _, a := range attachments {
a.DocumentID = documentID
a.Job = newUUID.String()
random := secrets.GenerateSalt()
a.FileID = random[0:9]
attachmentID := uniqueid.Generate()
a.RefID = attachmentID
err = p.AddAttachment(a)
if err != nil {
log.IfErr(tx.Rollback())
writeServerError(w, method, err)
return
}
}
p.RecordEvent(entity.EventTypeTemplateUse)
log.IfErr(tx.Commit())
newDocument, err := p.GetDocument(documentID)
if err != nil {
writeServerError(w, method, err)
return
}
event.Handler().Publish(string(event.TypeAddDocument), newDocument.Title)
data, err := json.Marshal(newDocument)
if err != nil {
writeJSONMarshalError(w, method, "document", err)
return
}
writeSuccessBytes(w, data)
}
type templateConfig struct {
ID string `json:"id"`
Description string `json:"description"`
Author string `json:"author"`
Title string `json:"title"`
}
func fetchStockTemplates() (templates []entity.Template) {
path := "./templates"
templates = make([]entity.Template, 0)
folders, err := ioutil.ReadDir(path)
log.IfErr(err)
for _, folder := range folders {
if folder.IsDir() {
files, err := ioutil.ReadDir(path + "/" + folder.Name())
log.IfErr(err)
for _, file := range files {
if !file.IsDir() && file.Name() == "template.json" {
data, err := ioutil.ReadFile(path + "/" + folder.Name() + "/" + file.Name())
if err != nil {
log.Error("error reading template.json", err)
} else {
var config = templateConfig{}
err = json.Unmarshal(data, &config)
if err != nil {
log.Error("error parsing template.json", err)
} else {
var template = entity.Template{}
template.ID = config.ID
template.Title = config.Title
template.Description = config.Description
template.Author = config.Author
template.Type = entity.TemplateTypePublic
templates = append(templates, template)
}
}
}
}
}
}
return
}
func fetchStockTemplate(ID string) (filename string, template []byte, err error) {
path := "./templates"
folders, err := ioutil.ReadDir(path)
if err != nil {
log.Error("error reading template directory", err)
return "", nil, err
}
for _, folder := range folders {
if folder.IsDir() {
files, err := ioutil.ReadDir(path + "/" + folder.Name())
if err != nil {
log.Error("error reading template sub-dir", err)
return "", nil, err
}
for _, file := range files {
if !file.IsDir() && file.Name() == "template.json" {
data, err := ioutil.ReadFile(path + "/" + folder.Name() + "/template.json")
if err != nil {
log.Error("error reading template.json", err)
return "", nil, err
}
var config = templateConfig{}
err = json.Unmarshal(data, &config)
if err != nil {
log.Error("error parsing template.json", err)
return "", nil, err
}
if config.ID == ID {
template, err = ioutil.ReadFile(path + "/" + folder.Name() + "/template.docx")
return folder.Name(), template, err
}
}
}
}
}
return
}

View file

@ -1,768 +0,0 @@
// 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 endpoint
import (
"database/sql"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"obiwan/utility"
"strconv"
"strings"
"github.com/documize/community/core/api/entity"
"github.com/documize/community/core/api/mail"
"github.com/documize/community/core/api/request"
"github.com/documize/community/core/api/util"
api "github.com/documize/community/core/convapi"
"github.com/documize/community/core/event"
"github.com/documize/community/core/log"
"github.com/documize/community/core/secrets"
"github.com/documize/community/core/streamutil"
"github.com/documize/community/core/stringutil"
"github.com/documize/community/core/uniqueid"
"github.com/gorilla/mux"
)
// AddUser is the endpoint that enables an administrator to add a new user for their orgaisation.
func AddUser(w http.ResponseWriter, r *http.Request) {
if IsInvalidLicense() {
util.WriteBadLicense(w)
return
}
method := "AddUser"
p := request.GetPersister(r)
if !p.Context.Administrator {
writeForbiddenError(w)
return
}
defer streamutil.Close(r.Body)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
writePayloadError(w, method, err)
return
}
userModel := entity.User{}
err = json.Unmarshal(body, &userModel)
if err != nil {
writeJSONMarshalError(w, method, "user", err)
return
}
// data validation
userModel.Email = strings.ToLower(strings.TrimSpace(userModel.Email))
userModel.Firstname = strings.TrimSpace(userModel.Firstname)
userModel.Lastname = strings.TrimSpace(userModel.Lastname)
userModel.Password = strings.TrimSpace(userModel.Password)
if len(userModel.Email) == 0 {
writeBadRequestError(w, method, "Missing email")
return
}
if len(userModel.Firstname) == 0 {
writeBadRequestError(w, method, "Missing firstname")
return
}
if len(userModel.Lastname) == 0 {
writeBadRequestError(w, method, "Missing lastname")
return
}
userModel.Initials = stringutil.MakeInitials(userModel.Firstname, userModel.Lastname)
// generate secrets
requestedPassword := secrets.GenerateRandomPassword()
userModel.Salt = secrets.GenerateSalt()
userModel.Password = secrets.GeneratePassword(requestedPassword, userModel.Salt)
// only create account if not dupe
addUser := true
addAccount := true
var userID string
userDupe, err := p.GetUserByEmail(userModel.Email)
if err != nil && err != sql.ErrNoRows {
writeGeneralSQLError(w, method, err)
return
}
if userModel.Email == userDupe.Email {
addUser = false
userID = userDupe.RefID
log.Info("Dupe user found, will not add")
}
tx, err := request.Db.Beginx()
if err != nil {
writeTransactionError(w, method, err)
return
}
p.Context.Transaction = tx
if addUser {
userID = uniqueid.Generate()
userModel.RefID = userID
err = p.AddUser(userModel)
if err != nil {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err)
return
}
log.Info("Adding user")
} else {
AttachUserAccounts(p, p.Context.OrgID, &userDupe)
for _, a := range userDupe.Accounts {
if a.OrgID == p.Context.OrgID {
addAccount = false
log.Info("Dupe account found, will not add")
break
}
}
}
// set up user account for the org
if addAccount {
var a entity.Account
a.RefID = uniqueid.Generate()
a.UserID = userID
a.OrgID = p.Context.OrgID
a.Editor = true
a.Admin = false
a.Active = true
err = p.AddAccount(a)
if err != nil {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err)
return
}
}
if addUser {
event.Handler().Publish(string(event.TypeAddUser))
p.RecordEvent(entity.EventTypeUserAdd)
}
if addAccount {
event.Handler().Publish(string(event.TypeAddAccount))
p.RecordEvent(entity.EventTypeAccountAdd)
}
log.IfErr(tx.Commit())
// If we did not add user or give them access (account) then we error back
if !addUser && !addAccount {
writeDuplicateError(w, method, "user")
return
}
// Invite new user
inviter, err := p.GetUser(p.Context.UserID)
log.IfErr(err)
// Prepare invitation email (that contains SSO link)
if addUser && addAccount {
size := len(requestedPassword)
auth := fmt.Sprintf("%s:%s:%s", p.Context.AppURL, userModel.Email, requestedPassword[:size])
encrypted := utility.EncodeBase64([]byte(auth))
url := fmt.Sprintf("%s/%s", p.Context.GetAppURL("auth/sso"), url.QueryEscape(string(encrypted)))
go mail.InviteNewUser(userModel.Email, inviter.Fullname(), url, userModel.Email, requestedPassword)
log.Info(fmt.Sprintf("%s invited by %s on %s", userModel.Email, inviter.Email, p.Context.AppURL))
} else {
go mail.InviteExistingUser(userModel.Email, inviter.Fullname(), p.Context.GetAppURL(""))
log.Info(fmt.Sprintf("%s is giving access to an existing user %s", inviter.Email, userModel.Email))
}
// Send back new user record
userModel, err = GetSecuredUser(p, p.Context.OrgID, userID)
json, err := json.Marshal(userModel)
if err != nil {
writeJSONMarshalError(w, method, "user", err)
return
}
writeSuccessBytes(w, json)
}
// GetOrganizationUsers is the endpoint that allows administrators to view the users in their organisation.
func GetOrganizationUsers(w http.ResponseWriter, r *http.Request) {
method := "GetUsersForOrganization"
p := request.GetPersister(r)
if !p.Context.Editor && !p.Context.Administrator {
writeForbiddenError(w)
return
}
active, err := strconv.ParseBool(r.URL.Query().Get("active"))
if err != nil {
active = false
}
users := []entity.User{}
if active {
users, err = p.GetActiveUsersForOrganization()
if err != nil && err != sql.ErrNoRows {
writeServerError(w, method, err)
return
}
} else {
users, err = p.GetUsersForOrganization()
if err != nil && err != sql.ErrNoRows {
writeServerError(w, method, err)
return
}
}
if len(users) == 0 {
users = []entity.User{}
}
for i := range users {
AttachUserAccounts(p, p.Context.OrgID, &users[i])
}
json, err := json.Marshal(users)
if err != nil {
writeJSONMarshalError(w, method, "user", err)
return
}
writeSuccessBytes(w, json)
}
// GetFolderUsers returns every user within a given space
func GetFolderUsers(w http.ResponseWriter, r *http.Request) {
method := "GetUsersForSpace"
p := request.GetPersister(r)
var users []entity.User
var err error
params := mux.Vars(r)
folderID := params["folderID"]
if len(folderID) == 0 {
writeBadRequestError(w, method, "missing folderID")
return
}
// check to see folder type as it determines user selection criteria
folder, err := p.GetLabel(folderID)
if err != nil && err != sql.ErrNoRows {
log.Error(fmt.Sprintf("%s: cannot fetch space %s", method, folderID), err)
writeUsers(w, nil)
return
}
switch folder.Type {
case entity.FolderTypePublic:
// return all users for team
users, err = p.GetActiveUsersForOrganization()
break
case entity.FolderTypePrivate:
// just me
var user entity.User
user, err = p.GetUser(p.Context.UserID)
users = append(users, user)
break
case entity.FolderTypeRestricted:
users, err = p.GetFolderUsers(folderID)
break
}
if err != nil && err != sql.ErrNoRows {
log.Error(fmt.Sprintf("%s: cannot fetch users for space %s", method, folderID), err)
writeUsers(w, nil)
return
}
writeUsers(w, users)
}
// GetUser returns user specified by Id
func GetUser(w http.ResponseWriter, r *http.Request) {
method := "GetUser"
p := request.GetPersister(r)
params := mux.Vars(r)
userID := params["userID"]
if len(userID) == 0 {
writeMissingDataError(w, method, "userId")
return
}
if userID != p.Context.UserID {
writeBadRequestError(w, method, "User Id mismatch")
return
}
user, err := GetSecuredUser(p, p.Context.OrgID, userID)
if err == sql.ErrNoRows {
writeNotFoundError(w, method, userID)
return
}
if err != nil {
writeServerError(w, method, err)
return
}
json, err := json.Marshal(user)
if err != nil {
writeJSONMarshalError(w, method, "user", err)
return
}
writeSuccessBytes(w, json)
}
// DeleteUser is the endpoint to delete a user specified by userID, the caller must be an Administrator.
func DeleteUser(w http.ResponseWriter, r *http.Request) {
method := "DeleteUser"
p := request.GetPersister(r)
if !p.Context.Administrator {
writeForbiddenError(w)
return
}
params := mux.Vars(r)
userID := params["userID"]
if len(userID) == 0 {
writeMissingDataError(w, method, "userID")
return
}
if userID == p.Context.UserID {
writeBadRequestError(w, method, "cannot delete self")
return
}
tx, err := request.Db.Beginx()
if err != nil {
writeTransactionError(w, method, err)
return
}
p.Context.Transaction = tx
err = p.DeactiveUser(userID)
if err != nil {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err)
return
}
err = p.ChangeLabelOwner(userID, p.Context.UserID)
log.IfErr(err)
p.RecordEvent(entity.EventTypeUserDelete)
log.IfErr(tx.Commit())
event.Handler().Publish(string(event.TypeRemoveUser))
writeSuccessString(w, "{}")
}
// UpdateUser is the endpoint to update user information for the given userID.
// Note that unless they have admin privildges, a user can only update their own information.
// Also, only admins can update user roles in organisations.
func UpdateUser(w http.ResponseWriter, r *http.Request) {
method := "UpdateUser"
p := request.GetPersister(r)
params := mux.Vars(r)
userID := params["userID"]
if len(userID) == 0 {
writeBadRequestError(w, method, "user id must be numeric")
return
}
defer streamutil.Close(r.Body)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
writePayloadError(w, method, err)
return
}
user := entity.User{}
err = json.Unmarshal(body, &user)
if err != nil {
writeJSONMarshalError(w, method, "user", err)
return
}
// can only update your own account unless you are an admin
if p.Context.UserID != userID && !p.Context.Administrator {
writeForbiddenError(w)
return
}
// can only update your own account unless you are an admin
if len(user.Email) == 0 {
writeBadRequestError(w, method, "missing email")
return
}
tx, err := request.Db.Beginx()
if err != nil {
writeTransactionError(w, method, err)
return
}
p.Context.Transaction = tx
user.RefID = userID
user.Initials = stringutil.MakeInitials(user.Firstname, user.Lastname)
err = p.UpdateUser(user)
if err != nil {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err)
return
}
// Now we update user roles for this organization.
// That means we have to first find their account record
// for this organization.
account, err := p.GetUserAccount(userID)
if err != nil {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err)
return
}
account.Editor = user.Editor
account.Admin = user.Admin
account.Active = user.Active
err = p.UpdateAccount(account)
if err != nil {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err)
return
}
p.RecordEvent(entity.EventTypeUserUpdate)
log.IfErr(tx.Commit())
json, err := json.Marshal(user)
if err != nil {
writeJSONMarshalError(w, method, "user", err)
return
}
writeSuccessBytes(w, json)
}
// ChangeUserPassword accepts password change from within the app.
func ChangeUserPassword(w http.ResponseWriter, r *http.Request) {
method := "ChangeUserPassword"
p := request.GetPersister(r)
params := mux.Vars(r)
userID := params["userID"]
if len(userID) == 0 {
writeMissingDataError(w, method, "user id")
return
}
defer streamutil.Close(r.Body)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
writePayloadError(w, method, err)
return
}
newPassword := string(body)
// can only update your own account unless you are an admin
if userID != p.Context.UserID && !p.Context.Administrator {
writeForbiddenError(w)
return
}
tx, err := request.Db.Beginx()
if err != nil {
writeTransactionError(w, method, err)
return
}
p.Context.Transaction = tx
user, err := p.GetUser(userID)
if err != nil {
writeGeneralSQLError(w, method, err)
return
}
user.Salt = secrets.GenerateSalt()
err = p.UpdateUserPassword(userID, user.Salt, secrets.GeneratePassword(newPassword, user.Salt))
if err != nil {
writeGeneralSQLError(w, method, err)
return
}
log.IfErr(tx.Commit())
writeSuccessEmptyJSON(w)
}
// GetUserFolderPermissions returns folder permission for authenticated user.
func GetUserFolderPermissions(w http.ResponseWriter, r *http.Request) {
method := "ChangeUserPassword"
p := request.GetPersister(r)
params := mux.Vars(r)
userID := params["userID"]
if userID != p.Context.UserID {
writeUnauthorizedError(w)
return
}
roles, err := p.GetUserLabelRoles()
if err == sql.ErrNoRows {
err = nil
roles = []entity.LabelRole{}
}
if err != nil {
writeServerError(w, method, err)
return
}
json, err := json.Marshal(roles)
if err != nil {
writeJSONMarshalError(w, method, "roles", err)
return
}
writeSuccessBytes(w, json)
}
// ForgotUserPassword initiates the change password procedure.
// Generates a reset token and sends email to the user.
// User has to click link in email and then provide a new password.
func ForgotUserPassword(w http.ResponseWriter, r *http.Request) {
method := "ForgotUserPassword"
p := request.GetPersister(r)
defer streamutil.Close(r.Body)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
writeBadRequestError(w, method, "cannot ready payload")
return
}
user := new(entity.User)
err = json.Unmarshal(body, &user)
if err != nil {
writePayloadError(w, method, err)
return
}
tx, err := request.Db.Beginx()
if err != nil {
writeTransactionError(w, method, err)
return
}
p.Context.Transaction = tx
token := secrets.GenerateSalt()
err = p.ForgotUserPassword(user.Email, token)
if err != nil && err != sql.ErrNoRows {
log.IfErr(tx.Rollback())
writeGeneralSQLError(w, method, err)
return
}
if err == sql.ErrNoRows {
writeServerError(w, method, fmt.Errorf("User %s not found for password reset process", user.Email))
return
}
log.IfErr(tx.Commit())
appURL := p.Context.GetAppURL(fmt.Sprintf("auth/reset/%s", token))
go mail.PasswordReset(user.Email, appURL)
writeSuccessEmptyJSON(w)
}
// ResetUserPassword stores the newly chosen password for the user.
func ResetUserPassword(w http.ResponseWriter, r *http.Request) {
api.SetJSONResponse(w)
p := request.GetPersister(r)
params := mux.Vars(r)
token := params["token"]
if len(token) == 0 {
log.ErrorString("ResetUserPassword - missing password reset token")
api.WriteErrorBadRequest(w, "missing password reset token")
return
}
defer streamutil.Close(r.Body)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
api.WriteErrorBadRequest(w, "Bad payload")
log.Error("ResetUserPassword - failed to read body", err)
return
}
newPassword := string(body)
tx, err := request.Db.Beginx()
if err != nil {
api.WriteError(w, err)
log.Error("ResetUserPassword - failed to get DB transaction", err)
return
}
p.Context.Transaction = tx
user, err := p.GetUserByToken(token)
if err != nil {
api.WriteError(w, err)
log.Error("ResetUserPassword - unable to retrieve user", err)
return
}
user.Salt = secrets.GenerateSalt()
err = p.UpdateUserPassword(user.RefID, user.Salt, secrets.GeneratePassword(newPassword, user.Salt))
if err != nil {
log.IfErr(tx.Rollback())
api.WriteError(w, err)
log.Error("ResetUserPassword - failed to change password", err)
return
}
p.RecordEvent(entity.EventTypeUserPasswordReset)
log.IfErr(tx.Commit())
_, err = w.Write([]byte("{}"))
log.IfErr(err)
}
// GetSecuredUser wipes credentials.
func GetSecuredUser(p request.Persister, orgID, user string) (u entity.User, err error) {
u, err = p.GetUser(user)
AttachUserAccounts(p, orgID, &u)
return
}
// AttachUserAccounts ...
func AttachUserAccounts(p request.Persister, orgID string, user *entity.User) {
user.ProtectSecrets()
a, err := p.GetUserAccounts(user.RefID)
if err != nil {
log.Error("Unable to fetch user accounts", err)
return
}
user.Accounts = a
user.Editor = false
user.Admin = false
user.Active = false
for _, account := range user.Accounts {
if account.OrgID == orgID {
user.Admin = account.Admin
user.Editor = account.Editor
user.Active = account.Active
break
}
}
}
func writeUsers(w http.ResponseWriter, u []entity.User) {
if u == nil {
u = []entity.User{}
}
j, err := json.Marshal(u)
if err != nil {
log.Error("unable to writeUsers", err)
writeServerError(w, "unabe to writeUsers", err)
return
}
writeSuccessBytes(w, j)
}

View file

@ -1,42 +0,0 @@
// Source: https://github.com/lib/pq/blob/b269bd035a727d6c1081f76e7a239a1b00674c40/encode.go#L521
//
// Copyright (c) 2011-2013, 'pq' Contributors Portions Copyright (C) 2011 Blake Mizerany
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// Package entity provides types that mirror database tables.
package entity
import (
"database/sql/driver"
"time"
)
// NullTime represents a time.Time that may be null. NullTime implements the
// sql.Scanner interface so it can be used as a scan destination, similar to
// sql.NullString.
type NullTime struct {
Time time.Time
Valid bool // Valid is true if Time is not NULL
}
// Scan implements the Scanner interface.
func (nt *NullTime) Scan(value interface{}) error {
nt.Time, nt.Valid = value.(time.Time)
return nil
}
// Value implements the driver Valuer interface.
func (nt NullTime) Value() (driver.Value, error) {
if !nt.Valid {
return nil, nil
}
return nt.Time, nil
}

View file

@ -1,567 +0,0 @@
// 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 entity provides types that mirror database tables.
package entity
import (
"fmt"
"strings"
"time"
)
// BaseEntity contains the database fields used in every table.
type BaseEntity struct {
ID uint64 `json:"-"`
RefID string `json:"id"`
Created time.Time `json:"created"`
Revised time.Time `json:"revised"`
}
// BaseEntityObfuscated is a mirror of BaseEntity,
// but with the fields invisible to JSON.
type BaseEntityObfuscated struct {
ID uint64 `json:"-"`
RefID string `json:"-"`
Created time.Time `json:"-"`
Revised time.Time `json:"-"`
}
// User defines a login.
type User struct {
BaseEntity
Firstname string `json:"firstname"`
Lastname string `json:"lastname"`
Email string `json:"email"`
Initials string `json:"initials"`
Active bool `json:"active"`
Editor bool `json:"editor"`
Admin bool `json:"admin"`
Global bool `json:"global"`
Password string `json:"-"`
Salt string `json:"-"`
Reset string `json:"-"`
Accounts []Account `json:"accounts"`
}
// ProtectSecrets blanks sensitive data.
func (user *User) ProtectSecrets() {
user.Password = ""
user.Salt = ""
user.Reset = ""
}
// Fullname returns Firstname + Lastname.
func (user *User) Fullname() string {
return fmt.Sprintf("%s %s", user.Firstname, user.Lastname)
}
// GetAccount returns matching org account using orgID
func (user *User) GetAccount(orgID string) (a Account, found bool) {
for _, a := range user.Accounts {
if a.OrgID == orgID {
return a, true
}
}
return a, false
}
// Organization defines a company that uses this app.
type Organization struct {
BaseEntity
Company string `json:"-"`
Title string `json:"title"`
Message string `json:"message"`
URL string `json:"url"`
Domain string `json:"domain"`
Email string `json:"email"`
AllowAnonymousAccess bool `json:"allowAnonymousAccess"`
AuthProvider string `json:"authProvider"`
AuthConfig string `json:"authConfig"`
ConversionEndpoint string `json:"conversionEndpoint"`
Serial string `json:"-"`
Active bool `json:"-"`
}
// Account links a User to an Organization.
type Account struct {
BaseEntity
Admin bool `json:"admin"`
Editor bool `json:"editor"`
UserID string `json:"userId"`
OrgID string `json:"orgId"`
Company string `json:"company"`
Title string `json:"title"`
Message string `json:"message"`
Domain string `json:"domain"`
Active bool `json:"active"`
}
// Label defines a container for documents.
type Label struct {
BaseEntity
Name string `json:"name"`
OrgID string `json:"orgId"`
UserID string `json:"userId"`
Type FolderType `json:"folderType"`
}
// FolderType determines folder visibility.
type FolderType int
const (
// FolderTypePublic can be seen by anyone
FolderTypePublic FolderType = 1
// FolderTypePrivate can only be seen by the person who owns it
FolderTypePrivate FolderType = 2
// FolderTypeRestricted can be seen by selected users
FolderTypeRestricted FolderType = 3
)
// IsPublic means the folder can be seen by anyone.
func (l *Label) IsPublic() bool {
return l.Type == FolderTypePublic
}
// IsPrivate means the folder can only be seen by the person who owns it.
func (l *Label) IsPrivate() bool {
return l.Type == FolderTypePrivate
}
// IsRestricted means the folder can be seen by selected users.
func (l *Label) IsRestricted() bool {
return l.Type == FolderTypeRestricted
}
// LabelRole determines user permissions for a folder.
type LabelRole struct {
BaseEntityObfuscated
OrgID string `json:"-"`
LabelID string `json:"folderId"`
UserID string `json:"userId"`
CanView bool `json:"canView"`
CanEdit bool `json:"canEdit"`
}
// Document represents a document.
type Document struct {
BaseEntity
OrgID string `json:"orgId"`
LabelID string `json:"folderId"`
UserID string `json:"userId"`
Job string `json:"job"`
Location string `json:"location"`
Title string `json:"name"`
Excerpt string `json:"excerpt"`
Slug string `json:"-"`
Tags string `json:"tags"`
Template bool `json:"template"`
Layout string `json:"layout"`
}
// SetDefaults ensures on blanks and cleans.
func (d *Document) SetDefaults() {
d.Title = strings.TrimSpace(d.Title)
if len(d.Title) == 0 {
d.Title = "Document"
}
}
// Attachment represents an attachment to a document.
type Attachment struct {
BaseEntity
OrgID string `json:"orgId"`
DocumentID string `json:"documentId"`
Job string `json:"job"`
FileID string `json:"fileId"`
Filename string `json:"filename"`
Data []byte `json:"-"`
Extension string `json:"extension"`
}
// Page represents a section within a document.
type Page struct {
BaseEntity
OrgID string `json:"orgId"`
DocumentID string `json:"documentId"`
UserID string `json:"userId"`
ContentType string `json:"contentType"`
PageType string `json:"pageType"`
BlockID string `json:"blockId"`
Level uint64 `json:"level"`
Sequence float64 `json:"sequence"`
Title string `json:"title"`
Body string `json:"body"`
Revisions uint64 `json:"revisions"`
}
// SetDefaults ensures no blank values.
func (p *Page) SetDefaults() {
if len(p.ContentType) == 0 {
p.ContentType = "wysiwyg"
}
p.Title = strings.TrimSpace(p.Title)
}
// IsSectionType tells us that page is "words"
func (p *Page) IsSectionType() bool {
return p.PageType == "section"
}
// IsTabType tells us that page is "SaaS data embed"
func (p *Page) IsTabType() bool {
return p.PageType == "tab"
}
// PageMeta holds raw page data that is used to
// render the actual page data.
type PageMeta struct {
ID uint64 `json:"id"`
Created time.Time `json:"created"`
Revised time.Time `json:"revised"`
OrgID string `json:"orgId"`
UserID string `json:"userId"`
DocumentID string `json:"documentId"`
PageID string `json:"pageId"`
RawBody string `json:"rawBody"` // a blob of data
Config string `json:"config"` // JSON based custom config for this type
ExternalSource bool `json:"externalSource"` // true indicates data sourced externally
}
// SetDefaults ensures no blank values.
func (p *PageMeta) SetDefaults() {
if len(p.Config) == 0 {
p.Config = "{}"
}
}
// Revision holds the previous version of a Page.
type Revision struct {
BaseEntity
OrgID string `json:"orgId"`
DocumentID string `json:"documentId"`
PageID string `json:"pageId"`
OwnerID string `json:"ownerId"`
UserID string `json:"userId"`
ContentType string `json:"contentType"`
PageType string `json:"pageType"`
Title string `json:"title"`
Body string `json:"body"`
RawBody string `json:"rawBody"`
Config string `json:"config"`
Email string `json:"email"`
Firstname string `json:"firstname"`
Lastname string `json:"lastname"`
Initials string `json:"initials"`
Revisions int `json:"revisions"`
}
// Block represents a section that has been published as a reusable content block.
type Block struct {
BaseEntity
OrgID string `json:"orgId"`
LabelID string `json:"folderId"`
UserID string `json:"userId"`
ContentType string `json:"contentType"`
PageType string `json:"pageType"`
Title string `json:"title"`
Body string `json:"body"`
Excerpt string `json:"excerpt"`
RawBody string `json:"rawBody"` // a blob of data
Config string `json:"config"` // JSON based custom config for this type
ExternalSource bool `json:"externalSource"` // true indicates data sourced externally
Used uint64 `json:"used"`
Firstname string `json:"firstname"`
Lastname string `json:"lastname"`
}
// DocumentMeta details who viewed the document.
type DocumentMeta struct {
Viewers []DocumentMetaViewer `json:"viewers"`
Editors []DocumentMetaEditor `json:"editors"`
}
// DocumentMetaViewer contains the "view" metatdata content.
type DocumentMetaViewer struct {
UserID string `json:"userId"`
Created time.Time `json:"created"`
Firstname string `json:"firstname"`
Lastname string `json:"lastname"`
}
// DocumentMetaEditor contains the "edit" metatdata content.
type DocumentMetaEditor struct {
PageID string `json:"pageId"`
UserID string `json:"userId"`
Action string `json:"action"`
Created time.Time `json:"created"`
Firstname string `json:"firstname"`
Lastname string `json:"lastname"`
}
// Search holds raw search results.
type Search struct {
ID string `json:"id"`
Created time.Time `json:"created"`
Revised time.Time `json:"revised"`
OrgID string
DocumentID string
Level uint64
Sequence float64
DocumentTitle string
Slug string
PageTitle string
Body string
}
// DocumentSearch represents 'presentable' search results.
type DocumentSearch struct {
ID string `json:"id"`
DocumentID string `json:"documentId"`
DocumentTitle string `json:"documentTitle"`
DocumentSlug string `json:"documentSlug"`
DocumentExcerpt string `json:"documentExcerpt"`
Tags string `json:"documentTags"`
PageTitle string `json:"pageTitle"`
LabelID string `json:"folderId"`
LabelName string `json:"folderName"`
FolderSlug string `json:"folderSlug"`
}
// SiteMeta holds information associated with an Organization.
type SiteMeta struct {
OrgID string `json:"orgId"`
Title string `json:"title"`
Message string `json:"message"`
URL string `json:"url"`
AllowAnonymousAccess bool `json:"allowAnonymousAccess"`
AuthProvider string `json:"authProvider"`
AuthConfig string `json:"authConfig"`
Version string `json:"version"`
Edition string `json:"edition"`
Valid bool `json:"valid"`
ConversionEndpoint string `json:"conversionEndpoint"`
}
// Template is used to create a new document.
// Template can consist of content, attachments and
// have associated meta data indentifying author, version
// contact details and more.
type Template struct {
ID string `json:"id"`
Title string `json:"title"`
Description string `json:"description"`
Author string `json:"author"`
Type TemplateType `json:"type"`
Dated time.Time `json:"dated"`
}
// TemplateType determines who can see a template.
type TemplateType int
const (
// TemplateTypePublic means anyone can see the template.
TemplateTypePublic TemplateType = 1
// TemplateTypePrivate means only the owner can see the template.
TemplateTypePrivate TemplateType = 2
// TemplateTypeRestricted means selected users can see the template.
TemplateTypeRestricted TemplateType = 3
)
// IsPublic means anyone can see the template.
func (t *Template) IsPublic() bool {
return t.Type == TemplateTypePublic
}
// IsPrivate means only the owner can see the template.
func (t *Template) IsPrivate() bool {
return t.Type == TemplateTypePrivate
}
// IsRestricted means selected users can see the template.
func (t *Template) IsRestricted() bool {
return t.Type == TemplateTypeRestricted
}
// FolderVisibility details who can see a particular folder
type FolderVisibility struct {
Name string `json:"name"`
LabelID string `json:"folderId"`
Type int `json:"folderType"`
UserID string `json:"userId"`
Firstname string `json:"firstname"`
Lastname string `json:"lastname"`
Email string `json:"email"`
}
// SitemapDocument details a document that can be exposed via Sitemap.
type SitemapDocument struct {
DocumentID string
Document string
FolderID string
Folder string
Revised time.Time
}
// Link defines a reference between a section and another document/section/attachment.
type Link struct {
BaseEntity
OrgID string `json:"orgId"`
FolderID string `json:"folderId"`
UserID string `json:"userId"`
LinkType string `json:"linkType"`
SourceDocumentID string `json:"sourceDocumentId"`
SourcePageID string `json:"sourcePageId"`
TargetDocumentID string `json:"targetDocumentId"`
TargetID string `json:"targetId"`
Orphan bool `json:"orphan"`
}
// LinkCandidate defines a potential link to a document/section/attachment.
type LinkCandidate struct {
RefID string `json:"id"`
LinkType string `json:"linkType"`
FolderID string `json:"folderId"`
DocumentID string `json:"documentId"`
TargetID string `json:"targetId"`
Title string `json:"title"` // what we label the link
Context string `json:"context"` // additional context (e.g. excerpt, parent, file extension)
}
// Pin defines a saved link to a document or space
type Pin struct {
BaseEntity
OrgID string `json:"orgId"`
UserID string `json:"userId"`
FolderID string `json:"folderId"`
DocumentID string `json:"documentId"`
Pin string `json:"pin"`
Sequence int `json:"sequence"`
}
// UserActivity represents an activity undertaken by a user.
type UserActivity struct {
ID uint64 `json:"-"`
OrgID string `json:"orgId"`
UserID string `json:"userId"`
LabelID string `json:"folderId"`
SourceID string `json:"sourceId"`
SourceName string `json:"sourceName"` // e.g. Document or Space name
SourceType ActivitySourceType `json:"sourceType"`
ActivityType ActivityType `json:"activityType"`
Created time.Time `json:"created"`
}
// ActivitySourceType details where the activity occured.
type ActivitySourceType int
// ActivityType determines type of user activity
type ActivityType int
const (
// ActivitySourceTypeSpace indicates activity against a space.
ActivitySourceTypeSpace ActivitySourceType = 1
// ActivitySourceTypeDocument indicates activity against a document.
ActivitySourceTypeDocument ActivitySourceType = 2
)
const (
// ActivityTypeCreated records user document creation
ActivityTypeCreated ActivityType = 1
// ActivityTypeRead states user has read document
ActivityTypeRead ActivityType = 2
// ActivityTypeEdited states user has editing document
ActivityTypeEdited ActivityType = 3
// ActivityTypeDeleted records user deleting space/document
ActivityTypeDeleted ActivityType = 4
// ActivityTypeArchived records user archiving space/document
ActivityTypeArchived ActivityType = 5
// ActivityTypeApproved records user approval of document
ActivityTypeApproved ActivityType = 6
// ActivityTypeReverted records user content roll-back to previous version
ActivityTypeReverted ActivityType = 7
// ActivityTypePublishedTemplate records user creating new document template
ActivityTypePublishedTemplate ActivityType = 8
// ActivityTypePublishedBlock records user creating reusable content block
ActivityTypePublishedBlock ActivityType = 9
// ActivityTypeFeedback records user providing document feedback
ActivityTypeFeedback ActivityType = 10
)
// AppEvent represents an event initiated by a user.
type AppEvent struct {
ID uint64 `json:"-"`
OrgID string `json:"orgId"`
UserID string `json:"userId"`
Type string `json:"eventType"`
IP string `json:"ip"`
Created time.Time `json:"created"`
}
// EventType defines valid event entry types
type EventType string
const (
EventTypeDocumentAdd EventType = "added-document"
EventTypeDocumentUpload EventType = "uploaded-document"
EventTypeDocumentView EventType = "viewed-document"
EventTypeDocumentUpdate EventType = "updated-document"
EventTypeDocumentDelete EventType = "removed-document"
EventTypeDocumentRevisions EventType = "viewed-document-revisions"
EventTypeSpaceAdd EventType = "added-space"
EventTypeSpaceUpdate EventType = "updated-space"
EventTypeSpaceDelete EventType = "removed-space"
EventTypeSpacePermission EventType = "changed-space-permissions"
EventTypeSpaceJoin EventType = "joined-space"
EventTypeSpaceInvite EventType = "invited-space"
EventTypeSectionAdd EventType = "added-document-section"
EventTypeSectionUpdate EventType = "updated-document-section"
EventTypeSectionDelete EventType = "removed-document-section"
EventTypeSectionRollback EventType = "rolled-back-document-section"
EventTypeSectionResequence EventType = "resequenced-document-section"
EventTypeSectionCopy EventType = "copied-document-section"
EventTypeAttachmentAdd EventType = "added-attachment"
EventTypeAttachmentDownload EventType = "downloaded-attachment"
EventTypeAttachmentDelete EventType = "removed-attachment"
EventTypePinAdd EventType = "added-pin"
EventTypePinDelete EventType = "removed-pin"
EventTypePinResequence EventType = "resequenced-pin"
EventTypeBlockAdd EventType = "added-reusable-block"
EventTypeBlockUpdate EventType = "updated-reusable-block"
EventTypeBlockDelete EventType = "removed-reusable-block"
EventTypeTemplateAdd EventType = "added-document-template"
EventTypeTemplateUse EventType = "used-document-template"
EventTypeUserAdd EventType = "added-user"
EventTypeUserUpdate EventType = "updated-user"
EventTypeUserDelete EventType = "removed-user"
EventTypeUserPasswordReset EventType = "reset-user-password"
EventTypeAccountAdd EventType = "added-account"
EventTypeSystemLicense EventType = "changed-system-license"
EventTypeSystemAuth EventType = "changed-system-auth"
EventTypeSystemSMTP EventType = "changed-system-smtp"
EventTypeSessionStart EventType = "started-session"
EventTypeSearch EventType = "searched"
)

View file

@ -1,106 +0,0 @@
// 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 mail
import (
//"fmt"
"errors"
"net/smtp"
"os"
"strings"
"testing"
"github.com/documize/community/core/log"
)
func TestMail(t *testing.T) {
sender := "sender@documize.com"
recipient := "recipient@documize.com"
contains := []string{}
var returnError error
smtpSendMail = func(addr string, a smtp.Auth, from string, to []string, msg []byte) error {
if addr != getHost() {
t.Error("host incorrect:" + addr)
}
if from != "noreply@documize.com" && from != "hello@documize.com" {
t.Error("sender incorrect:" + from)
}
if len(to) == 0 {
t.Error("no recipient")
} else {
if to[0] != recipient {
t.Error("recipient incorrect:" + to[0])
}
}
for _, cont := range contains {
if !strings.Contains(string(msg), cont) {
t.Error("body does not contain:`" + cont + "` html:" + string(msg))
}
}
//fmt.Println("DEBUG testSendMail", addr, a, from, to)
return returnError
}
err := os.Chdir("..")
if err != nil {
t.Error(err)
}
url := "https://documize.com"
contains = []string{url, sender}
InviteNewUser(recipient, sender, url, "username", "password")
contains = []string{url, "Your colleague"}
InviteNewUser(recipient, "", url, "username", "password")
contains = []string{url, sender}
InviteExistingUser(recipient, sender, url)
contains = []string{url, "Your colleague"}
InviteExistingUser(recipient, "", url)
contains = []string{url}
PasswordReset(recipient, url)
contains = []string{url, sender, "folder", "intro"}
ShareFolderExistingUser(recipient, sender, url, "folder", "intro")
contains = []string{url, "Your colleague", "folder", "intro"}
ShareFolderExistingUser(recipient, "", url, "folder", "intro")
contains = []string{url, sender, "folder", "invitationMessage string"}
ShareFolderNewUser(recipient, sender, url, "folder", "invitationMessage string")
contains = []string{url, "Your colleague", "folder", "invitationMessage string"}
ShareFolderNewUser(recipient, "", url, "folder", "invitationMessage string")
contains = []string{url}
returnError = errors.New("test error")
log.TestIfErr = true
InviteNewUser(recipient, sender, url, "username", "password")
if log.TestIfErr {
t.Error("did not log an error when it should have")
}
log.TestIfErr = true
InviteExistingUser(recipient, sender, url)
if log.TestIfErr {
t.Error("did not log an error when it should have")
}
log.TestIfErr = true
PasswordReset(recipient, url)
if log.TestIfErr {
t.Error("did not log an error when it should have")
}
log.TestIfErr = true
ShareFolderExistingUser(recipient, sender, url, "folder", "intro")
if log.TestIfErr {
t.Error("did not log an error when it should have")
}
log.TestIfErr = true
ShareFolderNewUser(recipient, sender, url, "folder", "invitationMessage string")
if log.TestIfErr {
t.Error("did not log an error when it should have")
}
}
// TODO: no tests (yet) for smtp.go as this is akin to a vendored package

View file

@ -21,9 +21,9 @@ import (
"github.com/documize/community/core/api/convert/documizeapi" "github.com/documize/community/core/api/convert/documizeapi"
"github.com/documize/community/core/api/convert/html" "github.com/documize/community/core/api/convert/html"
"github.com/documize/community/core/api/convert/md" "github.com/documize/community/core/api/convert/md"
"github.com/documize/community/core/api/request"
api "github.com/documize/community/core/convapi" api "github.com/documize/community/core/convapi"
"github.com/documize/community/core/log" "github.com/documize/community/core/log"
"github.com/documize/community/domain"
"github.com/documize/glick" "github.com/documize/glick"
) )
@ -48,9 +48,9 @@ func (i errorLog) Write(b []byte) (int, error) {
// Lib holds a pointer to the global glick Library for the Documize app. // Lib holds a pointer to the global glick Library for the Documize app.
var Lib *glick.Library var Lib *glick.Library
// LibSetup configures the global library at Lib, // Setup configures the global library at Lib,
// largely based on the "config.json" file. It should be called only once. // largely based on the "config.json" file. It should be called only once.
func LibSetup() error { func Setup(s *domain.Store) error {
if insecure == "true" { if insecure == "true" {
glick.InsecureSkipVerifyTLS = true glick.InsecureSkipVerifyTLS = true
} }
@ -100,7 +100,7 @@ func LibSetup() error {
var json = make([]byte, 0) var json = make([]byte, 0)
if PluginFile == "DB" { if PluginFile == "DB" {
json = []byte(request.ConfigString("FILEPLUGINS", "")) json = []byte(s.Setting.Get("FILEPLUGINS", ""))
if len(bytes.TrimSpace(json)) == 0 { if len(bytes.TrimSpace(json)) == 0 {
return nil // don't fail if the DB does not exist yet return nil // don't fail if the DB does not exist yet
} }

View file

@ -1,8 +0,0 @@
package api
import (
"github.com/documize/community/core/env"
)
// Runtime is code repair in progress
var Runtime env.Runtime

View file

@ -1,155 +0,0 @@
// 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/streamutil"
"github.com/pkg/errors"
)
// 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, active, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?)")
defer streamutil.Close(stmt)
if err != nil {
err = errors.Wrap(err, "unable to prepare insert for account")
return
}
_, err = stmt.Exec(account.RefID, account.OrgID, account.UserID, account.Admin, account.Editor, account.Active, account.Created, account.Revised)
if err != nil {
err = errors.Wrap(err, "unable to execute insert for account")
return
}
return
}
// GetUserAccount returns the database account record corresponding to the given userID, using the client's current organizaion.
func (p *Persister) GetUserAccount(userID string) (account entity.Account, err error) {
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=?")
defer streamutil.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 = 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 a.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 = 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 a.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
}
// CountOrgAccounts returns the numnber of active user accounts for specified organization.
func (p *Persister) CountOrgAccounts() (c int) {
row := Db.QueryRow("SELECT count(*) FROM account WHERE orgid=? AND active=1", p.Context.OrgID)
err := row.Scan(&c)
if err != nil && err != sql.ErrNoRows {
log.Error(p.Base.SQLSelectError("CountOrgAccounts", p.Context.OrgID), err)
return 0
}
if err == sql.ErrNoRows {
return 0
}
return
}
// UpdateAccount updates the database record for the given account to the given values.
func (p *Persister) UpdateAccount(account entity.Account) (err error) {
account.Revised = time.Now().UTC()
stmt, err := p.Context.Transaction.PrepareNamed("UPDATE account SET userid=:userid, admin=:admin, editor=:editor, active=:active, revised=:revised WHERE orgid=:orgid AND refid=:refid")
defer streamutil.Close(stmt)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare update for account %s", account.RefID), err)
return
}
_, err = stmt.Exec(&account)
if err != sql.ErrNoRows && err != nil {
log.Error(fmt.Sprintf("Unable to execute update for account %s", account.RefID), err)
return
}
return
}
// HasOrgAccount returns if the given orgID has valid userID.
func (p *Persister) HasOrgAccount(orgID, userID string) bool {
row := Db.QueryRow("SELECT count(*) FROM account WHERE orgid=? and userid=?", orgID, userID)
var count int
err := row.Scan(&count)
if err != nil && err != sql.ErrNoRows {
log.Error(p.Base.SQLSelectError("HasOrgAccount", userID), err)
return false
}
if err == sql.ErrNoRows {
return false
}
if count == 0 {
return false
}
return true
}
// DeleteAccount deletes the database record in the account table for user ID.
func (p *Persister) DeleteAccount(ID string) (rows int64, err error) {
return p.Base.DeleteConstrained(p.Context.Transaction, "account", p.Context.OrgID, ID)
}

View file

@ -1,139 +0,0 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
package request
/* TODO(Elliott)
import (
"github.com/documize/community/documize/api/entity"
"github.com/documize/community/core/environment"
"testing"
)
const testAcc = "TestAccount"
func testAddAccount(t *testing.T, p *Persister) entity.Account {
acc := entity.Account{
BaseEntity: entity.BaseEntity{RefID: testAcc},
Admin: true, // bool `json:"admin"`
Editor: true, // bool `json:"editor"`
UserID: p.Context.UserID, // string `json:"userId"`
OrgID: p.Context.OrgID, // string `json:"orgId"`
Company: "testCompany", // string `json:"company"`
Title: "testTitle", // string `json:"title"`
Message: "testMessage", // string `json:"message"`
Domain: "testDomain", // string `json:"domain"`
}
err := p.AddAccount(acc)
if err != nil {
t.Error(err)
t.Fail()
}
p.testCommit(t)
return acc
}
func testDeleteAccount(t *testing.T, p *Persister) {
p.testNewTx(t) // so that we can use it reliably in defer
rows, err := p.DeleteAccount(testAcc)
if err != nil {
t.Error(err)
t.Fail()
}
if rows != 1 {
t.Errorf("expected 1 row deleted got %d", rows)
t.Fail()
}
p.testCommit(t)
}
func TestAccount(t *testing.T) {
environment.Parse("db")
p := newTestPersister(t)
defer deleteTestAuditTrail(t, p)
org := testAddOrganization(t, p)
defer testDeleteOrganization(t, p)
user := testAddUser(t, p)
defer testDeleteUser(t, p)
acc := testAddAccount(t, p)
defer testDeleteAccount(t, p)
err := p.AddAccount(entity.Account{
BaseEntity: entity.BaseEntity{RefID: acc.RefID},
OrgID: org.RefID,
UserID: user.RefID,
})
if err == nil {
t.Error("did not error as expected on duplicate record")
}
p.testRollback(t)
acc2, err := p.GetUserAccount(user.RefID)
if err != nil {
t.Error(err)
}
if acc.Company != acc2.Company {
t.Errorf("bad data returned want: `%s` got: `%s`", acc.Company, acc2.Company)
}
p.testRollback(t)
gua, err := p.GetUserAccounts(user.RefID)
if err != nil {
t.Error(err)
}
if len(gua) != 1 {
t.Errorf("length is %d not 1 ", len(gua))
} else {
if acc.Company != gua[0].Company {
t.Errorf("bad data returned want: `%s` got: `%s`", acc.Company, gua[0].Company)
}
}
p.testRollback(t)
gabo, err := p.GetAccountsByOrg()
if err != nil {
t.Error(err)
}
if len(gabo) != 1 {
t.Errorf("length is %d not 1 ", len(gabo))
} else {
if acc.Company != gabo[0].Company {
t.Errorf("bad data returned want: `%s` got: `%s`", acc.Company, gabo[0].Company)
}
}
p.testRollback(t)
if p.HasOrgAccount("XXXXXXXX", "YYYYYY") {
t.Error("found account where there should not be one")
}
if !p.HasOrgAccount(org.RefID, user.RefID) {
t.Error("did not find account where there should be one")
}
p.testRollback(t)
acc.Admin = false
err = p.UpdateAccount(acc)
if err != nil {
t.Error(err)
}
p.testCommit(t)
acc3, err := p.GetUserAccount(user.RefID)
if err != nil {
t.Error(err)
}
if acc3.Admin {
t.Errorf("bad data returned")
}
p.testRollback(t)
}
*/

View file

@ -1,71 +0,0 @@
// 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/endpoint/models"
"github.com/documize/community/core/api/entity"
"github.com/documize/community/core/log"
"github.com/documize/community/core/streamutil"
)
// RecordUserActivity logs user initiated data changes.
func (p *Persister) RecordUserActivity(activity entity.UserActivity) (err error) {
activity.OrgID = p.Context.OrgID
activity.UserID = p.Context.UserID
activity.Created = time.Now().UTC()
stmt, err := p.Context.Transaction.Preparex("INSERT INTO useractivity (orgid, userid, labelid, sourceid, sourcetype, activitytype, created) VALUES (?, ?, ?, ?, ?, ?, ?)")
defer streamutil.Close(stmt)
if err != nil {
log.Error("Unable to prepare insert RecordUserActivity", err)
return
}
_, err = stmt.Exec(activity.OrgID, activity.UserID, activity.LabelID, activity.SourceID, activity.SourceType, activity.ActivityType, activity.Created)
if err != nil {
log.Error("Unable to execute insert RecordUserActivity", err)
return
}
return
}
// GetDocumentActivity returns the metadata for a specified document.
func (p *Persister) GetDocumentActivity(id string) (a []models.DocumentActivity, err error) {
s := `SELECT a.id, a.created, a.orgid, IFNULL(a.userid, '') AS userid, a.labelid, a.sourceid as documentid, a.activitytype,
IFNULL(u.firstname, 'Anonymous') AS firstname, IFNULL(u.lastname, 'Viewer') AS lastname
FROM useractivity a
LEFT JOIN user u ON a.userid=u.refid
WHERE a.orgid=? AND a.sourceid=? AND a.sourcetype=2
AND a.userid != '0' AND a.userid != ''
ORDER BY a.created DESC`
err = Db.Select(&a, s, p.Context.OrgID, id)
if len(a) == 0 {
a = []models.DocumentActivity{}
}
if err != nil && err != sql.ErrNoRows {
log.Error(fmt.Sprintf("Unable to execute GetDocumentActivity %s", id), err)
return
}
return
}

View file

@ -1,102 +0,0 @@
// 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/streamutil"
)
// 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 streamutil.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
}
// GetAttachment returns the database attachment record specified by the parameters.
func (p *Persister) GetAttachment(orgID, attachmentID string) (attachment entity.Attachment, err error) {
stmt, err := Db.Preparex("SELECT id, refid, orgid, documentid, job, fileid, filename, data, extension, created, revised FROM attachment WHERE orgid=? and refid=?")
defer streamutil.Close(stmt)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare select for attachment %s", attachmentID), err)
return
}
err = stmt.Get(&attachment, orgID, attachmentID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute select for attachment %s", attachmentID), 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 = 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 = 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) {
rows, _ = p.Base.DeleteConstrained(p.Context.Transaction, "attachment", p.Context.OrgID, id)
// Mark references to this document as orphaned
err = p.MarkOrphanAttachmentLink(id)
return
}

View file

@ -1,106 +0,0 @@
// 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)
}
*/

View file

@ -1,166 +0,0 @@
// 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/streamutil"
"github.com/jmoiron/sqlx"
)
// AddBlock saves reusable content block.
func (p *Persister) AddBlock(b entity.Block) (err error) {
b.OrgID = p.Context.OrgID
b.UserID = p.Context.UserID
b.Created = time.Now().UTC()
b.Revised = time.Now().UTC()
stmt, err := p.Context.Transaction.Preparex("INSERT INTO block (refid, orgid, labelid, userid, contenttype, pagetype, title, body, excerpt, rawbody, config, externalsource, used, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
defer streamutil.Close(stmt)
if err != nil {
log.Error("Unable to prepare insert AddBlock", err)
return
}
_, err = stmt.Exec(b.RefID, b.OrgID, b.LabelID, b.UserID, b.ContentType, b.PageType, b.Title, b.Body, b.Excerpt, b.RawBody, b.Config, b.ExternalSource, b.Used, b.Created, b.Revised)
if err != nil {
log.Error("Unable to execute insert AddBlock", err)
return
}
return
}
// GetBlock returns requested reusable content block.
func (p *Persister) GetBlock(id string) (b entity.Block, err error) {
stmt, err := Db.Preparex("SELECT a.id, a.refid, a.orgid, a.labelid, a.userid, a.contenttype, a.pagetype, a.title, a.body, a.excerpt, a.rawbody, a.config, a.externalsource, a.used, a.created, a.revised, b.firstname, b.lastname FROM block a LEFT JOIN user b ON a.userid = b.refid WHERE a.orgid=? AND a.refid=?")
defer streamutil.Close(stmt)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare select GetBlock %s", id), err)
return
}
err = stmt.Get(&b, p.Context.OrgID, id)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute select GetBlock %s", id), err)
return
}
return
}
// GetBlocksForSpace returns all reusable content scoped to given space.
func (p *Persister) GetBlocksForSpace(labelID string) (b []entity.Block, err error) {
err = Db.Select(&b, "SELECT a.id, a.refid, a.orgid, a.labelid, a.userid, a.contenttype, a.pagetype, a.title, a.body, a.excerpt, a.rawbody, a.config, a.externalsource, a.used, a.created, a.revised, b.firstname, b.lastname FROM block a LEFT JOIN user b ON a.userid = b.refid WHERE a.orgid=? AND a.labelid=? ORDER BY a.title", p.Context.OrgID, labelID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute select GetBlocksForSpace org %s and label %s", p.Context.OrgID, labelID), err)
return
}
return
}
// IncrementBlockUsage increments usage counter for content block.
func (p *Persister) IncrementBlockUsage(id string) (err error) {
stmt, err := p.Context.Transaction.Preparex("UPDATE block SET used=used+1, revised=? WHERE orgid=? AND refid=?")
defer streamutil.Close(stmt)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare update IncrementBlockUsage id %s", id), err)
return
}
_, err = stmt.Exec(time.Now().UTC(), p.Context.OrgID, id)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute IncrementBlockUsage id %s", id), err)
return
}
return
}
// DecrementBlockUsage decrements usage counter for content block.
func (p *Persister) DecrementBlockUsage(id string) (err error) {
stmt, err := p.Context.Transaction.Preparex("UPDATE block SET used=used-1, revised=? WHERE orgid=? AND refid=?")
defer streamutil.Close(stmt)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare update DecrementBlockUsage id %s", id), err)
return
}
_, err = stmt.Exec(time.Now().UTC(), p.Context.OrgID, id)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute DecrementBlockUsage id %s", id), err)
return
}
return
}
// RemoveBlockReference clears page.blockid for given blockID.
func (p *Persister) RemoveBlockReference(id string) (err error) {
stmt, err := p.Context.Transaction.Preparex("UPDATE page SET blockid='', revised=? WHERE orgid=? AND blockid=?")
defer streamutil.Close(stmt)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare update RemoveBlockReference id %s", id), err)
return
}
_, err = stmt.Exec(time.Now().UTC(), p.Context.OrgID, id)
// skip no rows affected
if err == sql.ErrNoRows {
return
}
if err != nil {
log.Error(fmt.Sprintf("Unable to execute RemoveBlockReference id %s", id), err)
return
}
return
}
// UpdateBlock updates existing reusable content block item.
func (p *Persister) UpdateBlock(b entity.Block) (err error) {
b.Revised = time.Now().UTC()
var stmt *sqlx.NamedStmt
stmt, err = p.Context.Transaction.PrepareNamed("UPDATE block SET title=:title, body=:body, excerpt=:excerpt, rawbody=:rawbody, config=:config, revised=:revised WHERE orgid=:orgid AND refid=:refid")
defer streamutil.Close(stmt)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare update UpdateBlock %s", b.RefID), err)
return
}
_, err = stmt.Exec(&b)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute update UpdateBlock %s", b.RefID), err)
return
}
return
}
// DeleteBlock removes reusable content block from database.
func (p *Persister) DeleteBlock(id string) (rows int64, err error) {
return p.Base.DeleteConstrained(p.Context.Transaction, "block", p.Context.OrgID, id)
}

View file

@ -1,152 +0,0 @@
// 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/streamutil"
)
/* 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 streamutil.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 streamutil.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 streamutil.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 streamutil.Close(stmt)
_, err = stmt.Exec()
return err
}

View file

@ -1,240 +0,0 @@
// 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"
"strings"
"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
Global bool
UserID string
OrgID string
OrgName string
SSL bool
AppURL string // e.g. https://{url}.documize.com
Subdomain string
ClientIP string
Expires time.Time
Transaction *sqlx.Tx
}
//GetAppURL returns full HTTP url for the app
func (c *Context) GetAppURL(endpoint string) string {
scheme := "http://"
if c.SSL {
scheme = "https://"
}
return fmt.Sprintf("%s%s/%s", scheme, c.AppURL, endpoint)
}
// 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
// get user IP from request
i := strings.LastIndex(r.RemoteAddr, ":")
if i == -1 {
c.ClientIP = r.RemoteAddr
} else {
c.ClientIP = r.RemoteAddr[:i]
}
fip := r.Header.Get("X-Forwarded-For")
if len(fip) > 0 {
c.ClientIP = fip
}
context.Set(r, rc, c)
}
// Persister stores the Context of the client along with a baseManager instance.
type Persister struct {
Context Context
Base baseManager
}
// GetPersister reurns a Persister which contains a Context which is based on the incoming request.
func GetPersister(r *http.Request) Persister {
var p = Persister{}
p.Context = getContext(r)
p.Context.AppURL = r.Host
p.Context.SSL = r.TLS != nil
return p
}
// CanViewDocumentInFolder returns if the user has permission to view a document within the specified folder.
func (p *Persister) CanViewDocumentInFolder(labelID string) (hasPermission bool) {
roles, err := p.GetUserLabelRoles()
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
log.Error(fmt.Sprintf("Unable to check folder %s for permission check", labelID), err)
return false
}
for _, role := range roles {
if role.LabelID == labelID && (role.CanView || role.CanEdit) {
return true
}
}
return false
}
// CanViewDocument returns if the clinet has permission to view a given document.
func (p *Persister) CanViewDocument(documentID string) (hasPermission bool) {
document, err := p.GetDocument(documentID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
log.Error(fmt.Sprintf("Unable to get document %s for permission check", documentID), err)
return false
}
roles, err := p.GetUserLabelRoles()
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
log.Error(fmt.Sprintf("Unable to get document %s for permission check", documentID), err)
return false
}
for _, role := range roles {
if role.LabelID == document.LabelID && (role.CanView || role.CanEdit) {
return true
}
}
return false
}
// CanChangeDocument returns if the clinet has permission to change a given document.
func (p *Persister) CanChangeDocument(documentID string) (hasPermission bool) {
document, err := p.GetDocument(documentID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
log.Error(fmt.Sprintf("Unable to get document %s for permission check", documentID), err)
return false
}
roles, err := p.GetUserLabelRoles()
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
log.Error(fmt.Sprintf("Unable to get document %s for permission check", documentID), err)
return false
}
for _, role := range roles {
if role.LabelID == document.LabelID && role.CanEdit {
return true
}
}
return false
}
// CanUploadDocument returns if the client has permission to upload documents to the given folderID.
func (p *Persister) CanUploadDocument(folderID string) (hasPermission bool) {
roles, err := p.GetUserLabelRoles()
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
log.Error(fmt.Sprintf("Unable to check permission for folder %s", folderID), err)
return false
}
for _, role := range roles {
if role.LabelID == folderID && role.CanEdit {
return true
}
}
return false
}
// CanViewFolder returns if the user has permission to view the given folderID.
func (p *Persister) CanViewFolder(folderID string) (hasPermission bool) {
roles, err := p.GetUserLabelRoles()
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
log.Error(fmt.Sprintf("Unable to check permission for folder %s", folderID), err)
return false
}
for _, role := range roles {
if role.LabelID == folderID && (role.CanView || role.CanEdit) {
return true
}
}
return false
}

View file

@ -1,80 +0,0 @@
// 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")
}
}
*/

View file

@ -1,13 +0,0 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
// Package request handles database requests for Documize, mostly from the endpoints package.
package request

View file

@ -1,428 +0,0 @@
// 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/streamutil"
)
// 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 streamutil.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
}
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) {
stmt, err := Db.Preparex("SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, layout, created, revised FROM document WHERE orgid=? and refid=?")
defer streamutil.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
}
return
}
// GetDocumentMeta returns the metadata for a specified document.
func (p *Persister) GetDocumentMeta(id string) (meta entity.DocumentMeta, err error) {
// sqlViewers := `SELECT CONVERT_TZ(MAX(a.created), @@session.time_zone, '+00:00') as created,
sqlViewers := `SELECT MAX(a.created) as created,
IFNULL(a.userid, '') AS userid, IFNULL(u.firstname, 'Anonymous') AS firstname, IFNULL(u.lastname, 'Viewer') AS lastname
FROM audit a LEFT JOIN user u ON a.userid=u.refid
WHERE a.orgid=? AND a.documentid=?
AND a.userid != '0' AND a.userid != ''
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,
sqlEdits := `SELECT a.created,
IFNULL(a.action, '') AS action, IFNULL(a.userid, '') AS userid, IFNULL(u.firstname, 'Anonymous') AS firstname, IFNULL(u.lastname, 'Viewer') AS lastname, IFNULL(a.pageid, '') AS 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.userid != ''
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, layout, 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 = Db.Select(&documents, "SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, layout, 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, layout, 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, layout, 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
}
// GetDocumentList returns a slice containing the documents available as templates to the client's organisation, in title order.
func (p *Persister) GetDocumentList() (documents []entity.Document, err error) {
err = Db.Select(&documents,
`SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, layout, created, revised FROM document WHERE orgid=? AND template=0 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 GetDocumentList org %s", p.Context.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
}
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, layout=:layout, revised=:revised WHERE orgid=:orgid AND refid=:refid")
defer streamutil.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
}
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 streamutil.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
}
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 streamutil.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
}
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
}
// Mark references to this document as orphaned
err = p.MarkOrphanDocumentLink(documentID)
if err != nil {
return
}
// Remove all references from this document
_, err = p.DeleteSourceDocumentLinks(documentID)
if err != nil {
return
}
return p.Base.DeleteConstrained(p.Context.Transaction, "document", p.Context.OrgID, documentID)
}

View file

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

View file

@ -1,44 +0,0 @@
// 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"
)
// 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)
}
// 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)
}

View file

@ -1,42 +0,0 @@
// 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)
}
}
*/

View file

@ -1,59 +0,0 @@
// 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 (
"time"
"github.com/documize/community/core/api/entity"
"github.com/documize/community/core/log"
)
// RecordEvent adds event entry for specified user.
func (p *Persister) RecordEvent(t entity.EventType) {
e := entity.AppEvent{}
e.OrgID = p.Context.OrgID
e.UserID = p.Context.UserID
e.Created = time.Now().UTC()
e.IP = p.Context.ClientIP
e.Type = string(t)
if e.OrgID == "" || e.UserID == "" {
log.Info("Missing OrgID/UserID for event record " + e.Type)
return
}
tx, err := Db.Beginx()
if err != nil {
log.Error("Unable to prepare insert RecordEvent", err)
return
}
stmt, err := tx.Preparex("INSERT INTO userevent (orgid, userid, eventtype, ip, created) VALUES (?, ?, ?, ?, ?)")
if err != nil {
tx.Rollback()
log.Error("Unable to prepare insert RecordEvent", err)
return
}
_, err = stmt.Exec(e.OrgID, e.UserID, e.Type, e.IP, e.Created)
if err != nil {
log.Error("Unable to execute insert RecordEvent", err)
tx.Rollback()
return
}
stmt.Close()
tx.Commit()
return
}

View file

@ -1,132 +0,0 @@
// 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"
// "time"
"github.com/jmoiron/sqlx"
// "github.com/documize/community/core/database"
// "github.com/documize/community/core/env"
"github.com/documize/community/core/log"
"github.com/documize/community/core/streamutil"
// "github.com/documize/community/core/web"
)
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
}
type baseManager struct {
}
func (m *baseManager) Delete(tx *sqlx.Tx, table string, id string) (rows int64, err error) {
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 streamutil.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 streamutil.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 streamutil.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
}
// SQLPrepareError returns a string detailing the location of the error.
func (m *baseManager) SQLPrepareError(method string, id string) string {
return fmt.Sprintf("Unable to prepare SQL for %s, ID %s", method, id)
}
// SQLSelectError returns a string detailing the location of the error.
func (m *baseManager) SQLSelectError(method string, id string) string {
return fmt.Sprintf("Unable to execute SQL for %s, ID %s", method, id)
}

View file

@ -1,37 +0,0 @@
// 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
}
*/

View file

@ -1,178 +0,0 @@
// 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/streamutil"
)
// 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 streamutil.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) {
stmt, err := Db.Preparex("SELECT id,refid,label as name,orgid,userid,type,created,revised FROM label WHERE orgid=? and refid=?")
defer streamutil.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) {
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) {
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) {
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 streamutil.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) {
stmt, err := p.Context.Transaction.Preparex("UPDATE label SET userid=? WHERE userid=? AND orgid=?")
defer streamutil.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) {
sql := `
SELECT a.userid,
COALESCE(u.firstname, '') as firstname,
COALESCE(u.lastname, '') as lastname,
COALESCE(u.email, '') as email,
a.labelid,
b.label as name,
b.type
FROM labelrole a
LEFT JOIN label b ON b.refid=a.labelid
LEFT JOIN user u ON u.refid=a.userid
WHERE a.orgid=? AND b.type != 2
GROUP BY a.labelid,a.userid
ORDER BY u.firstname,u.lastname`
err = Db.Select(&visibleTo, sql, p.Context.OrgID)
return
}
// DeleteLabel removes folder from the store.
func (p *Persister) DeleteLabel(labelID string) (rows int64, err error) {
return p.Base.DeleteConstrained(p.Context.Transaction, "label", p.Context.OrgID, labelID)
}

View file

@ -1,149 +0,0 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
package request
/* TODO(Elliott)
import (
"testing"
"github.com/documize/community/documize/api/entity"
)
const testName = "testLabelName"
func testAddLabel(t *testing.T, p *Persister) entity.Label {
lab := entity.Label{
BaseEntity: entity.BaseEntity{RefID: testLabelID},
Name: testName, // string `json:"name"`
OrgID: p.Context.OrgID, // string `json:"orgId"`
UserID: p.Context.UserID, // string `json:"userId"`
Type: entity.FolderTypePublic, //FolderType `json:"folderType"`
}
err := p.AddLabel(lab)
if err != nil {
t.Error(err)
}
p.testCommit(t)
return lab
}
func testDeleteLabel(t *testing.T, p *Persister) {
num, err := p.DeleteLabel(testLabelID)
if err != nil {
t.Error(err)
}
if num != 1 {
t.Error("one record not deleted:", num)
}
p.testCommit(t)
}
func TestLabel(t *testing.T) {
p := newTestPersister(t)
defer deleteTestAuditTrail(t, p)
lab := testAddLabel(t, p)
defer testDeleteLabel(t, p)
testAddLabelRole(t, p)
defer testDeleteLabelRole(t, p)
testAddUser(t, p)
defer testDeleteUser(t, p)
vis, err := p.GetFolderVisibility()
if err != nil {
t.Error(err)
}
for _, v := range vis {
if v.LabelID == testLabelID {
goto foundVis
}
}
t.Error("test label not found in GetFolderVisibility()")
foundVis:
p.testRollback(t)
err = p.AddLabel(lab)
if err == nil {
t.Error("did not error on duplicate label")
}
p.testRollback(t)
lab2, err := p.GetLabel(testLabelID)
if err != nil {
t.Error(err)
}
if lab.Name != lab2.Name {
t.Error("wrong data returned")
}
p.testRollback(t)
_, err = p.GetLabel("XXXXXXXXX")
if err == nil {
t.Error("did not error when it should have")
}
p.testRollback(t)
list, err := p.GetPublicFolders(p.Context.OrgID)
if err != nil {
t.Error(err)
}
if len(list) != 1 {
t.Errorf("wrong number of public folders %d", len(list))
} else {
if list[0].Name != lab.Name {
t.Errorf("wanted %s got %s", lab.Name, list[0].Name)
}
}
p.testRollback(t)
lab.Type = entity.FolderTypePrivate
err = p.UpdateLabel(lab)
if err != nil {
t.Error(err)
}
p.testCommit(t)
labels, err := p.GetLabels()
if err != nil {
t.Error(err)
}
for _, l := range labels {
if l.BaseEntity.RefID == testLabelID {
goto foundLabel
}
}
t.Error("test label not found in GetLabels()")
foundLabel:
p.testRollback(t)
gonzo := "Gonzo"
err = p.ChangeLabelOwner(p.Context.UserID, gonzo)
if err != nil {
t.Error(err)
}
p.testCommit(t)
u := p.Context.UserID
p.Context.UserID = gonzo
_, err = p.GetLabel(testLabelID)
if err != nil {
t.Error(err)
}
p.testRollback(t)
err = p.ChangeLabelOwner(gonzo, p.Context.UserID) // change it back for deletion
if err != nil {
t.Error(err)
}
p.testCommit(t)
p.Context.UserID = u // put back the right one, so that we delete correctly on tidy-up
}
*/

View file

@ -1,133 +0,0 @@
// 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/streamutil"
)
// 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 streamutil.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) {
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 streamutil.Close(stmt)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare move label roles for label %s", previousLabel), err)
return
}
_, err = stmt.Exec(newLabel, previousLabel, p.Context.OrgID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute move label roles for label %s", previousLabel), err)
}
return
}

View file

@ -1,236 +0,0 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
package request
/* TODO(Elliott)
import (
"testing"
"github.com/documize/community/documize/api/entity"
)
const testLabelRoleID = "testLabelRoleID"
func testAddLabelRole(t *testing.T, p *Persister) entity.LabelRole {
labrole := entity.LabelRole{
BaseEntityObfuscated: entity.BaseEntityObfuscated{RefID: testLabelRoleID},
OrgID: p.Context.OrgID, // string `json:"orgId"`
LabelID: testLabelID, // string `json:"userId"`
UserID: p.Context.UserID, // string `json:"userId"`
CanView: true, // bool `json:"canView"`
CanEdit: true, // bool `json:"canEdit"`
}
err := p.AddLabelRole(labrole)
if err != nil {
t.Error(err)
}
p.testCommit(t)
return labrole
}
func testDeleteLabelRole(t *testing.T, p *Persister) {
num, err := p.DeleteLabelRole(testLabelRoleID)
if err != nil {
t.Error(err)
}
if num != 1 {
t.Error("one record not deleted:", num)
}
p.testCommit(t)
}
func TestLabelRole(t *testing.T) {
p := newTestPersister(t)
defer deleteTestAuditTrail(t, p)
labRole := testAddLabelRole(t, p)
defer testDeleteLabelRole(t, p)
testAddDocument(t, p)
defer testDeleteDocument(t, p)
err := p.AddLabelRole(labRole)
if err == nil {
t.Error("did not error on duplicate label")
}
p.testRollback(t)
labs, err := p.GetLabelRoles(testLabelID)
if err != nil {
t.Error(err)
}
if len(labs) != 1 {
t.Errorf("wrong number of labels %d", len(labs))
} else {
if labs[0].LabelID != testLabelID {
t.Errorf("wrong data")
}
}
p.testRollback(t)
labs, err = p.GetLabelRoles("XXXXXXXXX")
if err != nil {
t.Error(err)
}
if len(labs) != 0 {
t.Errorf("wrong number of labels %d", len(labs))
}
p.testRollback(t)
labs, err = p.GetUserLabelRoles()
if err != nil {
t.Error(err)
}
if len(labs) != 1 {
t.Errorf("wrong number of labels %d", len(labs))
} else {
if labs[0].LabelID != testLabelID {
t.Errorf("wrong data")
}
}
p.testRollback(t)
if !p.CanUploadDocument(testLabelID) {
t.Error("unexpected result for can upload document")
}
if !p.CanChangeDocument(testDocID) {
t.Error("unexpected result for can change document")
}
_, err = p.DeleteLabelRoles(testLabelID)
if err != nil {
t.Error(err)
}
p.testCommit(t)
labs, err = p.GetUserLabelRoles()
if err != nil {
t.Error(err)
}
if len(labs) != 0 {
t.Errorf("wrong number of labels %d, record not deleted", len(labs))
}
p.testRollback(t)
testAddLabelRole(t, p) // reset
_, err = p.DeleteUserFolderRoles(testLabelID, p.Context.UserID)
if err != nil {
t.Error(err)
}
p.testCommit(t)
labs, err = p.GetUserLabelRoles()
if err != nil {
t.Error(err)
}
if len(labs) != 0 {
t.Errorf("wrong number of labels %d, record not deleted", len(labs))
}
p.testRollback(t)
testAddLabelRole(t, p) // reset
g := "Gonzo"
err = p.MoveLabelRoles(testLabelID, g)
if err != nil {
t.Error(err)
}
p.testCommit(t)
labs, err = p.GetLabelRoles(g)
if err != nil {
t.Error(err)
}
if len(labs) != 1 {
t.Errorf("wrong number of labels %d", len(labs))
} else {
if labs[0].LabelID != g {
t.Errorf("wrong data")
}
}
p.testRollback(t)
if p.CanUploadDocument(testLabelID) { // Gonzo
t.Error("unexpected result for can upload document")
}
if p.CanChangeDocument(testDocID) { // Gonzo
t.Error("unexpected result for can change document")
}
if p.CanChangeDocument("XXXXXXX") { // unknown docID
t.Error("unexpected result for can change document")
}
/*
lab2, err := p.GetLabelRoles(testLabelID)
if err != nil {
t.Error(err)
}
if lab.Name != lab2.Name {
t.Error("wrong data returned")
}
p.testRollback(t)
_, err = p.GetLabel("XXXXXXXXX")
if err == nil {
t.Error("did not error when it should have")
}
p.testRollback(t)
list, err := p.GetPublicFolders(p.Context.OrgID)
if err != nil {
t.Error(err)
}
if len(list) != 1 {
t.Errorf("wrong number of public folders %d", len(list))
} else {
if list[0].Name != lab.Name {
t.Errorf("wanted %s got %s", lab.Name, list[0].Name)
}
}
p.testRollback(t)
t.Log("TODO p.GetFolderVisibility() requires user and labelrole recorde")
lab.Type = entity.FolderTypePrivate
err = p.UpdateLabel(lab)
if err != nil {
t.Error(err)
}
p.testCommit(t)
labels, err := p.GetLabels()
if err != nil {
t.Error(err)
}
for _, l := range labels {
if l.BaseEntity.RefID == testLabelID {
goto foundLabel
}
}
t.Error("test label not found in GetLabels()")
foundLabel:
p.testRollback(t)
gonzo := "Gonzo"
err = p.ChangeLabelOwner(p.Context.UserID, gonzo)
if err != nil {
t.Error(err)
}
p.testCommit(t)
p.Context.UserID = gonzo
_, err = p.GetLabel(testLabelID)
if err != nil {
t.Error(err)
}
p.testRollback(t)
*/
//}

View file

@ -1,285 +0,0 @@
// 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/streamutil"
"github.com/documize/community/core/uniqueid"
)
// AddContentLink inserts wiki-link into the store.
// These links exist when content references another document or content.
func (p *Persister) AddContentLink(l entity.Link) (err error) {
l.Created = time.Now().UTC()
l.Revised = time.Now().UTC()
stmt, err := p.Context.Transaction.Preparex("INSERT INTO link (refid, orgid, folderid, userid, sourcedocumentid, sourcepageid, targetdocumentid, targetid, linktype, orphan, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
defer streamutil.Close(stmt)
if err != nil {
log.Error("Unable to prepare insert for link", err)
return
}
_, err = stmt.Exec(l.RefID, l.OrgID, l.FolderID, l.UserID, l.SourceDocumentID, l.SourcePageID, l.TargetDocumentID, l.TargetID, l.LinkType, l.Orphan, l.Created, l.Revised)
if err != nil {
log.Error("Unable to execute insert for link", err)
return
}
return
}
// SearchLinkCandidates returns matching documents, sections and attachments using keywords.
func (p *Persister) SearchLinkCandidates(keywords string) (docs []entity.LinkCandidate,
pages []entity.LinkCandidate, attachments []entity.LinkCandidate, err error) {
// find matching documents
temp := []entity.LinkCandidate{}
likeQuery := "title LIKE '%" + keywords + "%'"
err = Db.Select(&temp,
`SELECT refid as documentid, labelid as folderid,title from document WHERE orgid=? AND `+likeQuery+` 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 search links for org %s", p.Context.OrgID), err)
return
}
for _, r := range temp {
c := entity.LinkCandidate{
RefID: uniqueid.Generate(),
FolderID: r.FolderID,
DocumentID: r.DocumentID,
TargetID: r.DocumentID,
LinkType: "document",
Title: r.Title,
Context: "",
}
docs = append(docs, c)
}
// find matching sections
likeQuery = "p.title LIKE '%" + keywords + "%'"
temp = []entity.LinkCandidate{}
err = Db.Select(&temp,
`SELECT p.refid as targetid, p.documentid as documentid, p.title as title, p.pagetype as linktype, d.title as context, d.labelid as folderid from page p
LEFT JOIN document d ON d.refid=p.documentid WHERE p.orgid=? AND `+likeQuery+` AND d.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 p.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 search links for org %s", p.Context.OrgID), err)
return
}
for _, r := range temp {
c := entity.LinkCandidate{
RefID: uniqueid.Generate(),
FolderID: r.FolderID,
DocumentID: r.DocumentID,
TargetID: r.TargetID,
LinkType: r.LinkType,
Title: r.Title,
Context: r.Context,
}
pages = append(pages, c)
}
// find matching attachments
likeQuery = "a.filename LIKE '%" + keywords + "%'"
temp = []entity.LinkCandidate{}
err = Db.Select(&temp,
`SELECT a.refid as targetid, a.documentid as documentid, a.filename as title, a.extension as context, d.labelid as folderid from attachment a
LEFT JOIN document d ON d.refid=a.documentid WHERE a.orgid=? AND `+likeQuery+` AND d.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 a.filename`,
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 links for org %s", p.Context.OrgID), err)
return
}
for _, r := range temp {
c := entity.LinkCandidate{
RefID: uniqueid.Generate(),
FolderID: r.FolderID,
DocumentID: r.DocumentID,
TargetID: r.TargetID,
LinkType: "file",
Title: r.Title,
Context: r.Context,
}
attachments = append(attachments, c)
}
if len(docs) == 0 {
docs = []entity.LinkCandidate{}
}
if len(pages) == 0 {
pages = []entity.LinkCandidate{}
}
if len(attachments) == 0 {
attachments = []entity.LinkCandidate{}
}
return
}
// GetDocumentOutboundLinks returns outbound links for specified document.
func (p *Persister) GetDocumentOutboundLinks(documentID string) (links []entity.Link, err error) {
err = Db.Select(&links,
`select l.refid, l.orgid, l.folderid, l.userid, l.sourcedocumentid, l.sourcepageid, l.targetdocumentid, l.targetid, l.linktype, l.orphan, l.created, l.revised
FROM link l
WHERE l.orgid=? AND l.sourcedocumentid=?`,
p.Context.OrgID,
documentID)
if err != nil {
return
}
if len(links) == 0 {
links = []entity.Link{}
}
return
}
// GetPageLinks returns outbound links for specified page in document.
func (p *Persister) GetPageLinks(documentID, pageID string) (links []entity.Link, err error) {
err = Db.Select(&links,
`select l.refid, l.orgid, l.folderid, l.userid, l.sourcedocumentid, l.sourcepageid, l.targetdocumentid, l.targetid, l.linktype, l.orphan, l.created, l.revised
FROM link l
WHERE l.orgid=? AND l.sourcedocumentid=? AND l.sourcepageid=?`,
p.Context.OrgID,
documentID,
pageID)
if err != nil {
return
}
if len(links) == 0 {
links = []entity.Link{}
}
return
}
// MarkOrphanDocumentLink marks all link records referencing specified document.
func (p *Persister) MarkOrphanDocumentLink(documentID string) (err error) {
revised := time.Now().UTC()
stmt, err := p.Context.Transaction.Preparex("UPDATE link SET orphan=1, revised=? WHERE linktype='document' AND orgid=? AND targetdocumentid=?")
if err != nil {
return
}
defer streamutil.Close(stmt)
_, err = stmt.Exec(revised, p.Context.OrgID, documentID)
return
}
// MarkOrphanPageLink marks all link records referencing specified page.
func (p *Persister) MarkOrphanPageLink(pageID string) (err error) {
revised := time.Now().UTC()
stmt, err := p.Context.Transaction.Preparex("UPDATE link SET orphan=1, revised=? WHERE linktype='section' AND orgid=? AND targetid=?")
if err != nil {
return
}
defer streamutil.Close(stmt)
_, err = stmt.Exec(revised, p.Context.OrgID, pageID)
return
}
// MarkOrphanAttachmentLink marks all link records referencing specified attachment.
func (p *Persister) MarkOrphanAttachmentLink(attachmentID string) (err error) {
revised := time.Now().UTC()
stmt, err := p.Context.Transaction.Preparex("UPDATE link SET orphan=1, revised=? WHERE linktype='file' AND orgid=? AND targetid=?")
if err != nil {
return
}
defer streamutil.Close(stmt)
_, err = stmt.Exec(revised, p.Context.OrgID, attachmentID)
return
}
// DeleteSourcePageLinks removes saved links for given source.
func (p *Persister) DeleteSourcePageLinks(pageID string) (rows int64, err error) {
return p.Base.DeleteWhere(p.Context.Transaction, fmt.Sprintf("DELETE FROM link WHERE orgid=\"%s\" AND sourcepageid=\"%s\"", p.Context.OrgID, pageID))
}
// DeleteSourceDocumentLinks removes saved links for given document.
func (p *Persister) DeleteSourceDocumentLinks(documentID string) (rows int64, err error) {
return p.Base.DeleteWhere(p.Context.Transaction, fmt.Sprintf("DELETE FROM link WHERE orgid=\"%s\" AND sourcedocumentid=\"%s\"", p.Context.OrgID, documentID))
}
// DeleteLink removes saved link from the store.
func (p *Persister) DeleteLink(id string) (rows int64, err error) {
return p.Base.DeleteConstrained(p.Context.Transaction, "link", p.Context.OrgID, id)
}

View file

@ -1,210 +0,0 @@
// 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"
"github.com/documize/community/core/api/entity"
"github.com/documize/community/core/env"
"github.com/documize/community/core/log"
"github.com/documize/community/core/streamutil"
"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 streamutil.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) {
stmt, err := Db.Preparex("SELECT id, refid, company, title, message, url, domain, service as conversionendpoint, email, serial, active, allowanonymousaccess, authprovider, coalesce(authconfig,JSON_UNQUOTE('{}')) as authconfig, created, revised FROM organization WHERE refid=?")
defer streamutil.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
}
// 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 api.Runtime.Flags.SiteMode == env.SiteModeNormal { // only return an organization when running normally
var stmt *sqlx.Stmt
stmt, err = Db.Preparex("SELECT id, refid, company, title, message, url, domain, service as conversionendpoint, email, serial, active, allowanonymousaccess, authprovider, coalesce(authconfig,JSON_UNQUOTE('{}')) as authconfig, created, revised FROM organization WHERE domain=? AND active=1")
defer streamutil.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) {
org.Revised = time.Now().UTC()
stmt, err := p.Context.Transaction.PrepareNamed("UPDATE organization SET title=:title, message=:message, service=:conversionendpoint, email=:email, allowanonymousaccess=:allowanonymousaccess, revised=:revised WHERE refid=:refid")
defer streamutil.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) {
stmt, err := p.Context.Transaction.Preparex("UPDATE organization SET active=0 WHERE refid=?")
defer streamutil.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
}
// UpdateAuthConfig updates the given organization record in the database with the auth config details.
func (p *Persister) UpdateAuthConfig(org entity.Organization) (err error) {
org.Revised = time.Now().UTC()
stmt, err := p.Context.Transaction.PrepareNamed("UPDATE organization SET allowanonymousaccess=:allowanonymousaccess, authprovider=:authprovider, authconfig=:authconfig, revised=:revised WHERE refid=:refid")
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare UpdateAuthConfig %s", org.RefID), err)
return
}
defer streamutil.Close(stmt)
_, err = stmt.Exec(&org)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute UpdateAuthConfig %s", org.RefID), 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 ""
}

View file

@ -1,121 +0,0 @@
// 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)
}
*/

View file

@ -1,555 +0,0 @@
// 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"
"golang.org/x/net/html"
"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/streamutil"
"github.com/jmoiron/sqlx"
)
// 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) {
model.Page.OrgID = p.Context.OrgID
model.Page.UserID = p.Context.UserID
model.Page.Created = time.Now().UTC()
model.Page.Revised = time.Now().UTC()
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()
if model.Page.Sequence == 0 {
// Get maximum page sequence number and increment (used to be AND pagetype='section')
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 {
maxSeq = 2048
}
model.Page.Sequence = maxSeq * 2
}
stmt, err := p.Context.Transaction.Preparex("INSERT INTO page (refid, orgid, documentid, userid, contenttype, pagetype, level, title, body, revisions, sequence, blockid, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
defer streamutil.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.PageType, model.Page.Level, model.Page.Title, model.Page.Body, model.Page.Revisions, model.Page.Sequence, model.Page.BlockID, model.Page.Created, model.Page.Revised)
if err != nil {
log.Error("Unable to execute insert for page", err)
return
}
_ = 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 streamutil.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
}
return
}
// GetPage returns the pageID page record from the page table.
func (p *Persister) GetPage(pageID string) (page entity.Page, err error) {
stmt, err := Db.Preparex("SELECT a.id, a.refid, a.orgid, a.documentid, a.userid, a.contenttype, a.pagetype, a.level, a.sequence, a.title, a.body, a.revisions, a.blockid, a.created, a.revised FROM page a WHERE a.orgid=? AND a.refid=?")
defer streamutil.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 = Db.Select(&pages, "SELECT a.id, a.refid, a.orgid, a.documentid, a.userid, a.contenttype, a.pagetype, a.level, a.sequence, a.title, a.body, a.revisions, a.blockid, 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) {
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.pagetype, a.level, a.sequence, a.title, a.body, a.blockid, 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 streamutil.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 streamutil.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, pagetype, sequence, level, title, revisions, blockid, 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) {
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, pagetype, 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.pagetype, 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 streamutil.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 streamutil.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 streamutil.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
}
}
// find any content links in the HTML
links := GetContentLinks(page.Body)
// get a copy of previously saved links
previousLinks, _ := p.GetPageLinks(page.DocumentID, page.RefID)
// delete previous content links for this page
_, _ = p.DeleteSourcePageLinks(page.RefID)
// save latest content links for this page
for _, link := range links {
link.Orphan = false
link.OrgID = p.Context.OrgID
link.UserID = p.Context.UserID
link.SourceDocumentID = page.DocumentID
link.SourcePageID = page.RefID
if link.LinkType == "document" {
link.TargetID = ""
}
// We check if there was a previously saved version of this link.
// If we find one, we carry forward the orphan flag.
for _, p := range previousLinks {
if link.TargetID == p.TargetID && link.LinkType == p.LinkType {
link.Orphan = p.Orphan
break
}
}
// save
err := p.AddContentLink(link)
if err != nil {
log.Error(fmt.Sprintf("Unable to insert content links for page %s", page.RefID), err)
return err
}
}
return
}
// UpdatePageMeta persists meta information associated with a document page.
func (p *Persister) UpdatePageMeta(meta entity.PageMeta, updateUserID bool) (err error) {
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 streamutil.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 streamutil.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)
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 streamutil.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)
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 {
_, _ = p.Base.DeleteWhere(p.Context.Transaction, fmt.Sprintf("DELETE FROM pagemeta WHERE orgid='%s' AND pageid='%s'", p.Context.OrgID, pageID))
_, _ = searches.Delete(&databaseRequest{OrgID: p.Context.OrgID}, documentID, pageID)
// delete content links from this page
_, _ = p.DeleteSourcePageLinks(pageID)
// mark as orphan links to this page
_ = p.MarkOrphanPageLink(pageID)
// nuke revisions
_, _ = p.DeletePageRevisions(pageID)
}
return
}
// GetPageMeta returns the meta information associated with the page.
func (p *Persister) GetPageMeta(pageID string) (meta entity.PageMeta, err error) {
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 streamutil.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) {
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
}
/********************
* Page Revisions
********************/
// GetPageRevision returns the revisionID page revision record.
func (p *Persister) GetPageRevision(revisionID string) (revision entity.Revision, err error) {
stmt, err := Db.Preparex("SELECT id, refid, orgid, documentid, ownerid, pageid, userid, contenttype, pagetype, title, body, coalesce(rawbody, '') as rawbody, coalesce(config,JSON_UNQUOTE('{}')) as config, created, revised FROM revision WHERE orgid=? and refid=?")
defer streamutil.Close(stmt)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare select for revision %s", revisionID), err)
return
}
err = stmt.Get(&revision, p.Context.OrgID, revisionID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute select for revision %s", revisionID), err)
return
}
return
}
// GetDocumentRevisions returns a slice of page revision records for a given document, in the order they were created.
// Then audits that the get-page-revisions action has occurred.
func (p *Persister) GetDocumentRevisions(documentID string) (revisions []entity.Revision, err error) {
err = Db.Select(&revisions, "SELECT a.id, a.refid, a.orgid, a.documentid, a.ownerid, a.pageid, a.userid, a.contenttype, a.pagetype, a.title, /*a.body, a.rawbody, a.config,*/ a.created, a.revised, coalesce(b.email,'') as email, coalesce(b.firstname,'') as firstname, coalesce(b.lastname,'') as lastname, coalesce(b.initials,'') as initials, coalesce(p.revisions, 0) as revisions FROM revision a LEFT JOIN user b ON a.userid=b.refid LEFT JOIN page p ON a.pageid=p.refid WHERE a.orgid=? AND a.documentid=? AND a.pagetype='section' ORDER BY a.id DESC", p.Context.OrgID, documentID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute select revisions for org %s and document %s", p.Context.OrgID, documentID), err)
return
}
if len(revisions) == 0 {
revisions = []entity.Revision{}
}
return
}
// GetPageRevisions returns a slice of page revision records for a given pageID, in the order they were created.
// Then audits that the get-page-revisions action has occurred.
func (p *Persister) GetPageRevisions(pageID string) (revisions []entity.Revision, err error) {
err = Db.Select(&revisions, "SELECT a.id, a.refid, a.orgid, a.documentid, a.ownerid, a.pageid, a.userid, a.contenttype, a.pagetype, a.title, /*a.body, a.rawbody, a.config,*/ a.created, a.revised, coalesce(b.email,'') as email, coalesce(b.firstname,'') as firstname, coalesce(b.lastname,'') as lastname, coalesce(b.initials,'') as initials FROM revision a LEFT JOIN user b ON a.userid=b.refid WHERE a.orgid=? AND a.pageid=? AND a.pagetype='section' ORDER BY a.id DESC", p.Context.OrgID, pageID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute select revisions for org %s and page %s", p.Context.OrgID, pageID), err)
return
}
return
}
// DeletePageRevisions deletes all of the page revision records for a given pageID.
func (p *Persister) DeletePageRevisions(pageID string) (rows int64, err error) {
rows, err = p.Base.DeleteWhere(p.Context.Transaction, fmt.Sprintf("DELETE FROM revision WHERE orgid='%s' AND pageid='%s'", p.Context.OrgID, pageID))
return
}
// GetNextPageSequence returns the next sequence numbner to use for a page in given document.
func (p *Persister) GetNextPageSequence(documentID string) (maxSeq float64, err error) {
row := Db.QueryRow("SELECT max(sequence) FROM page WHERE orgid=? AND documentid=?", p.Context.OrgID, documentID)
// row := Db.QueryRow("SELECT max(sequence) FROM page WHERE orgid=? AND documentid=? AND pagetype='section'", p.Context.OrgID, documentID)
err = row.Scan(&maxSeq)
if err != nil {
maxSeq = 2048
}
maxSeq = maxSeq * 2
return
}
// GetContentLinks returns Documize generated <a> links.
// such links have an identifying attribute e.g. <a data-documize='true'...
func GetContentLinks(body string) (links []entity.Link) {
z := html.NewTokenizer(strings.NewReader(body))
for {
tt := z.Next()
switch {
case tt == html.ErrorToken:
// End of the document, we're done
return
case tt == html.StartTagToken:
t := z.Token()
// Check if the token is an <a> tag
isAnchor := t.Data == "a"
if !isAnchor {
continue
}
// Extract the content link
ok, link := getLink(t)
if ok {
links = append(links, link)
}
}
}
}
// Helper function to pull the href attribute from a Token
func getLink(t html.Token) (ok bool, link entity.Link) {
ok = false
// Iterate over all of the Token's attributes until we find an "href"
for _, a := range t.Attr {
switch a.Key {
case "data-documize":
ok = true
case "data-link-id":
link.RefID = strings.TrimSpace(a.Val)
case "data-link-space-id":
link.FolderID = strings.TrimSpace(a.Val)
case "data-link-target-document-id":
link.TargetDocumentID = strings.TrimSpace(a.Val)
case "data-link-target-id":
link.TargetID = strings.TrimSpace(a.Val)
case "data-link-type":
link.LinkType = strings.TrimSpace(a.Val)
}
}
return
}

View file

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

View file

@ -1,139 +0,0 @@
// 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/streamutil"
"github.com/jmoiron/sqlx"
)
// AddPin saves pinned item.
func (p *Persister) AddPin(pin entity.Pin) (err error) {
row := Db.QueryRow("SELECT max(sequence) FROM pin WHERE orgid=? AND userid=?", p.Context.OrgID, p.Context.UserID)
var maxSeq int
err = row.Scan(&maxSeq)
if err != nil {
maxSeq = 99
}
pin.Created = time.Now().UTC()
pin.Revised = time.Now().UTC()
pin.Sequence = maxSeq + 1
stmt, err := p.Context.Transaction.Preparex("INSERT INTO pin (refid, orgid, userid, labelid, documentid, pin, sequence, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)")
defer streamutil.Close(stmt)
if err != nil {
log.Error("Unable to prepare insert for pin", err)
return
}
_, err = stmt.Exec(pin.RefID, pin.OrgID, pin.UserID, pin.FolderID, pin.DocumentID, pin.Pin, pin.Sequence, pin.Created, pin.Revised)
if err != nil {
log.Error("Unable to execute insert for pin", err)
return
}
return
}
// GetPin returns requested pinned item.
func (p *Persister) GetPin(id string) (pin entity.Pin, err error) {
stmt, err := Db.Preparex("SELECT id, refid, orgid, userid, labelid as folderid, documentid, pin, sequence, created, revised FROM pin WHERE orgid=? AND refid=?")
defer streamutil.Close(stmt)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare select for pin %s", id), err)
return
}
err = stmt.Get(&pin, p.Context.OrgID, id)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute select for pin %s", id), err)
return
}
return
}
// GetUserPins returns pinned items for specified user.
func (p *Persister) GetUserPins(userID string) (pins []entity.Pin, err error) {
err = Db.Select(&pins, "SELECT id, refid, orgid, userid, labelid as folderid, documentid, pin, sequence, created, revised FROM pin WHERE orgid=? AND userid=? ORDER BY sequence", p.Context.OrgID, userID)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute select pin for org %s and user %s", p.Context.OrgID, userID), err)
return
}
return
}
// UpdatePin updates existing pinned item.
func (p *Persister) UpdatePin(pin entity.Pin) (err error) {
pin.Revised = time.Now().UTC()
var stmt *sqlx.NamedStmt
stmt, err = p.Context.Transaction.PrepareNamed("UPDATE pin SET labelid=:folderid, documentid=:documentid, pin=:pin, sequence=:sequence, revised=:revised WHERE orgid=:orgid AND refid=:refid")
defer streamutil.Close(stmt)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare update for pin %s", pin.RefID), err)
return
}
_, err = stmt.Exec(&pin)
if err != nil {
log.Error(fmt.Sprintf("Unable to execute update for pin %s", pin.RefID), err)
return
}
return
}
// UpdatePinSequence updates existing pinned item sequence number
func (p *Persister) UpdatePinSequence(pinID string, sequence int) (err error) {
stmt, err := p.Context.Transaction.Preparex("UPDATE pin SET sequence=?, revised=? WHERE orgid=? AND userid=? AND refid=?")
defer streamutil.Close(stmt)
if err != nil {
log.Error(fmt.Sprintf("Unable to prepare update for pin sequence %s", pinID), err)
return
}
_, err = stmt.Exec(sequence, time.Now().UTC(), p.Context.OrgID, p.Context.UserID, pinID)
return
}
// DeletePin removes folder from the store.
func (p *Persister) DeletePin(id string) (rows int64, err error) {
return p.Base.DeleteConstrained(p.Context.Transaction, "pin", p.Context.OrgID, id)
}
// DeletePinnedSpace removes any pins for specified space.
func (p *Persister) DeletePinnedSpace(spaceID string) (rows int64, err error) {
return p.Base.DeleteWhere(p.Context.Transaction, fmt.Sprintf("DELETE FROM pin WHERE orgid=\"%s\" AND labelid=\"%s\"", p.Context.OrgID, spaceID))
}
// DeletePinnedDocument removes any pins for specified document.
func (p *Persister) DeletePinnedDocument(documentID string) (rows int64, err error) {
return p.Base.DeleteWhere(p.Context.Transaction, fmt.Sprintf("DELETE FROM pin WHERE orgid=\"%s\" AND documentid=\"%s\"", p.Context.OrgID, documentID))
}

View file

@ -1,468 +0,0 @@
// 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/documize/community/core/api/entity"
"github.com/documize/community/core/log"
"github.com/documize/community/core/streamutil"
"github.com/documize/community/core/stringutil"
_ "github.com/go-sql-driver/mysql" // required for sqlx but not directly called
"github.com/jmoiron/sqlx"
)
// 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 := stringutil.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 streamutil.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 := stringutil.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 streamutil.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 streamutil.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 streamutil.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 streamutil.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 streamutil.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 streamutil.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 streamutil.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 streamutil.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
}

View file

@ -1,68 +0,0 @@
// 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/uniqueid"
)
// SetupPersister prepares context for database activity.
func SetupPersister() (*Persister, error) {
var err error
c := Context{
Authenticated: true, // bool
Guest: false, // bool
Administrator: true, // bool
Editor: true, // bool
UserID: uniqueid.Generate(), // string
OrgID: uniqueid.Generate(), // 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
}
// SetupOrganization creates "tenant" record in database.
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
}

View file

@ -1,320 +0,0 @@
// 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/streamutil"
)
// 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, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
defer streamutil.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, "", 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
}
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, global, password, salt, reset, created, revised FROM user WHERE refid=?")
defer streamutil.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, global, password, salt, reset, created, revised FROM user WHERE TRIM(LOWER(email))=?")
defer streamutil.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.global, u.password, u.salt, u.reset, 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 streamutil.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, global, password, salt, reset, created, revised FROM user WHERE reset=?")
defer streamutil.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, global, password, salt, reset, created, revised FROM user WHERE salt=?")
defer streamutil.Close(stmt)
if err != nil {
return
}
err = stmt.Get(&user, serial)
if err != nil {
return
}
return
}
// GetActiveUsersForOrganization returns a slice containing of active user records for the organization
// identified in the Persister.
func (p *Persister) GetActiveUsersForOrganization() (users []entity.User, err error) {
err = Db.Select(&users,
`SELECT u.id, u.refid, u.firstname, u.lastname, u.email, u.initials, u.password, u.salt, u.reset, u.created, u.revised
FROM user u
WHERE u.refid IN (SELECT userid FROM account WHERE orgid = ? AND active=1) ORDER BY u.firstname,u.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
}
// 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, 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 u.id, u.refid, u.firstname, u.lastname, u.email, u.initials, u.password, u.salt, u.reset, u.created, u.revised
FROM user u, account a
WHERE u.refid IN (SELECT userid from labelrole WHERE orgid=? AND labelid=?)
AND a.orgid=? AND u.refid = a.userid AND a.active=1
ORDER BY u.firstname, u.lastname`,
p.Context.OrgID, folderID, p.Context.OrgID)
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, initials=:initials WHERE refid=:refid")
defer streamutil.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
}
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 streamutil.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
}
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 streamutil.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
}
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 streamutil.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
}
return
}
// CountActiveUsers returns the number of active users in the system.
func CountActiveUsers() (c int) {
row := Db.QueryRow("SELECT count(*) FROM user u WHERE u.refid IN (SELECT userid FROM account WHERE active=1)")
err := row.Scan(&c)
if err != nil && err != sql.ErrNoRows {
log.Error("CountActiveUsers", err)
return 0
}
if err == sql.ErrNoRows {
return 0
}
return
}

View file

@ -1,218 +0,0 @@
// 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)
}
*/

View file

@ -1,124 +0,0 @@
// 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 store provides the implementation for a file system based storage provider.
// This enables all document upload previews to be processed AND stored locally.
package store
import (
"errors"
"fmt"
"io/ioutil"
"os"
"strings"
"github.com/documize/community/core/api/convert"
api "github.com/documize/community/core/convapi"
"github.com/documize/community/core/log"
)
var folderPath string
func init() {
tempDir := os.TempDir()
if !strings.HasSuffix(tempDir, string(os.PathSeparator)) {
tempDir += string(os.PathSeparator)
}
folderPath = tempDir + "documize" + string(os.PathSeparator) + "_uploads" + string(os.PathSeparator)
log.Info("Temporary upload directory: " + folderPath)
log.IfErr(os.MkdirAll(folderPath, os.ModePerm))
}
// LocalStorageProvider provides an implementation of StorageProvider.
type LocalStorageProvider struct {
}
// Upload a flie and store it locally.
func (store *LocalStorageProvider) Upload(job string, filename string, file []byte) (err error) {
destination := folderPath + job + string(os.PathSeparator)
err = os.MkdirAll(destination, os.ModePerm)
if err != nil {
log.Error(fmt.Sprintf("Cannot create local folder %s", destination), err)
return err
}
err = ioutil.WriteFile(destination+filename, file, 0666)
if err != nil {
log.Error(fmt.Sprintf("Cannot write to local file %s", destination+filename), err)
return err
}
return nil
}
// Convert a file from its native format into Documize internal format.
func (store *LocalStorageProvider) Convert(params api.ConversionJobRequest) (filename string, fileResult *api.DocumentConversionResponse, err error) {
fileResult = &api.DocumentConversionResponse{}
err = nil
path := folderPath
if params.Job == "" {
return filename, fileResult, errors.New("no job to convert")
}
inputFolder := path + params.Job + string(os.PathSeparator)
list, err := ioutil.ReadDir(inputFolder)
if err != nil {
return filename, fileResult, err
}
if len(list) == 0 {
return filename, fileResult, errors.New("no file to convert")
}
// remove temporary directory on exit
defer func() { log.IfErr(os.RemoveAll(inputFolder)) }()
for _, v := range list {
if v.Size() > 0 && !strings.HasPrefix(v.Name(), ".") && v.Mode().IsRegular() {
filename = inputFolder + v.Name()
log.Info(fmt.Sprintf("Fetching document %s", filename))
fileData, err := ioutil.ReadFile(filename)
if err != nil {
log.Error(fmt.Sprintf("Unable to fetch document %s", filename), err)
return filename, fileResult, err
}
if len(fileData) > 0 {
fileRequest := api.DocumentConversionRequest{}
fileRequest.Filename = filename
fileRequest.Filedata = fileData
fileRequest.PageBreakLevel = params.IndexDepth
fileRequest.LicenseKey = params.LicenseKey
fileRequest.LicenseSignature = params.LicenseSignature
fileRequest.ServiceEndpoint = params.ServiceEndpoint
//fileRequest.Job = params.OrgID + string(os.PathSeparator) + params.Job
//fileRequest.OrgID = params.OrgID
bits := strings.Split(filename, ".")
xtn := strings.ToLower(bits[len(bits)-1])
fileResult, err = convert.Convert(nil, xtn, &fileRequest)
return filename, fileResult, err
}
}
}
return filename, fileResult, nil
}

View file

@ -1,90 +0,0 @@
// 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 store
import (
"github.com/documize/community/core/api/plugins"
"github.com/documize/community/core/api/util"
api "github.com/documize/community/core/convapi"
"github.com/documize/community/core/log"
"io/ioutil"
"os"
"strings"
"testing"
)
var lsp LocalStorageProvider
func TestUpload(t *testing.T) {
jb := "job" + uniqueid.Generate()
fn := "file.txt"
cont := "content\n"
err := lsp.Upload(jb, fn, []byte(cont))
if err != nil {
t.Error(err)
}
b, e := ioutil.ReadFile(folderPath + jb + string(os.PathSeparator) + fn)
if e != nil {
t.Error(e)
}
if string(b) != cont {
t.Error("wrong content:" + string(b))
}
}
func TestConvert(t *testing.T) {
_, _, err :=
lsp.Convert(api.ConversionJobRequest{})
if err == nil {
t.Error("there should have been a convert error")
}
err = plugins.LibSetup()
if err == nil {
// t.Error("did not error with missing config.json")
}
defer log.IfErr(plugins.Lib.KillSubProcs())
jb := "job" + uniqueid.Generate()
_, _, err =
lsp.Convert(api.ConversionJobRequest{
Job: jb,
IndexDepth: 9,
OrgID: "Documize",
})
if err == nil {
t.Error("there should have been an error - directory not found")
}
fn := "content.html"
cont := "content\n"
err = lsp.Upload(jb, fn, []byte(cont))
if err != nil {
t.Error(err)
}
filename, fileResult, err :=
lsp.Convert(api.ConversionJobRequest{
Job: jb,
IndexDepth: 9,
OrgID: "Documize",
})
if err != nil {
t.Error(err)
}
if !strings.HasSuffix(filename, fn) {
t.Error("wrong filename:" + filename)
}
if fileResult.Excerpt != "content." {
t.Error("wrong excerpt:" + fileResult.Excerpt)
}
}

View file

@ -1,73 +0,0 @@
// 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 store
import (
"errors"
"strings"
"github.com/documize/community/core/api/entity"
api "github.com/documize/community/core/convapi"
"github.com/documize/community/core/stringutil"
)
// StorageProvider describes the interface for document conversion and take-on.
type StorageProvider interface {
Upload(job string, filename string, file []byte) (err error)
Convert(api.ConversionJobRequest) (filename string, fileResult *api.DocumentConversionResponse, err error)
}
// ConvertFileResult takes the results of a document upload and convert,
// and creates the outline of a database record suitable for inserting into the document
// table.
func ConvertFileResult(filename string, fileResult *api.DocumentConversionResponse) (document entity.Document) {
document = entity.Document{}
document.RefID = ""
document.OrgID = ""
document.LabelID = ""
document.Job = ""
document.Location = filename
if fileResult != nil {
if len(fileResult.Pages) > 0 {
document.Title = fileResult.Pages[0].Title
document.Slug = stringutil.MakeSlug(fileResult.Pages[0].Title)
}
document.Excerpt = fileResult.Excerpt
}
document.Tags = "" // now a # separated list of tag-words, rather than JSON
return document
}
// ExportAs takes a target extension name and html to create an exported file.
// If the target extension is "html" it simply returns the given html suitably wrapped,
// otherwise it runs the "Export" plugin for the given target extension name.
func ExportAs(xtn, html string) (*api.DocumentExport, error) {
if strings.ToLower(xtn) == "html" {
return &api.DocumentExport{File: []byte(html), Format: "html"}, nil
}
return nil, errors.New("Only 'HTML' export is supported")
/* This functionality removed for now
fileI, err := plugins.Lib.Run(nil, "Export", xtn, []byte(html))
if err != nil {
log.Error("ExportAs failed", err)
return nil, err
}
return fileI.(*api.DocumentExport), nil
*/
}

View file

@ -1,63 +0,0 @@
// 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 store
import (
"testing"
"github.com/documize/community/core/api/plugins"
api "github.com/documize/community/core/convapi"
"github.com/documize/community/core/log"
)
func TestExportAs(t *testing.T) {
err := plugins.LibSetup()
if err == nil {
// t.Error("did not error with missing config.json")
}
defer log.IfErr(plugins.Lib.KillSubProcs())
htm := "<p>some html</p>"
exp, err := ExportAs("html", htm)
if err != nil {
t.Error(err)
}
if string(exp.File) != htm {
t.Error("html returned not as expected: " + string(exp.File))
}
// log.TestIfErr = true
_, err = ExportAs("XXXXX", htm)
if err == nil /* || log.TestIfErr */ {
t.Error("did not error as expected")
}
}
func TestConvertFileResult(t *testing.T) {
fn := "afile"
doc := ConvertFileResult(fn, &api.DocumentConversionResponse{}) // should not panic
if doc.Location != fn {
t.Error("filename not passed through")
}
doc = ConvertFileResult(fn, nil) // should not panic
if doc.Location != fn {
t.Error("filename not passed through")
}
rj := "Romeo & Juliet"
doc = ConvertFileResult(fn, &api.DocumentConversionResponse{
Pages: []api.Page{{Title: rj}},
})
if doc.Title != rj || doc.Slug != "romeo-juliet" {
t.Error("title not passed through correctly")
}
}

View file

@ -1,188 +0,0 @@
// 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 util
import (
"encoding/json"
"fmt"
"net/http"
"github.com/documize/community/core/log"
)
// WritePayloadError error handling
func WritePayloadError(w http.ResponseWriter, method string, err error) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusBadRequest)
_, err2 := w.Write([]byte("{Error: 'Bad payload'}"))
log.IfErr(err2)
log.Error(fmt.Sprintf("Unable to decode HTML request body for method %s", method), err)
}
// WriteTransactionError error handling
func WriteTransactionError(w http.ResponseWriter, method string, err error) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusInternalServerError)
_, err2 := w.Write([]byte("{Error: 'No transaction'}"))
log.IfErr(err2)
log.Error(fmt.Sprintf("Unable to get database transaction for method %s", method), err)
}
// WriteMissingDataError error handling
func WriteMissingDataError(w http.ResponseWriter, method, parameter string) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusBadRequest)
_, err := w.Write([]byte("{Error: 'Missing data'}"))
log.IfErr(err)
log.Info(fmt.Sprintf("Missing data %s for method %s", parameter, method))
}
// WriteNotFoundError error handling
func WriteNotFoundError(w http.ResponseWriter, method string, id string) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusNotFound)
_, err := w.Write([]byte("{Error: 'Not found'}"))
log.IfErr(err)
log.Info(fmt.Sprintf("Not found ID %s for method %s", id, method))
}
// WriteGeneralSQLError error handling
func WriteGeneralSQLError(w http.ResponseWriter, method string, err error) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusBadRequest)
_, err2 := w.Write([]byte("{Error: 'SQL error'}"))
log.IfErr(err2)
log.Error(fmt.Sprintf("General SQL error for method %s", method), err)
}
// WriteJSONMarshalError error handling
func WriteJSONMarshalError(w http.ResponseWriter, method, entity string, err error) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusBadRequest)
_, err2 := w.Write([]byte("{Error: 'JSON marshal failed'}"))
log.IfErr(err2)
log.Error(fmt.Sprintf("Failed to JSON marshal %s for method %s", entity, method), err)
}
// WriteServerError error handling
func WriteServerError(w http.ResponseWriter, method string, err error) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusBadRequest)
_, err2 := w.Write([]byte("{Error: 'Internal server error'}"))
log.IfErr(err2)
log.Error(fmt.Sprintf("Internal server error for method %s", method), err)
}
// WriteDuplicateError error handling
func WriteDuplicateError(w http.ResponseWriter, method, entity string) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusConflict)
_, err := w.Write([]byte("{Error: 'Duplicate record'}"))
log.IfErr(err)
log.Info(fmt.Sprintf("Duplicate %s record detected for method %s", entity, method))
}
// WriteUnauthorizedError error handling
func WriteUnauthorizedError(w http.ResponseWriter) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusUnauthorized)
_, err := w.Write([]byte("{Error: 'Unauthorized'}"))
log.IfErr(err)
}
// WriteForbiddenError error handling
func WriteForbiddenError(w http.ResponseWriter) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusForbidden)
_, err := w.Write([]byte("{Error: 'Forbidden'}"))
log.IfErr(err)
}
// WriteBadRequestError error handling
func WriteBadRequestError(w http.ResponseWriter, method, message string) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusBadRequest)
_, err := w.Write([]byte("{Error: 'Bad Request'}"))
log.IfErr(err)
log.Info(fmt.Sprintf("Bad Request %s for method %s", message, method))
}
// WriteSuccessBytes dumps bytes to HTTP response
func WriteSuccessBytes(w http.ResponseWriter, data []byte) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusOK)
_, err := w.Write(data)
log.IfErr(err)
}
// WriteSuccessString writes string to HTTP response
func WriteSuccessString(w http.ResponseWriter, data string) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusOK)
_, err := w.Write([]byte(data))
log.IfErr(err)
}
// WriteSuccessEmptyJSON writes empty JSON HTTP response
func WriteSuccessEmptyJSON(w http.ResponseWriter) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusOK)
_, err := w.Write([]byte("{}"))
log.IfErr(err)
}
// WriteMarshalError error handling
func WriteMarshalError(w http.ResponseWriter, err error) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusBadRequest)
_, err2 := w.Write([]byte("{Error: 'JSON marshal failed'}"))
log.IfErr(err2)
}
// WriteJSON serializes data as JSON to HTTP response.
func WriteJSON(w http.ResponseWriter, v interface{}) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusOK)
j, err := json.Marshal(v)
if err != nil {
WriteMarshalError(w, err)
return
}
_, err = w.Write(j)
log.IfErr(err)
}
// WriteRequestError sends custom error message.
func WriteRequestError(w http.ResponseWriter, msg string) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusBadRequest)
_, err := w.Write([]byte(fmt.Sprintf("{Error: '%s'}", msg)))
log.IfErr(err)
}
// WriteBadLicense writes 402 when license is invalid
func WriteBadLicense(w http.ResponseWriter) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusPaymentRequired)
var e struct {
Reason string
}
e.Reason = "invalid or expired Documize license"
j, _ := json.Marshal(e)
_, err := w.Write(j)
log.IfErr(err)
}

View file

@ -20,7 +20,6 @@ import (
"github.com/documize/community/core/env" "github.com/documize/community/core/env"
"github.com/documize/community/core/streamutil" "github.com/documize/community/core/streamutil"
"github.com/documize/community/server/web" "github.com/documize/community/server/web"
"github.com/jmoiron/sqlx"
) )
// sql variantsa // sql variantsa
@ -30,14 +29,9 @@ const sqlVariantMariaDB string = "MariaDB"
var dbCheckOK bool // default false var dbCheckOK bool // default false
// dbPtr is a pointer to the central connection to the database, used by all database requests.
var dbPtr *sqlx.DB
// Check that the database is configured correctly and that all the required tables exist. // Check that the database is configured correctly and that all the required tables exist.
// It must be the first function called in this package. // It must be the first function called in this package.
func Check(runtime *env.Runtime) bool { func Check(runtime *env.Runtime) bool {
dbPtr = runtime.Db
runtime.Log.Info("Database checks: started") runtime.Log.Info("Database checks: started")
csBits := strings.Split(runtime.Flags.DBConn, "/") csBits := strings.Split(runtime.Flags.DBConn, "/")

View file

@ -18,63 +18,34 @@ import (
"strings" "strings"
"time" "time"
"github.com/documize/community/core/api"
"github.com/documize/community/core/env" "github.com/documize/community/core/env"
"github.com/documize/community/core/log"
"github.com/documize/community/core/secrets" "github.com/documize/community/core/secrets"
"github.com/documize/community/core/stringutil" "github.com/documize/community/core/stringutil"
"github.com/documize/community/core/uniqueid" "github.com/documize/community/core/uniqueid"
"github.com/documize/community/domain"
"github.com/documize/community/server/web" "github.com/documize/community/server/web"
) )
// runSQL creates a transaction per call // Handler contains the runtime information such as logging and database.
func runSQL(sql string) (id uint64, err error) { type Handler struct {
Runtime *env.Runtime
if strings.TrimSpace(sql) == "" { Store *domain.Store
return 0, nil
}
tx, err := (*dbPtr).Beginx()
if err != nil {
log.Error("runSql - failed to get transaction", err)
return
}
result, err := tx.Exec(sql)
if err != nil {
log.IfErr(tx.Rollback())
log.Error("runSql - unable to run sql", err)
return
}
if err = tx.Commit(); err != nil {
log.Error("runSql - unable to commit sql", err)
return
}
tempID, _ := result.LastInsertId()
id = uint64(tempID)
return
} }
// Create the tables in a blank database // Create the tables in a blank database
func Create(w http.ResponseWriter, r *http.Request) { func (h *Handler) Create(w http.ResponseWriter, r *http.Request) {
defer func() { defer func() {
target := "/setup" target := "/setup"
status := http.StatusBadRequest status := http.StatusBadRequest
if api.Runtime.Flags.SiteMode == env.SiteModeNormal { if h.Runtime.Flags.SiteMode == env.SiteModeNormal {
target = "/" target = "/"
status = http.StatusOK status = http.StatusOK
} }
req, err := http.NewRequest("GET", target, nil) req, err := http.NewRequest("GET", target, nil)
if err != nil { if err != nil {
log.Error("database.Create()'s error in defer ", err) h.Runtime.Log.Error("database.Create()'s error in defer ", err)
} }
http.Redirect(w, req, target, status) http.Redirect(w, req, target, status)
@ -82,20 +53,20 @@ func Create(w http.ResponseWriter, r *http.Request) {
err := r.ParseForm() err := r.ParseForm()
if err != nil { if err != nil {
log.Error("database.Create()'s r.ParseForm()", err) h.Runtime.Log.Error("database.Create()'s r.ParseForm()", err)
return return
} }
dbname := r.Form.Get("dbname") dbname := r.Form.Get("dbname")
dbhash := r.Form.Get("dbhash") dbhash := r.Form.Get("dbhash")
fmt.Println(dbname) h.Runtime.Log.Info(dbname)
fmt.Println(dbhash) h.Runtime.Log.Info(dbhash)
fmt.Println(web.SiteInfo.DBname) h.Runtime.Log.Info(web.SiteInfo.DBname)
fmt.Println(web.SiteInfo.DBhash) h.Runtime.Log.Info(web.SiteInfo.DBhash)
if dbname != web.SiteInfo.DBname || dbhash != web.SiteInfo.DBhash { if dbname != web.SiteInfo.DBname || dbhash != web.SiteInfo.DBhash {
log.Error("database.Create()'s security credentials error ", errors.New("bad db name or validation code")) h.Runtime.Log.Error("database.Create()'s security credentials error ", errors.New("bad db name or validation code"))
return return
} }
@ -118,23 +89,22 @@ func Create(w http.ResponseWriter, r *http.Request) {
details.Password == "" || details.Password == "" ||
details.Firstname == "" || details.Firstname == "" ||
details.Lastname == "" { details.Lastname == "" {
log.Error("database.Create() error ", h.Runtime.Log.Error("database.Create() error ", errors.New("required field in database set-up form blank"))
errors.New("required field in database set-up form blank"))
return return
} }
if err = Migrate(api.Runtime, false /* no tables exist yet */); err != nil { if err = Migrate(h.Runtime, false /* no tables exist yet */); err != nil {
log.Error("database.Create()", err) h.Runtime.Log.Error("database.Create()", err)
return return
} }
err = setupAccount(details, secrets.GenerateSalt()) err = setupAccount(h.Runtime, details, secrets.GenerateSalt())
if err != nil { if err != nil {
log.Error("database.Create()", err) h.Runtime.Log.Error("database.Create()", err)
return return
} }
api.Runtime.Flags.SiteMode = env.SiteModeNormal h.Runtime.Flags.SiteMode = env.SiteModeNormal
} }
// The result of completing the onboarding process. // The result of completing the onboarding process.
@ -152,7 +122,7 @@ type onboardRequest struct {
// setupAccount prepares the database for a newly onboard customer. // setupAccount prepares the database for a newly onboard customer.
// Once done, they can then login and use Documize. // Once done, they can then login and use Documize.
func setupAccount(completion onboardRequest, serial string) (err error) { func setupAccount(rt *env.Runtime, completion onboardRequest, serial string) (err error) {
//accountTitle := "This is where you will find documentation for your all projects. You can customize this message from the settings screen." //accountTitle := "This is where you will find documentation for your all projects. You can customize this message from the settings screen."
salt := secrets.GenerateSalt() salt := secrets.GenerateSalt()
password := secrets.GeneratePassword(completion.Password, salt) password := secrets.GeneratePassword(completion.Password, salt)
@ -162,10 +132,10 @@ func setupAccount(completion onboardRequest, serial string) (err error) {
sql := fmt.Sprintf("insert into organization (refid, company, title, message, domain, email, serial) values (\"%s\", \"%s\", \"%s\", \"%s\", \"%s\", \"%s\", \"%s\")", sql := fmt.Sprintf("insert into organization (refid, company, title, message, domain, email, serial) values (\"%s\", \"%s\", \"%s\", \"%s\", \"%s\", \"%s\", \"%s\")",
orgID, completion.Company, completion.CompanyLong, completion.Message, completion.URL, completion.Email, serial) orgID, completion.Company, completion.CompanyLong, completion.Message, completion.URL, completion.Email, serial)
_, err = runSQL(sql) _, err = runSQL(rt, sql)
if err != nil { if err != nil {
log.Error("Failed to insert into organization", err) rt.Log.Error("Failed to insert into organization", err)
return return
} }
@ -173,39 +143,70 @@ func setupAccount(completion onboardRequest, serial string) (err error) {
sql = fmt.Sprintf("insert into user (refid, firstname, lastname, email, initials, salt, password, global) values (\"%s\",\"%s\", \"%s\", \"%s\", \"%s\", \"%s\", \"%s\", 1)", sql = fmt.Sprintf("insert into user (refid, firstname, lastname, email, initials, salt, password, global) values (\"%s\",\"%s\", \"%s\", \"%s\", \"%s\", \"%s\", \"%s\", 1)",
userID, completion.Firstname, completion.Lastname, completion.Email, stringutil.MakeInitials(completion.Firstname, completion.Lastname), salt, password) userID, completion.Firstname, completion.Lastname, completion.Email, stringutil.MakeInitials(completion.Firstname, completion.Lastname), salt, password)
_, err = runSQL(sql) _, err = runSQL(rt, sql)
if err != nil { if err != nil {
log.Error("Failed with error", err) rt.Log.Error("Failed with error", err)
return err return err
} }
// Link user to organization. // Link user to organization.
accountID := uniqueid.Generate() accountID := uniqueid.Generate()
sql = fmt.Sprintf("insert into account (refid, userid, orgid, admin, editor) values (\"%s\", \"%s\", \"%s\",1, 1)", accountID, userID, orgID) sql = fmt.Sprintf("insert into account (refid, userid, orgid, admin, editor) values (\"%s\", \"%s\", \"%s\",1, 1)", accountID, userID, orgID)
_, err = runSQL(sql) _, err = runSQL(rt, sql)
if err != nil { if err != nil {
log.Error("Failed with error", err) rt.Log.Error("Failed with error", err)
return err return err
} }
// Set up default labels for main collection. // Set up default labels for main collection.
labelID := uniqueid.Generate() labelID := uniqueid.Generate()
sql = fmt.Sprintf("insert into label (refid, orgid, label, type, userid) values (\"%s\", \"%s\", \"My Project\", 2, \"%s\")", labelID, orgID, userID) sql = fmt.Sprintf("insert into label (refid, orgid, label, type, userid) values (\"%s\", \"%s\", \"My Project\", 2, \"%s\")", labelID, orgID, userID)
_, err = runSQL(sql) _, err = runSQL(rt, sql)
if err != nil { if err != nil {
log.Error("insert into label failed", err) rt.Log.Error("insert into label failed", err)
} }
labelRoleID := uniqueid.Generate() labelRoleID := uniqueid.Generate()
sql = fmt.Sprintf("insert into labelrole (refid, labelid, orgid, userid, canview, canedit) values (\"%s\", \"%s\", \"%s\", \"%s\", 1, 1)", labelRoleID, labelID, orgID, userID) sql = fmt.Sprintf("insert into labelrole (refid, labelid, orgid, userid, canview, canedit) values (\"%s\", \"%s\", \"%s\", \"%s\", 1, 1)", labelRoleID, labelID, orgID, userID)
_, err = runSQL(sql) _, err = runSQL(rt, sql)
if err != nil { if err != nil {
log.Error("insert into labelrole failed", err) rt.Log.Error("insert into labelrole failed", err)
} }
return return
} }
// runSQL creates a transaction per call
func runSQL(rt *env.Runtime, sql string) (id uint64, err error) {
if strings.TrimSpace(sql) == "" {
return 0, nil
}
tx, err := rt.Db.Beginx()
if err != nil {
rt.Log.Error("runSql - failed to get transaction", err)
return
}
result, err := tx.Exec(sql)
if err != nil {
tx.Rollback()
rt.Log.Error("runSql - unable to run sql", err)
return
}
if err = tx.Commit(); err != nil {
rt.Log.Error("runSql - unable to commit sql", err)
return
}
tempID, _ := result.LastInsertId()
id = uint64(tempID)
return
}

View file

@ -69,7 +69,7 @@ func migrations(lastMigration string) (migrationsT, error) {
} }
// migrate the database as required, by applying the migrations. // migrate the database as required, by applying the migrations.
func (m migrationsT) migrate(runtime env.Runtime, tx *sqlx.Tx) error { func (m migrationsT) migrate(runtime *env.Runtime, tx *sqlx.Tx) error {
for _, v := range m { for _, v := range m {
runtime.Log.Info("Processing migration file: " + v) runtime.Log.Info("Processing migration file: " + v)
@ -96,7 +96,7 @@ func (m migrationsT) migrate(runtime env.Runtime, tx *sqlx.Tx) error {
return nil return nil
} }
func lockDB(runtime env.Runtime) (bool, error) { func lockDB(runtime *env.Runtime) (bool, error) {
b := make([]byte, 2) b := make([]byte, 2)
_, err := rand.Read(b) _, err := rand.Read(b)
if err != nil { if err != nil {
@ -105,7 +105,7 @@ func lockDB(runtime env.Runtime) (bool, error) {
wait := ((time.Duration(b[0]) << 8) | time.Duration(b[1])) * time.Millisecond / 10 // up to 6.5 secs wait wait := ((time.Duration(b[0]) << 8) | time.Duration(b[1])) * time.Millisecond / 10 // up to 6.5 secs wait
time.Sleep(wait) time.Sleep(wait)
tx, err := (*dbPtr).Beginx() tx, err := runtime.Db.Beginx()
if err != nil { if err != nil {
return false, err return false, err
} }
@ -138,8 +138,8 @@ func lockDB(runtime env.Runtime) (bool, error) {
return true, err // success! return true, err // success!
} }
func unlockDB() error { func unlockDB(rt *env.Runtime) error {
tx, err := (*dbPtr).Beginx() tx, err := rt.Db.Beginx()
if err != nil { if err != nil {
return err return err
} }
@ -150,9 +150,9 @@ func unlockDB() error {
return tx.Commit() return tx.Commit()
} }
func migrateEnd(runtime env.Runtime, tx *sqlx.Tx, err error, amLeader bool) error { func migrateEnd(runtime *env.Runtime, tx *sqlx.Tx, err error, amLeader bool) error {
if amLeader { if amLeader {
defer func() { unlockDB() }() defer func() { unlockDB(runtime) }()
if tx != nil { if tx != nil {
if err == nil { if err == nil {
tx.Commit() tx.Commit()
@ -188,7 +188,7 @@ func getLastMigration(tx *sqlx.Tx) (lastMigration string, err error) {
} }
// Migrate the database as required, consolidated action. // Migrate the database as required, consolidated action.
func Migrate(runtime env.Runtime, ConfigTableExists bool) error { func Migrate(runtime *env.Runtime, ConfigTableExists bool) error {
amLeader := false amLeader := false
if ConfigTableExists { if ConfigTableExists {
@ -201,7 +201,7 @@ func Migrate(runtime env.Runtime, ConfigTableExists bool) error {
amLeader = true // what else can you do? amLeader = true // what else can you do?
} }
tx, err := (*dbPtr).Beginx() tx, err := runtime.Db.Beginx()
if err != nil { if err != nil {
return migrateEnd(runtime, tx, err, amLeader) return migrateEnd(runtime, tx, err, amLeader)
} }
@ -237,7 +237,7 @@ func Migrate(runtime env.Runtime, ConfigTableExists bool) error {
time.Sleep(time.Second) time.Sleep(time.Second)
runtime.Log.Info("Waiting for database migration completion") runtime.Log.Info("Waiting for database migration completion")
tx.Rollback() // ignore error tx.Rollback() // ignore error
tx, err := (*dbPtr).Beginx() // need this in order to see the changed situation since last tx tx, err := runtime.Db.Beginx() // need this in order to see the changed situation since last tx
if err != nil { if err != nil {
return migrateEnd(runtime, tx, err, amLeader) return migrateEnd(runtime, tx, err, amLeader)
} }

View file

@ -117,7 +117,7 @@ func (h *Handler) Login(w http.ResponseWriter, r *http.Request) {
func (h *Handler) ValidateToken(w http.ResponseWriter, r *http.Request) { func (h *Handler) ValidateToken(w http.ResponseWriter, r *http.Request) {
// TODO should this go after token validation? // TODO should this go after token validation?
if s := r.URL.Query().Get("section"); s != "" { if s := r.URL.Query().Get("section"); s != "" {
if err := provider.Callback(s, w, r); err != nil { if err := provider.Callback(s, h.Runtime, h.Store, w, r); err != nil {
h.Runtime.Log.Error("section validation failure", err) h.Runtime.Log.Error("section validation failure", err)
w.WriteHeader(http.StatusUnauthorized) w.WriteHeader(http.StatusUnauthorized)
} }

View file

@ -19,13 +19,13 @@ import (
"net/http" "net/http"
"strings" "strings"
"github.com/documize/community/core/api/store"
api "github.com/documize/community/core/convapi" api "github.com/documize/community/core/convapi"
"github.com/documize/community/core/request" "github.com/documize/community/core/request"
"github.com/documize/community/core/response" "github.com/documize/community/core/response"
"github.com/documize/community/core/stringutil" "github.com/documize/community/core/stringutil"
"github.com/documize/community/core/uniqueid" "github.com/documize/community/core/uniqueid"
"github.com/documize/community/domain" "github.com/documize/community/domain"
"github.com/documize/community/domain/conversion/store"
"github.com/documize/community/domain/document" "github.com/documize/community/domain/document"
"github.com/documize/community/model/activity" "github.com/documize/community/model/activity"
"github.com/documize/community/model/attachment" "github.com/documize/community/model/attachment"
@ -36,7 +36,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
) )
var storageProvider store.StorageProvider var storageProvider StorageProvider
func init() { func init() {
storageProvider = new(store.LocalStorageProvider) storageProvider = new(store.LocalStorageProvider)
@ -91,8 +91,8 @@ func (h *Handler) convert(w http.ResponseWriter, r *http.Request, job, folderID
method := "conversion.upload" method := "conversion.upload"
ctx := domain.GetRequestContext(r) ctx := domain.GetRequestContext(r)
licenseKey := h.Store.Setting.Get(ctx, "EDITION-LICENSE", "key") licenseKey := h.Store.Setting.Get("EDITION-LICENSE", "key")
licenseSignature := h.Store.Setting.Get(ctx, "EDITION-LICENSE", "signature") licenseSignature := h.Store.Setting.Get("EDITION-LICENSE", "signature")
k, _ := hex.DecodeString(licenseKey) k, _ := hex.DecodeString(licenseKey)
s, _ := hex.DecodeString(licenseSignature) s, _ := hex.DecodeString(licenseSignature)

View file

@ -15,14 +15,12 @@ package store
import ( import (
"errors" "errors"
"fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"strings" "strings"
"github.com/documize/community/core/api/convert" "github.com/documize/community/core/api/convert"
api "github.com/documize/community/core/convapi" api "github.com/documize/community/core/convapi"
"github.com/documize/community/core/log"
) )
var folderPath string var folderPath string
@ -33,8 +31,7 @@ func init() {
tempDir += string(os.PathSeparator) tempDir += string(os.PathSeparator)
} }
folderPath = tempDir + "documize" + string(os.PathSeparator) + "_uploads" + string(os.PathSeparator) folderPath = tempDir + "documize" + string(os.PathSeparator) + "_uploads" + string(os.PathSeparator)
log.Info("Temporary upload directory: " + folderPath) os.MkdirAll(folderPath, os.ModePerm)
log.IfErr(os.MkdirAll(folderPath, os.ModePerm))
} }
// LocalStorageProvider provides an implementation of StorageProvider. // LocalStorageProvider provides an implementation of StorageProvider.
@ -48,14 +45,12 @@ func (store *LocalStorageProvider) Upload(job string, filename string, file []by
err = os.MkdirAll(destination, os.ModePerm) err = os.MkdirAll(destination, os.ModePerm)
if err != nil { if err != nil {
log.Error(fmt.Sprintf("Cannot create local folder %s", destination), err)
return err return err
} }
err = ioutil.WriteFile(destination+filename, file, 0666) err = ioutil.WriteFile(destination+filename, file, 0666)
if err != nil { if err != nil {
log.Error(fmt.Sprintf("Cannot write to local file %s", destination+filename), err)
return err return err
} }
@ -85,18 +80,15 @@ func (store *LocalStorageProvider) Convert(params api.ConversionJobRequest) (fil
} }
// remove temporary directory on exit // remove temporary directory on exit
defer func() { log.IfErr(os.RemoveAll(inputFolder)) }() defer func() { os.RemoveAll(inputFolder) }()
for _, v := range list { for _, v := range list {
if v.Size() > 0 && !strings.HasPrefix(v.Name(), ".") && v.Mode().IsRegular() { if v.Size() > 0 && !strings.HasPrefix(v.Name(), ".") && v.Mode().IsRegular() {
filename = inputFolder + v.Name() filename = inputFolder + v.Name()
log.Info(fmt.Sprintf("Fetching document %s", filename))
fileData, err := ioutil.ReadFile(filename) fileData, err := ioutil.ReadFile(filename)
if err != nil { if err != nil {
log.Error(fmt.Sprintf("Unable to fetch document %s", filename), err)
return filename, fileResult, err return filename, fileResult, err
} }

View file

@ -1,90 +0,0 @@
// 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 store
import (
"github.com/documize/community/core/api/plugins"
"github.com/documize/community/core/api/util"
api "github.com/documize/community/core/convapi"
"github.com/documize/community/core/log"
"io/ioutil"
"os"
"strings"
"testing"
)
var lsp LocalStorageProvider
func TestUpload(t *testing.T) {
jb := "job" + uniqueid.Generate()
fn := "file.txt"
cont := "content\n"
err := lsp.Upload(jb, fn, []byte(cont))
if err != nil {
t.Error(err)
}
b, e := ioutil.ReadFile(folderPath + jb + string(os.PathSeparator) + fn)
if e != nil {
t.Error(e)
}
if string(b) != cont {
t.Error("wrong content:" + string(b))
}
}
func TestConvert(t *testing.T) {
_, _, err :=
lsp.Convert(api.ConversionJobRequest{})
if err == nil {
t.Error("there should have been a convert error")
}
err = plugins.LibSetup()
if err == nil {
// t.Error("did not error with missing config.json")
}
defer log.IfErr(plugins.Lib.KillSubProcs())
jb := "job" + uniqueid.Generate()
_, _, err =
lsp.Convert(api.ConversionJobRequest{
Job: jb,
IndexDepth: 9,
OrgID: "Documize",
})
if err == nil {
t.Error("there should have been an error - directory not found")
}
fn := "content.html"
cont := "content\n"
err = lsp.Upload(jb, fn, []byte(cont))
if err != nil {
t.Error(err)
}
filename, fileResult, err :=
lsp.Convert(api.ConversionJobRequest{
Job: jb,
IndexDepth: 9,
OrgID: "Documize",
})
if err != nil {
t.Error(err)
}
if !strings.HasSuffix(filename, fn) {
t.Error("wrong filename:" + filename)
}
if fileResult.Excerpt != "content." {
t.Error("wrong excerpt:" + fileResult.Excerpt)
}
}

View file

@ -19,19 +19,27 @@ import (
"html/template" "html/template"
"net/smtp" "net/smtp"
"github.com/documize/community/core/api/request" "github.com/documize/community/core/env"
"github.com/documize/community/core/log" "github.com/documize/community/domain"
"github.com/documize/community/server/web" "github.com/documize/community/server/web"
) )
// Mailer provides emailing facilities
type Mailer struct {
Runtime *env.Runtime
Store *domain.Store
Context domain.RequestContext
credentials credentials
}
// InviteNewUser invites someone new providing credentials, explaining the product and stating who is inviting them. // InviteNewUser invites someone new providing credentials, explaining the product and stating who is inviting them.
func InviteNewUser(recipient, inviter, url, username, password string) { func (m *Mailer) InviteNewUser(recipient, inviter, url, username, password string) {
method := "InviteNewUser" method := "InviteNewUser"
m.loadCredentials()
file, err := web.ReadFile("mail/invite-new-user.html") file, err := web.ReadFile("mail/invite-new-user.html")
if err != nil { if err != nil {
log.Error(fmt.Sprintf("%s - unable to load email template", method), err) m.Runtime.Log.Error(fmt.Sprintf("%s - unable to load email template", method), err)
return return
} }
@ -45,7 +53,7 @@ func InviteNewUser(recipient, inviter, url, username, password string) {
subject := fmt.Sprintf("%s has invited you to Documize", inviter) subject := fmt.Sprintf("%s has invited you to Documize", inviter)
e := NewEmail() e := NewEmail()
e.From = SMTPCreds.SMTPsender() e.From = m.credentials.SMTPsender
e.To = []string{recipient} e.To = []string{recipient}
e.Subject = subject e.Subject = subject
@ -65,24 +73,23 @@ func InviteNewUser(recipient, inviter, url, username, password string) {
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
t := template.Must(template.New("emailTemplate").Parse(emailTemplate)) t := template.Must(template.New("emailTemplate").Parse(emailTemplate))
log.IfErr(t.Execute(buffer, &parameters)) t.Execute(buffer, &parameters)
e.HTML = buffer.Bytes() e.HTML = buffer.Bytes()
err = e.Send(GetHost(), GetAuth()) err = e.Send(m.GetHost(), m.GetAuth())
if err != nil { if err != nil {
log.Error(fmt.Sprintf("%s - unable to send email", method), err) m.Runtime.Log.Error(fmt.Sprintf("%s - unable to send email", method), err)
} }
} }
// InviteExistingUser invites a known user to an organization. // InviteExistingUser invites a known user to an organization.
func InviteExistingUser(recipient, inviter, url string) { func (m *Mailer) InviteExistingUser(recipient, inviter, url string) {
method := "InviteExistingUser" method := "InviteExistingUser"
m.loadCredentials()
file, err := web.ReadFile("mail/invite-existing-user.html") file, err := web.ReadFile("mail/invite-existing-user.html")
if err != nil { if err != nil {
log.Error(fmt.Sprintf("%s - unable to load email template", method), err) m.Runtime.Log.Error(fmt.Sprintf("%s - unable to load email template", method), err)
return return
} }
@ -96,7 +103,7 @@ func InviteExistingUser(recipient, inviter, url string) {
subject := fmt.Sprintf("%s has invited you to their Documize account", inviter) subject := fmt.Sprintf("%s has invited you to their Documize account", inviter)
e := NewEmail() e := NewEmail()
e.From = SMTPCreds.SMTPsender() e.From = m.credentials.SMTPsender
e.To = []string{recipient} e.To = []string{recipient}
e.Subject = subject e.Subject = subject
@ -112,24 +119,23 @@ func InviteExistingUser(recipient, inviter, url string) {
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
t := template.Must(template.New("emailTemplate").Parse(emailTemplate)) t := template.Must(template.New("emailTemplate").Parse(emailTemplate))
log.IfErr(t.Execute(buffer, &parameters)) t.Execute(buffer, &parameters)
e.HTML = buffer.Bytes() e.HTML = buffer.Bytes()
err = e.Send(GetHost(), GetAuth()) err = e.Send(m.GetHost(), m.GetAuth())
if err != nil { if err != nil {
log.Error(fmt.Sprintf("%s - unable to send email", method), err) m.Runtime.Log.Error(fmt.Sprintf("%s - unable to send email", method), err)
} }
} }
// PasswordReset sends a reset email with an embedded token. // PasswordReset sends a reset email with an embedded token.
func PasswordReset(recipient, url string) { func (m *Mailer) PasswordReset(recipient, url string) {
method := "PasswordReset" method := "PasswordReset"
m.loadCredentials()
file, err := web.ReadFile("mail/password-reset.html") file, err := web.ReadFile("mail/password-reset.html")
if err != nil { if err != nil {
log.Error(fmt.Sprintf("%s - unable to load email template", method), err) m.Runtime.Log.Error(fmt.Sprintf("%s - unable to load email template", method), err)
return return
} }
@ -138,7 +144,7 @@ func PasswordReset(recipient, url string) {
subject := "Documize password reset request" subject := "Documize password reset request"
e := NewEmail() e := NewEmail()
e.From = SMTPCreds.SMTPsender() //e.g. "Documize <hello@documize.com>" e.From = m.credentials.SMTPsender //e.g. "Documize <hello@documize.com>"
e.To = []string{recipient} e.To = []string{recipient}
e.Subject = subject e.Subject = subject
@ -152,24 +158,23 @@ func PasswordReset(recipient, url string) {
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
t := template.Must(template.New("emailTemplate").Parse(emailTemplate)) t := template.Must(template.New("emailTemplate").Parse(emailTemplate))
log.IfErr(t.Execute(buffer, &parameters)) t.Execute(buffer, &parameters)
e.HTML = buffer.Bytes() e.HTML = buffer.Bytes()
err = e.Send(GetHost(), GetAuth()) err = e.Send(m.GetHost(), m.GetAuth())
if err != nil { if err != nil {
log.Error(fmt.Sprintf("%s - unable to send email", method), err) m.Runtime.Log.Error(fmt.Sprintf("%s - unable to send email", method), err)
} }
} }
// ShareFolderExistingUser provides an existing user with a link to a newly shared folder. // ShareFolderExistingUser provides an existing user with a link to a newly shared folder.
func ShareFolderExistingUser(recipient, inviter, url, folder, intro string) { func (m *Mailer) ShareFolderExistingUser(recipient, inviter, url, folder, intro string) {
method := "ShareFolderExistingUser" method := "ShareFolderExistingUser"
m.loadCredentials()
file, err := web.ReadFile("mail/share-folder-existing-user.html") file, err := web.ReadFile("mail/share-folder-existing-user.html")
if err != nil { if err != nil {
log.Error(fmt.Sprintf("%s - unable to load email template", method), err) m.Runtime.Log.Error(fmt.Sprintf("%s - unable to load email template", method), err)
return return
} }
@ -183,7 +188,7 @@ func ShareFolderExistingUser(recipient, inviter, url, folder, intro string) {
subject := fmt.Sprintf("%s has shared %s with you", inviter, folder) subject := fmt.Sprintf("%s has shared %s with you", inviter, folder)
e := NewEmail() e := NewEmail()
e.From = SMTPCreds.SMTPsender() e.From = m.credentials.SMTPsender
e.To = []string{recipient} e.To = []string{recipient}
e.Subject = subject e.Subject = subject
@ -203,24 +208,23 @@ func ShareFolderExistingUser(recipient, inviter, url, folder, intro string) {
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
t := template.Must(template.New("emailTemplate").Parse(emailTemplate)) t := template.Must(template.New("emailTemplate").Parse(emailTemplate))
log.IfErr(t.Execute(buffer, &parameters)) t.Execute(buffer, &parameters)
e.HTML = buffer.Bytes() e.HTML = buffer.Bytes()
err = e.Send(GetHost(), GetAuth()) err = e.Send(m.GetHost(), m.GetAuth())
if err != nil { if err != nil {
log.Error(fmt.Sprintf("%s - unable to send email", method), err) m.Runtime.Log.Error(fmt.Sprintf("%s - unable to send email", method), err)
} }
} }
// ShareFolderNewUser invites new user providing credentials, explaining the product and stating who is inviting them. // ShareFolderNewUser invites new user providing credentials, explaining the product and stating who is inviting them.
func ShareFolderNewUser(recipient, inviter, url, folder, invitationMessage string) { func (m *Mailer) ShareFolderNewUser(recipient, inviter, url, folder, invitationMessage string) {
method := "ShareFolderNewUser" method := "ShareFolderNewUser"
m.loadCredentials()
file, err := web.ReadFile("mail/share-folder-new-user.html") file, err := web.ReadFile("mail/share-folder-new-user.html")
if err != nil { if err != nil {
log.Error(fmt.Sprintf("%s - unable to load email template", method), err) m.Runtime.Log.Error(fmt.Sprintf("%s - unable to load email template", method), err)
return return
} }
@ -234,7 +238,7 @@ func ShareFolderNewUser(recipient, inviter, url, folder, invitationMessage strin
subject := fmt.Sprintf("%s has shared %s with you on Documize", inviter, folder) subject := fmt.Sprintf("%s has shared %s with you on Documize", inviter, folder)
e := NewEmail() e := NewEmail()
e.From = SMTPCreds.SMTPsender() e.From = m.credentials.SMTPsender
e.To = []string{recipient} e.To = []string{recipient}
e.Subject = subject e.Subject = subject
@ -254,41 +258,39 @@ func ShareFolderNewUser(recipient, inviter, url, folder, invitationMessage strin
buffer := new(bytes.Buffer) buffer := new(bytes.Buffer)
t := template.Must(template.New("emailTemplate").Parse(emailTemplate)) t := template.Must(template.New("emailTemplate").Parse(emailTemplate))
log.IfErr(t.Execute(buffer, &parameters)) t.Execute(buffer, &parameters)
e.HTML = buffer.Bytes() e.HTML = buffer.Bytes()
err = e.Send(GetHost(), GetAuth()) err = e.Send(m.GetHost(), m.GetAuth())
if err != nil { if err != nil {
log.Error(fmt.Sprintf("%s - unable to send email", method), err) m.Runtime.Log.Error(fmt.Sprintf("%s - unable to send email", method), err)
} }
} }
// SMTPCreds return SMTP configuration. type credentials struct {
var SMTPCreds = struct{ SMTPuserid, SMTPpassword, SMTPhost, SMTPport, SMTPsender func() string }{ SMTPuserid string
func() string { return request.ConfigString("SMTP", "userid") }, SMTPpassword string
func() string { return request.ConfigString("SMTP", "password") }, SMTPhost string
func() string { return request.ConfigString("SMTP", "host") }, SMTPport string
func() string { SMTPsender string
r := request.ConfigString("SMTP", "port")
if r == "" {
return "587" // default port number
}
return r
},
func() string { return request.ConfigString("SMTP", "sender") },
} }
// GetAuth to return SMTP credentials // GetAuth to return SMTP authentication details
func GetAuth() smtp.Auth { func (m *Mailer) GetAuth() smtp.Auth {
a := smtp.PlainAuth("", SMTPCreds.SMTPuserid(), SMTPCreds.SMTPpassword(), SMTPCreds.SMTPhost()) a := smtp.PlainAuth("", m.credentials.SMTPuserid, m.credentials.SMTPpassword, m.credentials.SMTPhost)
//fmt.Printf("DEBUG GetAuth() = %#v\n", a)
return a return a
} }
// GetHost to return SMTP host details // GetHost to return SMTP host details
func GetHost() string { func (m *Mailer) GetHost() string {
h := SMTPCreds.SMTPhost() + ":" + SMTPCreds.SMTPport() h := m.credentials.SMTPhost + ":" + m.credentials.SMTPport
//fmt.Printf("DEBUG GetHost() = %#v\n", h)
return h return h
} }
func (m *Mailer) loadCredentials() {
m.credentials.SMTPuserid = m.Store.Setting.Get("SMTP", "userid")
m.credentials.SMTPpassword = m.Store.Setting.Get("SMTP", "password")
m.credentials.SMTPhost = m.Store.Setting.Get("SMTP", "host")
m.credentials.SMTPport = m.Store.Setting.Get("SMTP", "port")
m.credentials.SMTPsender = m.Store.Setting.Get("SMTP", "sender")
}

View file

@ -109,7 +109,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
return return
} }
output, ok := provider.Render(model.Page.ContentType, provider.NewContext(model.Meta.OrgID, model.Meta.UserID), model.Meta.Config, model.Meta.RawBody) output, ok := provider.Render(model.Page.ContentType, provider.NewContext(model.Meta.OrgID, model.Meta.UserID, ctx), model.Meta.Config, model.Meta.RawBody)
if !ok { if !ok {
h.Runtime.Log.Info("provider.Render could not find: " + model.Page.ContentType) h.Runtime.Log.Info("provider.Render could not find: " + model.Page.ContentType)
} }
@ -496,7 +496,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
return return
} }
output, ok := provider.Render(model.Page.ContentType, provider.NewContext(model.Meta.OrgID, oldPageMeta.UserID), model.Meta.Config, model.Meta.RawBody) output, ok := provider.Render(model.Page.ContentType, provider.NewContext(model.Meta.OrgID, oldPageMeta.UserID, ctx), model.Meta.Config, model.Meta.RawBody)
if !ok { if !ok {
h.Runtime.Log.Info("provider.Render could not find: " + model.Page.ContentType) h.Runtime.Log.Info("provider.Render could not find: " + model.Page.ContentType)
} }

View file

@ -15,12 +15,14 @@ import (
"net/http" "net/http"
"github.com/documize/community/core/env" "github.com/documize/community/core/env"
"github.com/documize/community/domain"
"github.com/documize/community/domain/section/provider" "github.com/documize/community/domain/section/provider"
) )
// Provider represents Airtable // Provider represents Airtable
type Provider struct { type Provider struct {
Runtime env.Runtime Runtime *env.Runtime
Store *domain.Store
} }
// Meta describes us // Meta describes us

View file

@ -15,12 +15,14 @@ import (
"net/http" "net/http"
"github.com/documize/community/core/env" "github.com/documize/community/core/env"
"github.com/documize/community/domain"
"github.com/documize/community/domain/section/provider" "github.com/documize/community/domain/section/provider"
) )
// Provider represents code snippet // Provider represents code snippet
type Provider struct { type Provider struct {
Runtime env.Runtime Runtime *env.Runtime
Store *domain.Store
} }
// Meta describes us. // Meta describes us.

View file

@ -69,7 +69,7 @@ func (h *Handler) RunSectionCommand(w http.ResponseWriter, r *http.Request) {
return return
} }
if !provider.Command(sectionName, provider.NewContext(ctx.OrgID, ctx.UserID), w, r) { if !provider.Command(sectionName, provider.NewContext(ctx.OrgID, ctx.UserID, ctx), w, r) {
h.Runtime.Log.Info("Unable to run provider.Command() for: " + sectionName) h.Runtime.Log.Info("Unable to run provider.Command() for: " + sectionName)
response.WriteNotFoundError(w, "RunSectionCommand", sectionName) response.WriteNotFoundError(w, "RunSectionCommand", sectionName)
} }
@ -122,7 +122,7 @@ func (h *Handler) RefreshSections(w http.ResponseWriter, r *http.Request) {
return return
} }
pcontext := provider.NewContext(pm.OrgID, pm.UserID) pcontext := provider.NewContext(pm.OrgID, pm.UserID, ctx)
// Ask for data refresh // Ask for data refresh
data, ok := provider.Refresh(page.ContentType, pcontext, pm.Config, pm.RawBody) data, ok := provider.Refresh(page.ContentType, pcontext, pm.Config, pm.RawBody)

View file

@ -21,12 +21,14 @@ import (
"net/http" "net/http"
"github.com/documize/community/core/env" "github.com/documize/community/core/env"
"github.com/documize/community/domain"
"github.com/documize/community/domain/section/provider" "github.com/documize/community/domain/section/provider"
) )
// Provider represents Gemini // Provider represents Gemini
type Provider struct { type Provider struct {
Runtime env.Runtime Runtime *env.Runtime
Store *domain.Store
} }
// Meta describes us. // Meta describes us.
@ -66,7 +68,7 @@ func (*Provider) Render(ctx *provider.Context, config, data string) string {
} }
// Command handles authentication, workspace listing and items retrieval. // Command handles authentication, workspace listing and items retrieval.
func (*Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http.Request) { func (p *Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http.Request) {
query := r.URL.Query() query := r.URL.Query()
method := query.Get("method") method := query.Get("method")
@ -77,13 +79,13 @@ func (*Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http.R
switch method { switch method {
case "secrets": case "secrets":
secs(ctx, w, r) secs(ctx, p.Store, w, r)
case "auth": case "auth":
auth(ctx, w, r) auth(ctx, p.Store, w, r)
case "workspace": case "workspace":
workspace(ctx, w, r) workspace(ctx, p.Store, w, r)
case "items": case "items":
items(ctx, w, r) items(ctx, p.Store, w, r)
} }
} }
@ -97,7 +99,7 @@ func (p *Provider) Refresh(ctx *provider.Context, config, data string) (newData
return return
} }
c.Clean(ctx) c.Clean(ctx, p.Store)
if len(c.URL) == 0 { if len(c.URL) == 0 {
p.Runtime.Log.Info("Gemini.Refresh received empty URL") p.Runtime.Log.Info("Gemini.Refresh received empty URL")
@ -153,7 +155,7 @@ func (p *Provider) Refresh(ctx *provider.Context, config, data string) (newData
return return
} }
func auth(ctx *provider.Context, w http.ResponseWriter, r *http.Request) { func auth(ctx *provider.Context, store *domain.Store, w http.ResponseWriter, r *http.Request) {
defer r.Body.Close() defer r.Body.Close()
body, err := ioutil.ReadAll(r.Body) body, err := ioutil.ReadAll(r.Body)
@ -170,7 +172,7 @@ func auth(ctx *provider.Context, w http.ResponseWriter, r *http.Request) {
return return
} }
config.Clean(nil) // don't look at the database for the parameters config.Clean(nil, store) // don't look at the database for the parameters
if len(config.URL) == 0 { if len(config.URL) == 0 {
provider.WriteMessage(w, "gemini", "Missing URL value") provider.WriteMessage(w, "gemini", "Missing URL value")
@ -205,7 +207,7 @@ func auth(ctx *provider.Context, w http.ResponseWriter, r *http.Request) {
return return
} }
config.SaveSecrets(ctx) config.SaveSecrets(ctx, store)
defer res.Body.Close() defer res.Body.Close()
var g = geminiUser{} var g = geminiUser{}
@ -221,7 +223,7 @@ func auth(ctx *provider.Context, w http.ResponseWriter, r *http.Request) {
provider.WriteJSON(w, g) provider.WriteJSON(w, g)
} }
func workspace(ctx *provider.Context, w http.ResponseWriter, r *http.Request) { func workspace(ctx *provider.Context, store *domain.Store, w http.ResponseWriter, r *http.Request) {
defer r.Body.Close() defer r.Body.Close()
body, err := ioutil.ReadAll(r.Body) body, err := ioutil.ReadAll(r.Body)
@ -238,7 +240,7 @@ func workspace(ctx *provider.Context, w http.ResponseWriter, r *http.Request) {
return return
} }
config.Clean(ctx) config.Clean(ctx, store)
if len(config.URL) == 0 { if len(config.URL) == 0 {
provider.WriteMessage(w, "gemini", "Missing URL value") provider.WriteMessage(w, "gemini", "Missing URL value")
@ -293,7 +295,7 @@ func workspace(ctx *provider.Context, w http.ResponseWriter, r *http.Request) {
provider.WriteJSON(w, workspace) provider.WriteJSON(w, workspace)
} }
func items(ctx *provider.Context, w http.ResponseWriter, r *http.Request) { func items(ctx *provider.Context, store *domain.Store, w http.ResponseWriter, r *http.Request) {
defer r.Body.Close() defer r.Body.Close()
body, err := ioutil.ReadAll(r.Body) body, err := ioutil.ReadAll(r.Body)
@ -310,7 +312,7 @@ func items(ctx *provider.Context, w http.ResponseWriter, r *http.Request) {
return return
} }
config.Clean(ctx) config.Clean(ctx, store)
if len(config.URL) == 0 { if len(config.URL) == 0 {
provider.WriteMessage(w, "gemini", "Missing URL value") provider.WriteMessage(w, "gemini", "Missing URL value")
@ -368,7 +370,7 @@ func items(ctx *provider.Context, w http.ResponseWriter, r *http.Request) {
provider.WriteJSON(w, items) provider.WriteJSON(w, items)
} }
func secs(ctx *provider.Context, w http.ResponseWriter, r *http.Request) { func secs(ctx *provider.Context, store *domain.Store, w http.ResponseWriter, r *http.Request) {
sec, _ := getSecrets(ctx) sec, _ := getSecrets(ctx, store)
provider.WriteJSON(w, sec) provider.WriteJSON(w, sec)
} }

View file

@ -14,6 +14,7 @@ package gemini
import ( import (
"strings" "strings"
"github.com/documize/community/domain"
"github.com/documize/community/domain/section/provider" "github.com/documize/community/domain/section/provider"
) )
@ -86,9 +87,9 @@ type geminiConfig struct {
Filter map[string]interface{} `json:"filter"` Filter map[string]interface{} `json:"filter"`
} }
func (c *geminiConfig) Clean(ctx *provider.Context) { func (c *geminiConfig) Clean(ctx *provider.Context, store *domain.Store) {
if ctx != nil { if ctx != nil {
sec, err := getSecrets(ctx) sec, err := getSecrets(ctx, store)
if err == nil { if err == nil {
if len(sec.APIKey) > 0 && len(sec.Username) > 0 && len(sec.URL) > 0 { if len(sec.APIKey) > 0 && len(sec.Username) > 0 && len(sec.URL) > 0 {
c.APIKey = strings.TrimSpace(sec.APIKey) c.APIKey = strings.TrimSpace(sec.APIKey)
@ -102,12 +103,12 @@ func (c *geminiConfig) Clean(ctx *provider.Context) {
c.URL = strings.TrimSpace(c.URL) c.URL = strings.TrimSpace(c.URL)
} }
func (c *geminiConfig) SaveSecrets(ctx *provider.Context) { func (c *geminiConfig) SaveSecrets(ctx *provider.Context, store *domain.Store) {
var sec secrets var sec secrets
sec.APIKey = strings.TrimSpace(c.APIKey) sec.APIKey = strings.TrimSpace(c.APIKey)
sec.Username = strings.TrimSpace(c.Username) sec.Username = strings.TrimSpace(c.Username)
sec.URL = strings.TrimSpace(c.URL) sec.URL = strings.TrimSpace(c.URL)
ctx.MarshalSecrets(sec) ctx.MarshalSecrets(sec, store)
} }
type secrets struct { type secrets struct {
@ -116,7 +117,7 @@ type secrets struct {
APIKey string `json:"apikey"` APIKey string `json:"apikey"`
} }
func getSecrets(ctx *provider.Context) (sec secrets, err error) { func getSecrets(ctx *provider.Context, store *domain.Store) (sec secrets, err error) {
err = ctx.UnmarshalSecrets(&sec) err = ctx.UnmarshalSecrets(&sec, store)
return return
} }

View file

@ -17,32 +17,33 @@ import (
"net/url" "net/url"
"strings" "strings"
"github.com/documize/community/core/api/request" "github.com/documize/community/core/env"
"github.com/documize/community/domain"
"github.com/documize/community/domain/section/provider"
gogithub "github.com/google/go-github/github" gogithub "github.com/google/go-github/github"
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )
func clientID() string { func clientID(ctx domain.RequestContext, s *domain.Store) string {
return request.ConfigString(meta.ConfigHandle(), "clientID") return s.Setting.Get(meta.ConfigHandle(), "clientID")
} }
func clientSecret() string { func clientSecret(ctx domain.RequestContext, s *domain.Store) string {
return request.ConfigString(meta.ConfigHandle(), "clientSecret") return s.Setting.Get(meta.ConfigHandle(), "clientSecret")
} }
func authorizationCallbackURL() string { func authorizationCallbackURL(ctx domain.RequestContext, s *domain.Store) string {
// NOTE: URL value must have the path and query "/api/public/validate?section=github" // NOTE: URL value must have the path and query "/api/public/validate?section=github"
return request.ConfigString(meta.ConfigHandle(), "authorizationCallbackURL") return s.Setting.Get(meta.ConfigHandle(), "authorizationCallbackURL")
} }
func validateToken(ptoken string) error { func validateToken(ctx provider.Context, s *domain.Store, ptoken string) error {
// Github authorization check // Github authorization check
authClient := gogithub.NewClient((&gogithub.BasicAuthTransport{ authClient := gogithub.NewClient((&gogithub.BasicAuthTransport{
Username: clientID(), Username: clientID(ctx.Request, s),
Password: clientSecret(), Password: clientSecret(ctx.Request, s),
}).Client()) }).Client())
_, _, err := authClient.Authorizations.Check(clientID(), ptoken) _, _, err := authClient.Authorizations.Check(clientID(ctx.Request, s), ptoken)
return err return err
} }
@ -56,14 +57,15 @@ func (*Provider) githubClient(config *githubConfig) *gogithub.Client {
} }
// Callback is called by a browser redirect from Github, via the validation endpoint // Callback is called by a browser redirect from Github, via the validation endpoint
func Callback(res http.ResponseWriter, req *http.Request) error { func Callback(rt *env.Runtime, s *domain.Store, res http.ResponseWriter, req *http.Request) error {
ctx := domain.GetRequestContext(req)
code := req.URL.Query().Get("code") code := req.URL.Query().Get("code")
state := req.URL.Query().Get("state") state := req.URL.Query().Get("state")
ghurl := "https://github.com/login/oauth/access_token" ghurl := "https://github.com/login/oauth/access_token"
vals := "client_id=" + clientID() vals := "client_id=" + clientID(ctx, s)
vals += "&client_secret=" + clientSecret() vals += "&client_secret=" + clientSecret(ctx, s)
vals += "&code=" + code vals += "&code=" + code
vals += "&state=" + state vals += "&state=" + state

View file

@ -21,6 +21,7 @@ import (
"strings" "strings"
"github.com/documize/community/core/env" "github.com/documize/community/core/env"
"github.com/documize/community/domain"
"github.com/documize/community/domain/section/provider" "github.com/documize/community/domain/section/provider"
gogithub "github.com/google/go-github/github" gogithub "github.com/google/go-github/github"
) )
@ -43,7 +44,8 @@ func init() {
// Provider represents GitHub // Provider represents GitHub
type Provider struct { type Provider struct {
Runtime env.Runtime Runtime *env.Runtime
Store *domain.Store
} }
// Meta describes us. // Meta describes us.
@ -67,8 +69,8 @@ func (p *Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http
CID string `json:"clientID"` CID string `json:"clientID"`
URL string `json:"authorizationCallbackURL"` URL string `json:"authorizationCallbackURL"`
} }
ret.CID = clientID() ret.CID = clientID(ctx.Request, p.Store)
ret.URL = authorizationCallbackURL() ret.URL = authorizationCallbackURL(ctx.Request, p.Store)
provider.WriteJSON(w, ret) provider.WriteJSON(w, ret)
return return
} }
@ -86,7 +88,7 @@ func (p *Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http
if method == "saveSecret" { // secret Token update code if method == "saveSecret" { // secret Token update code
// write the new one, direct from JS // write the new one, direct from JS
if err = ctx.SaveSecrets(string(body)); err != nil { if err = ctx.SaveSecrets(string(body), p.Store); err != nil {
p.Runtime.Log.Error("github settoken configuration", err) p.Runtime.Log.Error("github settoken configuration", err)
provider.WriteError(w, "github", err) provider.WriteError(w, "github", err)
return return
@ -108,7 +110,7 @@ func (p *Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http
config.Clean() config.Clean()
// always use DB version of the token // always use DB version of the token
config.Token = ctx.GetSecrets("token") // get the secret token in the database config.Token = ctx.GetSecrets("token", p.Store) // get the secret token in the database
client := p.githubClient(&config) client := p.githubClient(&config)
@ -119,11 +121,11 @@ func (p *Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http
if len(config.Token) == 0 { if len(config.Token) == 0 {
err = errors.New("empty github token") err = errors.New("empty github token")
} else { } else {
err = validateToken(config.Token) err = validateToken(*ctx, p.Store, config.Token)
} }
if err != nil { if err != nil {
// token now invalid, so wipe it // token now invalid, so wipe it
ctx.SaveSecrets("") // ignore error, already in an error state ctx.SaveSecrets("", p.Store) // ignore error, already in an error state
p.Runtime.Log.Error("github check token validation", err) p.Runtime.Log.Error("github check token validation", err)
provider.WriteError(w, "github", err) provider.WriteError(w, "github", err)
return return
@ -156,7 +158,7 @@ func (p *Provider) Refresh(ctx *provider.Context, configJSON, data string) strin
} }
c.Clean() c.Clean()
c.Token = ctx.GetSecrets("token") c.Token = ctx.GetSecrets("token", p.Store)
client := p.githubClient(&c) client := p.githubClient(&c)
@ -193,7 +195,7 @@ func (p *Provider) Render(ctx *provider.Context, config, data string) string {
} }
c.Clean() c.Clean()
c.Token = ctx.GetSecrets("token") c.Token = ctx.GetSecrets("token", p.Store)
data = strings.TrimSpace(data) data = strings.TrimSpace(data)
if len(data) == 0 { if len(data) == 0 {

View file

@ -20,7 +20,7 @@ import (
gogithub "github.com/google/go-github/github" gogithub "github.com/google/go-github/github"
) )
func listFailed(rt env.Runtime, method string, config githubConfig, client *gogithub.Client, w http.ResponseWriter) (failed bool) { func listFailed(rt *env.Runtime, method string, config githubConfig, client *gogithub.Client, w http.ResponseWriter) (failed bool) {
switch method { // which list to choose? switch method { // which list to choose?
case "owners": case "owners":

View file

@ -16,12 +16,14 @@ import (
"github.com/documize/blackfriday" "github.com/documize/blackfriday"
"github.com/documize/community/core/env" "github.com/documize/community/core/env"
"github.com/documize/community/domain"
"github.com/documize/community/domain/section/provider" "github.com/documize/community/domain/section/provider"
) )
// Provider represents Markdown // Provider represents Markdown
type Provider struct { type Provider struct {
Runtime env.Runtime Runtime *env.Runtime
Store *domain.Store
} }
// Meta describes us // Meta describes us

View file

@ -22,6 +22,7 @@ import (
"net/url" "net/url"
"github.com/documize/community/core/env" "github.com/documize/community/core/env"
"github.com/documize/community/domain"
"github.com/documize/community/domain/section/provider" "github.com/documize/community/domain/section/provider"
) )
@ -29,7 +30,8 @@ const me = "papertrail"
// Provider represents Papertrail // Provider represents Papertrail
type Provider struct { type Provider struct {
Runtime env.Runtime Runtime *env.Runtime
Store *domain.Store
} }
// Meta describes us. // Meta describes us.
@ -45,7 +47,7 @@ func (*Provider) Meta() provider.TypeMeta {
} }
// Render converts Papertrail data into HTML suitable for browser rendering. // Render converts Papertrail data into HTML suitable for browser rendering.
func (*Provider) Render(ctx *provider.Context, config, data string) string { func (p *Provider) Render(ctx *provider.Context, config, data string) string {
var search papertrailSearch var search papertrailSearch
var events []papertrailEvent var events []papertrailEvent
var payload = papertrailRender{} var payload = papertrailRender{}
@ -54,7 +56,7 @@ func (*Provider) Render(ctx *provider.Context, config, data string) string {
json.Unmarshal([]byte(data), &search) json.Unmarshal([]byte(data), &search)
json.Unmarshal([]byte(config), &c) json.Unmarshal([]byte(config), &c)
c.APIToken = ctx.GetSecrets("APIToken") c.APIToken = ctx.GetSecrets("APIToken", p.Store)
max := len(search.Events) max := len(search.Events)
if c.Max < max { if c.Max < max {
@ -107,7 +109,7 @@ func (p *Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http
config.Clean() config.Clean()
if config.APIToken == provider.SecretReplacement || config.APIToken == "" { if config.APIToken == provider.SecretReplacement || config.APIToken == "" {
config.APIToken = ctx.GetSecrets("APIToken") config.APIToken = ctx.GetSecrets("APIToken", p.Store)
} }
if len(config.APIToken) == 0 { if len(config.APIToken) == 0 {
@ -117,7 +119,7 @@ func (p *Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http
switch method { switch method {
case "auth": case "auth":
auth(p.Runtime, ctx, config, w, r) auth(p.Runtime, p.Store, ctx, config, w, r)
case "options": case "options":
options(config, w, r) options(config, w, r)
} }
@ -135,7 +137,7 @@ func (p *Provider) Refresh(ctx *provider.Context, config, data string) (newData
c.Clean() c.Clean()
c.APIToken = ctx.GetSecrets("APIToken") c.APIToken = ctx.GetSecrets("APIToken", p.Store)
if len(c.APIToken) == 0 { if len(c.APIToken) == 0 {
p.Runtime.Log.Error("missing API token", err) p.Runtime.Log.Error("missing API token", err)
@ -160,7 +162,7 @@ func (p *Provider) Refresh(ctx *provider.Context, config, data string) (newData
return return
} }
func auth(rt env.Runtime, ctx *provider.Context, config papertrailConfig, w http.ResponseWriter, r *http.Request) { func auth(rt *env.Runtime, store *domain.Store, ctx *provider.Context, config papertrailConfig, w http.ResponseWriter, r *http.Request) {
result, err := fetchEvents(rt, config) result, err := fetchEvents(rt, config)
if result == nil { if result == nil {
@ -169,7 +171,7 @@ func auth(rt env.Runtime, ctx *provider.Context, config papertrailConfig, w http
if err != nil { if err != nil {
ctx.SaveSecrets(`{"APIToken":""}`) // invalid token, so reset it ctx.SaveSecrets(`{"APIToken":""}`, store) // invalid token, so reset it
if err.Error() == "forbidden" { if err.Error() == "forbidden" {
provider.WriteForbidden(w) provider.WriteForbidden(w)
@ -180,7 +182,7 @@ func auth(rt env.Runtime, ctx *provider.Context, config papertrailConfig, w http
return return
} }
ctx.SaveSecrets(`{"APIToken":"` + config.APIToken + `"}`) ctx.SaveSecrets(`{"APIToken":"`+config.APIToken+`"}`, store)
provider.WriteJSON(w, result) provider.WriteJSON(w, result)
} }
@ -253,7 +255,7 @@ func options(config papertrailConfig, w http.ResponseWriter, r *http.Request) {
provider.WriteJSON(w, options) provider.WriteJSON(w, options)
} }
func fetchEvents(rt env.Runtime, config papertrailConfig) (result interface{}, err error) { func fetchEvents(rt *env.Runtime, config papertrailConfig) (result interface{}, err error) {
var filter string var filter string
if len(config.Query) > 0 { if len(config.Query) > 0 {
filter = fmt.Sprintf("q=%s", url.QueryEscape(config.Query)) filter = fmt.Sprintf("q=%s", url.QueryEscape(config.Query))

View file

@ -19,8 +19,8 @@ import (
"sort" "sort"
"strings" "strings"
"github.com/documize/community/core/api/request" "github.com/documize/community/core/env"
"github.com/documize/community/core/log" "github.com/documize/community/domain"
) )
// SecretReplacement is a constant used to replace secrets in data-structures when required. // SecretReplacement is a constant used to replace secrets in data-structures when required.
@ -39,7 +39,7 @@ type TypeMeta struct {
Title string `json:"title"` Title string `json:"title"`
Description string `json:"description"` Description string `json:"description"`
Preview bool `json:"preview"` // coming soon! Preview bool `json:"preview"` // coming soon!
Callback func(http.ResponseWriter, *http.Request) error `json:"-"` Callback func(*env.Runtime, *domain.Store, http.ResponseWriter, *http.Request) error `json:"-"`
} }
// ConfigHandle returns the key name for database config table // ConfigHandle returns the key name for database config table
@ -61,14 +61,12 @@ type Context struct {
UserID string UserID string
prov Provider prov Provider
inCommand bool inCommand bool
Request domain.RequestContext
} }
// NewContext is a convenience function. // NewContext is a convenience function.
func NewContext(orgid, userid string) *Context { func NewContext(orgid, userid string, ctx domain.RequestContext) *Context {
if orgid == "" || userid == "" { return &Context{OrgID: orgid, UserID: userid, Request: ctx}
log.Error("NewContext incorrect orgid:"+orgid+" userid:"+userid, errors.New("bad section context"))
}
return &Context{OrgID: orgid, UserID: userid}
} }
// Register makes document section type available // Register makes document section type available
@ -104,11 +102,11 @@ func Command(section string, ctx *Context, w http.ResponseWriter, r *http.Reques
} }
// Callback passes parameters to the given section callback, the returned error indicates success. // Callback passes parameters to the given section callback, the returned error indicates success.
func Callback(section string, w http.ResponseWriter, r *http.Request) error { func Callback(section string, rt *env.Runtime, store *domain.Store, w http.ResponseWriter, r *http.Request) error {
s, ok := sectionsMap[section] s, ok := sectionsMap[section]
if ok { if ok {
if cb := s.Meta().Callback; cb != nil { if cb := s.Meta().Callback; cb != nil {
return cb(w, r) return cb(rt, store, w, r)
} }
} }
return errors.New("section not found") return errors.New("section not found")
@ -147,57 +145,47 @@ func WriteJSON(w http.ResponseWriter, v interface{}) {
} }
_, err = w.Write(j) _, err = w.Write(j)
log.IfErr(err)
} }
// WriteString writes string tp HTTP response. // WriteString writes string tp HTTP response.
func WriteString(w http.ResponseWriter, data string) { func WriteString(w http.ResponseWriter, data string) {
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
_, err := w.Write([]byte(data)) w.Write([]byte(data))
log.IfErr(err)
} }
// WriteEmpty returns just OK to HTTP response. // WriteEmpty returns just OK to HTTP response.
func WriteEmpty(w http.ResponseWriter) { func WriteEmpty(w http.ResponseWriter) {
w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
_, err := w.Write([]byte("{}")) w.Write([]byte("{}"))
log.IfErr(err)
} }
// WriteMarshalError write JSON marshalling error to HTTP response. // WriteMarshalError write JSON marshalling error to HTTP response.
func WriteMarshalError(w http.ResponseWriter, err error) { func WriteMarshalError(w http.ResponseWriter, err error) {
w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
_, err2 := w.Write([]byte("{Error: 'JSON marshal failed'}")) w.Write([]byte("{Error: 'JSON marshal failed'}"))
log.IfErr(err2)
log.Error("JSON marshall failed", err)
} }
// WriteMessage write string to HTTP response. // WriteMessage write string to HTTP response.
func WriteMessage(w http.ResponseWriter, section, msg string) { func WriteMessage(w http.ResponseWriter, section, msg string) {
w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
_, err := w.Write([]byte("{Message: " + msg + "}")) w.Write([]byte("{Message: " + msg + "}"))
log.IfErr(err)
log.Info(fmt.Sprintf("Error for section %s: %s", section, msg))
} }
// WriteError write given error to HTTP response. // WriteError write given error to HTTP response.
func WriteError(w http.ResponseWriter, section string, err error) { func WriteError(w http.ResponseWriter, section string, err error) {
w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusBadRequest) w.WriteHeader(http.StatusBadRequest)
_, err2 := w.Write([]byte("{Error: 'Internal server error'}")) w.Write([]byte("{Error: 'Internal server error'}"))
log.IfErr(err2)
log.Error(fmt.Sprintf("Error for section %s", section), err)
} }
// WriteForbidden write 403 to HTTP response. // WriteForbidden write 403 to HTTP response.
func WriteForbidden(w http.ResponseWriter) { func WriteForbidden(w http.ResponseWriter) {
w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusForbidden) w.WriteHeader(http.StatusForbidden)
_, err := w.Write([]byte("{Error: 'Unauthorized'}")) w.Write([]byte("{Error: 'Unauthorized'}"))
log.IfErr(err)
} }
// Secrets handling // Secrets handling
@ -206,17 +194,17 @@ func WriteForbidden(w http.ResponseWriter) {
// The secrets must be in the form of a JSON format string, for example `{"mysecret":"lover"}`. // The secrets must be in the form of a JSON format string, for example `{"mysecret":"lover"}`.
// An empty string signifies no valid secrets for this user/org combination. // An empty string signifies no valid secrets for this user/org combination.
// Note that this function can only be called within the Command method of a section. // Note that this function can only be called within the Command method of a section.
func (c *Context) SaveSecrets(JSONobj string) error { func (c *Context) SaveSecrets(JSONobj string, s *domain.Store) error {
if !c.inCommand { if !c.inCommand {
return errors.New("SaveSecrets() may only be called from within Command()") return errors.New("SaveSecrets() may only be called from within Command()")
} }
m := c.prov.Meta() m := c.prov.Meta()
return request.UserConfigSetJSON(c.OrgID, c.UserID, m.ContentType, JSONobj) return s.Setting.SetUser(c.OrgID, c.UserID, m.ContentType, JSONobj)
} }
// MarshalSecrets to the database. // MarshalSecrets to the database.
// Parameter the same as for json.Marshal(). // Parameter the same as for json.Marshal().
func (c *Context) MarshalSecrets(sec interface{}) error { func (c *Context) MarshalSecrets(sec interface{}, s *domain.Store) error {
if !c.inCommand { if !c.inCommand {
return errors.New("MarshalSecrets() may only be called from within Command()") return errors.New("MarshalSecrets() may only be called from within Command()")
} }
@ -224,7 +212,7 @@ func (c *Context) MarshalSecrets(sec interface{}) error {
if err != nil { if err != nil {
return err return err
} }
return c.SaveSecrets(string(byts)) return c.SaveSecrets(string(byts), s)
} }
// GetSecrets for the current context user/org. // GetSecrets for the current context user/org.
@ -232,9 +220,9 @@ func (c *Context) MarshalSecrets(sec interface{}) error {
// JSONpath format is defined at https://dev.mysql.com/doc/refman/5.7/en/json-path-syntax.html . // JSONpath format is defined at https://dev.mysql.com/doc/refman/5.7/en/json-path-syntax.html .
// An empty JSONpath returns the whole JSON object, as JSON. // An empty JSONpath returns the whole JSON object, as JSON.
// Errors return the empty string. // Errors return the empty string.
func (c *Context) GetSecrets(JSONpath string) string { func (c *Context) GetSecrets(JSONpath string, s *domain.Store) string {
m := c.prov.Meta() m := c.prov.Meta()
return request.UserConfigGetJSON(c.OrgID, c.UserID, m.ContentType, JSONpath) return s.Setting.GetUser(c.OrgID, c.UserID, m.ContentType, JSONpath)
} }
// ErrNoSecrets is returned if no secret is found in the database. // ErrNoSecrets is returned if no secret is found in the database.
@ -242,8 +230,8 @@ var ErrNoSecrets = errors.New("no secrets in database")
// UnmarshalSecrets from the database. // UnmarshalSecrets from the database.
// Parameter the same as for "v" in json.Unmarshal(). // Parameter the same as for "v" in json.Unmarshal().
func (c *Context) UnmarshalSecrets(v interface{}) error { func (c *Context) UnmarshalSecrets(v interface{}, s *domain.Store) error {
secTxt := c.GetSecrets("") // get all the json of the secrets secTxt := c.GetSecrets("", s) // get all the json of the secrets
if len(secTxt) > 0 { if len(secTxt) > 0 {
return json.Unmarshal([]byte(secTxt), v) return json.Unmarshal([]byte(secTxt), v)
} }

View file

@ -15,6 +15,7 @@ import (
"fmt" "fmt"
"github.com/documize/community/core/env" "github.com/documize/community/core/env"
"github.com/documize/community/domain"
"github.com/documize/community/domain/section/airtable" "github.com/documize/community/domain/section/airtable"
"github.com/documize/community/domain/section/code" "github.com/documize/community/domain/section/code"
"github.com/documize/community/domain/section/gemini" "github.com/documize/community/domain/section/gemini"
@ -28,17 +29,17 @@ import (
) )
// Register sections // Register sections
func Register(rt env.Runtime) { func Register(rt *env.Runtime, s *domain.Store) {
provider.Register("code", &code.Provider{Runtime: rt}) provider.Register("code", &code.Provider{Runtime: rt, Store: s})
provider.Register("gemini", &gemini.Provider{Runtime: rt}) provider.Register("gemini", &gemini.Provider{Runtime: rt, Store: s})
provider.Register("github", &github.Provider{Runtime: rt}) provider.Register("github", &github.Provider{Runtime: rt, Store: s})
provider.Register("markdown", &markdown.Provider{Runtime: rt}) provider.Register("markdown", &markdown.Provider{Runtime: rt, Store: s})
provider.Register("papertrail", &papertrail.Provider{Runtime: rt}) provider.Register("papertrail", &papertrail.Provider{Runtime: rt, Store: s})
provider.Register("table", &table.Provider{Runtime: rt}) provider.Register("table", &table.Provider{Runtime: rt, Store: s})
provider.Register("code", &code.Provider{Runtime: rt}) provider.Register("code", &code.Provider{Runtime: rt, Store: s})
provider.Register("trello", &trello.Provider{Runtime: rt}) provider.Register("trello", &trello.Provider{Runtime: rt, Store: s})
provider.Register("wysiwyg", &wysiwyg.Provider{Runtime: rt}) provider.Register("wysiwyg", &wysiwyg.Provider{Runtime: rt, Store: s})
provider.Register("airtable", &airtable.Provider{Runtime: rt}) provider.Register("airtable", &airtable.Provider{Runtime: rt, Store: s})
p := provider.List() p := provider.List()
rt.Log.Info(fmt.Sprintf("Registered %d sections", len(p))) rt.Log.Info(fmt.Sprintf("Registered %d sections", len(p)))

View file

@ -1,82 +0,0 @@
// 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 section
import (
"net/http"
"testing"
"github.com/documize/community/domain/section/provider"
)
type testsection provider.TypeMeta
var ts testsection
func init() {
provider.Register("testsection", &ts)
}
// Command is an end-point...
func (ts *testsection) Command(ctx *provider.Context, w http.ResponseWriter, r *http.Request) {}
var didRefresh bool
// Refresh existing data, returning data in the format of the target system
func (ts *testsection) Refresh(ctx *provider.Context, meta, data string) string {
didRefresh = true
return ""
}
// Render converts data in the target system format into HTML
func (*testsection) Render(ctx *provider.Context, meta, data string) string {
return "testsection " + data
}
func (*testsection) Meta() provider.TypeMeta {
section := provider.TypeMeta{}
section.ID = "TestGUID"
section.Title = "TestSection"
section.Description = "A Test Section"
section.ContentType = "testsection"
return section
}
func TestSection(t *testing.T) {
ctx := provider.NewContext("_orgid_", "_userid_")
if _, ok := provider.Refresh("testsection", ctx, "", ""); !ok {
t.Error("did not find 'testsection' smart section (1)")
}
if !didRefresh {
t.Error("did not run the test Refresh method")
}
out, ok := provider.Render("testsection", ctx, "meta", "dingbat")
if !ok {
t.Error("did not find 'testsection' smart section (2)")
}
if out != "testsection dingbat" {
t.Error("wrong output from Render")
}
sects := provider.GetSectionMeta()
for _, v := range sects {
if v.Title == "TestSection" {
return
}
//t.Logf("%v %v", v.Order, v.Title)
}
t.Error("TestSection not in meta output")
}

View file

@ -15,12 +15,14 @@ import (
"net/http" "net/http"
"github.com/documize/community/core/env" "github.com/documize/community/core/env"
"github.com/documize/community/domain"
"github.com/documize/community/domain/section/provider" "github.com/documize/community/domain/section/provider"
) )
// Provider represents Table // Provider represents Table
type Provider struct { type Provider struct {
Runtime env.Runtime Runtime *env.Runtime
Store *domain.Store
} }
// Meta describes us // Meta describes us

View file

@ -19,8 +19,8 @@ import (
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"github.com/documize/community/core/api/request"
"github.com/documize/community/core/env" "github.com/documize/community/core/env"
"github.com/documize/community/domain"
"github.com/documize/community/domain/section/provider" "github.com/documize/community/domain/section/provider"
) )
@ -38,7 +38,8 @@ func init() {
// Provider represents Trello // Provider represents Trello
type Provider struct { type Provider struct {
Runtime env.Runtime Runtime *env.Runtime
Store *domain.Store
} }
// Meta describes us. // Meta describes us.
@ -68,7 +69,7 @@ func (p *Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http
} }
config.Clean() config.Clean()
config.AppKey = request.ConfigString(meta.ConfigHandle(), "appKey") config.AppKey = p.Store.Setting.Get(meta.ConfigHandle(), "appKey")
if len(config.AppKey) == 0 { if len(config.AppKey) == 0 {
p.Runtime.Log.Info("missing trello App Key") p.Runtime.Log.Info("missing trello App Key")
@ -77,7 +78,7 @@ func (p *Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http
} }
if len(config.Token) == 0 { if len(config.Token) == 0 {
config.Token = ctx.GetSecrets("token") // get a token, if we have one config.Token = ctx.GetSecrets("token", p.Store) // get a token, if we have one
} }
if method != "config" { if method != "config" {
@ -94,7 +95,7 @@ func (p *Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http
if err != nil { if err != nil {
p.Runtime.Log.Error("failed to render cards", err) p.Runtime.Log.Error("failed to render cards", err)
provider.WriteError(w, "trello", err) provider.WriteError(w, "trello", err)
ctx.SaveSecrets("") // failure means our secrets are invalid ctx.SaveSecrets("", p.Store) // failure means our secrets are invalid
return return
} }
@ -106,7 +107,7 @@ func (p *Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http
if err != nil { if err != nil {
p.Runtime.Log.Error("failed to render board", err) p.Runtime.Log.Error("failed to render board", err)
provider.WriteError(w, "trello", err) provider.WriteError(w, "trello", err)
ctx.SaveSecrets("") // failure means our secrets are invalid ctx.SaveSecrets("", p.Store) // failure means our secrets are invalid
return return
} }
@ -118,7 +119,7 @@ func (p *Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http
if err != nil { if err != nil {
p.Runtime.Log.Error("failed to get Trello lists", err) p.Runtime.Log.Error("failed to get Trello lists", err)
provider.WriteError(w, "trello", err) provider.WriteError(w, "trello", err)
ctx.SaveSecrets("") // failure means our secrets are invalid ctx.SaveSecrets("", p.Store) // failure means our secrets are invalid
return return
} }
@ -150,7 +151,7 @@ func (p *Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http
p.Runtime.Log.Error("failed save Trello secrets", e) p.Runtime.Log.Error("failed save Trello secrets", e)
} }
ctx.SaveSecrets(string(b)) ctx.SaveSecrets(string(b), p.Store)
} }
// Render just sends back HMTL as-is. // Render just sends back HMTL as-is.

View file

@ -15,12 +15,14 @@ import (
"net/http" "net/http"
"github.com/documize/community/core/env" "github.com/documize/community/core/env"
"github.com/documize/community/domain"
"github.com/documize/community/domain/section/provider" "github.com/documize/community/domain/section/provider"
) )
// Provider represents WYSIWYG // Provider represents WYSIWYG
type Provider struct { type Provider struct {
Runtime env.Runtime Runtime *env.Runtime
Store *domain.Store
} }
// Meta describes us // Meta describes us

View file

@ -41,7 +41,7 @@ func (h *Handler) SMTP(w http.ResponseWriter, r *http.Request) {
return return
} }
config := h.Store.Setting.Get(ctx, "SMTP", "") config := h.Store.Setting.Get("SMTP", "")
var y map[string]interface{} var y map[string]interface{}
json.Unmarshal([]byte(config), &y) json.Unmarshal([]byte(config), &y)
@ -82,7 +82,7 @@ func (h *Handler) SetSMTP(w http.ResponseWriter, r *http.Request) {
return return
} }
h.Store.Setting.Set(ctx, "SMTP", config) h.Store.Setting.Set("SMTP", config)
h.Store.Audit.Record(ctx, audit.EventTypeSystemSMTP) h.Store.Audit.Record(ctx, audit.EventTypeSystemSMTP)
@ -98,7 +98,7 @@ func (h *Handler) License(w http.ResponseWriter, r *http.Request) {
return return
} }
config := h.Store.Setting.Get(ctx, "EDITION-LICENSE", "") config := h.Store.Setting.Get("EDITION-LICENSE", "")
if len(config) == 0 { if len(config) == 0 {
config = "{}" config = "{}"
} }
@ -159,7 +159,7 @@ func (h *Handler) SetLicense(w http.ResponseWriter, r *http.Request) {
js = string(j) js = string(j)
} }
h.Store.Setting.Set(ctx, "EDITION-LICENSE", js) h.Store.Setting.Set("EDITION-LICENSE", js)
event.Handler().Publish(string(event.TypeSystemLicenseChange)) event.Handler().Publish(string(event.TypeSystemLicenseChange))

View file

@ -17,7 +17,6 @@ import (
"github.com/documize/community/core/env" "github.com/documize/community/core/env"
"github.com/documize/community/core/streamutil" "github.com/documize/community/core/streamutil"
"github.com/documize/community/domain"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -27,7 +26,7 @@ type Scope struct {
} }
// Get fetches a configuration JSON element from the config table. // Get fetches a configuration JSON element from the config table.
func (s Scope) Get(ctx domain.RequestContext, area, path string) (value string) { func (s Scope) Get(area, path string) (value string) {
if path != "" { if path != "" {
path = "." + path path = "." + path
} }
@ -58,7 +57,7 @@ func (s Scope) Get(ctx domain.RequestContext, area, path string) (value string)
} }
// Set writes a configuration JSON element to the config table. // Set writes a configuration JSON element to the config table.
func (s Scope) Set(ctx domain.RequestContext, area, json string) error { func (s Scope) Set(area, json string) error {
if area == "" { if area == "" {
return errors.New("no area") return errors.New("no area")
} }
@ -81,7 +80,7 @@ func (s Scope) Set(ctx domain.RequestContext, area, json string) error {
// GetUser fetches a configuration JSON element from the userconfig table for a given orgid/userid combination. // GetUser 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. // Errors return the empty string. A blank path returns the whole JSON object, as JSON.
func (s Scope) GetUser(ctx domain.RequestContext, orgID, userID, area, path string) (value string) { func (s Scope) GetUser(orgID, userID, area, path string) (value string) {
if path != "" { if path != "" {
path = "." + path path = "." + path
} }
@ -113,7 +112,7 @@ func (s Scope) GetUser(ctx domain.RequestContext, orgID, userID, area, path stri
} }
// SetUser writes a configuration JSON element to the userconfig table for the current user. // SetUser writes a configuration JSON element to the userconfig table for the current user.
func (s Scope) SetUser(ctx domain.RequestContext, orgID, userID, area, json string) error { func (s Scope) SetUser(orgID, userID, area, json string) error {
if area == "" { if area == "" {
return errors.New("no area") return errors.New("no area")
} }

View file

@ -21,8 +21,6 @@ import (
"net/http" "net/http"
"strings" "strings"
"github.com/documize/api/wordsmith/log"
"github.com/documize/community/core/api/mail"
"github.com/documize/community/core/env" "github.com/documize/community/core/env"
"github.com/documize/community/core/request" "github.com/documize/community/core/request"
"github.com/documize/community/core/response" "github.com/documize/community/core/response"
@ -31,6 +29,7 @@ import (
"github.com/documize/community/core/stringutil" "github.com/documize/community/core/stringutil"
"github.com/documize/community/core/uniqueid" "github.com/documize/community/core/uniqueid"
"github.com/documize/community/domain" "github.com/documize/community/domain"
"github.com/documize/community/domain/mail"
"github.com/documize/community/model/account" "github.com/documize/community/model/account"
"github.com/documize/community/model/audit" "github.com/documize/community/model/audit"
"github.com/documize/community/model/space" "github.com/documize/community/model/space"
@ -449,8 +448,11 @@ func (h *Handler) SetPermissions(w http.ResponseWriter, r *http.Request) {
roleID := uniqueid.Generate() roleID := uniqueid.Generate()
role.RefID = roleID role.RefID = roleID
err = h.Store.Space.AddRole(ctx, role) err = h.Store.Space.AddRole(ctx, role)
if err != nil {
h.Runtime.Log.Error("add role", err)
}
roleCount++ roleCount++
log.IfErr(err)
// We send out folder invitation emails to those users // We send out folder invitation emails to those users
// that have *just* been given permissions. // that have *just* been given permissions.
@ -462,7 +464,8 @@ func (h *Handler) SetPermissions(w http.ResponseWriter, r *http.Request) {
existingUser, err = h.Store.User.Get(ctx, role.UserID) existingUser, err = h.Store.User.Get(ctx, role.UserID)
if err == nil { if err == nil {
go mail.ShareFolderExistingUser(existingUser.Email, inviter.Fullname(), url, sp.Name, model.Message) mailer := mail.Mailer{Runtime: h.Runtime, Store: h.Store, Context: ctx}
go mailer.ShareFolderExistingUser(existingUser.Email, inviter.Fullname(), url, sp.Name, model.Message)
h.Runtime.Log.Info(fmt.Sprintf("%s is sharing space %s with existing user %s", inviter.Email, sp.Name, existingUser.Email)) h.Runtime.Log.Info(fmt.Sprintf("%s is sharing space %s with existing user %s", inviter.Email, sp.Name, existingUser.Email))
} else { } else {
response.WriteServerError(w, method, err) response.WriteServerError(w, method, err)
@ -732,14 +735,15 @@ func (h *Handler) Invite(w http.ResponseWriter, r *http.Request) {
} }
url := ctx.GetAppURL(fmt.Sprintf("s/%s/%s", sp.RefID, stringutil.MakeSlug(sp.Name))) url := ctx.GetAppURL(fmt.Sprintf("s/%s/%s", sp.RefID, stringutil.MakeSlug(sp.Name)))
go mail.ShareFolderExistingUser(email, inviter.Fullname(), url, sp.Name, model.Message) mailer := mail.Mailer{Runtime: h.Runtime, Store: h.Store, Context: ctx}
go mailer.ShareFolderExistingUser(email, inviter.Fullname(), url, sp.Name, model.Message)
h.Runtime.Log.Info(fmt.Sprintf("%s is sharing space %s with existing user %s", inviter.Email, sp.Name, email)) h.Runtime.Log.Info(fmt.Sprintf("%s is sharing space %s with existing user %s", inviter.Email, sp.Name, email))
} else { } else {
// On-board new user // On-board new user
if strings.Contains(email, "@") { if strings.Contains(email, "@") {
url := ctx.GetAppURL(fmt.Sprintf("auth/share/%s/%s", sp.RefID, stringutil.MakeSlug(sp.Name))) url := ctx.GetAppURL(fmt.Sprintf("auth/share/%s/%s", sp.RefID, stringutil.MakeSlug(sp.Name)))
err = inviteNewUserToSharedSpace(ctx, h.Store, email, inviter, url, sp, model.Message) err = inviteNewUserToSharedSpace(ctx, h.Runtime, h.Store, email, inviter, url, sp, model.Message)
if err != nil { if err != nil {
ctx.Transaction.Rollback() ctx.Transaction.Rollback()

View file

@ -14,10 +14,11 @@ package space
import ( import (
"fmt" "fmt"
"github.com/documize/community/core/api/mail" "github.com/documize/community/core/env"
"github.com/documize/community/core/secrets" "github.com/documize/community/core/secrets"
"github.com/documize/community/core/uniqueid" "github.com/documize/community/core/uniqueid"
"github.com/documize/community/domain" "github.com/documize/community/domain"
"github.com/documize/community/domain/mail"
"github.com/documize/community/model/account" "github.com/documize/community/model/account"
"github.com/documize/community/model/space" "github.com/documize/community/model/space"
"github.com/documize/community/model/user" "github.com/documize/community/model/user"
@ -50,7 +51,7 @@ func addSpace(ctx domain.RequestContext, s *domain.Store, sp space.Space) (err e
// We create the user account with default values and then take them // We create the user account with default values and then take them
// through a welcome process designed to capture profile data. // through a welcome process designed to capture profile data.
// We add them to the organization and grant them view-only folder access. // We add them to the organization and grant them view-only folder access.
func inviteNewUserToSharedSpace(ctx domain.RequestContext, s *domain.Store, email string, invitedBy user.User, func inviteNewUserToSharedSpace(ctx domain.RequestContext, rt *env.Runtime, s *domain.Store, email string, invitedBy user.User,
baseURL string, sp space.Space, invitationMessage string) (err error) { baseURL string, sp space.Space, invitationMessage string) (err error) {
var u = user.User{} var u = user.User{}
@ -97,8 +98,10 @@ func inviteNewUserToSharedSpace(ctx domain.RequestContext, s *domain.Store, emai
return return
} }
mailer := mail.Mailer{Runtime: rt, Store: s, Context: ctx}
url := fmt.Sprintf("%s/%s", baseURL, u.Salt) url := fmt.Sprintf("%s/%s", baseURL, u.Salt)
go mail.ShareFolderNewUser(u.Email, invitedBy.Fullname(), url, sp.Name, invitationMessage) go mailer.ShareFolderNewUser(u.Email, invitedBy.Fullname(), url, sp.Name, invitationMessage)
return return
} }

View file

@ -144,10 +144,10 @@ type DocumentStorer interface {
// SettingStorer defines required methods for persisting global and user level settings // SettingStorer defines required methods for persisting global and user level settings
type SettingStorer interface { type SettingStorer interface {
Get(ctx RequestContext, area, path string) string Get(area, path string) string
Set(ctx RequestContext, area, value string) error Set(area, value string) error
GetUser(ctx RequestContext, orgID, userID, area, path string) string GetUser(orgID, userID, area, path string) string
SetUser(ctx RequestContext, orgID, userID, area, json string) error SetUser(orgID, userID, area, json string) error
} }
// AttachmentStorer defines required methods for persisting document attachments // AttachmentStorer defines required methods for persisting document attachments

View file

@ -22,7 +22,6 @@ import (
"strconv" "strconv"
"github.com/documize/community/core/api/mail"
"github.com/documize/community/core/env" "github.com/documize/community/core/env"
"github.com/documize/community/core/event" "github.com/documize/community/core/event"
"github.com/documize/community/core/request" "github.com/documize/community/core/request"
@ -32,6 +31,7 @@ import (
"github.com/documize/community/core/stringutil" "github.com/documize/community/core/stringutil"
"github.com/documize/community/core/uniqueid" "github.com/documize/community/core/uniqueid"
"github.com/documize/community/domain" "github.com/documize/community/domain"
"github.com/documize/community/domain/mail"
"github.com/documize/community/model/account" "github.com/documize/community/model/account"
"github.com/documize/community/model/audit" "github.com/documize/community/model/audit"
"github.com/documize/community/model/space" "github.com/documize/community/model/space"
@ -197,12 +197,14 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
encrypted := secrets.EncodeBase64([]byte(auth)) encrypted := secrets.EncodeBase64([]byte(auth))
url := fmt.Sprintf("%s/%s", ctx.GetAppURL("auth/sso"), url.QueryEscape(string(encrypted))) url := fmt.Sprintf("%s/%s", ctx.GetAppURL("auth/sso"), url.QueryEscape(string(encrypted)))
go mail.InviteNewUser(userModel.Email, inviter.Fullname(), url, userModel.Email, requestedPassword) mailer := mail.Mailer{Runtime: h.Runtime, Store: h.Store, Context: ctx}
go mailer.InviteNewUser(userModel.Email, inviter.Fullname(), url, userModel.Email, requestedPassword)
h.Runtime.Log.Info(fmt.Sprintf("%s invited by %s on %s", userModel.Email, inviter.Email, ctx.AppURL)) h.Runtime.Log.Info(fmt.Sprintf("%s invited by %s on %s", userModel.Email, inviter.Email, ctx.AppURL))
} else { } else {
go mail.InviteExistingUser(userModel.Email, inviter.Fullname(), ctx.GetAppURL("")) mailer := mail.Mailer{Runtime: h.Runtime, Store: h.Store, Context: ctx}
go mailer.InviteExistingUser(userModel.Email, inviter.Fullname(), ctx.GetAppURL(""))
h.Runtime.Log.Info(fmt.Sprintf("%s is giving access to an existing user %s", inviter.Email, userModel.Email)) h.Runtime.Log.Info(fmt.Sprintf("%s is giving access to an existing user %s", inviter.Email, userModel.Email))
} }
@ -580,7 +582,8 @@ func (h *Handler) ForgotPassword(w http.ResponseWriter, r *http.Request) {
ctx.Transaction.Commit() ctx.Transaction.Commit()
appURL := ctx.GetAppURL(fmt.Sprintf("auth/reset/%s", token)) appURL := ctx.GetAppURL(fmt.Sprintf("auth/reset/%s", token))
go mail.PasswordReset(u.Email, appURL) mailer := mail.Mailer{Runtime: h.Runtime, Store: h.Store, Context: ctx}
go mailer.PasswordReset(u.Email, appURL)
response.WriteEmpty(w) response.WriteEmpty(w)
} }

Some files were not shown because too many files have changed in this diff Show more