brave_search 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +27 -0
  3. data/README.md +179 -0
  4. data/Rakefile +8 -0
  5. data/brave_search.gemspec +41 -0
  6. data/dummy_app/.dockerignore +45 -0
  7. data/dummy_app/.github/dependabot.yml +12 -0
  8. data/dummy_app/.github/workflows/ci.yml +66 -0
  9. data/dummy_app/.rubocop.yml +8 -0
  10. data/dummy_app/Dockerfile +69 -0
  11. data/dummy_app/Gemfile +48 -0
  12. data/dummy_app/README.md +24 -0
  13. data/dummy_app/Rakefile +6 -0
  14. data/dummy_app/app/controllers/application_controller.rb +2 -0
  15. data/dummy_app/app/controllers/concerns/.keep +0 -0
  16. data/dummy_app/app/controllers/searches_controller.rb +117 -0
  17. data/dummy_app/app/jobs/application_job.rb +7 -0
  18. data/dummy_app/app/mailers/application_mailer.rb +4 -0
  19. data/dummy_app/app/models/application_record.rb +3 -0
  20. data/dummy_app/app/models/concerns/.keep +0 -0
  21. data/dummy_app/app/views/layouts/mailer.html.erb +13 -0
  22. data/dummy_app/app/views/layouts/mailer.text.erb +1 -0
  23. data/dummy_app/bin/brakeman +7 -0
  24. data/dummy_app/bin/dev +2 -0
  25. data/dummy_app/bin/docker-entrypoint +14 -0
  26. data/dummy_app/bin/rails +4 -0
  27. data/dummy_app/bin/rake +4 -0
  28. data/dummy_app/bin/rubocop +8 -0
  29. data/dummy_app/bin/setup +34 -0
  30. data/dummy_app/bin/thrust +5 -0
  31. data/dummy_app/config/application.rb +32 -0
  32. data/dummy_app/config/boot.rb +4 -0
  33. data/dummy_app/config/cable.yml +10 -0
  34. data/dummy_app/config/database.yml +41 -0
  35. data/dummy_app/config/environment.rb +5 -0
  36. data/dummy_app/config/environments/development.rb +70 -0
  37. data/dummy_app/config/environments/production.rb +86 -0
  38. data/dummy_app/config/environments/test.rb +53 -0
  39. data/dummy_app/config/initializers/brave_search.rb +10 -0
  40. data/dummy_app/config/initializers/cors.rb +16 -0
  41. data/dummy_app/config/initializers/filter_parameter_logging.rb +8 -0
  42. data/dummy_app/config/initializers/inflections.rb +16 -0
  43. data/dummy_app/config/locales/en.yml +31 -0
  44. data/dummy_app/config/puma.rb +41 -0
  45. data/dummy_app/config/routes.rb +16 -0
  46. data/dummy_app/config/storage.yml +34 -0
  47. data/dummy_app/config.ru +6 -0
  48. data/dummy_app/db/seeds.rb +9 -0
  49. data/dummy_app/lib/tasks/.keep +0 -0
  50. data/dummy_app/public/robots.txt +1 -0
  51. data/dummy_app/script/.keep +0 -0
  52. data/dummy_app/test/controllers/.keep +0 -0
  53. data/dummy_app/test/fixtures/files/.keep +0 -0
  54. data/dummy_app/test/integration/.keep +0 -0
  55. data/dummy_app/test/mailers/.keep +0 -0
  56. data/dummy_app/test/models/.keep +0 -0
  57. data/dummy_app/test/test_helper.rb +15 -0
  58. data/dummy_app/vendor/.keep +0 -0
  59. data/example.rb +32 -0
  60. data/lib/brave_search/async_client.rb +52 -0
  61. data/lib/brave_search/client.rb +140 -0
  62. data/lib/brave_search/configuration.rb +21 -0
  63. data/lib/brave_search/exporter.rb +43 -0
  64. data/lib/brave_search/exporters/base.rb +23 -0
  65. data/lib/brave_search/exporters/csv.rb +32 -0
  66. data/lib/brave_search/exporters/json.rb +25 -0
  67. data/lib/brave_search/exporters/xlsx.rb +47 -0
  68. data/lib/brave_search/jobs/export_job.rb +40 -0
  69. data/lib/brave_search/jobs/pdf_download_job.rb +38 -0
  70. data/lib/brave_search/pdf_downloader.rb +46 -0
  71. data/lib/brave_search/railtie.rb +15 -0
  72. data/lib/brave_search/results.rb +93 -0
  73. data/lib/brave_search/storage/s3.rb +47 -0
  74. data/lib/brave_search/storage.rb +21 -0
  75. data/lib/brave_search/summarizer.rb +38 -0
  76. data/lib/brave_search/summary_result.rb +76 -0
  77. data/lib/brave_search/version.rb +5 -0
  78. data/lib/brave_search.rb +38 -0
  79. data/lib/generators/brave_search/install_generator.rb +44 -0
  80. data/test_with_real_api.rb +69 -0
  81. metadata +248 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c976d8ab5da9bc80fd7d218034420f86f57088716d8dca2d482e1e27e6e48d4e
4
+ data.tar.gz: fec4b6454ea240e5e65a5489f8e3a1f04ac046ad174c648ef2766e5015704496
5
+ SHA512:
6
+ metadata.gz: 42b474a7ebec418ae7ff28e91e157aa91471c450e5ed07e75bf82449106754ef9dfa4241133387c912fceee9b433f5231dd018bdaea9ae1e4e7b959dc9c949c3
7
+ data.tar.gz: 9b326af2244876c3cfa68ece7db49334b945e33bd5b0bb58f719c7b2a596be08fdfffdb92fbf254a2942af61584ee7601296f4c94354d23a93bbe4ff244123bc
data/.rubocop.yml ADDED
@@ -0,0 +1,27 @@
1
+ require:
2
+ - rubocop-rspec
3
+
4
+ AllCops:
5
+ TargetRubyVersion: 3.0
6
+ NewCops: enable
7
+ Exclude:
8
+ - 'dummy_app/**/*'
9
+ - 'vendor/**/*'
10
+ - 'bin/**/*'
11
+
12
+ Layout/LineLength:
13
+ Max: 120
14
+
15
+ Metrics/BlockLength:
16
+ Exclude:
17
+ - 'spec/**/*'
18
+ - '*.gemspec'
19
+
20
+ Style/Documentation:
21
+ Enabled: false
22
+
23
+ Style/StringLiterals:
24
+ EnforcedStyle: double_quotes
25
+
26
+ Style/FrozenStringLiteralComment:
27
+ Enabled: true
data/README.md ADDED
@@ -0,0 +1,179 @@
1
+ # BraveSearch Ruby Gem
2
+
3
+ Ruby client for the Brave Search API with Rails 8 integration, Ruby 3+ pattern matching, and async support.
4
+
5
+ ## Features
6
+
7
+ - 🔍 **Multiple Search Types**: Web, news, video, image, suggest, and spellcheck
8
+ - ⚡ **Async Support**: Concurrent searches using Ruby 3+ Fiber.schedule
9
+ - 🎯 **Pattern Matching**: Ruby 3+ pattern matching support for results
10
+ - 🚆 **Rails 8 Integration**: Automatic configuration with Rails credentials
11
+ - 🧪 **Comprehensive Testing**: RSpec tests with WebMock
12
+ - 💎 **Modern Ruby**: Requires Ruby 3.0+ for modern features
13
+
14
+ ## Installation
15
+
16
+ Add this line to your application's Gemfile:
17
+
18
+ ```ruby
19
+ gem 'brave_search'
20
+ ```
21
+
22
+ And then execute:
23
+
24
+ $ bundle install
25
+
26
+ ## Usage
27
+
28
+ ### Basic Usage
29
+
30
+ ```ruby
31
+ client = BraveSearch::Client.new(api_key: 'your_api_key')
32
+ results = client.search(q: 'ruby programming', count: 10)
33
+
34
+ # Results wrapper with convenience methods
35
+ puts results.web_results.first[:title]
36
+ puts "Found #{results.count} total results"
37
+ ```
38
+
39
+ ### Multiple Search Types
40
+
41
+ ```ruby
42
+ # Different search types
43
+ web_results = client.search(q: 'ruby programming')
44
+ news_results = client.news_search(q: 'ruby news')
45
+ video_results = client.video_search(q: 'ruby tutorials')
46
+ image_results = client.image_search(q: 'ruby logo')
47
+
48
+ # Suggestions and spellcheck
49
+ suggestions = client.suggest(q: 'ruby prog')
50
+ spelling = client.spellcheck(q: 'rubyy')
51
+ ```
52
+
53
+ ### Ruby 3+ Pattern Matching
54
+
55
+ ```ruby
56
+ results = client.search(q: 'ruby programming')
57
+
58
+ case results
59
+ in { web: { results: [first, *rest] }, query: { original: String => query } }
60
+ puts "Found #{rest.length + 1} results for: '#{query}'"
61
+ puts "Top result: #{first[:title]}"
62
+ in { web: { results: [] } }
63
+ puts "No results found"
64
+ else
65
+ puts "Unexpected response"
66
+ end
67
+ ```
68
+
69
+ ### Async Support
70
+
71
+ ```ruby
72
+ require 'async'
73
+
74
+ async_client = BraveSearch::AsyncClient.new
75
+
76
+ # Single async search
77
+ Async do
78
+ result = async_client.search(q: 'ruby programming').wait
79
+ puts result.web_results.first[:title]
80
+ end
81
+
82
+ # Concurrent searches
83
+ Async do
84
+ results = async_client.concurrent_search([
85
+ 'ruby programming',
86
+ 'rails framework',
87
+ 'async ruby'
88
+ ]).wait
89
+
90
+ results.each { |r| puts "Found #{r.count} results" }
91
+ end
92
+ ```
93
+
94
+ ### S3-Compatible Storage & PDF Downloads
95
+
96
+ ```ruby
97
+ # Configure storage
98
+ BraveSearch.configure do |config|
99
+ config.storage_provider = :hetzner
100
+ config.storage_bucket = "research-papers"
101
+ config.storage_endpoint = "https://fsn1.your-objectstorage.com"
102
+ end
103
+
104
+ # Search and download PDFs automatically
105
+ client = BraveSearch::Client.new
106
+ downloaded = client.search_and_download_pdfs(
107
+ q: "machine learning papers filetype:pdf",
108
+ count: 5,
109
+ folder: "ml-papers/#{Date.today}"
110
+ ) do |completed, total|
111
+ puts "Downloaded #{completed}/#{total} PDFs"
112
+ end
113
+
114
+ # Manual PDF extraction and download
115
+ results = client.search(q: "research papers filetype:pdf")
116
+ pdf_urls = results.pdf_urls
117
+ puts "Found #{pdf_urls.length} PDFs"
118
+
119
+ # Download to specific storage
120
+ storage = BraveSearch::Storage.for(:digitalocean,
121
+ bucket: "documents",
122
+ endpoint: "https://nyc3.digitaloceanspaces.com"
123
+ )
124
+
125
+ results.download_pdfs(storage: storage, folder: "research") do |completed, total|
126
+ puts "Progress: #{completed}/#{total}"
127
+ end
128
+ ```
129
+
130
+ ### Supported Storage Providers
131
+
132
+ - **AWS S3**: `:aws`
133
+ - **Hetzner Object Storage**: `:hetzner`
134
+ - **DigitalOcean Spaces**: `:digitalocean`
135
+ - **Any S3-compatible**: `:s3`
136
+
137
+ ### Rails Integration
138
+
139
+ 1. Install the initializer:
140
+
141
+ ```bash
142
+ rails generate brave_search:install
143
+ ```
144
+
145
+ 2. Add your API key to Rails credentials:
146
+
147
+ ```bash
148
+ rails credentials:edit
149
+ ```
150
+
151
+ Add:
152
+ ```yaml
153
+ brave_api_key: your_api_key_here
154
+ ```
155
+
156
+ 3. Use in your Rails app:
157
+
158
+ ```ruby
159
+ client = BraveSearch::Client.new
160
+ results = client.search(q: 'ruby on rails')
161
+ ```
162
+
163
+ ## Configuration
164
+
165
+ ```ruby
166
+ BraveSearch.configure do |config|
167
+ config.api_key = 'your_api_key'
168
+ config.timeout = 30
169
+ config.retry_attempts = 3
170
+ end
171
+ ```
172
+
173
+ ## Development
174
+
175
+ After checking out the repo, run `bundle install` to install dependencies. Then, run `rake spec` to run the tests.
176
+
177
+ ## License
178
+
179
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/brave_search/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "brave_search"
7
+ spec.version = BraveSearch::VERSION
8
+ spec.authors = ["Remi AI"]
9
+ spec.email = ["denis@goremi.co.uk"]
10
+
11
+ spec.summary = "Ruby client for Brave Search API"
12
+ spec.description = "Simple Ruby client for Brave Search API with Rails integration"
13
+ spec.homepage = "https://github.com/Remi-org/brave-search-ruby"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 3.0.0"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = spec.homepage
19
+ spec.metadata["rubygems_mfa_required"] = "true"
20
+
21
+ spec.files = Dir.chdir(__dir__) do
22
+ `git ls-files -z 2>/dev/null`.split("\x0").reject do |f|
23
+ (File.expand_path(f) == __FILE__) ||
24
+ f.start_with?(*%w[bin/ test/ spec/ features/ .git appveyor Gemfile])
25
+ end
26
+ end
27
+ spec.bindir = "exe"
28
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
29
+ spec.require_paths = ["lib"]
30
+
31
+ spec.add_dependency "async", "~> 2.0"
32
+ spec.add_dependency "caxlsx", "~> 4.0"
33
+ spec.add_dependency "concurrent-ruby", "~> 1.2"
34
+ spec.add_dependency "httparty", "~> 0.21"
35
+
36
+ spec.add_development_dependency "rails", "~> 8.0"
37
+ spec.add_development_dependency "rspec", "~> 3.12"
38
+ spec.add_development_dependency "rubocop", "~> 1.50"
39
+ spec.add_development_dependency "rubocop-rspec", "~> 2.20"
40
+ spec.add_development_dependency "webmock", "~> 3.18"
41
+ end
@@ -0,0 +1,45 @@
1
+ # See https://docs.docker.com/engine/reference/builder/#dockerignore-file for more about ignoring files.
2
+
3
+ # Ignore git directory.
4
+ /.git/
5
+ /.gitignore
6
+
7
+ # Ignore bundler config.
8
+ /.bundle
9
+
10
+ # Ignore all environment files.
11
+ /.env*
12
+
13
+ # Ignore all default key files.
14
+ /config/master.key
15
+ /config/credentials/*.key
16
+
17
+ # Ignore all logfiles and tempfiles.
18
+ /log/*
19
+ /tmp/*
20
+ !/log/.keep
21
+ !/tmp/.keep
22
+
23
+ # Ignore pidfiles, but keep the directory.
24
+ /tmp/pids/*
25
+ !/tmp/pids/.keep
26
+
27
+ # Ignore storage (uploaded files in development and any SQLite databases).
28
+ /storage/*
29
+ !/storage/.keep
30
+ /tmp/storage/*
31
+ !/tmp/storage/.keep
32
+
33
+ # Ignore CI service files.
34
+ /.github
35
+
36
+ # Ignore Kamal files.
37
+ /config/deploy*.yml
38
+ /.kamal
39
+
40
+ # Ignore development files
41
+ /.devcontainer
42
+
43
+ # Ignore Docker-related files
44
+ /.dockerignore
45
+ /Dockerfile*
@@ -0,0 +1,12 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: bundler
4
+ directory: "/"
5
+ schedule:
6
+ interval: daily
7
+ open-pull-requests-limit: 10
8
+ - package-ecosystem: github-actions
9
+ directory: "/"
10
+ schedule:
11
+ interval: daily
12
+ open-pull-requests-limit: 10
@@ -0,0 +1,66 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches: [ main ]
7
+
8
+ jobs:
9
+ scan_ruby:
10
+ runs-on: ubuntu-latest
11
+
12
+ steps:
13
+ - name: Checkout code
14
+ uses: actions/checkout@v4
15
+
16
+ - name: Set up Ruby
17
+ uses: ruby/setup-ruby@v1
18
+ with:
19
+ ruby-version: .ruby-version
20
+ bundler-cache: true
21
+
22
+ - name: Scan for common Rails security vulnerabilities using static analysis
23
+ run: bin/brakeman --no-pager
24
+
25
+ lint:
26
+ runs-on: ubuntu-latest
27
+ steps:
28
+ - name: Checkout code
29
+ uses: actions/checkout@v4
30
+
31
+ - name: Set up Ruby
32
+ uses: ruby/setup-ruby@v1
33
+ with:
34
+ ruby-version: .ruby-version
35
+ bundler-cache: true
36
+
37
+ - name: Lint code for consistent style
38
+ run: bin/rubocop -f github
39
+
40
+ test:
41
+ runs-on: ubuntu-latest
42
+
43
+ # services:
44
+ # redis:
45
+ # image: redis
46
+ # ports:
47
+ # - 6379:6379
48
+ # options: --health-cmd "redis-cli ping" --health-interval 10s --health-timeout 5s --health-retries 5
49
+ steps:
50
+ - name: Install packages
51
+ run: sudo apt-get update && sudo apt-get install --no-install-recommends -y build-essential git libyaml-dev pkg-config
52
+
53
+ - name: Checkout code
54
+ uses: actions/checkout@v4
55
+
56
+ - name: Set up Ruby
57
+ uses: ruby/setup-ruby@v1
58
+ with:
59
+ ruby-version: .ruby-version
60
+ bundler-cache: true
61
+
62
+ - name: Run tests
63
+ env:
64
+ RAILS_ENV: test
65
+ # REDIS_URL: redis://localhost:6379/0
66
+ run: bin/rails db:test:prepare test
@@ -0,0 +1,8 @@
1
+ # Omakase Ruby styling for Rails
2
+ inherit_gem: { rubocop-rails-omakase: rubocop.yml }
3
+
4
+ # Overwrite or add rules to create your own house style
5
+ #
6
+ # # Use `[a, [b, c]]` not `[ a, [ b, c ] ]`
7
+ # Layout/SpaceInsideArrayLiteralBrackets:
8
+ # Enabled: false
@@ -0,0 +1,69 @@
1
+ # syntax=docker/dockerfile:1
2
+ # check=error=true
3
+
4
+ # This Dockerfile is designed for production, not development. Use with Kamal or build'n'run by hand:
5
+ # docker build -t dummy_app .
6
+ # docker run -d -p 80:80 -e RAILS_MASTER_KEY=<value from config/master.key> --name dummy_app dummy_app
7
+
8
+ # For a containerized dev environment, see Dev Containers: https://guides.rubyonrails.org/getting_started_with_devcontainer.html
9
+
10
+ # Make sure RUBY_VERSION matches the Ruby version in .ruby-version
11
+ ARG RUBY_VERSION=3.4.1
12
+ FROM docker.io/library/ruby:$RUBY_VERSION-slim AS base
13
+
14
+ # Rails app lives here
15
+ WORKDIR /rails
16
+
17
+ # Install base packages
18
+ RUN apt-get update -qq && \
19
+ apt-get install --no-install-recommends -y curl libjemalloc2 libvips sqlite3 && \
20
+ rm -rf /var/lib/apt/lists /var/cache/apt/archives
21
+
22
+ # Set production environment
23
+ ENV RAILS_ENV="production" \
24
+ BUNDLE_DEPLOYMENT="1" \
25
+ BUNDLE_PATH="/usr/local/bundle" \
26
+ BUNDLE_WITHOUT="development"
27
+
28
+ # Throw-away build stage to reduce size of final image
29
+ FROM base AS build
30
+
31
+ # Install packages needed to build gems
32
+ RUN apt-get update -qq && \
33
+ apt-get install --no-install-recommends -y build-essential git libyaml-dev pkg-config && \
34
+ rm -rf /var/lib/apt/lists /var/cache/apt/archives
35
+
36
+ # Install application gems
37
+ COPY Gemfile Gemfile.lock ./
38
+ RUN bundle install && \
39
+ rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \
40
+ bundle exec bootsnap precompile --gemfile
41
+
42
+ # Copy application code
43
+ COPY . .
44
+
45
+ # Precompile bootsnap code for faster boot times
46
+ RUN bundle exec bootsnap precompile app/ lib/
47
+
48
+
49
+
50
+
51
+ # Final stage for app image
52
+ FROM base
53
+
54
+ # Copy built artifacts: gems, application
55
+ COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
56
+ COPY --from=build /rails /rails
57
+
58
+ # Run and own only the runtime files as a non-root user for security
59
+ RUN groupadd --system --gid 1000 rails && \
60
+ useradd rails --uid 1000 --gid 1000 --create-home --shell /bin/bash && \
61
+ chown -R rails:rails db log storage tmp
62
+ USER 1000:1000
63
+
64
+ # Entrypoint prepares the database.
65
+ ENTRYPOINT ["/rails/bin/docker-entrypoint"]
66
+
67
+ # Start server via Thruster by default, this can be overwritten at runtime
68
+ EXPOSE 80
69
+ CMD ["./bin/thrust", "./bin/rails", "server"]
data/dummy_app/Gemfile ADDED
@@ -0,0 +1,48 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
4
+ gem "rails", "~> 8.0.2"
5
+ gem "brave_search", path: "../"
6
+ # Use sqlite3 as the database for Active Record
7
+ gem "sqlite3", ">= 2.1"
8
+ # Use the Puma web server [https://github.com/puma/puma]
9
+ gem "puma", ">= 5.0"
10
+ # Build JSON APIs with ease [https://github.com/rails/jbuilder]
11
+ # gem "jbuilder"
12
+
13
+ # Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]
14
+ # gem "bcrypt", "~> 3.1.7"
15
+
16
+ # Windows does not include zoneinfo files, so bundle the tzinfo-data gem
17
+ gem "tzinfo-data", platforms: %i[ windows jruby ]
18
+
19
+ # Use the database-backed adapters for Rails.cache, Active Job, and Action Cable
20
+ gem "solid_cache"
21
+ gem "solid_queue"
22
+ gem "solid_cable"
23
+
24
+ # Reduces boot times through caching; required in config/boot.rb
25
+ gem "bootsnap", require: false
26
+
27
+ # Deploy this application anywhere as a Docker container [https://kamal-deploy.org]
28
+ gem "kamal", require: false
29
+
30
+ # Add HTTP asset caching/compression and X-Sendfile acceleration to Puma [https://github.com/basecamp/thruster/]
31
+ gem "thruster", require: false
32
+
33
+ # Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]
34
+ # gem "image_processing", "~> 1.2"
35
+
36
+ # Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin Ajax possible
37
+ # gem "rack-cors"
38
+
39
+ group :development, :test do
40
+ # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
41
+ gem "debug", platforms: %i[ mri windows ], require: "debug/prelude"
42
+
43
+ # Static analysis for security vulnerabilities [https://brakemanscanner.org/]
44
+ gem "brakeman", require: false
45
+
46
+ # Omakase Ruby styling [https://github.com/rails/rubocop-rails-omakase/]
47
+ gem "rubocop-rails-omakase", require: false
48
+ end
@@ -0,0 +1,24 @@
1
+ # README
2
+
3
+ This README would normally document whatever steps are necessary to get the
4
+ application up and running.
5
+
6
+ Things you may want to cover:
7
+
8
+ * Ruby version
9
+
10
+ * System dependencies
11
+
12
+ * Configuration
13
+
14
+ * Database creation
15
+
16
+ * Database initialization
17
+
18
+ * How to run the test suite
19
+
20
+ * Services (job queues, cache servers, search engines, etc.)
21
+
22
+ * Deployment instructions
23
+
24
+ * ...
@@ -0,0 +1,6 @@
1
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
2
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
+
4
+ require_relative "config/application"
5
+
6
+ Rails.application.load_tasks
@@ -0,0 +1,2 @@
1
+ class ApplicationController < ActionController::API
2
+ end
File without changes
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ class SearchesController < ApplicationController
4
+ def show
5
+ query = params[:q] || "ruby programming papers filetype:pdf"
6
+ @client = BraveSearch::Client.new
7
+ @results = @client.search(q: query, count: 5)
8
+
9
+ render json: {
10
+ query: query,
11
+ web_results: @results.web_results.length,
12
+ pdf_urls: extract_pdf_urls(@results)
13
+ }
14
+ end
15
+
16
+ def download_pdfs
17
+ query = params[:q] || "ruby programming papers filetype:pdf"
18
+
19
+ client = BraveSearch::Client.new
20
+ storage = BraveSearch::Storage.for(:hetzner,
21
+ bucket: "research-papers",
22
+ endpoint: "https://fsn1.your-objectstorage.com"
23
+ )
24
+
25
+ downloaded = client.search_and_download_pdfs(
26
+ q: query,
27
+ count: 3,
28
+ storage: storage,
29
+ folder: "papers/#{Date.today}"
30
+ ) do |completed, total|
31
+ puts "Downloaded #{completed}/#{total} PDFs"
32
+ end
33
+
34
+ render json: {
35
+ query: query,
36
+ downloaded: downloaded.length,
37
+ files: downloaded.map { |d| d[:key] }
38
+ }
39
+ rescue StandardError => e
40
+ render json: { error: e.message }, status: 422
41
+ end
42
+
43
+ def export
44
+ query = params[:q] || "ruby programming"
45
+ format = params[:format] || "json"
46
+
47
+ client = BraveSearch::Client.new
48
+
49
+ if params[:async]
50
+ storage_config = {
51
+ provider: :hetzner,
52
+ options: {
53
+ bucket: "research-exports",
54
+ endpoint: "https://fsn1.your-objectstorage.com"
55
+ }
56
+ }
57
+
58
+ key = "exports/#{Date.today}/#{SecureRandom.hex(8)}.#{format}"
59
+ job = client.search_and_export_async(
60
+ q: query,
61
+ format: format,
62
+ storage_config: storage_config,
63
+ key: key
64
+ )
65
+
66
+ render json: { job_id: job.job_id, key: key }
67
+ else
68
+ result = client.search_and_export(q: query, format: format.to_sym)
69
+
70
+ respond_to do |format_type|
71
+ format_type.json { render json: result[:content] }
72
+ format_type.any { send_data result[:content], filename: "search_results.#{format}" }
73
+ end
74
+ end
75
+ rescue StandardError => e
76
+ render json: { error: e.message }, status: 422
77
+ end
78
+
79
+ def summarize
80
+ query = params[:q] || "AI research trends 2024"
81
+
82
+ client = BraveSearch::Client.new
83
+
84
+ if params[:key]
85
+ # Summarize existing search results
86
+ summary = client.summarizer.summarize(key: params[:key])
87
+ render json: {
88
+ type: "summary",
89
+ summary: summary.summary,
90
+ status: summary.status
91
+ }
92
+ else
93
+ # Search and summarize in one step
94
+ result = client.summarizer.search_and_summarize(q: query)
95
+
96
+ render json: {
97
+ type: "search_and_summarize",
98
+ query: query,
99
+ summary: result.summary,
100
+ key: result.key,
101
+ status: result.status,
102
+ enriched_results: result.enriched_results.map(&:to_h)
103
+ }
104
+ end
105
+ rescue StandardError => e
106
+ render json: { error: e.message }, status: 422
107
+ end
108
+
109
+ private
110
+
111
+ def extract_pdf_urls(results)
112
+ results.web_results
113
+ .select { |r| r[:url]&.end_with?(".pdf") }
114
+ .map { |r| r[:url] }
115
+ .first(3)
116
+ end
117
+ end
@@ -0,0 +1,7 @@
1
+ class ApplicationJob < ActiveJob::Base
2
+ # Automatically retry jobs that encountered a deadlock
3
+ # retry_on ActiveRecord::Deadlocked
4
+
5
+ # Most jobs are safe to ignore if the underlying records are no longer available
6
+ # discard_on ActiveJob::DeserializationError
7
+ end
@@ -0,0 +1,4 @@
1
+ class ApplicationMailer < ActionMailer::Base
2
+ default from: "from@example.com"
3
+ layout "mailer"
4
+ end
@@ -0,0 +1,3 @@
1
+ class ApplicationRecord < ActiveRecord::Base
2
+ primary_abstract_class
3
+ end