1
0
Fork 0
mirror of https://github.com/documize/community.git synced 2025-08-02 20:15:26 +02:00

improve level code

This commit is contained in:
Harvey Kandola 2018-01-10 16:07:17 +00:00
parent 049b83e0b9
commit 5f59e95495
25 changed files with 1104 additions and 461 deletions

View file

@ -31,8 +31,9 @@ import (
"github.com/documize/community/domain/section/provider"
"github.com/documize/community/model/activity"
"github.com/documize/community/model/audit"
"github.com/documize/community/model/doc"
"github.com/documize/community/model/page"
pm "github.com/documize/community/model/permission"
"github.com/documize/community/model/workflow"
htmldiff "github.com/documize/html-diff"
)
@ -53,17 +54,14 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
return
}
// check param
documentID := request.Param(r, "documentID")
if len(documentID) == 0 {
response.WriteMissingDataError(w, method, "documentID")
return
}
if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w)
return
}
// read payload
defer streamutil.Close(r.Body)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
@ -90,6 +88,38 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
return
}
// Check protection and approval process
document, err := h.Store.Document.Get(ctx, documentID)
if err != nil {
response.WriteBadRequestError(w, method, err.Error())
h.Runtime.Log.Error(method, err)
return
}
// Protect locked
if document.Protection == workflow.ProtectionLock {
response.WriteForbiddenError(w)
h.Runtime.Log.Info("attempted write to locked document")
return
}
// Check edit permission
if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w)
return
}
// if document review process then we must mark page as pending
if document.Protection == workflow.ProtectionReview {
if model.Page.RelativeID == "" {
model.Page.Status = workflow.ChangePendingNew
} else {
model.Page.Status = workflow.ChangePending
}
} else {
model.Page.Status = workflow.ChangePublished
}
pageID := uniqueid.Generate()
model.Page.RefID = pageID
model.Meta.PageID = pageID
@ -97,7 +127,6 @@ func (h *Handler) Add(w http.ResponseWriter, r *http.Request) {
model.Meta.UserID = ctx.UserID // required for Render call below
model.Page.SetDefaults()
model.Meta.SetDefaults()
// page.Title = template.HTMLEscapeString(page.Title)
doc, err := h.Store.Document.Get(ctx, documentID)
if err != nil {
@ -229,16 +258,11 @@ func (h *Handler) GetPages(w http.ResponseWriter, r *http.Request) {
response.WriteJSON(w, pages)
}
// Delete deletes a page.
func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
method := "page.delete"
// GetMeta gets page meta data for specified document page.
func (h *Handler) GetMeta(w http.ResponseWriter, r *http.Request) {
method := "page.meta"
ctx := domain.GetRequestContext(r)
if !h.Runtime.Product.License.IsValid() {
response.WriteBadLicense(w)
return
}
documentID := request.Param(r, "documentID")
if len(documentID) == 0 {
response.WriteMissingDataError(w, method, "documentID")
@ -251,156 +275,31 @@ func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
return
}
if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
if !permission.CanViewDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w)
return
}
doc, err := h.Store.Document.Get(ctx, documentID)
meta, err := h.Store.Page.GetPageMeta(ctx, pageID)
if err == sql.ErrNoRows {
response.WriteNotFoundError(w, method, pageID)
h.Runtime.Log.Info(method + " no record")
meta = page.Meta{}
response.WriteJSON(w, meta)
return
}
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)
if meta.DocumentID != documentID {
response.WriteBadRequestError(w, method, "documentID mismatch")
h.Runtime.Log.Error(method, err)
return
}
page, err := h.Store.Page.Get(ctx, pageID)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
if len(page.BlockID) > 0 {
h.Store.Block.DecrementUsage(ctx, page.BlockID)
}
_, err = h.Store.Page.Delete(ctx, documentID, pageID)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
LabelID: doc.LabelID,
SourceID: documentID,
SourceType: activity.SourceTypeDocument,
ActivityType: activity.TypeDeleted})
h.Store.Audit.Record(ctx, audit.EventTypeSectionDelete)
go h.Indexer.DeleteContent(ctx, pageID)
h.Store.Link.DeleteSourcePageLinks(ctx, pageID)
h.Store.Link.MarkOrphanPageLink(ctx, pageID)
h.Store.Page.DeletePageRevisions(ctx, pageID)
ctx.Transaction.Commit()
response.WriteEmpty(w)
}
// DeletePages batch deletes pages.
func (h *Handler) DeletePages(w http.ResponseWriter, r *http.Request) {
method := "page.delete.pages"
ctx := domain.GetRequestContext(r)
if !h.Runtime.Product.License.IsValid() {
response.WriteBadLicense(w)
return
}
documentID := request.Param(r, "documentID")
if len(documentID) == 0 {
response.WriteMissingDataError(w, method, "documentID")
return
}
if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w)
return
}
defer streamutil.Close(r.Body)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
response.WriteBadRequestError(w, method, "Bad body")
return
}
model := new([]page.LevelRequest)
err = json.Unmarshal(body, &model)
if err != nil {
response.WriteBadRequestError(w, method, "JSON marshal")
return
}
doc, err := h.Store.Document.Get(ctx, documentID)
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)
h.Runtime.Log.Error(method, err)
return
}
for _, page := range *model {
pageData, err := h.Store.Page.Get(ctx, page.PageID)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
if len(pageData.BlockID) > 0 {
h.Store.Block.DecrementUsage(ctx, pageData.BlockID)
}
_, err = h.Store.Page.Delete(ctx, documentID, page.PageID)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
go h.Indexer.DeleteContent(ctx, page.PageID)
h.Store.Link.DeleteSourcePageLinks(ctx, page.PageID)
h.Store.Link.MarkOrphanPageLink(ctx, page.PageID)
h.Store.Page.DeletePageRevisions(ctx, page.PageID)
}
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
LabelID: doc.LabelID,
SourceID: documentID,
SourceType: activity.SourceTypeDocument,
ActivityType: activity.TypeDeleted})
h.Store.Audit.Record(ctx, audit.EventTypeSectionDelete)
ctx.Transaction.Commit()
response.WriteEmpty(w)
response.WriteJSON(w, meta)
}
// Update will persist changed page and note the fact
@ -415,11 +314,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
return
}
// if !ctx.Editor {
// response.WriteForbiddenError(w)
// return
// }
// Check params
documentID := request.Param(r, "documentID")
if len(documentID) == 0 {
response.WriteMissingDataError(w, method, "documentID")
@ -432,11 +327,7 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
return
}
if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w)
return
}
// Read payload
defer streamutil.Close(r.Body)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
@ -458,13 +349,26 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
return
}
// Check protection and approval process
doc, err := h.Store.Document.Get(ctx, documentID)
if err != nil {
response.WriteServerError(w, method, err)
response.WriteBadRequestError(w, method, err.Error())
h.Runtime.Log.Error(method, err)
return
}
if doc.Protection == workflow.ProtectionLock {
response.WriteForbiddenError(w)
h.Runtime.Log.Info("attempted write to locked document")
return
}
// Check edit permission
if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w)
return
}
ctx.Transaction, err = h.Runtime.Db.Beginx()
if err != nil {
response.WriteServerError(w, method, err)
@ -492,6 +396,11 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
skipRevision := false
skipRevision, err = strconv.ParseBool(request.Query(r, "r"))
// We don't track revisions for non-published pages
if model.Page.Status != workflow.ChangePublished {
skipRevision = true
}
err = h.Store.Page.Update(ctx, model.Page, refID, ctx.UserID, skipRevision)
if err != nil {
response.WriteServerError(w, method, err)
@ -562,6 +471,258 @@ func (h *Handler) Update(w http.ResponseWriter, r *http.Request) {
response.WriteJSON(w, updatedPage)
}
// Delete deletes a page.
func (h *Handler) Delete(w http.ResponseWriter, r *http.Request) {
method := "page.delete"
ctx := domain.GetRequestContext(r)
if !h.Runtime.Product.License.IsValid() {
response.WriteBadLicense(w)
return
}
documentID := request.Param(r, "documentID")
if len(documentID) == 0 {
response.WriteMissingDataError(w, method, "documentID")
return
}
pageID := request.Param(r, "pageID")
if len(pageID) == 0 {
response.WriteMissingDataError(w, method, "pageID")
return
}
if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w)
return
}
doc, err := h.Store.Document.Get(ctx, documentID)
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
// Protect locked
if doc.Protection == workflow.ProtectionLock {
response.WriteForbiddenError(w)
h.Runtime.Log.Info("attempted delete section on locked document")
return
}
ctx.Transaction, err = h.Runtime.Db.Beginx()
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
p, err := h.Store.Page.Get(ctx, pageID)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
if len(p.BlockID) > 0 {
h.Store.Block.DecrementUsage(ctx, p.BlockID)
}
_, err = h.Store.Page.Delete(ctx, documentID, pageID)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
LabelID: doc.LabelID,
SourceID: documentID,
SourceType: activity.SourceTypeDocument,
ActivityType: activity.TypeDeleted})
h.Store.Audit.Record(ctx, audit.EventTypeSectionDelete)
go h.Indexer.DeleteContent(ctx, pageID)
h.Store.Link.DeleteSourcePageLinks(ctx, pageID)
h.Store.Link.MarkOrphanPageLink(ctx, pageID)
h.Store.Page.DeletePageRevisions(ctx, pageID)
ctx.Transaction.Commit()
// Re-level all pages in document
ctx.Transaction, err = h.Runtime.Db.Beginx()
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
p2, err := h.Store.Page.GetPages(ctx, documentID)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
page.Levelize(p2)
for _, i := range p2 {
err = h.Store.Page.UpdateLevel(ctx, documentID, i.RefID, int(i.Level))
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
}
ctx.Transaction.Commit()
response.WriteEmpty(w)
}
// DeletePages batch deletes pages.
func (h *Handler) DeletePages(w http.ResponseWriter, r *http.Request) {
method := "page.delete.pages"
ctx := domain.GetRequestContext(r)
if !h.Runtime.Product.License.IsValid() {
response.WriteBadLicense(w)
return
}
documentID := request.Param(r, "documentID")
if len(documentID) == 0 {
response.WriteMissingDataError(w, method, "documentID")
return
}
if !permission.CanChangeDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w)
return
}
defer streamutil.Close(r.Body)
body, err := ioutil.ReadAll(r.Body)
if err != nil {
response.WriteBadRequestError(w, method, "Bad body")
return
}
model := new([]page.LevelRequest)
err = json.Unmarshal(body, &model)
if err != nil {
response.WriteBadRequestError(w, method, "JSON marshal")
return
}
doc, err := h.Store.Document.Get(ctx, documentID)
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
// Protect locked
if doc.Protection == workflow.ProtectionLock {
response.WriteForbiddenError(w)
h.Runtime.Log.Info("attempted delete sections on locked document")
return
}
ctx.Transaction, err = h.Runtime.Db.Beginx()
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
for _, page := range *model {
pageData, err := h.Store.Page.Get(ctx, page.PageID)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
if len(pageData.BlockID) > 0 {
h.Store.Block.DecrementUsage(ctx, pageData.BlockID)
}
_, err = h.Store.Page.Delete(ctx, documentID, page.PageID)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
go h.Indexer.DeleteContent(ctx, page.PageID)
h.Store.Link.DeleteSourcePageLinks(ctx, page.PageID)
h.Store.Link.MarkOrphanPageLink(ctx, page.PageID)
h.Store.Page.DeletePageRevisions(ctx, page.PageID)
}
h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
LabelID: doc.LabelID,
SourceID: documentID,
SourceType: activity.SourceTypeDocument,
ActivityType: activity.TypeDeleted})
h.Store.Audit.Record(ctx, audit.EventTypeSectionDelete)
ctx.Transaction.Commit()
// Re-level all pages in document
ctx.Transaction, err = h.Runtime.Db.Beginx()
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
p2, err := h.Store.Page.GetPages(ctx, documentID)
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
page.Levelize(p2)
for _, i := range p2 {
err = h.Store.Page.UpdateLevel(ctx, documentID, i.RefID, int(i.Level))
if err != nil {
ctx.Transaction.Rollback()
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
}
ctx.Transaction.Commit()
response.WriteEmpty(w)
}
//**************************************************
// Table of Contents
//**************************************************
// ChangePageSequence will swap page sequence for a given number of pages.
func (h *Handler) ChangePageSequence(w http.ResponseWriter, r *http.Request) {
method := "page.sequence"
@ -684,75 +845,10 @@ func (h *Handler) ChangePageLevel(w http.ResponseWriter, r *http.Request) {
response.WriteEmpty(w)
}
// GetMeta gets page meta data for specified document page.
func (h *Handler) GetMeta(w http.ResponseWriter, r *http.Request) {
method := "page.meta"
ctx := domain.GetRequestContext(r)
documentID := request.Param(r, "documentID")
if len(documentID) == 0 {
response.WriteMissingDataError(w, method, "documentID")
return
}
pageID := request.Param(r, "pageID")
if len(pageID) == 0 {
response.WriteMissingDataError(w, method, "pageID")
return
}
if !permission.CanViewDocument(ctx, *h.Store, documentID) {
response.WriteForbiddenError(w)
return
}
meta, err := h.Store.Page.GetPageMeta(ctx, pageID)
if err == sql.ErrNoRows {
response.WriteNotFoundError(w, method, pageID)
h.Runtime.Log.Info(method + " no record")
meta = page.Meta{}
response.WriteJSON(w, meta)
return
}
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
if meta.DocumentID != documentID {
response.WriteBadRequestError(w, method, "documentID mismatch")
h.Runtime.Log.Error(method, err)
return
}
response.WriteJSON(w, meta)
}
//**************************************************
// Copy Move Page
//**************************************************
// GetMoveCopyTargets returns available documents for page copy/move axction.
func (h *Handler) GetMoveCopyTargets(w http.ResponseWriter, r *http.Request) {
method := "page.targets"
ctx := domain.GetRequestContext(r)
var d []doc.Document
var err error
d, err = h.Store.Document.DocumentList(ctx)
if len(d) == 0 {
d = []doc.Document{}
}
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
response.WriteJSON(w, d)
}
// Copy copies page to either same or different document.
func (h *Handler) Copy(w http.ResponseWriter, r *http.Request) {
method := "page.targets"
@ -790,6 +886,12 @@ func (h *Handler) Copy(w http.ResponseWriter, r *http.Request) {
return
}
// workflow check
if doc.Protection == workflow.ProtectionLock || doc.Protection == workflow.ProtectionReview {
response.WriteForbiddenError(w)
return
}
p, err := h.Store.Page.Get(ctx, pageID)
if err == sql.ErrNoRows {
response.WriteNotFoundError(w, method, documentID)
@ -1101,3 +1203,176 @@ func (h *Handler) Rollback(w http.ResponseWriter, r *http.Request) {
response.WriteJSON(w, p)
}
//**************************************************
// Bulk data fetching (reduce network traffic)
//**************************************************
// FetchPages returns all page data for given document: page, meta data, pending changes.
func (h *Handler) FetchPages(w http.ResponseWriter, r *http.Request) {
method := "page.FetchPages"
ctx := domain.GetRequestContext(r)
model := []page.BulkRequest{}
// check params
documentID := request.Param(r, "documentID")
if len(documentID) == 0 {
response.WriteMissingDataError(w, method, "documentID")
return
}
doc, err := h.Store.Document.Get(ctx, documentID)
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
// published pages and new pages awaiting approval
pages, err := h.Store.Page.GetPages(ctx, documentID)
if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
if len(pages) == 0 {
pages = []page.Page{}
}
// unpublished pages
unpublished, err := h.Store.Page.GetUnpublishedPages(ctx, documentID)
if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
if len(unpublished) == 0 {
unpublished = []page.Page{}
}
// meta for all pages
meta, err := h.Store.Page.GetDocumentPageMeta(ctx, documentID, false)
if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
if len(meta) == 0 {
meta = []page.Meta{}
}
// permissions
perms, err := h.Store.Permission.GetUserSpacePermissions(ctx, doc.LabelID)
if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err)
return
}
if len(perms) == 0 {
perms = []pm.Permission{}
}
permissions := pm.DecodeUserPermissions(perms)
roles, err := h.Store.Permission.GetUserDocumentPermissions(ctx, doc.RefID)
if err != nil && err != sql.ErrNoRows {
response.WriteServerError(w, method, err)
return
}
if len(roles) == 0 {
roles = []pm.Permission{}
}
docRoles := pm.DecodeUserDocumentPermissions(roles)
// check document view permissions
if !permissions.SpaceView && !permissions.SpaceManage && !permissions.SpaceOwner {
response.WriteForbiddenError(w)
return
}
// process published pages
for _, p := range pages {
// only send back pages that user can see
process := false
forcePending := false
if process == false && p.Status == workflow.ChangePublished {
process = true
}
if process == false && p.Status == workflow.ChangePendingNew && p.RelativeID == "" && p.UserID == ctx.UserID {
process = true
forcePending = true // user has newly created page which should be treated as pending
}
if process == false && p.Status == workflow.ChangeUnderReview && p.RelativeID == "" && p.UserID == ctx.UserID {
process = true
forcePending = true // user has newly created page which should be treated as pending
}
if process == false && p.Status == workflow.ChangeUnderReview && p.RelativeID == "" && (permissions.DocumentApprove || docRoles.DocumentRoleApprove) {
process = true
forcePending = true // user has newly created page which should be treated as pending
}
if process {
d := page.BulkRequest{}
d.Page = p
for _, m := range meta {
if p.RefID == m.PageID {
d.Meta = m
break
}
}
d.Pending = []page.PendingPage{}
// process pending pages
for _, up := range unpublished {
if up.RelativeID == p.RefID {
ud := page.PendingPage{}
ud.Page = up
for _, m := range meta {
if up.RefID == m.PageID {
ud.Meta = m
break
}
}
owner, err := h.Store.User.Get(ctx, up.UserID)
if err == nil {
ud.Owner = owner.Fullname()
}
d.Pending = append(d.Pending, ud)
}
}
// Handle situation where we need approval, and user has created new page
if forcePending && len(d.Pending) == 0 && doc.Protection == workflow.ProtectionReview {
ud := page.PendingPage{}
ud.Page = d.Page
ud.Meta = d.Meta
owner, err := h.Store.User.Get(ctx, d.Page.UserID)
if err == nil {
ud.Owner = owner.Fullname()
}
d.Pending = append(d.Pending, ud)
}
model = append(model, d)
}
}
// Attach numbers to pages, 1.1, 2.1.1 etc.
t := []page.Page{}
for _, i := range model {
t = append(t, i.Page)
}
page.Numberize(t)
for i, j := range t {
model[i].Page = j
}
// deliver payload
response.WriteJSON(w, model)
}