2017-08-02 11:46:45 +01:00
|
|
|
// Copyright 2016 Documize Inc. <legal@documize.com>. All rights reserved.
|
|
|
|
//
|
|
|
|
// This software (Documize Community Edition) is licensed under
|
|
|
|
// GNU AGPL v3 http://www.gnu.org/licenses/agpl-3.0.en.html
|
|
|
|
//
|
|
|
|
// You can operate outside the AGPL restrictions by purchasing
|
|
|
|
// Documize Enterprise Edition and obtaining a commercial license
|
|
|
|
// by contacting <sales@documize.com>.
|
|
|
|
//
|
|
|
|
// https://documize.com
|
|
|
|
|
|
|
|
package template
|
|
|
|
|
|
|
|
import (
|
|
|
|
"database/sql"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
|
|
|
|
|
|
|
"github.com/documize/community/core/env"
|
|
|
|
"github.com/documize/community/core/event"
|
|
|
|
"github.com/documize/community/core/request"
|
|
|
|
"github.com/documize/community/core/response"
|
|
|
|
"github.com/documize/community/core/secrets"
|
|
|
|
"github.com/documize/community/core/streamutil"
|
|
|
|
"github.com/documize/community/core/stringutil"
|
|
|
|
"github.com/documize/community/core/uniqueid"
|
|
|
|
"github.com/documize/community/domain"
|
2017-09-18 17:53:42 +01:00
|
|
|
"github.com/documize/community/domain/permission"
|
2017-08-15 19:41:44 +01:00
|
|
|
indexer "github.com/documize/community/domain/search"
|
2017-08-02 11:46:45 +01:00
|
|
|
"github.com/documize/community/model/attachment"
|
|
|
|
"github.com/documize/community/model/audit"
|
|
|
|
"github.com/documize/community/model/doc"
|
|
|
|
"github.com/documize/community/model/page"
|
2017-09-18 17:53:42 +01:00
|
|
|
pm "github.com/documize/community/model/permission"
|
2017-08-02 11:46:45 +01:00
|
|
|
"github.com/documize/community/model/template"
|
2018-03-14 13:38:37 +00:00
|
|
|
"github.com/documize/community/model/workflow"
|
2017-08-02 11:46:45 +01:00
|
|
|
uuid "github.com/nu7hatch/gouuid"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Handler contains the runtime information such as logging and database.
|
|
|
|
type Handler struct {
|
|
|
|
Runtime *env.Runtime
|
|
|
|
Store *domain.Store
|
2017-08-15 19:41:44 +01:00
|
|
|
Indexer indexer.Indexer
|
2017-08-02 11:46:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// SavedList returns all templates saved by the user
|
|
|
|
func (h *Handler) SavedList(w http.ResponseWriter, r *http.Request) {
|
|
|
|
method := "template.saved"
|
|
|
|
ctx := domain.GetRequestContext(r)
|
|
|
|
|
2017-08-17 09:37:33 +01:00
|
|
|
folderID := request.Param(r, "folderID")
|
|
|
|
if len(folderID) == 0 {
|
|
|
|
response.WriteMissingDataError(w, method, "folderID")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
documents, err := h.Store.Document.TemplatesBySpace(ctx, folderID)
|
2017-08-02 11:46:45 +01:00
|
|
|
if err != nil {
|
|
|
|
response.WriteServerError(w, method, err)
|
2017-08-03 10:00:24 +01:00
|
|
|
h.Runtime.Log.Error(method, err)
|
2017-08-02 11:46:45 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
templates := []template.Template{}
|
|
|
|
|
|
|
|
for _, d := range documents {
|
|
|
|
var t = template.Template{}
|
|
|
|
t.ID = d.RefID
|
|
|
|
t.Title = d.Title
|
|
|
|
t.Description = d.Excerpt
|
|
|
|
t.Author = ""
|
|
|
|
t.Dated = d.Created
|
|
|
|
t.Type = template.TypePrivate
|
|
|
|
|
2017-08-17 09:37:33 +01:00
|
|
|
if d.LabelID == folderID {
|
|
|
|
templates = append(templates, t)
|
|
|
|
}
|
2017-08-02 11:46:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
response.WriteJSON(w, templates)
|
|
|
|
}
|
|
|
|
|
|
|
|
// SaveAs saves existing document as a template.
|
|
|
|
func (h *Handler) SaveAs(w http.ResponseWriter, r *http.Request) {
|
|
|
|
method := "template.saved"
|
|
|
|
ctx := domain.GetRequestContext(r)
|
|
|
|
|
|
|
|
if !h.Runtime.Product.License.IsValid() {
|
|
|
|
response.WriteBadLicense(w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var model struct {
|
|
|
|
DocumentID string
|
|
|
|
Name string
|
|
|
|
Excerpt string
|
|
|
|
}
|
|
|
|
|
|
|
|
defer streamutil.Close(r.Body)
|
|
|
|
body, err := ioutil.ReadAll(r.Body)
|
|
|
|
if err != nil {
|
|
|
|
response.WriteBadRequestError(w, method, "Bad payload")
|
2017-08-03 10:00:24 +01:00
|
|
|
h.Runtime.Log.Error(method, err)
|
2017-08-02 11:46:45 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
err = json.Unmarshal(body, &model)
|
|
|
|
if err != nil {
|
|
|
|
response.WriteBadRequestError(w, method, "unmarshal")
|
2017-08-03 10:00:24 +01:00
|
|
|
h.Runtime.Log.Error(method, err)
|
2017-08-02 11:46:45 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-09-19 17:58:33 +01:00
|
|
|
// Duplicate document
|
|
|
|
doc, err := h.Store.Document.Get(ctx, model.DocumentID)
|
2017-08-02 11:46:45 +01:00
|
|
|
if err != nil {
|
|
|
|
response.WriteServerError(w, method, err)
|
2017-08-03 10:00:24 +01:00
|
|
|
h.Runtime.Log.Error(method, err)
|
2017-08-02 11:46:45 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-09-19 17:58:33 +01:00
|
|
|
if !permission.HasPermission(ctx, *h.Store, doc.LabelID, pm.DocumentTemplate) {
|
|
|
|
response.WriteForbiddenError(w)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// DB transaction
|
|
|
|
ctx.Transaction, err = h.Runtime.Db.Beginx()
|
2017-08-02 11:46:45 +01:00
|
|
|
if err != nil {
|
|
|
|
response.WriteServerError(w, method, err)
|
2017-08-03 10:00:24 +01:00
|
|
|
h.Runtime.Log.Error(method, err)
|
2017-08-02 11:46:45 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
docID := uniqueid.Generate()
|
|
|
|
doc.Template = true
|
|
|
|
doc.Title = model.Name
|
|
|
|
doc.Excerpt = model.Excerpt
|
|
|
|
doc.RefID = docID
|
|
|
|
doc.ID = 0
|
|
|
|
doc.Template = true
|
2018-03-20 14:17:11 +00:00
|
|
|
doc.Lifecycle = workflow.LifecycleLive
|
2017-08-02 11:46:45 +01:00
|
|
|
|
|
|
|
// Duplicate pages and associated meta
|
|
|
|
pages, err := h.Store.Page.GetPages(ctx, model.DocumentID)
|
|
|
|
var pageModel []page.NewPage
|
|
|
|
|
|
|
|
if err != nil {
|
2018-02-04 15:43:57 +00:00
|
|
|
ctx.Transaction.Rollback()
|
2017-08-02 11:46:45 +01:00
|
|
|
response.WriteServerError(w, method, err)
|
2017-08-03 10:00:24 +01:00
|
|
|
h.Runtime.Log.Error(method, err)
|
2017-08-02 11:46:45 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, p := range pages {
|
|
|
|
p.DocumentID = docID
|
|
|
|
p.ID = 0
|
|
|
|
|
|
|
|
meta, err2 := h.Store.Page.GetPageMeta(ctx, p.RefID)
|
|
|
|
if err2 != nil {
|
2018-02-04 15:43:57 +00:00
|
|
|
ctx.Transaction.Rollback()
|
2017-08-02 11:46:45 +01:00
|
|
|
response.WriteServerError(w, method, err2)
|
2017-08-03 10:00:24 +01:00
|
|
|
h.Runtime.Log.Error(method, err)
|
2017-08-02 11:46:45 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
pageID := uniqueid.Generate()
|
|
|
|
p.RefID = pageID
|
|
|
|
meta.PageID = pageID
|
|
|
|
meta.DocumentID = docID
|
|
|
|
|
|
|
|
m := page.NewPage{}
|
|
|
|
m.Page = p
|
|
|
|
m.Meta = meta
|
|
|
|
|
|
|
|
pageModel = append(pageModel, m)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Duplicate attachments
|
|
|
|
attachments, _ := h.Store.Attachment.GetAttachments(ctx, model.DocumentID)
|
|
|
|
for i, a := range attachments {
|
|
|
|
a.DocumentID = docID
|
|
|
|
a.RefID = uniqueid.Generate()
|
|
|
|
a.ID = 0
|
|
|
|
attachments[i] = a
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now create the template: document, attachments, pages and their meta
|
|
|
|
err = h.Store.Document.Add(ctx, doc)
|
|
|
|
if err != nil {
|
|
|
|
ctx.Transaction.Rollback()
|
|
|
|
response.WriteServerError(w, method, err)
|
2017-08-03 10:00:24 +01:00
|
|
|
h.Runtime.Log.Error(method, err)
|
2017-08-02 11:46:45 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, a := range attachments {
|
|
|
|
err = h.Store.Attachment.Add(ctx, a)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
ctx.Transaction.Rollback()
|
|
|
|
response.WriteServerError(w, method, err)
|
2017-08-03 10:00:24 +01:00
|
|
|
h.Runtime.Log.Error(method, err)
|
2017-08-02 11:46:45 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, m := range pageModel {
|
|
|
|
err = h.Store.Page.Add(ctx, m)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
ctx.Transaction.Rollback()
|
|
|
|
response.WriteServerError(w, method, err)
|
2017-08-03 10:00:24 +01:00
|
|
|
h.Runtime.Log.Error(method, err)
|
2017-08-02 11:46:45 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Commit and return new document template
|
|
|
|
ctx.Transaction.Commit()
|
|
|
|
|
2018-02-04 15:43:57 +00:00
|
|
|
h.Store.Audit.Record(ctx, audit.EventTypeTemplateAdd)
|
|
|
|
|
2017-08-02 11:46:45 +01:00
|
|
|
doc, err = h.Store.Document.Get(ctx, docID)
|
|
|
|
if err != nil {
|
|
|
|
response.WriteServerError(w, method, err)
|
2017-08-03 10:00:24 +01:00
|
|
|
h.Runtime.Log.Error(method, err)
|
2017-08-02 11:46:45 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
response.WriteJSON(w, doc)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Use creates new document using a saved document as a template.
|
|
|
|
// If template ID is ZERO then we provide an Empty Document as the new document.
|
|
|
|
func (h *Handler) Use(w http.ResponseWriter, r *http.Request) {
|
|
|
|
method := "template.saved"
|
|
|
|
ctx := domain.GetRequestContext(r)
|
|
|
|
|
|
|
|
folderID := request.Param(r, "folderID")
|
|
|
|
if len(folderID) == 0 {
|
|
|
|
response.WriteMissingDataError(w, method, "folderID")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
templateID := request.Param(r, "templateID")
|
|
|
|
if len(templateID) == 0 {
|
|
|
|
response.WriteMissingDataError(w, method, "templateID")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
defer streamutil.Close(r.Body)
|
|
|
|
body, err := ioutil.ReadAll(r.Body)
|
|
|
|
if err != nil {
|
|
|
|
response.WriteBadRequestError(w, method, "Bad payload")
|
2017-08-03 10:00:24 +01:00
|
|
|
h.Runtime.Log.Error(method, err)
|
2017-08-02 11:46:45 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
docTitle := string(body)
|
|
|
|
|
|
|
|
// Define an empty document just in case user wanted one.
|
|
|
|
var d = doc.Document{}
|
|
|
|
d.Title = docTitle
|
|
|
|
d.Location = fmt.Sprintf("template-%s", templateID)
|
2017-10-10 16:02:46 -04:00
|
|
|
d.Excerpt = "Add detailed description for document..."
|
2017-08-02 11:46:45 +01:00
|
|
|
d.Slug = stringutil.MakeSlug(d.Title)
|
|
|
|
d.Tags = ""
|
|
|
|
d.LabelID = folderID
|
|
|
|
documentID := uniqueid.Generate()
|
|
|
|
d.RefID = documentID
|
|
|
|
|
|
|
|
var pages = []page.Page{}
|
|
|
|
var attachments = []attachment.Attachment{}
|
|
|
|
|
|
|
|
// Fetch document and associated pages, attachments if we have template ID
|
|
|
|
if templateID != "0" {
|
|
|
|
d, err = h.Store.Document.Get(ctx, templateID)
|
|
|
|
|
|
|
|
if err == sql.ErrNoRows {
|
|
|
|
response.WriteNotFoundError(w, method, templateID)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
response.WriteServerError(w, method, err)
|
2017-08-03 10:00:24 +01:00
|
|
|
h.Runtime.Log.Error(method, err)
|
2017-08-02 11:46:45 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
pages, _ = h.Store.Page.GetPages(ctx, templateID)
|
|
|
|
attachments, _ = h.Store.Attachment.GetAttachmentsWithData(ctx, templateID)
|
|
|
|
}
|
|
|
|
|
2018-04-20 14:38:35 +01:00
|
|
|
// Fetch space where document resides.
|
|
|
|
sp, err := h.Store.Space.Get(ctx, folderID)
|
|
|
|
if err != nil {
|
|
|
|
response.WriteServerError(w, method, err)
|
|
|
|
h.Runtime.Log.Error(method, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2017-08-02 11:46:45 +01:00
|
|
|
ctx.Transaction, err = h.Runtime.Db.Beginx()
|
|
|
|
if err != nil {
|
|
|
|
response.WriteServerError(w, method, err)
|
2017-08-03 10:00:24 +01:00
|
|
|
h.Runtime.Log.Error(method, err)
|
2017-08-02 11:46:45 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prepare new document
|
|
|
|
documentID = uniqueid.Generate()
|
|
|
|
d.RefID = documentID
|
|
|
|
d.Template = false
|
|
|
|
d.LabelID = folderID
|
|
|
|
d.UserID = ctx.UserID
|
|
|
|
d.Title = docTitle
|
2018-04-20 14:38:35 +01:00
|
|
|
d.Lifecycle = sp.Lifecycle
|
2017-08-02 11:46:45 +01:00
|
|
|
|
|
|
|
err = h.Store.Document.Add(ctx, d)
|
|
|
|
if err != nil {
|
|
|
|
ctx.Transaction.Rollback()
|
|
|
|
response.WriteServerError(w, method, err)
|
2017-08-03 10:00:24 +01:00
|
|
|
h.Runtime.Log.Error(method, err)
|
2017-08-02 11:46:45 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, p := range pages {
|
|
|
|
meta, err2 := h.Store.Page.GetPageMeta(ctx, p.RefID)
|
|
|
|
if err2 != nil {
|
|
|
|
ctx.Transaction.Rollback()
|
|
|
|
response.WriteServerError(w, method, err)
|
2017-08-03 10:00:24 +01:00
|
|
|
h.Runtime.Log.Error(method, err)
|
2017-08-02 11:46:45 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
p.DocumentID = documentID
|
|
|
|
pageID := uniqueid.Generate()
|
|
|
|
p.RefID = pageID
|
|
|
|
|
|
|
|
meta.PageID = pageID
|
|
|
|
meta.DocumentID = documentID
|
|
|
|
|
|
|
|
model := page.NewPage{}
|
|
|
|
model.Page = p
|
|
|
|
model.Meta = meta
|
|
|
|
|
|
|
|
err = h.Store.Page.Add(ctx, model)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
ctx.Transaction.Rollback()
|
|
|
|
response.WriteServerError(w, method, err)
|
2017-08-03 10:00:24 +01:00
|
|
|
h.Runtime.Log.Error(method, err)
|
2017-08-02 11:46:45 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
newUUID, _ := uuid.NewV4()
|
|
|
|
|
|
|
|
for _, a := range attachments {
|
|
|
|
a.DocumentID = documentID
|
|
|
|
a.Job = newUUID.String()
|
|
|
|
random := secrets.GenerateSalt()
|
|
|
|
a.FileID = random[0:9]
|
|
|
|
attachmentID := uniqueid.Generate()
|
|
|
|
a.RefID = attachmentID
|
|
|
|
|
|
|
|
err = h.Store.Attachment.Add(ctx, a)
|
|
|
|
if err != nil {
|
|
|
|
ctx.Transaction.Rollback()
|
|
|
|
response.WriteServerError(w, method, err)
|
2017-08-03 10:00:24 +01:00
|
|
|
h.Runtime.Log.Error(method, err)
|
2017-08-02 11:46:45 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx.Transaction.Commit()
|
|
|
|
|
2018-02-04 15:43:57 +00:00
|
|
|
h.Store.Audit.Record(ctx, audit.EventTypeTemplateUse)
|
|
|
|
|
2017-08-02 11:46:45 +01:00
|
|
|
nd, err := h.Store.Document.Get(ctx, documentID)
|
|
|
|
if err != nil {
|
|
|
|
response.WriteServerError(w, method, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
event.Handler().Publish(string(event.TypeAddDocument), nd.Title)
|
|
|
|
|
2017-08-15 19:41:44 +01:00
|
|
|
a, _ := h.Store.Attachment.GetAttachments(ctx, documentID)
|
2018-03-14 13:38:37 +00:00
|
|
|
|
|
|
|
if nd.Lifecycle == workflow.LifecycleLive {
|
|
|
|
go h.Indexer.IndexDocument(ctx, nd, a)
|
|
|
|
} else {
|
|
|
|
go h.Indexer.DeleteDocument(ctx, d.RefID)
|
|
|
|
}
|
2017-08-15 19:41:44 +01:00
|
|
|
|
2017-08-02 11:46:45 +01:00
|
|
|
response.WriteJSON(w, nd)
|
|
|
|
}
|