forked from elastic/elasticsearch-rails
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathapplication.rb
252 lines (202 loc) · 7.9 KB
/
application.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
# Licensed to Elasticsearch B.V. under one or more contributor
# license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright
# ownership. Elasticsearch B.V. licenses this file to you under
# the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
$LOAD_PATH.unshift File.expand_path('../../../lib/', __FILE__)
require 'sinatra/base'
require 'multi_json'
require 'oj'
require 'hashie/mash'
require 'elasticsearch'
require 'elasticsearch/model'
require 'elasticsearch/persistence'
class Note
attr_reader :attributes
def initialize(attributes={})
@attributes = Hashie::Mash.new(attributes)
__add_date
__extract_tags
__truncate_text
end
def method_missing(method_name, *arguments, &block)
attributes.respond_to?(method_name) ? attributes.__send__(method_name, *arguments, &block) : super
end
def respond_to?(method_name, include_private=false)
attributes.respond_to?(method_name) || super
end
def tags; attributes.tags || []; end
def to_hash
@attributes.to_hash
end
def __extract_tags
tags = attributes['text'].scan(/(\[\w+\])/).flatten if attributes['text']
unless tags.nil? || tags.empty?
attributes.update 'tags' => tags.map { |t| t.tr('[]', '') }
attributes['text'].gsub!(/(\[\w+\])/, '').strip!
end
end
def __add_date
attributes['created_at'] ||= Time.now.utc.iso8601
end
def __truncate_text
attributes['text'] = attributes['text'][0...80] + ' (...)' if attributes['text'] && attributes['text'].size > 80
end
end
class NoteRepository
include Elasticsearch::Persistence::Repository
include Elasticsearch::Persistence::Repository::DSL
client Elasticsearch::Client.new url: ENV['ELASTICSEARCH_URL'], log: true
index_name :notes
mapping do
indexes :text, analyzer: 'snowball'
indexes :tags, type: 'keyword'
indexes :created_at, type: 'date'
end
def deserialize(document)
Note.new document['_source'].merge('id' => document['_id'])
end
end unless defined?(NoteRepository)
class Application < Sinatra::Base
enable :logging
enable :inline_templates
enable :method_override
configure :development do
enable :dump_errors
disable :show_exceptions
require 'sinatra/reloader'
register Sinatra::Reloader
end
set :repository, NoteRepository.new
set :per_page, 25
get '/' do
@page = [ params[:p].to_i, 1 ].max
@notes = settings.repository.search \
query: ->(q, t) do
query = if q && !q.empty?
{ match: { text: q } }
else
{ match_all: {} }
end
filter = if t && !t.empty?
{ term: { tags: t } }
end
if filter
{ bool: { must: [ query ], filter: filter } }
else
query
end
end.(params[:q], params[:t]),
sort: [{created_at: {order: 'desc'}}],
size: settings.per_page,
from: settings.per_page * (@page-1),
aggregations: { tags: { terms: { field: 'tags' } } },
highlight: { fields: { text: { fragment_size: 0, pre_tags: ['<em class="hl">'],post_tags: ['</em>'] } } }
erb :index
end
post '/' do
unless params[:text].empty?
@note = Note.new params
settings.repository.save(@note, refresh: true)
end
redirect back
end
delete '/:id' do |id|
settings.repository.delete(id, refresh: true)
redirect back
end
end
Application.run! if $0 == __FILE__
__END__
@@ layout
<!DOCTYPE html>
<html>
<head>
<title>Notes</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style>
body { color: #222; background: #fff; font: normal 80%/120% 'Helvetica Neue', sans-serif; margin: 4em; position: relative; }
header { color: #666; border-bottom: 2px solid #666; }
header:after { display: table; content: ""; line-height: 0; clear: both; }
#left { width: 20em; float: left }
#main { margin-left: 20em; }
header h1 { font-weight: normal; float: left; padding: 0.4em 0 0 0; margin: 0; }
header form { margin-left: 19.5em; }
header form input { font-size: 120%; width: 40em; border: none; padding: 0.5em; position: relative; bottom: -0.2em; background: transparent; }
header form input:focus { outline-width: 0; }
#left h2 { color: #999; font-size: 160%; font-weight: normal; text-transform: uppercase; letter-spacing: -0.05em; }
#left h2 { border-top: 2px solid #999; width: 9.4em; padding: 0.5em 0 0.5em 0; margin: 0; }
#left textarea { font: normal 140%/140% monospace; border: 1px solid #999; padding: 0.5em; width: 12em; }
#left form p { margin: 0; }
#left a { color: #000; }
#left small.c { color: #333; background: #ccc; text-align: center; min-width: 1.75em; min-height: 1.5em; border-radius: 1em; display: inline-block; padding-top: 0.25em; float: right; margin-right: 6em; }
#left small.i { color: #ccc; background: #333; }
#facets { list-style-type: none; padding: 0; margin: 0 0 1em 0; }
#facets li { padding: 0 0 0.5em 0; }
.note { border-bottom: 1px solid #999; position: relative; padding: 0.5em 0; }
.note p { font-size: 140%; }
.note small { font-size: 70%; color: #999; }
.note small.d { border-left: 1px solid #999; padding-left: 0.5em; margin-left: 0.5em; }
.note em.hl { background: #fcfcad; border-radius: 0.5em; padding: 0.2em 0.4em 0.2em 0.4em; }
.note strong.t { color: #fff; background: #999; font-size: 70%; font-weight: bold; border-radius: 0.6em; padding: 0.2em 0.6em 0.3em 0.7em; }
.note form { position: absolute; bottom: 1.5em; right: 1em; }
.pagination { color: #000; font-weight: bold; text-align: right; }
.pagination:visited { color: #000; }
.pagination a { text-decoration: none; }
.pagination:hover a { text-decoration: underline; }
}
</style>
</head>
<body>
<%= yield %>
</body>
</html>
@@ index
<header>
<h1>Notes</h1>
<form action="/" method='get'>
<input type="text" name="q" value="<%= params[:q] %>" id="q" autofocus="autofocus" placeholder="type a search query and press enter..." />
</form>
</header>
<section id="left">
<p><a href="/">All notes</a> <small class="c i"><%= @notes.size %></small></p>
<ul id="facets">
<% @notes.response.aggregations.tags.buckets.each do |term| %>
<li><a href="/?t=<%= term['key'] %>"><%= term['key'] %></a> <small class="c"><%= term['doc_count'] %></small></li>
<% end %>
</ul>
<h2>Add a note</h2>
<form action="/" method='post'>
<p><textarea name="text" rows="5"></textarea></p>
<p><input type="submit" accesskey="s" value="Save" /></p>
</form>
</section>
<section id="main">
<% if @notes.empty? %>
<p>No notes found.</p>
<% end %>
<% @notes.each_with_hit do |note, hit| %>
<div class="note">
<p>
<%= hit.highlight && hit.highlight.size > 0 ? hit.highlight.text.first : note.text %>
<% note.tags.each do |tag| %> <strong class="t"><%= tag %></strong><% end %>
<small class="d"><%= Time.parse(note.created_at).strftime('%d/%m/%Y %H:%M') %></small>
<form action="/<%= note.id %>" method="post"><input type="hidden" name="_method" value="delete" /><button>Delete</button></form>
</p>
</div>
<% end %>
<% if @notes.size > 0 && @page.next <= @notes.total / settings.per_page %>
<p class="pagination"><a href="?p=<%= @page.next %>">→ Load next</a></p>
<% end %>
</section>