From 562872a4a8a7b5bd1f880dca6b31f2e0f11ce2dc Mon Sep 17 00:00:00 2001 From: Harvey Kandola Date: Wed, 2 Aug 2017 15:26:31 +0100 Subject: [PATCH] removed old API --- build.sh | 2 +- core/api/endpoint/attachment_endpoint.go | 226 ---- core/api/endpoint/authentication_endpoint.go | 356 ----- core/api/endpoint/conversion_endpoint.go | 263 ---- core/api/endpoint/doc.go | 13 - core/api/endpoint/document_endpoint.go | 447 ------- core/api/endpoint/endpoint_test.go | 110 -- core/api/endpoint/global_endpoint.go | 271 ---- core/api/endpoint/init.go | 173 --- core/api/endpoint/jwt.go | 141 -- core/api/endpoint/keycloak.go | 514 -------- core/api/endpoint/label_endpoint.go | 935 ------------- core/api/endpoint/link_endpoint.go | 168 --- core/api/endpoint/meta_endpoint.go | 179 --- core/api/endpoint/models/models.go | 82 -- core/api/endpoint/org_endpoint.go | 108 -- core/api/endpoint/page_endpoint.go | 1157 ----------------- core/api/endpoint/pin_endpoint.go | 256 ---- core/api/endpoint/sections_endpoint.go | 416 ------ core/api/endpoint/templates_endpoint.go | 572 -------- core/api/endpoint/user_endpoint.go | 768 ----------- core/api/entity/nulltime.go | 42 - core/api/entity/objects.go | 567 -------- core/api/mail/mailer_test.go | 106 -- core/api/plugins/glick.go | 8 +- core/api/repair.go | 8 - core/api/request/account.go | 155 --- core/api/request/account_test.go | 139 -- core/api/request/activity.go | 71 - core/api/request/attachment.go | 102 -- core/api/request/attachment_test.go | 106 -- core/api/request/block.go | 166 --- core/api/request/config.go | 152 --- core/api/request/context.go | 240 ---- core/api/request/context_test.go | 80 -- core/api/request/doc.go | 13 - core/api/request/document.go | 428 ------ core/api/request/document_test.go | 256 ---- core/api/request/domain.go | 44 - core/api/request/domain_test.go | 42 - core/api/request/event.go | 59 - core/api/request/init.go | 132 -- core/api/request/init_test.go | 37 - core/api/request/label.go | 178 --- core/api/request/label_test.go | 149 --- core/api/request/labelrole.go | 133 -- core/api/request/labelrole_test.go | 236 ---- core/api/request/link.go | 285 ---- core/api/request/organization.go | 210 --- core/api/request/organization_test.go | 121 -- core/api/request/page.go | 555 -------- core/api/request/page_test.go | 278 ---- core/api/request/pin.go | 139 -- core/api/request/search.go | 468 ------- core/api/request/setup.go | 68 - core/api/request/user.go | 320 ----- core/api/request/user_test.go | 218 ---- core/api/store/local.go | 124 -- core/api/store/local_test.go | 90 -- core/api/store/store.go | 73 -- core/api/store/store_test.go | 63 - core/api/util/writeHTTP.go | 188 --- core/database/check.go | 6 - core/database/create.go | 123 +- core/database/migrate.go | 22 +- domain/auth/endpoint.go | 2 +- domain/conversion/conversion.go | 8 +- domain/conversion/store/local.go | 12 +- domain/conversion/store/local_test.go | 90 -- {core/api => domain}/mail/email.html | 0 .../mail/invite-existing-user.html | 0 .../api => domain}/mail/invite-new-user.html | 0 {core/api => domain}/mail/mailer.go | 126 +- {core/api => domain}/mail/password-reset.html | 0 .../mail/share-folder-existing-user.html | 0 .../mail/share-folder-new-user.html | 0 {core/api => domain}/mail/smtp.go | 0 domain/page/endpoint.go | 4 +- domain/section/airtable/airtable.go | 4 +- domain/section/code/code.go | 4 +- domain/section/endpoint.go | 4 +- domain/section/gemini/gemini.go | 34 +- domain/section/gemini/model.go | 13 +- domain/section/github/auth.go | 32 +- domain/section/github/github.go | 20 +- domain/section/github/lists.go | 2 +- domain/section/markdown/markdown.go | 4 +- domain/section/papertrail/papertrail.go | 22 +- domain/section/provider/provider.go | 70 +- domain/section/register.go | 23 +- domain/section/section_test.go | 82 -- domain/section/table/table.go | 4 +- domain/section/trello/trello.go | 17 +- domain/section/wysiwyg/wysiwyg.go | 4 +- domain/setting/endpoint.go | 8 +- domain/setting/mysql/setting.go | 9 +- domain/space/endpoint.go | 16 +- domain/space/space.go | 9 +- domain/storer.go | 8 +- domain/user/endpoint.go | 11 +- edition/boot/runtime.go | 2 +- edition/community.go | 8 +- server/routing/routes.go | 4 +- server/server.go | 8 +- server/web/serve.go | 12 +- 105 files changed, 337 insertions(+), 14496 deletions(-) delete mode 100644 core/api/endpoint/attachment_endpoint.go delete mode 100644 core/api/endpoint/authentication_endpoint.go delete mode 100644 core/api/endpoint/conversion_endpoint.go delete mode 100644 core/api/endpoint/doc.go delete mode 100644 core/api/endpoint/document_endpoint.go delete mode 100644 core/api/endpoint/endpoint_test.go delete mode 100644 core/api/endpoint/global_endpoint.go delete mode 100644 core/api/endpoint/init.go delete mode 100644 core/api/endpoint/jwt.go delete mode 100644 core/api/endpoint/keycloak.go delete mode 100644 core/api/endpoint/label_endpoint.go delete mode 100644 core/api/endpoint/link_endpoint.go delete mode 100644 core/api/endpoint/meta_endpoint.go delete mode 100644 core/api/endpoint/models/models.go delete mode 100644 core/api/endpoint/org_endpoint.go delete mode 100644 core/api/endpoint/page_endpoint.go delete mode 100644 core/api/endpoint/pin_endpoint.go delete mode 100644 core/api/endpoint/sections_endpoint.go delete mode 100644 core/api/endpoint/templates_endpoint.go delete mode 100644 core/api/endpoint/user_endpoint.go delete mode 100644 core/api/entity/nulltime.go delete mode 100644 core/api/entity/objects.go delete mode 100644 core/api/mail/mailer_test.go delete mode 100644 core/api/repair.go delete mode 100644 core/api/request/account.go delete mode 100644 core/api/request/account_test.go delete mode 100644 core/api/request/activity.go delete mode 100644 core/api/request/attachment.go delete mode 100644 core/api/request/attachment_test.go delete mode 100644 core/api/request/block.go delete mode 100644 core/api/request/config.go delete mode 100644 core/api/request/context.go delete mode 100644 core/api/request/context_test.go delete mode 100644 core/api/request/doc.go delete mode 100644 core/api/request/document.go delete mode 100644 core/api/request/document_test.go delete mode 100644 core/api/request/domain.go delete mode 100644 core/api/request/domain_test.go delete mode 100644 core/api/request/event.go delete mode 100644 core/api/request/init.go delete mode 100644 core/api/request/init_test.go delete mode 100644 core/api/request/label.go delete mode 100644 core/api/request/label_test.go delete mode 100644 core/api/request/labelrole.go delete mode 100644 core/api/request/labelrole_test.go delete mode 100644 core/api/request/link.go delete mode 100644 core/api/request/organization.go delete mode 100644 core/api/request/organization_test.go delete mode 100644 core/api/request/page.go delete mode 100644 core/api/request/page_test.go delete mode 100644 core/api/request/pin.go delete mode 100644 core/api/request/search.go delete mode 100644 core/api/request/setup.go delete mode 100644 core/api/request/user.go delete mode 100644 core/api/request/user_test.go delete mode 100644 core/api/store/local.go delete mode 100644 core/api/store/local_test.go delete mode 100644 core/api/store/store.go delete mode 100644 core/api/store/store_test.go delete mode 100644 core/api/util/writeHTTP.go delete mode 100644 domain/conversion/store/local_test.go rename {core/api => domain}/mail/email.html (100%) rename {core/api => domain}/mail/invite-existing-user.html (100%) rename {core/api => domain}/mail/invite-new-user.html (100%) rename {core/api => domain}/mail/mailer.go (60%) rename {core/api => domain}/mail/password-reset.html (100%) rename {core/api => domain}/mail/share-folder-existing-user.html (100%) rename {core/api => domain}/mail/share-folder-new-user.html (100%) rename {core/api => domain}/mail/smtp.go (100%) delete mode 100644 domain/section/section_test.go diff --git a/build.sh b/build.sh index 1a842a1f..54ea8f5e 100755 --- a/build.sh +++ b/build.sh @@ -19,7 +19,7 @@ cp gui/dist-prod/*.* embed/bindata cp gui/dist-prod/favicon.ico embed/bindata/public rm -rf 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 rm -rf embed/bindata/scripts mkdir -p embed/bindata/scripts diff --git a/core/api/endpoint/attachment_endpoint.go b/core/api/endpoint/attachment_endpoint.go deleted file mode 100644 index f6c7e561..00000000 --- a/core/api/endpoint/attachment_endpoint.go +++ /dev/null @@ -1,226 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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) -} diff --git a/core/api/endpoint/authentication_endpoint.go b/core/api/endpoint/authentication_endpoint.go deleted file mode 100644 index 81b9144e..00000000 --- a/core/api/endpoint/authentication_endpoint.go +++ /dev/null @@ -1,356 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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 -} diff --git a/core/api/endpoint/conversion_endpoint.go b/core/api/endpoint/conversion_endpoint.go deleted file mode 100644 index d31d71c8..00000000 --- a/core/api/endpoint/conversion_endpoint.go +++ /dev/null @@ -1,263 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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 -} diff --git a/core/api/endpoint/doc.go b/core/api/endpoint/doc.go deleted file mode 100644 index 4277c025..00000000 --- a/core/api/endpoint/doc.go +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// https://documize.com - -// Package endpoint provides API endpoints for Documize. -package endpoint diff --git a/core/api/endpoint/document_endpoint.go b/core/api/endpoint/document_endpoint.go deleted file mode 100644 index 5cc6febb..00000000 --- a/core/api/endpoint/document_endpoint.go +++ /dev/null @@ -1,447 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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("")...) - for _, page := range pages { - html = append(html, []byte(fmt.Sprintf("", page.Level))...) - html = append(html, stringutil.EscapeHTMLcomplexCharsByte([]byte(page.Title))...) - html = append(html, []byte(fmt.Sprintf("", page.Level))...) - html = append(html, stringutil.EscapeHTMLcomplexCharsByte([]byte(page.Body))...) - } - html = append(html, []byte("")...) - - 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) -} diff --git a/core/api/endpoint/endpoint_test.go b/core/api/endpoint/endpoint_test.go deleted file mode 100644 index 1770d5c3..00000000 --- a/core/api/endpoint/endpoint_test.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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() - } - } -} - -*/ diff --git a/core/api/endpoint/global_endpoint.go b/core/api/endpoint/global_endpoint.go deleted file mode 100644 index 8d4be1cb..00000000 --- a/core/api/endpoint/global_endpoint.go +++ /dev/null @@ -1,271 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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"` -} - -/* - - some key - some signature - -*/ - -// 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) -} diff --git a/core/api/endpoint/init.go b/core/api/endpoint/init.go deleted file mode 100644 index 84b8f765..00000000 --- a/core/api/endpoint/init.go +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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) -} diff --git a/core/api/endpoint/jwt.go b/core/api/endpoint/jwt.go deleted file mode 100644 index 616d64cb..00000000 --- a/core/api/endpoint/jwt.go +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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 -} diff --git a/core/api/endpoint/keycloak.go b/core/api/endpoint/keycloak.go deleted file mode 100644 index c106a577..00000000 --- a/core/api/endpoint/keycloak.go +++ /dev/null @@ -1,514 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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"` -} diff --git a/core/api/endpoint/label_endpoint.go b/core/api/endpoint/label_endpoint.go deleted file mode 100644 index 5f521afd..00000000 --- a/core/api/endpoint/label_endpoint.go +++ /dev/null @@ -1,935 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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 -} diff --git a/core/api/endpoint/link_endpoint.go b/core/api/endpoint/link_endpoint.go deleted file mode 100644 index 994c9180..00000000 --- a/core/api/endpoint/link_endpoint.go +++ /dev/null @@ -1,168 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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) -} diff --git a/core/api/endpoint/meta_endpoint.go b/core/api/endpoint/meta_endpoint.go deleted file mode 100644 index 183ea71b..00000000 --- a/core/api/endpoint/meta_endpoint.go +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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 := - ` - - {{range .}} - {{ .URL }} - {{ .Date }} - {{end}} -` - - 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 -} diff --git a/core/api/endpoint/models/models.go b/core/api/endpoint/models/models.go deleted file mode 100644 index 054e3a41..00000000 --- a/core/api/endpoint/models/models.go +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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"` -} diff --git a/core/api/endpoint/org_endpoint.go b/core/api/endpoint/org_endpoint.go deleted file mode 100644 index 7d46bbbd..00000000 --- a/core/api/endpoint/org_endpoint.go +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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) -} diff --git a/core/api/endpoint/page_endpoint.go b/core/api/endpoint/page_endpoint.go deleted file mode 100644 index e993cf71..00000000 --- a/core/api/endpoint/page_endpoint.go +++ /dev/null @@ -1,1157 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// https://documize.com - -package endpoint - -import ( - "database/sql" - "encoding/json" - // "html/template" - "io/ioutil" - "net/http" - "strconv" - - "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/streamutil" - "github.com/documize/community/core/uniqueid" - "github.com/documize/community/domain/section/provider" - htmldiff "github.com/documize/html-diff" - "github.com/gorilla/mux" -) - -// AddDocumentPage inserts new section into document. -func AddDocumentPage(w http.ResponseWriter, r *http.Request) { - if IsInvalidLicense() { - util.WriteBadLicense(w) - return - } - - method := "AddDocumentPage" - 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 - } - - defer streamutil.Close(r.Body) - body, err := ioutil.ReadAll(r.Body) - - if err != nil { - writeBadRequestError(w, method, "Bad payload") - return - } - - model := new(models.PageModel) - err = json.Unmarshal(body, &model) - - if err != nil { - writePayloadError(w, method, err) - return - } - - if model.Page.DocumentID != documentID { - writeBadRequestError(w, method, "documentID mismatch") - return - } - - if model.Meta.DocumentID != documentID { - writeBadRequestError(w, method, "documentID mismatch") - return - } - - pageID := uniqueid.Generate() - model.Page.RefID = pageID - model.Meta.PageID = pageID - model.Meta.OrgID = p.Context.OrgID // required for Render call below - model.Meta.UserID = p.Context.UserID // required for Render call below - model.Page.SetDefaults() - model.Meta.SetDefaults() - // page.Title = template.HTMLEscapeString(page.Title) - - 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 - - output, ok := provider.Render(model.Page.ContentType, provider.NewContext(model.Meta.OrgID, model.Meta.UserID), model.Meta.Config, model.Meta.RawBody) - if !ok { - log.ErrorString("provider.Render could not find: " + model.Page.ContentType) - } - - model.Page.Body = output - - err = p.AddPage(*model) - if err != nil { - log.IfErr(tx.Rollback()) - writeGeneralSQLError(w, method, err) - return - } - - if len(model.Page.BlockID) > 0 { - p.IncrementBlockUsage(model.Page.BlockID) - } - - _ = p.RecordUserActivity(entity.UserActivity{ - LabelID: doc.LabelID, - SourceID: model.Page.DocumentID, - SourceType: entity.ActivitySourceTypeDocument, - ActivityType: entity.ActivityTypeCreated}) - - p.RecordEvent(entity.EventTypeSectionAdd) - - log.IfErr(tx.Commit()) - - newPage, _ := p.GetPage(pageID) - - json, err := json.Marshal(newPage) - if err != nil { - writeJSONMarshalError(w, method, "page", err) - return - } - - writeSuccessBytes(w, json) -} - -// GetDocumentPage gets specified page for document. -func GetDocumentPage(w http.ResponseWriter, r *http.Request) { - method := "GetDocumentPage" - p := request.GetPersister(r) - - params := mux.Vars(r) - documentID := params["documentID"] - pageID := params["pageID"] - - if len(documentID) == 0 { - writeMissingDataError(w, method, "documentID") - return - } - - if len(pageID) == 0 { - writeMissingDataError(w, method, "pageID") - return - } - - if !p.CanViewDocument(documentID) { - writeForbiddenError(w) - return - } - - page, err := p.GetPage(pageID) - - if err == sql.ErrNoRows { - writeNotFoundError(w, method, documentID) - return - } - - if err != nil { - writeGeneralSQLError(w, method, err) - return - } - - if page.DocumentID != documentID { - writeBadRequestError(w, method, "documentID mismatch") - return - } - - json, err := json.Marshal(page) - - if err != nil { - writeJSONMarshalError(w, method, "document", err) - return - } - - writeSuccessBytes(w, json) -} - -// GetDocumentPages gets all pages for document. -func GetDocumentPages(w http.ResponseWriter, r *http.Request) { - method := "GetDocumentPages" - 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 - } - - query := r.URL.Query() - content := query.Get("content") - - var pages []entity.Page - var err error - - if len(content) > 0 { - pages, err = p.GetPagesWithoutContent(documentID) - } else { - pages, err = p.GetPages(documentID) - } - - if len(pages) == 0 { - pages = []entity.Page{} - } - - if err != nil { - writeGeneralSQLError(w, method, err) - return - } - - json, err := json.Marshal(pages) - - if err != nil { - writeJSONMarshalError(w, method, "document", err) - return - } - - writeSuccessBytes(w, json) -} - -// GetDocumentPagesBatch gets specified pages for document. -func GetDocumentPagesBatch(w http.ResponseWriter, r *http.Request) { - method := "GetDocumentPagesBatch" - 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 - } - - defer streamutil.Close(r.Body) - body, err := ioutil.ReadAll(r.Body) - - if err != nil { - writePayloadError(w, method, err) - return - } - - requestedPages := string(body) - - pages, err := p.GetPagesWhereIn(documentID, requestedPages) - - if err == sql.ErrNoRows { - writeNotFoundError(w, method, documentID) - return - } - - if err != nil { - writeGeneralSQLError(w, method, err) - return - } - - json, err := json.Marshal(pages) - if err != nil { - writeJSONMarshalError(w, method, "document", err) - return - } - - writeSuccessBytes(w, json) -} - -// DeleteDocumentPage deletes a page. -func DeleteDocumentPage(w http.ResponseWriter, r *http.Request) { - if IsInvalidLicense() { - util.WriteBadLicense(w) - return - } - - method := "DeleteDocumentPage" - p := request.GetPersister(r) - - params := mux.Vars(r) - documentID := params["documentID"] - - if len(documentID) == 0 { - writeMissingDataError(w, method, "documentID") - return - } - - pageID := params["pageID"] - - if len(pageID) == 0 { - writeMissingDataError(w, method, "pageID") - 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 - - page, err := p.GetPage(pageID) - if err != nil { - log.IfErr(tx.Rollback()) - writeGeneralSQLError(w, method, err) - return - } - - if len(page.BlockID) > 0 { - p.DecrementBlockUsage(page.BlockID) - } - - _, err = p.DeletePage(documentID, pageID) - - if err != nil { - log.IfErr(tx.Rollback()) - writeGeneralSQLError(w, method, err) - return - } - - _ = p.RecordUserActivity(entity.UserActivity{ - LabelID: doc.LabelID, - SourceID: documentID, - SourceType: entity.ActivitySourceTypeDocument, - ActivityType: entity.ActivityTypeDeleted}) - - p.RecordEvent(entity.EventTypeSectionDelete) - - log.IfErr(tx.Commit()) - - writeSuccessEmptyJSON(w) -} - -// DeleteDocumentPages batch deletes pages. -func DeleteDocumentPages(w http.ResponseWriter, r *http.Request) { - if IsInvalidLicense() { - util.WriteBadLicense(w) - return - } - - method := "DeleteDocumentPages" - 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 - } - - defer streamutil.Close(r.Body) - body, err := ioutil.ReadAll(r.Body) - if err != nil { - writeBadRequestError(w, method, "Bad body") - return - } - - model := new([]models.PageLevelRequestModel) - err = json.Unmarshal(body, &model) - - if err != nil { - writePayloadError(w, method, err) - 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 - - for _, page := range *model { - pageData, err := p.GetPage(page.PageID) - if err != nil { - log.IfErr(tx.Rollback()) - writeGeneralSQLError(w, method, err) - return - } - - if len(pageData.BlockID) > 0 { - p.DecrementBlockUsage(pageData.BlockID) - } - - _, err = p.DeletePage(documentID, page.PageID) - if err != nil { - log.IfErr(tx.Rollback()) - writeGeneralSQLError(w, method, err) - return - } - } - - _ = p.RecordUserActivity(entity.UserActivity{ - LabelID: doc.LabelID, - SourceID: documentID, - SourceType: entity.ActivitySourceTypeDocument, - ActivityType: entity.ActivityTypeDeleted}) - - p.RecordEvent(entity.EventTypeSectionDelete) - - log.IfErr(tx.Commit()) - - writeSuccessEmptyJSON(w) -} - -// UpdateDocumentPage will persist changed page and note the fact -// that this is a new revision. If the page is the first in a document -// then the corresponding document title will also be changed. -func UpdateDocumentPage(w http.ResponseWriter, r *http.Request) { - if IsInvalidLicense() { - util.WriteBadLicense(w) - return - } - - method := "UpdateDocumentPage" - p := request.GetPersister(r) - params := mux.Vars(r) - - if !p.Context.Editor { - writeForbiddenError(w) - return - } - - documentID := params["documentID"] - if len(documentID) == 0 { - writeMissingDataError(w, method, "documentID") - return - } - - pageID := params["pageID"] - if len(pageID) == 0 { - writeMissingDataError(w, method, "pageID") - return - } - - defer streamutil.Close(r.Body) - body, err := ioutil.ReadAll(r.Body) - if err != nil { - writeBadRequestError(w, method, "Bad request body") - return - } - - model := new(models.PageModel) - err = json.Unmarshal(body, &model) - if err != nil { - writePayloadError(w, method, err) - return - } - - if model.Page.RefID != pageID || model.Page.DocumentID != documentID { - writeBadRequestError(w, method, "id mismatch") - return - } - - doc, err := p.GetDocument(documentID) - if err != nil { - writeGeneralSQLError(w, method, err) - return - } - - p.Context.Transaction, err = request.Db.Beginx() - if err != nil { - writeTransactionError(w, method, err) - return - } - - model.Page.SetDefaults() - model.Meta.SetDefaults() - - oldPageMeta, err := p.GetPageMeta(pageID) - - if err != nil { - log.Error("unable to fetch old pagemeta record", err) - writeBadRequestError(w, method, err.Error()) - return - } - - output, ok := provider.Render(model.Page.ContentType, provider.NewContext(model.Meta.OrgID, oldPageMeta.UserID), model.Meta.Config, model.Meta.RawBody) - if !ok { - log.ErrorString("provider.Render could not find: " + model.Page.ContentType) - } - model.Page.Body = output - - var skipRevision bool - skipRevision, err = strconv.ParseBool(r.URL.Query().Get("r")) - - refID := uniqueid.Generate() - err = p.UpdatePage(model.Page, refID, p.Context.UserID, skipRevision) - if err != nil { - writeGeneralSQLError(w, method, err) - log.IfErr(p.Context.Transaction.Rollback()) - return - } - - err = p.UpdatePageMeta(model.Meta, true) // change the UserID to the current one - - _ = p.RecordUserActivity(entity.UserActivity{ - LabelID: doc.LabelID, - SourceID: model.Page.DocumentID, - SourceType: entity.ActivitySourceTypeDocument, - ActivityType: entity.ActivityTypeEdited}) - - p.RecordEvent(entity.EventTypeSectionUpdate) - - log.IfErr(p.Context.Transaction.Commit()) - - updatedPage, err := p.GetPage(pageID) - - json, err := json.Marshal(updatedPage) - if err != nil { - writeJSONMarshalError(w, method, "page", err) - return - } - - writeSuccessBytes(w, json) -} - -// ChangeDocumentPageSequence will swap page sequence for a given number of pages. -func ChangeDocumentPageSequence(w http.ResponseWriter, r *http.Request) { - if IsInvalidLicense() { - util.WriteBadLicense(w) - return - } - - method := "ChangeDocumentPageSequence" - 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 - } - - defer streamutil.Close(r.Body) - body, err := ioutil.ReadAll(r.Body) - - if err != nil { - writePayloadError(w, method, err) - return - } - - model := new([]models.PageSequenceRequestModel) - err = json.Unmarshal(body, &model) - - if err != nil { - writeBadRequestError(w, method, "bad payload") - return - } - - tx, err := request.Db.Beginx() - - if err != nil { - writeTransactionError(w, method, err) - return - } - - p.Context.Transaction = tx - - for _, page := range *model { - err = p.UpdatePageSequence(documentID, page.PageID, page.Sequence) - - if err != nil { - log.IfErr(tx.Rollback()) - writeGeneralSQLError(w, method, err) - return - } - } - - p.RecordEvent(entity.EventTypeSectionResequence) - - log.IfErr(tx.Commit()) - - writeSuccessEmptyJSON(w) -} - -// ChangeDocumentPageLevel handles page indent/outdent changes. -func ChangeDocumentPageLevel(w http.ResponseWriter, r *http.Request) { - if IsInvalidLicense() { - util.WriteBadLicense(w) - return - } - - method := "ChangeDocumentPageLevel" - 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 - } - - defer streamutil.Close(r.Body) - body, err := ioutil.ReadAll(r.Body) - - if err != nil { - writePayloadError(w, method, err) - return - } - - model := new([]models.PageLevelRequestModel) - err = json.Unmarshal(body, &model) - - if err != nil { - writeBadRequestError(w, method, "bad payload") - return - } - - tx, err := request.Db.Beginx() - - if err != nil { - writeTransactionError(w, method, err) - return - } - - p.Context.Transaction = tx - - for _, page := range *model { - err = p.UpdatePageLevel(documentID, page.PageID, page.Level) - - if err != nil { - log.IfErr(tx.Rollback()) - writeGeneralSQLError(w, method, err) - return - } - } - - p.RecordEvent(entity.EventTypeSectionResequence) - - log.IfErr(tx.Commit()) - - writeSuccessEmptyJSON(w) -} - -// GetDocumentPageMeta gets page meta data for specified document page. -func GetDocumentPageMeta(w http.ResponseWriter, r *http.Request) { - method := "GetDocumentPageMeta" - p := request.GetPersister(r) - - params := mux.Vars(r) - documentID := params["documentID"] - pageID := params["pageID"] - - if len(documentID) == 0 { - writeMissingDataError(w, method, "documentID") - return - } - - if len(pageID) == 0 { - writeMissingDataError(w, method, "pageID") - return - } - - if !p.CanViewDocument(documentID) { - writeForbiddenError(w) - return - } - - meta, err := p.GetPageMeta(pageID) - - if err == sql.ErrNoRows { - writeNotFoundError(w, method, pageID) - return - } - - if err != nil { - writeGeneralSQLError(w, method, err) - return - } - - if meta.DocumentID != documentID { - writeBadRequestError(w, method, "documentID mismatch") - return - } - - json, err := json.Marshal(meta) - - if err != nil { - writeJSONMarshalError(w, method, "pagemeta", err) - return - } - - writeSuccessBytes(w, json) -} - -// GetPageMoveCopyTargets returns available documents for page copy/move axction. -func GetPageMoveCopyTargets(w http.ResponseWriter, r *http.Request) { - method := "GetPageMoveCopyTargets" - p := request.GetPersister(r) - - var d []entity.Document - var err error - - d, err = p.GetDocumentList() - - if len(d) == 0 { - d = []entity.Document{} - } - - if err != nil { - writeGeneralSQLError(w, method, err) - return - } - - json, err := json.Marshal(d) - - if err != nil { - writeJSONMarshalError(w, method, "document", err) - return - } - - writeSuccessBytes(w, json) -} - -//************************************************** -// Page Revisions -//************************************************** - -// GetDocumentRevisions returns all changes for a document. -func GetDocumentRevisions(w http.ResponseWriter, r *http.Request) { - if IsInvalidLicense() { - util.WriteBadLicense(w) - return - } - - method := "GetDocumentPageRevisions" - 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 - } - - revisions, _ := p.GetDocumentRevisions(documentID) - - if len(revisions) == 0 { - revisions = []entity.Revision{} - } - - payload, err := json.Marshal(revisions) - - if err != nil { - writeJSONMarshalError(w, method, "revision", err) - return - } - - p.RecordEvent(entity.EventTypeDocumentRevisions) - - writeSuccessBytes(w, payload) -} - -// GetDocumentPageRevisions returns all changes for a given page. -func GetDocumentPageRevisions(w http.ResponseWriter, r *http.Request) { - if IsInvalidLicense() { - util.WriteBadLicense(w) - return - } - - method := "GetDocumentPageRevisions" - 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 - } - - pageID := params["pageID"] - - if len(pageID) == 0 { - writeMissingDataError(w, method, "pageID") - return - } - - revisions, _ := p.GetPageRevisions(pageID) - - if len(revisions) == 0 { - revisions = []entity.Revision{} - } - - payload, err := json.Marshal(revisions) - - if err != nil { - writeJSONMarshalError(w, method, "revision", err) - return - } - - writeSuccessBytes(w, payload) -} - -// GetDocumentPageDiff returns HTML diff between two revisions of a given page. -func GetDocumentPageDiff(w http.ResponseWriter, r *http.Request) { - if IsInvalidLicense() { - util.WriteBadLicense(w) - return - } - - method := "GetDocumentPageDiff" - 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 - } - - pageID := params["pageID"] - - if len(pageID) == 0 { - writeMissingDataError(w, method, "pageID") - return - } - - revisionID := params["revisionID"] - - if len(revisionID) == 0 { - writeMissingDataError(w, method, "revisionID") - return - } - - page, err := p.GetPage(pageID) - - if err == sql.ErrNoRows { - writeNotFoundError(w, method, revisionID) - return - } - - revision, _ := p.GetPageRevision(revisionID) - - latestHTML := page.Body - previousHTML := revision.Body - var result []byte - - var cfg = &htmldiff.Config{ - Granularity: 5, - InsertedSpan: []htmldiff.Attribute{{Key: "style", Val: "background-color: palegreen;"}}, - DeletedSpan: []htmldiff.Attribute{{Key: "style", Val: "background-color: lightpink; text-decoration: line-through;"}}, - ReplacedSpan: []htmldiff.Attribute{{Key: "style", Val: "background-color: lightskyblue;"}}, - CleanTags: []string{"documize"}, - } - res, err := cfg.HTMLdiff([]string{latestHTML, previousHTML}) - if err != nil { - writeServerError(w, method, err) - return - } - - result = []byte(res[0]) - - _, err = w.Write(result) - log.IfErr(err) -} - -// RollbackDocumentPage rolls-back to a specific page revision. -func RollbackDocumentPage(w http.ResponseWriter, r *http.Request) { - method := "RollbackDocumentPage" - p := request.GetPersister(r) - - params := mux.Vars(r) - documentID := params["documentID"] - - if len(documentID) == 0 { - writeMissingDataError(w, method, "documentID") - return - } - - pageID := params["pageID"] - - if len(pageID) == 0 { - writeMissingDataError(w, method, "pageID") - return - } - - revisionID := params["revisionID"] - - if len(revisionID) == 0 { - writeMissingDataError(w, method, "revisionID") - 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 - - // fetch page - page, err := p.GetPage(pageID) - if err != nil { - writeGeneralSQLError(w, method, err) - return - } - - // fetch page meta - meta, err := p.GetPageMeta(pageID) - if err != nil { - writeGeneralSQLError(w, method, err) - return - } - - // fetch revision - revision, err := p.GetPageRevision(revisionID) - if err != nil { - writeGeneralSQLError(w, method, err) - return - } - - // fetch doc - doc, err := p.GetDocument(documentID) - if err != nil { - writeGeneralSQLError(w, method, err) - return - } - - // roll back page - page.Body = revision.Body - refID := uniqueid.Generate() - - err = p.UpdatePage(page, refID, p.Context.UserID, false) - if err != nil { - log.IfErr(tx.Rollback()) - writeGeneralSQLError(w, method, err) - return - } - - // roll back page meta - meta.Config = revision.Config - meta.RawBody = revision.RawBody - - err = p.UpdatePageMeta(meta, false) - if err != nil { - log.IfErr(tx.Rollback()) - writeGeneralSQLError(w, method, err) - return - } - - _ = p.RecordUserActivity(entity.UserActivity{ - LabelID: doc.LabelID, - SourceID: page.DocumentID, - SourceType: entity.ActivitySourceTypeDocument, - ActivityType: entity.ActivityTypeReverted}) - - p.RecordEvent(entity.EventTypeSectionRollback) - - log.IfErr(tx.Commit()) - - payload, err := json.Marshal(page) - if err != nil { - writeJSONMarshalError(w, method, "revision", err) - return - } - - writeSuccessBytes(w, payload) -} - -//************************************************** -// Copy Move Page -//************************************************** - -// CopyPage copies page to either same or different document. -func CopyPage(w http.ResponseWriter, r *http.Request) { - method := "CopyPage" - p := request.GetPersister(r) - - params := mux.Vars(r) - documentID := params["documentID"] - pageID := params["pageID"] - targetID := params["targetID"] - - // data checks - if len(documentID) == 0 { - writeMissingDataError(w, method, "documentID") - return - } - if len(pageID) == 0 { - writeMissingDataError(w, method, "pageID") - return - } - if len(targetID) == 0 { - writeMissingDataError(w, method, "targetID") - return - } - - // permission - if !p.CanViewDocument(documentID) { - writeForbiddenError(w) - return - } - - // fetch data - doc, err := p.GetDocument(documentID) - if err != nil { - writeGeneralSQLError(w, method, err) - return - } - - page, err := p.GetPage(pageID) - if err == sql.ErrNoRows { - writeNotFoundError(w, method, documentID) - return - } - if err != nil { - writeGeneralSQLError(w, method, err) - return - } - - pageMeta, err := p.GetPageMeta(pageID) - if err == sql.ErrNoRows { - writeNotFoundError(w, method, documentID) - return - } - if err != nil { - writeGeneralSQLError(w, method, err) - return - } - - newPageID := uniqueid.Generate() - page.RefID = newPageID - page.Level = 1 - page.Sequence = 0 - page.DocumentID = targetID - page.UserID = p.Context.UserID - pageMeta.DocumentID = targetID - pageMeta.PageID = newPageID - pageMeta.UserID = p.Context.UserID - - model := new(models.PageModel) - model.Meta = pageMeta - model.Page = page - - tx, err := request.Db.Beginx() - if err != nil { - writeTransactionError(w, method, err) - return - } - p.Context.Transaction = tx - - err = p.AddPage(*model) - if err != nil { - log.IfErr(tx.Rollback()) - writeGeneralSQLError(w, method, err) - return - } - - if len(model.Page.BlockID) > 0 { - p.IncrementBlockUsage(model.Page.BlockID) - } - - // Log action against target document - _ = p.RecordUserActivity(entity.UserActivity{ - LabelID: doc.LabelID, - SourceID: targetID, - SourceType: entity.ActivitySourceTypeDocument, - ActivityType: entity.ActivityTypeEdited}) - - p.RecordEvent(entity.EventTypeSectionCopy) - - log.IfErr(tx.Commit()) - - newPage, _ := p.GetPage(pageID) - json, err := json.Marshal(newPage) - - if err != nil { - writeJSONMarshalError(w, method, "page", err) - return - } - - writeSuccessBytes(w, json) -} diff --git a/core/api/endpoint/pin_endpoint.go b/core/api/endpoint/pin_endpoint.go deleted file mode 100644 index 73f3686f..00000000 --- a/core/api/endpoint/pin_endpoint.go +++ /dev/null @@ -1,256 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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) -} diff --git a/core/api/endpoint/sections_endpoint.go b/core/api/endpoint/sections_endpoint.go deleted file mode 100644 index ffb7dd94..00000000 --- a/core/api/endpoint/sections_endpoint.go +++ /dev/null @@ -1,416 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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) -} diff --git a/core/api/endpoint/templates_endpoint.go b/core/api/endpoint/templates_endpoint.go deleted file mode 100644 index f3e64d2c..00000000 --- a/core/api/endpoint/templates_endpoint.go +++ /dev/null @@ -1,572 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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 -} diff --git a/core/api/endpoint/user_endpoint.go b/core/api/endpoint/user_endpoint.go deleted file mode 100644 index 8d87715c..00000000 --- a/core/api/endpoint/user_endpoint.go +++ /dev/null @@ -1,768 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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) -} diff --git a/core/api/entity/nulltime.go b/core/api/entity/nulltime.go deleted file mode 100644 index eb48acf4..00000000 --- a/core/api/entity/nulltime.go +++ /dev/null @@ -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 -} diff --git a/core/api/entity/objects.go b/core/api/entity/objects.go deleted file mode 100644 index fca550ca..00000000 --- a/core/api/entity/objects.go +++ /dev/null @@ -1,567 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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" -) diff --git a/core/api/mail/mailer_test.go b/core/api/mail/mailer_test.go deleted file mode 100644 index ee3f78ee..00000000 --- a/core/api/mail/mailer_test.go +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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 diff --git a/core/api/plugins/glick.go b/core/api/plugins/glick.go index fcf91e46..2e6e1670 100644 --- a/core/api/plugins/glick.go +++ b/core/api/plugins/glick.go @@ -21,9 +21,9 @@ import ( "github.com/documize/community/core/api/convert/documizeapi" "github.com/documize/community/core/api/convert/html" "github.com/documize/community/core/api/convert/md" - "github.com/documize/community/core/api/request" api "github.com/documize/community/core/convapi" "github.com/documize/community/core/log" + "github.com/documize/community/domain" "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. 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. -func LibSetup() error { +func Setup(s *domain.Store) error { if insecure == "true" { glick.InsecureSkipVerifyTLS = true } @@ -100,7 +100,7 @@ func LibSetup() error { var json = make([]byte, 0) if PluginFile == "DB" { - json = []byte(request.ConfigString("FILEPLUGINS", "")) + json = []byte(s.Setting.Get("FILEPLUGINS", "")) if len(bytes.TrimSpace(json)) == 0 { return nil // don't fail if the DB does not exist yet } diff --git a/core/api/repair.go b/core/api/repair.go deleted file mode 100644 index 90aa6a33..00000000 --- a/core/api/repair.go +++ /dev/null @@ -1,8 +0,0 @@ -package api - -import ( - "github.com/documize/community/core/env" -) - -// Runtime is code repair in progress -var Runtime env.Runtime diff --git a/core/api/request/account.go b/core/api/request/account.go deleted file mode 100644 index 3d8a8d77..00000000 --- a/core/api/request/account.go +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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) -} diff --git a/core/api/request/account_test.go b/core/api/request/account_test.go deleted file mode 100644 index d0379c23..00000000 --- a/core/api/request/account_test.go +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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) - -} -*/ diff --git a/core/api/request/activity.go b/core/api/request/activity.go deleted file mode 100644 index 7cbda728..00000000 --- a/core/api/request/activity.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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 -} diff --git a/core/api/request/attachment.go b/core/api/request/attachment.go deleted file mode 100644 index 3702d9f3..00000000 --- a/core/api/request/attachment.go +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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 -} diff --git a/core/api/request/attachment_test.go b/core/api/request/attachment_test.go deleted file mode 100644 index e0130e3c..00000000 --- a/core/api/request/attachment_test.go +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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) -} -*/ diff --git a/core/api/request/block.go b/core/api/request/block.go deleted file mode 100644 index 8acfba3a..00000000 --- a/core/api/request/block.go +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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) -} diff --git a/core/api/request/config.go b/core/api/request/config.go deleted file mode 100644 index 29290c82..00000000 --- a/core/api/request/config.go +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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 -} diff --git a/core/api/request/context.go b/core/api/request/context.go deleted file mode 100644 index 1860ba3e..00000000 --- a/core/api/request/context.go +++ /dev/null @@ -1,240 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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 -} diff --git a/core/api/request/context_test.go b/core/api/request/context_test.go deleted file mode 100644 index 9684ebd4..00000000 --- a/core/api/request/context_test.go +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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") - } - -} -*/ diff --git a/core/api/request/doc.go b/core/api/request/doc.go deleted file mode 100644 index d1936914..00000000 --- a/core/api/request/doc.go +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// https://documize.com - -// Package request handles database requests for Documize, mostly from the endpoints package. -package request diff --git a/core/api/request/document.go b/core/api/request/document.go deleted file mode 100644 index 087089b6..00000000 --- a/core/api/request/document.go +++ /dev/null @@ -1,428 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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) -} diff --git a/core/api/request/document_test.go b/core/api/request/document_test.go deleted file mode 100644 index cbf1fd2a..00000000 --- a/core/api/request/document_test.go +++ /dev/null @@ -1,256 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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) - -} -*/ diff --git a/core/api/request/domain.go b/core/api/request/domain.go deleted file mode 100644 index 1ff22301..00000000 --- a/core/api/request/domain.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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) -} diff --git a/core/api/request/domain_test.go b/core/api/request/domain_test.go deleted file mode 100644 index fa33cd42..00000000 --- a/core/api/request/domain_test.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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) - } -} -*/ diff --git a/core/api/request/event.go b/core/api/request/event.go deleted file mode 100644 index eac98b4d..00000000 --- a/core/api/request/event.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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 -} diff --git a/core/api/request/init.go b/core/api/request/init.go deleted file mode 100644 index 1ae64ef3..00000000 --- a/core/api/request/init.go +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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) -} diff --git a/core/api/request/init_test.go b/core/api/request/init_test.go deleted file mode 100644 index b84bedb6..00000000 --- a/core/api/request/init_test.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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 -} -*/ diff --git a/core/api/request/label.go b/core/api/request/label.go deleted file mode 100644 index 99f96879..00000000 --- a/core/api/request/label.go +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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) -} diff --git a/core/api/request/label_test.go b/core/api/request/label_test.go deleted file mode 100644 index b0ff22d6..00000000 --- a/core/api/request/label_test.go +++ /dev/null @@ -1,149 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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 - -} -*/ diff --git a/core/api/request/labelrole.go b/core/api/request/labelrole.go deleted file mode 100644 index 0b13255b..00000000 --- a/core/api/request/labelrole.go +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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 -} diff --git a/core/api/request/labelrole_test.go b/core/api/request/labelrole_test.go deleted file mode 100644 index 98b0155a..00000000 --- a/core/api/request/labelrole_test.go +++ /dev/null @@ -1,236 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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) - -*/ -//} diff --git a/core/api/request/link.go b/core/api/request/link.go deleted file mode 100644 index 3426b3be..00000000 --- a/core/api/request/link.go +++ /dev/null @@ -1,285 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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) -} diff --git a/core/api/request/organization.go b/core/api/request/organization.go deleted file mode 100644 index b9079243..00000000 --- a/core/api/request/organization.go +++ /dev/null @@ -1,210 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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 "" -} diff --git a/core/api/request/organization_test.go b/core/api/request/organization_test.go deleted file mode 100644 index 8e8d2c86..00000000 --- a/core/api/request/organization_test.go +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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) -} -*/ diff --git a/core/api/request/page.go b/core/api/request/page.go deleted file mode 100644 index 7d91d489..00000000 --- a/core/api/request/page.go +++ /dev/null @@ -1,555 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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 links. -// such links have an identifying attribute e.g. 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 -} diff --git a/core/api/request/page_test.go b/core/api/request/page_test.go deleted file mode 100644 index 12097b9f..00000000 --- a/core/api/request/page_test.go +++ /dev/null @@ -1,278 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// https://documize.com - -package request - -/* TODO(Elliott) -import ( - "strings" - "testing" - - "github.com/documize/community/documize/api/endpoint/models" - "github.com/documize/community/documize/api/entity" -) - -func testAddPages(t *testing.T, p *Persister) []entity.Page { - testPages := []entity.Page{ - { - BaseEntity: entity.BaseEntity{RefID: "testPage1"}, - OrgID: p.Context.OrgID, // string `json:"orgId"` - DocumentID: testDocID, // string `json:"documentId"` - Level: 1, // uint64 `json:"level"` - Title: "Document title", // string `json:"title"` - Body: "The quick brown fox jumps over the lazy dog", // string `json:"body"` - Sequence: 1.0, // float64 `json:"sequence"` - Revisions: 0, // uint64 `json:"revisions"` - }, - { - BaseEntity: entity.BaseEntity{RefID: "testPage2"}, - OrgID: p.Context.OrgID, // string `json:"orgId"` - DocumentID: testDocID, // string `json:"documentId"` - Level: 2, // uint64 `json:"level"` - Title: "Document sub-title one", // string `json:"title"` - Body: ` -The Tao that can be spoken is not the eternal Tao -The name that can be named is not the eternal name -The nameless is the origin of Heaven and Earth -The named is the mother of myriad things -Thus, constantly without desire, one observes its essence -Constantly with desire, one observes its manifestations -These two emerge together but differ in name -The unity is said to be the mystery -Mystery of mysteries, the door to all wonders -`, // string `json:"body"` - Sequence: 2.0, // float64 `json:"sequence"` - Revisions: 0, // uint64 `json:"revisions"` - }, - { - BaseEntity: entity.BaseEntity{RefID: "testPage3"}, - OrgID: p.Context.OrgID, // string `json:"orgId"` - DocumentID: testDocID, // string `json:"documentId"` - Level: 2, // uint64 `json:"level"` - Title: "Document sub-title two", // string `json:"title"` - Body: ` -Bent double, like old beggars under sacks, -Knock-kneed, coughing like hags, we cursed through sludge, -Till on the haunting flares we turned our backs, -And towards our distant rest began to trudge. -Men marched asleep. Many had lost their boots, -But limped on, blood-shod. All went lame; all blind; -Drunk with fatigue; deaf even to the hoots -Of gas-shells dropping softly behind. - -Gas! GAS! Quick, boys!—An ecstasy of fumbling -Fitting the clumsy helmets just in time, -But someone still was yelling out and stumbling -And flound’ring like a man in fire or lime.— -Dim through the misty panes and thick green light, -As under a green sea, I saw him drowning. - -In all my dreams before my helpless sight, -He plunges at me, guttering, choking, drowning. - -If in some smothering dreams, you too could pace -Behind the wagon that we flung him in, -And watch the white eyes writhing in his face, -His hanging face, like a devil’s sick of sin; -If you could hear, at every jolt, the blood -Come gargling from the froth-corrupted lungs, -Obscene as cancer, bitter as the cud -Of vile, incurable sores on innocent tongues,— -My friend, you would not tell with such high zest -To children ardent for some desperate glory, -The old Lie: Dulce et decorum est -Pro patria mori. -`, // string `json:"body"` - Sequence: 3.0, // float64 `json:"sequence"` - Revisions: 0, // uint64 `json:"revisions"` - }, - } - - for _, page := range testPages { - err := p.AddPage(models.PageModel{Page: page}) - if err != nil { - t.Error(err) - t.Fail() - } - p.testCommit(t) - } - return testPages -} - -func testDeletePages(t *testing.T, p *Persister, pages []entity.Page) { - p.testNewTx(t) // so that we can use it reliably in defer - for _, pg := range pages { - _, err := p.DeletePage(testDocID, pg.RefID) - if err != nil { - t.Error(err) - //t.Fail() - } - // this code is belt-and-braces, as document delete should also delete any pages - //if rows != 1 { - // t.Errorf("expected 1 page row deleted got %d", rows) - // //t.Fail() - //} - p.testCommit(t) - } -} - -func TestPage(t *testing.T) { - p := newTestPersister(t) - defer deleteTestAuditTrail(t, p) - org := testAddOrganization(t, p) - defer testDeleteOrganization(t, p) - user := testAddUser(t, p) - defer testDeleteUser(t, p) - acc := testAddAccount(t, p) - defer testDeleteAccount(t, p) - doc := testAddDocument(t, p) - defer testDeleteDocument(t, p) - pages := testAddPages(t, p) - defer testDeletePages(t, p, pages) - - // keep vars - _ = org - _ = user - _ = acc - _ = doc - - err := p.AddPage(models.PageModel{Page: pages[0]}) - if err == nil { - t.Error("did not error on add of duplicate record") - } - p.testRollback(t) - - retpgs, err := p.GetPages(doc.RefID) // a bad ID just brings back 0 pages, so not tested - if err != nil { - t.Error(err) - } - if len(retpgs) != len(pages) { - t.Errorf("wrong number of pages returned, expected %d got %d", len(pages), len(retpgs)) - } else { - for l := range retpgs { - if retpgs[l].Body != pages[l].Body { - t.Errorf("wrong body content") - } - } - } - p.testRollback(t) - - retpgswoc, err := p.GetPagesWithoutContent(doc.RefID) // a bad ID just brings back 0 pages, so not tested - if err != nil { - t.Error(err) - } - if len(retpgswoc) != len(pages) { - t.Errorf("wrong number of pages returned, expected %d got %d", len(pages), len(retpgswoc)) - } else { - for l := range retpgswoc { - if retpgswoc[l].Title != pages[l].Title { - t.Errorf("wrong title content") - } - } - } - p.testRollback(t) - - retpgswi, err := p.GetPagesWhereIn(doc.RefID, pages[0].BaseEntity.RefID+","+pages[2].BaseEntity.RefID) - if err != nil { - t.Error(err) - } - if len(retpgswi) != 2 { - t.Errorf("wrong number of pages returned, expected %d got %d", 2, len(retpgswi)) - } else { - if retpgswi[1].Body != pages[2].Body { - t.Errorf("wrong WhereIn content") - } - } - p.testRollback(t) - - retpg, err := p.GetPage(pages[0].BaseEntity.RefID) - if err != nil { - t.Error(err) - } - if retpg.Body != pages[0].Body { - t.Errorf("wrong page returned, expected body of `%s` got `%s`", pages[0].Body, retpg.Body) - } - p.testRollback(t) - - _, err = p.GetPage("XXXXXXXXXXX") - if err == nil { - t.Error("no error on unknown page") - } - p.testRollback(t) - - meaningOfLife := 42.0 - err = p.UpdatePageSequence(doc.RefID, "testPage3", meaningOfLife) - if err != nil { - t.Error(err) - } - p.testCommit(t) - retpg, err = p.GetPage("testPage3") - if err != nil { - t.Error(err) - } - if retpg.Sequence != meaningOfLife { - t.Errorf("wrong page returned, expected sequence of `%g` got `%g`", meaningOfLife, retpg.Sequence) - } - p.testRollback(t) - - err = p.UpdatePageLevel(doc.RefID, "testPage3", 3) - if err != nil { - t.Error(err) - } - p.testCommit(t) - retpg, err = p.GetPage("testPage3") - if err != nil { - t.Error(err) - } - if retpg.Level != 3 { - t.Errorf("wrong page returned, expected level of `3` got `%d`", retpg.Level) - } - p.testRollback(t) - - newPg := pages[0] - newPg.Body += "!" - err = p.UpdatePage(newPg, pages[0].BaseEntity.RefID, p.Context.UserID, false) - if err != nil { - t.Error(err) - } - p.testCommit(t) - retpg, err = p.GetPage(pages[0].BaseEntity.RefID) - if err != nil { - t.Error(err) - } - if retpg.Body[len(retpg.Body)-1] != byte('!') { - t.Errorf("wrong page returned, expected string ending in '!' got `%s`", retpg.Body) - } - p.testRollback(t) - - revs, err := p.GetPageRevisions(pages[0].BaseEntity.RefID) - if err != nil { - t.Error(err) - } - if len(revs) != 1 { - t.Error("wrong number of page revisions") - t.Fail() - } - if revs[0].Body != strings.TrimSuffix(pages[0].Body, "!") { - t.Error("wrong revision data:", revs[0].Body) - } - p.testRollback(t) - - rev, err := p.GetPageRevision(revs[0].BaseEntity.RefID) - if err != nil { - t.Error(err) - } - if revs[0].Body != rev.Body { - t.Error("wrong revision data:", revs[0].Body, rev.Body) - } - p.testRollback(t) -} -*/ diff --git a/core/api/request/pin.go b/core/api/request/pin.go deleted file mode 100644 index 3e7fd6d8..00000000 --- a/core/api/request/pin.go +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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)) -} diff --git a/core/api/request/search.go b/core/api/request/search.go deleted file mode 100644 index 78337642..00000000 --- a/core/api/request/search.go +++ /dev/null @@ -1,468 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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 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 -} diff --git a/core/api/request/setup.go b/core/api/request/setup.go deleted file mode 100644 index ff68b4c8..00000000 --- a/core/api/request/setup.go +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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 -} diff --git a/core/api/request/user.go b/core/api/request/user.go deleted file mode 100644 index fad469cf..00000000 --- a/core/api/request/user.go +++ /dev/null @@ -1,320 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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 -} diff --git a/core/api/request/user_test.go b/core/api/request/user_test.go deleted file mode 100644 index f7664a43..00000000 --- a/core/api/request/user_test.go +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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) - -} -*/ diff --git a/core/api/store/local.go b/core/api/store/local.go deleted file mode 100644 index df66d04a..00000000 --- a/core/api/store/local.go +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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 -} diff --git a/core/api/store/local_test.go b/core/api/store/local_test.go deleted file mode 100644 index 49494d66..00000000 --- a/core/api/store/local_test.go +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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) - } -} diff --git a/core/api/store/store.go b/core/api/store/store.go deleted file mode 100644 index 6164c767..00000000 --- a/core/api/store/store.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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 - */ -} diff --git a/core/api/store/store_test.go b/core/api/store/store_test.go deleted file mode 100644 index 2e206ff7..00000000 --- a/core/api/store/store_test.go +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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 := "

some html

" - - 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") - } -} diff --git a/core/api/util/writeHTTP.go b/core/api/util/writeHTTP.go deleted file mode 100644 index d5977653..00000000 --- a/core/api/util/writeHTTP.go +++ /dev/null @@ -1,188 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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) -} diff --git a/core/database/check.go b/core/database/check.go index fad6570c..3d84361c 100644 --- a/core/database/check.go +++ b/core/database/check.go @@ -20,7 +20,6 @@ import ( "github.com/documize/community/core/env" "github.com/documize/community/core/streamutil" "github.com/documize/community/server/web" - "github.com/jmoiron/sqlx" ) // sql variantsa @@ -30,14 +29,9 @@ const sqlVariantMariaDB string = "MariaDB" 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. // It must be the first function called in this package. func Check(runtime *env.Runtime) bool { - dbPtr = runtime.Db - runtime.Log.Info("Database checks: started") csBits := strings.Split(runtime.Flags.DBConn, "/") diff --git a/core/database/create.go b/core/database/create.go index 3ab22f6a..3ef068d8 100644 --- a/core/database/create.go +++ b/core/database/create.go @@ -18,63 +18,34 @@ import ( "strings" "time" - "github.com/documize/community/core/api" "github.com/documize/community/core/env" - "github.com/documize/community/core/log" "github.com/documize/community/core/secrets" "github.com/documize/community/core/stringutil" "github.com/documize/community/core/uniqueid" + "github.com/documize/community/domain" "github.com/documize/community/server/web" ) -// runSQL creates a transaction per call -func runSQL(sql string) (id uint64, err error) { - - if strings.TrimSpace(sql) == "" { - 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 +// Handler contains the runtime information such as logging and database. +type Handler struct { + Runtime *env.Runtime + Store *domain.Store } // 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() { target := "/setup" status := http.StatusBadRequest - if api.Runtime.Flags.SiteMode == env.SiteModeNormal { + if h.Runtime.Flags.SiteMode == env.SiteModeNormal { target = "/" status = http.StatusOK } req, err := http.NewRequest("GET", target, 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) @@ -82,20 +53,20 @@ func Create(w http.ResponseWriter, r *http.Request) { err := r.ParseForm() if err != nil { - log.Error("database.Create()'s r.ParseForm()", err) + h.Runtime.Log.Error("database.Create()'s r.ParseForm()", err) return } dbname := r.Form.Get("dbname") dbhash := r.Form.Get("dbhash") - fmt.Println(dbname) - fmt.Println(dbhash) - fmt.Println(web.SiteInfo.DBname) - fmt.Println(web.SiteInfo.DBhash) + h.Runtime.Log.Info(dbname) + h.Runtime.Log.Info(dbhash) + h.Runtime.Log.Info(web.SiteInfo.DBname) + h.Runtime.Log.Info(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 } @@ -118,23 +89,22 @@ func Create(w http.ResponseWriter, r *http.Request) { details.Password == "" || details.Firstname == "" || details.Lastname == "" { - log.Error("database.Create() error ", - errors.New("required field in database set-up form blank")) + h.Runtime.Log.Error("database.Create() error ", errors.New("required field in database set-up form blank")) return } - if err = Migrate(api.Runtime, false /* no tables exist yet */); err != nil { - log.Error("database.Create()", err) + if err = Migrate(h.Runtime, false /* no tables exist yet */); err != nil { + h.Runtime.Log.Error("database.Create()", err) return } - err = setupAccount(details, secrets.GenerateSalt()) + err = setupAccount(h.Runtime, details, secrets.GenerateSalt()) if err != nil { - log.Error("database.Create()", err) + h.Runtime.Log.Error("database.Create()", err) return } - api.Runtime.Flags.SiteMode = env.SiteModeNormal + h.Runtime.Flags.SiteMode = env.SiteModeNormal } // The result of completing the onboarding process. @@ -152,7 +122,7 @@ type onboardRequest struct { // setupAccount prepares the database for a newly onboard customer. // 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." salt := secrets.GenerateSalt() 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\")", orgID, completion.Company, completion.CompanyLong, completion.Message, completion.URL, completion.Email, serial) - _, err = runSQL(sql) + _, err = runSQL(rt, sql) if err != nil { - log.Error("Failed to insert into organization", err) + rt.Log.Error("Failed to insert into organization", err) 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)", 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 { - log.Error("Failed with error", err) + rt.Log.Error("Failed with error", err) return err } // Link user to organization. accountID := uniqueid.Generate() 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 { - log.Error("Failed with error", err) + rt.Log.Error("Failed with error", err) return err } // Set up default labels for main collection. labelID := uniqueid.Generate() 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 { - log.Error("insert into label failed", err) + rt.Log.Error("insert into label failed", err) } 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) - _, err = runSQL(sql) + _, err = runSQL(rt, sql) if err != nil { - log.Error("insert into labelrole failed", err) + rt.Log.Error("insert into labelrole failed", err) } 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 +} diff --git a/core/database/migrate.go b/core/database/migrate.go index 17abfc93..17b4dc73 100644 --- a/core/database/migrate.go +++ b/core/database/migrate.go @@ -69,7 +69,7 @@ func migrations(lastMigration string) (migrationsT, error) { } // 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 { runtime.Log.Info("Processing migration file: " + v) @@ -96,7 +96,7 @@ func (m migrationsT) migrate(runtime env.Runtime, tx *sqlx.Tx) error { return nil } -func lockDB(runtime env.Runtime) (bool, error) { +func lockDB(runtime *env.Runtime) (bool, error) { b := make([]byte, 2) _, err := rand.Read(b) 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 time.Sleep(wait) - tx, err := (*dbPtr).Beginx() + tx, err := runtime.Db.Beginx() if err != nil { return false, err } @@ -138,8 +138,8 @@ func lockDB(runtime env.Runtime) (bool, error) { return true, err // success! } -func unlockDB() error { - tx, err := (*dbPtr).Beginx() +func unlockDB(rt *env.Runtime) error { + tx, err := rt.Db.Beginx() if err != nil { return err } @@ -150,9 +150,9 @@ func unlockDB() error { 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 { - defer func() { unlockDB() }() + defer func() { unlockDB(runtime) }() if tx != nil { if err == nil { tx.Commit() @@ -188,7 +188,7 @@ func getLastMigration(tx *sqlx.Tx) (lastMigration string, err error) { } // 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 if ConfigTableExists { @@ -201,7 +201,7 @@ func Migrate(runtime env.Runtime, ConfigTableExists bool) error { amLeader = true // what else can you do? } - tx, err := (*dbPtr).Beginx() + tx, err := runtime.Db.Beginx() if err != nil { return migrateEnd(runtime, tx, err, amLeader) } @@ -236,8 +236,8 @@ func Migrate(runtime env.Runtime, ConfigTableExists bool) error { for targetMigration != lastMigration { time.Sleep(time.Second) runtime.Log.Info("Waiting for database migration completion") - tx.Rollback() // ignore error - tx, err := (*dbPtr).Beginx() // need this in order to see the changed situation since last tx + tx.Rollback() // ignore error + tx, err := runtime.Db.Beginx() // need this in order to see the changed situation since last tx if err != nil { return migrateEnd(runtime, tx, err, amLeader) } diff --git a/domain/auth/endpoint.go b/domain/auth/endpoint.go index b6fd496f..ce239831 100644 --- a/domain/auth/endpoint.go +++ b/domain/auth/endpoint.go @@ -117,7 +117,7 @@ func (h *Handler) Login(w http.ResponseWriter, r *http.Request) { func (h *Handler) ValidateToken(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 { + if err := provider.Callback(s, h.Runtime, h.Store, w, r); err != nil { h.Runtime.Log.Error("section validation failure", err) w.WriteHeader(http.StatusUnauthorized) } diff --git a/domain/conversion/conversion.go b/domain/conversion/conversion.go index 9850f91d..ef28f0d0 100644 --- a/domain/conversion/conversion.go +++ b/domain/conversion/conversion.go @@ -19,13 +19,13 @@ import ( "net/http" "strings" - "github.com/documize/community/core/api/store" api "github.com/documize/community/core/convapi" "github.com/documize/community/core/request" "github.com/documize/community/core/response" "github.com/documize/community/core/stringutil" "github.com/documize/community/core/uniqueid" "github.com/documize/community/domain" + "github.com/documize/community/domain/conversion/store" "github.com/documize/community/domain/document" "github.com/documize/community/model/activity" "github.com/documize/community/model/attachment" @@ -36,7 +36,7 @@ import ( "github.com/pkg/errors" ) -var storageProvider store.StorageProvider +var storageProvider StorageProvider func init() { storageProvider = new(store.LocalStorageProvider) @@ -91,8 +91,8 @@ func (h *Handler) convert(w http.ResponseWriter, r *http.Request, job, folderID method := "conversion.upload" ctx := domain.GetRequestContext(r) - licenseKey := h.Store.Setting.Get(ctx, "EDITION-LICENSE", "key") - licenseSignature := h.Store.Setting.Get(ctx, "EDITION-LICENSE", "signature") + licenseKey := h.Store.Setting.Get("EDITION-LICENSE", "key") + licenseSignature := h.Store.Setting.Get("EDITION-LICENSE", "signature") k, _ := hex.DecodeString(licenseKey) s, _ := hex.DecodeString(licenseSignature) diff --git a/domain/conversion/store/local.go b/domain/conversion/store/local.go index df66d04a..ae49f4b2 100644 --- a/domain/conversion/store/local.go +++ b/domain/conversion/store/local.go @@ -15,14 +15,12 @@ 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 @@ -33,8 +31,7 @@ func init() { 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)) + os.MkdirAll(folderPath, os.ModePerm) } // 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) 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 } @@ -85,18 +80,15 @@ func (store *LocalStorageProvider) Convert(params api.ConversionJobRequest) (fil } // remove temporary directory on exit - defer func() { log.IfErr(os.RemoveAll(inputFolder)) }() + defer func() { 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 } diff --git a/domain/conversion/store/local_test.go b/domain/conversion/store/local_test.go deleted file mode 100644 index 49494d66..00000000 --- a/domain/conversion/store/local_test.go +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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) - } -} diff --git a/core/api/mail/email.html b/domain/mail/email.html similarity index 100% rename from core/api/mail/email.html rename to domain/mail/email.html diff --git a/core/api/mail/invite-existing-user.html b/domain/mail/invite-existing-user.html similarity index 100% rename from core/api/mail/invite-existing-user.html rename to domain/mail/invite-existing-user.html diff --git a/core/api/mail/invite-new-user.html b/domain/mail/invite-new-user.html similarity index 100% rename from core/api/mail/invite-new-user.html rename to domain/mail/invite-new-user.html diff --git a/core/api/mail/mailer.go b/domain/mail/mailer.go similarity index 60% rename from core/api/mail/mailer.go rename to domain/mail/mailer.go index fd1c6c7b..395933b2 100644 --- a/core/api/mail/mailer.go +++ b/domain/mail/mailer.go @@ -19,19 +19,27 @@ import ( "html/template" "net/smtp" - "github.com/documize/community/core/api/request" - "github.com/documize/community/core/log" + "github.com/documize/community/core/env" + "github.com/documize/community/domain" "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. -func InviteNewUser(recipient, inviter, url, username, password string) { +func (m *Mailer) InviteNewUser(recipient, inviter, url, username, password string) { method := "InviteNewUser" + m.loadCredentials() file, err := web.ReadFile("mail/invite-new-user.html") - 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 } @@ -45,7 +53,7 @@ func InviteNewUser(recipient, inviter, url, username, password string) { subject := fmt.Sprintf("%s has invited you to Documize", inviter) e := NewEmail() - e.From = SMTPCreds.SMTPsender() + e.From = m.credentials.SMTPsender e.To = []string{recipient} e.Subject = subject @@ -65,24 +73,23 @@ func InviteNewUser(recipient, inviter, url, username, password string) { buffer := new(bytes.Buffer) t := template.Must(template.New("emailTemplate").Parse(emailTemplate)) - log.IfErr(t.Execute(buffer, ¶meters)) + t.Execute(buffer, ¶meters) e.HTML = buffer.Bytes() - err = e.Send(GetHost(), GetAuth()) - + err = e.Send(m.GetHost(), m.GetAuth()) 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. -func InviteExistingUser(recipient, inviter, url string) { +func (m *Mailer) InviteExistingUser(recipient, inviter, url string) { method := "InviteExistingUser" + m.loadCredentials() file, err := web.ReadFile("mail/invite-existing-user.html") - 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 } @@ -96,7 +103,7 @@ func InviteExistingUser(recipient, inviter, url string) { subject := fmt.Sprintf("%s has invited you to their Documize account", inviter) e := NewEmail() - e.From = SMTPCreds.SMTPsender() + e.From = m.credentials.SMTPsender e.To = []string{recipient} e.Subject = subject @@ -112,24 +119,23 @@ func InviteExistingUser(recipient, inviter, url string) { buffer := new(bytes.Buffer) t := template.Must(template.New("emailTemplate").Parse(emailTemplate)) - log.IfErr(t.Execute(buffer, ¶meters)) + t.Execute(buffer, ¶meters) e.HTML = buffer.Bytes() - err = e.Send(GetHost(), GetAuth()) - + err = e.Send(m.GetHost(), m.GetAuth()) 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. -func PasswordReset(recipient, url string) { +func (m *Mailer) PasswordReset(recipient, url string) { method := "PasswordReset" + m.loadCredentials() file, err := web.ReadFile("mail/password-reset.html") - 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 } @@ -138,7 +144,7 @@ func PasswordReset(recipient, url string) { subject := "Documize password reset request" e := NewEmail() - e.From = SMTPCreds.SMTPsender() //e.g. "Documize " + e.From = m.credentials.SMTPsender //e.g. "Documize " e.To = []string{recipient} e.Subject = subject @@ -152,24 +158,23 @@ func PasswordReset(recipient, url string) { buffer := new(bytes.Buffer) t := template.Must(template.New("emailTemplate").Parse(emailTemplate)) - log.IfErr(t.Execute(buffer, ¶meters)) + t.Execute(buffer, ¶meters) e.HTML = buffer.Bytes() - err = e.Send(GetHost(), GetAuth()) - + err = e.Send(m.GetHost(), m.GetAuth()) 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. -func ShareFolderExistingUser(recipient, inviter, url, folder, intro string) { +func (m *Mailer) ShareFolderExistingUser(recipient, inviter, url, folder, intro string) { method := "ShareFolderExistingUser" + m.loadCredentials() file, err := web.ReadFile("mail/share-folder-existing-user.html") - 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 } @@ -183,7 +188,7 @@ func ShareFolderExistingUser(recipient, inviter, url, folder, intro string) { subject := fmt.Sprintf("%s has shared %s with you", inviter, folder) e := NewEmail() - e.From = SMTPCreds.SMTPsender() + e.From = m.credentials.SMTPsender e.To = []string{recipient} e.Subject = subject @@ -203,24 +208,23 @@ func ShareFolderExistingUser(recipient, inviter, url, folder, intro string) { buffer := new(bytes.Buffer) t := template.Must(template.New("emailTemplate").Parse(emailTemplate)) - log.IfErr(t.Execute(buffer, ¶meters)) + t.Execute(buffer, ¶meters) e.HTML = buffer.Bytes() - err = e.Send(GetHost(), GetAuth()) - + err = e.Send(m.GetHost(), m.GetAuth()) 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. -func ShareFolderNewUser(recipient, inviter, url, folder, invitationMessage string) { +func (m *Mailer) ShareFolderNewUser(recipient, inviter, url, folder, invitationMessage string) { method := "ShareFolderNewUser" + m.loadCredentials() file, err := web.ReadFile("mail/share-folder-new-user.html") - 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 } @@ -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) e := NewEmail() - e.From = SMTPCreds.SMTPsender() + e.From = m.credentials.SMTPsender e.To = []string{recipient} e.Subject = subject @@ -254,41 +258,39 @@ func ShareFolderNewUser(recipient, inviter, url, folder, invitationMessage strin buffer := new(bytes.Buffer) t := template.Must(template.New("emailTemplate").Parse(emailTemplate)) - log.IfErr(t.Execute(buffer, ¶meters)) + t.Execute(buffer, ¶meters) e.HTML = buffer.Bytes() - err = e.Send(GetHost(), GetAuth()) - + err = e.Send(m.GetHost(), m.GetAuth()) 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. -var SMTPCreds = struct{ SMTPuserid, SMTPpassword, SMTPhost, SMTPport, SMTPsender func() string }{ - func() string { return request.ConfigString("SMTP", "userid") }, - func() string { return request.ConfigString("SMTP", "password") }, - func() string { return request.ConfigString("SMTP", "host") }, - func() string { - r := request.ConfigString("SMTP", "port") - if r == "" { - return "587" // default port number - } - return r - }, - func() string { return request.ConfigString("SMTP", "sender") }, +type credentials struct { + SMTPuserid string + SMTPpassword string + SMTPhost string + SMTPport string + SMTPsender string } -// GetAuth to return SMTP credentials -func GetAuth() smtp.Auth { - a := smtp.PlainAuth("", SMTPCreds.SMTPuserid(), SMTPCreds.SMTPpassword(), SMTPCreds.SMTPhost()) - //fmt.Printf("DEBUG GetAuth() = %#v\n", a) +// GetAuth to return SMTP authentication details +func (m *Mailer) GetAuth() smtp.Auth { + a := smtp.PlainAuth("", m.credentials.SMTPuserid, m.credentials.SMTPpassword, m.credentials.SMTPhost) return a } // GetHost to return SMTP host details -func GetHost() string { - h := SMTPCreds.SMTPhost() + ":" + SMTPCreds.SMTPport() - //fmt.Printf("DEBUG GetHost() = %#v\n", h) +func (m *Mailer) GetHost() string { + h := m.credentials.SMTPhost + ":" + m.credentials.SMTPport 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") +} diff --git a/core/api/mail/password-reset.html b/domain/mail/password-reset.html similarity index 100% rename from core/api/mail/password-reset.html rename to domain/mail/password-reset.html diff --git a/core/api/mail/share-folder-existing-user.html b/domain/mail/share-folder-existing-user.html similarity index 100% rename from core/api/mail/share-folder-existing-user.html rename to domain/mail/share-folder-existing-user.html diff --git a/core/api/mail/share-folder-new-user.html b/domain/mail/share-folder-new-user.html similarity index 100% rename from core/api/mail/share-folder-new-user.html rename to domain/mail/share-folder-new-user.html diff --git a/core/api/mail/smtp.go b/domain/mail/smtp.go similarity index 100% rename from core/api/mail/smtp.go rename to domain/mail/smtp.go diff --git a/domain/page/endpoint.go b/domain/page/endpoint.go index 6ada2917..8b60bafb 100644 --- a/domain/page/endpoint.go +++ b/domain/page/endpoint.go @@ -109,7 +109,7 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) { 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 { 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 } - 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 { h.Runtime.Log.Info("provider.Render could not find: " + model.Page.ContentType) } diff --git a/domain/section/airtable/airtable.go b/domain/section/airtable/airtable.go index 77f0f32e..5662da69 100644 --- a/domain/section/airtable/airtable.go +++ b/domain/section/airtable/airtable.go @@ -15,12 +15,14 @@ import ( "net/http" "github.com/documize/community/core/env" + "github.com/documize/community/domain" "github.com/documize/community/domain/section/provider" ) // Provider represents Airtable type Provider struct { - Runtime env.Runtime + Runtime *env.Runtime + Store *domain.Store } // Meta describes us diff --git a/domain/section/code/code.go b/domain/section/code/code.go index 048f7844..156f28dd 100644 --- a/domain/section/code/code.go +++ b/domain/section/code/code.go @@ -15,12 +15,14 @@ import ( "net/http" "github.com/documize/community/core/env" + "github.com/documize/community/domain" "github.com/documize/community/domain/section/provider" ) // Provider represents code snippet type Provider struct { - Runtime env.Runtime + Runtime *env.Runtime + Store *domain.Store } // Meta describes us. diff --git a/domain/section/endpoint.go b/domain/section/endpoint.go index fb41d49f..be4f8989 100644 --- a/domain/section/endpoint.go +++ b/domain/section/endpoint.go @@ -69,7 +69,7 @@ func (h *Handler) RunSectionCommand(w http.ResponseWriter, r *http.Request) { 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) response.WriteNotFoundError(w, "RunSectionCommand", sectionName) } @@ -122,7 +122,7 @@ func (h *Handler) RefreshSections(w http.ResponseWriter, r *http.Request) { return } - pcontext := provider.NewContext(pm.OrgID, pm.UserID) + pcontext := provider.NewContext(pm.OrgID, pm.UserID, ctx) // Ask for data refresh data, ok := provider.Refresh(page.ContentType, pcontext, pm.Config, pm.RawBody) diff --git a/domain/section/gemini/gemini.go b/domain/section/gemini/gemini.go index 1f2922a7..018f3e72 100644 --- a/domain/section/gemini/gemini.go +++ b/domain/section/gemini/gemini.go @@ -21,12 +21,14 @@ import ( "net/http" "github.com/documize/community/core/env" + "github.com/documize/community/domain" "github.com/documize/community/domain/section/provider" ) // Provider represents Gemini type Provider struct { - Runtime env.Runtime + Runtime *env.Runtime + Store *domain.Store } // 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. -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() method := query.Get("method") @@ -77,13 +79,13 @@ func (*Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http.R switch method { case "secrets": - secs(ctx, w, r) + secs(ctx, p.Store, w, r) case "auth": - auth(ctx, w, r) + auth(ctx, p.Store, w, r) case "workspace": - workspace(ctx, w, r) + workspace(ctx, p.Store, w, r) 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 } - c.Clean(ctx) + c.Clean(ctx, p.Store) if len(c.URL) == 0 { 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 } -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() body, err := ioutil.ReadAll(r.Body) @@ -170,7 +172,7 @@ func auth(ctx *provider.Context, w http.ResponseWriter, r *http.Request) { 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 { provider.WriteMessage(w, "gemini", "Missing URL value") @@ -205,7 +207,7 @@ func auth(ctx *provider.Context, w http.ResponseWriter, r *http.Request) { return } - config.SaveSecrets(ctx) + config.SaveSecrets(ctx, store) defer res.Body.Close() var g = geminiUser{} @@ -221,7 +223,7 @@ func auth(ctx *provider.Context, w http.ResponseWriter, r *http.Request) { 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() body, err := ioutil.ReadAll(r.Body) @@ -238,7 +240,7 @@ func workspace(ctx *provider.Context, w http.ResponseWriter, r *http.Request) { return } - config.Clean(ctx) + config.Clean(ctx, store) if len(config.URL) == 0 { 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) } -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() body, err := ioutil.ReadAll(r.Body) @@ -310,7 +312,7 @@ func items(ctx *provider.Context, w http.ResponseWriter, r *http.Request) { return } - config.Clean(ctx) + config.Clean(ctx, store) if len(config.URL) == 0 { 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) } -func secs(ctx *provider.Context, w http.ResponseWriter, r *http.Request) { - sec, _ := getSecrets(ctx) +func secs(ctx *provider.Context, store *domain.Store, w http.ResponseWriter, r *http.Request) { + sec, _ := getSecrets(ctx, store) provider.WriteJSON(w, sec) } diff --git a/domain/section/gemini/model.go b/domain/section/gemini/model.go index f7224c0d..2006593f 100644 --- a/domain/section/gemini/model.go +++ b/domain/section/gemini/model.go @@ -14,6 +14,7 @@ package gemini import ( "strings" + "github.com/documize/community/domain" "github.com/documize/community/domain/section/provider" ) @@ -86,9 +87,9 @@ type geminiConfig struct { 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 { - sec, err := getSecrets(ctx) + sec, err := getSecrets(ctx, store) if err == nil { if len(sec.APIKey) > 0 && len(sec.Username) > 0 && len(sec.URL) > 0 { c.APIKey = strings.TrimSpace(sec.APIKey) @@ -102,12 +103,12 @@ func (c *geminiConfig) Clean(ctx *provider.Context) { 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 sec.APIKey = strings.TrimSpace(c.APIKey) sec.Username = strings.TrimSpace(c.Username) sec.URL = strings.TrimSpace(c.URL) - ctx.MarshalSecrets(sec) + ctx.MarshalSecrets(sec, store) } type secrets struct { @@ -116,7 +117,7 @@ type secrets struct { APIKey string `json:"apikey"` } -func getSecrets(ctx *provider.Context) (sec secrets, err error) { - err = ctx.UnmarshalSecrets(&sec) +func getSecrets(ctx *provider.Context, store *domain.Store) (sec secrets, err error) { + err = ctx.UnmarshalSecrets(&sec, store) return } diff --git a/domain/section/github/auth.go b/domain/section/github/auth.go index 34ffc041..ae5eb889 100644 --- a/domain/section/github/auth.go +++ b/domain/section/github/auth.go @@ -17,32 +17,33 @@ import ( "net/url" "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" "golang.org/x/oauth2" ) -func clientID() string { - return request.ConfigString(meta.ConfigHandle(), "clientID") +func clientID(ctx domain.RequestContext, s *domain.Store) string { + return s.Setting.Get(meta.ConfigHandle(), "clientID") } -func clientSecret() string { - return request.ConfigString(meta.ConfigHandle(), "clientSecret") +func clientSecret(ctx domain.RequestContext, s *domain.Store) string { + 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" - 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 authClient := gogithub.NewClient((&gogithub.BasicAuthTransport{ - Username: clientID(), - Password: clientSecret(), + Username: clientID(ctx.Request, s), + Password: clientSecret(ctx.Request, s), }).Client()) - _, _, err := authClient.Authorizations.Check(clientID(), ptoken) + _, _, err := authClient.Authorizations.Check(clientID(ctx.Request, s), ptoken) 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 -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") state := req.URL.Query().Get("state") ghurl := "https://github.com/login/oauth/access_token" - vals := "client_id=" + clientID() - vals += "&client_secret=" + clientSecret() + vals := "client_id=" + clientID(ctx, s) + vals += "&client_secret=" + clientSecret(ctx, s) vals += "&code=" + code vals += "&state=" + state diff --git a/domain/section/github/github.go b/domain/section/github/github.go index e9c80860..380f1cbe 100644 --- a/domain/section/github/github.go +++ b/domain/section/github/github.go @@ -21,6 +21,7 @@ import ( "strings" "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" ) @@ -43,7 +44,8 @@ func init() { // Provider represents GitHub type Provider struct { - Runtime env.Runtime + Runtime *env.Runtime + Store *domain.Store } // Meta describes us. @@ -67,8 +69,8 @@ func (p *Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http CID string `json:"clientID"` URL string `json:"authorizationCallbackURL"` } - ret.CID = clientID() - ret.URL = authorizationCallbackURL() + ret.CID = clientID(ctx.Request, p.Store) + ret.URL = authorizationCallbackURL(ctx.Request, p.Store) provider.WriteJSON(w, ret) return } @@ -86,7 +88,7 @@ func (p *Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http if method == "saveSecret" { // secret Token update code // 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) provider.WriteError(w, "github", err) return @@ -108,7 +110,7 @@ func (p *Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http config.Clean() // 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) @@ -119,11 +121,11 @@ func (p *Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http if len(config.Token) == 0 { err = errors.New("empty github token") } else { - err = validateToken(config.Token) + err = validateToken(*ctx, p.Store, config.Token) } if err != nil { // 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) provider.WriteError(w, "github", err) return @@ -156,7 +158,7 @@ func (p *Provider) Refresh(ctx *provider.Context, configJSON, data string) strin } c.Clean() - c.Token = ctx.GetSecrets("token") + c.Token = ctx.GetSecrets("token", p.Store) client := p.githubClient(&c) @@ -193,7 +195,7 @@ func (p *Provider) Render(ctx *provider.Context, config, data string) string { } c.Clean() - c.Token = ctx.GetSecrets("token") + c.Token = ctx.GetSecrets("token", p.Store) data = strings.TrimSpace(data) if len(data) == 0 { diff --git a/domain/section/github/lists.go b/domain/section/github/lists.go index 8444064c..0c929905 100644 --- a/domain/section/github/lists.go +++ b/domain/section/github/lists.go @@ -20,7 +20,7 @@ import ( 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? case "owners": diff --git a/domain/section/markdown/markdown.go b/domain/section/markdown/markdown.go index 6c8b1fe6..bf7539da 100644 --- a/domain/section/markdown/markdown.go +++ b/domain/section/markdown/markdown.go @@ -16,12 +16,14 @@ import ( "github.com/documize/blackfriday" "github.com/documize/community/core/env" + "github.com/documize/community/domain" "github.com/documize/community/domain/section/provider" ) // Provider represents Markdown type Provider struct { - Runtime env.Runtime + Runtime *env.Runtime + Store *domain.Store } // Meta describes us diff --git a/domain/section/papertrail/papertrail.go b/domain/section/papertrail/papertrail.go index 32ba96cf..86c2c92e 100644 --- a/domain/section/papertrail/papertrail.go +++ b/domain/section/papertrail/papertrail.go @@ -22,6 +22,7 @@ import ( "net/url" "github.com/documize/community/core/env" + "github.com/documize/community/domain" "github.com/documize/community/domain/section/provider" ) @@ -29,7 +30,8 @@ const me = "papertrail" // Provider represents Papertrail type Provider struct { - Runtime env.Runtime + Runtime *env.Runtime + Store *domain.Store } // Meta describes us. @@ -45,7 +47,7 @@ func (*Provider) Meta() provider.TypeMeta { } // 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 events []papertrailEvent 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(config), &c) - c.APIToken = ctx.GetSecrets("APIToken") + c.APIToken = ctx.GetSecrets("APIToken", p.Store) max := len(search.Events) if c.Max < max { @@ -107,7 +109,7 @@ func (p *Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http config.Clean() if config.APIToken == provider.SecretReplacement || config.APIToken == "" { - config.APIToken = ctx.GetSecrets("APIToken") + config.APIToken = ctx.GetSecrets("APIToken", p.Store) } if len(config.APIToken) == 0 { @@ -117,7 +119,7 @@ func (p *Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http switch method { case "auth": - auth(p.Runtime, ctx, config, w, r) + auth(p.Runtime, p.Store, ctx, config, w, r) case "options": options(config, w, r) } @@ -135,7 +137,7 @@ func (p *Provider) Refresh(ctx *provider.Context, config, data string) (newData c.Clean() - c.APIToken = ctx.GetSecrets("APIToken") + c.APIToken = ctx.GetSecrets("APIToken", p.Store) if len(c.APIToken) == 0 { p.Runtime.Log.Error("missing API token", err) @@ -160,7 +162,7 @@ func (p *Provider) Refresh(ctx *provider.Context, config, data string) (newData 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) if result == nil { @@ -169,7 +171,7 @@ func auth(rt env.Runtime, ctx *provider.Context, config papertrailConfig, w http if err != nil { - ctx.SaveSecrets(`{"APIToken":""}`) // invalid token, so reset it + ctx.SaveSecrets(`{"APIToken":""}`, store) // invalid token, so reset it if err.Error() == "forbidden" { provider.WriteForbidden(w) @@ -180,7 +182,7 @@ func auth(rt env.Runtime, ctx *provider.Context, config papertrailConfig, w http return } - ctx.SaveSecrets(`{"APIToken":"` + config.APIToken + `"}`) + ctx.SaveSecrets(`{"APIToken":"`+config.APIToken+`"}`, store) provider.WriteJSON(w, result) } @@ -253,7 +255,7 @@ func options(config papertrailConfig, w http.ResponseWriter, r *http.Request) { 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 if len(config.Query) > 0 { filter = fmt.Sprintf("q=%s", url.QueryEscape(config.Query)) diff --git a/domain/section/provider/provider.go b/domain/section/provider/provider.go index f45faa96..76c70b17 100644 --- a/domain/section/provider/provider.go +++ b/domain/section/provider/provider.go @@ -19,8 +19,8 @@ import ( "sort" "strings" - "github.com/documize/community/core/api/request" - "github.com/documize/community/core/log" + "github.com/documize/community/core/env" + "github.com/documize/community/domain" ) // SecretReplacement is a constant used to replace secrets in data-structures when required. @@ -32,14 +32,14 @@ var sectionsMap = make(map[string]Provider) // TypeMeta details a "smart section" that represents a "page" in a document. type TypeMeta struct { - ID string `json:"id"` - Order int `json:"order"` - ContentType string `json:"contentType"` - PageType string `json:"pageType"` - Title string `json:"title"` - Description string `json:"description"` - Preview bool `json:"preview"` // coming soon! - Callback func(http.ResponseWriter, *http.Request) error `json:"-"` + ID string `json:"id"` + Order int `json:"order"` + ContentType string `json:"contentType"` + PageType string `json:"pageType"` + Title string `json:"title"` + Description string `json:"description"` + Preview bool `json:"preview"` // coming soon! + Callback func(*env.Runtime, *domain.Store, http.ResponseWriter, *http.Request) error `json:"-"` } // ConfigHandle returns the key name for database config table @@ -61,14 +61,12 @@ type Context struct { UserID string prov Provider inCommand bool + Request domain.RequestContext } // NewContext is a convenience function. -func NewContext(orgid, userid string) *Context { - if orgid == "" || userid == "" { - log.Error("NewContext incorrect orgid:"+orgid+" userid:"+userid, errors.New("bad section context")) - } - return &Context{OrgID: orgid, UserID: userid} +func NewContext(orgid, userid string, ctx domain.RequestContext) *Context { + return &Context{OrgID: orgid, UserID: userid, Request: ctx} } // 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. -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] if ok { if cb := s.Meta().Callback; cb != nil { - return cb(w, r) + return cb(rt, store, w, r) } } return errors.New("section not found") @@ -147,57 +145,47 @@ func WriteJSON(w http.ResponseWriter, v interface{}) { } _, err = w.Write(j) - log.IfErr(err) } // WriteString writes string tp HTTP response. func WriteString(w http.ResponseWriter, data string) { w.WriteHeader(http.StatusOK) - _, err := w.Write([]byte(data)) - log.IfErr(err) + w.Write([]byte(data)) } // WriteEmpty returns just OK to HTTP response. func WriteEmpty(w http.ResponseWriter) { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(http.StatusOK) - _, err := w.Write([]byte("{}")) - log.IfErr(err) + w.Write([]byte("{}")) } // WriteMarshalError write JSON marshalling error to HTTP response. 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) - log.Error("JSON marshall failed", err) + w.Write([]byte("{Error: 'JSON marshal failed'}")) } // WriteMessage write string to HTTP response. func WriteMessage(w http.ResponseWriter, section, msg string) { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(http.StatusBadRequest) - _, err := w.Write([]byte("{Message: " + msg + "}")) - log.IfErr(err) - log.Info(fmt.Sprintf("Error for section %s: %s", section, msg)) + w.Write([]byte("{Message: " + msg + "}")) } // WriteError write given error to HTTP response. func WriteError(w http.ResponseWriter, section 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("Error for section %s", section), err) + w.Write([]byte("{Error: 'Internal server error'}")) } // WriteForbidden write 403 to HTTP response. func WriteForbidden(w http.ResponseWriter) { w.Header().Set("Content-Type", "application/json; charset=utf-8") w.WriteHeader(http.StatusForbidden) - _, err := w.Write([]byte("{Error: 'Unauthorized'}")) - log.IfErr(err) + w.Write([]byte("{Error: 'Unauthorized'}")) } // 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"}`. // 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. -func (c *Context) SaveSecrets(JSONobj string) error { +func (c *Context) SaveSecrets(JSONobj string, s *domain.Store) error { if !c.inCommand { return errors.New("SaveSecrets() may only be called from within Command()") } 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. // 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 { 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 { return err } - return c.SaveSecrets(string(byts)) + return c.SaveSecrets(string(byts), s) } // 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 . // An empty JSONpath returns the whole JSON object, as JSON. // 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() - 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. @@ -242,8 +230,8 @@ var ErrNoSecrets = errors.New("no secrets in database") // UnmarshalSecrets from the database. // Parameter the same as for "v" in json.Unmarshal(). -func (c *Context) UnmarshalSecrets(v interface{}) error { - secTxt := c.GetSecrets("") // get all the json of the secrets +func (c *Context) UnmarshalSecrets(v interface{}, s *domain.Store) error { + secTxt := c.GetSecrets("", s) // get all the json of the secrets if len(secTxt) > 0 { return json.Unmarshal([]byte(secTxt), v) } diff --git a/domain/section/register.go b/domain/section/register.go index 5f679b5f..fb8e9ba4 100644 --- a/domain/section/register.go +++ b/domain/section/register.go @@ -15,6 +15,7 @@ import ( "fmt" "github.com/documize/community/core/env" + "github.com/documize/community/domain" "github.com/documize/community/domain/section/airtable" "github.com/documize/community/domain/section/code" "github.com/documize/community/domain/section/gemini" @@ -28,17 +29,17 @@ import ( ) // Register sections -func Register(rt env.Runtime) { - provider.Register("code", &code.Provider{Runtime: rt}) - provider.Register("gemini", &gemini.Provider{Runtime: rt}) - provider.Register("github", &github.Provider{Runtime: rt}) - provider.Register("markdown", &markdown.Provider{Runtime: rt}) - provider.Register("papertrail", &papertrail.Provider{Runtime: rt}) - provider.Register("table", &table.Provider{Runtime: rt}) - provider.Register("code", &code.Provider{Runtime: rt}) - provider.Register("trello", &trello.Provider{Runtime: rt}) - provider.Register("wysiwyg", &wysiwyg.Provider{Runtime: rt}) - provider.Register("airtable", &airtable.Provider{Runtime: rt}) +func Register(rt *env.Runtime, s *domain.Store) { + provider.Register("code", &code.Provider{Runtime: rt, Store: s}) + provider.Register("gemini", &gemini.Provider{Runtime: rt, Store: s}) + provider.Register("github", &github.Provider{Runtime: rt, Store: s}) + provider.Register("markdown", &markdown.Provider{Runtime: rt, Store: s}) + provider.Register("papertrail", &papertrail.Provider{Runtime: rt, Store: s}) + provider.Register("table", &table.Provider{Runtime: rt, Store: s}) + provider.Register("code", &code.Provider{Runtime: rt, Store: s}) + provider.Register("trello", &trello.Provider{Runtime: rt, Store: s}) + provider.Register("wysiwyg", &wysiwyg.Provider{Runtime: rt, Store: s}) + provider.Register("airtable", &airtable.Provider{Runtime: rt, Store: s}) p := provider.List() rt.Log.Info(fmt.Sprintf("Registered %d sections", len(p))) diff --git a/domain/section/section_test.go b/domain/section/section_test.go deleted file mode 100644 index 9e491a3b..00000000 --- a/domain/section/section_test.go +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2016 Documize Inc. . 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 . -// -// 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") - -} diff --git a/domain/section/table/table.go b/domain/section/table/table.go index 0350a225..b5901914 100644 --- a/domain/section/table/table.go +++ b/domain/section/table/table.go @@ -15,12 +15,14 @@ import ( "net/http" "github.com/documize/community/core/env" + "github.com/documize/community/domain" "github.com/documize/community/domain/section/provider" ) // Provider represents Table type Provider struct { - Runtime env.Runtime + Runtime *env.Runtime + Store *domain.Store } // Meta describes us diff --git a/domain/section/trello/trello.go b/domain/section/trello/trello.go index 02fa0c93..3dfde571 100644 --- a/domain/section/trello/trello.go +++ b/domain/section/trello/trello.go @@ -19,8 +19,8 @@ import ( "io/ioutil" "net/http" - "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" ) @@ -38,7 +38,8 @@ func init() { // Provider represents Trello type Provider struct { - Runtime env.Runtime + Runtime *env.Runtime + Store *domain.Store } // Meta describes us. @@ -68,7 +69,7 @@ func (p *Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http } config.Clean() - config.AppKey = request.ConfigString(meta.ConfigHandle(), "appKey") + config.AppKey = p.Store.Setting.Get(meta.ConfigHandle(), "appKey") if len(config.AppKey) == 0 { 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 { - 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" { @@ -94,7 +95,7 @@ func (p *Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http if err != nil { p.Runtime.Log.Error("failed to render cards", 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 } @@ -106,7 +107,7 @@ func (p *Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http if err != nil { p.Runtime.Log.Error("failed to render board", 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 } @@ -118,7 +119,7 @@ func (p *Provider) Command(ctx *provider.Context, w http.ResponseWriter, r *http if err != nil { p.Runtime.Log.Error("failed to get Trello lists", 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 } @@ -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) } - ctx.SaveSecrets(string(b)) + ctx.SaveSecrets(string(b), p.Store) } // Render just sends back HMTL as-is. diff --git a/domain/section/wysiwyg/wysiwyg.go b/domain/section/wysiwyg/wysiwyg.go index 6004d1d5..f10d31b1 100644 --- a/domain/section/wysiwyg/wysiwyg.go +++ b/domain/section/wysiwyg/wysiwyg.go @@ -15,12 +15,14 @@ import ( "net/http" "github.com/documize/community/core/env" + "github.com/documize/community/domain" "github.com/documize/community/domain/section/provider" ) // Provider represents WYSIWYG type Provider struct { - Runtime env.Runtime + Runtime *env.Runtime + Store *domain.Store } // Meta describes us diff --git a/domain/setting/endpoint.go b/domain/setting/endpoint.go index dc1b9180..a85cdead 100644 --- a/domain/setting/endpoint.go +++ b/domain/setting/endpoint.go @@ -41,7 +41,7 @@ func (h *Handler) SMTP(w http.ResponseWriter, r *http.Request) { return } - config := h.Store.Setting.Get(ctx, "SMTP", "") + config := h.Store.Setting.Get("SMTP", "") var y map[string]interface{} json.Unmarshal([]byte(config), &y) @@ -82,7 +82,7 @@ func (h *Handler) SetSMTP(w http.ResponseWriter, r *http.Request) { return } - h.Store.Setting.Set(ctx, "SMTP", config) + h.Store.Setting.Set("SMTP", config) h.Store.Audit.Record(ctx, audit.EventTypeSystemSMTP) @@ -98,7 +98,7 @@ func (h *Handler) License(w http.ResponseWriter, r *http.Request) { return } - config := h.Store.Setting.Get(ctx, "EDITION-LICENSE", "") + config := h.Store.Setting.Get("EDITION-LICENSE", "") if len(config) == 0 { config = "{}" } @@ -159,7 +159,7 @@ func (h *Handler) SetLicense(w http.ResponseWriter, r *http.Request) { js = string(j) } - h.Store.Setting.Set(ctx, "EDITION-LICENSE", js) + h.Store.Setting.Set("EDITION-LICENSE", js) event.Handler().Publish(string(event.TypeSystemLicenseChange)) diff --git a/domain/setting/mysql/setting.go b/domain/setting/mysql/setting.go index 2c9bb641..eda200a5 100644 --- a/domain/setting/mysql/setting.go +++ b/domain/setting/mysql/setting.go @@ -17,7 +17,6 @@ import ( "github.com/documize/community/core/env" "github.com/documize/community/core/streamutil" - "github.com/documize/community/domain" "github.com/pkg/errors" ) @@ -27,7 +26,7 @@ type Scope struct { } // 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 != "" { 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. -func (s Scope) Set(ctx domain.RequestContext, area, json string) error { +func (s Scope) Set(area, json string) error { if 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. // 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 != "" { 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. -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 == "" { return errors.New("no area") } diff --git a/domain/space/endpoint.go b/domain/space/endpoint.go index e700c87e..9aecd1ae 100644 --- a/domain/space/endpoint.go +++ b/domain/space/endpoint.go @@ -21,8 +21,6 @@ import ( "net/http" "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/request" "github.com/documize/community/core/response" @@ -31,6 +29,7 @@ import ( "github.com/documize/community/core/stringutil" "github.com/documize/community/core/uniqueid" "github.com/documize/community/domain" + "github.com/documize/community/domain/mail" "github.com/documize/community/model/account" "github.com/documize/community/model/audit" "github.com/documize/community/model/space" @@ -449,8 +448,11 @@ func (h *Handler) SetPermissions(w http.ResponseWriter, r *http.Request) { roleID := uniqueid.Generate() role.RefID = roleID err = h.Store.Space.AddRole(ctx, role) + if err != nil { + h.Runtime.Log.Error("add role", err) + } + roleCount++ - log.IfErr(err) // We send out folder invitation emails to those users // 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) 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)) } else { 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))) - 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)) } else { // On-board new user if strings.Contains(email, "@") { 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 { ctx.Transaction.Rollback() diff --git a/domain/space/space.go b/domain/space/space.go index f6748c01..99862286 100644 --- a/domain/space/space.go +++ b/domain/space/space.go @@ -14,10 +14,11 @@ package space import ( "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/uniqueid" "github.com/documize/community/domain" + "github.com/documize/community/domain/mail" "github.com/documize/community/model/account" "github.com/documize/community/model/space" "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 // through a welcome process designed to capture profile data. // 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) { var u = user.User{} @@ -97,8 +98,10 @@ func inviteNewUserToSharedSpace(ctx domain.RequestContext, s *domain.Store, emai return } + mailer := mail.Mailer{Runtime: rt, Store: s, Context: ctx} + 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 } diff --git a/domain/storer.go b/domain/storer.go index fd3ec2bc..b60bd9a9 100644 --- a/domain/storer.go +++ b/domain/storer.go @@ -144,10 +144,10 @@ type DocumentStorer interface { // SettingStorer defines required methods for persisting global and user level settings type SettingStorer interface { - Get(ctx RequestContext, area, path string) string - Set(ctx RequestContext, area, value string) error - GetUser(ctx RequestContext, orgID, userID, area, path string) string - SetUser(ctx RequestContext, orgID, userID, area, json string) error + Get(area, path string) string + Set(area, value string) error + GetUser(orgID, userID, area, path string) string + SetUser(orgID, userID, area, json string) error } // AttachmentStorer defines required methods for persisting document attachments diff --git a/domain/user/endpoint.go b/domain/user/endpoint.go index 847e2e67..64e45c4c 100644 --- a/domain/user/endpoint.go +++ b/domain/user/endpoint.go @@ -22,7 +22,6 @@ import ( "strconv" - "github.com/documize/community/core/api/mail" "github.com/documize/community/core/env" "github.com/documize/community/core/event" "github.com/documize/community/core/request" @@ -32,6 +31,7 @@ import ( "github.com/documize/community/core/stringutil" "github.com/documize/community/core/uniqueid" "github.com/documize/community/domain" + "github.com/documize/community/domain/mail" "github.com/documize/community/model/account" "github.com/documize/community/model/audit" "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)) 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)) } 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)) } @@ -580,7 +582,8 @@ func (h *Handler) ForgotPassword(w http.ResponseWriter, r *http.Request) { ctx.Transaction.Commit() 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) } diff --git a/edition/boot/runtime.go b/edition/boot/runtime.go index 0e44377b..71f4e31e 100644 --- a/edition/boot/runtime.go +++ b/edition/boot/runtime.go @@ -68,7 +68,7 @@ func InitRuntime(r *env.Runtime, s *domain.Store) bool { // go into setup mode if required if r.Flags.SiteMode != env.SiteModeOffline { if database.Check(r) { - if err := database.Migrate(*r, true /* the config table exists */); err != nil { + if err := database.Migrate(r, true /* the config table exists */); err != nil { r.Log.Error("unable to run database migration", err) return false } diff --git a/edition/community.go b/edition/community.go index e3583c9d..5cf06672 100644 --- a/edition/community.go +++ b/edition/community.go @@ -15,8 +15,6 @@ package main import ( "fmt" - "github.com/documize/community/core/api" - "github.com/documize/community/core/api/request" "github.com/documize/community/core/env" "github.com/documize/community/domain" "github.com/documize/community/domain/section" @@ -61,12 +59,8 @@ func main() { // runtime.Log = runtime.Log.SetDB(runtime.Db) } - // temp code repair - api.Runtime = rt - request.Db = rt.Db - // Register smart sections - section.Register(rt) + section.Register(&rt, &s) ready := make(chan struct{}, 1) // channel signals router ready server.Start(&rt, &s, ready) diff --git a/server/routing/routes.go b/server/routing/routes.go index a8f87df4..6dbb26f7 100644 --- a/server/routing/routes.go +++ b/server/routing/routes.go @@ -166,5 +166,7 @@ func RegisterEndpoints(rt *env.Runtime, s *domain.Store) { Add(rt, RoutePrefixRoot, "robots.txt", []string{"GET", "OPTIONS"}, nil, meta.RobotsTxt) Add(rt, RoutePrefixRoot, "sitemap.xml", []string{"GET", "OPTIONS"}, nil, meta.Sitemap) - Add(rt, RoutePrefixRoot, "{rest:.*}", nil, nil, web.EmberHandler) + + webHandler := web.Handler{Runtime: rt, Store: s} + Add(rt, RoutePrefixRoot, "{rest:.*}", nil, nil, webHandler.EmberHandler) } diff --git a/server/server.go b/server/server.go index 793350e9..09afb14d 100644 --- a/server/server.go +++ b/server/server.go @@ -18,7 +18,6 @@ import ( "strings" "github.com/codegangsta/negroni" - "github.com/documize/community/core/api/endpoint" "github.com/documize/community/core/api/plugins" "github.com/documize/community/core/database" "github.com/documize/community/core/env" @@ -32,7 +31,7 @@ var testHost string // used during automated testing // Start router to handle all HTTP traffic. func Start(rt *env.Runtime, s *domain.Store, ready chan struct{}) { - err := plugins.LibSetup() + err := plugins.Setup(s) if err != nil { rt.Log.Error("Terminating before running - invalid plugin.json", err) os.Exit(1) @@ -45,7 +44,8 @@ func Start(rt *env.Runtime, s *domain.Store, ready chan struct{}) { case env.SiteModeOffline: rt.Log.Info("Serving OFFLINE web server") case env.SiteModeSetup: - routing.Add(rt, routing.RoutePrefixPrivate, "setup", []string{"POST", "OPTIONS"}, nil, database.Create) + dbHandler := database.Handler{Runtime: rt, Store: s} + routing.Add(rt, routing.RoutePrefixPrivate, "setup", []string{"POST", "OPTIONS"}, nil, dbHandler.Create) rt.Log.Info("Serving SETUP web server") case env.SiteModeBadDB: rt.Log.Info("Serving BAD DATABASE web server") @@ -70,7 +70,7 @@ func Start(rt *env.Runtime, s *domain.Store, ready chan struct{}) { // "/api/..." router.PathPrefix(routing.RoutePrefixPrivate).Handler(negroni.New( - negroni.HandlerFunc(endpoint.Authorize), + negroni.HandlerFunc(cm.Authorize), negroni.Wrap(routing.BuildRoutes(rt, routing.RoutePrefixPrivate)), )) diff --git a/server/web/serve.go b/server/web/serve.go index 65322ba2..f2d08cbc 100644 --- a/server/web/serve.go +++ b/server/web/serve.go @@ -16,9 +16,9 @@ import ( "html/template" "net/http" - "github.com/documize/community/core/api" "github.com/documize/community/core/env" "github.com/documize/community/core/secrets" + "github.com/documize/community/domain" ) // SiteInfo describes set-up information about the site @@ -30,10 +30,16 @@ func init() { SiteInfo.DBhash = secrets.GenerateRandomPassword() // do this only once } +// Handler contains the runtime information such as logging and database. +type Handler struct { + Runtime *env.Runtime + Store *domain.Store +} + // EmberHandler serves HTML web pages -func EmberHandler(w http.ResponseWriter, r *http.Request) { +func (h *Handler) EmberHandler(w http.ResponseWriter, r *http.Request) { filename := "index.html" - switch api.Runtime.Flags.SiteMode { + switch h.Runtime.Flags.SiteMode { case env.SiteModeOffline: filename = "offline.html" case env.SiteModeSetup: