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

Pinned documents

Closes #278

Pin documents to the top of each space.
This commit is contained in:
sauls8t 2020-02-03 21:00:35 +00:00
parent 2b66d0096a
commit e014f5b5c1
18 changed files with 541 additions and 88 deletions

View file

@ -14,6 +14,7 @@ package document
import (
"database/sql"
"encoding/json"
"errors"
"io/ioutil"
"net/http"
"sort"
@ -88,18 +89,12 @@ func (h *Handler) Get(w http.ResponseWriter, r *http.Request) {
return
}
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
SpaceID: document.SpaceID,
DocumentID: document.RefID,
SourceType: activity.SourceTypeDocument,
ActivityType: activity.TypeRead})
if err != nil {
ctx.Transaction.Rollback()
h.Runtime.Log.Error(method, err)
return
}
ctx.Transaction.Commit()
}
@ -190,10 +185,26 @@ func (h *Handler) BySpace(w http.ResponseWriter, r *http.Request) {
}
}
// Sort document list by title.
sort.Sort(doc.ByName(filtered))
sortedDocs := doc.SortedDocs{}
response.WriteJSON(w, filtered)
for j := range filtered {
if filtered[j].Sequence == doc.Unsequenced {
sortedDocs.Unpinned = append(sortedDocs.Unpinned, filtered[j])
} else {
sortedDocs.Pinned = append(sortedDocs.Pinned, filtered[j])
}
}
// Sort document list by title.
sort.Sort(doc.ByName(sortedDocs.Unpinned))
// Sort document list by sequence.
sort.Sort(doc.BySeq(sortedDocs.Pinned))
final := sortedDocs.Pinned
final = append(final, sortedDocs.Unpinned...)
response.WriteJSON(w, final)
}
// Update updates an existing document using the format described
@ -249,7 +260,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
}
if oldDoc.SpaceID != d.SpaceID {
h.Store.Category.RemoveDocumentCategories(ctx, d.RefID)
_, _ = h.Store.Category.RemoveDocumentCategories(ctx, d.RefID)
err = h.Store.Document.MoveActivity(ctx, documentID, oldDoc.SpaceID, d.SpaceID)
if err != nil {
h.Runtime.Rollback(ctx.Transaction)
@ -312,9 +323,9 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
h.Runtime.Commit(ctx.Transaction)
h.Store.Space.SetStats(ctx, d.SpaceID)
_ = h.Store.Space.SetStats(ctx, d.SpaceID)
if oldDoc.SpaceID != d.SpaceID {
h.Store.Space.SetStats(ctx, oldDoc.SpaceID)
_ = h.Store.Space.SetStats(ctx, oldDoc.SpaceID)
}
h.Store.Audit.Record(ctx, audit.EventTypeDocumentUpdate)
@ -480,18 +491,13 @@ func (h *Handler) SearchDocuments(w http.ResponseWriter, r *http.Request) {
return
}
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
SpaceID: "",
DocumentID: "",
Metadata: options.Keywords,
SourceType: activity.SourceTypeSearch,
ActivityType: activity.TypeSearched})
if err != nil {
ctx.Transaction.Rollback()
h.Runtime.Log.Error(method, err)
}
ctx.Transaction.Commit()
}
}
@ -526,18 +532,13 @@ func (h *Handler) recordSearchActivity(ctx domain.RequestContext, q []search.Que
}
if _, isExisting := prev[q[i].DocumentID]; !isExisting {
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
SpaceID: q[i].SpaceID,
DocumentID: q[i].DocumentID,
Metadata: keywords,
SourceType: activity.SourceTypeSearch,
ActivityType: activity.TypeSearched})
if err != nil {
ctx.Transaction.Rollback()
h.Runtime.Log.Error(method, err)
}
prev[q[i].DocumentID] = true
}
}
@ -713,16 +714,11 @@ func (h *Handler) FetchDocumentData(w http.ResponseWriter, r *http.Request) {
}
if document.Lifecycle == workflow.LifecycleLive {
err = h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
SpaceID: document.SpaceID,
DocumentID: document.RefID,
SourceType: activity.SourceTypeDocument,
ActivityType: activity.TypeRead})
if err != nil {
ctx.Transaction.Rollback()
h.Runtime.Log.Error(method, err)
}
}
ctx.Transaction.Commit()
@ -786,7 +782,7 @@ func (h *Handler) Export(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(http.StatusOK)
w.Write([]byte(export))
_, _ = w.Write([]byte(export))
}
// Duplicate makes a copy of a document.
@ -1014,3 +1010,219 @@ func (h *Handler) Duplicate(w http.ResponseWriter, r *http.Request) {
response.WriteEmpty(w)
}
// Pin marks existing document with sequence number so that it
// appears at the top-most space view.
func (h *Handler) Pin(w http.ResponseWriter, r *http.Request) {
method := "document.Pin"
ctx := domain.GetRequestContext(r)
documentID := request.Param(r, "documentID")
if len(documentID) == 0 {
response.WriteMissingDataError(w, method, "documentID")
return
}
var ok bool
ctx.Transaction, ok = h.Runtime.StartTx(sql.LevelReadUncommitted)
if !ok {
h.Runtime.Log.Info("unable to start transaction " + method)
response.WriteServerError(w, method, errors.New("unable to start transaction"))
return
}
d, err := h.Store.Document.Get(ctx, documentID)
if err != nil {
h.Runtime.Rollback(ctx.Transaction)
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
if !permission.CanManageSpace(ctx, *h.Store, d.SpaceID) {
h.Runtime.Rollback(ctx.Transaction)
response.WriteForbiddenError(w)
return
}
// Calculate the next sequence number for this newly pinned document.
seq, err := h.Store.Document.PinSequence(ctx, d.SpaceID)
if err != nil {
h.Runtime.Rollback(ctx.Transaction)
h.Runtime.Log.Error(method, err)
response.WriteServerError(w, method, err)
return
}
err = h.Store.Document.Pin(ctx, documentID, seq+1)
if err != nil {
h.Runtime.Rollback(ctx.Transaction)
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
SpaceID: d.SpaceID,
DocumentID: documentID,
SourceType: activity.SourceTypeDocument,
ActivityType: activity.TypePinned})
h.Runtime.Commit(ctx.Transaction)
response.WriteEmpty(w)
}
// Unpin removes an existing document from the space pinned list.
func (h *Handler) Unpin(w http.ResponseWriter, r *http.Request) {
method := "document.Unpin"
ctx := domain.GetRequestContext(r)
documentID := request.Param(r, "documentID")
if len(documentID) == 0 {
response.WriteMissingDataError(w, method, "documentID")
return
}
var ok bool
ctx.Transaction, ok = h.Runtime.StartTx(sql.LevelReadUncommitted)
if !ok {
h.Runtime.Log.Info("unable to start transaction " + method)
response.WriteServerError(w, method, errors.New("unable to start transaction"))
return
}
d, err := h.Store.Document.Get(ctx, documentID)
if err != nil {
h.Runtime.Rollback(ctx.Transaction)
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
if !permission.CanManageSpace(ctx, *h.Store, d.SpaceID) {
h.Runtime.Rollback(ctx.Transaction)
response.WriteForbiddenError(w)
return
}
err = h.Store.Document.Unpin(ctx, documentID)
if err != nil {
h.Runtime.Rollback(ctx.Transaction)
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
SpaceID: d.SpaceID,
DocumentID: documentID,
SourceType: activity.SourceTypeDocument,
ActivityType: activity.TypeUnpinned})
h.Runtime.Commit(ctx.Transaction)
response.WriteEmpty(w)
}
// PinMove moves pinned document up or down in the sequence.
func (h *Handler) PinMove(w http.ResponseWriter, r *http.Request) {
method := "document.PinMove"
ctx := domain.GetRequestContext(r)
documentID := request.Param(r, "documentID")
if len(documentID) == 0 {
response.WriteMissingDataError(w, method, "documentID")
return
}
direction := request.Query(r, "direction")
if len(direction) == 0 {
response.WriteMissingDataError(w, method, "direction")
return
}
var ok bool
ctx.Transaction, ok = h.Runtime.StartTx(sql.LevelReadUncommitted)
if !ok {
h.Runtime.Log.Info("unable to start transaction " + method)
response.WriteServerError(w, method, errors.New("unable to start transaction"))
return
}
d, err := h.Store.Document.Get(ctx, documentID)
if err != nil {
h.Runtime.Rollback(ctx.Transaction)
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
if !permission.CanManageSpace(ctx, *h.Store, d.SpaceID) {
h.Runtime.Rollback(ctx.Transaction)
response.WriteForbiddenError(w)
return
}
// Get all pinned documents in the space.
pinnedDocs, err := h.Store.Document.Pinned(ctx, d.SpaceID)
if err != nil {
h.Runtime.Rollback(ctx.Transaction)
h.Runtime.Log.Error(method, err)
response.WriteServerError(w, method, err)
return
}
// Sort document list by sequence.
sort.Sort(doc.BySeq(pinnedDocs))
// Resequence the documents.
for i := range pinnedDocs {
if pinnedDocs[i].RefID == documentID {
if direction == "u" {
if i-1 >= 0 {
me := pinnedDocs[i].Sequence
target := pinnedDocs[i-1].Sequence
pinnedDocs[i-1].Sequence = me
pinnedDocs[i].Sequence = target
}
}
if direction == "d" {
if i+1 < len(pinnedDocs) {
me := pinnedDocs[i].Sequence
target := pinnedDocs[i+1].Sequence
pinnedDocs[i+1].Sequence = me
pinnedDocs[i].Sequence = target
}
}
break
}
}
// Sort document list by sequence.
sort.Sort(doc.BySeq(pinnedDocs))
// Save the resequenced documents.
for i := range pinnedDocs {
err = h.Store.Document.Pin(ctx, pinnedDocs[i].RefID, i+1)
if err != nil {
h.Runtime.Rollback(ctx.Transaction)
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
}
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
SpaceID: d.SpaceID,
DocumentID: documentID,
SourceType: activity.SourceTypeDocument,
ActivityType: activity.TypePinSequence})
h.Runtime.Commit(ctx.Transaction)
response.WriteEmpty(w)
}

View file

@ -16,10 +16,11 @@ import (
"fmt"
"time"
"github.com/pkg/errors"
"github.com/documize/community/domain"
"github.com/documize/community/domain/store"
"github.com/documize/community/model/doc"
"github.com/pkg/errors"
)
// Store provides data access to space category information.
@ -36,10 +37,12 @@ func (s Store) Add(ctx domain.RequestContext, d doc.Document) (err error) {
_, err = ctx.Transaction.Exec(s.Bind(`
INSERT INTO dmz_doc (c_refid, c_orgid, c_spaceid, c_userid, c_job, c_location, c_name, c_desc, c_slug, c_tags,
c_template, c_protection, c_approval, c_lifecycle, c_versioned, c_versionid, c_versionorder, c_groupid, c_created, c_revised)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
c_template, c_protection, c_approval, c_lifecycle, c_versioned, c_versionid, c_versionorder, c_seq, c_groupid,
c_created, c_revised)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`),
d.RefID, d.OrgID, d.SpaceID, d.UserID, d.Job, d.Location, d.Name, 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)
d.Template, d.Protection, d.Approval, d.Lifecycle, d.Versioned, d.VersionID, d.VersionOrder, d.Sequence,
d.GroupID, d.Created, d.Revised)
if err != nil {
err = errors.Wrap(err, "execute insert document")
@ -55,7 +58,7 @@ func (s Store) Get(ctx domain.RequestContext, id string) (document doc.Document,
c_job AS job, c_location AS location, c_name AS name, c_desc AS excerpt, c_slug AS slug,
c_tags AS tags, c_template AS template, c_protection AS protection, c_approval AS approval,
c_lifecycle AS lifecycle, c_versioned AS versioned, c_versionid AS versionid,
c_versionorder AS versionorder, c_groupid AS groupid, c_created AS created, c_revised AS revised
c_versionorder AS versionorder, c_seq AS sequence, c_groupid AS groupid, c_created AS created, c_revised AS revised
FROM dmz_doc
WHERE c_orgid=? AND c_refid=?`),
ctx.OrgID, id)
@ -78,7 +81,7 @@ func (s Store) GetBySpace(ctx domain.RequestContext, spaceID string) (documents
c_job AS job, c_location AS location, c_name AS name, c_desc AS excerpt, c_slug AS slug,
c_tags AS tags, c_template AS template, c_protection AS protection, c_approval AS approval,
c_lifecycle AS lifecycle, c_versioned AS versioned, c_versionid AS versionid,
c_versionorder AS versionorder, c_groupid AS groupid, c_created AS created, c_revised AS revised
c_versionorder AS versionorder, c_seq AS sequence, c_groupid AS groupid, c_created AS created, c_revised AS revised
FROM dmz_doc
WHERE c_orgid=? AND c_template=`+s.IsFalse()+` AND c_spaceid IN
(SELECT c_refid FROM dmz_permission WHERE c_orgid=? AND c_location='space' AND c_refid=? AND c_refid IN
@ -111,7 +114,7 @@ func (s Store) TemplatesBySpace(ctx domain.RequestContext, spaceID string) (docu
c_job AS job, c_location AS location, c_name AS name, c_desc AS excerpt, c_slug AS slug,
c_tags AS tags, c_template AS template, c_protection AS protection, c_approval AS approval,
c_lifecycle AS lifecycle, c_versioned AS versioned, c_versionid AS versionid,
c_versionorder AS versionorder, c_groupid AS groupid, c_created AS created, c_revised AS revised
c_versionorder AS versionorder, c_seq AS sequence, c_groupid AS groupid, c_created AS created, c_revised AS revised
FROM dmz_doc
WHERE c_orgid=? AND c_spaceid=? AND c_template=`+s.IsTrue()+` AND c_lifecycle=1
AND c_spaceid IN
@ -167,7 +170,8 @@ func (s Store) Update(ctx domain.RequestContext, document doc.Document) (err err
c_spaceid=:spaceid, c_userid=:userid, c_job=:job, c_location=:location, c_name=:name,
c_desc=:excerpt, c_slug=:slug, c_tags=:tags, c_template=:template,
c_protection=:protection, c_approval=:approval, c_lifecycle=:lifecycle,
c_versioned=:versioned, c_versionid=:versionid, c_versionorder=:versionorder,
c_versioned=:versioned, c_versionid=:versionid, c_versionorder=:versionorder,
c_seq=:sequence,
c_groupid=:groupid, c_revised=:revised
WHERE c_orgid=:orgid AND c_refid=:refid`),
&document)
@ -331,3 +335,78 @@ func (s Store) GetVersions(ctx domain.RequestContext, groupID string) (v []doc.V
return
}
// Pin allocates sequence number to specified document so that it appears
// at the documents list.
func (s Store) Pin(ctx domain.RequestContext, documentID string, seq int) (err error) {
_, err = ctx.Transaction.Exec(s.Bind("UPDATE dmz_doc SET c_seq=? WHERE c_orgid=? AND c_refid=?"),
seq, ctx.OrgID, documentID)
if err != nil {
err = errors.Wrap(err, "document.store.Pin")
}
return
}
// Unpin resets sequence number for given document.
func (s Store) Unpin(ctx domain.RequestContext, documentID string) (err error) {
_, err = ctx.Transaction.Exec(s.Bind("UPDATE dmz_doc SET c_seq=? WHERE c_orgid=? AND c_refid=?"),
doc.Unsequenced, ctx.OrgID, documentID)
if err != nil {
err = errors.Wrap(err, "document.store.Unpin")
}
return
}
// PinSequence fectches pinned documents and returns current
// maximum sequence value.
func (s Store) PinSequence(ctx domain.RequestContext, spaceID string) (max int, err error) {
max = 0
err = s.Runtime.Db.Get(&max, s.Bind(`
SELECT MAX(c_seq)
FROM dmz_doc
WHERE c_orgid=? AND c_spaceid=?
AND c_seq != 99999`),
ctx.OrgID, spaceID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
max = doc.Unsequenced
err = errors.Wrap(err, "document.store.PinSequence")
}
return
}
// Pinned documents for space are fetched.
func (s Store) Pinned(ctx domain.RequestContext, spaceID string) (d []doc.Document, err error) {
d = []doc.Document{}
err = s.Runtime.Db.Select(&d, s.Bind(`
SELECT id, c_refid AS refid, c_orgid AS orgid, c_spaceid AS spaceid, c_userid AS userid,
c_job AS job, c_location AS location, c_name AS name, c_desc AS excerpt, c_slug AS slug,
c_tags AS tags, c_template AS template, c_protection AS protection, c_approval AS approval,
c_lifecycle AS lifecycle, c_versioned AS versioned, c_versionid AS versionid,
c_versionorder AS versionorder, c_seq AS sequence, c_groupid AS groupid,
c_created AS created, c_revised AS revised
FROM dmz_doc
WHERE c_orgid=? AND c_spaceid=?
AND c_seq != 99999
ORDER BY c_seq`),
ctx.OrgID, spaceID)
if err == sql.ErrNoRows {
err = nil
}
if err != nil {
err = errors.Wrap(err, "document.store.Pinned")
}
return
}