5 Commits

Author SHA1 Message Date
  Dylan Baker 59bb48b2ef Fix next_page heuristic 3 years ago
  Dylan Baker 8d3e2a9c08 Fix result limit/offset mismatch 3 years ago
  Dylan Baker 8a1b934bac Don't show next page button if there is no next page 3 years ago
  Dylan Baker b3aafafa29 Implement date filters 3 years ago
  Dylan Baker bfc8cd2511 Log in development 3 years ago
7 changed files with 219 additions and 105 deletions
  1. 1
    0
      .gitignore
  2. 3
    1
      db/connect.rb
  3. 55
    16
      lib/search.rb
  4. 11
    1
      web/assets/styles/form.scss
  5. 34
    11
      web/server.rb
  6. 52
    22
      web/views/partials/search.erb
  7. 63
    54
      web/views/results.erb

+ 1
- 0
.gitignore View File

@@ -2,3 +2,4 @@
2 2
 vendor/
3 3
 web/public/*.css
4 4
 web/public/*.js
5
+db/log

+ 3
- 1
db/connect.rb View File

@@ -1,4 +1,5 @@
1 1
 require 'dotenv'
2
+require 'logger'
2 3
 
3 4
 Dotenv.load(File.expand_path('../.env'))
4 5
 
@@ -7,5 +8,6 @@ DB =
7 8
     adapter: :postgres,
8 9
     database: ENV['DB_DATABASE'],
9 10
     user: ENV['DB_USERNAME'],
10
-    password: ENV['DB_PASSWORD']
11
+    password: ENV['DB_PASSWORD'],
12
+    logger: ENV['APP_ENV'] == 'development' ? Logger.new('db/log') : nil
11 13
   )

+ 55
- 16
lib/search.rb View File

@@ -1,10 +1,43 @@
1 1
 require 'sequel'
2 2
 
3
+RESULTS_PER_PAGE = 50
4
+
3 5
 def search(params)
4 6
   query = params[:q].strip
5
-  offset = (params[:page] - 1) * 10
7
+  offset = (params[:page] - 1) * RESULTS_PER_PAGE
6 8
   username = params[:username].strip
7
-  case params[:type]
9
+  from_date = params[:from_date].strip
10
+  to_date = params[:to_date].strip
11
+
12
+  errors = Array.new
13
+
14
+  if from_date.empty?
15
+    from_date = nil
16
+  else
17
+    if from_date.match(/\d{4}-\d{2}-\d{2}/)
18
+      from_date = Date.parse(params[:from_date]) rescue errors << 'Invalid From Date'
19
+    else
20
+      errors << 'From Date must be in the format YYYY-MM-DD'
21
+    end
22
+  end
23
+
24
+  if to_date.empty?
25
+    to_date = nil
26
+  else
27
+    if to_date.match(/\d{4}-\d{2}-\d{2}/)
28
+      to_date = Date.parse(params[:to_date]).next rescue errors << 'Invalid To Date'
29
+    else
30
+      errors << 'To Date must be in the format YYYY-MM-DD'
31
+    end
32
+  end
33
+
34
+  if errors.empty? && !to_date.nil? && !from_date.nil? &&  to_date < from_date
35
+    errors << 'To Date must be after From Date'
36
+  end
37
+
38
+  return {results: Array.new, errors: errors} unless errors.empty?
39
+
40
+  results = case params[:type]
8 41
   when 'threads'
9 42
     sort =
10 43
       case params[:sort]
@@ -15,43 +48,49 @@ def search(params)
15 48
       else
16 49
         'created_at DESC'
17 50
       end
18
-    search_threads(query, username, sort, offset)
51
+    search_threads(query, username, from_date, to_date, sort, offset)
19 52
   when 'posts'
20
-    search_posts(query, username, offset)
53
+    search_posts(query, username, from_date, to_date, offset)
21 54
   else
22 55
     Array.new
23 56
   end
57
+
58
+  {results: results, errors: errors}
24 59
 end
25 60
 
26
-def search_threads(query, username, sort, offset)
27
-  DB[<<-SQL, query, username, username, offset]
61
+def search_threads(query, username, from_date, to_date, sort, offset)
62
+  DB[<<-SQL, query, username, username, from_date, from_date, to_date, to_date, offset]
28 63
     SELECT
29
-      threads.*
64
+      threads.*,
65
+      count(*) OVER() AS full_count
30 66
     FROM threads
31 67
     WHERE
32 68
       to_tsvector(title) @@ plainto_tsquery(?)
33 69
       AND (LOWER(threads.creator) = LOWER(?) OR ? = '')
70
+      AND (created_at >= ? OR ? IS NULL)
71
+      AND (created_at <= ? OR ? IS NULL)
34 72
     ORDER BY #{sort}
35
-    LIMIT 50
73
+    LIMIT #{RESULTS_PER_PAGE}
36 74
     OFFSET ?;
37 75
   SQL
38 76
 end
39 77
 
40
-def search_posts(query, username, offset)
41
-  DB[<<-SQL, query, username, username, offset]
78
+def search_posts(query, username, from_date, to_date, offset)
79
+  DB[<<-SQL, query, username, username, from_date, from_date, to_date, to_date, offset]
42 80
     SELECT
43 81
       posts.*,
44 82
       threads.title as thread_title,
45
-      threads.remote_id as remote_thread_id
83
+      threads.remote_id as remote_thread_id,
84
+      count(*) OVER() AS full_count
46 85
     FROM posts
47 86
     INNER JOIN threads on posts.thread_id = threads.id
48 87
     WHERE
49 88
       tsv @@ plainto_tsquery(?)
50
-      AND (
51
-        (LOWER(posts.creator) = LOWER(?)) OR (? = '')
52
-      )
53
-    ORDER BY created_at DESC
54
-    LIMIT 50
89
+      AND ((LOWER(posts.creator) = LOWER(?)) OR (? = ''))
90
+      AND (posts.created_at >= ? OR ? IS NULL)
91
+      AND (posts.created_at <= ? OR ? IS NULL)
92
+    ORDER BY posts.created_at DESC
93
+    LIMIT #{RESULTS_PER_PAGE}
55 94
     OFFSET ?;
56 95
   SQL
57 96
 end

+ 11
- 1
web/assets/styles/form.scss View File

@@ -17,12 +17,22 @@
17 17
   margin: 1em 0;
18 18
 }
19 19
 
20
+.filters__section {
21
+  display: flex;
22
+  padding: .5em 0;
23
+}
24
+
20 25
 .filters__section--sort {
21 26
   display: none;
22 27
 }
23 28
 
24 29
 .filters__section--sort.open {
25
-  display: block;
30
+  display: flex;
31
+}
32
+
33
+.filters__subsection {
34
+  flex: 1;
35
+  padding: 0 1em;
26 36
 }
27 37
 
28 38
 .form__field {

+ 34
- 11
web/server.rb View File

@@ -34,16 +34,28 @@ class VLVSearch < Sinatra::Base
34 34
     params[:page] = params[:page].to_i
35 35
     params[:q] = String.new unless params[:q]
36 36
     params[:username] = String.new unless params[:username]
37
+    params[:from_date] = String.new unless params[:from_date]
38
+    params[:to_date] = String.new unless params[:to_date]
37 39
 
38 40
     results = search(params)
39
-    previous_url, next_url = build_urls(params)
40 41
 
41
-    locals =
42
-      params.merge(
43
-        results: results, previous_url: previous_url, next_url: next_url
44
-      )
42
+    unless results[:errors].empty?
43
+      erb :results, { locals: {errors: results[:errors]}, layout: :layout }
44
+    else
45
+      params[:current_count] = results[:results].to_a.size
46
+      params[:full_count] = results[:results].empty? ? 0 : results[:results].first[:full_count]
47
+      previous_url, next_url = build_urls(params)
48
+
49
+      locals =
50
+        params.merge(
51
+          results: results[:results],
52
+          previous_url: previous_url,
53
+          next_url: next_url,
54
+          errors: Array.new,
55
+        )
45 56
 
46
-    erb :results, { locals: locals, layout: :layout }
57
+      erb :results, { locals: locals, layout: :layout }
58
+    end
47 59
   end
48 60
 
49 61
   get '/login' do
@@ -75,17 +87,28 @@ class VLVSearch < Sinatra::Base
75 87
     def build_urls(params)
76 88
       current_page = params[:page].to_i
77 89
       previous_page = current_page > 1 ? current_page - 1 : nil
78
-      next_page = current_page + 1
90
+
91
+      if (params[:current_count] + ((current_page - 1) * RESULTS_PER_PAGE)) == params[:full_count]
92
+        next_page = nil
93
+      else
94
+        next_page = current_page + 1
95
+      end
79 96
 
80 97
       url_params = { q: params[:q], type: params[:type] }
81 98
       url_params[:username] = params[:username] if params[:username]
99
+      url_params[:from_date] = params[:from_date] if params[:from_date]
100
+      url_params[:to_date] = params[:to_date] if params[:to_date]
82 101
 
83
-      [previous_page, next_page].map do |page|
102
+      [
84 103
         URI::Generic.build(
85 104
           path: '/search',
86
-          query: URI.encode_www_form(url_params.merge(page: page))
87
-        )
88
-      end
105
+          query: URI.encode_www_form(url_params.merge(page: previous_page))
106
+        ),
107
+        next_page.nil? ? nil : URI::Generic.build(
108
+          path: '/search',
109
+          query: URI.encode_www_form(url_params.merge(page: next_page))
110
+        ),
111
+      ]
89 112
     end
90 113
 
91 114
     def current_user

+ 52
- 22
web/views/partials/search.erb View File

@@ -2,30 +2,60 @@
2 2
   <input name="q" type="search" value="<%= params[:q] %>" placeholder="Search for..." class="form__search" required>
3 3
   <div class="filters">
4 4
     <div class="filters__section">
5
-      <p>Search in:</p>
6
-      <label class="form__label">
7
-        <input type="radio" name="type" value="threads" <% if [nil, "threads"].include?(params[:type]) %>checked<% end%>>
8
-        Threads
9
-      </label>
10
-      <label class="form__label">
11
-        <input type="radio" name="type" value="posts" <% if params[:type] == "posts" %>checked<% end %>>
12
-        Posts
13
-      </label>
5
+      <div class="filters__subsection">
6
+        <p>Search in:</p>
7
+        <label class="form__label">
8
+          <input type="radio" name="type" value="threads" <% if [nil, "threads"].include?(params[:type]) %>checked<% end%>>
9
+          Threads
10
+        </label>
11
+        <label class="form__label">
12
+          <input type="radio" name="type" value="posts" <% if params[:type] == "posts" %>checked<% end %>>
13
+          Posts
14
+        </label>
15
+      </div>
16
+      <div class="filters__subsection">
17
+        <p>From Date <em>(YYYY-MM-DD)</em>:</p>
18
+        <label class="form__label">
19
+          <input
20
+          type="text"
21
+          name="from_date"
22
+          value="<%= params[:from_date] %>"
23
+          class="form__text-field"
24
+          pattern="[0-9]{4}-[0-9]{2}-[0-9]{2}"
25
+          maxlength="10"
26
+        >
27
+        </label>
28
+      </div>
29
+      <div class="filters__subsection">
30
+        <p>To Date <em>(YYYY-MM-DD)</em>:</p>
31
+        <label class="form__label">
32
+          <input
33
+          type="text"
34
+          name="to_date"
35
+          value="<%= params[:to_date] %>"
36
+          class="form__text-field"
37
+          pattern="[0-9]{4}-[0-9]{2}-[0-9]{2}"
38
+          maxlength="10"
39
+        >
40
+        </label>
41
+      </div>
14 42
     </div>
15 43
     <div class="filters__section filters__section--sort <%= params[:type] == 'threads' ? 'open' : '' %>">
16
-      <p>Sort by:</p>
17
-      <label class="form__label">
18
-        <input type="radio" name="sort" value="thread" <% if [nil, "thread"].include? params[:sort] %>checked<% end%>>
19
-        Thread creation
20
-      </label>
21
-      <label class="form__label">
22
-        <input type="radio" name="sort" value="post" <% if params[:sort] == "post" %>checked<% end %>>
23
-        Most recent post
24
-      </label>
25
-    </div>
26
-    <div class="filters__section">
27
-      <p>Posted by:</p>
28
-      <input type="text" name="username" value="<%= params[:username] %>" class="form__text-field">
44
+      <div class="filters__subsection">
45
+        <p>Sort by:</p>
46
+        <label class="form__label">
47
+          <input type="radio" name="sort" value="thread" <% if [nil, "thread"].include? params[:sort] %>checked<% end%>>
48
+          Thread creation
49
+        </label>
50
+        <label class="form__label">
51
+          <input type="radio" name="sort" value="post" <% if params[:sort] == "post" %>checked<% end %>>
52
+          Most recent post
53
+        </label>
54
+      </div>
55
+      <div class="filters__subsection">
56
+        <p>Posted by:</p>
57
+        <input type="text" name="username" value="<%= params[:username] %>" class="form__text-field">
58
+      </div>
29 59
     </div>
30 60
   </div>
31 61
   <input type="submit" value="Search" class="btn form__submit">

+ 63
- 54
web/views/results.erb View File

@@ -1,63 +1,72 @@
1 1
 <%= erb :'partials/search' %>
2 2
 
3 3
 <div class="results-container">
4
-  <% if params[:q] %>
5
-    <p>Showing results for <strong><em><%= params[:q] %></em></strong>:</p>
6
-  <% end %>
7
-  <div class="results">
8
-    <% if results.empty? %>
9
-      <h3>No results!</h3>
4
+  <% if errors.empty? %>
5
+    <% if params[:q] %>
6
+      <p>Showing results for <strong><em><%= params[:q] %></em></strong>:</p>
10 7
     <% end %>
11
-    <% results.each do |result| %>
12
-      <div class="results__result <%= params[:type] %>">
13
-        <% if params[:type] == "threads" %>
14
-          <%= external_link(
15
-            "http://board.vivalavinyl.com/thread/view/#{result[:remote_id]}",
16
-            result[:title]
17
-          ) %>
18
-          <p class="result__info">
19
-            <span>
20
-              created by <%= result[:creator] %>
21
-              on <%= result[:created_at].strftime('%B %d, %Y') %>
22
-            </span>
23
-            <span>
24
-              last post by <%= result[:last_post_creator] %>
25
-              at <%= result[:last_post_created_at].strftime('%-I:%M %p EST') %>
26
-              on <%= result[:last_post_created_at].strftime('%B %-d, %Y') %>
27
-            </span>
28
-          </p>
29
-        <% elsif params[:type] == "posts" %>
30
-          <p>
31
-            In thread:
32
-            <strong>
33
-              <%= external_link(
34
-                "http://board.vivalavinyl.com/thread/view/#{result[:remote_thread_id]}",
35
-                result[:thread_title]
36
-              ) %>
37
-            </strong>
38
-          </p>
39
-          <p class="result__info">
40
-            <%= result[:creator] %> posted this at <%= result[:created_at] %>
41
-          </p>
42
-          <p class="result__body">
43
-            <%= result[:body] %>
44
-          </p>
8
+    <div class="results">
9
+      <% if results.empty? %>
10
+        <h3>No results!</h3>
11
+      <% end %>
12
+      <% results.each do |result| %>
13
+        <div class="results__result <%= params[:type] %>">
14
+          <% if params[:type] == "threads" %>
15
+            <%= external_link(
16
+              "http://board.vivalavinyl.com/thread/view/#{result[:remote_id]}",
17
+              result[:title]
18
+            ) %>
19
+            <p class="result__info">
20
+              <span>
21
+                created by <%= result[:creator] %>
22
+                on <%= result[:created_at].strftime('%B %d, %Y') %>
23
+              </span>
24
+              <span>
25
+                last post by <%= result[:last_post_creator] %>
26
+                at <%= result[:last_post_created_at].strftime('%-I:%M %p EST') %>
27
+                on <%= result[:last_post_created_at].strftime('%B %-d, %Y') %>
28
+              </span>
29
+            </p>
30
+          <% elsif params[:type] == "posts" %>
31
+            <p>
32
+              In thread:
33
+              <strong>
34
+                <%= external_link(
35
+                  "http://board.vivalavinyl.com/thread/view/#{result[:remote_thread_id]}",
36
+                  result[:thread_title]
37
+                ) %>
38
+              </strong>
39
+            </p>
40
+            <p class="result__info">
41
+              <%= result[:creator] %> posted this at <%= result[:created_at] %>
42
+            </p>
43
+            <p class="result__body">
44
+              <%= result[:body] %>
45
+            </p>
46
+          <% end %>
47
+        </div>
48
+      <% end %>
49
+
50
+      <% if params[:q] %>
51
+        <% if params[:page] > 1 %>
52
+          <a class="btn btn--small btn--prev" href="<%= previous_url %>">
53
+            previous page
54
+          </a>
45 55
         <% end %>
46
-      </div>
47
-    <% end %>
48 56
 
49
-    <% if params[:q] %>
50
-      <% if params[:page] > 1 %>
51
-        <a class="btn btn--small btn--prev" href="<%= previous_url %>">
52
-          previous page
53
-        </a>
57
+        <% unless results.empty? || next_url.nil? %>
58
+          <a class="btn btn--small btn--next" href="<%= next_url %>">
59
+            next page
60
+          </a>
61
+        <% end %>
54 62
       <% end %>
55
-
56
-      <% unless results.empty? %>
57
-        <a class="btn btn--small btn--next" href="<%= next_url %>">
58
-          next page
59
-        </a>
63
+    </div>
64
+  <% else %>
65
+    <div class="errors">
66
+      <h3 class="errors__heading">Error(s):</h3>
67
+      <% errors.each do |error| %>
68
+        <p class="errors__error error"><%= error %></p>
60 69
       <% end %>
61
-    <% end %>
62
-  </div>
70
+    </div>
71
+  <% end %>
63 72
 </div>

Loading…
Cancel
Save