diff --git a/domain/attachment/store.go b/domain/attachment/store.go
index b6ce964a..78a1f5cd 100644
--- a/domain/attachment/store.go
+++ b/domain/attachment/store.go
@@ -34,8 +34,10 @@ func (s Store) Add(ctx domain.RequestContext, a attachment.Attachment) (err erro
a.OrgID = ctx.OrgID
a.Created = time.Now().UTC()
a.Revised = time.Now().UTC()
- bits := strings.Split(a.Filename, ".")
- a.Extension = bits[len(bits)-1]
+ if len(a.Extension) == 0 {
+ bits := strings.Split(a.Filename, ".")
+ a.Extension = bits[len(bits)-1]
+ }
_, err = ctx.Transaction.Exec(s.Bind("INSERT INTO dmz_doc_attachment (c_refid, c_orgid, c_docid, c_sectionid, c_job, c_fileid, c_filename, c_data, c_extension, c_created, c_revised) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"),
a.RefID, a.OrgID, a.DocumentID, a.SectionID, a.Job, a.FileID, a.Filename, a.Data, a.Extension, a.Created, a.Revised)
diff --git a/domain/document/endpoint.go b/domain/document/endpoint.go
index 2ceedb54..a072ed06 100644
--- a/domain/document/endpoint.go
+++ b/domain/document/endpoint.go
@@ -24,6 +24,7 @@ import (
"github.com/documize/community/core/response"
"github.com/documize/community/core/streamutil"
"github.com/documize/community/core/stringutil"
+ "github.com/documize/community/core/uniqueid"
"github.com/documize/community/domain"
"github.com/documize/community/domain/organization"
"github.com/documize/community/domain/permission"
@@ -34,6 +35,7 @@ import (
"github.com/documize/community/model/audit"
"github.com/documize/community/model/doc"
"github.com/documize/community/model/link"
+ "github.com/documize/community/model/page"
pm "github.com/documize/community/model/permission"
"github.com/documize/community/model/search"
"github.com/documize/community/model/space"
@@ -766,3 +768,216 @@ func (h *Handler) Export(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(export))
}
+
+// Duplicate makes a copy of a document.
+// Name of new document is required.
+func (h *Handler) Duplicate(w http.ResponseWriter, r *http.Request) {
+ method := "document.Duplicate"
+ ctx := domain.GetRequestContext(r)
+
+ // Parse payload
+ defer streamutil.Close(r.Body)
+ body, err := ioutil.ReadAll(r.Body)
+ if err != nil {
+ response.WriteBadRequestError(w, method, err.Error())
+ h.Runtime.Log.Error(method, err)
+ return
+ }
+
+ m := doc.DuplicateModel{}
+ err = json.Unmarshal(body, &m)
+ if err != nil {
+ response.WriteBadRequestError(w, method, err.Error())
+ h.Runtime.Log.Error(method, err)
+ return
+ }
+
+ // Check permissions
+ if !permission.CanViewDocument(ctx, *h.Store, m.DocumentID) {
+ response.WriteForbiddenError(w)
+ return
+ }
+ if !permission.CanUploadDocument(ctx, *h.Store, m.SpaceID) {
+ response.WriteForbiddenError(w)
+ return
+ }
+
+ ctx.Transaction, err = h.Runtime.Db.Beginx()
+ if err != nil {
+ response.WriteServerError(w, method, err)
+ h.Runtime.Log.Error(method, err)
+ return
+ }
+
+ // Get document to be duplicated.
+ d, err := h.Store.Document.Get(ctx, m.DocumentID)
+ if err != nil {
+ ctx.Transaction.Rollback()
+ response.WriteServerError(w, method, err)
+ h.Runtime.Log.Error(method, err)
+ return
+ }
+
+ // Assign new ID and remove versioning info.
+ d.RefID = uniqueid.Generate()
+ d.GroupID = ""
+ d.Name = m.Name
+
+ // Fetch doc attachments, links.
+ da, err := h.Store.Attachment.GetAttachmentsWithData(ctx, m.DocumentID)
+ if err != nil {
+ ctx.Transaction.Rollback()
+ response.WriteServerError(w, method, err)
+ h.Runtime.Log.Error(method, err)
+ return
+ }
+ dl, err := h.Store.Link.GetDocumentOutboundLinks(ctx, m.DocumentID)
+ if err != nil {
+ ctx.Transaction.Rollback()
+ response.WriteServerError(w, method, err)
+ h.Runtime.Log.Error(method, err)
+ return
+ }
+
+ // Fetch published and unpublished sections.
+ pages, err := h.Store.Page.GetPages(ctx, m.DocumentID)
+ if err != nil && err != sql.ErrNoRows {
+ ctx.Transaction.Rollback()
+ response.WriteServerError(w, method, err)
+ h.Runtime.Log.Error(method, err)
+ return
+ }
+ if len(pages) == 0 {
+ pages = []page.Page{}
+ }
+ unpublished, err := h.Store.Page.GetUnpublishedPages(ctx, m.DocumentID)
+ if err != nil && err != sql.ErrNoRows {
+ ctx.Transaction.Rollback()
+ response.WriteServerError(w, method, err)
+ h.Runtime.Log.Error(method, err)
+ return
+ }
+ if len(unpublished) == 0 {
+ unpublished = []page.Page{}
+ }
+ pages = append(pages, unpublished...)
+ meta, err := h.Store.Page.GetDocumentPageMeta(ctx, m.DocumentID, false)
+ if err != nil && err != sql.ErrNoRows {
+ ctx.Transaction.Rollback()
+ response.WriteServerError(w, method, err)
+ h.Runtime.Log.Error(method, err)
+ return
+ }
+ if len(meta) == 0 {
+ meta = []page.Meta{}
+ }
+
+ // Duplicate the complete document starting with the document.
+ err = h.Store.Document.Add(ctx, d)
+ if err != nil {
+ ctx.Transaction.Rollback()
+ response.WriteServerError(w, method, err)
+ h.Runtime.Log.Error(method, err)
+ return
+ }
+ // Attachments
+ for i := range da {
+ da[i].RefID = uniqueid.Generate()
+ da[i].DocumentID = d.RefID
+
+ err = h.Store.Attachment.Add(ctx, da[i])
+ if err != nil {
+ ctx.Transaction.Rollback()
+ response.WriteServerError(w, method, err)
+ h.Runtime.Log.Error(method, err)
+ return
+ }
+ }
+ // Links
+ for l := range dl {
+ dl[l].SourceDocumentID = d.RefID
+ dl[l].RefID = uniqueid.Generate()
+
+ err = h.Store.Link.Add(ctx, dl[l])
+ if err != nil {
+ ctx.Transaction.Rollback()
+ response.WriteServerError(w, method, err)
+ h.Runtime.Log.Error(method, err)
+ return
+ }
+ }
+ // Sections
+ for j := range pages {
+ // Get meta for section
+ sm := page.Meta{}
+ for k := range meta {
+ if meta[k].SectionID == pages[j].RefID {
+ sm = meta[k]
+ break
+ }
+ }
+
+ // Get attachments for section.
+ sa, err := h.Store.Attachment.GetSectionAttachments(ctx, pages[j].RefID)
+ if err != nil {
+ ctx.Transaction.Rollback()
+ response.WriteServerError(w, method, err)
+ h.Runtime.Log.Error(method, err)
+ return
+ }
+
+ pages[j].RefID = uniqueid.Generate()
+ pages[j].DocumentID = d.RefID
+ sm.DocumentID = d.RefID
+ sm.SectionID = pages[j].RefID
+
+ err = h.Store.Page.Add(ctx, page.NewPage{Page: pages[j], Meta: sm})
+ if err != nil {
+ ctx.Transaction.Rollback()
+ response.WriteServerError(w, method, err)
+ h.Runtime.Log.Error(method, err)
+ return
+ }
+
+ // Now add any section attachments.
+ for n := range sa {
+ sa[n].RefID = uniqueid.Generate()
+ sa[n].DocumentID = d.RefID
+ sa[n].SectionID = pages[j].RefID
+
+ err = h.Store.Attachment.Add(ctx, sa[n])
+ if err != nil {
+ ctx.Transaction.Rollback()
+ response.WriteServerError(w, method, err)
+ h.Runtime.Log.Error(method, err)
+ return
+ }
+ }
+ }
+
+ // Record activity and finish.
+ h.Store.Activity.RecordUserActivity(ctx, activity.UserActivity{
+ SpaceID: d.SpaceID,
+ DocumentID: d.RefID,
+ SourceType: activity.SourceTypeDocument,
+ ActivityType: activity.TypeCreated})
+
+ ctx.Transaction.Commit()
+
+ h.Store.Audit.Record(ctx, audit.EventTypeDocumentAdd)
+
+ // Update search index if published.
+ if d.Lifecycle == workflow.LifecycleLive {
+ a, _ := h.Store.Attachment.GetAttachments(ctx, d.RefID)
+ go h.Indexer.IndexDocument(ctx, d, a)
+
+ pages, _ := h.Store.Page.GetPages(ctx, d.RefID)
+ for i := range pages {
+ go h.Indexer.IndexContent(ctx, pages[i])
+ }
+ } else {
+ go h.Indexer.DeleteDocument(ctx, d.RefID)
+ }
+
+ response.WriteEmpty(w)
+}
diff --git a/domain/link/store.go b/domain/link/store.go
index baf7aeb5..cdbf71de 100644
--- a/domain/link/store.go
+++ b/domain/link/store.go
@@ -76,13 +76,13 @@ func (s Store) GetDocumentOutboundLinks(ctx domain.RequestContext, documentID st
WHERE c_orgid=? AND c_sourcedocid=?`),
ctx.OrgID, documentID)
- if err != nil && err != sql.ErrNoRows {
- err = errors.Wrap(err, "select document oubound links")
- return
- }
- if len(links) == 0 {
+ if err == sql.ErrNoRows || len(links) == 0 {
+ err = nil
links = []link.Link{}
}
+ if err != nil {
+ err = errors.Wrap(err, "select document oubound links")
+ }
return
}
diff --git a/gui/app/components/document/document-toolbar.js b/gui/app/components/document/document-toolbar.js
index 5c8ace31..8280ddca 100644
--- a/gui/app/components/document/document-toolbar.js
+++ b/gui/app/components/document/document-toolbar.js
@@ -48,6 +48,7 @@ export default Component.extend(ModalMixin, AuthMixin, Notifier, {
this.get('permissions.documentEdit')) return true;
}),
+ duplicateName: '',
init() {
this._super(...arguments);
@@ -57,7 +58,7 @@ export default Component.extend(ModalMixin, AuthMixin, Notifier, {
pinId: '',
newName: ''
};
-
+
this.saveTemplate = {
name: '',
description: ''
@@ -88,6 +89,10 @@ export default Component.extend(ModalMixin, AuthMixin, Notifier, {
this.modalOpen("#document-template-modal", {show:true}, "#new-template-name");
},
+ onShowDuplicateModal() {
+ this.modalOpen("#document-duplicate-modal", {show:true}, "#duplicate-name");
+ },
+
onShowDeleteModal() {
this.modalOpen("#document-delete-modal", {show:true});
},
@@ -183,6 +188,25 @@ export default Component.extend(ModalMixin, AuthMixin, Notifier, {
return true;
},
+ onDuplicate() {
+ let name = this.get('duplicateName');
+
+ if (_.isEmpty(name)) {
+ $("#duplicate-name").addClass("is-invalid").focus();
+ return;
+ }
+
+ $("#duplicate-name").removeClass("is-invalid");
+
+ this.set('duplicateName', '');
+
+ this.get('onDuplicate')(name);
+
+ this.modalClose('#document-duplicate-modal');
+
+ return true;
+ },
+
onExport() {
let spec = {
spaceId: this.get('document.spaceId'),
diff --git a/gui/app/pods/document/index/controller.js b/gui/app/pods/document/index/controller.js
index 4777c3f9..3fc60131 100644
--- a/gui/app/pods/document/index/controller.js
+++ b/gui/app/pods/document/index/controller.js
@@ -189,6 +189,12 @@ export default Controller.extend(Notifier, {
});
},
+ onDuplicate(name) {
+ this.get('documentService').duplicate(this.get('folder.id'), this.get('document.id'), name).then(() => {
+ this.notifySuccess('Duplicated');
+ });
+ },
+
onPageSequenceChange(currentPageId, changes) {
this.set('currentPageId', currentPageId);
diff --git a/gui/app/pods/document/index/template.hbs b/gui/app/pods/document/index/template.hbs
index f21d2e0e..79887efd 100644
--- a/gui/app/pods/document/index/template.hbs
+++ b/gui/app/pods/document/index/template.hbs
@@ -17,6 +17,7 @@
refresh=(action "refresh")
onSaveTemplate=(action "onSaveTemplate")
onSaveDocument=(action "onSaveDocument")
+ onDuplicate=(action "onDuplicate")
onDocumentDelete=(action "onDocumentDelete")}}
@@ -63,7 +64,7 @@