1
0
Fork 0
mirror of https://github.com/documize/community.git synced 2025-07-24 15:49:44 +02:00
documize/domain/meta/endpoint.go
HarveyKandola 8a65567169 Implement PostgreSQL Full Text Search++
1. Full text search supports MySQL, MariaDB, Percona and now PostgreSQL.
2. Changed SQL Variant to typed enum.
3. Changed doc.Versioned from INT to BOOL.
4. Search Reindexer now parses all documents and attachments.
5. Site meta API call returns storage provider type.
6. README prep'ed for PostgreSQL support.
7. DELETE SQL statements ignore zero rows affected.

Closes #100 !!!

Co-Authored-By: Saul S <sauls8t@users.noreply.github.com>
Co-Authored-By: McMatts <matt@documize.com>
2018-09-28 16:33:15 +01:00

279 lines
7.7 KiB
Go

// 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 meta
import (
"bytes"
"fmt"
"net/http"
"strings"
"text/template"
"github.com/documize/community/core/env"
"github.com/documize/community/core/response"
"github.com/documize/community/core/stringutil"
"github.com/documize/community/domain"
"github.com/documize/community/domain/auth"
"github.com/documize/community/domain/organization"
indexer "github.com/documize/community/domain/search"
"github.com/documize/community/domain/store"
"github.com/documize/community/model/doc"
"github.com/documize/community/model/org"
"github.com/documize/community/model/space"
)
// Handler contains the runtime information such as logging and database.
type Handler struct {
Runtime *env.Runtime
Store *store.Store
Indexer indexer.Indexer
}
// Meta provides org meta data based upon request domain (e.g. acme.documize.com).
func (h *Handler) Meta(w http.ResponseWriter, r *http.Request) {
data := org.SiteMeta{}
data.URL = organization.GetSubdomainFromHost(r)
org, err := h.Store.Organization.GetOrganizationByDomain(data.URL)
if err != nil {
h.Runtime.Log.Info("unable to fetch request meta for " + data.URL)
response.WriteNotFound(w)
return
}
data.OrgID = org.RefID
data.Title = org.Title
data.Message = org.Message
data.AllowAnonymousAccess = org.AllowAnonymousAccess
data.AuthProvider = strings.TrimSpace(org.AuthProvider)
data.AuthConfig = org.AuthConfig
data.MaxTags = org.MaxTags
data.Version = h.Runtime.Product.Version
data.Edition = h.Runtime.Product.License.Edition
data.Valid = h.Runtime.Product.License.Valid
data.ConversionEndpoint = org.ConversionEndpoint
data.License = h.Runtime.Product.License
data.Storage = h.Runtime.StoreProvider.Type()
// Strip secrets
data.AuthConfig = auth.StripAuthSecrets(h.Runtime, org.AuthProvider, org.AuthConfig)
response.WriteJSON(w, data)
}
// RobotsTxt returns robots.txt depending on site configuration.
// Did we allow anonymouse access?
func (h *Handler) RobotsTxt(w http.ResponseWriter, r *http.Request) {
method := "GetRobots"
ctx := domain.GetRequestContext(r)
dom := organization.GetSubdomainFromHost(r)
o, err := h.Store.Organization.GetOrganizationByDomain(dom)
// default is to deny
robots :=
`User-agent: *
Disallow: /
`
if err != nil {
h.Runtime.Log.Info(fmt.Sprintf("%s failed to get Organization for domain %s", method, dom))
o = org.Organization{}
o.AllowAnonymousAccess = false
}
// Anonymous access would mean we allow bots to crawl.
if o.AllowAnonymousAccess {
sitemap := ctx.GetAppURL("sitemap.xml")
robots = fmt.Sprintf(`User-agent: *
Disallow: /settings/
Disallow: /settings/*
Disallow: /profile/
Disallow: /profile/*
Disallow: /auth/login/
Disallow: /auth/login/
Disallow: /auth/logout/
Disallow: /auth/logout/*
Disallow: /auth/reset/*
Disallow: /auth/reset/*
Disallow: /auth/sso/
Disallow: /auth/sso/*
Disallow: /auth/*
Disallow: /auth/**
Disallow: /share
Disallow: /share/*
Sitemap: %s`, sitemap)
}
response.WriteBytes(w, []byte(robots))
}
// Sitemap returns URLs that can be indexed.
// We only include public folders and documents (e.g. can be seen by everyone).
func (h *Handler) Sitemap(w http.ResponseWriter, r *http.Request) {
method := "meta.Sitemap"
ctx := domain.GetRequestContext(r)
dom := organization.GetSubdomainFromHost(r)
o, err := h.Store.Organization.GetOrganizationByDomain(dom)
if err != nil {
h.Runtime.Log.Info(fmt.Sprintf("%s failed to get Organization for domain %s", method, dom))
o = org.Organization{}
o.AllowAnonymousAccess = false
}
sitemap :=
`<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
{{range .}}<url>
<loc>{{ .URL }}</loc>
<lastmod>{{ .Date }}</lastmod>
</url>{{end}}
</urlset>`
var items []sitemapItem
// Anonymous access means we announce folders/documents shared with 'Everyone'.
if o.AllowAnonymousAccess {
// Grab shared folders
folders, err := h.Store.Space.PublicSpaces(ctx, o.RefID)
if err != nil {
folders = []space.Space{}
h.Runtime.Log.Error(fmt.Sprintf("%s failed to get folders for domain %s", method, dom), err)
}
for _, folder := range folders {
var item sitemapItem
item.URL = ctx.GetAppURL(fmt.Sprintf("s/%s/%s", folder.RefID, stringutil.MakeSlug(folder.Name)))
item.Date = folder.Revised.Format("2006-01-02T15:04:05.999999-07:00")
items = append(items, item)
}
// Grab documents from shared folders
var documents []doc.SitemapDocument
documents, err = h.Store.Document.PublicDocuments(ctx, o.RefID)
if err != nil {
documents = []doc.SitemapDocument{}
h.Runtime.Log.Error(fmt.Sprintf("%s failed to get documents for domain %s", method, dom), err)
}
for _, document := range documents {
var item sitemapItem
item.URL = ctx.GetAppURL(fmt.Sprintf("s/%s/%s/d/%s/%s",
document.SpaceID, stringutil.MakeSlug(document.Space), document.DocumentID, stringutil.MakeSlug(document.Document)))
item.Date = document.Revised.Format("2006-01-02T15:04:05.999999-07:00")
items = append(items, item)
}
}
buffer := new(bytes.Buffer)
t := template.Must(template.New("tmp").Parse(sitemap))
t.Execute(buffer, &items)
response.WriteBytes(w, buffer.Bytes())
}
// Reindex indexes all documents and attachments.
func (h *Handler) Reindex(w http.ResponseWriter, r *http.Request) {
ctx := domain.GetRequestContext(r)
if !ctx.GlobalAdmin {
response.WriteForbiddenError(w)
h.Runtime.Log.Info(fmt.Sprintf("%s attempted search reindex"))
return
}
go h.rebuildSearchIndex(ctx)
response.WriteEmpty(w)
}
// rebuildSearchIndex indexes all documents and attachments.
func (h *Handler) rebuildSearchIndex(ctx domain.RequestContext) {
method := "meta.rebuildSearchIndex"
docs, err := h.Store.Meta.GetDocumentsID(ctx)
if err != nil {
h.Runtime.Log.Error(method, err)
return
}
h.Runtime.Log.Info(fmt.Sprintf("Search re-index started for %d documents", len(docs)))
for i := range docs {
d := docs[i]
doc, err := h.Store.Document.Get(ctx, d)
if err != nil {
h.Runtime.Log.Error(method, err)
return
}
at, err := h.Store.Attachment.GetAttachments(ctx, d)
if err != nil {
h.Runtime.Log.Error(method, err)
return
}
h.Indexer.IndexDocument(ctx, doc, at)
pages, err := h.Store.Meta.GetDocumentPages(ctx, d)
if err != nil {
h.Runtime.Log.Error(method, err)
return
}
for j := range pages {
h.Indexer.IndexContent(ctx, pages[j])
}
// Log process every N documents.
if i%100 == 0 {
h.Runtime.Log.Info(fmt.Sprintf("Search re-indexed %d documents...", i))
}
}
h.Runtime.Log.Info(fmt.Sprintf("Search re-index finished for %d documents", len(docs)))
}
// SearchStatus returns state of search index
func (h *Handler) SearchStatus(w http.ResponseWriter, r *http.Request) {
method := "meta.SearchStatus"
ctx := domain.GetRequestContext(r)
if !ctx.GlobalAdmin {
response.WriteForbiddenError(w)
h.Runtime.Log.Info(fmt.Sprintf("%s attempted get of search status"))
return
}
count, err := h.Store.Meta.SearchIndexCount(ctx)
if err != nil {
response.WriteServerError(w, method, err)
h.Runtime.Log.Error(method, err)
return
}
var ss = searchStatus{Entries: count}
response.WriteJSON(w, ss)
}
type sitemapItem struct {
URL string
Date string
}
type searchStatus struct {
Entries int `json:"entries"`
}