mirror of
https://github.com/documize/community.git
synced 2025-08-02 20:15:26 +02:00
Support for document draft-live publication workflows
This commit is contained in:
parent
bde0091a4a
commit
9235c183c5
14 changed files with 828 additions and 697 deletions
|
@ -19,11 +19,8 @@ import (
|
|||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/documize/community/model/workflow"
|
||||
|
||||
"github.com/documize/community/core/env"
|
||||
|
||||
api "github.com/documize/community/core/convapi"
|
||||
"github.com/documize/community/core/env"
|
||||
"github.com/documize/community/core/request"
|
||||
"github.com/documize/community/core/response"
|
||||
"github.com/documize/community/core/stringutil"
|
||||
|
@ -37,6 +34,7 @@ import (
|
|||
"github.com/documize/community/model/audit"
|
||||
"github.com/documize/community/model/doc"
|
||||
"github.com/documize/community/model/page"
|
||||
"github.com/documize/community/model/space"
|
||||
uuid "github.com/nu7hatch/gouuid"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
@ -144,7 +142,16 @@ func (h *Handler) convert(w http.ResponseWriter, r *http.Request, job, folderID
|
|||
return
|
||||
}
|
||||
|
||||
nd, err := processDocument(ctx, h.Runtime, h.Store, h.Indexer, filename, job, folderID, fileResult)
|
||||
// Fetch space where document resides.
|
||||
sp, err := h.Store.Space.Get(ctx, folderID)
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
nd, err := processDocument(ctx, h.Runtime, h.Store, h.Indexer, filename, job, sp, fileResult)
|
||||
if err != nil {
|
||||
ctx.Transaction.Rollback()
|
||||
response.WriteServerError(w, method, err)
|
||||
|
@ -158,16 +165,16 @@ func (h *Handler) convert(w http.ResponseWriter, r *http.Request, job, folderID
|
|||
response.WriteJSON(w, nd)
|
||||
}
|
||||
|
||||
func processDocument(ctx domain.RequestContext, r *env.Runtime, store *domain.Store, indexer indexer.Indexer, filename, job, folderID string, fileResult *api.DocumentConversionResponse) (newDocument doc.Document, err error) {
|
||||
func processDocument(ctx domain.RequestContext, r *env.Runtime, store *domain.Store, indexer indexer.Indexer, filename, job string, sp space.Space, fileResult *api.DocumentConversionResponse) (newDocument doc.Document, err error) {
|
||||
// Convert into database objects
|
||||
document := convertFileResult(filename, fileResult)
|
||||
document.Job = job
|
||||
document.OrgID = ctx.OrgID
|
||||
document.LabelID = folderID
|
||||
document.LabelID = sp.RefID
|
||||
document.UserID = ctx.UserID
|
||||
documentID := uniqueid.Generate()
|
||||
document.RefID = documentID
|
||||
document.Lifecycle = workflow.LifecycleLive
|
||||
document.Lifecycle = sp.Lifecycle
|
||||
|
||||
err = store.Document.Add(ctx, document)
|
||||
if err != nil {
|
||||
|
|
|
@ -253,22 +253,34 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
// 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})
|
||||
}
|
||||
// Detect change in document status/lifecycle.
|
||||
if d.Lifecycle != oldDoc.Lifecycle {
|
||||
// Record document being marked as archived.
|
||||
if 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})
|
||||
// Record document being marked as draft.
|
||||
if d.Lifecycle == workflow.LifecycleDraft {
|
||||
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
LabelID: d.LabelID,
|
||||
DocumentID: documentID,
|
||||
SourceType: activity.SourceTypeDocument,
|
||||
ActivityType: activity.TypeDraft})
|
||||
}
|
||||
|
||||
// Record document being marked as live.
|
||||
if d.Lifecycle == workflow.LifecycleLive {
|
||||
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
|
||||
LabelID: d.LabelID,
|
||||
DocumentID: documentID,
|
||||
SourceType: activity.SourceTypeDocument,
|
||||
ActivityType: activity.TypePublished})
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Transaction.Commit()
|
||||
|
@ -318,7 +330,7 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
// If approval workflow then only approvers can delete page
|
||||
if doc.Protection == workflow.ProtectionReview {
|
||||
approvers, err := permission.GetDocumentApprovers(ctx, *h.Store, doc.LabelID, doc.RefID)
|
||||
approvers, err := permission.GetUsersWithDocumentPermission(ctx, *h.Store, doc.LabelID, doc.RefID, pm.DocumentApprove)
|
||||
if err != nil {
|
||||
response.WriteForbiddenError(w)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
|
|
|
@ -1440,7 +1440,7 @@ func (h *Handler) workflowPermitsChange(doc dm.Document, ctx domain.RequestConte
|
|||
|
||||
// If approval workflow then only approvers can delete page
|
||||
if doc.Protection == workflow.ProtectionReview {
|
||||
approvers, err := permission.GetDocumentApprovers(ctx, *h.Store, doc.LabelID, doc.RefID)
|
||||
approvers, err := permission.GetUsersWithDocumentPermission(ctx, *h.Store, doc.LabelID, doc.RefID, pm.DocumentApprove)
|
||||
if err != nil {
|
||||
h.Runtime.Log.Error("workflowAllowsChange", err)
|
||||
return false, err
|
||||
|
|
|
@ -227,8 +227,74 @@ func HasPermission(ctx domain.RequestContext, s domain.Store, spaceID string, ac
|
|||
return false
|
||||
}
|
||||
|
||||
// GetDocumentApprovers returns list of users who can approve given document in given space
|
||||
func GetDocumentApprovers(ctx domain.RequestContext, s domain.Store, spaceID, documentID string) (users []u.User, err error) {
|
||||
// // GetDocumentApprovers returns list of users who can approve given document in given space
|
||||
// func GetDocumentApprovers(ctx domain.RequestContext, s domain.Store, spaceID, documentID string) (users []u.User, err error) {
|
||||
// users = []u.User{}
|
||||
// prev := make(map[string]bool) // used to ensure we only process user once
|
||||
|
||||
// // Permissions can be assigned to both groups and individual users.
|
||||
// // Pre-fetch users with group membership to help us work out
|
||||
// // if user belongs to a group with permissions.
|
||||
// groupMembers, err := s.Group.GetMembers(ctx)
|
||||
// if err != nil {
|
||||
// return users, err
|
||||
// }
|
||||
|
||||
// // space permissions
|
||||
// sp, err := s.Permission.GetSpacePermissions(ctx, spaceID)
|
||||
// if err != nil {
|
||||
// return users, err
|
||||
// }
|
||||
// // document permissions
|
||||
// dp, err := s.Permission.GetDocumentPermissions(ctx, documentID)
|
||||
// if err != nil {
|
||||
// return users, err
|
||||
// }
|
||||
|
||||
// // all permissions
|
||||
// all := sp
|
||||
// all = append(all, dp...)
|
||||
|
||||
// for _, p := range all {
|
||||
// // only approvers
|
||||
// if p.Action != pm.DocumentApprove {
|
||||
// continue
|
||||
// }
|
||||
|
||||
// if p.Who == pm.GroupPermission {
|
||||
// // get group records for just this group
|
||||
// groupRecords := group.FilterGroupRecords(groupMembers, p.WhoID)
|
||||
|
||||
// for i := range groupRecords {
|
||||
// user, err := s.User.Get(ctx, groupRecords[i].UserID)
|
||||
// if err != nil {
|
||||
// return users, err
|
||||
// }
|
||||
// if _, isExisting := prev[user.RefID]; !isExisting {
|
||||
// users = append(users, user)
|
||||
// prev[user.RefID] = true
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// if p.Who == pm.UserPermission {
|
||||
// user, err := s.User.Get(ctx, p.WhoID)
|
||||
// if err != nil {
|
||||
// return users, err
|
||||
// }
|
||||
|
||||
// if _, isExisting := prev[user.RefID]; !isExisting {
|
||||
// users = append(users, user)
|
||||
// prev[user.RefID] = true
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// return users, err
|
||||
// }
|
||||
|
||||
// GetUsersWithDocumentPermission returns list of users who have specified document permission in given space
|
||||
func GetUsersWithDocumentPermission(ctx domain.RequestContext, s domain.Store, spaceID, documentID string, permissionRequired pm.Action) (users []u.User, err error) {
|
||||
users = []u.User{}
|
||||
prev := make(map[string]bool) // used to ensure we only process user once
|
||||
|
||||
|
@ -257,7 +323,7 @@ func GetDocumentApprovers(ctx domain.RequestContext, s domain.Store, spaceID, do
|
|||
|
||||
for _, p := range all {
|
||||
// only approvers
|
||||
if p.Action != pm.DocumentApprove {
|
||||
if p.Action != permissionRequired {
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
@ -35,8 +35,8 @@ func (s Scope) Add(ctx domain.RequestContext, sp space.Space) (err error) {
|
|||
sp.Created = time.Now().UTC()
|
||||
sp.Revised = time.Now().UTC()
|
||||
|
||||
_, err = ctx.Transaction.Exec("INSERT INTO label (refid, label, orgid, userid, type, likes, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
sp.RefID, sp.Name, sp.OrgID, sp.UserID, sp.Type, sp.Likes, sp.Created, sp.Revised)
|
||||
_, err = ctx.Transaction.Exec("INSERT INTO label (refid, label, orgid, userid, type, lifecycle, likes, created, revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||
sp.RefID, sp.Name, sp.OrgID, sp.UserID, sp.Type, sp.Lifecycle, sp.Likes, sp.Created, sp.Revised)
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, "unable to execute insert for label")
|
||||
|
@ -47,7 +47,7 @@ func (s Scope) Add(ctx domain.RequestContext, sp space.Space) (err error) {
|
|||
|
||||
// Get returns a space from the store.
|
||||
func (s Scope) Get(ctx domain.RequestContext, id string) (sp space.Space, err error) {
|
||||
err = s.Runtime.Db.Get(&sp, "SELECT id,refid,label as name,orgid,userid,type,likes,created,revised FROM label WHERE orgid=? and refid=?",
|
||||
err = s.Runtime.Db.Get(&sp, "SELECT id,refid,label as name,orgid,userid,type,lifecycle,likes,created,revised FROM label WHERE orgid=? and refid=?",
|
||||
ctx.OrgID, id)
|
||||
|
||||
if err != nil {
|
||||
|
@ -59,7 +59,7 @@ func (s Scope) Get(ctx domain.RequestContext, id string) (sp space.Space, err er
|
|||
|
||||
// PublicSpaces returns spaces that anyone can see.
|
||||
func (s Scope) PublicSpaces(ctx domain.RequestContext, orgID string) (sp []space.Space, err error) {
|
||||
qry := "SELECT id,refid,label as name,orgid,userid,type,likes,created,revised FROM label a where orgid=? AND type=1"
|
||||
qry := "SELECT id,refid,label as name,orgid,userid,type,lifecycle,likes,created,revised FROM label a where orgid=? AND type=1"
|
||||
|
||||
err = s.Runtime.Db.Select(&sp, qry, orgID)
|
||||
|
||||
|
@ -78,7 +78,7 @@ func (s Scope) PublicSpaces(ctx domain.RequestContext, orgID string) (sp []space
|
|||
// Also handles which spaces can be seen by anonymous users.
|
||||
func (s Scope) GetViewable(ctx domain.RequestContext) (sp []space.Space, err error) {
|
||||
q := `
|
||||
SELECT id,refid,label as name,orgid,userid,type,likes,created,revised FROM label
|
||||
SELECT id,refid,label as name,orgid,userid,type,lifecycle,likes,created,revised 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
|
||||
|
@ -109,7 +109,7 @@ func (s Scope) GetViewable(ctx domain.RequestContext) (sp []space.Space, err err
|
|||
// GetAll for admin users!
|
||||
func (s Scope) GetAll(ctx domain.RequestContext) (sp []space.Space, err error) {
|
||||
qry := `
|
||||
SELECT id,refid,label as name,orgid,userid,type,likes,created,revised FROM label
|
||||
SELECT id,refid,label as name,orgid,userid,type,lifecycle,likes,created,revised FROM label
|
||||
WHERE orgid=?
|
||||
ORDER BY name`
|
||||
|
||||
|
@ -130,7 +130,7 @@ func (s Scope) GetAll(ctx domain.RequestContext) (sp []space.Space, err error) {
|
|||
func (s Scope) Update(ctx domain.RequestContext, sp space.Space) (err error) {
|
||||
sp.Revised = time.Now().UTC()
|
||||
|
||||
_, err = ctx.Transaction.NamedExec("UPDATE label SET label=:name, type=:type, userid=:userid, likes=:likes, revised=:revised WHERE orgid=:orgid AND refid=:refid", &sp)
|
||||
_, err = ctx.Transaction.NamedExec("UPDATE label SET label=:name, type=:type, lifecycle=:lifecycle, userid=:userid, likes=:likes, revised=:revised WHERE orgid=:orgid AND refid=:refid", &sp)
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrap(err, fmt.Sprintf("unable to execute update for label %s", sp.RefID))
|
||||
|
|
|
@ -294,6 +294,14 @@ func (h *Handler) Use(w http.ResponseWriter, r *http.Request) {
|
|||
attachments, _ = h.Store.Attachment.GetAttachmentsWithData(ctx, templateID)
|
||||
}
|
||||
|
||||
// Fetch space where document resides.
|
||||
sp, err := h.Store.Space.Get(ctx, folderID)
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
h.Runtime.Log.Error(method, err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Transaction, err = h.Runtime.Db.Beginx()
|
||||
if err != nil {
|
||||
response.WriteServerError(w, method, err)
|
||||
|
@ -308,7 +316,7 @@ func (h *Handler) Use(w http.ResponseWriter, r *http.Request) {
|
|||
d.LabelID = folderID
|
||||
d.UserID = ctx.UserID
|
||||
d.Title = docTitle
|
||||
d.Lifecycle = workflow.LifecycleLive
|
||||
d.Lifecycle = sp.Lifecycle
|
||||
|
||||
err = h.Store.Document.Add(ctx, d)
|
||||
if err != nil {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue