1
0
Fork 0
mirror of https://github.com/documize/community.git synced 2025-08-04 13:05:23 +02:00

Support for document versioning

This commit is contained in:
sauls8t 2018-03-19 12:24:58 +00:00
parent bc2cab5721
commit a7a82d9fe3
9 changed files with 158 additions and 110 deletions

View file

@ -65,7 +65,6 @@ func FilterCategoryProtected(docs []doc.Document, cats []category.Category, memb
// 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")
@ -145,3 +144,32 @@ func CopyDocument(ctx domain.RequestContext, s domain.Store, documentID string)
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
}
// 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) {
method := "document.get"
method := "document.Get"
ctx := domain.GetRequestContext(r)
id := request.Param(r, "documentID")
@ -101,7 +102,7 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
// DocumentLinks is an endpoint returning the links for a document.
func (h *Handler) DocumentLinks(w http.ResponseWriter, r *http.Request) {
method := "document.links"
method := "document.DocumentLinks"
ctx := domain.GetRequestContext(r)
id := request.Param(r, "documentID")
@ -142,7 +143,8 @@ func (h *Handler) BySpace(w http.ResponseWriter, r *http.Request) {
// get user permissions
viewDrafts := permission.CanViewDrafts(ctx, *h.Store, spaceID)
// get complete list of documents
// Get complete list of documents regardless of category permission
// and versioning.
documents, err := h.Store.Document.GetBySpace(ctx, spaceID)
if err != nil {
response.WriteServerError(w, method, err)
@ -150,21 +152,25 @@ func (h *Handler) BySpace(w http.ResponseWriter, r *http.Request) {
return
}
// sort by title
// Sort by title.
sort.Sort(doc.ByTitle(documents))
// remove documents that cannot be seen due to lack of category view/access permission
// 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)
filtered := FilterCategoryProtected(documents, cats, members, viewDrafts)
// Keep the latest version when faced with multiple versions.
filtered = FilterLastVersion(filtered)
response.WriteJSON(w, filtered)
}
// Update updates an existing document using the
// format described in NewDocumentModel() encoded as JSON in the request.
// Update updates an existing document using the format described
// in NewDocumentModel() encoded as JSON in the request.
func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
method := "document.space"
method := "document.Update"
ctx := domain.GetRequestContext(r)
documentID := request.Param(r, "documentID")
@ -203,7 +209,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
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)
if err != nil {
ctx.Transaction.Rollback()
@ -224,7 +230,20 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
return
}
// Record document being marked as archived
// 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,
@ -233,7 +252,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
ActivityType: activity.TypeArchived})
}
// Record document being marked as draft
// 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,
@ -246,7 +265,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
h.Store.Audit.Record(ctx, audit.EventTypeDocumentUpdate)
// Live document indexed for search
// Live document indexed for search.
if d.Lifecycle == workflow.LifecycleLive {
a, _ := h.Store.Attachment.GetAttachments(ctx, documentID)
go h.Indexer.IndexDocument(ctx, d, a)
@ -259,7 +278,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
// Delete is an endpoint that deletes a document specified by documentID.
func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
method := "document.delete"
method := "document.Delete"
ctx := domain.GetRequestContext(r)
documentID := request.Param(r, "documentID")
@ -348,9 +367,10 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
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) {
method := "document.search"
method := "document.SearchDocuments"
ctx := domain.GetRequestContext(r)
defer streamutil.Close(r.Body)
@ -467,12 +487,22 @@ func (h *Handler) FetchDocumentData(w http.ResponseWriter, r *http.Request) {
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.Document = document
data.Permissions = record
data.Roles = rolesRecord
data.Links = l
data.Spaces = sp
data.Versions = v
ctx.Transaction, err = h.Runtime.Db.Beginx()
if err != nil {
@ -509,4 +539,5 @@ type BulkDocumentData struct {
Roles pm.DocumentRecord `json:"roles"`
Spaces []space.Space `json:"folders"`
Links []link.Link `json:"links"`
Versions []doc.Version `json:"versions"`
}

View file

@ -97,20 +97,13 @@ func (s Scope) DocumentMeta(ctx domain.RequestContext, id string) (meta doc.Docu
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.
// 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) {
err = s.Runtime.Db.Select(&documents, `
SELECT id, refid, orgid, labelid, userid, job, location, title, excerpt, slug, tags, template,
@ -125,7 +118,7 @@ func (s Scope) GetBySpace(ctx domain.RequestContext, spaceID string) (documents
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 || len(documents) == 0 {
err = nil
@ -138,35 +131,6 @@ func (s Scope) GetBySpace(ctx domain.RequestContext, spaceID string) (documents
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, lifecycle, versioned, versionid, versionorder, groupid, created, revised
FROM document
WHERE orgid=? AND template=1 AND lifecycle=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 == sql.ErrNoRows || len(documents) == 0 {
err = nil
documents = []doc.Document{}
}
if err != nil {
err = errors.Wrap(err, "select document templates")
}
return
}
// 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) {
err = s.Runtime.Db.Select(&documents,
@ -196,7 +160,9 @@ func (s Scope) TemplatesBySpace(ctx domain.RequestContext, spaceID string) (docu
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) {
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
@ -217,35 +183,6 @@ func (s Scope) PublicDocuments(ctx domain.RequestContext, orgID string) (documen
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, lifecycle, versioned, versionid, versionorder, groupid, created, revised
FROM document
WHERE orgid=? AND template=0 AND lifecycle=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 == sql.ErrNoRows {
err = nil
documents = []doc.Document{}
}
if err != nil {
err = errors.Wrap(err, "select documents list")
}
return
}
// Update changes the given document record to the new values, updates search information and audits the action.
func (s Scope) Update(ctx domain.RequestContext, document doc.Document) (err error) {
document.Revised = time.Now().UTC()
@ -255,11 +192,27 @@ func (s Scope) Update(ctx domain.RequestContext, document doc.Document) (err err
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`,
WHERE orgid=:orgid AND refid=:refid`,
&document)
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
@ -341,3 +294,28 @@ func (s Scope) DeleteBySpace(ctx domain.RequestContext, spaceID string) (rows in
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
}