1
0
Fork 0
mirror of https://github.com/documize/community.git synced 2025-07-27 09:09:44 +02:00

Merge pull request #137 from documize/version-drafts-history-archiving

Document lifecycle and versioning
This commit is contained in:
Saul S 2018-03-19 16:20:40 +00:00 committed by GitHub
commit 7eb99c52f2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
58 changed files with 7393 additions and 1278 deletions

View file

@ -25,7 +25,7 @@ Anyone who wants a single place for any kind of document.
Anyone who wants to loop in external participants complete security. Anyone who wants to loop in external participants complete security.
Anyone who wishes documentation and knowledge capture worked like agile software development. Anyone who wishes documentation and knowledge capture worked like agile software development.
## What's different about Documize? ## What's different about Documize?
Sane organization through personal, team and public spaces. Sane organization through personal, team and public spaces.
@ -52,7 +52,7 @@ Space view.
## Latest version ## Latest version
Community edition: v1.58.0 Community edition: v1.59.0
## OS support ## OS support

View file

@ -170,7 +170,7 @@ func setupAccount(rt *env.Runtime, completion onboardRequest, serial string) (er
} }
// assign permissions to space // assign permissions to space
perms := []string{"view", "manage", "own", "doc-add", "doc-edit", "doc-delete", "doc-move", "doc-copy", "doc-template", "doc-approve"} perms := []string{"view", "manage", "own", "doc-add", "doc-edit", "doc-delete", "doc-move", "doc-copy", "doc-template", "doc-approve", "doc-version", "doc-lifecycle"}
for _, p := range perms { for _, p := range perms {
sql = fmt.Sprintf("insert into permission (orgid, who, whoid, action, scope, location, refid) values (\"%s\", 'user', \"%s\", \"%s\", 'object', 'space', \"%s\")", orgID, userID, p, labelID) sql = fmt.Sprintf("insert into permission (orgid, who, whoid, action, scope, location, refid) values (\"%s\", 'user', \"%s\", \"%s\", 'object', 'space', \"%s\")", orgID, userID, p, labelID)
_, err = runSQL(rt, sql) _, err = runSQL(rt, sql)

View file

@ -0,0 +1,27 @@
/* enterprise edition */
-- document lifecycle and versioning
ALTER TABLE document ADD COLUMN `lifecycle` INT NOT NULL DEFAULT 1 AFTER `approval`;
ALTER TABLE document ADD COLUMN `versioned` INT NOT NULL DEFAULT 0 AFTER `lifecycle`;
ALTER TABLE document ADD COLUMN `versionid` VARCHAR(100) DEFAULT '' NOT NULL AFTER `versioned`;
ALTER TABLE document ADD COLUMN `versionorder` INT NOT NULL DEFAULT 0 AFTER `versionid`;
ALTER TABLE document ADD COLUMN `groupid` CHAR(16) NOT NULL COLLATE utf8_bin AFTER `versionorder`;
-- grant doc-lifecycle permission
INSERT INTO permission(orgid, who, whoid, action, scope, location, refid, created)
SELECT orgid, who, whoid, 'doc-lifecycle' AS action, scope, location, refid, created
FROM permission
WHERE action = 'doc-edit' OR action = 'doc-approve';
-- grant doc-versions permission
INSERT INTO permission(orgid, who, whoid, action, scope, location, refid, created)
SELECT orgid, who, whoid, 'doc-version' AS action, scope, location, refid, created
FROM permission
WHERE action = 'doc-edit' OR action = 'doc-approve';
-- implement document section name search indexing
INSERT INTO search (orgid, documentid, itemid, itemtype, content)
SELECT orgid, documentid, refid as itemid, "page" as itemtype, title as content
FROM page WHERE status=0
-- deprecations

View file

@ -13,10 +13,12 @@ package mysql
import ( import (
"database/sql" "database/sql"
"fmt"
"time" "time"
"github.com/documize/community/core/env" "github.com/documize/community/core/env"
"github.com/documize/community/domain" "github.com/documize/community/domain"
"github.com/documize/community/domain/store/mysql"
"github.com/documize/community/model/activity" "github.com/documize/community/model/activity"
"github.com/pkg/errors" "github.com/pkg/errors"
) )
@ -69,3 +71,12 @@ func (s Scope) GetDocumentActivity(ctx domain.RequestContext, id string) (a []ac
return return
} }
// DeleteDocumentChangeActivity removes all entries for document changes (add, remove, update).
func (s Scope) DeleteDocumentChangeActivity(ctx domain.RequestContext, documentID string) (rows int64, err error) {
b := mysql.BaseQuery{}
rows, err = b.DeleteWhere(ctx.Transaction,
fmt.Sprintf("DELETE FROM useractivity WHERE orgid='%s' AND documentid='%s' AND (activitytype=1 OR activitytype=2 OR activitytype=3 OR activitytype=4 OR activitytype=7)", ctx.OrgID, documentID))
return
}

View file

@ -30,6 +30,7 @@ import (
indexer "github.com/documize/community/domain/search" indexer "github.com/documize/community/domain/search"
"github.com/documize/community/model/attachment" "github.com/documize/community/model/attachment"
"github.com/documize/community/model/audit" "github.com/documize/community/model/audit"
"github.com/documize/community/model/workflow"
uuid "github.com/nu7hatch/gouuid" uuid "github.com/nu7hatch/gouuid"
) )
@ -161,7 +162,12 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
a, _ := h.Store.Attachment.GetAttachments(ctx, documentID) a, _ := h.Store.Attachment.GetAttachments(ctx, documentID)
d, _ := h.Store.Document.Get(ctx, documentID) d, _ := h.Store.Document.Get(ctx, documentID)
go h.Indexer.IndexDocument(ctx, d, a)
if d.Lifecycle == workflow.LifecycleLive {
go h.Indexer.IndexDocument(ctx, d, a)
} else {
go h.Indexer.DeleteDocument(ctx, d.RefID)
}
response.WriteEmpty(w) response.WriteEmpty(w)
} }
@ -236,7 +242,12 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
all, _ := h.Store.Attachment.GetAttachments(ctx, documentID) all, _ := h.Store.Attachment.GetAttachments(ctx, documentID)
d, _ := h.Store.Document.Get(ctx, documentID) d, _ := h.Store.Document.Get(ctx, documentID)
go h.Indexer.IndexDocument(ctx, d, all)
if d.Lifecycle == workflow.LifecycleLive {
go h.Indexer.IndexDocument(ctx, d, all)
} else {
go h.Indexer.DeleteDocument(ctx, d.RefID)
}
response.WriteEmpty(w) response.WriteEmpty(w)
} }

175
domain/document/document.go Normal file
View file

@ -0,0 +1,175 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
package document
import (
"github.com/documize/community/core/uniqueid"
"github.com/documize/community/domain"
"github.com/documize/community/model/category"
"github.com/documize/community/model/doc"
"github.com/documize/community/model/page"
"github.com/documize/community/model/workflow"
"github.com/pkg/errors"
)
// FilterCategoryProtected removes documents that cannot be seen by user due to
// document cateogory viewing permissions.
func FilterCategoryProtected(docs []doc.Document, cats []category.Category, members []category.Member, viewDrafts bool) (filtered []doc.Document) {
filtered = []doc.Document{}
for _, doc := range docs {
hasCategory := false
canSeeCategory := false
skip := false
// drafts included if user can see them
if doc.Lifecycle == workflow.LifecycleDraft && !viewDrafts {
skip = true
}
// archived never included
if doc.Lifecycle == workflow.LifecycleArchived {
skip = true
}
OUTER:
for _, m := range members {
if m.DocumentID == doc.RefID {
hasCategory = true
for _, cat := range cats {
if cat.RefID == m.CategoryID {
canSeeCategory = true
continue OUTER
}
}
}
}
if !skip && (!hasCategory || canSeeCategory) {
filtered = append(filtered, doc)
}
}
return
}
// CopyDocument clones an existing document
func CopyDocument(ctx domain.RequestContext, s domain.Store, documentID string) (newDocumentID string, err error) {
doc, err := s.Document.Get(ctx, documentID)
if err != nil {
err = errors.Wrap(err, "unable to fetch existing document")
return
}
newDocumentID = uniqueid.Generate()
doc.RefID = newDocumentID
doc.ID = 0
doc.Versioned = false
doc.VersionID = ""
doc.GroupID = ""
doc.Template = false
// Duplicate pages and associated meta
pages, err := s.Page.GetPages(ctx, documentID)
if err != nil {
err = errors.Wrap(err, "unable to get existing pages")
return
}
var pageModel []page.NewPage
for _, p := range pages {
p.DocumentID = newDocumentID
p.ID = 0
meta, err2 := s.Page.GetPageMeta(ctx, p.RefID)
if err2 != nil {
err = errors.Wrap(err, "unable to get existing pages meta")
return
}
pageID := uniqueid.Generate()
p.RefID = pageID
meta.PageID = pageID
meta.DocumentID = newDocumentID
m := page.NewPage{}
m.Page = p
m.Meta = meta
pageModel = append(pageModel, m)
}
// Duplicate attachments
attachments, _ := s.Attachment.GetAttachments(ctx, documentID)
for i, a := range attachments {
a.DocumentID = newDocumentID
a.RefID = uniqueid.Generate()
a.ID = 0
attachments[i] = a
}
// Now create the template: document, attachments, pages and their meta
err = s.Document.Add(ctx, doc)
if err != nil {
err = errors.Wrap(err, "unable to add copied document")
return
}
for _, a := range attachments {
err = s.Attachment.Add(ctx, a)
if err != nil {
err = errors.Wrap(err, "unable to add copied attachment")
return
}
}
for _, m := range pageModel {
err = s.Page.Add(ctx, m)
if err != nil {
err = errors.Wrap(err, "unable to add copied page")
return
}
}
return
}
// FilterLastVersion returns the latest version of each document
// by removing all previous versions.
// If a document is not versioned, it is returned as-is.
func FilterLastVersion(docs []doc.Document) (filtered []doc.Document) {
filtered = []doc.Document{}
prev := make(map[string]bool)
for _, doc := range docs {
add := false
if doc.GroupID == "" {
add = true
} else {
if _, isExisting := prev[doc.GroupID]; !isExisting {
add = true
prev[doc.GroupID] = true
} else {
add = false
}
}
if add {
filtered = append(filtered, doc)
}
}
return
}

View file

@ -44,9 +44,10 @@ type Handler struct {
Indexer indexer.Indexer Indexer indexer.Indexer
} }
// Get is an endpoint that returns the document-level information for a given documentID. // Get is an endpoint that returns the document-level information for a
// given documentID.
func (h *Handler) Get(w http.ResponseWriter, r *http.Request) { func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
method := "document.get" method := "document.Get"
ctx := domain.GetRequestContext(r) ctx := domain.GetRequestContext(r)
id := request.Param(r, "documentID") id := request.Param(r, "documentID")
@ -78,15 +79,18 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
return return
} }
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{ // draft mode does not record document views
LabelID: document.LabelID, if document.Lifecycle == workflow.LifecycleLive {
DocumentID: document.RefID, err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
SourceType: activity.SourceTypeDocument, LabelID: document.LabelID,
ActivityType: activity.TypeRead}) DocumentID: document.RefID,
SourceType: activity.SourceTypeDocument,
ActivityType: activity.TypeRead})
if err != nil { if err != nil {
ctx.Transaction.Rollback() ctx.Transaction.Rollback()
h.Runtime.Log.Error(method, err) h.Runtime.Log.Error(method, err)
}
} }
ctx.Transaction.Commit() ctx.Transaction.Commit()
@ -98,7 +102,7 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
// DocumentLinks is an endpoint returning the links for a document. // DocumentLinks is an endpoint returning the links for a document.
func (h *Handler) DocumentLinks(w http.ResponseWriter, r *http.Request) { func (h *Handler) DocumentLinks(w http.ResponseWriter, r *http.Request) {
method := "document.links" method := "document.DocumentLinks"
ctx := domain.GetRequestContext(r) ctx := domain.GetRequestContext(r)
id := request.Param(r, "documentID") id := request.Param(r, "documentID")
@ -136,55 +140,37 @@ func (h *Handler) BySpace(w http.ResponseWriter, r *http.Request) {
return return
} }
// get complete list of documents // get user permissions
viewDrafts := permission.CanViewDrafts(ctx, *h.Store, spaceID)
// Get complete list of documents regardless of category permission
// and versioning.
documents, err := h.Store.Document.GetBySpace(ctx, spaceID) documents, err := h.Store.Document.GetBySpace(ctx, spaceID)
if err != nil { if err != nil {
response.WriteServerError(w, method, err) response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err) h.Runtime.Log.Error(method, err)
return return
} }
if len(documents) == 0 {
documents = []doc.Document{}
}
// Sort by title.
sort.Sort(doc.ByTitle(documents)) sort.Sort(doc.ByTitle(documents))
// remove documents that cannot be seen due to lack of // Remove documents that cannot be seen due to lack of
// category view/access permission // category view/access permission.
filtered := []doc.Document{}
cats, err := h.Store.Category.GetBySpace(ctx, spaceID) cats, err := h.Store.Category.GetBySpace(ctx, spaceID)
members, err := h.Store.Category.GetSpaceCategoryMembership(ctx, spaceID) members, err := h.Store.Category.GetSpaceCategoryMembership(ctx, spaceID)
filtered := FilterCategoryProtected(documents, cats, members, viewDrafts)
for _, doc := range documents { // Keep the latest version when faced with multiple versions.
hasCategory := false filtered = FilterLastVersion(filtered)
canSeeCategory := false
OUTER:
for _, m := range members {
if m.DocumentID == doc.RefID {
hasCategory = true
for _, cat := range cats {
if cat.RefID == m.CategoryID {
canSeeCategory = true
continue OUTER
}
}
}
}
if !hasCategory || canSeeCategory {
filtered = append(filtered, doc)
}
}
response.WriteJSON(w, filtered) response.WriteJSON(w, filtered)
} }
// Update updates an existing document using the // Update updates an existing document using the format described
// format described in NewDocumentModel() encoded as JSON in the request. // in NewDocumentModel() encoded as JSON in the request.
func (h *Handler) Update(w http.ResponseWriter, r *http.Request) { func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
method := "document.space" method := "document.Update"
ctx := domain.GetRequestContext(r) ctx := domain.GetRequestContext(r)
documentID := request.Param(r, "documentID") documentID := request.Param(r, "documentID")
@ -223,7 +209,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
return return
} }
// if space changed for document, remove document categories // If space changed for document, remove document categories.
oldDoc, err := h.Store.Document.Get(ctx, documentID) oldDoc, err := h.Store.Document.Get(ctx, documentID)
if err != nil { if err != nil {
ctx.Transaction.Rollback() ctx.Transaction.Rollback()
@ -244,19 +230,60 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
return return
} }
// If document part of versioned document group
// then document name must be applied to all documents
// in the group.
if len(d.GroupID) > 0 {
err = h.Store.Document.UpdateGroup(ctx, d)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
}
// Record document being marked as archived.
if d.Lifecycle != oldDoc.Lifecycle && d.Lifecycle == workflow.LifecycleArchived {
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
LabelID: d.LabelID,
DocumentID: documentID,
SourceType: activity.SourceTypeDocument,
ActivityType: activity.TypeArchived})
}
// Record document being marked as draft.
if d.Lifecycle != oldDoc.Lifecycle && d.Lifecycle == workflow.LifecycleDraft {
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
LabelID: d.LabelID,
DocumentID: documentID,
SourceType: activity.SourceTypeDocument,
ActivityType: activity.TypeDraft})
}
ctx.Transaction.Commit() ctx.Transaction.Commit()
h.Store.Audit.Record(ctx, audit.EventTypeDocumentUpdate) h.Store.Audit.Record(ctx, audit.EventTypeDocumentUpdate)
a, _ := h.Store.Attachment.GetAttachments(ctx, documentID) // Live document indexed for search.
go h.Indexer.IndexDocument(ctx, d, a) if d.Lifecycle == workflow.LifecycleLive {
a, _ := h.Store.Attachment.GetAttachments(ctx, documentID)
go h.Indexer.IndexDocument(ctx, d, a)
pages, _ := h.Store.Page.GetPages(ctx, d.RefID)
for i := range pages {
go h.Indexer.IndexContent(ctx, pages[i])
}
} else {
go h.Indexer.DeleteDocument(ctx, d.RefID)
}
response.WriteEmpty(w) response.WriteEmpty(w)
} }
// Delete is an endpoint that deletes a document specified by documentID. // Delete is an endpoint that deletes a document specified by documentID.
func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) { func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
method := "document.delete" method := "document.Delete"
ctx := domain.GetRequestContext(r) ctx := domain.GetRequestContext(r)
documentID := request.Param(r, "documentID") documentID := request.Param(r, "documentID")
@ -327,11 +354,14 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
h.Store.Link.MarkOrphanDocumentLink(ctx, documentID) h.Store.Link.MarkOrphanDocumentLink(ctx, documentID)
h.Store.Link.DeleteSourceDocumentLinks(ctx, documentID) h.Store.Link.DeleteSourceDocumentLinks(ctx, documentID)
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{ // Draft actions are not logged
LabelID: doc.LabelID, if doc.Lifecycle == workflow.LifecycleLive {
DocumentID: documentID, h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
SourceType: activity.SourceTypeDocument, LabelID: doc.LabelID,
ActivityType: activity.TypeDeleted}) DocumentID: documentID,
SourceType: activity.SourceTypeDocument,
ActivityType: activity.TypeDeleted})
}
ctx.Transaction.Commit() ctx.Transaction.Commit()
@ -342,9 +372,10 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
response.WriteEmpty(w) response.WriteEmpty(w)
} }
// SearchDocuments endpoint takes a list of keywords and returns a list of document references matching those keywords. // SearchDocuments endpoint takes a list of keywords and returns a list of
// document references matching those keywords.
func (h *Handler) SearchDocuments(w http.ResponseWriter, r *http.Request) { func (h *Handler) SearchDocuments(w http.ResponseWriter, r *http.Request) {
method := "document.search" method := "document.SearchDocuments"
ctx := domain.GetRequestContext(r) ctx := domain.GetRequestContext(r)
defer streamutil.Close(r.Body) defer streamutil.Close(r.Body)
@ -412,6 +443,12 @@ func (h *Handler) FetchDocumentData(w http.ResponseWriter, r *http.Request) {
return return
} }
// Don't serve archived document
if document.Lifecycle == workflow.LifecycleArchived {
response.WriteForbiddenError(w)
return
}
// permissions // permissions
perms, err := h.Store.Permission.GetUserSpacePermissions(ctx, document.LabelID) perms, err := h.Store.Permission.GetUserSpacePermissions(ctx, document.LabelID)
if err != nil && err != sql.ErrNoRows { if err != nil && err != sql.ErrNoRows {
@ -455,12 +492,22 @@ func (h *Handler) FetchDocumentData(w http.ResponseWriter, r *http.Request) {
sp = []space.Space{} sp = []space.Space{}
} }
// Get version information for this document.
v, err := h.Store.Document.GetVersions(ctx, document.GroupID)
if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
// Prepare response.
data := BulkDocumentData{} data := BulkDocumentData{}
data.Document = document data.Document = document
data.Permissions = record data.Permissions = record
data.Roles = rolesRecord data.Roles = rolesRecord
data.Links = l data.Links = l
data.Spaces = sp data.Spaces = sp
data.Versions = v
ctx.Transaction, err = h.Runtime.Db.Beginx() ctx.Transaction, err = h.Runtime.Db.Beginx()
if err != nil { if err != nil {
@ -469,15 +516,17 @@ func (h *Handler) FetchDocumentData(w http.ResponseWriter, r *http.Request) {
return return
} }
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{ if document.Lifecycle == workflow.LifecycleLive {
LabelID: document.LabelID, err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
DocumentID: document.RefID, LabelID: document.LabelID,
SourceType: activity.SourceTypeDocument, DocumentID: document.RefID,
ActivityType: activity.TypeRead}) SourceType: activity.SourceTypeDocument,
ActivityType: activity.TypeRead})
if err != nil { if err != nil {
ctx.Transaction.Rollback() ctx.Transaction.Rollback()
h.Runtime.Log.Error(method, err) h.Runtime.Log.Error(method, err)
}
} }
ctx.Transaction.Commit() ctx.Transaction.Commit()
@ -495,4 +544,5 @@ type BulkDocumentData struct {
Roles pm.DocumentRecord `json:"roles"` Roles pm.DocumentRecord `json:"roles"`
Spaces []space.Space `json:"folders"` Spaces []space.Space `json:"folders"`
Links []link.Link `json:"links"` Links []link.Link `json:"links"`
Versions []doc.Version `json:"versions"`
} }

View file

@ -29,13 +29,16 @@ type Scope struct {
} }
// Add inserts the given document record into the document table and audits that it has been done. // Add inserts the given document record into the document table and audits that it has been done.
func (s Scope) Add(ctx domain.RequestContext, document doc.Document) (err error) { func (s Scope) Add(ctx domain.RequestContext, d doc.Document) (err error) {
document.OrgID = ctx.OrgID d.OrgID = ctx.OrgID
document.Created = time.Now().UTC() d.Created = time.Now().UTC()
document.Revised = document.Created // put same time in both fields d.Revised = d.Created // put same time in both fields
_, err = ctx.Transaction.Exec("INSERT INTO document (refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, protection, approval, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", _, err = ctx.Transaction.Exec(`
document.RefID, document.OrgID, document.LabelID, document.UserID, document.Job, document.Location, document.Title, document.Excerpt, document.Slug, document.Tags, document.Template, document.Protection, document.Approval, document.Created, document.Revised) INSERT INTO document (refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, protection, approval, lifecycle, versioned, versionid, versionorder, groupid, created, revised)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
d.RefID, d.OrgID, d.LabelID, d.UserID, d.Job, d.Location, d.Title, d.Excerpt, d.Slug, d.Tags,
d.Template, d.Protection, d.Approval, d.Lifecycle, d.Versioned, d.VersionID, d.VersionOrder, d.GroupID, d.Created, d.Revised)
if err != nil { if err != nil {
err = errors.Wrap(err, "execuet insert document") err = errors.Wrap(err, "execuet insert document")
@ -46,7 +49,11 @@ func (s Scope) Add(ctx domain.RequestContext, document doc.Document) (err error)
// Get fetches the document record with the given id fromt the document table and audits that it has been got. // Get fetches the document record with the given id fromt the document table and audits that it has been got.
func (s Scope) Get(ctx domain.RequestContext, id string) (document doc.Document, err error) { func (s Scope) Get(ctx domain.RequestContext, id string) (document doc.Document, err error) {
err = s.Runtime.Db.Get(&document, "SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, protection, approval, created, revised FROM document WHERE orgid=? and refid=?", err = s.Runtime.Db.Get(&document, `
SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template,
protection, approval, lifecycle, versioned, versionid, versionorder, groupid, created, revised
FROM document
WHERE orgid=? and refid=?`,
ctx.OrgID, id) ctx.OrgID, id)
if err != nil { if err != nil {
@ -90,37 +97,32 @@ func (s Scope) DocumentMeta(ctx domain.RequestContext, id string) (meta doc.Docu
return return
} }
// GetAll returns a slice containg all of the the documents for the client's organisation.
func (s Scope) GetAll() (ctx domain.RequestContext, documents []doc.Document, err error) {
err = s.Runtime.Db.Select(&documents, "SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, protection, approval, created, revised FROM document WHERE orgid=? AND template=0 ORDER BY title", ctx.OrgID)
if err != nil {
err = errors.Wrap(err, "select documents")
}
return
}
// GetBySpace returns a slice containing the documents for a given space. // GetBySpace returns a slice containing the documents for a given space.
// No attempt is made to hide documents that are protected //
// by category permissions -- caller must filter as required. // No attempt is made to hide documents that are protected by category
// permissions hence caller must filter as required.
//
// All versions of a document are returned, hence caller must
// decide what to do with them.
func (s Scope) GetBySpace(ctx domain.RequestContext, spaceID string) (documents []doc.Document, err error) { func (s Scope) GetBySpace(ctx domain.RequestContext, spaceID string) (documents []doc.Document, err error) {
err = s.Runtime.Db.Select(&documents, ` err = s.Runtime.Db.Select(&documents, `
SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, protection, approval, created, revised SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template,
protection, approval, lifecycle, versioned, versionid, versionorder, groupid, created, revised
FROM document FROM document
WHERE orgid=? AND template=0 AND labelid IN ( WHERE orgid=? AND template=0 AND labelid IN (
SELECT refid FROM label WHERE orgid=? AND refid IN SELECT refid FROM label WHERE orgid=? AND refid IN
(SELECT refid FROM permission WHERE orgid=? AND location='space' AND refid=? AND refid IN ( (SELECT refid FROM permission WHERE orgid=? AND location='space' AND refid=? AND refid IN (
SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') AND location='space' AND action='view' SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') AND location='space' AND action='view'
UNION ALL UNION ALL
SELECT p.refid from permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=? SELECT p.refid from permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=?
AND p.who='role' AND p.location='space' AND p.refid=? AND p.action='view' AND (r.userid=? OR r.userid='0') AND p.who='role' AND p.location='space' AND p.refid=? AND p.action='view' AND (r.userid=? OR r.userid='0')
)) ))
) )
ORDER BY title`, ctx.OrgID, ctx.OrgID, ctx.OrgID, spaceID, ctx.OrgID, ctx.UserID, ctx.OrgID, spaceID, ctx.UserID) ORDER BY title, versionorder`, ctx.OrgID, ctx.OrgID, ctx.OrgID, spaceID, ctx.OrgID, ctx.UserID, ctx.OrgID, spaceID, ctx.UserID)
if err == sql.ErrNoRows { if err == sql.ErrNoRows || len(documents) == 0 {
err = nil err = nil
documents = []doc.Document{}
} }
if err != nil { if err != nil {
err = errors.Wrap(err, "select documents by space") err = errors.Wrap(err, "select documents by space")
@ -129,37 +131,18 @@ func (s Scope) GetBySpace(ctx domain.RequestContext, spaceID string) (documents
return return
} }
// Templates returns a slice containing the documents available as templates to the client's organisation, in title order.
func (s Scope) Templates(ctx domain.RequestContext) (documents []doc.Document, err error) {
err = s.Runtime.Db.Select(&documents,
`SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, protection, approval, created, revised FROM document WHERE orgid=? AND template=1
AND labelid IN
(
SELECT refid FROM label WHERE orgid=?
AND refid IN (SELECT refid FROM permission WHERE orgid=? AND location='space' AND refid IN (
SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') AND location='space' AND action='view'
UNION ALL
SELECT p.refid from permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=? AND p.who='role' AND p.location='space' AND p.action='view' AND (r.userid=? OR r.userid='0')
))
)
ORDER BY title`, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
if err != nil {
err = errors.Wrap(err, "select document templates")
}
return
}
// TemplatesBySpace returns a slice containing the documents available as templates for given space. // TemplatesBySpace returns a slice containing the documents available as templates for given space.
func (s Scope) TemplatesBySpace(ctx domain.RequestContext, spaceID string) (documents []doc.Document, err error) { func (s Scope) TemplatesBySpace(ctx domain.RequestContext, spaceID string) (documents []doc.Document, err error) {
err = s.Runtime.Db.Select(&documents, err = s.Runtime.Db.Select(&documents,
`SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, protection, approval, created, revised FROM document WHERE orgid=? AND labelid=? AND template=1 `SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template,
protection, approval, lifecycle, versioned, versionid, versionorder, groupid, created, revised
FROM document
WHERE orgid=? AND labelid=? AND template=1 ANd lifecycle=1
AND labelid IN AND labelid IN
( (
SELECT refid FROM label WHERE orgid=? SELECT refid FROM label WHERE orgid=?
AND refid IN (SELECT refid FROM permission WHERE orgid=? AND location='space' AND refid IN ( AND refid IN (SELECT refid FROM permission WHERE orgid=? AND location='space' AND refid IN (
SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') AND location='space' AND action='view' SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') AND location='space' AND action='view'
UNION ALL UNION ALL
SELECT p.refid from permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=? AND p.who='role' AND p.location='space' AND p.action='view' AND (r.userid=? OR r.userid='0') SELECT p.refid from permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=? AND p.who='role' AND p.location='space' AND p.action='view' AND (r.userid=? OR r.userid='0')
)) ))
@ -170,7 +153,6 @@ func (s Scope) TemplatesBySpace(ctx domain.RequestContext, spaceID string) (docu
err = nil err = nil
documents = []doc.Document{} documents = []doc.Document{}
} }
if err != nil { if err != nil {
err = errors.Wrap(err, "select space document templates") err = errors.Wrap(err, "select space document templates")
} }
@ -178,44 +160,24 @@ func (s Scope) TemplatesBySpace(ctx domain.RequestContext, spaceID string) (docu
return return
} }
// PublicDocuments returns a slice of SitemapDocument records, holding documents in folders of type 1 (entity.TemplateTypePublic). // PublicDocuments returns a slice of SitemapDocument records
// linking to documents in public spaces.
// These documents can then be seen by search crawlers.
func (s Scope) PublicDocuments(ctx domain.RequestContext, orgID string) (documents []doc.SitemapDocument, err error) { func (s Scope) PublicDocuments(ctx domain.RequestContext, orgID string) (documents []doc.SitemapDocument, err error) {
err = s.Runtime.Db.Select(&documents, err = s.Runtime.Db.Select(&documents,
`SELECT d.refid as documentid, d.title as document, d.revised as revised, l.refid as folderid, l.label as folder `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 FROM document d LEFT JOIN label l ON l.refid=d.labelid
WHERE d.orgid=? WHERE d.orgid=?
AND l.type=1 AND l.type=1
AND d.template=0`, orgID) AND d.lifecycle=1
AND d.template=0`, orgID)
if err != nil {
err = errors.Wrap(err, fmt.Sprintf("execute GetPublicDocuments for org %s%s", orgID))
}
return
}
// DocumentList returns a slice containing the documents available as templates to the client's organisation, in title order.
func (s Scope) DocumentList(ctx domain.RequestContext) (documents []doc.Document, err error) {
err = s.Runtime.Db.Select(&documents,
`SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template, protection, approval, created, revised FROM document WHERE orgid=? AND template=0
AND labelid IN
(
SELECT refid FROM label WHERE orgid=?
AND refid IN (SELECT refid FROM permission WHERE orgid=? AND location='space' AND refid IN (
SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') AND location='space' AND action='view'
UNION ALL
SELECT p.refid from permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=? AND p.who='role' AND p.location='space' AND p.action='view' AND (r.userid=? OR r.userid='0')
))
)
ORDER BY title`, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.OrgID, ctx.UserID, ctx.OrgID, ctx.UserID)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
err = nil err = nil
documents = []doc.Document{} documents = []doc.SitemapDocument{}
} }
if err != nil { if err != nil {
err = errors.Wrap(err, "select documents list") err = errors.Wrap(err, fmt.Sprintf("execute GetPublicDocuments for org %s%s", orgID))
} }
return return
@ -225,11 +187,32 @@ func (s Scope) DocumentList(ctx domain.RequestContext) (documents []doc.Document
func (s Scope) Update(ctx domain.RequestContext, document doc.Document) (err error) { func (s Scope) Update(ctx domain.RequestContext, document doc.Document) (err error) {
document.Revised = time.Now().UTC() document.Revised = time.Now().UTC()
_, err = ctx.Transaction.NamedExec("UPDATE document SET labelid=:labelid, userid=:userid, job=:job, location=:location, title=:title, excerpt=:excerpt, slug=:slug, tags=:tags, template=:template, protection=:protection, approval=:approval, revised=:revised WHERE orgid=:orgid AND refid=:refid", _, err = ctx.Transaction.NamedExec(`
UPDATE document
SET
labelid=:labelid, userid=:userid, job=:job, location=:location, title=:title, excerpt=:excerpt, slug=:slug, tags=:tags, template=:template,
protection=:protection, approval=:approval, lifecycle=:lifecycle, versioned=:versioned, versionid=:versionid, versionorder=:versionorder, groupid=:groupid, revised=:revised
WHERE orgid=:orgid AND refid=:refid`,
&document) &document)
if err != nil { if err != nil {
err = errors.Wrap(err, "execute update document") err = errors.Wrap(err, "document.store.Update")
}
return
}
// UpdateGroup applies same values to all documents
// with the same group ID.
func (s Scope) UpdateGroup(ctx domain.RequestContext, d doc.Document) (err error) {
_, err = ctx.Transaction.Exec(`UPDATE document SET title=?, excerpt=? WHERE orgid=? AND groupid=?`,
d.Title, d.Excerpt, ctx.OrgID, d.GroupID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, "document.store.UpdateTitle")
} }
return return
@ -311,3 +294,28 @@ func (s Scope) DeleteBySpace(ctx domain.RequestContext, spaceID string) (rows in
return b.DeleteConstrained(ctx.Transaction, "document", ctx.OrgID, spaceID) return b.DeleteConstrained(ctx.Transaction, "document", ctx.OrgID, spaceID)
} }
// GetVersions returns a slice containing the documents for a given space.
//
// No attempt is made to hide documents that are protected by category
// permissions hence caller must filter as required.
//
// All versions of a document are returned, hence caller must
// decide what to do with them.
func (s Scope) GetVersions(ctx domain.RequestContext, groupID string) (v []doc.Version, err error) {
err = s.Runtime.Db.Select(&v, `
SELECT versionid, refid as documentid
FROM document
WHERE orgid=? AND groupid=?
ORDER BY versionorder`, ctx.OrgID, groupID)
if err == sql.ErrNoRows || len(v) == 0 {
err = nil
v = []doc.Version{}
}
if err != nil {
err = errors.Wrap(err, "document.store.GetVersions")
}
return
}

View file

@ -164,19 +164,27 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
h.Store.Block.IncrementUsage(ctx, model.Page.BlockID) h.Store.Block.IncrementUsage(ctx, model.Page.BlockID)
} }
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{ // Draft actions are not logged
LabelID: doc.LabelID, if doc.Lifecycle == workflow.LifecycleLive {
DocumentID: model.Page.DocumentID, h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
PageID: model.Page.RefID, LabelID: doc.LabelID,
SourceType: activity.SourceTypePage, DocumentID: model.Page.DocumentID,
ActivityType: activity.TypeCreated}) PageID: model.Page.RefID,
SourceType: activity.SourceTypePage,
ActivityType: activity.TypeCreated})
}
ctx.Transaction.Commit() ctx.Transaction.Commit()
h.Store.Audit.Record(ctx, audit.EventTypeSectionAdd) h.Store.Audit.Record(ctx, audit.EventTypeSectionAdd)
np, _ := h.Store.Page.Get(ctx, pageID) np, _ := h.Store.Page.Get(ctx, pageID)
go h.Indexer.IndexContent(ctx, np)
if doc.Lifecycle == workflow.LifecycleLive {
go h.Indexer.IndexContent(ctx, np)
} else {
go h.Indexer.DeleteDocument(ctx, doc.RefID)
}
response.WriteJSON(w, np) response.WriteJSON(w, np)
} }
@ -309,6 +317,7 @@ func (h *Handler) GetMeta(w http.ResponseWriter, r *http.Request) {
// Update will persist changed page and note the fact // Update will persist changed page and note the fact
// that this is a new revision. If the page is the first in a document // that this is a new revision. If the page is the first in a document
// then the corresponding document title will also be changed. // then the corresponding document title will also be changed.
// Draft documents do not get revision entry.
func (h *Handler) Update(w http.ResponseWriter, r *http.Request) { func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
method := "page.update" method := "page.update"
ctx := domain.GetRequestContext(r) ctx := domain.GetRequestContext(r)
@ -406,6 +415,11 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
skipRevision = true skipRevision = true
} }
// We only track revisions for live documents
if doc.Lifecycle != workflow.LifecycleLive {
skipRevision = true
}
err = h.Store.Page.Update(ctx, model.Page, refID, ctx.UserID, skipRevision) err = h.Store.Page.Update(ctx, model.Page, refID, ctx.UserID, skipRevision)
if err != nil { if err != nil {
ctx.Transaction.Rollback() ctx.Transaction.Rollback()
@ -422,12 +436,15 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
return return
} }
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{ // Draft edits are not logged
LabelID: doc.LabelID, if doc.Lifecycle == workflow.LifecycleLive {
DocumentID: model.Page.DocumentID, h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
PageID: model.Page.RefID, LabelID: doc.LabelID,
SourceType: activity.SourceTypePage, DocumentID: model.Page.DocumentID,
ActivityType: activity.TypeEdited}) PageID: model.Page.RefID,
SourceType: activity.SourceTypePage,
ActivityType: activity.TypeEdited})
}
h.Store.Audit.Record(ctx, audit.EventTypeSectionUpdate) h.Store.Audit.Record(ctx, audit.EventTypeSectionUpdate)
@ -470,7 +487,11 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
ctx.Transaction.Commit() ctx.Transaction.Commit()
go h.Indexer.IndexContent(ctx, model.Page) if doc.Lifecycle == workflow.LifecycleLive {
go h.Indexer.IndexContent(ctx, model.Page)
} else {
go h.Indexer.DeleteDocument(ctx, doc.RefID)
}
updatedPage, err := h.Store.Page.Get(ctx, pageID) updatedPage, err := h.Store.Page.Get(ctx, pageID)
@ -547,12 +568,15 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
return return
} }
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{ // Draft actions are not logged
LabelID: doc.LabelID, if doc.Lifecycle == workflow.LifecycleLive {
DocumentID: documentID, h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
PageID: pageID, LabelID: doc.LabelID,
SourceType: activity.SourceTypePage, DocumentID: documentID,
ActivityType: activity.TypeDeleted}) PageID: pageID,
SourceType: activity.SourceTypePage,
ActivityType: activity.TypeDeleted})
}
go h.Indexer.DeleteContent(ctx, pageID) go h.Indexer.DeleteContent(ctx, pageID)
@ -660,12 +684,15 @@ func (h *Handler) DeletePages(w http.ResponseWriter, r *http.Request) {
h.Store.Page.DeletePageRevisions(ctx, page.PageID) h.Store.Page.DeletePageRevisions(ctx, page.PageID)
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{ // Draft actions are not logged
LabelID: doc.LabelID, if doc.Lifecycle == workflow.LifecycleLive {
DocumentID: documentID, h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
PageID: page.PageID, LabelID: doc.LabelID,
SourceType: activity.SourceTypePage, DocumentID: documentID,
ActivityType: activity.TypeDeleted}) PageID: page.PageID,
SourceType: activity.SourceTypePage,
ActivityType: activity.TypeDeleted})
}
} }
ctx.Transaction.Commit() ctx.Transaction.Commit()
@ -925,13 +952,15 @@ func (h *Handler) Copy(w http.ResponseWriter, r *http.Request) {
h.Store.Block.IncrementUsage(ctx, model.Page.BlockID) h.Store.Block.IncrementUsage(ctx, model.Page.BlockID)
} }
// Log action against target document // Log t actions are not logged
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{ if doc.Lifecycle == workflow.LifecycleLive {
LabelID: doc.LabelID, h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
DocumentID: targetID, LabelID: doc.LabelID,
PageID: newPageID, DocumentID: targetID,
SourceType: activity.SourceTypePage, PageID: newPageID,
ActivityType: activity.TypeCreated}) SourceType: activity.SourceTypePage,
ActivityType: activity.TypeCreated})
}
ctx.Transaction.Commit() ctx.Transaction.Commit()
@ -1174,12 +1203,15 @@ func (h *Handler) Rollback(w http.ResponseWriter, r *http.Request) {
return return
} }
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{ // Draft actions are not logged
LabelID: doc.LabelID, if doc.Lifecycle == workflow.LifecycleLive {
DocumentID: p.DocumentID, h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
PageID: p.RefID, LabelID: doc.LabelID,
SourceType: activity.SourceTypePage, DocumentID: p.DocumentID,
ActivityType: activity.TypeReverted}) PageID: p.RefID,
SourceType: activity.SourceTypePage,
ActivityType: activity.TypeReverted})
}
ctx.Transaction.Commit() ctx.Transaction.Commit()

View file

@ -165,6 +165,44 @@ func CanViewSpace(ctx domain.RequestContext, s domain.Store, spaceID string) boo
return false return false
} }
// CanViewDrafts returns if the user has permission to view drafts in space.
func CanViewDrafts(ctx domain.RequestContext, s domain.Store, spaceID string) bool {
roles, err := s.Permission.GetUserSpacePermissions(ctx, spaceID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
return false
}
for _, role := range roles {
if role.OrgID == ctx.OrgID && role.RefID == spaceID && role.Location == pm.LocationSpace && role.Scope == pm.ScopeRow &&
pm.ContainsPermission(role.Action, pm.DocumentLifecycle) {
return true
}
}
return false
}
// CanManageVersion returns if the user has permission to manage versions in space.
func CanManageVersion(ctx domain.RequestContext, s domain.Store, spaceID string) bool {
roles, err := s.Permission.GetUserSpacePermissions(ctx, spaceID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
return false
}
for _, role := range roles {
if role.OrgID == ctx.OrgID && role.RefID == spaceID && role.Location == pm.LocationSpace && role.Scope == pm.ScopeRow &&
pm.ContainsPermission(role.Action, pm.DocumentVersion) {
return true
}
}
return false
}
// HasPermission returns if user can perform specified actions. // HasPermission returns if user can perform specified actions.
func HasPermission(ctx domain.RequestContext, s domain.Store, spaceID string, actions ...pm.Action) bool { func HasPermission(ctx domain.RequestContext, s domain.Store, spaceID string, actions ...pm.Action) bool {
roles, err := s.Permission.GetUserSpacePermissions(ctx, spaceID) roles, err := s.Permission.GetUserSpacePermissions(ctx, spaceID)

View file

@ -121,6 +121,12 @@ func (s Scope) IndexContent(ctx domain.RequestContext, p page.Page) (err error)
err = errors.Wrap(err, "execute insert document content entry") err = errors.Wrap(err, "execute insert document content entry")
} }
_, err = ctx.Transaction.Exec("INSERT INTO search (orgid, documentid, itemid, itemtype, content) VALUES (?, ?, ?, ?, ?)",
ctx.OrgID, p.DocumentID, p.RefID, "page", p.Title)
if err != nil {
err = errors.Wrap(err, "execute insert document page title entry")
}
return nil return nil
} }
@ -148,7 +154,6 @@ func (s Scope) DeleteContent(ctx domain.RequestContext, pageID string) (err erro
// Visible documents include both those in the client's own organisation and those that are public, or whose visibility includes the client. // Visible documents include both those in the client's own organisation and those that are public, or whose visibility includes the client.
func (s Scope) Documents(ctx domain.RequestContext, q search.QueryOptions) (results []search.QueryResult, err error) { func (s Scope) Documents(ctx domain.RequestContext, q search.QueryOptions) (results []search.QueryResult, err error) {
q.Keywords = strings.TrimSpace(q.Keywords) q.Keywords = strings.TrimSpace(q.Keywords)
if len(q.Keywords) == 0 { if len(q.Keywords) == 0 {
return return
} }
@ -204,29 +209,29 @@ func (s Scope) Documents(ctx domain.RequestContext, q search.QueryOptions) (resu
func (s Scope) matchFullText(ctx domain.RequestContext, keywords, itemType string) (r []search.QueryResult, err error) { func (s Scope) matchFullText(ctx domain.RequestContext, keywords, itemType string) (r []search.QueryResult, err error) {
sql1 := ` sql1 := `
SELECT SELECT
s.id, s.orgid, s.documentid, s.itemid, s.itemtype, s.id, s.orgid, s.documentid, s.itemid, s.itemtype,
d.labelid as spaceid, COALESCE(d.title,'Unknown') AS document, d.tags, d.excerpt, d.labelid as spaceid, COALESCE(d.title,'Unknown') AS document, d.tags,
d.excerpt, d.template, d.versionid,
COALESCE(l.label,'Unknown') AS space COALESCE(l.label,'Unknown') AS space
FROM FROM
search s, search s,
document d document d
LEFT JOIN LEFT JOIN
label l ON l.orgid=d.orgid AND l.refid = d.labelid label l ON l.orgid=d.orgid AND l.refid = d.labelid
WHERE WHERE
s.orgid = ? s.orgid = ?
AND s.itemtype = ? AND s.itemtype = ?
AND s.documentid = d.refid AND s.documentid = d.refid
-- AND d.template = 0 AND d.labelid IN
AND d.labelid IN
( (
SELECT refid FROM label WHERE orgid=? SELECT refid FROM label WHERE orgid=? AND refid IN
AND refid IN (SELECT refid FROM permission WHERE orgid=? AND location='space' AND refid IN ( (
SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') AND location='space' AND action='view' SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') AND location='space' AND action='view'
UNION ALL UNION ALL
SELECT p.refid from permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=? AND p.who='role' AND p.location='space' AND p.action='view' AND r.userid=? SELECT p.refid from permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=? AND p.who='role' AND p.location='space' AND r.userid=?
)) )
) )
AND MATCH(s.content) AGAINST(? IN BOOLEAN MODE)` AND MATCH(s.content) AGAINST(? IN BOOLEAN MODE)`
err = s.Runtime.Db.Select(&r, err = s.Runtime.Db.Select(&r,
@ -235,7 +240,6 @@ func (s Scope) matchFullText(ctx domain.RequestContext, keywords, itemType strin
itemType, itemType,
ctx.OrgID, ctx.OrgID,
ctx.OrgID, ctx.OrgID,
ctx.OrgID,
ctx.UserID, ctx.UserID,
ctx.OrgID, ctx.OrgID,
ctx.UserID, ctx.UserID,
@ -245,7 +249,6 @@ func (s Scope) matchFullText(ctx domain.RequestContext, keywords, itemType strin
err = nil err = nil
r = []search.QueryResult{} r = []search.QueryResult{}
} }
if err != nil { if err != nil {
err = errors.Wrap(err, "search document "+itemType) err = errors.Wrap(err, "search document "+itemType)
} }
@ -261,25 +264,25 @@ func (s Scope) matchLike(ctx domain.RequestContext, keywords, itemType string) (
keywords = fmt.Sprintf("%%%s%%", keywords) keywords = fmt.Sprintf("%%%s%%", keywords)
sql1 := ` sql1 := `
SELECT SELECT
s.id, s.orgid, s.documentid, s.itemid, s.itemtype, s.id, s.orgid, s.documentid, s.itemid, s.itemtype,
d.labelid as spaceid, COALESCE(d.title,'Unknown') AS document, d.tags, d.excerpt, d.labelid as spaceid, COALESCE(d.title,'Unknown') AS document, d.tags, d.excerpt,
COALESCE(l.label,'Unknown') AS space COALESCE(l.label,'Unknown') AS space
FROM FROM
search s, search s,
document d document d
LEFT JOIN LEFT JOIN
label l ON l.orgid=d.orgid AND l.refid = d.labelid label l ON l.orgid=d.orgid AND l.refid = d.labelid
WHERE WHERE
s.orgid = ? s.orgid = ?
AND s.itemtype = ? AND s.itemtype = ?
AND s.documentid = d.refid AND s.documentid = d.refid
-- AND d.template = 0 -- AND d.template = 0
AND d.labelid IN AND d.labelid IN
( (
SELECT refid FROM label WHERE orgid=? SELECT refid FROM label WHERE orgid=?
AND refid IN (SELECT refid FROM permission WHERE orgid=? AND location='space' AND refid IN ( AND refid IN (SELECT refid FROM permission WHERE orgid=? AND location='space' AND refid IN (
SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') AND location='space' AND action='view' SELECT refid from permission WHERE orgid=? AND who='user' AND (whoid=? OR whoid='0') AND location='space' AND action='view'
UNION ALL UNION ALL
SELECT p.refid from permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=? AND p.who='role' SELECT p.refid from permission p LEFT JOIN rolemember r ON p.whoid=r.roleid WHERE p.orgid=? AND p.who='role'
AND p.location='space' AND p.action='view' AND (r.userid=? OR r.userid='0') AND p.location='space' AND p.action='view' AND (r.userid=? OR r.userid='0')

View file

@ -168,18 +168,17 @@ type AuditStorer interface {
type DocumentStorer interface { type DocumentStorer interface {
Add(ctx RequestContext, document doc.Document) (err error) Add(ctx RequestContext, document doc.Document) (err error)
Get(ctx RequestContext, id string) (document doc.Document, err error) Get(ctx RequestContext, id string) (document doc.Document, err error)
GetAll() (ctx RequestContext, documents []doc.Document, err error)
GetBySpace(ctx RequestContext, spaceID string) (documents []doc.Document, err error) GetBySpace(ctx RequestContext, spaceID string) (documents []doc.Document, err error)
DocumentList(ctx RequestContext) (documents []doc.Document, err error)
Templates(ctx RequestContext) (documents []doc.Document, err error)
TemplatesBySpace(ctx RequestContext, spaceID string) (documents []doc.Document, err error) TemplatesBySpace(ctx RequestContext, spaceID string) (documents []doc.Document, err error)
DocumentMeta(ctx RequestContext, id string) (meta doc.DocumentMeta, err error) DocumentMeta(ctx RequestContext, id string) (meta doc.DocumentMeta, err error)
PublicDocuments(ctx RequestContext, orgID string) (documents []doc.SitemapDocument, err error) PublicDocuments(ctx RequestContext, orgID string) (documents []doc.SitemapDocument, err error)
Update(ctx RequestContext, document doc.Document) (err error) Update(ctx RequestContext, document doc.Document) (err error)
UpdateGroup(ctx RequestContext, document doc.Document) (err error)
ChangeDocumentSpace(ctx RequestContext, document, space string) (err error) ChangeDocumentSpace(ctx RequestContext, document, space string) (err error)
MoveDocumentSpace(ctx RequestContext, id, move string) (err error) MoveDocumentSpace(ctx RequestContext, id, move string) (err error)
Delete(ctx RequestContext, documentID string) (rows int64, err error) Delete(ctx RequestContext, documentID string) (rows int64, err error)
DeleteBySpace(ctx RequestContext, spaceID string) (rows int64, err error) DeleteBySpace(ctx RequestContext, spaceID string) (rows int64, err error)
GetVersions(ctx RequestContext, groupID string) (v []doc.Version, err error)
} }
// SettingStorer defines required methods for persisting global and user level settings // SettingStorer defines required methods for persisting global and user level settings
@ -217,6 +216,7 @@ type LinkStorer interface {
type ActivityStorer interface { type ActivityStorer interface {
RecordUserActivity(ctx RequestContext, activity activity.UserActivity) (err error) RecordUserActivity(ctx RequestContext, activity activity.UserActivity) (err error)
GetDocumentActivity(ctx RequestContext, id string) (a []activity.DocumentActivity, err error) GetDocumentActivity(ctx RequestContext, id string) (a []activity.DocumentActivity, err error)
DeleteDocumentChangeActivity(ctx RequestContext, id string) (rows int64, err error)
} }
// SearchStorer defines required methods for persisting search queries // SearchStorer defines required methods for persisting search queries

View file

@ -35,6 +35,7 @@ import (
"github.com/documize/community/model/page" "github.com/documize/community/model/page"
pm "github.com/documize/community/model/permission" pm "github.com/documize/community/model/permission"
"github.com/documize/community/model/template" "github.com/documize/community/model/template"
"github.com/documize/community/model/workflow"
uuid "github.com/nu7hatch/gouuid" uuid "github.com/nu7hatch/gouuid"
) )
@ -377,7 +378,12 @@ func (h *Handler) Use(w http.ResponseWriter, r *http.Request) {
event.Handler().Publish(string(event.TypeAddDocument), nd.Title) event.Handler().Publish(string(event.TypeAddDocument), nd.Title)
a, _ := h.Store.Attachment.GetAttachments(ctx, documentID) a, _ := h.Store.Attachment.GetAttachments(ctx, documentID)
go h.Indexer.IndexDocument(ctx, nd, a)
if nd.Lifecycle == workflow.LifecycleLive {
go h.Indexer.IndexDocument(ctx, nd, a)
} else {
go h.Indexer.DeleteDocument(ctx, d.RefID)
}
response.WriteJSON(w, nd) response.WriteJSON(w, nd)
} }

View file

@ -41,7 +41,7 @@ func main() {
// product details // product details
rt.Product = env.ProdInfo{} rt.Product = env.ProdInfo{}
rt.Product.Major = "1" rt.Product.Major = "1"
rt.Product.Minor = "58" rt.Product.Minor = "59"
rt.Product.Patch = "0" rt.Product.Patch = "0"
rt.Product.Version = fmt.Sprintf("%s.%s.%s", rt.Product.Major, rt.Product.Minor, rt.Product.Patch) rt.Product.Version = fmt.Sprintf("%s.%s.%s", rt.Product.Major, rt.Product.Minor, rt.Product.Patch)
rt.Product.Edition = "Community" rt.Product.Edition = "Community"

File diff suppressed because one or more lines are too long

View file

@ -9,18 +9,16 @@
// //
// https://documize.com // https://documize.com
import Service, { inject as service } from '@ember/service'; import AuthProvider from '../../mixins/auth';
import ModalMixin from '../../mixins/modal';
import Component from '@ember/component';
export default Service.extend({ export default Component.extend(AuthProvider, ModalMixin, {
ajax: service(),
getDocumentSummary(documentId) { init() {
return this.get('ajax').request(`activity/document/${documentId}`, { this._super(...arguments);
method: "GET" },
}).then((response) => {
return response; actions: {
}).catch(() => {
return [];
});
} }
}); });

View file

@ -13,11 +13,12 @@ import $ from 'jquery';
import { computed } from '@ember/object'; import { computed } from '@ember/object';
import { notEmpty } from '@ember/object/computed'; import { notEmpty } from '@ember/object/computed';
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import Component from '@ember/component';
import { A } from "@ember/array" import { A } from "@ember/array"
import { schedule } from '@ember/runloop'; import { schedule } from '@ember/runloop';
import ModalMixin from '../../mixins/modal';
import Component from '@ember/component';
export default Component.extend({ export default Component.extend(ModalMixin, {
documentService: service('document'), documentService: service('document'),
categoryService: service('category'), categoryService: service('category'),
sessionService: service('session'), sessionService: service('session'),

View file

@ -19,12 +19,12 @@ export default Component.extend(ModalMixin, {
groupSvc: service('group'), groupSvc: service('group'),
spaceSvc: service('folder'), spaceSvc: service('folder'),
userSvc: service('user'), userSvc: service('user'),
appMeta: service(), appMeta: service(),
store: service(), store: service(),
spacePermissions: null, spacePermissions: null,
users: null, users: null,
searchText: '', searchText: '',
didReceiveAttrs() { didReceiveAttrs() {
let spacePermissions = A([]); let spacePermissions = A([]);
let constants = this.get('constants'); let constants = this.get('constants');
@ -65,7 +65,7 @@ export default Component.extend(ModalMixin, {
// always show everyone // always show everyone
if (!hasEveryoneId) { if (!hasEveryoneId) {
let pr = this.permissionRecord(constants.WhoType.User, constants.EveryoneUserId, ' ' + constants.EveryoneUserName); let pr = this.permissionRecord(constants.WhoType.User, constants.EveryoneUserId, ' ' + constants.EveryoneUserName);
spacePermissions.pushObject(pr); spacePermissions.pushObject(pr);
} }
this.set('spacePermissions', spacePermissions.sortBy('who', 'name')); this.set('spacePermissions', spacePermissions.sortBy('who', 'name'));
@ -93,6 +93,8 @@ export default Component.extend(ModalMixin, {
documentCopy: false, documentCopy: false,
documentTemplate: false, documentTemplate: false,
documentApprove: false, documentApprove: false,
documentLifecycle: false,
documentVersion: false,
}; };
let rec = this.get('store').normalize('space-permission', raw); let rec = this.get('store').normalize('space-permission', raw);
@ -132,7 +134,8 @@ export default Component.extend(ModalMixin, {
let hasEveryone = _.find(permissions, (permission) => { let hasEveryone = _.find(permissions, (permission) => {
return permission.get('whoId') === constants.EveryoneUserId && return permission.get('whoId') === constants.EveryoneUserId &&
(permission.get('spaceView') || permission.get('documentAdd') || permission.get('documentEdit') || permission.get('documentDelete') || (permission.get('spaceView') || permission.get('documentAdd') || permission.get('documentEdit') || permission.get('documentDelete') ||
permission.get('documentMove') || permission.get('documentCopy') || permission.get('documentTemplate') || permission.get('documentApprove')); permission.get('documentMove') || permission.get('documentCopy') || permission.get('documentTemplate') ||
permission.get('documentApprove') || permission.get('documentLifecycle') || permission.get('documentVersion'));
}); });
// see if more than oen user is granted access to space (excluding everyone) // see if more than oen user is granted access to space (excluding everyone)
@ -140,8 +143,9 @@ export default Component.extend(ModalMixin, {
permissions.forEach((permission) => { permissions.forEach((permission) => {
if (permission.get('whoId') !== constants.EveryoneUserId && if (permission.get('whoId') !== constants.EveryoneUserId &&
(permission.get('spaceView') || permission.get('documentAdd') || permission.get('documentEdit') || permission.get('documentDelete') || (permission.get('spaceView') || permission.get('documentAdd') || permission.get('documentEdit') || permission.get('documentDelete') ||
permission.get('documentMove') || permission.get('documentCopy') || permission.get('documentTemplate') || permission.get('documentApprove'))) { permission.get('documentMove') || permission.get('documentCopy') || permission.get('documentTemplate') ||
roleCount += 1; permission.get('documentApprove') || permission.get('documentLifecycle') || permission.get('documentVersion'))) {
roleCount += 1;
} }
}); });
@ -161,7 +165,7 @@ export default Component.extend(ModalMixin, {
}, },
onSearch() { onSearch() {
debounce(this, function() { debounce(this, function () {
let searchText = this.get('searchText').trim(); let searchText = this.get('searchText').trim();
if (searchText.length === 0) { if (searchText.length === 0) {
@ -181,7 +185,6 @@ export default Component.extend(ModalMixin, {
if (is.undefined(exists)) { if (is.undefined(exists)) {
spacePermissions.pushObject(this.permissionRecord(constants.WhoType.User, user.get('id'), user.get('fullname'))); spacePermissions.pushObject(this.permissionRecord(constants.WhoType.User, user.get('id'), user.get('fullname')));
this.set('spacePermissions', spacePermissions); this.set('spacePermissions', spacePermissions);
// this.set('spacePermissions', spacePermissions.sortBy('who', 'name'));
} }
}, },
} }

View file

@ -12,21 +12,18 @@
import Component from '@ember/component'; import Component from '@ember/component';
export default Component.extend({ export default Component.extend({
resultPhrase: "", resultPhrase: '',
init() {
this._super(...arguments);
this.results = [];
},
didReceiveAttrs() { didReceiveAttrs() {
this._super(...arguments);
let docs = this.get('results'); let docs = this.get('results');
let duped = []; let duped = [];
let phrase = 'Nothing found'; let phrase = 'Nothing found';
if (docs.length > 0) { if (docs.length > 0) {
duped = _.uniq(docs, function (item) { duped = _.uniq(docs, function (item) {
return item.documentId; return item.get('documentId');
}); });
let references = docs.length === 1 ? "reference" : "references"; let references = docs.length === 1 ? "reference" : "references";

View file

@ -11,10 +11,10 @@
import $ from 'jquery'; import $ from 'jquery';
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import Component from '@ember/component';
import AuthMixin from '../../mixins/auth'; import AuthMixin from '../../mixins/auth';
import TooltipMixin from '../../mixins/tooltip'; import TooltipMixin from '../../mixins/tooltip';
import ModalMixin from '../../mixins/modal'; import ModalMixin from '../../mixins/modal';
import Component from '@ember/component';
export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, { export default Component.extend(ModalMixin, TooltipMixin, AuthMixin, {
userSvc: service('user'), userSvc: service('user'),

View file

@ -1,11 +1,11 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved. // Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
// //
// This software (Documize Community Edition) is licensed under // This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html // GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
// //
// You can operate outside the AGPL restrictions by purchasing // You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license // Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>. // by contacting <sales@documize.com>.
// //
// https://documize.com // https://documize.com
@ -15,52 +15,70 @@ import EmberObject from "@ember/object";
// let constants = this.get('constants'); // let constants = this.get('constants');
let constants = EmberObject.extend({ let constants = EmberObject.extend({
// Document // Document
ProtectionType: { // eslint-disable-line ember/avoid-leaking-state-in-ember-objects ProtectionType: { // eslint-disable-line ember/avoid-leaking-state-in-ember-objects
None: 0, None: 0,
Lock: 1, Lock: 1,
Review: 2, Review: 2,
NoneLabel: 'Changes permitted without approval', NoneLabel: 'Changes permitted without approval',
LockLabel: 'Locked, changes not permitted', LockLabel: 'Locked, changes not permitted',
ReviewLabel: 'Changes require approval before publication' ReviewLabel: 'Changes require approval before publication'
}, },
// Document // Document
ApprovalType: { // eslint-disable-line ember/avoid-leaking-state-in-ember-objects ApprovalType: { // eslint-disable-line ember/avoid-leaking-state-in-ember-objects
None: 0, None: 0,
Anybody: 1, Anybody: 1,
Majority: 2, Majority: 2,
Unanimous: 3, Unanimous: 3,
AnybodyLabel: 'Approval required from any approver', AnybodyLabel: 'Approval required from any approver',
MajorityLabel: 'Majority approval required from approvers', MajorityLabel: 'Majority approval required from approvers',
UnanimousLabel: 'Unanimous approval required from all approvers' UnanimousLabel: 'Unanimous approval required from all approvers'
}, },
// Section // Section
ChangeState: { // eslint-disable-line ember/avoid-leaking-state-in-ember-objects ChangeState: { // eslint-disable-line ember/avoid-leaking-state-in-ember-objects
Published: 0, Published: 0,
Pending: 1, Pending: 1,
UnderReview: 2, UnderReview: 2,
Rejected: 3, Rejected: 3,
PendingNew: 4, PendingNew: 4,
}, },
// Section // Section
PageType: { // eslint-disable-line ember/avoid-leaking-state-in-ember-objects PageType: { // eslint-disable-line ember/avoid-leaking-state-in-ember-objects
Tab: 'tab', Tab: 'tab',
Section: 'section' Section: 'section'
}, },
// Who a permission record relates to // Who a permission record relates to
WhoType: { // eslint-disable-line ember/avoid-leaking-state-in-ember-objects WhoType: { // eslint-disable-line ember/avoid-leaking-state-in-ember-objects
User: 'user', User: 'user',
Group: 'role' Group: 'role'
}, },
EveryoneUserId: "0", EveryoneUserId: '0',
EveryoneUserName: "Everyone" EveryoneUserName: "Everyone",
// Document
Lifecycle: { // eslint-disable-line ember/avoid-leaking-state-in-ember-objects
Draft: 0,
Live: 1,
Archived: 2,
DraftLabel: 'Draft',
LiveLabel: 'Live',
ArchivedLabel: 'Archived',
},
// Document Version -- document.groupId links different versions of documents together
VersionCreateMode: { // eslint-disable-line ember/avoid-leaking-state-in-ember-objects
Unversioned: 1, // turn unversioned into versioned document
Cloned: 2, // create versioned document by cloning existing versioned document
Linked: 3 // link existing unversion document into this version group
}
}); });
export default { constants } export default { constants }

View file

@ -1,24 +1,25 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved. // Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
// //
// This software (Documize Community Edition) is licensed under // This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html // GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
// //
// You can operate outside the AGPL restrictions by purchasing // You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license // Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>. // by contacting <sales@documize.com>.
// //
// https://documize.com // https://documize.com
export function initialize(application) { export function initialize(application) {
application.inject('route', 'econstants', 'econstants:main'); application.inject('route', 'econstants', 'econstants:main');
application.inject('controller', 'econstants', 'econstants:main'); application.inject('controller', 'econstants', 'econstants:main');
application.inject('component', 'econstants', 'econstants:main'); application.inject('component', 'econstants', 'econstants:main');
application.inject('template', 'econstants', 'econstants:main'); application.inject('template', 'econstants', 'econstants:main');
application.inject('service', 'econstants', 'econstants:main'); application.inject('service', 'econstants', 'econstants:main');
application.inject('model', 'econstants', 'econstants:main');
} }
export default { export default {
name: 'econstants', name: 'econstants',
after: "application", after: "application",
initialize: initialize initialize: initialize
}; };

View file

@ -24,5 +24,7 @@ export default Model.extend({
space: attr(), space: attr(),
spaceId: attr(), spaceId: attr(),
spaceSlug: attr(), spaceSlug: attr(),
template: attr(),
versionId: attr(),
selected: attr() selected: attr()
}); });

View file

@ -10,10 +10,9 @@
// https://documize.com // https://documize.com
import { computed } from '@ember/object'; import { computed } from '@ember/object';
import Model from 'ember-data/model';
import attr from 'ember-data/attr'; import attr from 'ember-data/attr';
import stringUtil from '../utils/string'; import stringUtil from '../utils/string';
// import { belongsTo, hasMany } from 'ember-data/relationships'; import Model from 'ember-data/model';
export default Model.extend({ export default Model.extend({
name: attr('string'), name: attr('string'),
@ -27,6 +26,11 @@ export default Model.extend({
template: attr('boolean'), template: attr('boolean'),
protection: attr('number', { defaultValue: 0 }), protection: attr('number', { defaultValue: 0 }),
approval: attr('number', { defaultValue: 0 }), approval: attr('number', { defaultValue: 0 }),
lifecycle: attr('number', { defaultValue: 1 }),
versioned: attr('boolean'),
versionId: attr('string'),
versionOrder: attr('number', { defaultValue: 0 }),
groupId: attr('string'),
// client-side property // client-side property
selected: attr('boolean', { defaultValue: false }), selected: attr('boolean', { defaultValue: false }),
@ -34,5 +38,24 @@ export default Model.extend({
return stringUtil.makeSlug(this.get('name')); return stringUtil.makeSlug(this.get('name'));
}), }),
created: attr(), created: attr(),
revised: attr() revised: attr(),
isDraft: computed('lifecycle', function () {
let constants = this.get('constants');
return this.get('lifecycle') == constants.Lifecycle.Draft;
}),
lifecycleLabel: computed('lifecycle', function () {
let constants = this.get('constants');
switch (this.get('lifecycle')) {
case constants.Lifecycle.Draft:
return constants.Lifecycle.DraftLabel;
case constants.Lifecycle.Live:
return constants.Lifecycle.LiveLabel;
case constants.Lifecycle.Archived:
return constants.Lifecycle.ArchivedLabel;
}
return '';
}),
}); });

View file

@ -10,11 +10,10 @@
// https://documize.com // https://documize.com
import { computed } from '@ember/object'; import { computed } from '@ember/object';
import Model from 'ember-data/model';
import attr from 'ember-data/attr'; import attr from 'ember-data/attr';
import constants from '../utils/constants'; import constants from '../utils/constants';
import stringUtil from '../utils/string'; import stringUtil from '../utils/string';
// import { belongsTo, hasMany } from 'ember-data/relationships'; import Model from 'ember-data/model';
export default Model.extend({ export default Model.extend({
name: attr('string'), name: attr('string'),

View file

@ -27,6 +27,8 @@ export default Model.extend({
documentCopy: attr('boolean'), documentCopy: attr('boolean'),
documentTemplate: attr('boolean'), documentTemplate: attr('boolean'),
documentApprove: attr('boolean'), documentApprove: attr('boolean'),
documentLifecycle: attr('boolean'),
documentVersion: attr('boolean'),
name: attr('string'), // read-only name: attr('string'), // read-only
members: attr('number') // read-only members: attr('number') // read-only
}); });

View file

@ -0,0 +1,17 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
import Controller from '@ember/controller';
export default Controller.extend({
actions: {
}
});

View file

@ -0,0 +1,25 @@
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
//
// This software (Documize Community Edition) is licensed under
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
//
// You can operate outside the AGPL restrictions by purchasing
// Documize Enterprise Edition and obtaining a commercial license
// by contacting <sales@documize.com>.
//
// https://documize.com
import Route from '@ember/routing/route';
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
export default Route.extend(AuthenticatedRouteMixin, {
beforeModel() {
if (!this.session.isAdmin) {
this.transitionTo('auth.login');
}
},
activate() {
this.get('browser').setTitle('Archive');
}
});

View file

@ -0,0 +1 @@
{{customize/archive-admin}}

View file

@ -2,7 +2,7 @@
{{#toolbar/t-toolbar}} {{#toolbar/t-toolbar}}
{{#toolbar/t-links}} {{#toolbar/t-links}}
{{#link-to "folders" class="link" tagName="li"}}Spaces{{/link-to}} {{#link-to "folders" class="link" tagName="li" }}Spaces{{/link-to}}
{{/toolbar/t-links}} {{/toolbar/t-links}}
{{#toolbar/t-actions}} {{#toolbar/t-actions}}
{{/toolbar/t-actions}} {{/toolbar/t-actions}}
@ -12,19 +12,20 @@
<div class="row"> <div class="row">
<div class="col my-5"> <div class="col my-5">
<ul class="tabnav-control"> <ul class="tabnav-control">
{{#link-to 'customize.general' activeClass='selected' class="tab" tagName="li"}}General{{/link-to}} {{#link-to 'customize.general' activeClass='selected' class="tab" tagName="li" }}General{{/link-to}}
{{#link-to 'customize.folders' activeClass='selected' class="tab" tagName="li"}}Spaces{{/link-to}} {{#link-to 'customize.folders' activeClass='selected' class="tab" tagName="li" }}Spaces{{/link-to}}
{{#link-to 'customize.groups' activeClass='selected' class="tab" tagName="li"}}Groups{{/link-to}} {{#link-to 'customize.groups' activeClass='selected' class="tab" tagName="li" }}Groups{{/link-to}}
{{#link-to 'customize.users' activeClass='selected' class="tab" tagName="li"}}Users{{/link-to}} {{#link-to 'customize.users' activeClass='selected' class="tab" tagName="li" }}Users{{/link-to}}
{{#if session.isGlobalAdmin}} {{#if session.isGlobalAdmin}}
{{#link-to 'customize.smtp' activeClass='selected' class="tab" tagName="li"}}SMTP{{/link-to}} {{#link-to 'customize.smtp' activeClass='selected' class="tab" tagName="li" }}SMTP{{/link-to}}
{{#link-to 'customize.license' activeClass='selected' class="tab" tagName="li"}}License{{/link-to}} {{#link-to 'customize.license' activeClass='selected' class="tab" tagName="li" }}License{{/link-to}}
{{#link-to 'customize.auth' activeClass='selected' class="tab" tagName="li"}}Authentication{{/link-to}} {{#link-to 'customize.auth' activeClass='selected' class="tab" tagName="li" }}Authentication{{/link-to}}
{{/if}} {{/if}}
{{#link-to 'customize.archive' activeClass='selected' class="tab" tagName="li" }}Archive{{/link-to}}
</ul> </ul>
</div> </div>
</div> </div>
<div class="mt-4 margin-bottom-100"> <div class="mt-4 margin-bottom-100">
{{outlet}} {{outlet}}
</div> </div>
</div> </div>

View file

@ -43,7 +43,8 @@ export default Route.extend(AuthenticatedRouteMixin, {
sections: this.modelFor('document').sections, sections: this.modelFor('document').sections,
permissions: this.modelFor('document').permissions, permissions: this.modelFor('document').permissions,
roles: this.modelFor('document').roles, roles: this.modelFor('document').roles,
blocks: this.modelFor('document').blocks blocks: this.modelFor('document').blocks,
versions: this.modelFor('document').versions
}); });
}, },
@ -57,10 +58,11 @@ export default Route.extend(AuthenticatedRouteMixin, {
controller.set('permissions', model.permissions); controller.set('permissions', model.permissions);
controller.set('roles', model.roles); controller.set('roles', model.roles);
controller.set('blocks', model.blocks); controller.set('blocks', model.blocks);
controller.set('versions', model.versions);
}, },
activate: function() { activate: function () {
this._super(...arguments); this._super(...arguments);
window.scrollTo(0,0); window.scrollTo(0, 0);
} }
}); });

View file

@ -1,6 +1,5 @@
{{toolbar/nav-bar}} {{toolbar/nav-bar}} {{toolbar/for-document document=document spaces=folders space=folder
permissions=permissions roles=roles tab=tab versions=versions
{{toolbar/for-document document=document spaces=folders space=folder permissions=permissions roles=roles tab=tab
onDocumentDelete=(action 'onDocumentDelete') onDocumentDelete=(action 'onDocumentDelete')
onSaveTemplate=(action 'onSaveTemplate') onSaveTemplate=(action 'onSaveTemplate')
onSaveDocument=(action 'onSaveDocument') onSaveDocument=(action 'onSaveDocument')
@ -10,9 +9,10 @@
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
{{document/document-heading document=document permissions=permissions {{document/document-heading document=document permissions=permissions
versions=versions
onSaveDocument=(action 'onSaveDocument')}} onSaveDocument=(action 'onSaveDocument')}}
{{document/document-meta document=document folder=folder folders=folders
{{document/document-meta document=document folder=folder folders=folders permissions=permissions pages=pages permissions=permissions pages=pages versions=versions
onSaveDocument=(action 'onSaveDocument')}} onSaveDocument=(action 'onSaveDocument')}}
</div> </div>
</div> </div>
@ -38,8 +38,8 @@
{{#if (eq tab 'content')}} {{#if (eq tab 'content')}}
{{document/view-content {{document/view-content
document=document links=links pages=pages blocks=blocks currentPageId=currentPageId document=document links=links pages=pages blocks=blocks currentPageId=currentPageId
folder=folder folders=folders sections=sections permissions=permissions roles=roles folder=folder folders=folders sections=sections permissions=permissions roles=roles
onSavePage=(action 'onSavePage') onInsertSection=(action 'onInsertSection') onSavePage=(action 'onSavePage') onInsertSection=(action 'onInsertSection')
onSavePageAsBlock=(action 'onSavePageAsBlock') onDeleteBlock=(action 'onDeleteBlock') onSavePageAsBlock=(action 'onSavePageAsBlock') onDeleteBlock=(action 'onDeleteBlock')
onCopyPage=(action 'onCopyPage') onMovePage=(action 'onMovePage') onDeletePage=(action 'onPageDeleted') onCopyPage=(action 'onCopyPage') onMovePage=(action 'onMovePage') onDeletePage=(action 'onPageDeleted')
@ -56,4 +56,4 @@
{{/if}} {{/if}}
</div> </div>
</div> </div>
</div> </div>

View file

@ -32,6 +32,7 @@ export default Route.extend(AuthenticatedRouteMixin, {
this.set('permissions', data.permissions); this.set('permissions', data.permissions);
this.set('roles', data.roles); this.set('roles', data.roles);
this.set('links', data.links); this.set('links', data.links);
this.set('versions', data.versions);
resolve(); resolve();
}); });
}); });
@ -45,13 +46,14 @@ export default Route.extend(AuthenticatedRouteMixin, {
permissions: this.get('permissions'), permissions: this.get('permissions'),
roles: this.get('roles'), roles: this.get('roles'),
links: this.get('links'), links: this.get('links'),
versions: this.get('versions'),
sections: this.get('sectionService').getAll(), sections: this.get('sectionService').getAll(),
blocks: this.get('sectionService').getSpaceBlocks(this.get('folder.id')) blocks: this.get('sectionService').getSpaceBlocks(this.get('folder.id'))
}); });
}, },
actions: { actions: {
error(error /*, transition*/ ) { error(error /*, transition*/) {
if (error) { if (error) {
this.transitionTo('/not-found'); this.transitionTo('/not-found');
return false; return false;

View file

@ -16,7 +16,7 @@ var Router = EmberRouter.extend({
location: config.locationType location: config.locationType
}); });
export default Router.map(function() { export default Router.map(function () {
this.route('folders', { this.route('folders', {
path: '/' path: '/'
}); });
@ -30,7 +30,7 @@ export default Router.map(function() {
{ {
path: 's/:folder_id/:folder_slug' path: 's/:folder_id/:folder_slug'
}, },
function() { function () {
this.route('category', { this.route('category', {
path: 'category' path: 'category'
}); });
@ -42,7 +42,7 @@ export default Router.map(function() {
{ {
path: 's/:folder_id/:folder_slug/d/:document_id/:document_slug' path: 's/:folder_id/:folder_slug/d/:document_id/:document_slug'
}, },
function() { function () {
this.route('section', { this.route('section', {
path: 'section/:page_id' path: 'section/:page_id'
}); });
@ -57,7 +57,7 @@ export default Router.map(function() {
{ {
path: 'settings' path: 'settings'
}, },
function() { function () {
this.route('general', { this.route('general', {
path: 'general' path: 'general'
}); });
@ -82,6 +82,9 @@ export default Router.map(function() {
this.route('audit', { this.route('audit', {
path: 'audit' path: 'audit'
}); });
this.route('archive', {
path: 'archive'
});
} }
); );
@ -98,7 +101,7 @@ export default Router.map(function() {
{ {
path: 'auth' path: 'auth'
}, },
function() { function () {
this.route('sso', { this.route('sso', {
path: 'sso/:token' path: 'sso/:token'
}); });

View file

@ -334,6 +334,7 @@ export default Service.extend({
folders: [], folders: [],
folder: {}, folder: {},
links: [], links: [],
versions: [],
}; };
let doc = this.get('store').normalize('document', response.document); let doc = this.get('store').normalize('document', response.document);
@ -357,6 +358,7 @@ export default Service.extend({
data.folders = folders; data.folders = folders;
data.folder = folders.findBy('id', doc.get('folderId')); data.folder = folders.findBy('id', doc.get('folderId'));
data.links = response.links; data.links = response.links;
data.versions = response.versions;
return data; return data;
}).catch((error) => { }).catch((error) => {
@ -366,7 +368,7 @@ export default Service.extend({
// fetchPages returns all pages, page meta and pending changes for document. // fetchPages returns all pages, page meta and pending changes for document.
// This method bulk fetches data to reduce network chatter. // This method bulk fetches data to reduce network chatter.
// We produce a bunch of calculated boolean's for UI display purposes // We produce a bunch of calculated boolean's for UI display purposes
// that can tell us quickly about pending changes for UI display. // that can tell us quickly about pending changes for UI display.
fetchPages(documentId, currentUserId) { fetchPages(documentId, currentUserId) {
let constants = this.get('constants'); let constants = this.get('constants');
@ -400,7 +402,7 @@ export default Service.extend({
page.pending.forEach((i) => { page.pending.forEach((i) => {
let p = this.get('store').normalize('page', i.page); let p = this.get('store').normalize('page', i.page);
p = this.get('store').push(p); p = this.get('store').push(p);
let m = this.get('store').normalize('page-meta', i.meta); let m = this.get('store').normalize('page-meta', i.meta);
m = this.get('store').push(m); m = this.get('store').push(m);
@ -451,7 +453,7 @@ export default Service.extend({
userHasChangeRejected: userHasChangeRejected, userHasChangeRejected: userHasChangeRejected,
userHasNewPagePending: p.isNewPageUserPending(this.get('sessionService.user.id')) userHasNewPagePending: p.isNewPageUserPending(this.get('sessionService.user.id'))
}; };
let pim = this.get('store').normalize('page-container', pi); let pim = this.get('store').normalize('page-container', pi);
pim = this.get('store').push(pim); pim = this.get('store').push(pim);
data.pushObject(pim); data.pushObject(pim);

View file

@ -39,4 +39,4 @@ export default Service.extend({
return error; return error;
}); });
}, },
}); });

View file

@ -100,6 +100,7 @@ $link-hover-decoration: none;
@import "node_modules/bootstrap/scss/popover"; @import "node_modules/bootstrap/scss/popover";
@import "node_modules/bootstrap/scss/tooltip"; @import "node_modules/bootstrap/scss/tooltip";
@import "node_modules/bootstrap/scss/tables"; @import "node_modules/bootstrap/scss/tables";
@import "node_modules/bootstrap/scss/badge";
// @import "node_modules/bootstrap/scss/navbar"; // @import "node_modules/bootstrap/scss/navbar";
// @import "node_modules/bootstrap/scss/images"; // @import "node_modules/bootstrap/scss/images";

View file

@ -23,7 +23,7 @@
margin: 0 0 0 10px; margin: 0 0 0 10px;
display: inline-block; display: inline-block;
cursor: pointer; cursor: pointer;
> .email { > .email {
font-size: 0.9rem; font-size: 0.9rem;
color: $color-off-black; color: $color-off-black;
@ -124,7 +124,7 @@
} }
} }
} }
> .smtp-failure { > .smtp-failure {
font-size: 1.2rem; font-size: 1.2rem;
font-weight: bold; font-weight: bold;
@ -136,4 +136,16 @@
font-weight: bold; font-weight: bold;
color: $color-green; color: $color-green;
} }
> .archive-admin {
> .list {
> .item {
margin: 15px 0;
padding: 15px;
@include ease-in();
font-size: 1.2rem;
color: $color-primary;
}
}
}
} }

View file

@ -17,7 +17,6 @@
position: relative; position: relative;
margin: 0 0 30px 0; margin: 0 0 30px 0;
width: 100%; width: 100%;
// height: 150px;
&:hover { &:hover {
> .checkbox { > .checkbox {
@ -33,26 +32,33 @@
> .title { > .title {
color: $color-black; color: $color-black;
font-size: 1.3rem; font-weight: bold;
font-size: 1.4rem;
margin-bottom: 5px;
> .version {
font-size: 1.1rem;
font-weight: bold;
color: $color-gray;
}
}
> .space {
color: $color-off-black;
font-size: 1.2rem;
margin-bottom: 5px; margin-bottom: 5px;
} }
> .snippet { > .snippet {
color: $color-gray; color: $color-gray;
font-size: 1rem; font-size: 1.1rem;
line-height: 24px; margin-bottom: 10px;
} }
&:hover { &:hover {
color: $color-gray;
> .title { > .title {
color: $color-link; color: $color-link;
} }
> .snippet {
color: $color-link;
}
} }
} }
@ -61,7 +67,7 @@
display: inline-block; display: inline-block;
margin: 5px 10px 0 5px; margin: 5px 10px 0 5px;
color: $color-gray; color: $color-gray;
font-size: 0.875rem; font-size: 1rem;
font-style: italic; font-style: italic;
&:hover { &:hover {

View file

@ -61,6 +61,27 @@
box-shadow: 0 27px 24px 0 rgba(0, 0, 0, 0.2), 0 40px 77px 0 rgba(0, 0, 0, 0.22); box-shadow: 0 27px 24px 0 rgba(0, 0, 0, 0.2), 0 40px 77px 0 rgba(0, 0, 0, 0.22);
} }
.drag-handle {
font-size: 1.5rem;
color: $color-gray-light;
cursor: pointer;
}
.drag-indicator-dropzone {
opacity: 1 !important;
border: 2px dotted $color-border;
}
.drag-indicator-chosen {
opacity: 1 !important;
background: $color-off-white;
}
.drag-indicator-dragged {
opacity: 1 !important;
@include card();
}
@import "widget-avatar"; @import "widget-avatar";
@import "widget-button"; @import "widget-button";
@import "widget-checkbox"; @import "widget-checkbox";

View file

@ -0,0 +1,25 @@
<div class="row">
<div class="col">
<div class="view-customize">
<h1 class="admin-heading">Archive</h1>
<h2 class="sub-heading">Mark as live documents currently marked as archived</h2>
<div class="archive-admin my-5">
<ul class="list">
{{#each docs as |doc|}}
<li class="item row">
<div class="col-12 col-sm-10">{{doc.name}}</div>
<div class="col-12 col-sm-2 float-right">
<button class="btn btn-success" {{action 'onMarkLive' doc.id}}>Unarchive</button>
</div>
</li>
{{/each}}
</ul>
{{#if (eq docs.length 0)}}
<p>Nothing found</p>
{{/if}}
</div>
</div>
</div>
</div>

View file

@ -7,6 +7,9 @@
<div class="title">{{ document.name }}</div> <div class="title">{{ document.name }}</div>
<div class="snippet">{{ document.excerpt }}</div> <div class="snippet">{{ document.excerpt }}</div>
{{folder/document-tags documentTags=document.tags}} {{folder/document-tags documentTags=document.tags}}
{{#if (not-eq document.lifecycle constants.Lifecycle.Live)}}
<button type="button" class="mt-3 btn btn-warning text-uppercase font-weight-bold">{{document.lifecycleLabel}}</button>
{{/if}}
{{/link-to}} {{/link-to}}
{{#if hasDocumentActions}} {{#if hasDocumentActions}}
@ -39,11 +42,13 @@
</ul> </ul>
</div> </div>
{{#ui/ui-dialog title="Delete Documents" confirmCaption="Delete" buttonType="btn-danger" show=showDeleteDialog onAction=(action 'onDeleteDocuments')}} {{#ui/ui-dialog title="Delete Documents" confirmCaption="Delete" buttonType="btn-danger" show=showDeleteDialog onAction=(action
'onDeleteDocuments')}}
<p>Are you sure you want to delete {{selectedDocuments.length}} {{selectedCaption}}?</p> <p>Are you sure you want to delete {{selectedDocuments.length}} {{selectedCaption}}?</p>
{{/ui/ui-dialog}} {{/ui/ui-dialog}}
{{#ui/ui-dialog title="Move Documents" confirmCaption="Move" buttonType="btn-success" show=showMoveDialog onAction=(action 'onMoveDocuments')}} {{#ui/ui-dialog title="Move Documents" confirmCaption="Move" buttonType="btn-success" show=showMoveDialog onAction=(action
'onMoveDocuments')}}
<p>Select space for {{selectedDocuments.length}} {{selectedCaption}}</p> <p>Select space for {{selectedDocuments.length}} {{selectedCaption}}</p>
{{ui/ui-list-picker items=moveOptions nameField='name' singleSelect=true}} {{ui/ui-list-picker items=moveOptions nameField='name' singleSelect=true}}
{{/ui/ui-dialog}} {{/ui/ui-dialog}}

View file

@ -4,7 +4,7 @@
<div class="modal-header">Space Permissions</div> <div class="modal-header">Space Permissions</div>
<div class="modal-body" style="overflow-x: auto;"> <div class="modal-body" style="overflow-x: auto;">
<div class="space-admin table-responsive"> <div class="space-admin table-responsive">
<table class="table table-hover permission-table mb-3"> <table class="table table-hover permission-table mb-3">
<thead> <thead>
<tr> <tr>
@ -24,6 +24,8 @@
<th class="text-info">Copy</th> <th class="text-info">Copy</th>
<th class="text-info">Templates</th> <th class="text-info">Templates</th>
<th class="text-info">Approval</th> <th class="text-info">Approval</th>
<th class="text-info">Lifecycle</th>
<th class="text-info">Versions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -38,53 +40,35 @@
<small class="form-text text-muted d-inline-block">({{permission.members}})</small> <small class="form-text text-muted d-inline-block">({{permission.members}})</small>
</span> </span>
{{else}} {{else}}
{{#if (eq permission.whoId constants.EveryoneUserId)}} {{#if (eq permission.whoId constants.EveryoneUserId)}}
<span class="button-icon-green button-icon-small align-middle"> <span class="button-icon-green button-icon-small align-middle">
<i class="material-icons">language</i> <i class="material-icons">language</i>
</span> </span>
<span class="color-green">&nbsp;{{permission.name}}</span> <span class="color-green">&nbsp;{{permission.name}}</span>
{{else}} {{else}}
<span class="button-icon-gray button-icon-small align-middle"> <span class="button-icon-gray button-icon-small align-middle">
<i class="material-icons">person</i> <i class="material-icons">person</i>
</span> </span>
<span class="">&nbsp;{{permission.name}} <span class="">&nbsp;{{permission.name}}
{{#if (eq permission.whoId session.user.id)}} {{#if (eq permission.whoId session.user.id)}}
<small class="form-text text-muted d-inline-block">(you)</small> <small class="form-text text-muted d-inline-block">(you)</small>
{{/if}} {{/if}}
</span> </span>
{{/if}} {{/if}}
{{/if}} {{/if}}
</td> </td>
<td> <td>{{input type="checkbox" id=(concat 'space-role-view-' permission.whoId) checked=permission.spaceView}}</td>
{{input type="checkbox" id=(concat 'space-role-view-' permission.whoId) checked=permission.spaceView}} <td>{{input type="checkbox" id=(concat 'space-role-manage-' permission.whoId) checked=permission.spaceManage}}</td>
</td> <td>{{input type="checkbox" id=(concat 'space-role-owner-' permission.whoId) checked=permission.spaceOwner}}</td>
<td> <td>{{input type="checkbox" id=(concat 'doc-role-add-' permission.whoId) checked=permission.documentAdd}}</td>
{{input type="checkbox" id=(concat 'space-role-manage-' permission.whoId) checked=permission.spaceManage}} <td>{{input type="checkbox" id=(concat 'doc-role-edit-' permission.whoId) checked=permission.documentEdit}}</td>
</td> <td>{{input type="checkbox" id=(concat 'doc-role-delete-' permission.whoId) checked=permission.documentDelete}}</td>
<td> <td>{{input type="checkbox" id=(concat 'doc-role-move-' permission.whoId) checked=permission.documentMove}}</td>
{{input type="checkbox" id=(concat 'space-role-owner-' permission.whoId) checked=permission.spaceOwner}} <td>{{input type="checkbox" id=(concat 'doc-role-copy-' permission.whoId) checked=permission.documentCopy}}</td>
</td> <td>{{input type="checkbox" id=(concat 'doc-role-template-' permission.whoId) checked=permission.documentTemplate}}</td>
<td> <td>{{input type="checkbox" id=(concat 'doc-role-approve-' permission.whoId) checked=permission.documentApprove}}</td>
{{input type="checkbox" id=(concat 'doc-role-add-' permission.whoId) checked=permission.documentAdd}} <td>{{input type="checkbox" id=(concat 'doc-role-lifecycle-' permission.whoId) checked=permission.documentLifecycle}}</td>
</td> <td>{{input type="checkbox" id=(concat 'doc-role-version-' permission.whoId) checked=permission.documentVersion}}</td>
<td>
{{input type="checkbox" id=(concat 'doc-role-edit-' permission.whoId) checked=permission.documentEdit}}
</td>
<td>
{{input type="checkbox" id=(concat 'doc-role-delete-' permission.whoId) checked=permission.documentDelete}}
</td>
<td>
{{input type="checkbox" id=(concat 'doc-role-move-' permission.whoId) checked=permission.documentMove}}
</td>
<td>
{{input type="checkbox" id=(concat 'doc-role-copy-' permission.whoId) checked=permission.documentCopy}}
</td>
<td>
{{input type="checkbox" id=(concat 'doc-role-template-' permission.whoId) checked=permission.documentTemplate}}
</td>
<td>
{{input type="checkbox" id=(concat 'doc-role-approve-' permission.whoId) checked=permission.documentApprove}}
</td>
</tr> </tr>
{{/each}} {{/each}}
</tbody> </tbody>
@ -108,13 +92,13 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-dismiss="modal">Cancel</button> <button type="button" class="btn btn-outline-secondary" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-success" onclick={{action 'setPermissions'}}>Save</button> <button type="button" class="btn btn-success" onclick= {{action 'setPermissions'}}>Save</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,15 +1,23 @@
<div class="view-search my-5"> <div class="view-search my-5">
<div class="heading">{{resultPhrase}}</div> <div class="heading">{{resultPhrase}}</div>
<ul class="documents"> <ul class="documents">
{{#each documents key="id" as |result index|}} {{#each documents key="id" as |result index|}}
<li class="document"> <li class="document">
<a class="link" href="s/{{result.spaceId}}/{{result.spaceSlug}}/d/{{ result.documentId }}/{{result.documentSlug}}?page={{ result.itemId }}"> <a class="link" href="s/{{result.spaceId}}/{{result.spaceSlug}}/d/{{ result.documentId }}/{{result.documentSlug}}?page={{ result.itemId }}">
<div class="title">{{result.document}}</div> <div class="title">
{{result.document}}
{{#if (gt result.versionId.length 0)}}
<span class="version">&nbsp;&nbsp;{{result.versionId}}</span>
{{/if}}
</div>
<div class="space">{{result.space}}</div>
<div class="snippet">{{result.excerpt}}</div> <div class="snippet">{{result.excerpt}}</div>
<div class="snippet">({{result.space}})</div>
{{folder/document-tags documentTags=result.tags}} {{folder/document-tags documentTags=result.tags}}
</a> {{#if result.template}}
</li> <button type="button" class="mt-3 btn btn-warning text-uppercase font-weight-bold">TEMPLATE</button>
{{/each}} {{/if}}
</ul> </a>
</div> </li>
{{/each}}
</ul>
</div>

3933
gui/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{ {
"name": "documize", "name": "documize",
"version": "1.58.0", "version": "1.59.0",
"description": "The Document IDE", "description": "The Document IDE",
"private": true, "private": true,
"repository": "", "repository": "",
@ -51,6 +51,7 @@
"bootstrap": "^4.0.0", "bootstrap": "^4.0.0",
"mermaid": "^7.1.2", "mermaid": "^7.1.2",
"node-sass": "^4.7.2", "node-sass": "^4.7.2",
"npm": "^5.7.1",
"popper.js": "^1.12.9" "popper.js": "^1.12.9"
} }
} }

116
gui/vendor/sortable.js vendored
View file

@ -1,3 +1,65 @@
/**
* jQuery plugin for Sortable
* @author RubaXa <trash@rubaxa.org>
* @license MIT
*/
(function (factory) {
"use strict";
if (typeof define === "function" && define.amd) {
define(["jquery"], factory);
}
else {
/* jshint sub:true */
factory(jQuery);
}
})(function ($) {
"use strict";
/* CODE */
/**
* jQuery plugin for Sortable
* @param {Object|String} options
* @param {..*} [args]
* @returns {jQuery|*}
*/
$.fn.sortable = function (options) {
var retVal,
args = arguments;
this.each(function () {
var $el = $(this),
sortable = $el.data('sortable');
if (!sortable && (options instanceof Object || !options)) {
sortable = new Sortable(this, options);
$el.data('sortable', sortable);
}
if (sortable) {
if (options === 'widget') {
retVal = sortable;
}
else if (options === 'destroy') {
sortable.destroy();
$el.removeData('sortable');
}
else if (typeof sortable[options] === 'function') {
retVal = sortable[options].apply(sortable, [].slice.call(args, 1));
}
else if (options in sortable.options) {
retVal = sortable.option.apply(sortable, args);
}
}
});
return (retVal === void 0) ? this : retVal;
};
});
/**! /**!
* Sortable * Sortable
* @author RubaXa <trash@rubaxa.org> * @author RubaXa <trash@rubaxa.org>
@ -109,7 +171,7 @@
scrollOffsetX, scrollOffsetX,
scrollOffsetY scrollOffsetY
; ;
// Delect scrollEl // Delect scrollEl
if (scrollParentEl !== rootEl) { if (scrollParentEl !== rootEl) {
@ -160,7 +222,7 @@
scrollOffsetY = vy ? vy * speed : 0; scrollOffsetY = vy ? vy * speed : 0;
scrollOffsetX = vx ? vx * speed : 0; scrollOffsetX = vx ? vx * speed : 0;
if ('function' === typeof(scrollCustomFn)) { if ('function' === typeof (scrollCustomFn)) {
return scrollCustomFn.call(_this, scrollOffsetX, scrollOffsetY, evt); return scrollCustomFn.call(_this, scrollOffsetX, scrollOffsetY, evt);
} }
@ -202,7 +264,7 @@
var originalGroup = options.group; var originalGroup = options.group;
if (!originalGroup || typeof originalGroup != 'object') { if (!originalGroup || typeof originalGroup != 'object') {
originalGroup = {name: originalGroup}; originalGroup = { name: originalGroup };
} }
group.name = originalGroup.name; group.name = originalGroup.name;
@ -212,7 +274,7 @@
options.group = group; options.group = group;
} }
; ;
/** /**
@ -261,7 +323,7 @@
fallbackClass: 'sortable-fallback', fallbackClass: 'sortable-fallback',
fallbackOnBody: false, fallbackOnBody: false,
fallbackTolerance: 0, fallbackTolerance: 0,
fallbackOffset: {x: 0, y: 0} fallbackOffset: { x: 0, y: 0 }
}; };
@ -555,7 +617,7 @@
_onTouchMove: function (/**TouchEvent*/evt) { _onTouchMove: function (/**TouchEvent*/evt) {
if (tapEvt) { if (tapEvt) {
var options = this.options, var options = this.options,
fallbackTolerance = options.fallbackTolerance, fallbackTolerance = options.fallbackTolerance,
fallbackOffset = options.fallbackOffset, fallbackOffset = options.fallbackOffset,
touch = evt.touches ? evt.touches[0] : evt, touch = evt.touches ? evt.touches[0] : evt,
@ -781,7 +843,7 @@
halfway = (floating ? (evt.clientX - targetRect.left) / width : (evt.clientY - targetRect.top) / height) > 0.5, halfway = (floating ? (evt.clientX - targetRect.left) / width : (evt.clientY - targetRect.top) / height) > 0.5,
nextSibling = target.nextElementSibling, nextSibling = target.nextElementSibling,
after = false after = false
; ;
if (floating) { if (floating) {
var elTop = dragEl.offsetTop, var elTop = dragEl.offsetTop,
@ -795,7 +857,7 @@
} else { } else {
after = tgTop > elTop; after = tgTop > elTop;
} }
} else if (!isMovingBetweenSortable) { } else if (!isMovingBetweenSortable) {
after = (nextSibling !== dragEl) && !isLong || halfway && isLong; after = (nextSibling !== dragEl) && !isLong || halfway && isLong;
} }
@ -963,30 +1025,30 @@
this._nulling(); this._nulling();
}, },
_nulling: function() { _nulling: function () {
rootEl = rootEl =
dragEl = dragEl =
parentEl = parentEl =
ghostEl = ghostEl =
nextEl = nextEl =
cloneEl = cloneEl =
lastDownEl = lastDownEl =
scrollEl = scrollEl =
scrollParentEl = scrollParentEl =
tapEvt = tapEvt =
touchEvt = touchEvt =
moved = moved =
newIndex = newIndex =
lastEl = lastEl =
lastCSS = lastCSS =
putSortable = putSortable =
activeGroup = activeGroup =
Sortable.active = null; Sortable.active = null;
savedInputChecked.forEach(function (el) { savedInputChecked.forEach(function (el) {
el.checked = true; el.checked = true;
@ -1455,7 +1517,7 @@
}; };
} }
})); }));
} catch (err) {} } catch (err) { }
// Export utils // Export utils
Sortable.utils = { Sortable.utils = {

File diff suppressed because it is too large Load diff

View file

@ -1,16 +1,14 @@
{ {
"community": "community": {
{ "version": "1.59.0",
"version": "1.58.0",
"major": 1, "major": 1,
"minor": 58, "minor": 59,
"patch": 0 "patch": 0
}, },
"enterprise": "enterprise": {
{ "version": "1.61.0",
"version": "1.60.0",
"major": 1, "major": 1,
"minor": 60, "minor": 61,
"patch": 0 "patch": 0
} }
} }

View file

@ -95,6 +95,12 @@ const (
// TypeSentSecureLink records user sending secure document link to email address(es) // TypeSentSecureLink records user sending secure document link to email address(es)
TypeSentSecureLink Type = 12 TypeSentSecureLink Type = 12
// TypeDraft records user marking space/document as draft
TypeDraft Type = 13
// TypeVersioned records user creating new document version
TypeVersioned Type = 14
) )
// TypeName returns one-work descriptor for activity type // TypeName returns one-work descriptor for activity type

View file

@ -22,18 +22,23 @@ import (
// Document represents the purpose of Documize. // Document represents the purpose of Documize.
type Document struct { type Document struct {
model.BaseEntity model.BaseEntity
OrgID string `json:"orgId"` OrgID string `json:"orgId"`
LabelID string `json:"folderId"` LabelID string `json:"folderId"`
UserID string `json:"userId"` UserID string `json:"userId"`
Job string `json:"job"` Job string `json:"job"`
Location string `json:"location"` Location string `json:"location"`
Title string `json:"name"` Title string `json:"name"`
Excerpt string `json:"excerpt"` Excerpt string `json:"excerpt"`
Slug string `json:"-"` Slug string `json:"-"`
Tags string `json:"tags"` Tags string `json:"tags"`
Template bool `json:"template"` Template bool `json:"template"`
Protection workflow.Protection `json:"protection"` Protection workflow.Protection `json:"protection"`
Approval workflow.Approval `json:"approval"` Approval workflow.Approval `json:"approval"`
Lifecycle workflow.Lifecycle `json:"lifecycle"`
Versioned bool `json:"versioned"`
VersionID string `json:"versionId"`
VersionOrder int `json:"versionOrder"`
GroupID string `json:"groupId"`
} }
// SetDefaults ensures on blanks and cleans. // SetDefaults ensures on blanks and cleans.
@ -89,3 +94,9 @@ type SitemapDocument struct {
Folder string Folder string
Revised time.Time Revised time.Time
} }
// Version points to a version of a document.
type Version struct {
VersionID string `json:"versionId"`
DocumentID string `json:"documentId"`
}

View file

@ -93,6 +93,12 @@ const (
// DocumentApprove means you can approve a change to a document // DocumentApprove means you can approve a change to a document
DocumentApprove Action = "doc-approve" DocumentApprove Action = "doc-approve"
// DocumentLifecycle means you can move a document between DRAFT/LIVE/ARCHIVE states
DocumentLifecycle Action = "doc-lifecycle"
// DocumentVersion means you can manage document versions
DocumentVersion Action = "doc-version"
// CategoryView action means you can view a category and documents therein // CategoryView action means you can view a category and documents therein
CategoryView Action = "view" CategoryView Action = "view"
) )

View file

@ -15,21 +15,23 @@ package permission
// This data structure is made from database permission records for the space, // This data structure is made from database permission records for the space,
// and it is designed to be sent to HTTP clients (web, mobile). // and it is designed to be sent to HTTP clients (web, mobile).
type Record struct { type Record struct {
OrgID string `json:"orgId"` OrgID string `json:"orgId"`
SpaceID string `json:"folderId"` SpaceID string `json:"folderId"`
WhoID string `json:"whoId"` WhoID string `json:"whoId"`
Who WhoType `json:"who"` Who WhoType `json:"who"`
SpaceView bool `json:"spaceView"` SpaceView bool `json:"spaceView"`
SpaceManage bool `json:"spaceManage"` SpaceManage bool `json:"spaceManage"`
SpaceOwner bool `json:"spaceOwner"` SpaceOwner bool `json:"spaceOwner"`
DocumentAdd bool `json:"documentAdd"` DocumentAdd bool `json:"documentAdd"`
DocumentEdit bool `json:"documentEdit"` DocumentEdit bool `json:"documentEdit"`
DocumentDelete bool `json:"documentDelete"` DocumentDelete bool `json:"documentDelete"`
DocumentMove bool `json:"documentMove"` DocumentMove bool `json:"documentMove"`
DocumentCopy bool `json:"documentCopy"` DocumentCopy bool `json:"documentCopy"`
DocumentTemplate bool `json:"documentTemplate"` DocumentTemplate bool `json:"documentTemplate"`
DocumentApprove bool `json:"documentApprove"` DocumentApprove bool `json:"documentApprove"`
Name string `json:"name"` // read-only, user or group name DocumentLifecycle bool `json:"documentLifecycle"`
DocumentVersion bool `json:"documentVersion"`
Name string `json:"name"` // read-only, user or group name
} }
// DecodeUserPermissions returns a flat, usable permission summary record // DecodeUserPermissions returns a flat, usable permission summary record
@ -67,6 +69,10 @@ func DecodeUserPermissions(perm []Permission) (r Record) {
r.DocumentTemplate = true r.DocumentTemplate = true
case DocumentApprove: case DocumentApprove:
r.DocumentApprove = true r.DocumentApprove = true
case DocumentLifecycle:
r.DocumentLifecycle = true
case DocumentVersion:
r.DocumentVersion = true
} }
} }
@ -107,6 +113,12 @@ func EncodeUserPermissions(r Record) (perm []Permission) {
if r.DocumentApprove { if r.DocumentApprove {
perm = append(perm, EncodeRecord(r, DocumentApprove)) perm = append(perm, EncodeRecord(r, DocumentApprove))
} }
if r.DocumentVersion {
perm = append(perm, EncodeRecord(r, DocumentVersion))
}
if r.DocumentLifecycle {
perm = append(perm, EncodeRecord(r, DocumentLifecycle))
}
return return
} }
@ -114,7 +126,8 @@ func EncodeUserPermissions(r Record) (perm []Permission) {
// HasAnyPermission returns true if user has at least one permission. // HasAnyPermission returns true if user has at least one permission.
func HasAnyPermission(p Record) bool { func HasAnyPermission(p Record) bool {
return p.SpaceView || p.SpaceManage || p.SpaceOwner || p.DocumentAdd || p.DocumentEdit || return p.SpaceView || p.SpaceManage || p.SpaceOwner || p.DocumentAdd || p.DocumentEdit ||
p.DocumentDelete || p.DocumentMove || p.DocumentCopy || p.DocumentTemplate || p.DocumentApprove p.DocumentDelete || p.DocumentMove || p.DocumentCopy || p.DocumentTemplate || p.DocumentApprove ||
p.DocumentLifecycle || p.DocumentVersion
} }
// EncodeRecord creates standard permission record representing user permissions for a space. // EncodeRecord creates standard permission record representing user permissions for a space.
@ -138,7 +151,6 @@ type CategoryViewRequestModel struct {
CategoryID string `json:"categoryID"` CategoryID string `json:"categoryID"`
WhoID string `json:"whoId"` WhoID string `json:"whoId"`
Who WhoType `json:"who"` Who WhoType `json:"who"`
// UserID string `json:"userId"`
} }
// SpaceRequestModel details which users have what permissions on a given space. // SpaceRequestModel details which users have what permissions on a given space.

View file

@ -34,4 +34,6 @@ type QueryResult struct {
SpaceID string `json:"spaceId"` SpaceID string `json:"spaceId"`
Space string `json:"space"` Space string `json:"space"`
SpaceSlug string `json:"spaceSlug"` SpaceSlug string `json:"spaceSlug"`
Template bool `json:"template"`
VersionID string `json:"versionId"`
} }

View file

@ -63,3 +63,17 @@ const (
// ChangePendingNew means a new section to a document is pending review // ChangePendingNew means a new section to a document is pending review
ChangePendingNew ChangeStatus = 4 ChangePendingNew ChangeStatus = 4
) )
// Lifecycle tells us if document is in Draft, Live, Archived
type Lifecycle int
const (
// LifecycleDraft means document is in draft mode with restricted viewing
LifecycleDraft Lifecycle = 0
// LifecycleLive means document can be seen by all
LifecycleLive Lifecycle = 1
// LifecycleArchived means document has been archived
LifecycleArchived Lifecycle = 2
)

View file

@ -13,8 +13,10 @@ package server
import ( import (
"context" "context"
"database/sql"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt"
"net/http" "net/http"
"strings" "strings"
@ -77,9 +79,10 @@ func (m *middleware) Authorize(w http.ResponseWriter, r *http.Request, next http
var org = org.Organization{} var org = org.Organization{}
var err = errors.New("") var err = errors.New("")
var dom string
if len(rc.OrgID) == 0 { if len(rc.OrgID) == 0 {
dom := organization.GetRequestSubdomain(r) dom = organization.GetRequestSubdomain(r)
dom = m.Store.Organization.CheckDomain(rc, dom) dom = m.Store.Organization.CheckDomain(rc, dom)
org, err = m.Store.Organization.GetOrganizationByDomain(dom) org, err = m.Store.Organization.GetOrganizationByDomain(dom)
} else { } else {
@ -88,6 +91,12 @@ func (m *middleware) Authorize(w http.ResponseWriter, r *http.Request, next http
// Inability to find org record spells the end of this request. // Inability to find org record spells the end of this request.
if err != nil { if err != nil {
if err == sql.ErrNoRows {
response.WriteForbiddenError(w)
m.Runtime.Log.Info(fmt.Sprintf("unable to find org (domain: %s, orgID: %s)", dom, rc.OrgID))
return
}
response.WriteForbiddenError(w) response.WriteForbiddenError(w)
m.Runtime.Log.Error(method, err) m.Runtime.Log.Error(method, err)
return return

View file

@ -88,6 +88,16 @@ func Add(rt *env.Runtime, prefix, path string, methods, queries []string, endPtF
return nil return nil
} }
// AddPrivate endpoint
func AddPrivate(rt *env.Runtime, path string, methods, queries []string, endPtFn RouteFunc) error {
return Add(rt, RoutePrefixPrivate, path, methods, queries, endPtFn)
}
// AddPublic endpoint
func AddPublic(rt *env.Runtime, path string, methods, queries []string, endPtFn RouteFunc) error {
return Add(rt, RoutePrefixPublic, path, methods, queries, endPtFn)
}
// Remove an endpoint. // Remove an endpoint.
func Remove(rt *env.Runtime, prefix, path string, methods, queries []string) error { func Remove(rt *env.Runtime, prefix, path string, methods, queries []string) error {
k, e := routesKey(rt, prefix, path, methods, queries) k, e := routesKey(rt, prefix, path, methods, queries)

View file

@ -70,8 +70,8 @@ func RegisterEndpoints(rt *env.Runtime, s *domain.Store) {
// Non-secure public info routes // Non-secure public info routes
//************************************************** //**************************************************
Add(rt, RoutePrefixPublic, "meta", []string{"GET", "OPTIONS"}, nil, meta.Meta) AddPublic(rt, "meta", []string{"GET", "OPTIONS"}, nil, meta.Meta)
Add(rt, RoutePrefixPublic, "version", []string{"GET", "OPTIONS"}, nil, func(w http.ResponseWriter, r *http.Request) { AddPublic(rt, "version", []string{"GET", "OPTIONS"}, nil, func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(rt.Product.Version)) w.Write([]byte(rt.Product.Version))
}) })
@ -79,129 +79,129 @@ func RegisterEndpoints(rt *env.Runtime, s *domain.Store) {
// Non-secure public service routes // Non-secure public service routes
//************************************************** //**************************************************
Add(rt, RoutePrefixPublic, "authenticate/keycloak", []string{"POST", "OPTIONS"}, nil, keycloak.Authenticate) AddPublic(rt, "authenticate/keycloak", []string{"POST", "OPTIONS"}, nil, keycloak.Authenticate)
Add(rt, RoutePrefixPublic, "authenticate", []string{"POST", "OPTIONS"}, nil, auth.Login) AddPublic(rt, "authenticate", []string{"POST", "OPTIONS"}, nil, auth.Login)
Add(rt, RoutePrefixPublic, "validate", []string{"GET", "OPTIONS"}, nil, auth.ValidateToken) AddPublic(rt, "validate", []string{"GET", "OPTIONS"}, nil, auth.ValidateToken)
Add(rt, RoutePrefixPublic, "forgot", []string{"POST", "OPTIONS"}, nil, user.ForgotPassword) AddPublic(rt, "forgot", []string{"POST", "OPTIONS"}, nil, user.ForgotPassword)
Add(rt, RoutePrefixPublic, "reset/{token}", []string{"POST", "OPTIONS"}, nil, user.ResetPassword) AddPublic(rt, "reset/{token}", []string{"POST", "OPTIONS"}, nil, user.ResetPassword)
Add(rt, RoutePrefixPublic, "share/{spaceID}", []string{"POST", "OPTIONS"}, nil, space.AcceptInvitation) AddPublic(rt, "share/{spaceID}", []string{"POST", "OPTIONS"}, nil, space.AcceptInvitation)
Add(rt, RoutePrefixPublic, "attachments/{orgID}/{attachmentID}", []string{"GET", "OPTIONS"}, nil, attachment.Download) AddPublic(rt, "attachments/{orgID}/{attachmentID}", []string{"GET", "OPTIONS"}, nil, attachment.Download)
//************************************************** //**************************************************
// Secured private routes (require authentication) // Secured private routes (require authentication)
//************************************************** //**************************************************
Add(rt, RoutePrefixPrivate, "import/folder/{folderID}", []string{"POST", "OPTIONS"}, nil, conversion.UploadConvert) AddPrivate(rt, "import/folder/{folderID}", []string{"POST", "OPTIONS"}, nil, conversion.UploadConvert)
Add(rt, RoutePrefixPrivate, "documents", []string{"GET", "OPTIONS"}, nil, document.BySpace) AddPrivate(rt, "documents", []string{"GET", "OPTIONS"}, nil, document.BySpace)
Add(rt, RoutePrefixPrivate, "documents/{documentID}", []string{"GET", "OPTIONS"}, nil, document.Get) AddPrivate(rt, "documents/{documentID}", []string{"GET", "OPTIONS"}, nil, document.Get)
Add(rt, RoutePrefixPrivate, "documents/{documentID}", []string{"PUT", "OPTIONS"}, nil, document.Update) AddPrivate(rt, "documents/{documentID}", []string{"PUT", "OPTIONS"}, nil, document.Update)
Add(rt, RoutePrefixPrivate, "documents/{documentID}", []string{"DELETE", "OPTIONS"}, nil, document.Delete) AddPrivate(rt, "documents/{documentID}", []string{"DELETE", "OPTIONS"}, nil, document.Delete)
Add(rt, RoutePrefixPrivate, "documents/{documentID}/pages/level", []string{"POST", "OPTIONS"}, nil, page.ChangePageLevel) AddPrivate(rt, "documents/{documentID}/pages/level", []string{"POST", "OPTIONS"}, nil, page.ChangePageLevel)
Add(rt, RoutePrefixPrivate, "documents/{documentID}/pages/sequence", []string{"POST", "OPTIONS"}, nil, page.ChangePageSequence) AddPrivate(rt, "documents/{documentID}/pages/sequence", []string{"POST", "OPTIONS"}, nil, page.ChangePageSequence)
Add(rt, RoutePrefixPrivate, "documents/{documentID}/pages/{pageID}/revisions", []string{"GET", "OPTIONS"}, nil, page.GetRevisions) AddPrivate(rt, "documents/{documentID}/pages/{pageID}/revisions", []string{"GET", "OPTIONS"}, nil, page.GetRevisions)
Add(rt, RoutePrefixPrivate, "documents/{documentID}/pages/{pageID}/revisions/{revisionID}", []string{"GET", "OPTIONS"}, nil, page.GetDiff) AddPrivate(rt, "documents/{documentID}/pages/{pageID}/revisions/{revisionID}", []string{"GET", "OPTIONS"}, nil, page.GetDiff)
Add(rt, RoutePrefixPrivate, "documents/{documentID}/pages/{pageID}/revisions/{revisionID}", []string{"POST", "OPTIONS"}, nil, page.Rollback) AddPrivate(rt, "documents/{documentID}/pages/{pageID}/revisions/{revisionID}", []string{"POST", "OPTIONS"}, nil, page.Rollback)
Add(rt, RoutePrefixPrivate, "documents/{documentID}/revisions", []string{"GET", "OPTIONS"}, nil, page.GetDocumentRevisions) AddPrivate(rt, "documents/{documentID}/revisions", []string{"GET", "OPTIONS"}, nil, page.GetDocumentRevisions)
Add(rt, RoutePrefixPrivate, "documents/{documentID}/pages", []string{"GET", "OPTIONS"}, nil, page.GetPages) AddPrivate(rt, "documents/{documentID}/pages", []string{"GET", "OPTIONS"}, nil, page.GetPages)
Add(rt, RoutePrefixPrivate, "documents/{documentID}/pages/{pageID}", []string{"PUT", "OPTIONS"}, nil, page.Update) AddPrivate(rt, "documents/{documentID}/pages/{pageID}", []string{"PUT", "OPTIONS"}, nil, page.Update)
Add(rt, RoutePrefixPrivate, "documents/{documentID}/pages/{pageID}", []string{"DELETE", "OPTIONS"}, nil, page.Delete) AddPrivate(rt, "documents/{documentID}/pages/{pageID}", []string{"DELETE", "OPTIONS"}, nil, page.Delete)
Add(rt, RoutePrefixPrivate, "documents/{documentID}/pages", []string{"DELETE", "OPTIONS"}, nil, page.DeletePages) AddPrivate(rt, "documents/{documentID}/pages", []string{"DELETE", "OPTIONS"}, nil, page.DeletePages)
Add(rt, RoutePrefixPrivate, "documents/{documentID}/pages/{pageID}", []string{"GET", "OPTIONS"}, nil, page.GetPage) AddPrivate(rt, "documents/{documentID}/pages/{pageID}", []string{"GET", "OPTIONS"}, nil, page.GetPage)
Add(rt, RoutePrefixPrivate, "documents/{documentID}/pages", []string{"POST", "OPTIONS"}, nil, page.Add) AddPrivate(rt, "documents/{documentID}/pages", []string{"POST", "OPTIONS"}, nil, page.Add)
Add(rt, RoutePrefixPrivate, "documents/{documentID}/attachments", []string{"GET", "OPTIONS"}, nil, attachment.Get) AddPrivate(rt, "documents/{documentID}/attachments", []string{"GET", "OPTIONS"}, nil, attachment.Get)
Add(rt, RoutePrefixPrivate, "documents/{documentID}/attachments/{attachmentID}", []string{"DELETE", "OPTIONS"}, nil, attachment.Delete) AddPrivate(rt, "documents/{documentID}/attachments/{attachmentID}", []string{"DELETE", "OPTIONS"}, nil, attachment.Delete)
Add(rt, RoutePrefixPrivate, "documents/{documentID}/attachments", []string{"POST", "OPTIONS"}, nil, attachment.Add) AddPrivate(rt, "documents/{documentID}/attachments", []string{"POST", "OPTIONS"}, nil, attachment.Add)
Add(rt, RoutePrefixPrivate, "documents/{documentID}/pages/{pageID}/meta", []string{"GET", "OPTIONS"}, nil, page.GetMeta) AddPrivate(rt, "documents/{documentID}/pages/{pageID}/meta", []string{"GET", "OPTIONS"}, nil, page.GetMeta)
Add(rt, RoutePrefixPrivate, "documents/{documentID}/pages/{pageID}/copy/{targetID}", []string{"POST", "OPTIONS"}, nil, page.Copy) AddPrivate(rt, "documents/{documentID}/pages/{pageID}/copy/{targetID}", []string{"POST", "OPTIONS"}, nil, page.Copy)
Add(rt, RoutePrefixPrivate, "organizations/{orgID}", []string{"GET", "OPTIONS"}, nil, organization.Get) AddPrivate(rt, "organizations/{orgID}", []string{"GET", "OPTIONS"}, nil, organization.Get)
Add(rt, RoutePrefixPrivate, "organizations/{orgID}", []string{"PUT", "OPTIONS"}, nil, organization.Update) AddPrivate(rt, "organizations/{orgID}", []string{"PUT", "OPTIONS"}, nil, organization.Update)
Add(rt, RoutePrefixPrivate, "space/{spaceID}", []string{"DELETE", "OPTIONS"}, nil, space.Delete) AddPrivate(rt, "space/{spaceID}", []string{"DELETE", "OPTIONS"}, nil, space.Delete)
Add(rt, RoutePrefixPrivate, "space/{spaceID}/move/{moveToId}", []string{"DELETE", "OPTIONS"}, nil, space.Remove) AddPrivate(rt, "space/{spaceID}/move/{moveToId}", []string{"DELETE", "OPTIONS"}, nil, space.Remove)
Add(rt, RoutePrefixPrivate, "space/{spaceID}/invitation", []string{"POST", "OPTIONS"}, nil, space.Invite) AddPrivate(rt, "space/{spaceID}/invitation", []string{"POST", "OPTIONS"}, nil, space.Invite)
Add(rt, RoutePrefixPrivate, "space/manage", []string{"GET", "OPTIONS"}, nil, space.GetAll) AddPrivate(rt, "space/manage", []string{"GET", "OPTIONS"}, nil, space.GetAll)
Add(rt, RoutePrefixPrivate, "space/{spaceID}", []string{"GET", "OPTIONS"}, nil, space.Get) AddPrivate(rt, "space/{spaceID}", []string{"GET", "OPTIONS"}, nil, space.Get)
Add(rt, RoutePrefixPrivate, "space", []string{"GET", "OPTIONS"}, nil, space.GetViewable) AddPrivate(rt, "space", []string{"GET", "OPTIONS"}, nil, space.GetViewable)
Add(rt, RoutePrefixPrivate, "space/{spaceID}", []string{"PUT", "OPTIONS"}, nil, space.Update) AddPrivate(rt, "space/{spaceID}", []string{"PUT", "OPTIONS"}, nil, space.Update)
Add(rt, RoutePrefixPrivate, "space", []string{"POST", "OPTIONS"}, nil, space.Add) AddPrivate(rt, "space", []string{"POST", "OPTIONS"}, nil, space.Add)
Add(rt, RoutePrefixPrivate, "category/space/{spaceID}/summary", []string{"GET", "OPTIONS"}, nil, category.GetSummary) AddPrivate(rt, "category/space/{spaceID}/summary", []string{"GET", "OPTIONS"}, nil, category.GetSummary)
Add(rt, RoutePrefixPrivate, "category/document/{documentID}", []string{"GET", "OPTIONS"}, nil, category.GetDocumentCategoryMembership) AddPrivate(rt, "category/document/{documentID}", []string{"GET", "OPTIONS"}, nil, category.GetDocumentCategoryMembership)
Add(rt, RoutePrefixPrivate, "category/space/{spaceID}", []string{"GET", "OPTIONS"}, []string{"filter", "all"}, category.GetAll) AddPrivate(rt, "category/space/{spaceID}", []string{"GET", "OPTIONS"}, []string{"filter", "all"}, category.GetAll)
Add(rt, RoutePrefixPrivate, "category/space/{spaceID}", []string{"GET", "OPTIONS"}, nil, category.Get) AddPrivate(rt, "category/space/{spaceID}", []string{"GET", "OPTIONS"}, nil, category.Get)
Add(rt, RoutePrefixPrivate, "category/member/space/{spaceID}", []string{"GET", "OPTIONS"}, nil, category.GetSpaceCategoryMembers) AddPrivate(rt, "category/member/space/{spaceID}", []string{"GET", "OPTIONS"}, nil, category.GetSpaceCategoryMembers)
Add(rt, RoutePrefixPrivate, "category/member", []string{"POST", "OPTIONS"}, nil, category.SetDocumentCategoryMembership) AddPrivate(rt, "category/member", []string{"POST", "OPTIONS"}, nil, category.SetDocumentCategoryMembership)
Add(rt, RoutePrefixPrivate, "category/{categoryID}", []string{"PUT", "OPTIONS"}, nil, category.Update) AddPrivate(rt, "category/{categoryID}", []string{"PUT", "OPTIONS"}, nil, category.Update)
Add(rt, RoutePrefixPrivate, "category/{categoryID}", []string{"DELETE", "OPTIONS"}, nil, category.Delete) AddPrivate(rt, "category/{categoryID}", []string{"DELETE", "OPTIONS"}, nil, category.Delete)
Add(rt, RoutePrefixPrivate, "category", []string{"POST", "OPTIONS"}, nil, category.Add) AddPrivate(rt, "category", []string{"POST", "OPTIONS"}, nil, category.Add)
Add(rt, RoutePrefixPrivate, "users/{userID}/password", []string{"POST", "OPTIONS"}, nil, user.ChangePassword) AddPrivate(rt, "users/{userID}/password", []string{"POST", "OPTIONS"}, nil, user.ChangePassword)
Add(rt, RoutePrefixPrivate, "users", []string{"POST", "OPTIONS"}, nil, user.Add) AddPrivate(rt, "users", []string{"POST", "OPTIONS"}, nil, user.Add)
Add(rt, RoutePrefixPrivate, "users/space/{spaceID}", []string{"GET", "OPTIONS"}, nil, user.GetSpaceUsers) AddPrivate(rt, "users/space/{spaceID}", []string{"GET", "OPTIONS"}, nil, user.GetSpaceUsers)
Add(rt, RoutePrefixPrivate, "users", []string{"GET", "OPTIONS"}, nil, user.GetOrganizationUsers) AddPrivate(rt, "users", []string{"GET", "OPTIONS"}, nil, user.GetOrganizationUsers)
Add(rt, RoutePrefixPrivate, "users/{userID}", []string{"GET", "OPTIONS"}, nil, user.Get) AddPrivate(rt, "users/{userID}", []string{"GET", "OPTIONS"}, nil, user.Get)
Add(rt, RoutePrefixPrivate, "users/{userID}", []string{"PUT", "OPTIONS"}, nil, user.Update) AddPrivate(rt, "users/{userID}", []string{"PUT", "OPTIONS"}, nil, user.Update)
Add(rt, RoutePrefixPrivate, "users/{userID}", []string{"DELETE", "OPTIONS"}, nil, user.Delete) AddPrivate(rt, "users/{userID}", []string{"DELETE", "OPTIONS"}, nil, user.Delete)
Add(rt, RoutePrefixPrivate, "users/sync", []string{"GET", "OPTIONS"}, nil, keycloak.Sync) AddPrivate(rt, "users/sync", []string{"GET", "OPTIONS"}, nil, keycloak.Sync)
Add(rt, RoutePrefixPrivate, "users/match", []string{"POST", "OPTIONS"}, nil, user.MatchUsers) AddPrivate(rt, "users/match", []string{"POST", "OPTIONS"}, nil, user.MatchUsers)
Add(rt, RoutePrefixPrivate, "users/import", []string{"POST", "OPTIONS"}, nil, user.BulkImport) AddPrivate(rt, "users/import", []string{"POST", "OPTIONS"}, nil, user.BulkImport)
Add(rt, RoutePrefixPrivate, "search", []string{"POST", "OPTIONS"}, nil, document.SearchDocuments) AddPrivate(rt, "search", []string{"POST", "OPTIONS"}, nil, document.SearchDocuments)
Add(rt, RoutePrefixPrivate, "templates", []string{"POST", "OPTIONS"}, nil, template.SaveAs) AddPrivate(rt, "templates", []string{"POST", "OPTIONS"}, nil, template.SaveAs)
Add(rt, RoutePrefixPrivate, "templates/{templateID}/folder/{folderID}", []string{"POST", "OPTIONS"}, []string{"type", "saved"}, template.Use) AddPrivate(rt, "templates/{templateID}/folder/{folderID}", []string{"POST", "OPTIONS"}, []string{"type", "saved"}, template.Use)
Add(rt, RoutePrefixPrivate, "templates/{folderID}", []string{"GET", "OPTIONS"}, nil, template.SavedList) AddPrivate(rt, "templates/{folderID}", []string{"GET", "OPTIONS"}, nil, template.SavedList)
Add(rt, RoutePrefixPrivate, "sections", []string{"GET", "OPTIONS"}, nil, section.GetSections) AddPrivate(rt, "sections", []string{"GET", "OPTIONS"}, nil, section.GetSections)
Add(rt, RoutePrefixPrivate, "sections", []string{"POST", "OPTIONS"}, nil, section.RunSectionCommand) AddPrivate(rt, "sections", []string{"POST", "OPTIONS"}, nil, section.RunSectionCommand)
Add(rt, RoutePrefixPrivate, "sections/refresh", []string{"GET", "OPTIONS"}, nil, section.RefreshSections) AddPrivate(rt, "sections/refresh", []string{"GET", "OPTIONS"}, nil, section.RefreshSections)
Add(rt, RoutePrefixPrivate, "sections/blocks/space/{folderID}", []string{"GET", "OPTIONS"}, nil, block.GetBySpace) AddPrivate(rt, "sections/blocks/space/{folderID}", []string{"GET", "OPTIONS"}, nil, block.GetBySpace)
Add(rt, RoutePrefixPrivate, "sections/blocks/{blockID}", []string{"GET", "OPTIONS"}, nil, block.Get) AddPrivate(rt, "sections/blocks/{blockID}", []string{"GET", "OPTIONS"}, nil, block.Get)
Add(rt, RoutePrefixPrivate, "sections/blocks/{blockID}", []string{"PUT", "OPTIONS"}, nil, block.Update) AddPrivate(rt, "sections/blocks/{blockID}", []string{"PUT", "OPTIONS"}, nil, block.Update)
Add(rt, RoutePrefixPrivate, "sections/blocks/{blockID}", []string{"DELETE", "OPTIONS"}, nil, block.Delete) AddPrivate(rt, "sections/blocks/{blockID}", []string{"DELETE", "OPTIONS"}, nil, block.Delete)
Add(rt, RoutePrefixPrivate, "sections/blocks", []string{"POST", "OPTIONS"}, nil, block.Add) AddPrivate(rt, "sections/blocks", []string{"POST", "OPTIONS"}, nil, block.Add)
Add(rt, RoutePrefixPrivate, "links/{folderID}/{documentID}/{pageID}", []string{"GET", "OPTIONS"}, nil, link.GetLinkCandidates) AddPrivate(rt, "links/{folderID}/{documentID}/{pageID}", []string{"GET", "OPTIONS"}, nil, link.GetLinkCandidates)
Add(rt, RoutePrefixPrivate, "links", []string{"GET", "OPTIONS"}, nil, link.SearchLinkCandidates) AddPrivate(rt, "links", []string{"GET", "OPTIONS"}, nil, link.SearchLinkCandidates)
Add(rt, RoutePrefixPrivate, "documents/{documentID}/links", []string{"GET", "OPTIONS"}, nil, document.DocumentLinks) AddPrivate(rt, "documents/{documentID}/links", []string{"GET", "OPTIONS"}, nil, document.DocumentLinks)
Add(rt, RoutePrefixPrivate, "global/smtp", []string{"GET", "OPTIONS"}, nil, setting.SMTP) AddPrivate(rt, "global/smtp", []string{"GET", "OPTIONS"}, nil, setting.SMTP)
Add(rt, RoutePrefixPrivate, "global/smtp", []string{"PUT", "OPTIONS"}, nil, setting.SetSMTP) AddPrivate(rt, "global/smtp", []string{"PUT", "OPTIONS"}, nil, setting.SetSMTP)
Add(rt, RoutePrefixPrivate, "global/license", []string{"GET", "OPTIONS"}, nil, setting.License) AddPrivate(rt, "global/license", []string{"GET", "OPTIONS"}, nil, setting.License)
Add(rt, RoutePrefixPrivate, "global/license", []string{"PUT", "OPTIONS"}, nil, setting.SetLicense) AddPrivate(rt, "global/license", []string{"PUT", "OPTIONS"}, nil, setting.SetLicense)
Add(rt, RoutePrefixPrivate, "global/auth", []string{"GET", "OPTIONS"}, nil, setting.AuthConfig) AddPrivate(rt, "global/auth", []string{"GET", "OPTIONS"}, nil, setting.AuthConfig)
Add(rt, RoutePrefixPrivate, "global/auth", []string{"PUT", "OPTIONS"}, nil, setting.SetAuthConfig) AddPrivate(rt, "global/auth", []string{"PUT", "OPTIONS"}, nil, setting.SetAuthConfig)
Add(rt, RoutePrefixPrivate, "pin/{userID}", []string{"POST", "OPTIONS"}, nil, pin.Add) AddPrivate(rt, "pin/{userID}", []string{"POST", "OPTIONS"}, nil, pin.Add)
Add(rt, RoutePrefixPrivate, "pin/{userID}", []string{"GET", "OPTIONS"}, nil, pin.GetUserPins) AddPrivate(rt, "pin/{userID}", []string{"GET", "OPTIONS"}, nil, pin.GetUserPins)
Add(rt, RoutePrefixPrivate, "pin/{userID}/sequence", []string{"POST", "OPTIONS"}, nil, pin.UpdatePinSequence) AddPrivate(rt, "pin/{userID}/sequence", []string{"POST", "OPTIONS"}, nil, pin.UpdatePinSequence)
Add(rt, RoutePrefixPrivate, "pin/{userID}/{pinID}", []string{"DELETE", "OPTIONS"}, nil, pin.DeleteUserPin) AddPrivate(rt, "pin/{userID}/{pinID}", []string{"DELETE", "OPTIONS"}, nil, pin.DeleteUserPin)
Add(rt, RoutePrefixPrivate, "group/{groupID}/members", []string{"GET", "OPTIONS"}, nil, group.GetGroupMembers) AddPrivate(rt, "group/{groupID}/members", []string{"GET", "OPTIONS"}, nil, group.GetGroupMembers)
Add(rt, RoutePrefixPrivate, "group", []string{"POST", "OPTIONS"}, nil, group.Add) AddPrivate(rt, "group", []string{"POST", "OPTIONS"}, nil, group.Add)
Add(rt, RoutePrefixPrivate, "group", []string{"GET", "OPTIONS"}, nil, group.Groups) AddPrivate(rt, "group", []string{"GET", "OPTIONS"}, nil, group.Groups)
Add(rt, RoutePrefixPrivate, "group/{groupID}", []string{"PUT", "OPTIONS"}, nil, group.Update) AddPrivate(rt, "group/{groupID}", []string{"PUT", "OPTIONS"}, nil, group.Update)
Add(rt, RoutePrefixPrivate, "group/{groupID}", []string{"DELETE", "OPTIONS"}, nil, group.Delete) AddPrivate(rt, "group/{groupID}", []string{"DELETE", "OPTIONS"}, nil, group.Delete)
Add(rt, RoutePrefixPrivate, "group/{groupID}/join/{userID}", []string{"POST", "OPTIONS"}, nil, group.JoinGroup) AddPrivate(rt, "group/{groupID}/join/{userID}", []string{"POST", "OPTIONS"}, nil, group.JoinGroup)
Add(rt, RoutePrefixPrivate, "group/{groupID}/leave/{userID}", []string{"DELETE", "OPTIONS"}, nil, group.LeaveGroup) AddPrivate(rt, "group/{groupID}/leave/{userID}", []string{"DELETE", "OPTIONS"}, nil, group.LeaveGroup)
Add(rt, RoutePrefixPrivate, "documents/{documentID}/permissions", []string{"GET", "OPTIONS"}, nil, permission.GetDocumentPermissions) AddPrivate(rt, "documents/{documentID}/permissions", []string{"GET", "OPTIONS"}, nil, permission.GetDocumentPermissions)
Add(rt, RoutePrefixPrivate, "documents/{documentID}/permissions", []string{"PUT", "OPTIONS"}, nil, permission.SetDocumentPermissions) AddPrivate(rt, "documents/{documentID}/permissions", []string{"PUT", "OPTIONS"}, nil, permission.SetDocumentPermissions)
Add(rt, RoutePrefixPrivate, "documents/{documentID}/permissions/user", []string{"GET", "OPTIONS"}, nil, permission.GetUserDocumentPermissions) AddPrivate(rt, "documents/{documentID}/permissions/user", []string{"GET", "OPTIONS"}, nil, permission.GetUserDocumentPermissions)
Add(rt, RoutePrefixPrivate, "space/{spaceID}/permissions", []string{"PUT", "OPTIONS"}, nil, permission.SetSpacePermissions) AddPrivate(rt, "space/{spaceID}/permissions", []string{"PUT", "OPTIONS"}, nil, permission.SetSpacePermissions)
Add(rt, RoutePrefixPrivate, "space/{spaceID}/permissions/user", []string{"GET", "OPTIONS"}, nil, permission.GetUserSpacePermissions) AddPrivate(rt, "space/{spaceID}/permissions/user", []string{"GET", "OPTIONS"}, nil, permission.GetUserSpacePermissions)
Add(rt, RoutePrefixPrivate, "space/{spaceID}/permissions", []string{"GET", "OPTIONS"}, nil, permission.GetSpacePermissions) AddPrivate(rt, "space/{spaceID}/permissions", []string{"GET", "OPTIONS"}, nil, permission.GetSpacePermissions)
Add(rt, RoutePrefixPrivate, "category/{categoryID}/permission", []string{"PUT", "OPTIONS"}, nil, permission.SetCategoryPermissions) AddPrivate(rt, "category/{categoryID}/permission", []string{"PUT", "OPTIONS"}, nil, permission.SetCategoryPermissions)
Add(rt, RoutePrefixPrivate, "category/{categoryID}/permission", []string{"GET", "OPTIONS"}, nil, permission.GetCategoryPermissions) AddPrivate(rt, "category/{categoryID}/permission", []string{"GET", "OPTIONS"}, nil, permission.GetCategoryPermissions)
Add(rt, RoutePrefixPrivate, "category/{categoryID}/user", []string{"GET", "OPTIONS"}, nil, permission.GetCategoryViewers) AddPrivate(rt, "category/{categoryID}/user", []string{"GET", "OPTIONS"}, nil, permission.GetCategoryViewers)
// fetch methods exist to speed up UI rendering by returning data in bulk // fetch methods exist to speed up UI rendering by returning data in bulk
Add(rt, RoutePrefixPrivate, "fetch/category/space/{spaceID}", []string{"GET", "OPTIONS"}, nil, category.FetchSpaceData) AddPrivate(rt, "fetch/category/space/{spaceID}", []string{"GET", "OPTIONS"}, nil, category.FetchSpaceData)
Add(rt, RoutePrefixPrivate, "fetch/document/{documentID}", []string{"GET", "OPTIONS"}, nil, document.FetchDocumentData) AddPrivate(rt, "fetch/document/{documentID}", []string{"GET", "OPTIONS"}, nil, document.FetchDocumentData)
Add(rt, RoutePrefixPrivate, "fetch/page/{documentID}", []string{"GET", "OPTIONS"}, nil, page.FetchPages) AddPrivate(rt, "fetch/page/{documentID}", []string{"GET", "OPTIONS"}, nil, page.FetchPages)
Add(rt, RoutePrefixRoot, "robots.txt", []string{"GET", "OPTIONS"}, nil, meta.RobotsTxt) 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, "sitemap.xml", []string{"GET", "OPTIONS"}, nil, meta.Sitemap)