mirror of
https://github.com/documize/community.git
synced 2025-07-21 14:19:43 +02:00
WIP document versioning
This commit is contained in:
parent
ba52dfa11d
commit
089457f16e
6 changed files with 222 additions and 67 deletions
147
domain/document/document.go
Normal file
147
domain/document/document.go
Normal file
|
@ -0,0 +1,147 @@
|
|||
// 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
|
||||
}
|
|
@ -153,45 +153,10 @@ func (h *Handler) BySpace(w http.ResponseWriter, r *http.Request) {
|
|||
// sort by title
|
||||
sort.Sort(doc.ByTitle(documents))
|
||||
|
||||
// remove documents that cannot be seen due to lack of
|
||||
// category view/access permission
|
||||
filtered := []doc.Document{}
|
||||
// remove documents that cannot be seen due to lack of category view/access permission
|
||||
cats, err := h.Store.Category.GetBySpace(ctx, spaceID)
|
||||
members, err := h.Store.Category.GetSpaceCategoryMembership(ctx, spaceID)
|
||||
|
||||
for _, doc := range documents {
|
||||
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)
|
||||
}
|
||||
}
|
||||
filtered := FilterCategoryProtected(documents, cats, members, viewDrafts)
|
||||
|
||||
response.WriteJSON(w, filtered)
|
||||
}
|
||||
|
@ -259,6 +224,24 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
|
|||
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()
|
||||
|
||||
h.Store.Audit.Record(ctx, audit.EventTypeDocumentUpdate)
|
||||
|
|
|
@ -175,7 +175,7 @@ func CanViewDrafts(ctx domain.RequestContext, s domain.Store, spaceID string) bo
|
|||
return false
|
||||
}
|
||||
for _, role := range roles {
|
||||
if role.RefID == spaceID && role.Location == pm.LocationSpace && role.Scope == pm.ScopeRow &&
|
||||
if role.OrgID == ctx.OrgID && role.RefID == spaceID && role.Location == pm.LocationSpace && role.Scope == pm.ScopeRow &&
|
||||
pm.ContainsPermission(role.Action, pm.DocumentLifecycle) {
|
||||
return true
|
||||
}
|
||||
|
@ -184,6 +184,25 @@ func CanViewDrafts(ctx domain.RequestContext, s domain.Store, spaceID string) bo
|
|||
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.
|
||||
func HasPermission(ctx domain.RequestContext, s domain.Store, spaceID string, actions ...pm.Action) bool {
|
||||
roles, err := s.Permission.GetUserSpacePermissions(ctx, spaceID)
|
||||
|
|
|
@ -28,9 +28,9 @@ export default Model.extend({
|
|||
approval: attr('number', { defaultValue: 0 }),
|
||||
lifecycle: attr('number', { defaultValue: 1 }),
|
||||
versioned: attr('boolean'),
|
||||
versionID: attr('string'),
|
||||
versionId: attr('string'),
|
||||
versionOrder: attr('number', { defaultValue: 0 }),
|
||||
groupID: attr('string'),
|
||||
groupId: attr('string'),
|
||||
|
||||
// client-side property
|
||||
selected: attr('boolean', { defaultValue: false }),
|
||||
|
|
13
gui/vendor/sortable.js
vendored
13
gui/vendor/sortable.js
vendored
|
@ -160,7 +160,7 @@
|
|||
scrollOffsetY = vy ? vy * speed : 0;
|
||||
scrollOffsetX = vx ? vx * speed : 0;
|
||||
|
||||
if ('function' === typeof(scrollCustomFn)) {
|
||||
if ('function' === typeof (scrollCustomFn)) {
|
||||
return scrollCustomFn.call(_this, scrollOffsetX, scrollOffsetY, evt);
|
||||
}
|
||||
|
||||
|
@ -202,7 +202,7 @@
|
|||
var originalGroup = options.group;
|
||||
|
||||
if (!originalGroup || typeof originalGroup != 'object') {
|
||||
originalGroup = {name: originalGroup};
|
||||
originalGroup = { name: originalGroup };
|
||||
}
|
||||
|
||||
group.name = originalGroup.name;
|
||||
|
@ -261,7 +261,7 @@
|
|||
fallbackClass: 'sortable-fallback',
|
||||
fallbackOnBody: false,
|
||||
fallbackTolerance: 0,
|
||||
fallbackOffset: {x: 0, y: 0}
|
||||
fallbackOffset: { x: 0, y: 0 }
|
||||
};
|
||||
|
||||
|
||||
|
@ -963,7 +963,7 @@
|
|||
this._nulling();
|
||||
},
|
||||
|
||||
_nulling: function() {
|
||||
_nulling: function () {
|
||||
rootEl =
|
||||
dragEl =
|
||||
parentEl =
|
||||
|
@ -1455,7 +1455,7 @@
|
|||
};
|
||||
}
|
||||
}));
|
||||
} catch (err) {}
|
||||
} catch (err) { }
|
||||
|
||||
// Export utils
|
||||
Sortable.utils = {
|
||||
|
@ -1489,3 +1489,6 @@
|
|||
Sortable.version = '1.6.0';
|
||||
return Sortable;
|
||||
});
|
||||
|
||||
|
||||
// http://rubaxa.github.io/Sortable/
|
||||
|
|
|
@ -95,6 +95,9 @@ const (
|
|||
|
||||
// TypeSentSecureLink records user sending secure document link to email address(es)
|
||||
TypeSentSecureLink Type = 12
|
||||
|
||||
// TypeDraft records user marking space/document as draft
|
||||
TypeDraft Type = 13
|
||||
)
|
||||
|
||||
// TypeName returns one-work descriptor for activity type
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue