ElasticSearch search in attachments
Here is simplest possible demo of what elasticsearch can do:
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no" />
<title>Attachments Demo</title>
<link rel="stylesheet" href="bower_components/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="bower_components/fontawesome/css/font-awesome.css" />
<style>
html,
body {
height: 100vh;
}
mark {
background: yellow;
}
.drop:after {
content: 'Drop file here';
pointer-events: none;
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
background-color: #999;
background-image: radial-gradient(rgba(0, 0, 0, 0) 50%, rgba(0, 0, 0, 1) 100%);
opacity: 0.5;
color: white;
line-height: 100vh;
text-align: center;
font-size: 10vh;
text-transform: uppercase;
text-shadow: 1px 1px 5px #000;
}
</style>
</head>
<body>
<div class="container" style="padding: 15px">
<form class="row" data-bind="submit: reload">
<p class="col-md-10">
<input type="search" class="form-control" data-bind="value: q" />
</p>
<p class="col-md-2">
<button class="btn btn-default btn-block" data-bind="click: reload">Filter</button>
</p>
</form>
<ul class="list-group" data-bind="foreach: hits">
<li class="list-group-item">
<div class="media">
<div class="media-body">
<i data-bind="css: icon"></i>
<strong data-bind="text: _source.name"></strong>
</div>
<div class="media-right">
<a class="text-danger" href="#" data-bind="click: $root.delete">delete</a>
</div>
</div>
<div data-bind="if: hasHighlights">
<ol data-bind="foreach: highlight.content">
<li data-bind="html: $data"></li>
</ol>
</div>
</li>
</ul>
</div>
<script src="bower_components/jquery/dist/jquery.js"></script>
<script src="bower_components/bootstrap/dist/js/bootstrap.js"></script>
<script src="bower_components/knockout/dist/knockout.debug.js"></script>
<script src="bower_components/elasticsearch/elasticsearch.js"></script>
<script>
function Hit(data) {
var self = this
ko.utils.extend(self, data)
self.hasHighlights = self.highlight && self.highlight.content && self.highlight.content.length > 0
if (
self._source.type === 'application/msword' ||
self._source.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
) {
self.icon = 'fa fa-file-word-o'
} else {
self.icon = 'fa fa-file-text-o'
}
}
function Model() {
var self = this
self.index = 'attachments'
self.type = 'document' // need to be replaced in mapping also
self.client = new elasticsearch.Client({
host: 'localhost:9200',
log: 'trace'
})
self.q = ko.observable('')
self.hits = ko.observableArray()
self.reload = function () {
var body = {
index: self.index,
type: self.type,
body: {
_source: {
exclude: ['content']
}
}
}
if (self.q() && self.q().trim().length > 0) {
body.body.query = {
query_string: {
query: self.q()
}
}
body.body.highlight = {
pre_tags: ['<mark>'],
post_tags: ['</mark>'],
fields: {
content: {}
}
}
}
self.client.search(body).then(function (body) {
self.hits(
body.hits.hits.map(function (hit) {
return new Hit(hit)
})
)
}, alert)
}
self.delete = function (data) {
self.client.delete(
{
index: self.index,
type: self.type,
id: data._id,
ignore: [404]
},
function (err, body) {
if (body.found) {
self.hits.remove(data)
}
}
)
}
function nop(event) {
event.stopPropagation()
event.preventDefault()
event.dataTransfer.dropEffect = 'copy'
document.body.className = event.type === 'dragleave' ? '' : 'drop'
}
document.body.addEventListener('dragenter', nop)
document.body.addEventListener('dragover', nop, false)
document.body.addEventListener('dragleave', nop)
document.body.addEventListener(
'drop',
function (event) {
event.stopPropagation()
event.preventDefault()
;[].forEach.call(event.dataTransfer.files, function (file) {
var reader = new FileReader()
reader.addEventListener('load', function (event) {
self.client
.create({
index: self.index,
type: self.type,
body: {
name: file.name,
date: new Date(file.lastModified).toISOString(),
type: file.type,
content: event.target.result.substr(event.target.result.indexOf(',') + 1)
}
})
.then(function (body) {
body._source = {
name: file.name,
date: new Date(file.lastModified).toISOString(),
type: file.type
}
self.hits.push(new Hit(body))
})
})
reader.readAsDataURL(file)
document.body.className = ''
})
},
false
)
self.mappings = {
index: self.index,
body: {
mappings: {}
}
}
self.mappings.body.mappings[self.type] = {
properties: {
name: { type: 'string' },
date: { type: 'date' },
type: { type: 'string' },
content: {
type: 'attachment',
fields: {
content: { store: 'yes' },
author: { store: 'yes' },
title: { store: 'yes' },
date: { store: 'yes' },
keywords: { store: 'yes' },
_name: { store: 'yes' },
_content_type: { store: 'yes' }
}
}
}
}
self.client.indices.exists({ index: self.index }, function (body, exists) {
if (!exists) {
self.client.indices.create(self.mappings)
} else {
self.reload()
}
})
}
var model = new Model()
ko.applyBindings(model)
</script>
</body>
</html>
bower.json
{
"name": "attachments_demo",
"version": "0.0.0",
"dependencies": {
"jquery": "~2.1.4",
"bootstrap": "~3.3.5",
"knockout": "~3.3.0",
"fontawesome": "~4.3.0",
"elasticsearch": "~5.0.0"
}
}
elasticsearch.yml
cluster.name: attachments
node.name: ${COMPUTERNAME}
plugin.mandatory: mapper-attachments
http.cors.enabled: true
index.number_of_shards: 1
index.number_of_replicas: 0
For this demo to work mapper-attachments plugin should be installed.