mirror of
https://github.com/documize/community.git
synced 2025-08-02 20:15:26 +02:00
rollback page revisions
This commit is contained in:
parent
ae03928569
commit
87d1334280
130 changed files with 39899 additions and 34 deletions
|
@ -26,6 +26,7 @@ import (
|
|||
"github.com/documize/community/core/log"
|
||||
"github.com/documize/community/core/section/provider"
|
||||
"github.com/documize/community/core/utility"
|
||||
htmldiff "github.com/documize/html-diff"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
@ -645,3 +646,232 @@ func GetDocumentPageMeta(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
writeSuccessBytes(w, json)
|
||||
}
|
||||
|
||||
/********************
|
||||
* Page Revisions
|
||||
********************/
|
||||
|
||||
// GetDocumentRevisions returns all changes for a document.
|
||||
func GetDocumentRevisions(w http.ResponseWriter, r *http.Request) {
|
||||
method := "GetDocumentPageRevisions"
|
||||
p := request.GetPersister(r)
|
||||
|
||||
params := mux.Vars(r)
|
||||
documentID := params["documentID"]
|
||||
|
||||
if len(documentID) == 0 {
|
||||
writeMissingDataError(w, method, "documentID")
|
||||
return
|
||||
}
|
||||
|
||||
if !p.CanViewDocument(documentID) {
|
||||
writeForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
revisions, err := p.GetDocumentRevisions(documentID)
|
||||
|
||||
payload, err := json.Marshal(revisions)
|
||||
|
||||
if err != nil {
|
||||
writeJSONMarshalError(w, method, "revision", err)
|
||||
return
|
||||
}
|
||||
|
||||
writeSuccessBytes(w, payload)
|
||||
}
|
||||
|
||||
// GetDocumentPageRevisions returns all changes for a given page.
|
||||
func GetDocumentPageRevisions(w http.ResponseWriter, r *http.Request) {
|
||||
method := "GetDocumentPageRevisions"
|
||||
p := request.GetPersister(r)
|
||||
|
||||
params := mux.Vars(r)
|
||||
documentID := params["documentID"]
|
||||
|
||||
if len(documentID) == 0 {
|
||||
writeMissingDataError(w, method, "documentID")
|
||||
return
|
||||
}
|
||||
|
||||
if !p.CanViewDocument(documentID) {
|
||||
writeForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
pageID := params["pageID"]
|
||||
|
||||
if len(pageID) == 0 {
|
||||
writeMissingDataError(w, method, "pageID")
|
||||
return
|
||||
}
|
||||
|
||||
revisions, err := p.GetPageRevisions(pageID)
|
||||
|
||||
payload, err := json.Marshal(revisions)
|
||||
|
||||
if err != nil {
|
||||
writeJSONMarshalError(w, method, "revision", err)
|
||||
return
|
||||
}
|
||||
|
||||
writeSuccessBytes(w, payload)
|
||||
}
|
||||
|
||||
// GetDocumentPageDiff returns HTML diff between two revisions of a given page.
|
||||
func GetDocumentPageDiff(w http.ResponseWriter, r *http.Request) {
|
||||
method := "GetDocumentPageDiff"
|
||||
p := request.GetPersister(r)
|
||||
|
||||
params := mux.Vars(r)
|
||||
documentID := params["documentID"]
|
||||
|
||||
if len(documentID) == 0 {
|
||||
writeMissingDataError(w, method, "documentID")
|
||||
return
|
||||
}
|
||||
|
||||
if !p.CanViewDocument(documentID) {
|
||||
writeForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
pageID := params["pageID"]
|
||||
|
||||
if len(pageID) == 0 {
|
||||
writeMissingDataError(w, method, "pageID")
|
||||
return
|
||||
}
|
||||
|
||||
revisionID := params["revisionID"]
|
||||
|
||||
if len(revisionID) == 0 {
|
||||
writeMissingDataError(w, method, "revisionID")
|
||||
return
|
||||
}
|
||||
|
||||
page, err := p.GetPage(pageID)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
writeNotFoundError(w, method, revisionID)
|
||||
return
|
||||
}
|
||||
|
||||
revision, err := p.GetPageRevision(revisionID)
|
||||
|
||||
latestHTML := page.Body
|
||||
previousHTML := revision.Body
|
||||
var result []byte
|
||||
|
||||
var cfg = &htmldiff.Config{
|
||||
Granularity: 5,
|
||||
InsertedSpan: []htmldiff.Attribute{{Key: "style", Val: "background-color: palegreen;"}},
|
||||
DeletedSpan: []htmldiff.Attribute{{Key: "style", Val: "background-color: lightpink; text-decoration: line-through;"}},
|
||||
ReplacedSpan: []htmldiff.Attribute{{Key: "style", Val: "background-color: lightskyblue;"}},
|
||||
CleanTags: []string{"documize"},
|
||||
}
|
||||
res, err := cfg.HTMLdiff([]string{latestHTML, previousHTML})
|
||||
if err != nil {
|
||||
writeServerError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
result = []byte(res[0])
|
||||
|
||||
_, err = w.Write(result)
|
||||
log.IfErr(err)
|
||||
}
|
||||
|
||||
// RollbackDocumentPage rolls-back to a specific page revision.
|
||||
func RollbackDocumentPage(w http.ResponseWriter, r *http.Request) {
|
||||
method := "RollbackDocumentPage"
|
||||
p := request.GetPersister(r)
|
||||
|
||||
params := mux.Vars(r)
|
||||
documentID := params["documentID"]
|
||||
|
||||
if len(documentID) == 0 {
|
||||
writeMissingDataError(w, method, "documentID")
|
||||
return
|
||||
}
|
||||
|
||||
pageID := params["pageID"]
|
||||
|
||||
if len(pageID) == 0 {
|
||||
writeMissingDataError(w, method, "pageID")
|
||||
return
|
||||
}
|
||||
|
||||
revisionID := params["revisionID"]
|
||||
|
||||
if len(revisionID) == 0 {
|
||||
writeMissingDataError(w, method, "revisionID")
|
||||
return
|
||||
}
|
||||
|
||||
if !p.CanChangeDocument(documentID) {
|
||||
writeForbiddenError(w)
|
||||
return
|
||||
}
|
||||
|
||||
tx, err := request.Db.Beginx()
|
||||
if err != nil {
|
||||
writeTransactionError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
p.Context.Transaction = tx
|
||||
|
||||
// fetch page
|
||||
page, err := p.GetPage(pageID)
|
||||
if err != nil {
|
||||
writeGeneralSQLError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
// fetch page meta
|
||||
meta, err := p.GetPageMeta(pageID)
|
||||
if err != nil {
|
||||
writeGeneralSQLError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
// fetch revision
|
||||
revision, err := p.GetPageRevision(revisionID)
|
||||
if err != nil {
|
||||
writeGeneralSQLError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
// roll back page
|
||||
page.Body = revision.Body
|
||||
refID := util.UniqueID()
|
||||
|
||||
err = p.UpdatePage(page, refID, p.Context.UserID, false)
|
||||
if err != nil {
|
||||
log.IfErr(tx.Rollback())
|
||||
writeGeneralSQLError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
// roll back page meta
|
||||
meta.Config = revision.Config
|
||||
meta.RawBody = revision.RawBody
|
||||
|
||||
err = p.UpdatePageMeta(meta, false)
|
||||
if err != nil {
|
||||
log.IfErr(tx.Rollback())
|
||||
writeGeneralSQLError(w, method, err)
|
||||
return
|
||||
}
|
||||
|
||||
log.IfErr(tx.Commit())
|
||||
|
||||
payload, err := json.Marshal(page)
|
||||
if err != nil {
|
||||
writeJSONMarshalError(w, method, "revision", err)
|
||||
return
|
||||
}
|
||||
|
||||
writeSuccessBytes(w, payload)
|
||||
}
|
||||
|
|
|
@ -159,6 +159,11 @@ func init() {
|
|||
log.IfErr(Add(RoutePrefixPrivate, "documents/{documentID}/pages/level", []string{"POST", "OPTIONS"}, nil, ChangeDocumentPageLevel))
|
||||
log.IfErr(Add(RoutePrefixPrivate, "documents/{documentID}/pages/sequence", []string{"POST", "OPTIONS"}, nil, ChangeDocumentPageSequence))
|
||||
log.IfErr(Add(RoutePrefixPrivate, "documents/{documentID}/pages/batch", []string{"POST", "OPTIONS"}, nil, GetDocumentPagesBatch))
|
||||
log.IfErr(Add(RoutePrefixPrivate, "documents/{documentID}/pages/{pageID}/revisions", []string{"GET", "OPTIONS"}, nil, GetDocumentPageRevisions))
|
||||
log.IfErr(Add(RoutePrefixPrivate, "documents/{documentID}/pages/{pageID}/revisions/{revisionID}", []string{"GET", "OPTIONS"}, nil, GetDocumentPageDiff))
|
||||
log.IfErr(Add(RoutePrefixPrivate, "documents/{documentID}/pages/{pageID}/revisions/{revisionID}", []string{"POST", "OPTIONS"}, nil, RollbackDocumentPage))
|
||||
log.IfErr(Add(RoutePrefixPrivate, "documents/{documentID}/revisions", []string{"GET", "OPTIONS"}, nil, GetDocumentRevisions))
|
||||
|
||||
log.IfErr(Add(RoutePrefixPrivate, "documents/{documentID}/pages", []string{"GET", "OPTIONS"}, nil, GetDocumentPages))
|
||||
log.IfErr(Add(RoutePrefixPrivate, "documents/{documentID}/pages/{pageID}", []string{"PUT", "OPTIONS"}, nil, UpdateDocumentPage))
|
||||
log.IfErr(Add(RoutePrefixPrivate, "documents/{documentID}/pages/{pageID}", []string{"DELETE", "OPTIONS"}, nil, DeleteDocumentPage))
|
||||
|
|
|
@ -238,6 +238,27 @@ func (p *PageMeta) SetDefaults() {
|
|||
}
|
||||
}
|
||||
|
||||
// Revision holds the previous version of a Page.
|
||||
type Revision struct {
|
||||
BaseEntity
|
||||
OrgID string `json:"orgId"`
|
||||
DocumentID string `json:"documentId"`
|
||||
PageID string `json:"pageId"`
|
||||
OwnerID string `json:"ownerId"`
|
||||
UserID string `json:"userId"`
|
||||
ContentType string `json:"contentType"`
|
||||
PageType string `json:"pageType"`
|
||||
Title string `json:"title"`
|
||||
Body string `json:"body"`
|
||||
RawBody string `json:"rawBody"`
|
||||
Config string `json:"config"`
|
||||
Email string `json:"email"`
|
||||
Firstname string `json:"firstname"`
|
||||
Lastname string `json:"lastname"`
|
||||
Initials string `json:"initials"`
|
||||
Revisions int `json:"revisions"`
|
||||
}
|
||||
|
||||
// DocumentMeta details who viewed the document.
|
||||
type DocumentMeta struct {
|
||||
Viewers []DocumentMetaViewer `json:"viewers"`
|
||||
|
|
|
@ -258,36 +258,6 @@ func (p *Persister) UpdatePage(page entity.Page, refID, userID string, skipRevis
|
|||
}
|
||||
}
|
||||
|
||||
//if page.Level == 1 { // may need to update the document name
|
||||
//var doc entity.Document
|
||||
|
||||
//stmt4, err := p.Context.Transaction.Preparex("SELECT id, refid, orgid, labelid, job, location, title, excerpt, slug, tags, template, created, revised FROM document WHERE refid=?")
|
||||
//defer utility.Close(stmt4)
|
||||
|
||||
//if err != nil {
|
||||
//log.Error(fmt.Sprintf("Unable to prepare pagemanager doc query for Id %s", page.DocumentID), err)
|
||||
//return err
|
||||
//}
|
||||
|
||||
//err = stmt4.Get(&doc, page.DocumentID)
|
||||
|
||||
//if err != nil {
|
||||
//log.Error(fmt.Sprintf("Unable to execute pagemanager document query for Id %s", page.DocumentID), err)
|
||||
//return err
|
||||
//}
|
||||
|
||||
//if doc.Title != page.Title {
|
||||
//doc.Title = page.Title
|
||||
//doc.Revised = page.Revised
|
||||
//err = p.UpdateDocument(doc)
|
||||
|
||||
//if err != nil {
|
||||
//log.Error(fmt.Sprintf("Unable to update document when page 1 altered DocumentId %s", page.DocumentID), err)
|
||||
//return err
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
|
||||
// find any content links in the HTML
|
||||
links := util.GetContentLinks(page.Body)
|
||||
|
||||
|
@ -336,6 +306,7 @@ func (p *Persister) UpdatePage(page entity.Page, refID, userID string, skipRevis
|
|||
func (p *Persister) UpdatePageMeta(meta entity.PageMeta, updateUserID bool) (err error) {
|
||||
err = nil
|
||||
meta.Revised = time.Now().UTC()
|
||||
|
||||
if updateUserID {
|
||||
meta.UserID = p.Context.UserID
|
||||
}
|
||||
|
@ -424,6 +395,9 @@ func (p *Persister) DeletePage(documentID, pageID string) (rows int64, err error
|
|||
// mark as orphan links to this page
|
||||
err = p.MarkOrphanPageLink(pageID)
|
||||
|
||||
// nuke revisions
|
||||
_, err = p.DeletePageRevisions(pageID)
|
||||
|
||||
p.Base.Audit(p.Context, "remove-page", documentID, pageID)
|
||||
}
|
||||
|
||||
|
@ -469,3 +443,68 @@ func (p *Persister) GetDocumentPageMeta(documentID string, externalSourceOnly bo
|
|||
|
||||
return
|
||||
}
|
||||
|
||||
/********************
|
||||
* Page Revisions
|
||||
********************/
|
||||
|
||||
// GetPageRevision returns the revisionID page revision record.
|
||||
func (p *Persister) GetPageRevision(revisionID string) (revision entity.Revision, err error) {
|
||||
stmt, err := Db.Preparex("SELECT id, refid, orgid, documentid, ownerid, pageid, userid, contenttype, pagetype, title, body, rawbody, coalesce(config,JSON_UNQUOTE('{}')) as config, created, revised FROM revision WHERE orgid=? and refid=?")
|
||||
defer utility.Close(stmt)
|
||||
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("Unable to prepare select for revision %s", revisionID), err)
|
||||
return
|
||||
}
|
||||
|
||||
err = stmt.Get(&revision, p.Context.OrgID, revisionID)
|
||||
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("Unable to execute select for revision %s", revisionID), err)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetDocumentRevisions returns a slice of page revision records for a given document, in the order they were created.
|
||||
// Then audits that the get-page-revisions action has occurred.
|
||||
func (p *Persister) GetDocumentRevisions(documentID string) (revisions []entity.Revision, err error) {
|
||||
err = Db.Select(&revisions, "SELECT a.id, a.refid, a.orgid, a.documentid, a.ownerid, a.pageid, a.userid, a.contenttype, a.pagetype, a.title, /*a.body, a.rawbody, a.config,*/ a.created, a.revised, coalesce(b.email,'') as email, coalesce(b.firstname,'') as firstname, coalesce(b.lastname,'') as lastname, coalesce(b.initials,'') as initials, coalesce(p.revisions, 0) as revisions FROM revision a LEFT JOIN user b ON a.userid=b.refid LEFT JOIN page p ON a.pageid=p.refid WHERE a.orgid=? AND a.documentid=? AND a.pagetype='section' ORDER BY a.id DESC", p.Context.OrgID, documentID)
|
||||
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("Unable to execute select revisions for org %s and document %s", p.Context.OrgID, documentID), err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(revisions) == 0 {
|
||||
revisions = []entity.Revision{}
|
||||
}
|
||||
|
||||
p.Base.Audit(p.Context, "get-document-revisions", documentID, "")
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// GetPageRevisions returns a slice of page revision records for a given pageID, in the order they were created.
|
||||
// Then audits that the get-page-revisions action has occurred.
|
||||
func (p *Persister) GetPageRevisions(pageID string) (revisions []entity.Revision, err error) {
|
||||
err = Db.Select(&revisions, "SELECT a.id, a.refid, a.orgid, a.documentid, a.ownerid, a.pageid, a.userid, a.contenttype, a.pagetype, a.title, /*a.body, a.rawbody, a.config,*/ a.created, a.revised, coalesce(b.email,'') as email, coalesce(b.firstname,'') as firstname, coalesce(b.lastname,'') as lastname, coalesce(b.initials,'') as initials FROM revision a LEFT JOIN user b ON a.userid=b.refid WHERE a.orgid=? AND a.pageid=? AND a.pagetype='section' ORDER BY a.id DESC", p.Context.OrgID, pageID)
|
||||
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("Unable to execute select revisions for org %s and page %s", p.Context.OrgID, pageID), err)
|
||||
return
|
||||
}
|
||||
|
||||
p.Base.Audit(p.Context, "get-page-revisions", "", pageID)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// DeletePageRevisions deletes all of the page revision records for a given pageID.
|
||||
func (p *Persister) DeletePageRevisions(pageID string) (rows int64, err error) {
|
||||
rows, err = p.Base.DeleteWhere(p.Context.Transaction, fmt.Sprintf("DELETE FROM revision WHERE orgid='%s' AND pageid='%s'", p.Context.OrgID, pageID))
|
||||
|
||||
return
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue