1
0
Fork 0
mirror of https://github.com/documize/community.git synced 2025-07-19 13:19:43 +02:00
documize/domain/search/mysql/store.go

319 lines
9.2 KiB
Go
Raw Normal View History

// 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 mysql
import (
2017-08-15 14:15:31 +01:00
"database/sql"
"fmt"
"strings"
"github.com/documize/community/core/env"
"github.com/documize/community/core/streamutil"
"github.com/documize/community/core/stringutil"
"github.com/documize/community/domain"
2017-08-15 19:41:44 +01:00
"github.com/documize/community/model/attachment"
"github.com/documize/community/model/doc"
"github.com/documize/community/model/page"
"github.com/documize/community/model/search"
2018-01-10 16:07:17 +00:00
"github.com/documize/community/model/workflow"
"github.com/jmoiron/sqlx"
"github.com/pkg/errors"
)
// Scope provides data access to MySQL.
type Scope struct {
Runtime *env.Runtime
}
2017-08-15 19:41:44 +01:00
// IndexDocument adds search index entries for document inserting title, tags and attachments as
// searchable items. Any existing document entries are removed.
func (s Scope) IndexDocument(ctx domain.RequestContext, doc doc.Document, a []attachment.Attachment) (err error) {
// remove previous search entries
2018-09-19 16:03:29 +01:00
_, err = ctx.Transaction.Exec("DELETE FROM dmz_search WHERE c_orgid=? AND c_docid=? AND (c_itemtype='doc' OR c_itemtype='file' OR c_itemtype='tag')",
2017-09-25 14:37:11 +01:00
ctx.OrgID, doc.RefID)
if err != nil {
2017-08-15 19:41:44 +01:00
err = errors.Wrap(err, "execute delete document index entries")
}
2017-08-15 19:41:44 +01:00
// insert doc title
2018-09-19 16:03:29 +01:00
_, err = ctx.Transaction.Exec("INSERT INTO dmz_search (c_orgid, c_docid, c_itemid, c_itemtype, c_content) VALUES (?, ?, ?, ?, ?)",
ctx.OrgID, doc.RefID, "", "doc", doc.Name)
if err != nil {
2017-08-15 19:41:44 +01:00
err = errors.Wrap(err, "execute insert document title entry")
}
2017-08-15 19:41:44 +01:00
// insert doc tags
tags := strings.Split(doc.Tags, "#")
for _, t := range tags {
if len(t) == 0 {
continue
}
2018-09-19 16:03:29 +01:00
_, err = ctx.Transaction.Exec("INSERT INTO dmz_search (c_orgid, c_docid, c_itemid, c_itemtype, c_content) VALUES (?, ?, ?, ?, ?)",
2017-09-25 14:37:11 +01:00
ctx.OrgID, doc.RefID, "", "tag", t)
2017-08-15 19:41:44 +01:00
if err != nil {
err = errors.Wrap(err, "execute insert document tag entry")
return
}
}
2017-08-15 19:41:44 +01:00
for _, file := range a {
2018-09-19 16:03:29 +01:00
_, err = ctx.Transaction.Exec("INSERT INTO dmz_search (c_orgid, c_docid, c_itemid, c_itemtype, c_content) VALUES (?, ?, ?, ?, ?)",
2017-09-25 14:37:11 +01:00
ctx.OrgID, doc.RefID, file.RefID, "file", file.Filename)
2017-08-15 19:41:44 +01:00
if err != nil {
err = errors.Wrap(err, "execute insert document file entry")
}
}
return nil
}
2017-08-15 19:41:44 +01:00
// DeleteDocument removes all search entries for document.
func (s Scope) DeleteDocument(ctx domain.RequestContext, ID string) (err error) {
2018-09-19 16:03:29 +01:00
_, err = ctx.Transaction.Exec("DELETE FROM dmz_search WHERE c_orgid=? AND c_docid=?", ctx.OrgID, ID)
if err != nil {
2017-08-15 19:41:44 +01:00
err = errors.Wrap(err, "execute delete document entries")
}
2017-08-15 19:41:44 +01:00
return
}
2017-08-15 19:41:44 +01:00
// IndexContent adds search index entry for document context.
// Any existing document entries are removed.
func (s Scope) IndexContent(ctx domain.RequestContext, p page.Page) (err error) {
2018-01-10 16:07:17 +00:00
// we do not index pending pages
if p.Status == workflow.ChangePending || p.Status == workflow.ChangePendingNew {
return
}
2017-08-15 19:41:44 +01:00
// remove previous search entries
2018-09-19 16:03:29 +01:00
_, err = ctx.Transaction.Exec("DELETE FROM dmz_search WHERE c_orgid=? AND c_docid=? AND c_itemid=? AND c_itemtype='page'",
2017-09-25 14:37:11 +01:00
ctx.OrgID, p.DocumentID, p.RefID)
if err != nil {
2017-08-15 19:41:44 +01:00
err = errors.Wrap(err, "execute delete document content entry")
}
2017-08-15 19:41:44 +01:00
// prepare content
content, err := stringutil.HTML(p.Body).Text(false)
if err != nil {
2017-08-15 20:29:35 +01:00
err = errors.Wrap(err, "search strip HTML failed")
2017-08-15 19:41:44 +01:00
return
}
2017-08-15 19:41:44 +01:00
content = strings.TrimSpace(content)
2018-09-19 16:03:29 +01:00
_, err = ctx.Transaction.Exec("INSERT INTO dmz_search (c_orgid, c_docid, c_itemid, c_itemtype, c_content) VALUES (?, ?, ?, ?, ?)",
2017-09-25 14:37:11 +01:00
ctx.OrgID, p.DocumentID, p.RefID, "page", content)
if err != nil {
2017-08-15 19:41:44 +01:00
err = errors.Wrap(err, "execute insert document content entry")
}
2018-09-19 16:03:29 +01:00
_, err = ctx.Transaction.Exec("INSERT INTO dmz_search (c_orgid, c_docid, c_itemid, c_itemtype, c_content) VALUES (?, ?, ?, ?, ?)",
ctx.OrgID, p.DocumentID, p.RefID, "page", p.Name)
2018-03-19 15:04:02 +00:00
if err != nil {
err = errors.Wrap(err, "execute insert document page title entry")
}
2017-08-15 19:41:44 +01:00
return nil
}
2017-08-15 19:41:44 +01:00
// DeleteContent removes all search entries for specific document content.
func (s Scope) DeleteContent(ctx domain.RequestContext, pageID string) (err error) {
// remove all search entries
var stmt1 *sqlx.Stmt
2018-09-19 16:03:29 +01:00
stmt1, err = ctx.Transaction.Preparex("DELETE FROM dmz_search WHERE c_orgid=? AND c_itemid=? AND c_itemtype=?")
2017-08-15 19:41:44 +01:00
defer streamutil.Close(stmt1)
if err != nil {
2017-08-15 19:41:44 +01:00
err = errors.Wrap(err, "prepare delete document content entry")
return
}
2017-08-15 20:29:35 +01:00
_, err = stmt1.Exec(ctx.OrgID, pageID, "page")
if err != nil {
2017-08-15 19:41:44 +01:00
err = errors.Wrap(err, "execute delete document content entry")
return
}
return
}
// Documents searches the documents that the client is allowed to see, using the keywords search string, then audits that search.
// Visible documents include both those in the client's own organization and those that are public, or whose visibility includes the client.
2017-08-15 14:15:31 +01:00
func (s Scope) Documents(ctx domain.RequestContext, q search.QueryOptions) (results []search.QueryResult, err error) {
q.Keywords = strings.TrimSpace(q.Keywords)
if len(q.Keywords) == 0 {
return
}
2017-08-15 14:15:31 +01:00
results = []search.QueryResult{}
2017-08-15 14:15:31 +01:00
// Match doc names
if q.Doc {
r1, err1 := s.matchFullText(ctx, q.Keywords, "doc")
if err1 != nil {
err = errors.Wrap(err1, "search document names")
return
}
2017-08-15 14:15:31 +01:00
results = append(results, r1...)
}
2017-08-15 14:15:31 +01:00
// Match doc content
if q.Content {
r2, err2 := s.matchFullText(ctx, q.Keywords, "page")
if err2 != nil {
err = errors.Wrap(err2, "search document content")
return
}
2017-08-15 14:15:31 +01:00
results = append(results, r2...)
}
2017-08-15 14:15:31 +01:00
// Match doc tags
if q.Tag {
r3, err3 := s.matchFullText(ctx, q.Keywords, "tag")
if err3 != nil {
err = errors.Wrap(err3, "search document tag")
return
}
2017-08-15 14:15:31 +01:00
results = append(results, r3...)
}
2017-08-15 14:15:31 +01:00
// Match doc attachments
if q.Attachment {
r4, err4 := s.matchLike(ctx, q.Keywords, "file")
if err4 != nil {
err = errors.Wrap(err4, "search document attachments")
return
}
2017-08-15 14:15:31 +01:00
results = append(results, r4...)
}
2018-03-30 17:03:18 +01:00
if len(results) == 0 {
results = []search.QueryResult{}
}
2017-08-15 14:15:31 +01:00
return
}
func (s Scope) matchFullText(ctx domain.RequestContext, keywords, itemType string) (r []search.QueryResult, err error) {
sql1 := `
2018-03-19 15:04:02 +00:00
SELECT
2018-09-19 16:03:29 +01:00
s.id, s.c_orgid AS orgid, s.c_docid AS documentid, s.c_itemid AS itemid, s.c_itemtype AS itemtype,
d.c_spaceid as spaceid, COALESCE(d.c_name,'Unknown') AS document, d.c_tags AS tags,
d.c_desc AS excerpt, d.c_template AS template, d.c_versionid AS versionid,
COALESCE(l.c_name,'Unknown') AS space
2017-08-15 14:15:31 +01:00
FROM
2018-09-19 16:03:29 +01:00
dmz_search s,
dmz_doc d
2018-03-19 15:04:02 +00:00
LEFT JOIN
2018-09-19 16:03:29 +01:00
dmz_space l ON l.c_orgid=d.c_orgid AND l.c_refid = d.c_spaceid
2017-08-15 14:15:31 +01:00
WHERE
2018-09-19 16:03:29 +01:00
s.c_orgid = ?
AND s.c_itemtype = ?
AND s.c_docid = d.refid
AND d.c_spaceid IN
(
2018-09-19 16:03:29 +01:00
SELECT c_refid FROM dmz_space WHERE c_orgid=? AND c_refid IN
2018-03-19 15:04:02 +00:00
(
2018-09-19 16:03:29 +01:00
SELECT c_refid from dmz_permission WHERE c_orgid=? AND c_who='user' AND (c_whoid=? OR c_whoid='0') AND c_location='space'
UNION ALL
2018-09-19 16:03:29 +01:00
SELECT p.c_refid from dmz_permission p LEFT JOIN dmz_group_member r ON p.c_whoid=r.c_groupid WHERE p.c_orgid=? AND p.c_who='role'
AND p.c_location='space' AND (r.c_userid=? OR r.c_userid='0')
2018-03-19 15:04:02 +00:00
)
)
2018-09-19 16:03:29 +01:00
AND MATCH(s.c_content) AGAINST(? IN BOOLEAN MODE)`
2017-08-15 14:15:31 +01:00
err = s.Runtime.Db.Select(&r,
sql1,
ctx.OrgID,
2017-08-15 14:15:31 +01:00
itemType,
ctx.OrgID,
ctx.OrgID,
ctx.UserID,
ctx.OrgID,
2017-08-15 14:15:31 +01:00
ctx.UserID,
keywords)
if err == sql.ErrNoRows {
err = nil
r = []search.QueryResult{}
}
if err != nil {
err = errors.Wrap(err, "search document "+itemType)
}
return
}
func (s Scope) matchLike(ctx domain.RequestContext, keywords, itemType string) (r []search.QueryResult, err error) {
// LIKE clause does not like quotes!
keywords = strings.Replace(keywords, "'", "", -1)
keywords = strings.Replace(keywords, "\"", "", -1)
keywords = strings.Replace(keywords, "%", "", -1)
keywords = fmt.Sprintf("%%%s%%", keywords)
sql1 := `
2018-03-19 15:04:02 +00:00
SELECT
2018-09-19 16:03:29 +01:00
s.id, s.c_orgid AS orgid, s.c_docid AS documentid, s.c_itemid AS itemid, s.c_itemtype AS itemtype,
d.c_spaceid as spaceid, COALESCE(d.c_name,'Unknown') AS document, d.c_tags AS tags, d.c_desc AS excerpt,
COALESCE(l.c_name,'Unknown') AS space
2017-08-15 14:15:31 +01:00
FROM
2018-09-19 16:03:29 +01:00
dmz_search s,
dmz_doc d
2018-03-19 15:04:02 +00:00
LEFT JOIN
2018-09-19 16:03:29 +01:00
dmz_space l ON l.c_orgid=d.c_orgid AND l.c_refid = d.c_spaceid
2017-08-15 14:15:31 +01:00
WHERE
2018-09-19 16:03:29 +01:00
s.c_orgid = ?
AND s.c_itemtype = ?
AND s.c_docid = d.c_refid
AND d.c_spaceid IN
(
2018-09-19 16:03:29 +01:00
SELECT c_refid FROM dmz_space WHERE c_orgid=? AND c_refid IN
2018-03-30 17:03:18 +01:00
(
2018-09-19 16:03:29 +01:00
SELECT c_refid from dmz_permission WHERE c_orgid=? AND c_who='user' AND (c_whoid=? OR c_whoid='0') AND c_location='space'
UNION ALL
2018-09-19 16:03:29 +01:00
SELECT p.c_refid from dmz_permission p LEFT JOIN dmz_group_member r ON p.c_whoid=r.c_groupid WHERE p.c_orgid=? AND p.c_who='role'
AND p.c_location='space' AND (r.c_userid=? OR r.c_userid='0')
2018-03-30 17:03:18 +01:00
)
)
2018-09-19 16:03:29 +01:00
AND s.c_content LIKE ?`
2017-08-15 14:15:31 +01:00
err = s.Runtime.Db.Select(&r,
sql1,
ctx.OrgID,
itemType,
ctx.OrgID,
ctx.OrgID,
ctx.UserID,
2017-08-15 14:15:31 +01:00
ctx.OrgID,
ctx.UserID,
keywords)
if err == sql.ErrNoRows {
err = nil
r = []search.QueryResult{}
}
if err != nil {
2017-08-15 14:15:31 +01:00
err = errors.Wrap(err, "search document "+itemType)
}
return
}