Browse Source

Initial commit

master
Dylan Baker 4 years ago
commit
bd9160957b

+ 2
- 0
.bundle/config View File

@@ -0,0 +1,2 @@
1
+---
2
+BUNDLE_PATH: "vendor/"

+ 4
- 0
.gitignore View File

@@ -0,0 +1,4 @@
1
+.env
2
+vendor/
3
+web/public/*.css
4
+web/public/*.js

+ 23
- 0
Gemfile View File

@@ -0,0 +1,23 @@
1
+# frozen_string_literal: true
2
+
3
+source "https://rubygems.org"
4
+
5
+git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
6
+
7
+# gem "rails"
8
+
9
+gem "httparty", "~> 0.18.0"
10
+gem "nokogiri", "~> 1.10"
11
+gem "sqlite3", "~> 1.4"
12
+
13
+gem "pg", "~> 1.2"
14
+
15
+gem "sequel", "~> 5.30"
16
+
17
+gem "sinatra", "~> 2.0"
18
+
19
+gem "dotenv", "~> 2.7"
20
+
21
+gem "sassc", "~> 2.2"
22
+
23
+gem "rake", "~> 13.0"

+ 50
- 0
Gemfile.lock View File

@@ -0,0 +1,50 @@
1
+GEM
2
+  remote: https://rubygems.org/
3
+  specs:
4
+    dotenv (2.7.5)
5
+    ffi (1.12.2)
6
+    httparty (0.18.0)
7
+      mime-types (~> 3.0)
8
+      multi_xml (>= 0.5.2)
9
+    mime-types (3.3.1)
10
+      mime-types-data (~> 3.2015)
11
+    mime-types-data (3.2019.1009)
12
+    mini_portile2 (2.4.0)
13
+    multi_xml (0.6.0)
14
+    mustermann (1.1.1)
15
+      ruby2_keywords (~> 0.0.1)
16
+    nokogiri (1.10.9)
17
+      mini_portile2 (~> 2.4.0)
18
+    pg (1.2.2)
19
+    rack (2.2.2)
20
+    rack-protection (2.0.8.1)
21
+      rack
22
+    rake (13.0.1)
23
+    ruby2_keywords (0.0.2)
24
+    sassc (2.2.1)
25
+      ffi (~> 1.9)
26
+    sequel (5.30.0)
27
+    sinatra (2.0.8.1)
28
+      mustermann (~> 1.0)
29
+      rack (~> 2.0)
30
+      rack-protection (= 2.0.8.1)
31
+      tilt (~> 2.0)
32
+    sqlite3 (1.4.2)
33
+    tilt (2.0.10)
34
+
35
+PLATFORMS
36
+  ruby
37
+
38
+DEPENDENCIES
39
+  dotenv (~> 2.7)
40
+  httparty (~> 0.18.0)
41
+  nokogiri (~> 1.10)
42
+  pg (~> 1.2)
43
+  rake (~> 13.0)
44
+  sassc (~> 2.2)
45
+  sequel (~> 5.30)
46
+  sinatra (~> 2.0)
47
+  sqlite3 (~> 1.4)
48
+
49
+BUNDLED WITH
50
+   1.17.3

+ 31
- 0
Rakefile View File

@@ -0,0 +1,31 @@
1
+require 'dotenv/load'
2
+require 'sassc'
3
+
4
+require_relative './db/migrate'
5
+require_relative './db/scrape'
6
+
7
+task 'migrate' do
8
+  migrate
9
+end
10
+
11
+task 'scrape' do
12
+  scrape
13
+end
14
+
15
+task 'build' do
16
+  scss = File.read('./web/assets/style.scss')
17
+  css = SassC::Engine.new(scss, style: :compressed).render
18
+  File.write('./web/public/style.css', css)
19
+  js = File.read('./web/assets/script.js')
20
+  File.write('./web/public/script.js', js)
21
+end
22
+
23
+task 'deploy' do
24
+  username = ENV['PROD_USERNAME']
25
+  hostname = ENV['PROD_HOSTNAME']
26
+  puts `rsync -rv ./db #{username}@#{hostname}:/var/www/vlv-search/`
27
+  puts `rsync -rv ./lib #{username}@#{hostname}:/var/www/vlv-search/`
28
+  puts `rsync -rv ./web #{username}@#{hostname}:/var/www/vlv-search/`
29
+  puts `rsync -rv ./Gemfile #{username}@#{hostname}:/var/www/vlv-search/`
30
+  puts `rsync -rv ./Gemfile.lock #{username}@#{hostname}:/var/www/vlv-search/`
31
+end

+ 43
- 0
db/migrate.rb View File

@@ -0,0 +1,43 @@
1
+require 'sequel'
2
+
3
+def migrate
4
+  db = Sequel.connect(adapter: :postgres, database: 'vlv2')
5
+
6
+  db.create_table :threads do
7
+    primary_key :id
8
+    String :title
9
+    String :creator, index: true
10
+    Integer :remote_id, index: true, unique: true
11
+    String :last_post_creator
12
+    DateTime :last_post_created_at
13
+    DateTime :created_at, index: true
14
+  end
15
+
16
+  db.create_table :posts do
17
+    primary_key :id
18
+    String :body
19
+    String :creator, index: true
20
+    Integer :remote_id, index: true, unique: true
21
+    foreign_key :thread_id, :threads
22
+    DateTime :created_at, index: true
23
+  end
24
+
25
+  db.create_function(:update_last_post, <<-SQL, language: :plpgsql, returns: :trigger)
26
+    BEGIN
27
+      UPDATE threads
28
+      SET
29
+        last_post_created_at = NEW.created_at,
30
+        last_post_creator = NEW.creator
31
+      WHERE
32
+        threads.id = NEW.thread_id;
33
+      RETURN NEW;
34
+    END
35
+  SQL
36
+
37
+  db.run(<<-SQL)
38
+    CREATE TRIGGER update_last_post
39
+    AFTER INSERT ON posts
40
+    FOR EACH ROW
41
+    EXECUTE PROCEDURE update_last_post();
42
+  SQL
43
+end

+ 137
- 0
db/scrape.rb View File

@@ -0,0 +1,137 @@
1
+require 'dotenv/load'
2
+require 'net/http'
3
+require 'nokogiri'
4
+require 'sequel'
5
+
6
+require_relative '../lib/auth'
7
+
8
+def fetch_page(page_number, cookie)
9
+  url = URI("http://board.vivalavinyl.com/thread/list/#{page_number}")
10
+  http = Net::HTTP.new(url.host, url.port)
11
+  request = Net::HTTP::Get.new(url)
12
+  request['cookie'] = cookie
13
+  response = http.request(request)
14
+  Nokogiri.HTML(response.body)
15
+end
16
+
17
+def fetch_thread(thread, cookie)
18
+  url =
19
+    URI(
20
+      "http://board.vivalavinyl.com/thread/view/#{thread[:remote_id]}&ajax=true"
21
+    )
22
+  http = Net::HTTP.new(url.host, url.port)
23
+  request = Net::HTTP::Get.new(url)
24
+  request['cookie'] = cookie
25
+  response = http.request(request)
26
+  Nokogiri.HTML(response.body)
27
+end
28
+
29
+def parse_posts(thread, page)
30
+  posts = Array.new
31
+
32
+  page.css('.post').each do |_post|
33
+    post = Hash.new
34
+    post[:remote_id] = _post.at_css('ul.view')[:id].split('_')[1].to_i
35
+    post[:creator] = _post.at_css('.memberlink').text.strip
36
+    date, time =
37
+      _post.at_css('.postinfo').text.split('posted this')[1].split('@')
38
+    post[:created_at] = Time.parse("#{date} #{time}")
39
+    post[:body] = _post.at_css('.postbody').children.map(&:to_html).join.strip
40
+    post[:thread_id] = thread[:id]
41
+    posts << post
42
+  end
43
+
44
+  posts
45
+end
46
+
47
+def insert_post(post, db)
48
+  db.exec(
49
+    'insert into posts (body, timestamp, creator, thread_id, remote_id) values ($1, $2, $3, $4, $5)',
50
+    [
51
+      post[:body],
52
+      post[:created_at].to_s,
53
+      post[:creator],
54
+      post[:thread_id].to_i,
55
+      post[:id].to_i
56
+    ]
57
+  )
58
+end
59
+
60
+def parse_threads(page)
61
+  threads = Array.new
62
+  page.css('.even, .odd').each do |row|
63
+    thread = Hash.new
64
+    thread_link = row.at_css('.subject > a')
65
+    next if thread_link.nil?
66
+    thread[:remote_id] = thread_link['href'].split('/')[3]
67
+    thread[:title] = thread_link.text
68
+    creator = row.at_css('.memberlink').text.strip
69
+    creator = creator[0..-2] if creator.match(/\+$/)
70
+    thread[:creator] = creator
71
+    threads << thread
72
+  end
73
+  threads
74
+end
75
+
76
+def scrape(first: 0, last: 0)
77
+  cookie = login(ENV['VLV_USERNAME'], ENV['VLV_PASSWORD'])
78
+  db = Sequel.connect(adapter: :postgres, database: ENV['DB_DATABASE'])
79
+
80
+  (first..last).each do |page_number|
81
+    page = fetch_page(page_number, cookie)
82
+    threads = parse_threads(page)
83
+    threads.each do |t|
84
+      puts t[:title]
85
+
86
+      page = fetch_thread(t, cookie)
87
+      first_post = page.at_css('.postinfo:first-child')
88
+
89
+      next if first_post.nil?
90
+
91
+      post_info = first_post.text.split('posted this')
92
+      date, time = post_info[1].split('@')
93
+      t[:created_at] = Time.parse("#{date} #{time}")
94
+
95
+      thread = db.from(:threads).first(remote_id: t[:remote_id])
96
+      is_new_thread = thread.nil?
97
+      if is_new_thread
98
+        puts '  Inserting thread'
99
+        id =
100
+          db.from(:threads).insert(
101
+            title: t[:title],
102
+            creator: t[:creator],
103
+            remote_id: t[:remote_id],
104
+            created_at: t[:created_at]
105
+          )
106
+        t[:id] = id
107
+        thread = t
108
+      end
109
+
110
+      posts = parse_posts(thread, page)
111
+
112
+      last_post = posts.last
113
+      unless db.from(:posts).first(remote_id: last_post[:remote_id]).nil?
114
+        puts '  Up to date, skipping'
115
+        next
116
+      end
117
+
118
+      posts_count = posts.size
119
+      posts.each_with_index do |p, index|
120
+        msg = "  Inserting post #{index + 1}/#{posts_count}"
121
+        print msg
122
+        if is_new_thread || db.from(:posts).first(remote_id: p[:remote_id]).nil?
123
+          db.from(:posts).insert(
124
+            body: p[:body],
125
+            created_at: p[:created_at],
126
+            thread_id: p[:thread_id],
127
+            creator: p[:creator],
128
+            remote_id: p[:remote_id]
129
+          )
130
+        end
131
+        print "\b" * msg.size unless index == posts_count - 1
132
+      end
133
+
134
+      puts
135
+    end
136
+  end
137
+end

+ 12
- 0
lib/auth.rb View File

@@ -0,0 +1,12 @@
1
+require 'net/http'
2
+
3
+def login(username, password)
4
+  url = URI('http://board.vivalavinyl.com/main/login')
5
+  http = Net::HTTP.new(url.host, url.port)
6
+  request = Net::HTTP::Post.new(url)
7
+  request.body = "name=#{username}&pass=#{password}&login=login"
8
+  response = http.request(request)
9
+  cookies = response.response['set-cookie']
10
+  return nil if cookies.nil?
11
+  cookies.split('; ').first
12
+end

+ 58
- 0
lib/search.rb View File

@@ -0,0 +1,58 @@
1
+require 'sequel'
2
+
3
+def search(params)
4
+  db = Sequel.connect(adapter: :postgres, database: ENV['DB_DATABASE'])
5
+  query = params[:q].strip
6
+  offset = (params[:page] - 1) * 10
7
+  username = params[:username].strip
8
+  case params[:type]
9
+  when 'threads'
10
+    sort =
11
+      case params[:sort]
12
+      when 'thread'
13
+        'threads.created_at DESC'
14
+      when 'post'
15
+        'posts.created_at DESC'
16
+      else
17
+        'threads.created_at DESC'
18
+      end
19
+    search_threads(db, query, username, sort, offset)
20
+  when 'posts'
21
+    search_posts(db, query, username, offset)
22
+  else
23
+    Array.new
24
+  end
25
+end
26
+
27
+def search_threads(db, query, username, sort, offset)
28
+  db[<<-SQL, query, username, username, String.new, offset]
29
+    SELECT
30
+      threads.*
31
+    FROM threads
32
+    WHERE
33
+      to_tsvector(title) @@ plainto_tsquery(?)
34
+      AND (LOWER(threads.creator) = LOWER(?) OR ? = '')
35
+    ORDER BY #{sort}
36
+    LIMIT 10
37
+    OFFSET ?;
38
+  SQL
39
+end
40
+
41
+def search_posts(db, query, username, offset)
42
+  db[<<-SQL, query, username, username, offset]
43
+    SELECT
44
+      posts.*,
45
+      threads.title as thread_title,
46
+      threads.remote_id as remote_thread_id
47
+    FROM posts
48
+    INNER JOIN threads on posts.thread_id = threads.id
49
+    WHERE
50
+      to_tsvector(body) @@ plainto_tsquery(?)
51
+      AND (
52
+        (LOWER(posts.creator) = LOWER(?)) OR (? = '')
53
+      )
54
+    ORDER BY created_at DESC
55
+    LIMIT 10
56
+    OFFSET ?;
57
+  SQL
58
+end

+ 6
- 0
web/assets/script.js View File

@@ -0,0 +1,6 @@
1
+var types = document.querySelectorAll('input[type=radio][name=type]');
2
+types.forEach(function (type) {
3
+  type.addEventListener('change', function (e) {
4
+    document.querySelector('.filters__section--sort').classList.toggle('open');
5
+  });
6
+});

+ 4
- 0
web/assets/style.scss View File

@@ -0,0 +1,4 @@
1
+@import './web/assets/styles/auth.scss';
2
+@import './web/assets/styles/form.scss';
3
+@import './web/assets/styles/global.scss';
4
+@import './web/assets/styles/results.scss';

+ 14
- 0
web/assets/styles/auth.scss View File

@@ -0,0 +1,14 @@
1
+.auth {
2
+  align-items: center;
3
+  display: flex;
4
+  font-size: 14px;
5
+  justify-content: space-between;
6
+}
7
+
8
+.auth__copy {
9
+  margin: 0;
10
+}
11
+
12
+.auth__username {
13
+  font-weight: bold;
14
+}

+ 51
- 0
web/assets/styles/form.scss View File

@@ -0,0 +1,51 @@
1
+.form {
2
+  display: flex;
3
+  flex-direction: column;
4
+  margin: 2em auto;
5
+  width: 100%;
6
+}
7
+
8
+.form__search {
9
+  border: 1px solid #999;
10
+  border-radius: 5px;
11
+  font-size: 20px;
12
+  flex: 1;
13
+  padding: 0.5em;
14
+}
15
+
16
+.form .filters {
17
+  margin: 1em 0;
18
+}
19
+
20
+.filters__section--sort {
21
+  display: none;
22
+}
23
+
24
+.filters__section--sort.open {
25
+  display: block;
26
+}
27
+
28
+.form__field {
29
+  margin: 10px 0;
30
+}
31
+
32
+.form__text-field {
33
+  border: 1px solid #000000;
34
+  border-radius: 4px;
35
+  font-size: 16px;
36
+  padding: 5px;
37
+  width: 100%;
38
+}
39
+
40
+.form__submit {
41
+  font-size: 16px;
42
+}
43
+
44
+.form--login {
45
+  margin-top: 0;
46
+}
47
+
48
+.form--login .form__label {
49
+  display: block;
50
+  margin: 10px 0;
51
+}

+ 49
- 0
web/assets/styles/global.scss View File

@@ -0,0 +1,49 @@
1
+* {
2
+  box-sizing: border-box;
3
+}
4
+
5
+body {
6
+  font-family: verdana, sans-serif;
7
+}
8
+
9
+img {
10
+  max-width: 100%;
11
+}
12
+
13
+blockquote {
14
+  font-style: italic;
15
+  margin: 0;
16
+}
17
+
18
+.error {
19
+  color: #DD2222;
20
+}
21
+
22
+.container {
23
+  margin: auto;
24
+  max-width: 800px;
25
+}
26
+
27
+.header__link {
28
+  color: black;
29
+  text-decoration: none;
30
+}
31
+
32
+.btn {
33
+  background-color: rgb(40, 96, 144);
34
+  border: 1px solid #222;
35
+  border-radius: 5px;
36
+  color: #ffffff;
37
+  cursor: pointer;
38
+  font-size: 20px;
39
+  padding: 0.5em 0.5em;
40
+  text-decoration: none;
41
+}
42
+
43
+.btn:hover {
44
+  background-color: rgb(30, 85, 135);
45
+}
46
+
47
+.btn--small {
48
+  font-size: 14px;
49
+}

+ 42
- 0
web/assets/styles/results.scss View File

@@ -0,0 +1,42 @@
1
+.results-container {
2
+  margin: auto;
3
+  max-width: 800px;
4
+  padding-bottom: 1em;
5
+}
6
+
7
+.results__subheader {
8
+  border-bottom: 1px solid black;
9
+  margin: 1.5em 0;
10
+  padding: 5px 0;
11
+}
12
+
13
+.results__result {
14
+  background: #efefef;
15
+  border: 1px solid black;
16
+  border-radius: 5px;
17
+  margin: 1em 0;
18
+  padding: 0.5em;
19
+}
20
+
21
+.results__result.threads .result__info {
22
+  margin-bottom: 0;
23
+}
24
+
25
+.result__info {
26
+  display: flex;
27
+  font-size: 12px;
28
+  justify-content: space-between;
29
+
30
+  @media(max-width: 500px) {
31
+    flex-direction: column;
32
+
33
+    span {
34
+      display: inline-block;
35
+      margin: 5px 0;
36
+    }
37
+  }
38
+}
39
+
40
+.btn--prev {
41
+  margin-right: 1em;
42
+}

+ 84
- 0
web/server.rb View File

@@ -0,0 +1,84 @@
1
+require 'dotenv/load'
2
+require 'sequel'
3
+require 'sinatra'
4
+
5
+require_relative '../lib/auth'
6
+require_relative '../lib/search'
7
+
8
+class VLVSearch < Sinatra::Base
9
+  enable :sessions
10
+
11
+  get '/' do
12
+    redirect '/login' unless signed_in?
13
+    params[:type] = 'threads'
14
+    erb :index, { locals: params, layout: :layout }
15
+  end
16
+
17
+  get '/search' do
18
+    redirect '/login' unless signed_in?
19
+    params[:type] = 'threads' unless params[:type]
20
+    params[:page] = 1 unless params[:page]
21
+    params[:page] = params[:page].to_i
22
+
23
+    results = search(params)
24
+    previous_url, next_url = build_urls(params)
25
+
26
+    locals =
27
+      params.merge(
28
+        results: results, previous_url: previous_url, next_url: next_url
29
+      )
30
+
31
+    erb :results, { locals: locals, layout: :layout }
32
+  end
33
+
34
+  get '/login' do
35
+    erb :login, { layout: :layout, locals: { error_message: nil } }
36
+  end
37
+
38
+  post '/login' do
39
+    username = params[:username]
40
+    password = params[:password]
41
+    cookie = login(username, password)
42
+
43
+    if cookie.nil?
44
+      erb :login,
45
+          { layout: :layout, locals: { error_message: 'Invalid credentials' } }
46
+    else
47
+      session[:user_id] = username
48
+      redirect '/'
49
+    end
50
+  end
51
+
52
+  post '/logout' do
53
+    session[:user_id] = nil
54
+    redirect '/'
55
+  end
56
+
57
+  helpers do
58
+    def build_urls(params)
59
+      current_page = params[:page].to_i
60
+      previous_page = current_page > 1 ? current_page - 1 : nil
61
+      next_page = current_page + 1
62
+
63
+      url_params = { q: params[:q], type: params[:type] }
64
+      url_params[:username] = params[:username] if params[:username]
65
+
66
+      [previous_page, next_page].map do |page|
67
+        URI::Generic.build(
68
+          path: '/search',
69
+          query: URI.encode_www_form(url_params.merge(page: page))
70
+        )
71
+      end
72
+    end
73
+
74
+    def current_user
75
+      session[:user_id]
76
+    end
77
+
78
+    def signed_in?
79
+      !!session[:user_id]
80
+    end
81
+
82
+    run!
83
+  end
84
+end

+ 1
- 0
web/views/index.erb View File

@@ -0,0 +1 @@
1
+<%= erb :'partials/search' %>

+ 17
- 0
web/views/layout.erb View File

@@ -0,0 +1,17 @@
1
+<!DOCTYPE html>
2
+<html lang="en">
3
+  <head>
4
+    <title>vlv search</title>
5
+    <link rel="stylesheet" href="/style.css">
6
+  </head>
7
+  <body>
8
+    <div class="container">
9
+      <h1 class="header">
10
+        <a href="/" class="header__link">vlv search</a>
11
+      </h1>
12
+      <%= erb :'partials/auth' %>
13
+      <%= yield %>
14
+    </div>
15
+    <script src="/script.js"></script>
16
+  </body>
17
+</html>

+ 19
- 0
web/views/login.erb View File

@@ -0,0 +1,19 @@
1
+<div class="container">
2
+  <h2 class="container__heading">Login</h2>
3
+  <form class="form form--login" action="/login" method="POST">
4
+    <% if error_message %>
5
+      <p class="error"><%= error_message %></p>
6
+    <% end %>
7
+    <div class="form__field">
8
+      <label class="form__label">Username</label>
9
+      <input type="text" name="username" class="form__text-field" value="<%= params[:username] %>">
10
+    </div>
11
+    <div class="form__field">
12
+      <label class="form__label">Password</label>
13
+      <input type="password" name="password" class="form__text-field" value="<%= params[:password] %>">
14
+    </div>
15
+    <div class="form__field">
16
+      <input type="submit" value="Login" class="btn form__submit">
17
+    </div>
18
+  </form>
19
+</div>

+ 8
- 0
web/views/partials/auth.erb View File

@@ -0,0 +1,8 @@
1
+<% if signed_in? %>
2
+  <form class="auth" method="POST" action="/logout">
3
+    <p class="auth__copy">
4
+      Logged in as <span class="auth__username"><%= current_user %></span>
5
+    </p>
6
+    <input type="submit" class="auth__logout btn btn--small" value="Logout">
7
+  </form>
8
+<% end %>

+ 34
- 0
web/views/partials/search.erb View File

@@ -0,0 +1,34 @@
1
+<div class="search">
2
+  <form action="/search" method="get" class="form">
3
+    <input name="q" type="search" value="<%= params[:q] %>" placeholder="Search for..." class="form__search" required>
4
+    <div class="filters">
5
+      <div class="filters__section">
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__section filters__section--sort <%= params[:type] == 'threads' ? 'open' : '' %>">
17
+        <p>Sort by:</p>
18
+        <label class="form__label">
19
+          <input type="radio" name="sort" value="thread" <% if [nil, "thread"].include? params[:sort] %>checked<% end%>>
20
+          Thread creation
21
+        </label>
22
+        <label class="form__label">
23
+          <input type="radio" name="sort" value="post" <% if params[:sort] == "post" %>checked<% end %>>
24
+          Most recent post
25
+        </label>
26
+      </div>
27
+      <div class="filters__section">
28
+        <p>Posted by:</p>
29
+        <input type="text" name="username" value="<%= params[:username] %>" class="form__text-field">
30
+      </div>
31
+    </div>
32
+    <input type="submit" value="Search" class="btn form__submit">
33
+  </form>
34
+</div>

+ 57
- 0
web/views/results.erb View File

@@ -0,0 +1,57 @@
1
+<%= erb :'partials/search' %>
2
+
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>
10
+    <% end %>
11
+    <% results.each do |result| %>
12
+      <div class="results__result <%= params[:type] %>">
13
+        <% if params[:type] == "threads" %>
14
+          <a href="http://board.vivalavinyl.com/thread/view/<%= result[:remote_id] %>">
15
+            <%= result[:title] %>
16
+          </a>
17
+          <p class="result__info">
18
+            <span>
19
+              created by <%= result[:creator] %>
20
+              on <%= result[:created_at].strftime('%B %d, %Y') %>
21
+            </span>
22
+            <span>
23
+              last post by <%= result[:last_post_creator] %>
24
+              at <%= result[:last_post_created_at].strftime('%-I:%M %p EST') %>
25
+              on <%= result[:last_post_created_at].strftime('%B %-d, %Y') %>
26
+            </span>
27
+          </p>
28
+        <% elsif params[:type] == "posts" %>
29
+          <p>
30
+            In thread:
31
+            <a href="http://board.vivalavinyl.com/thread/view/<%= result[:remote_thread_id] %>">
32
+              <strong><%= result[:thread_title] %></strong>
33
+            </a>
34
+          </p>
35
+          <p class="result__info">
36
+            <%= result[:creator] %> posted this at <%= result[:created_at] %>
37
+          </p>
38
+          <p class="result__body">
39
+            <%= result[:body] %>
40
+          </p>
41
+        <% end %>
42
+      </div>
43
+    <% end %>
44
+
45
+    <% if params[:q] %>
46
+      <% if params[:page] > 1 %>
47
+        <a class="btn btn--small btn--prev" href="<%= previous_url %>">
48
+          previous page
49
+        </a>
50
+      <% end %>
51
+
52
+      <a class="btn btn--small btn--next" href="<%= next_url %>">
53
+        next page
54
+      </a>
55
+    <% end %>
56
+  </div>
57
+</div>

Loading…
Cancel
Save