├── .gitignore ├── .ruby-version ├── .travis.yml ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── Rakefile ├── app ├── controllers │ ├── account.rb │ ├── feeds.rb │ ├── import.rb │ ├── items.rb │ ├── login.rb │ ├── remote.rb │ ├── search.rb │ ├── subscriptions.rb │ ├── tags.rb │ └── user.rb ├── helpers │ ├── general.rb │ ├── routing.rb │ └── subscriptions.rb ├── models │ ├── cache.rb │ ├── citation.rb │ ├── definition.rb │ ├── delivery.rb │ ├── event.rb │ ├── ext │ │ ├── agency.rb │ │ └── legislator.rb │ ├── interest.rb │ ├── item.rb │ ├── receipt.rb │ ├── report.rb │ ├── search.rb │ ├── seen_item.rb │ ├── subscription.rb │ ├── tag.rb │ └── user.rb └── views │ ├── 404.erb │ ├── 500.erb │ ├── _show_results.erb │ ├── about.erb │ ├── account │ ├── _blocked.erb │ ├── _collections.erb │ ├── _description.erb │ ├── _interest.erb │ ├── collection.erb │ ├── forgot.erb │ ├── login.erb │ ├── mail │ │ ├── confirm_account.erb │ │ ├── new_password.erb │ │ └── reset_password.erb │ ├── resend_confirm.erb │ ├── settings.erb │ ├── subscriptions.erb │ ├── unsubscribe.erb │ └── welcome.erb │ ├── collections.erb │ ├── downtime.erb │ ├── emails │ ├── donations │ │ ├── general.erb │ │ └── open_states.erb │ ├── footers │ │ ├── general.erb │ │ └── open_states.erb │ ├── header.erb │ └── headers │ │ ├── general.erb │ │ └── open_states.erb │ ├── import.erb │ ├── index.erb │ ├── layout.erb │ ├── partials │ ├── _flash.erb │ ├── _follow.erb │ └── _share.erb │ ├── rss │ ├── _items.erb │ ├── collection.erb │ └── interest.erb │ ├── search │ ├── _item.erb │ ├── _related_interests.erb │ ├── _search.erb │ ├── items.erb │ └── search.erb │ ├── show.erb │ └── subscriptions ├── config.ru.example ├── config ├── admin.rb ├── config.yml.example ├── cron │ ├── backup │ ├── example │ │ └── crontab │ ├── production │ │ ├── backup.sh │ │ ├── check.sh │ │ ├── crontab │ │ ├── deliver.sh │ │ ├── disabled │ │ └── rake.sh │ └── staging │ │ ├── check.sh │ │ ├── crontab │ │ ├── deliver.sh │ │ ├── disabled │ │ └── rake.sh ├── email.rb ├── environment.rb ├── services.yml.example └── slack.rb ├── deliveries ├── email.rb └── manager.rb ├── fabfile.py ├── misc ├── header.htm ├── mock_email.html ├── stats.rb ├── top_alerts.rb └── usc.json ├── public ├── BingSiteAuth.xml ├── assets │ ├── css │ │ ├── documents.css │ │ ├── html5-reset.css │ │ └── main.css │ ├── images │ │ ├── bg_body.jpg │ │ ├── bg_body.png │ │ ├── bg_currentCat.png │ │ ├── bg_featured_collection.png │ │ ├── bg_footer.png │ │ ├── bg_hat.png │ │ ├── bg_header.png │ │ ├── bg_header_email.gif │ │ ├── bg_homeWrapper.png │ │ ├── bg_openstates_email.jpg │ │ ├── bg_sectionHeader.png │ │ ├── btn_rss.png │ │ ├── bullet.png │ │ ├── donor_bg.gif │ │ ├── donor_btn.gif │ │ ├── featured │ │ │ └── eff-logo.png │ │ ├── homeSearch.png │ │ ├── icon.png │ │ ├── icon_check.png │ │ ├── icon_error.png │ │ ├── icon_feedback.png │ │ ├── icon_rss_large.png │ │ ├── icon_search.png │ │ ├── icon_settings.png │ │ ├── icon_subscriptions.png │ │ ├── icon_type_bill.png │ │ ├── icon_type_feed.png │ │ ├── icon_type_search.png │ │ ├── icons_resources.png │ │ ├── icons_share.png │ │ ├── loader-32.gif │ │ ├── logo_openstates_email.png │ │ ├── logo_scout.png │ │ ├── logo_sunlightFoundation.png │ │ ├── logo_sunlightFoundation_dark.png │ │ ├── logo_sunlightFoundation_email.png │ │ ├── more.png │ │ ├── newSearch.png │ │ ├── productOf.png │ │ ├── profile-placeholder.png │ │ ├── rss-alerts.png │ │ ├── rss.png │ │ ├── scout-collections-1.png │ │ ├── scout-collections-2.png │ │ ├── sidebar_divider.png │ │ ├── splash.png │ │ ├── splash_text.png │ │ ├── through_arrow.png │ │ ├── tinyborder.png │ │ ├── tinyborder_andshadow.png │ │ ├── tinyborder_andshadow_vertical.png │ │ ├── tinyborder_andshadow_vertical_dark.png │ │ ├── tinyborder_andshadow_vertical_original.png │ │ ├── tinyborder_entry.png │ │ ├── tinyborder_footer.png │ │ ├── tinyshadow.png │ │ └── youtube.png │ └── js │ │ ├── h5f.min.js │ │ ├── html5.js │ │ ├── jquery-2.0.3.js │ │ ├── jquery-2.0.3.min.js │ │ ├── jquery-2.0.3.min.map │ │ ├── jquery.pjax.js │ │ └── main.js ├── favicon.ico ├── google612fedd45b9451ba.html ├── robots.txt ├── robots.txt.else └── robots.txt.production ├── reindex.md ├── scout.rb ├── subscriptions ├── adapters │ ├── court_opinions.rb │ ├── documents.rb │ ├── federal_bills.rb │ ├── federal_bills_activity.rb │ ├── federal_bills_hearings.rb │ ├── federal_bills_upcoming_floor.rb │ ├── federal_bills_votes.rb │ ├── feed.rb │ ├── regulations.rb │ ├── speeches.rb │ ├── state_bills.rb │ ├── state_bills_activity.rb │ ├── state_bills_votes.rb │ ├── state_legislators.rb │ └── state_legislators_bills.rb ├── helpers.rb ├── manager.rb └── views │ ├── court_opinions │ ├── _data.erb │ ├── _email.erb │ ├── _filter.erb │ ├── _result.erb │ ├── _rss.erb │ └── _show.erb │ ├── documents │ ├── _data.erb │ ├── _email.erb │ ├── _filter.erb │ ├── _result.erb │ ├── _rss.erb │ └── _show.erb │ ├── federal_bills │ ├── _data.erb │ ├── _email.erb │ ├── _filter.erb │ ├── _result.erb │ ├── _rss.erb │ └── _show.erb │ ├── federal_bills_activity │ ├── _email.erb │ ├── _result.erb │ └── _rss.erb │ ├── federal_bills_hearings │ ├── _email.erb │ ├── _result.erb │ └── _rss.erb │ ├── federal_bills_upcoming_floor │ ├── _email.erb │ ├── _result.erb │ └── _rss.erb │ ├── federal_bills_votes │ ├── _email.erb │ ├── _result.erb │ └── _rss.erb │ ├── feed │ ├── _email.erb │ ├── _result.erb │ └── _rss.erb │ ├── regulations │ ├── _data.erb │ ├── _email.erb │ ├── _filter.erb │ ├── _result.erb │ ├── _rss.erb │ └── _show.erb │ ├── speeches │ ├── _data.erb │ ├── _email.erb │ ├── _filter.erb │ ├── _result.erb │ ├── _rss.erb │ └── _show.erb │ ├── state_bills │ ├── _data.erb │ ├── _email.erb │ ├── _filter.erb │ ├── _result.erb │ ├── _rss.erb │ └── _show.erb │ ├── state_bills_activity │ ├── _email.erb │ ├── _result.erb │ └── _rss.erb │ ├── state_bills_votes │ ├── _email.erb │ ├── _result.erb │ └── _rss.erb │ ├── state_legislators │ ├── _data.erb │ └── _show.erb │ └── state_legislators_bills │ ├── _email.erb │ ├── _result.erb │ └── _rss.erb ├── tasks ├── analytics.rake ├── assets.rake ├── backups.rake ├── collection.rake ├── crontab.rake ├── deliver.rake ├── sitemap.rake ├── subscriptions.rake ├── sunlight.rake ├── sync.rake ├── test.rake ├── users.rake └── warnings.rake ├── test ├── delivery │ ├── check_test.rb │ ├── delivery_test.rb │ └── schedule_test.rb ├── factories.rb ├── fixtures │ ├── federal_bills │ │ ├── environment │ │ │ ├── check.json │ │ │ ├── initialize.json │ │ │ └── search.json │ │ └── item │ │ │ ├── hr4192-112.json │ │ │ └── hr4193-112.json │ ├── services.yml │ ├── state_bills │ │ ├── conscience │ │ │ ├── check.json │ │ │ ├── initialize.json │ │ │ └── search.json │ │ ├── environment │ │ │ ├── check.json │ │ │ ├── initialize.json │ │ │ └── search.json │ │ ├── environment_transition │ │ │ ├── check.json │ │ │ ├── initialize.json │ │ │ └── search.json │ │ ├── science │ │ │ ├── check.json │ │ │ ├── initialize.json │ │ │ └── search.json │ │ └── science_transition │ │ │ ├── check.json │ │ │ ├── initialize.json │ │ │ └── search.json │ └── urls │ │ └── hr624-113-eh.htm ├── functional │ ├── accounts_test.rb │ ├── feeds_test.rb │ ├── import_test.rb │ ├── interests_test.rb │ ├── items_test.rb │ ├── login_test.rb │ ├── remote_test.rb │ ├── search_test.rb │ └── tags_test.rb ├── helpers │ ├── routing_test.rb │ └── subscriptions_test.rb ├── test_helper.rb └── unit │ ├── interest_test.rb │ ├── map_test.rb │ ├── search_test.rb │ ├── tag_test.rb │ └── user_test.rb ├── unicorn.rb.example └── vendor └── cache ├── activemodel-3.2.17.gem ├── activesupport-3.2.17.gem ├── addressable-2.3.6.gem ├── asset_sync-1.0.0.gem ├── aws-sdk-1.38.0.gem ├── backports-3.6.0.gem ├── bcrypt-3.1.7.gem ├── big_sitemap-1.1.0.gem ├── builder-3.0.4.gem ├── climate_control-0.0.3.gem ├── cocaine-0.5.4.gem ├── curb-0.8.5.gem ├── debugger-ruby_core_source-1.3.2.gem ├── escape_utils-1.0.1.gem ├── excon-0.32.1.gem ├── factory_girl-4.4.0.gem ├── feedbag-0.9.2.gem ├── feedjira-1.1.0.gem ├── fog-1.21.0.gem ├── fog-brightbox-0.0.1.gem ├── fog-core-1.21.1.gem ├── fog-json-1.0.0.gem ├── formatador-0.2.4.gem ├── gman-2.0.0.gem ├── hpricot-0.8.6.gem ├── http_router-0.11.1.gem ├── i18n-0.6.9.gem ├── json-1.8.1.gem ├── kgio-2.9.2.gem ├── loofah-1.2.1.gem ├── mail-2.2.7.gem ├── mime-types-2.2.gem ├── mini_portile-0.5.3.gem ├── mongoid-3.1.6.gem ├── mongoid-paperclip-0.0.9.gem ├── moped-1.5.2.gem ├── multi_json-1.9.2.gem ├── multipart-post-2.0.0.gem ├── net-scp-1.1.2.gem ├── net-ssh-2.8.0.gem ├── nokogiri-1.6.1.gem ├── oj-2.7.1.gem ├── origin-1.1.0.gem ├── padrino-core-0.12.0.gem ├── padrino-helpers-0.12.0.gem ├── paint-0.8.7.gem ├── paperclip-4.1.1.gem ├── polyglot-0.3.4.gem ├── pony-1.8.gem ├── postmark-1.1.0.gem ├── public_suffix-1.4.2.gem ├── rack-1.5.2.gem ├── rack-lineprof-0.0.2.gem ├── rack-protection-1.5.2.gem ├── rack-ssl-1.4.1.gem ├── rack-test-0.6.2.gem ├── rainbow-2.0.0.gem ├── raindrops-0.13.0.gem ├── rake-10.3.1.gem ├── rblineprof-0.3.6.gem ├── rinku-1.7.3.gem ├── rspec-mocks-2.14.6.gem ├── ruby-hmac-0.4.0.gem ├── safe_yaml-1.0.1.gem ├── sanitize-2.1.0.gem ├── sax-machine-0.2.1.gem ├── sinatra-1.4.4.gem ├── sinatra-contrib-1.4.2.gem ├── sinatra-cross_origin-0.3.2.gem ├── sinatra-flash-0.3.0.gem ├── slack-notifier-0.4.1.gem ├── swot-0.3.0.gem ├── term-ansicolor-1.3.0.gem ├── thor-0.17.0.gem ├── tilt-1.4.1.gem ├── timecop-0.7.1.gem ├── tins-1.1.0.gem ├── treetop-1.5.3.gem ├── tzinfo-0.3.39.gem ├── unf-0.1.3.gem ├── unf_ext-0.0.6.gem ├── unicorn-4.8.2.gem ├── url_mount-0.2.1.gem ├── uuidtools-2.1.4.gem └── wirb-1.0.3.gem /.gitignore: -------------------------------------------------------------------------------- 1 | /config.ru 2 | /unicorn.rb 3 | /unicorn.pid 4 | /config/config.yml 5 | /config/mongoid.yml 6 | /config/services.yml 7 | /vendor/cache 8 | /.bundle 9 | /public/sitemap 10 | /public/assets/*/*.gz 11 | /public/assets/*/*/*.gz 12 | 13 | # copied into place on deploy 14 | /public/robots.txt 15 | 16 | /public/system 17 | 18 | /tmp 19 | /REVISION 20 | /TODO* 21 | /todo* 22 | /log 23 | 24 | *.kate-swp 25 | .DS_Store 26 | *.pyc 27 | 28 | /stats.rb 29 | /dump 30 | /migration.rb 31 | /*.txt 32 | 33 | /cl.rb 34 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.1.1 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.1.1 4 | 5 | services: mongodb 6 | 7 | bundler_args: --local 8 | 9 | before_script: 10 | - cp config/config.yml.example config/config.yml 11 | 12 | notifications: 13 | email: 14 | recipients: 15 | - eric@sunlightfoundation.com 16 | on_success: change 17 | on_failure: change 18 | 19 | branches: 20 | only: 21 | - master -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'oj' 4 | gem 'multi_json' 5 | gem 'curb' 6 | gem 'safe_yaml' # avoids problems with YAML vulnerabilities, but no symbol keys 7 | 8 | gem 'sinatra', '~> 1.4' 9 | gem 'sinatra-cross_origin' 10 | gem 'sinatra-contrib', '~> 1.4' 11 | gem 'sinatra-flash' 12 | 13 | gem 'padrino-helpers', '~> 0.11' 14 | gem 'rinku' 15 | gem 'escape_utils' 16 | 17 | gem "mongoid", '~> 3.1' 18 | gem "mongoid-paperclip", require: "mongoid_paperclip" 19 | 20 | # exception handling 21 | gem "sentry-raven", git: "https://github.com/getsentry/raven-ruby.git" 22 | 23 | # asset syncing to S3 AND backup monitoring 24 | gem "aws-sdk" 25 | gem 'asset_sync' 26 | gem "unf" 27 | 28 | gem 'ruby-hmac' 29 | gem 'bcrypt' 30 | 31 | # detect gov't and educational email addresses 32 | gem 'gman' 33 | gem 'swot' 34 | 35 | gem 'postmark', '~> 1.0' 36 | gem 'pony', '~> 1.4' 37 | 38 | gem 'slack-notifier' # yes 39 | 40 | gem 'rack', '~> 1.5' 41 | gem 'rack-ssl' 42 | gem 'rake' 43 | 44 | gem 'big_sitemap' 45 | 46 | group :development do 47 | gem 'wirb' 48 | gem 'paint' 49 | gem 'unicorn' 50 | gem 'rblineprof' 51 | gem 'rack-lineprof' 52 | end 53 | 54 | group :test do 55 | gem 'rack-test' 56 | gem 'rspec-mocks', '~> 2.14' # 3.X would need refactoring 57 | gem 'timecop', '~> 0.7' 58 | gem 'factory_girl' 59 | end 60 | 61 | # feed parsing and discovery 62 | gem 'feedjira' 63 | gem 'feedbag' 64 | gem 'sanitize' 65 | 66 | # advanced search string parsing 67 | gem 'lucene_query_parser', git: 'https://github.com/sunlightlabs/lucene_query_parser.git' 68 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | All code in this repository is covered under the General Public License v3, whose full text can be found here: 2 | 3 | http://www.gnu.org/licenses/gpl-3.0.txt -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | task default: 'tests:all' 2 | 3 | task :environment do 4 | require 'rubygems' 5 | require 'bundler/setup' 6 | require './config/environment' 7 | end 8 | 9 | require 'rake/testtask' 10 | 11 | Dir.glob('tasks/*.rake').each {|filename| load filename} 12 | 13 | namespace :tests do 14 | Rake::TestTask.new(:all) do |t| 15 | t.libs << "test" 16 | t.test_files = FileList['test/**/*_test.rb'] 17 | end 18 | 19 | ["functional", "helpers", "delivery", "unit"].each do |type| 20 | Rake::TestTask.new(type.to_sym) do |t| 21 | t.libs << "test" 22 | t.test_files = FileList["test/#{type}/*_test.rb"] 23 | end 24 | end 25 | end 26 | 27 | desc "Run through each model and create all indexes" 28 | task create_indexes: :environment do 29 | begin 30 | Mongoid.models.each do |model| 31 | model.create_indexes 32 | puts "Created indexes for #{model}" 33 | end 34 | 35 | rescue Exception => ex 36 | report = Report.exception 'Indexes', "Exception creating indexes", ex 37 | Admin.report report 38 | puts "Error creating indexes, emailed report." 39 | end 40 | end 41 | 42 | desc "Clear the database" 43 | task clear_data: :environment do 44 | models = Dir.glob('app/models/*.rb').map do |file| 45 | File.basename(file, File.extname(file)).camelize.constantize 46 | end 47 | 48 | models.each do |model| 49 | model.delete_all 50 | puts "Cleared model #{model}." 51 | end 52 | end 53 | 54 | desc "Clear all cached content." 55 | task clear_cache: :environment do 56 | Cache.delete_all 57 | puts "Cleared cache." 58 | end 59 | -------------------------------------------------------------------------------- /app/controllers/feeds.rb: -------------------------------------------------------------------------------- 1 | # All RSS feeds go through this controller. 2 | # They have CORS enabled, so they can be used as an informal API, 3 | # even from within the browser. 4 | 5 | get "/interest/:interest_id.rss" do 6 | cross_origin 7 | 8 | unless interest = Interest.find(params[:interest_id]) 9 | halt 404 and return 10 | end 11 | 12 | items = SeenItem.where(interest_id: interest.id).desc :date 13 | 14 | rss_for "interest", items, interest: interest 15 | end 16 | 17 | get "/user/:user_id/:collection.rss" do 18 | cross_origin 19 | 20 | name = Tag.deslugify params[:collection] 21 | unless (user = load_user) and (collection = user.tags.where(name: name).first) 22 | halt 404 and return 23 | end 24 | 25 | interest_ids = collection.interests.only(:_id).map &:_id 26 | items = SeenItem.where(interest_id: {"$in" => interest_ids}).desc :date 27 | 28 | rss_for "collection", items, collection: collection 29 | end 30 | 31 | helpers do 32 | 33 | def rss_for(view, items, locals = {}) 34 | page = (params[:page] || 1).to_i 35 | page = 1 if page <= 0 or page > 200000000 36 | per_page = 100 37 | 38 | items = items.skip(per_page * (page - 1)).limit(per_page) 39 | 40 | headers["Content-Type"] = "application/rss+xml" 41 | erb :"rss/#{view}", layout: false, locals: { 42 | items: items, 43 | url: request.url 44 | }.merge(locals) 45 | end 46 | 47 | end -------------------------------------------------------------------------------- /app/controllers/subscriptions.rb: -------------------------------------------------------------------------------- 1 | # delete any interest, by ID (from the subscriptions management page) 2 | delete '/interest/:id' do 3 | requires_login 4 | 5 | if interest = current_user.interests.find(params[:id]) 6 | interest.destroy 7 | halt 200 8 | else 9 | halt 404 10 | end 11 | end 12 | 13 | 14 | # update any interest, by ID (from the subscriptions management page) 15 | put '/interest/:id' do 16 | requires_login 17 | 18 | unless interest = current_user.interests.find(params[:id]) 19 | halt 404 and return false 20 | end 21 | 22 | if params[:interest]['notifications'] 23 | interest.notifications = params[:interest]['notifications'] 24 | end 25 | 26 | collections = [] 27 | if params[:interest]['collections'] 28 | halt 500 if interest.tag? # no! 29 | interest.new_tags = params[:interest]['collections'] 30 | collections = interest.tags.map do |name| 31 | current_user.tags.find_or_initialize_by name: name 32 | end 33 | end 34 | 35 | if interest.save 36 | # should be guaranteed to be safe 37 | collections.each {|collection| collection.save! if collection.new_record?} 38 | 39 | pane = partial "account/collections", engine: :erb, locals: {collections: current_user.tags} 40 | json 200, { 41 | interest_collections: interest.tags, 42 | notifications: interest.notifications, 43 | collections_pane: pane 44 | } 45 | else 46 | halt 500 47 | end 48 | end -------------------------------------------------------------------------------- /app/controllers/tags.rb: -------------------------------------------------------------------------------- 1 | put '/account/collection/:name/description' do 2 | requires_login 3 | 4 | name = params[:name].strip.downcase 5 | unless collection = current_user.tags.where(name: Tag.deslugify(name)).first 6 | halt 404 and return 7 | end 8 | 9 | collection.description = params[:description] 10 | 11 | if collection.save 12 | description = partial "account/description", engine: "erb", locals: { 13 | user: current_user, collection: collection 14 | } 15 | 16 | json 200, { 17 | description_pane: description 18 | } 19 | else 20 | halt 500 21 | end 22 | end 23 | 24 | put '/account/collection/:name/public' do 25 | requires_login 26 | 27 | name = params[:name].strip.downcase 28 | unless collection = current_user.tags.where(name: Tag.deslugify(name)).first 29 | halt 404 and return 30 | end 31 | 32 | collection.public = params[:public] 33 | collection.save! 34 | 35 | redirect Tag.collection_path(current_user, collection) 36 | end 37 | 38 | delete "/account/collections" do 39 | requires_login 40 | 41 | names = (params[:names] || []).map {|name| Tag.deslugify name} 42 | 43 | names.each do |name| 44 | if collection = current_user.tags.where(name: name).first 45 | collection.destroy 46 | end 47 | end 48 | 49 | redirect "/account/subscriptions" 50 | end -------------------------------------------------------------------------------- /app/controllers/user.rb: -------------------------------------------------------------------------------- 1 | post "/user/:user_id/:collection/follow" do 2 | requires_login 3 | 4 | user, collection = load_user_and_collection 5 | halt 404 if collection.private? 6 | halt 500 if user == current_user # no! 7 | 8 | interest = Interest.for_tag current_user, user, collection 9 | interest.save! if interest.new_record? 10 | 11 | json 200, { 12 | interest_id: interest.id.to_s 13 | } 14 | end 15 | 16 | delete "/user/:user_id/:collection/unfollow" do 17 | requires_login 18 | 19 | user, collection = load_user_and_collection 20 | halt 404 if collection.private? 21 | 22 | interest = Interest.for_tag current_user, user, collection 23 | interest.destroy unless interest.new_record? 24 | 25 | status 200 26 | end 27 | 28 | 29 | get "/user/:user_id/:collection" do 30 | # temporary workaround to ensure it doesn't matter what order controllers get loaded in 31 | pass if params[:collection]['.'] 32 | 33 | user, collection = load_user_and_collection 34 | 35 | if collection.private? and (user != current_user) 36 | halt 404 and return 37 | end 38 | 39 | interest = Interest.for_tag current_user, user, collection 40 | 41 | # load in users' other shared collections 42 | other_public_collections = user.tags.where(public: true, _id: {"$ne" => collection._id}).to_a 43 | 44 | # preview of items fetched so far for this collection 45 | interest_ids = collection.interests.distinct :_id 46 | items = SeenItem.where( 47 | interest_id: {"$in" => interest_ids}, 48 | date: {"$lt" => Time.zone.now} 49 | ).desc :date 50 | items = items.limit(10).to_a 51 | 52 | erb :"account/collection", locals: { 53 | collection: collection, 54 | user: user, 55 | interest: interest, 56 | interests: collection.interests, 57 | items: items, 58 | other_public_collections: other_public_collections, 59 | edit: (user == current_user) 60 | } 61 | end 62 | 63 | helpers do 64 | 65 | def load_user_and_collection 66 | unless user = load_user 67 | halt 404 68 | end 69 | 70 | unless collection = user.tags.where(name: Tag.deslugify(params[:collection])).first 71 | halt 404 72 | end 73 | 74 | [user, collection] 75 | end 76 | 77 | end -------------------------------------------------------------------------------- /app/helpers/subscriptions.rb: -------------------------------------------------------------------------------- 1 | ../../subscriptions/helpers.rb -------------------------------------------------------------------------------- /app/models/cache.rb: -------------------------------------------------------------------------------- 1 | # A cached response of a request to a data source. 2 | # 3 | # Used by the subscription manager only. 4 | class Cache 5 | include Mongoid::Document 6 | include Mongoid::Timestamps 7 | 8 | # @return [String] the request URL 9 | field :url 10 | # @return [Symbol] one of `:search`, `:find` or `:fetch` 11 | field :function, type: Symbol 12 | # @return [String,Symbol] either a subscription adapter's lowercase 13 | # underscored name or `:document` 14 | field :subscription_type 15 | # @return [String] the response body 16 | field :content 17 | 18 | # Used by `Manager.cache_for`. 19 | index({url: 1, function: 1, subscription_type: 1}) 20 | # used for cache clearing 21 | index({subscription_type: 1, function: 1}) 22 | # used for debugging 23 | index created_at: 1 24 | end 25 | -------------------------------------------------------------------------------- /app/models/citation.rb: -------------------------------------------------------------------------------- 1 | # A reference to a section of a codified legal instrument, such as the US Code. 2 | # 3 | # Provides information about a citation if the query terms match a citation. 4 | # Pages are added to `sitemap.xml` for each citation in the US Code. 5 | # @see https://scout.sunlightfoundation.com/search/all/5%20USC%20552 6 | class Citation 7 | include Mongoid::Document 8 | include Mongoid::Timestamps 9 | 10 | # @return [String] the citation's identifier, e.g. "usc/5/552" 11 | field :citation_id 12 | # @return [String] the citation's type, e.g. "usc" 13 | field :citation_type 14 | # @return [String] the citation's description, e.g. "Public information; 15 | # agency rules, opinions, orders, records, and proceedings" 16 | field :description 17 | 18 | # @return [Hash] US code specific information, e.g. section number, title 19 | # number and name 20 | field :usc, type: Hash, default: {} 21 | 22 | validates_presence_of :citation_id 23 | 24 | index citation_id: 1 25 | index citation_type: 1 26 | end 27 | -------------------------------------------------------------------------------- /app/models/definition.rb: -------------------------------------------------------------------------------- 1 | # A definition of a legal term. 2 | # 3 | # If a term appears in an item's text, and if the subscription adapter's views 4 | # implements the glossary feature, then all occurrences of the term will have a 5 | # tooltip containing the definition. 6 | # @see https://scout.sunlightfoundation.com/item/speech/CREC-2013-11-14-pt1-PgS8027.chunk5/sen-harry-reid-executive-session 7 | class Definition 8 | include Mongoid::Document 9 | include Mongoid::Timestamps 10 | 11 | # @return [String] the term 12 | field :term 13 | # @return [String] a plain-text short definition 14 | field :short_definition 15 | # @return [String] a plain-text long definition 16 | field :long_definition_text 17 | # @return [String] an HTML long definition 18 | field :long_definition_html 19 | # @return [String] the source of the definition, e.g. "Congress.gov" 20 | field :source 21 | # @return [String] the URL to the source of the definition 22 | field :source_url 23 | 24 | validates_presence_of :term 25 | validates_presence_of :short_definition 26 | validates_presence_of :long_definition_text 27 | validates_presence_of :long_definition_html 28 | 29 | index term: 1 30 | end 31 | -------------------------------------------------------------------------------- /app/models/delivery.rb: -------------------------------------------------------------------------------- 1 | # An item in a queue of to-be-delivered items. 2 | # 3 | # The queue is designed to empty itself as items are delivered (like most MTAs) 4 | # with successful deliveries stored separately as receipts. 5 | class Delivery 6 | include Mongoid::Document 7 | include Mongoid::Timestamps 8 | 9 | belongs_to :interest 10 | belongs_to :user 11 | 12 | # @return [String] the lowercase underscored name of the subscription's adapter 13 | field :subscription_type 14 | # @return [String] what the user is interested in (terms, feed URL, etc.) 15 | field :interest_in 16 | 17 | # used for DEBUG/TESTING CONVENIENCE ONLY - 18 | # During actual delivery, the email to deliver this to 19 | # should be looked up AGAIN, in case the user's email has changed. 20 | # @return [String] the subscriber's email address 21 | field :user_email 22 | 23 | # Alice tags her interests, creating a collection. Bob is interested in 24 | # Alice's collection; his interest is modeled as an interest of type "tag" 25 | # pointing to the collection. Bob sees an item in one of Alice's collected 26 | # interests through his interest. 27 | # 28 | # For deliveries related to Bob's interest in Alice's collection, the `user` 29 | # is Bob, the `interest` is Alice's interest and `seen_through` is Bob's 30 | # interest. 31 | belongs_to :seen_through, class_name: "Interest" 32 | 33 | 34 | 35 | # the delivery task should look at *this* field, so that we can 36 | # add the ability to override per-interest, per-subscription, whatever 37 | # @return [String] the way in which to deliver the alert ("email") 38 | 39 | # TODO: kill this field 40 | field :mechanism 41 | 42 | # @return [Hash] a copy of the item's attributes 43 | field :item, :type => Hash, :default => {} 44 | 45 | index subscription_type: 1 46 | index user_email: 1 47 | index "item.date" => 1 48 | index "item.item_id" => 1 49 | index interest_id: 1 50 | index user_id: 1 51 | index seen_through_id: 1 52 | 53 | validates_presence_of :interest_id 54 | validates_presence_of :subscription_type 55 | validates_presence_of :interest_in 56 | validates_presence_of :user_id 57 | validates_presence_of :item 58 | 59 | # Schedules an item, from a data source, related to an interest, to be 60 | # delivered to a user via email, either immediately or daily. 61 | # 62 | # @param [SeenItem] item the item to deliver 63 | # @param [Interest] interest the interest to which the item is related 64 | # @param [String] subscription_type the lowercase underscored name of the 65 | # subscription's adapter 66 | # @param [Interest] seen_through "tag" interests see items through other 67 | # user's interests 68 | # @param [User] user the user to deliver the item to 69 | # @param [String] "email" 70 | # @param [String] email_frequency either "daily" or "immediate" 71 | def self.schedule!(item, interest, subscription_type, seen_through, user, mechanism, email_frequency) 72 | create! user_id: user.id, 73 | 74 | # for convenience of debugging only - what these values were at schedule-time 75 | user_email: user.email, 76 | 77 | subscription_type: subscription_type, 78 | 79 | interest_in: interest.in, 80 | interest: interest, 81 | 82 | seen_through: seen_through, 83 | 84 | mechanism: mechanism, 85 | email_frequency: email_frequency, 86 | 87 | # drop the item into the delivery wholesale 88 | item: item.attributes.dup 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /app/models/ext/agency.rb: -------------------------------------------------------------------------------- 1 | class Agency 2 | include Mongoid::Document 3 | include Mongoid::Timestamps 4 | 5 | field :agency_id, type: String 6 | field :name 7 | field :short_name 8 | 9 | index agency_id: 1 10 | index name: 1 11 | index short_name: 1 12 | 13 | validates_presence_of :agency_id 14 | validates_presence_of :name 15 | 16 | default_scope asc(:name) 17 | 18 | def self.agencies_url 19 | "https://www.federalregister.gov/api/v1/agencies" 20 | end 21 | 22 | def self.agency_for(result) 23 | { 24 | name: result['name'], 25 | agency_id: result['id'], 26 | short_name: result['short_name'] 27 | } 28 | end 29 | 30 | end -------------------------------------------------------------------------------- /app/models/ext/legislator.rb: -------------------------------------------------------------------------------- 1 | class Legislator 2 | include Mongoid::Document 3 | include Mongoid::Timestamps 4 | 5 | field :name 6 | field :title 7 | field :bioguide_id 8 | 9 | validates_presence_of :name 10 | validates_presence_of :bioguide_id 11 | validates_uniqueness_of :bioguide_id 12 | 13 | index name: 1 14 | index bioguide_id: 1 15 | index({title: -1, name: 1}) 16 | 17 | default_scope desc(:title).asc(:name) 18 | 19 | def self.url_for_current 20 | api_key = Environment.config['subscriptions']['sunlight_api_key'] 21 | fields = %w{bioguide_id name_suffix first_name middle_name last_name nickname party state title} 22 | 23 | url = "https://congress.api.sunlightfoundation.com" 24 | url << "/legislators?per_page=all" 25 | url << "&apikey=#{api_key}" 26 | url << "&fields=#{fields.join ','}" 27 | url 28 | end 29 | 30 | def self.name_for(legislator) 31 | first = legislator['nickname'].present? ? legislator['nickname'] : legislator['first_name'] 32 | last = legislator['last_name'] 33 | last << " #{legislator['name_suffix']}" if legislator['name_suffix'].present? 34 | "#{last}, #{first} [#{legislator['party']}-#{legislator['state']}]" 35 | end 36 | end -------------------------------------------------------------------------------- /app/models/item.rb: -------------------------------------------------------------------------------- 1 | # An item (which may or not have been seen) that is not attached to any 2 | # interest or subscription. 3 | # 4 | # Pages are added to `sitemap.xml` for each item. Primes the cache with items 5 | # from data sources. 6 | class Item 7 | include Mongoid::Document 8 | include Mongoid::Timestamps 9 | 10 | # @return [String] the item's type, e.g. "bill" 11 | field :item_type 12 | 13 | # @return [String] the item's unique identifier among items of the same type 14 | field :item_id 15 | 16 | # @return [Time] the time at which this item occurred, at which it was created 17 | # or published, or another time of origin 18 | field :date, type: Time # fine 19 | 20 | # @return [Hash] arbitrary data 21 | # @note Seen items and "item" interests store identical data. 22 | field :data, type: Hash, default: {} 23 | 24 | index item_id: 1 25 | index item_type: 1 26 | index created_at: 1 27 | index({item_type: 1, created_at: 1}) 28 | 29 | validates_presence_of :item_type 30 | validates_presence_of :item_id 31 | 32 | # @return [Class] the subscription adapter for this item 33 | def adapter 34 | Subscription.adapter_for(item_types[item_type]['adapter']) 35 | end 36 | 37 | # defer to seenitem's path generation 38 | def path 39 | SeenItem.generate_path item_id, item_type, data 40 | end 41 | 42 | # @param [SeenItem] a seen item 43 | def self.from_seen!(seen_item) 44 | item = Item.find_or_initialize_by( 45 | item_type: seen_item.item_type, 46 | item_id: seen_item.item_id, 47 | ) 48 | 49 | item.date = seen_item.date 50 | item.data = seen_item.data 51 | 52 | item.save! 53 | end 54 | 55 | # prepare a SeenItem, un-assigned to a subscription, as if it just came out 56 | # of an adapter (I know, this is weird - these models should get merged) 57 | # warning: lacking a date field, geez - another thing to refactor 58 | # @param [Item] an item 59 | # @return [SeenItem] a seen item 60 | def self.to_seen!(item) 61 | SeenItem.new( 62 | item_id: item.item_id, 63 | item_type: item.item_type, 64 | date: item.date, 65 | data: item.data 66 | ) 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /app/models/receipt.rb: -------------------------------------------------------------------------------- 1 | # A statement acknowledging the successful sending of a notification to a 2 | # subscriber. The notification may be, for example, an email message about new 3 | # items related to an interest of the subscriber. 4 | class Receipt 5 | include Mongoid::Document 6 | include Mongoid::Timestamps 7 | 8 | # @return [Array] a list of the attributes of the deliveries made in 9 | # this transaction (each delivery is about a single item). 10 | # these deliveries will have had their `item.data` fields removed, to save storage. 11 | field :deliveries, :type => Array 12 | 13 | # @return [String] the user's ID 14 | field :user_id 15 | # @return [String] the user's email address, if delivered via email 16 | field :user_email 17 | # @return [String] the service that generated the user, if not Scout itself, 18 | # e.g. "open_states" 19 | field :user_service 20 | 21 | # @return [String] "email" 22 | # TODO: kill this 23 | field :mechanism 24 | 25 | # @return [String] the email's "Subject" header 26 | field :subject 27 | # @return [Time] the time at which the notification was sent 28 | field :delivered_at, :type => Time 29 | 30 | index delivered_at: 1 31 | index user_id: 1 32 | index user_email: 1 33 | index user_service: 1 34 | index({created_at: 1, user_service: 1}) 35 | index mechanism: 1 36 | index created_at: 1 37 | index({mechanism: 1, user_service: 1, created_at: 1}) 38 | 39 | validates_presence_of :delivered_at 40 | 41 | # if the user is still around, no harm if it's not 42 | belongs_to :user 43 | 44 | scope :for_time, ->(start, ending) { 45 | where(delivered_at: { 46 | "$gt" => Time.zone.parse(start).midnight, 47 | "$lt" => Time.zone.parse(ending).midnight 48 | }) 49 | } 50 | end 51 | -------------------------------------------------------------------------------- /app/models/report.rb: -------------------------------------------------------------------------------- 1 | # A report to be sent to the administrators about an error or a warning during 2 | # execution, of a successful execution, or of the completion of a task. 3 | class Report 4 | include Mongoid::Document 5 | include Mongoid::Timestamps 6 | 7 | field :status 8 | field :source 9 | field :message 10 | field :elapsed_time, type: Float 11 | field :attached, type: Hash, :default => {} 12 | 13 | index status: 1 14 | index source: 1 15 | index created_at: 1 16 | 17 | # @private 18 | def self.file(status, source, message, attached = {}) 19 | report = Report.create!(source: source.to_s, status: status, message: message, attached: attached) 20 | # stdout, but don't bother stdout-ing reports COMPLETE reports, or reports that will be emailed 21 | puts "\n#{report}" unless Sinatra::Application.test? or ['FAILURE', 'WARNING', 'COMPLETE'].include?(status) 22 | report 23 | end 24 | 25 | def self.failure(source, message, objects = {}) 26 | file 'FAILURE', source, message, objects 27 | end 28 | 29 | def self.warning(source, message, objects = {}) 30 | file 'WARNING', source, message, objects 31 | end 32 | 33 | def self.success(source, message, objects = {}) 34 | file 'SUCCESS', source, message, objects 35 | end 36 | 37 | def self.complete(source, message, objects = {}) 38 | file 'COMPLETE', source, message, objects 39 | end 40 | 41 | def self.exception(source, message, exception, objects = {}) 42 | file 'FAILURE', source, message, { 43 | 'exception' => exception_to_hash(exception) 44 | }.merge(objects) 45 | end 46 | 47 | def to_s 48 | "[#{status}] #{source} | #{message}" 49 | end 50 | 51 | def self.exception_to_hash(exception) 52 | { 53 | 'backtrace' => exception.backtrace, 54 | 'message' => exception.message, 55 | 'type' => exception.class.to_s 56 | } 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /app/models/tag.rb: -------------------------------------------------------------------------------- 1 | # A user may tag their interests to produce collections. 2 | class Tag 3 | include Mongoid::Document 4 | include Mongoid::Timestamps 5 | 6 | # @return [User] the user who created the collection 7 | belongs_to :user 8 | 9 | # @return [String] the collection's name 10 | field :name 11 | # @return [Boolean] whether the collection is public or private 12 | field :public, type: Boolean, default: false 13 | # @return [String] the collection's description 14 | field :description 15 | 16 | index name: 1 17 | index public: 1 18 | index user_id: 1 19 | 20 | index({user_id: 1, name: 1}) 21 | index({user_id: 1, public: 1, _id: 1}) 22 | 23 | validates_uniqueness_of :name, scope: :user_id 24 | 25 | default_scope desc(:created_at) 26 | 27 | # not a formal relationship, depends on interests keeping their own tag array 28 | def interests 29 | user.interests.where tags: name 30 | end 31 | 32 | after_destroy :remove_from_interests 33 | # @private 34 | def remove_from_interests 35 | interests.each do |interest| 36 | interest.pull :tags, name 37 | end 38 | end 39 | 40 | # @return URL to a user's tag 41 | def self.collection_path(user, collection) 42 | user_id = user.username.present? ? user.username : user.id.to_s 43 | "/user/#{user_id}/#{Tag.slugify collection.name}" 44 | end 45 | 46 | # @return [Boolean] whether the collection is private 47 | def private? 48 | !public? 49 | end 50 | 51 | def self.normalize(name) 52 | name.gsub(/[^\w\d\s]/, '').gsub(/\s{2,}/, ' ').strip.downcase 53 | end 54 | 55 | def self.slugify(name) 56 | name.strip.tr ' ', '-' 57 | end 58 | 59 | def self.deslugify(name) 60 | name.strip.tr '-', ' ' 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /app/views/404.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |

404

4 | 5 |

6 | <%= sadness %>: what you are looking for has not been found. If you believe this is an error on our part, send us a note. 7 |

8 | 9 |

10 | Return to the homepage 11 |

12 | 13 |
14 |
-------------------------------------------------------------------------------- /app/views/500.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |

500

4 | 5 |

6 | <%= sadness %>: Some kind of error just occurred. We've been notified, and will look into it. 7 |

8 | 9 |

10 | Return to the homepage 11 |

12 | 13 |
14 |
-------------------------------------------------------------------------------- /app/views/_show_results.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | <% if items and items.any? %> 4 | <% items.sort {|a, b| b.date <=> a.date}.first(5).each do |item| %> 5 | 6 | <%= partial "subscriptions/#{subscription.subscription_type}/result", :engine => :erb, :locals => {:item => item} %> 7 | 8 | <% end %> 9 | <% else %> 10 | 11 | 18 | 19 | <% end %> 20 | 21 |
12 | <% if items %> 13 | Nothing yet. 14 | <% else %> 15 | Error loading information. 16 | <% end %> 17 |
-------------------------------------------------------------------------------- /app/views/about.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |

About

4 | 5 |

6 | Scout is a free service that provides daily insight to how our laws and regulations are shaped in Washington, DC and our state capitols. 7 |

8 |

9 | These days, you can receive electronic alerts to know when a company is in the news, when a TV show is scheduled to air or when a sports team wins. Now, you can also be alerted when our elected officials take action on an issue you care about. 10 |

11 |

12 | Scout allows anyone to subscribe to customized email or text alerts on what Congress is doing around an issue or a specific bill, as well as bills in the state legislature and federal regulations. You can also add external RSS feeds to complement a Scout subscription, such as press releases from a member of Congress or an issue-based blog. 13 |

14 |

15 | Anyone can create a collection of Scout alerts around a topic, for personal organization or to make it easy for others to easily follow a whole topic at once. 16 |

17 |

18 | Researchers can use Scout to see when Congress talks about an issue over time. Members of the media can use Scout to track when legislation important to their beat moves ahead in Congress or in state houses. Non-profits can use Scout as a tool to keep tabs on how federal and state lawmakers are making policy around a specific issue. 19 |

20 |

21 | Early testing of Scout during its open beta phase alerted Sunlight and allies in time to successfully stop an overly broad exemption to the Freedom of Information Act from being applied to legislation that was moving quickly in Congress. Read more about that here. 22 |

23 |

24 | Thank you to the Stanton Foundation, who contributed generous support to Scout's development. 25 |

26 |
27 | 28 |
29 |

30 | 31 |

32 |
33 | 34 |
35 |

Data

36 | 37 |

38 | Scout was developed by Sunlight Labs and data comes from a variety of sources. 39 |

40 | 41 | <% search_types.each do |subscription_type| %> 42 |

43 | <%= Subscription.adapter_for(subscription_type).search_name nil %> 44 |

45 | <%= partial "subscriptions/#{subscription_type}/data", engine: :erb %> 46 | <% end %> 47 | 48 |
49 |
50 |

Contact Us

51 | 52 |

53 | For questions or comments, please . We promise to respond promptly. 54 |

55 |
56 |
57 | 66 | -------------------------------------------------------------------------------- /app/views/account/_blocked.erb: -------------------------------------------------------------------------------- 1 |
2 |

3 | This email is already registered through a partner service, 4 | 5 | <%= Environment.services[service]['name'] %>. 6 |

7 |

8 | Unfortunately, at this time you cannot use the same email address on both services. 9 |

10 |

11 | You can use an alternate email address, or use a plus sign (+) to make a harmless modification to your email address. For example, if your email is example@gmail.com, you can safely use example+scout@gmail.com to register and receive emails. 12 |

13 |
-------------------------------------------------------------------------------- /app/views/account/_collections.erb: -------------------------------------------------------------------------------- 1 |

2 | Scout Collections 3 | <% if collections.any? %> 4 | edit 5 | <% end %> 6 |

7 | 8 | <% if collections.any? %> 9 |
10 | 11 | 12 | 27 | 28 | 31 |
32 |
33 | <% else %> 34 |

35 | Add your alerts to Scout collections to organize and share them. 36 |

37 |

Learn more about how Scout collections work. 38 |

39 | <% end %> -------------------------------------------------------------------------------- /app/views/account/_description.erb: -------------------------------------------------------------------------------- 1 |
2 | 3 | <% if collection.description.present? %> 4 | <%= light_format collection.description %> 5 | <% end %> 6 | 7 | <% if user == current_user %> 8 | 19 | <% end %> 20 |
21 | 22 | <% if user == current_user %> 23 |
24 | 27 | 30 | cancel 31 |
32 | <% end %> -------------------------------------------------------------------------------- /app/views/account/_interest.erb: -------------------------------------------------------------------------------- 1 | <% edit = defined?(edit) ? edit : false %> 2 | 3 |
6 | 7 | <% if edit %> 8 |
9 |
10 | 11 |
12 | 13 | 14 | 15 | 16 |
17 | 18 |
19 | spinning 20 | okay 21 | not okay 22 |
23 |
24 |
25 | <% end %> 26 | 27 |
28 |

29 | 30 | <% if interest.search? %> 31 | <% if interest.search_type == "all" %> 32 | Everything 33 | <% else %> 34 | <%= interest.subscriptions.first.search_name %> 35 | <% end %> 36 | <% else %> 37 | <%= interest_name interest %> 38 | <% end %> 39 | 40 | 41 | 42 | <%= interest_description interest %> 43 | 44 |

45 | 46 | 47 | RSS 48 | 49 | 50 | <% if edit %> 51 |
52 | 53 | <%= notification_name(interest.notifications || current_user.notifications) %> 54 | change 55 | 56 | 57 |
58 |
    59 | <% types = ["email_immediate", "email_daily", "none"] %> 60 | <% allowable = current_user.allowable_notifications %> 61 | <% preference = interest.notifications || current_user.notifications %> 62 | <% default = types.delete current_user.notifications %> 63 | 64 |
  • 65 | <%= notification_radio_for default, preference == default, allowable.include?(default) %> 66 |
  • 67 | 68 | <% types.each do |type| %> 69 |
  • 70 | <%= notification_radio_for type, preference == type, allowable.include?(type) %> 71 |
  • 72 | <% end %> 73 | 74 |
75 |
76 | 77 |
78 | 79 | <% unless interest.tag? %> 80 |
81 |
82 | 83 | " 86 | /> 87 | 90 | 91 | 94 |
95 |
96 | <% end %> 97 | <% end %> 98 | 99 |
100 | 101 |
102 |
-------------------------------------------------------------------------------- /app/views/account/forgot.erb: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |

Forgot your Password?

6 | 14 |
15 |
-------------------------------------------------------------------------------- /app/views/account/login.erb: -------------------------------------------------------------------------------- 1 | <%= flash_for [:login, :user, :forgot] %> 2 | 3 | <% if defined?(blocked) and blocked.present? %> 4 | <%= partial "account/blocked", engine: :erb, locals: {service: blocked} %> 5 | <% end %> 6 | 7 |
8 | 9 | 10 | 11 |
12 | 13 |

Login

14 | 29 |
30 | 31 |
32 | 33 |

Sign Up

34 | 35 | <% if @new_user and @new_user.errors %> 36 | <% @new_user.errors.each do |field, msg| %> 37 |
<%= msg %>
38 | <% end %> 39 | <% end %> 40 | 41 | 42 | 67 |
68 |
-------------------------------------------------------------------------------- /app/views/account/mail/confirm_account.erb: -------------------------------------------------------------------------------- 1 |

Hi,

2 | 3 |

To confirm that you want to receive Scout alerts at this email address, click on the link below:

4 | 5 |

6 | <%= Environment.config['hostname'] %>/account/confirm?confirm_token=<%= user.confirm_token %> 7 |

8 | 9 |
10 | 11 |

12 | This email is sent from Scout, a service of the <%= Environment.config['organization']['name'] %>. 13 |

14 | 15 |

16 | If you received this message in error, you can safely delete it. 17 |

-------------------------------------------------------------------------------- /app/views/account/mail/new_password.erb: -------------------------------------------------------------------------------- 1 |

Hi,

2 | 3 |

4 | We've reset your password. You should login as soon as possible with this new password, and change it to something else. 5 |

6 | 7 |

8 | Your new password: <%= new_password %> 9 |

10 | 11 |

Visit Scout to log in with your new password:

12 | 13 |

<%= Environment.config['hostname'] %>/login

14 | 15 |
16 | 17 |

18 | This email is sent from Scout, a service of the <%= Environment.config['organization']['name'] %>. 19 |

20 | 21 |

22 | If you received this message in error, you can safely delete it. 23 |

-------------------------------------------------------------------------------- /app/views/account/mail/reset_password.erb: -------------------------------------------------------------------------------- 1 |

Hi,

2 | 3 |

We've received a request to reset the password for the account at this email address.

4 | 5 |

Click the link below to confirm your email. This will send you another email with your new password.

6 | 7 |

8 | <%= Environment.config['hostname'] %>/account/password/reset?reset_token=<%= user.reset_token %> 9 |

10 | 11 |
12 | 13 |

14 | This email is sent from Scout, a service of the <%= Environment.config['organization']['name'] %>. 15 |

16 | 17 |

18 | If you received this message in error, you can safely delete it. 19 |

-------------------------------------------------------------------------------- /app/views/account/resend_confirm.erb: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |

Resend Confirmation Email

6 | 7 | <%= flash_for [:resend] %> 8 | 9 | 16 |
17 |
-------------------------------------------------------------------------------- /app/views/account/unsubscribe.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | <% if (current_user.notifications == "none") and !current_user.announcements? and !current_user.organization_announcements? %> 5 |

You're Unsubscribed

6 |

7 | You're unsubscribed from everything. If you think we haven't properly unsubscribed you, send us an email and we'll take care of it. 8 |

9 | <% else %> 10 |

Unsubscribe from Everything?

11 | 12 |

13 | This will turn off emails for new alerts, Scout feature announcements, and <%= Environment.config['organization']['name'] %> announcements. 14 |

15 | 16 |
17 | 20 |
21 | <% end %> 22 |
23 |
-------------------------------------------------------------------------------- /app/views/account/welcome.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Update Your Password

4 | 5 | <%= flash_for [:user, :password] %> 6 | <%= errors_for user %> 7 | 8 |

9 | Your email has been confirmed, and you will start receiving alerts. 10 |

11 | 12 |

13 | Next, update your password and notification settings. 14 |

15 | 16 |
17 | 18 | 19 |
20 |
    21 |
    22 |
  • 23 | 24 | 25 |
  • 26 |
  • 27 | 28 | 29 |
  • 30 | 31 | 35 | 36 | 40 |
    41 |
42 |
43 | 44 |
45 | 48 |
49 |
50 |
51 |
52 |
-------------------------------------------------------------------------------- /app/views/downtime.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Downtime

4 | 5 |

6 | We've taken Scout offline while we work through some server issues. Thanks for your patience -- we'll have Scout working again as soon as possible. 7 |

8 | 9 |
10 |
-------------------------------------------------------------------------------- /app/views/emails/donations/general.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 23 | 24 | 25 | 26 |
  6 | 7 | 8 | 9 |

Take a stand for transparency: 10 | donate to the Sunlight Foundation today!

11 | 12 | 13 | 14 | 21 | 22 |
 
27 | -------------------------------------------------------------------------------- /app/views/emails/donations/open_states.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 23 | 24 | 25 | 26 |
  6 | 7 | 8 | 9 |

Take a stand for transparency: 10 | donate to the Sunlight Foundation today!

11 | 12 | 13 | 14 | 21 | 22 |
 
27 | -------------------------------------------------------------------------------- /app/views/emails/footers/general.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 13 | 14 | 25 | 26 | 27 | 28 | 29 | <%# donation %> 30 | 31 | 32 | -------------------------------------------------------------------------------- /app/views/emails/footers/open_states.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | 13 | 14 | 25 | 26 | 27 | 28 | 29 | <%# donation %> 30 | 31 | 32 | -------------------------------------------------------------------------------- /app/views/emails/header.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/views/emails/headers/general.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 |
4 | 5 | 6 | 7 |
10 | -------------------------------------------------------------------------------- /app/views/emails/headers/open_states.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 |
4 | " target="_blank"> 5 | 6 | 7 |
10 | -------------------------------------------------------------------------------- /app/views/index.erb: -------------------------------------------------------------------------------- 1 | <% content_for(:home) {true} %> 2 | 3 |
4 |

5 | Get alerts on issues you care about. 6 |

7 |
8 |
9 |

Congress

10 |

11 | Stay up to date on the Hill, from bills to speeches to government reports. 12 |

13 |
14 | 15 |
16 |

After Congress

17 |

18 | What happens after a law passes? Follow regulatory activity and court opinions across the federal government. 19 |

20 |
21 | 22 |
23 |

State Legislation

24 |

25 | Track state legislative action for all 50 states, plus D.C. and Puerto Rico. Powered by Open States. 26 |

27 |
28 | 29 |
30 |

And More!

31 |

32 | Get custom alerts via RSS or Atom feeds, such as alerts on new FEC filings. Or, create custom collections of alerts. 33 |

34 |
35 | 36 |
37 |
38 | 39 |
40 |
41 |

Featured Collections

42 |
43 |
44 |
45 | "/> 46 |
47 |

48 | The Electronic Frontier Foundation is following NSA spying reform. 49 |

50 |
51 | 52 |
53 |
54 |
55 |
56 | 57 | "/> 58 | 59 |
60 | 64 |
65 |
66 | 67 | -------------------------------------------------------------------------------- /app/views/partials/_flash.erb: -------------------------------------------------------------------------------- 1 | <% types.each do |msg| %> 2 |
3 | <% if flash[msg] %> 4 |
5 | <%= flash[msg] %> 6 |
7 | <% end %> 8 |
9 | <% end %> -------------------------------------------------------------------------------- /app/views/partials/_share.erb: -------------------------------------------------------------------------------- 1 |
-------------------------------------------------------------------------------- /app/views/rss/_items.erb: -------------------------------------------------------------------------------- 1 | <% items.each do |item| %> 2 | <% 3 | unless defined?(interest) 4 | interest = item.interest 5 | end 6 | %> 7 | 8 | 9 | 10 | <%= partial "subscriptions/#{item.subscription_type}/rss", engine: "erb", locals: {item: item, interest: interest} %> 11 | 12 | <%= item_url item %> 13 | <%= item_guid item %> 14 | <%= rss_date item.date %> 15 | 16 | <% end %> -------------------------------------------------------------------------------- /app/views/rss/collection.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Scout - Activity for collection "<%= collection.name %>" 5 | <%= Environment.config['hostname'] %> 6 | 7 | <% if collection.description.present? %> 8 | <%= collection.description %> 9 | <% else %> 10 | <%= Environment.config['organization']['rss_description'] %> 11 | <% end %> 12 | 13 | en-us 14 | 15 | 16 | <%= partial "rss/items", engine: :erb, locals: {items: items} %> 17 | 18 | -------------------------------------------------------------------------------- /app/views/rss/interest.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Scout - Activity for <%= interest_name interest %> 5 | <%= Environment.config['hostname'] %> 6 | <%= Environment.config['organization']['rss_description'] %> 7 | en-us 8 | 9 | 10 | <%= partial "rss/items", engine: :erb, locals: {items: items, interest: interest} %> 11 | 12 | -------------------------------------------------------------------------------- /app/views/search/_item.erb: -------------------------------------------------------------------------------- 1 |
  • 4 | 5 |
    6 | 9 |
    10 | 11 | <%= partial "subscriptions/#{item.subscription_type}/result", engine: "erb", locals: {interest: interest, item: item, query: query} %> 12 | 13 | <% if defined?(preview) and preview %> 14 |
    15 | 16 | <% if item.search? %> 17 | Matched search: 18 | <% elsif item.item? %> 19 | Related to bill: 20 | <% elsif item.feed? %> 21 | From feed: 22 | <% end %> 23 | 24 | 25 | <%= interest_name interest, long: false %> 26 | 27 |
    28 | <% end %> 29 |
  • -------------------------------------------------------------------------------- /app/views/search/_related_interests.erb: -------------------------------------------------------------------------------- 1 | <% if current_interest %> 2 |
    3 | <% if current_interest.query_type == 'simple' %> 4 | Your "<%= interest_name current_interest %>" subscriptions 5 | <% else %> 6 | Your related subscriptions 7 | <% end %> 8 |
    9 | <% end %> 10 | 11 | <% grouped = related_interests.to_a.group_by(&:search_type) %> 12 | <% (["all"] + search_types).each do |search_type| %> 13 | <% if interests = grouped[search_type] %> 14 | <% sorted = interests.sort_by {|s| s.data.keys.size} %> 15 | 44 | <% end %> 45 | <% end %> -------------------------------------------------------------------------------- /app/views/search/_search.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 | 4 | 6 | placeholder="Search for a keyword or phrase..." 7 | <% else %> 8 | placeholder="Enter search terms..." 9 | <% end %> 10 | value="<%= h params[:query] %>" 11 | /> 12 |
    13 | 14 |
    15 | 16 | 19 | 20 |
    21 | 22 |
    23 | /> 24 | 25 | /> 26 | 27 |
    28 | 29 |
    30 | 31 |
    32 |
      34 | > 35 |
    • e.g. crowdfunding, intellectual property, 5 USC 552
    • 36 |
    37 |
      39 | > 40 |
    • quotes around phrases, e.g. "open government"
    • 41 |
    • minus sign excludes a term, e.g. -fire, -"fire sale"
    • 42 |
    43 |
    44 |
    -------------------------------------------------------------------------------- /app/views/search/items.erb: -------------------------------------------------------------------------------- 1 | <% if items %> 2 | <%# client-side pagination is just that %> 3 | <% if defined?(per_page) && per_page %> 4 | <% items = items.first per_page %> 5 | <% end %> 6 | 7 | <% items.each do |item| %> 8 | <%= partial "search/item", engine: "erb", locals: {item: item, query: query, interest: interest} %> 9 | <% end %> 10 | <% end %> -------------------------------------------------------------------------------- /app/views/subscriptions: -------------------------------------------------------------------------------- 1 | ../../subscriptions/views/ -------------------------------------------------------------------------------- /config.ru.example: -------------------------------------------------------------------------------- 1 | ENV['RACK_ENV'] ||= 'development' 2 | 3 | require 'rubygems' 4 | require 'bundler/setup' 5 | 6 | require './scout' 7 | 8 | run Sinatra::Application 9 | -------------------------------------------------------------------------------- /config/cron/backup: -------------------------------------------------------------------------------- 1 | 2 | # runs on the mongo-scout database server, as of 2014-05-05. 3 | 4 | 0 5 * * * /home/eric/backups/to-s3.sh > /home/eric/backups/to-s3.log 2>&1 5 | 6 | # leave newline 7 | -------------------------------------------------------------------------------- /config/cron/example/crontab: -------------------------------------------------------------------------------- 1 | # SCOUT_PATH is the path to the clone of the repository 2 | SCOUT_PATH=/path/to/repository 3 | # SCOUT_ENV_FILE contains all environment variables for Scout 4 | SCOUT_ENV_FILE=/path/to/.profile 5 | # SCOUT_LOG_PATH keeps log files generated by Scout 6 | SCOUT_LOG_PATH=/path/to/logs 7 | 8 | # Assumes that server time is UTC and than local time is EST. 9 | 10 | # Initialize any uninitialized subscriptions every half hour 11 | */30 * * * * . $SCOUT_ENV_FILE && cd $SCOUT_PATH && rake subscriptions:reinitialize > $SCOUT_LOG_PATH/subscriptions-reinitialize.log 2>&1 12 | 13 | # At midnight, email a report of Google's crawling activity 14 | 0 5 * * * . $SCOUT_ENV_FILE && cd $SCOUT_PATH && rake analytics:google > $SCOUT_LOG_PATH/analytics-google.log 2>&1 15 | 16 | # At 12:15am on Monday, email about last week's user activity 17 | 15 5 * * 1 . $SCOUT_ENV_FILE && cd $SCOUT_PATH && rake analytics:weekly service=scout > $SCOUT_LOG_PATH/analytics-weekly.log 2>&1 18 | 19 | # At 12:30am, regenerate the sitemap (you should sync before running this job) 20 | 30 5 * * * . $SCOUT_ENV_FILE && cd $SCOUT_PATH && rake sitemap > $SCOUT_LOG_PATH/sitemap.log 2>&1 21 | 22 | ### Delivery tasks 23 | 24 | # At 8am, deliver daily emails 25 | 0 13 * * * . $SCOUT_ENV_FILE && cd $SCOUT_PATH && rake deliver:email_daily > $SCOUT_LOG_PATH/deliver-email_daily.log 2>&1 26 | 27 | # Every 10 minutes, deliver immediate emails 28 | */10 * * * * . $SCOUT_ENV_FILE && cd $SCOUT_PATH && rake deliver:email_immediate > $SCOUT_LOG_PATH/deliver-email_immediate.log 2>&1 29 | 30 | ### Polling tasks 31 | 32 | # Every hour, check RSS and Atom feeds for new items 33 | 45 * * * * . $SCOUT_ENV_FILE && cd $SCOUT_PATH && rake subscriptions:check:feed > $SCOUT_LOG_PATH/check-feed.log 2>&1 34 | 35 | # # Add any other syncing and polling jobs, depending on your subscription adapters 36 | -------------------------------------------------------------------------------- /config/cron/production/backup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Scout backup script. It's not the best, but it works. 4 | # TODO: 5 | # * Version on database server should be properly synced to version control. 6 | # * Should run from a different server than the database server. 7 | # * Should be burned down to the ground and turned into a proper backup system. 8 | 9 | DUMP_PATH="/home/ubuntu/bkups/" 10 | DUMP_DIR="dump" 11 | S3_PATH="s3://scout-assets/scout/backups/mongo-scout" 12 | 13 | today=$(date +%Y%m%d) 14 | two_weeks_ago=$(date +%Y%m%d --date '14 days ago') 15 | 16 | # should be made relative 17 | cd $DUMP_PATH 18 | 19 | # previous dump might be hanging about ... 20 | rm -rf ${DUMP_PATH}${DUMP_DIR} 21 | 22 | MONGODUMP="mongodump --db=scout" 23 | 24 | # maintain a whitelist of collections to dump. 25 | # Obviously: must be updated when new collections are added to the system! 26 | 27 | # clear any existing, possibly aborted, past dumps 28 | ##rm -rf dump 29 | ##rm *.tgz 30 | 31 | # easy to store, but easy to restore too 32 | $MONGODUMP --collection=agencies 33 | $MONGODUMP --collection=definitions 34 | $MONGODUMP --collection=legislators 35 | $MONGODUMP --collection=citations 36 | $MONGODUMP --collection=system.indexes 37 | 38 | # growth should be manageable for some time 39 | $MONGODUMP --collection=events 40 | $MONGODUMP --collection=receipts 41 | $MONGODUMP --collection=reports 42 | 43 | # absolutely vital: must be saved 44 | $MONGODUMP --collection=interests 45 | $MONGODUMP --collection=subscriptions 46 | $MONGODUMP --collection=tags 47 | $MONGODUMP --collection=users 48 | 49 | # seen_items is huge (1.7M items as of this writing, takes a while to write), 50 | # but it's worth backing up if possible. 51 | # 52 | # The growth curve could be seriously bent downwards by addressing: 53 | # https://github.com/sunlightlabs/scout/issues/410 54 | # 55 | # But this would not solve the problem -- these 1.7M are already post-restore, 56 | # and represent close to the original ~40 items per-subscription. It's just a lot 57 | # of stuff. 58 | # 59 | # As of right now, it takes up too much disk space, so disabling. 60 | # I would prefer this be backed up, though. 61 | # 62 | # $MONGODUMP --collection=seen_items 63 | 64 | 65 | # blacklisted: 66 | # 67 | # items are huge, and for purely caching/sitemap purposes. 68 | # they can be restored using the instructions in reindex.md. 69 | # $MONGODUMP --collection=items 70 | # 71 | # caches are huge, and also purely caching. can be restored through normal site use. 72 | # $MONGODUMP --collection=caches 73 | # 74 | # deliveries are an ephemeral queue. anything that might happen to be backed up, 75 | # should not be restored, for fear of re-delivery of old items. 76 | # $MONGODUMP --collection=deliveries 77 | 78 | # as of 2014-05-04, /dump takes up 1.1G 79 | # as of 2014-05-04, $today.tgz takes up 135M 80 | tar -czvf $today.tgz $DUMP_DIR 81 | 82 | # put to s3 83 | s3cmd put $today.tgz ${S3_PATH}/$today.tgz 84 | 85 | # cleanup locally 86 | rm -rf ${DUMP_PATH}${DUMP_DIR} 87 | rm ${DUMP_PATH}$today.tgz 88 | 89 | # cleanup globally 90 | s3cmd del ${S3_PATH}/$two_weeks_ago.tgz 91 | -------------------------------------------------------------------------------- /config/cron/production/check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | . /projects/scout/.bashrc 4 | cd /projects/scout/current 5 | 6 | FIRST=$1 7 | shift 8 | RACK_ENV=production rake subscriptions:check:$FIRST $@ > /projects/scout/shared/cron/check/$FIRST.last 2>&1 -------------------------------------------------------------------------------- /config/cron/production/deliver.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | . /projects/scout/.bashrc 4 | cd /projects/scout/current 5 | 6 | RACK_ENV=production rake deliver:$1 > /projects/scout/shared/cron/deliver/$1.last 2>&1 -------------------------------------------------------------------------------- /config/cron/production/disabled: -------------------------------------------------------------------------------- 1 | # The crontab is currently disabled. 2 | # 3 | # If this comment is here, it means this action was taken deliberately, by an 4 | # administrator. 5 | # 6 | # But don't forget that the crontab is off! 7 | # The below task will send an email every so often with a reminder. 8 | 9 | # Currently: every 6 hours 10 | 0 */6 * * * /projects/scout/current/config/cron/production/rake.sh crontab:warn 11 | 12 | 13 | # leave trailing newline please 14 | -------------------------------------------------------------------------------- /config/cron/production/rake.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | . /projects/scout/.bashrc 4 | cd /projects/scout/current 5 | 6 | FIRST=$1 7 | shift 8 | RACK_ENV=production rake $FIRST $@ > /projects/scout/shared/cron/$FIRST.last 2>&1 -------------------------------------------------------------------------------- /config/cron/staging/check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | . /projects/alarms/.bashrc 4 | cd /projects/alarms/current 5 | 6 | FIRST=$1 7 | shift 8 | rake subscriptions:check:$FIRST $@ > /projects/alarms/shared/cron/check/$FIRST.last 2>&1 -------------------------------------------------------------------------------- /config/cron/staging/crontab: -------------------------------------------------------------------------------- 1 | ######## Miscellaneous tasks ########### 2 | 3 | */30 * * * * /projects/alarms/current/config/cron/staging/rake.sh subscriptions:reinitialize 4 | 5 | 6 | ######## External data loading ######### 7 | 8 | # reload glossary terms once a day 9 | 30 10 * * * /projects/alarms/current/config/cron/staging/rake.sh glossary:load 10 | 11 | # reload legislators once a day 12 | 20 8 * * * /projects/alarms/current/config/cron/staging/rake.sh legislators:load 13 | 14 | # reload agencies once a week (these don't change often) 15 | 0 0 * * 0 /projects/alarms/current/config/cron/staging/rake.sh agencies:load 16 | 17 | 18 | ######## Delivery tasks ######## 19 | 20 | # email digests - once a day, in the morning, 8am/9am EST/EDT 21 | 0 13 * * * /projects/alarms/current/config/cron/staging/deliver.sh email_daily 22 | 23 | # immediate emails - throughout the day, every 10 minutes 24 | */10 * * * * /projects/alarms/current/config/cron/staging/deliver.sh email_immediate 25 | 26 | ######## Polling tasks ######### 27 | 28 | 29 | ### keyword search subscriptions 30 | 31 | # bills in Congress - daily, bounded by THOMAS/GPO 32 | 45 10 * * * /projects/alarms/current/config/cron/staging/check.sh federal_bills 33 | 34 | # bills in the states - Open States 35 | 0 12 * * * /projects/alarms/current/config/cron/staging/check.sh state_bills 36 | 0 12 * * * /projects/alarms/current/config/cron/staging/check.sh state_legislators_bills 37 | 38 | # speeches in Congress - daily, bounded by GPO 39 | 0 9 * * * /projects/alarms/current/config/cron/staging/check.sh speeches 40 | 41 | # regulations - FederalRegister.gov updates at unknown intervals 42 | 45 11 * * * /projects/alarms/current/config/cron/staging/check.sh regulations 43 | 44 | # documents - varies by source 45 | 0 10 * * * /projects/alarms/current/config/cron/staging/check.sh documents 46 | 47 | 48 | ### item-specific subscriptions 49 | 50 | # activity on bills in Congress - daily, bounded by THOMAS 51 | 0 12 * * * /projects/alarms/current/config/cron/staging/check.sh federal_bills_activity 52 | 53 | # upcoming notices for bills in congress - bounded by party leadership 54 | 45 * * * * /projects/alarms/current/config/cron/staging/check.sh federal_bills_upcoming_floor 55 | 56 | # upcoming hearings for bills in Congress - updated throughout the day 57 | 30 * * * * /projects/alarms/current/config/cron/staging/check.sh federal_bills_hearings 58 | 59 | # vote results for bills in Congress - updated by House/Senate throughout the day 60 | 0 * * * * /projects/alarms/current/config/cron/staging/check.sh federal_bills_votes 61 | 62 | # activity on bills in the States - 7am (Open States done scraping by 6:30am) 63 | 0 12 * * * /projects/alarms/current/config/cron/staging/check.sh state_bills_activity 64 | 0 12 * * * /projects/alarms/current/config/cron/staging/check.sh state_bills_votes 65 | 66 | 67 | # external feed subscriptions, once an hour 68 | 0 * * * * /projects/alarms/current/config/cron/staging/check.sh feed 69 | 70 | # keep the newline below 71 | -------------------------------------------------------------------------------- /config/cron/staging/deliver.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | . /projects/alarms/.bashrc 4 | cd /projects/alarms/current 5 | 6 | rake deliver:$1 > /projects/alarms/shared/cron/deliver/$1.last 2>&1 -------------------------------------------------------------------------------- /config/cron/staging/disabled: -------------------------------------------------------------------------------- 1 | # The crontab is currently disabled. 2 | # 3 | # If this comment is here, it means this action was taken deliberately, by an 4 | # administrator. 5 | # 6 | # But don't forget that the crontab is off! 7 | # The below task will send an email every so often with a reminder. 8 | 9 | # Currently: every 6 hours 10 | 0 */6 * * * /projects/alarms/current/config/cron/staging/rake.sh crontab:warn 11 | 12 | 13 | # leave trailing newline please 14 | -------------------------------------------------------------------------------- /config/cron/staging/rake.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | . /projects/alarms/.bashrc 4 | cd /projects/alarms/current 5 | 6 | rake $1 > /projects/alarms/shared/cron/$1.last 2>&1 -------------------------------------------------------------------------------- /config/services.yml.example: -------------------------------------------------------------------------------- 1 | # services Scout will deliver alerts for 2 | 3 | # service_name: 4 | # name: Open States 5 | # url: http://openstates.org 6 | # secret_key: -------------------------------------------------------------------------------- /config/slack.rb: -------------------------------------------------------------------------------- 1 | require 'slack-notifier' 2 | 3 | module Slack 4 | 5 | def self.message!(subject, body = nil) 6 | config = Environment.config 7 | return unless slack? 8 | 9 | notifier = Slack::Notifier.new config['slack']['team'], config['slack']['token'] 10 | notifier.channel = config['slack']['channel'] 11 | notifier.username = config['slack']['username'] 12 | 13 | # just to be nice, add the colons if not configured correctly 14 | emoji = config['slack']['icon_emoji'] 15 | emoji = ":#{emoji}:" if emoji[":"].nil? 16 | 17 | if body 18 | message = [subject, "```\n#{body}\n```"].join("\n\n") 19 | else 20 | message = subject 21 | end 22 | 23 | notifier.ping message, icon_emoji: emoji 24 | rescue Exception => exc 25 | report = Report.exception 'Slack notifications', "Exception notifying slack", exc 26 | Admin.report report, slack: false # don't try to slack a slack error 27 | puts "Error notifying slack, emailed report." 28 | end 29 | 30 | # always disable slack in test mode 31 | # allow development mode to disable slack by withholding the slack section/team 32 | def self.slack? 33 | config = Environment.config 34 | !Sinatra::Application.test? and config['slack'] and config['slack']['team'].present? 35 | end 36 | 37 | end -------------------------------------------------------------------------------- /misc/header.htm: -------------------------------------------------------------------------------- 1 |

    Hey there!

    2 | We've recently made some fixes and improvements to our state bill alerts. This means we're now catching a lot more state bills that match your searches, including ones we missed earlier in 2013. 3 |

    4 | We apologize for the inconvenience; you may receive a large number of state bills in this email. This is the first time our state bill collection has seen the beginning of a legislative session across all 50 states, so the backlog is heavy. We're catching you up on these bills in one single email, rather than many, by grouping together up to the last 50 results for each of your state bill alerts. 5 |

    6 | Your state bill alerts will return to normal after this, and will be more timely and relevant than before. 7 |

    -------------------------------------------------------------------------------- /misc/stats.rb: -------------------------------------------------------------------------------- 1 | def topline 2 | msg = "" 3 | 4 | [nil, "open_states"].each do |service| 5 | msg << "[#{service || "scout"}]\n" 6 | msg << "\n" 7 | 8 | total_users = User.where(service: service).count 9 | active_users = User.where(service: service).select {|u| u.interests.count > 0} 10 | active_outside = active_users.reject {|u| u.email =~ /sunlightfoundation\.com/} 11 | 12 | msg << "Total users: #{total_users}\n" 13 | msg << "Active users (at least 1 alert): #{active_users.size}\n" 14 | msg << "Active outside users (at least 1 alert, excluding sunlightfoundation.com emails): #{active_outside.size}\n" 15 | msg << "\n" 16 | 17 | active_outside_alerts = active_outside.map {|u| u.interests.count}.sum 18 | msg << "Alerts by active outside users: #{active_outside_alerts}\n" 19 | msg << "\n" 20 | msg << "\n" 21 | end 22 | 23 | Admin.sensitive "Scout User Stats", msg 24 | end 25 | 26 | def activity_report(days) 27 | all_start = Time.zone.parse(days.first).midnight.strftime "%B %d, %Y" 28 | all_end = Time.zone.parse(days.last).midnight.strftime "%B %d, %Y" 29 | 30 | subject = "Activity from #{all_start} - #{all_end}" 31 | msg = "" 32 | 33 | days.each do |day| 34 | start_time = Time.zone.parse(day).midnight # midnight Eastern time 35 | end_time = start_time + 1.day 36 | ending = end_time.strftime "%Y-%m-%d" 37 | 38 | users = User.asc(:created_at).for_time day, ending 39 | interests = Interest.for_time day, ending 40 | unsubscribes = Event.where(type: "unsubscribe-alert").for_time day, ending 41 | receipts = Receipt.where(mechanism: "email").for_time day, ending 42 | 43 | msg << "- #{start_time.strftime("%B %d, %Y")}\n" 44 | msg << "\n" 45 | 46 | msg << "#{users.count} new users\n" 47 | msg << "#{interests.count} alerts created across all users\n" 48 | msg << "#{unsubscribes.count} alerts removed across all users\n" 49 | msg << "#{receipts.count} delivered emails across all users" 50 | msg << "\n" 51 | 52 | users.each do |user| 53 | source = if user.source.is_a?(Hash) 54 | user.source['utm_source'] 55 | else 56 | user.source 57 | end 58 | 59 | msg << "#{user.created_at.in_time_zone.strftime "%H:%M"} #{user.contact} - (#{user.interests.for_time(day, ending).count}) - #{source}\n" 60 | end 61 | 62 | msg << "\n\n" 63 | end 64 | 65 | Admin.sensitive subject, msg 66 | end -------------------------------------------------------------------------------- /misc/top_alerts.rb: -------------------------------------------------------------------------------- 1 | # scratch code used to calculate top searches and bills 2 | 3 | is = Interest.where(interest_type: "search").select do |i| 4 | i.user.service.nil? and (i.user.email !~ /sunlightfoundation/i) 5 | end; is.size 6 | 7 | is = Interest.where(interest_type: "item", item_type: "bill").select do |i| 8 | i.user.service.nil? and (i.user.email !~ /sunlightfoundation/i) 9 | end; is.size 10 | 11 | 12 | terms = {} 13 | is.each do |interest| 14 | terms[interest.in] ||= 0 15 | terms[interest.in] += 1 16 | end; is.size 17 | 18 | sorted = terms.keys.sort_by {|key| terms[key]} -------------------------------------------------------------------------------- /public/BingSiteAuth.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 94D90F3959142808A053EE51720B3CC1 4 | -------------------------------------------------------------------------------- /public/assets/css/html5-reset.css: -------------------------------------------------------------------------------- 1 | /* http://meyerweb.com/eric/tools/css/reset/ 2 | v2.0 | 20110126 3 | License: none (public domain) 4 | */ 5 | 6 | html, body, div, span, applet, object, iframe, 7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 8 | a, abbr, acronym, address, big, cite, code, 9 | del, dfn, em, img, ins, kbd, q, s, samp, 10 | small, strike, strong, sub, sup, tt, var, 11 | b, u, i, center, 12 | dl, dt, dd, ol, ul, li, 13 | fieldset, form, label, legend, 14 | table, caption, tbody, tfoot, thead, tr, th, td, 15 | article, aside, canvas, details, embed, 16 | figure, figcaption, footer, header, hgroup, 17 | menu, nav, output, ruby, section, summary, 18 | time, mark, audio, video { 19 | margin: 0; 20 | padding: 0; 21 | border: 0; 22 | font-size: 100%; 23 | font: inherit; 24 | vertical-align: baseline; 25 | } 26 | /* HTML5 display-role reset for older browsers */ 27 | article, aside, details, figcaption, figure, 28 | footer, header, hgroup, menu, nav, section { 29 | display: block; 30 | } 31 | body { 32 | line-height: 1.3; 33 | } 34 | 35 | html {-webkit-text-size-adjust:none} 36 | 37 | ol, ul { 38 | list-style: none; 39 | } 40 | blockquote, q { 41 | quotes: none; 42 | } 43 | blockquote:before, blockquote:after, 44 | q:before, q:after { 45 | content: ''; 46 | content: none; 47 | } 48 | table { 49 | border-collapse: collapse; 50 | border-spacing: 0; 51 | xwidth: 100%; 52 | margin-bottom: 20px; 53 | } -------------------------------------------------------------------------------- /public/assets/images/bg_body.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/bg_body.jpg -------------------------------------------------------------------------------- /public/assets/images/bg_body.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/bg_body.png -------------------------------------------------------------------------------- /public/assets/images/bg_currentCat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/bg_currentCat.png -------------------------------------------------------------------------------- /public/assets/images/bg_featured_collection.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/bg_featured_collection.png -------------------------------------------------------------------------------- /public/assets/images/bg_footer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/bg_footer.png -------------------------------------------------------------------------------- /public/assets/images/bg_hat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/bg_hat.png -------------------------------------------------------------------------------- /public/assets/images/bg_header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/bg_header.png -------------------------------------------------------------------------------- /public/assets/images/bg_header_email.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/bg_header_email.gif -------------------------------------------------------------------------------- /public/assets/images/bg_homeWrapper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/bg_homeWrapper.png -------------------------------------------------------------------------------- /public/assets/images/bg_openstates_email.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/bg_openstates_email.jpg -------------------------------------------------------------------------------- /public/assets/images/bg_sectionHeader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/bg_sectionHeader.png -------------------------------------------------------------------------------- /public/assets/images/btn_rss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/btn_rss.png -------------------------------------------------------------------------------- /public/assets/images/bullet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/bullet.png -------------------------------------------------------------------------------- /public/assets/images/donor_bg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/donor_bg.gif -------------------------------------------------------------------------------- /public/assets/images/donor_btn.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/donor_btn.gif -------------------------------------------------------------------------------- /public/assets/images/featured/eff-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/featured/eff-logo.png -------------------------------------------------------------------------------- /public/assets/images/homeSearch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/homeSearch.png -------------------------------------------------------------------------------- /public/assets/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/icon.png -------------------------------------------------------------------------------- /public/assets/images/icon_check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/icon_check.png -------------------------------------------------------------------------------- /public/assets/images/icon_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/icon_error.png -------------------------------------------------------------------------------- /public/assets/images/icon_feedback.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/icon_feedback.png -------------------------------------------------------------------------------- /public/assets/images/icon_rss_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/icon_rss_large.png -------------------------------------------------------------------------------- /public/assets/images/icon_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/icon_search.png -------------------------------------------------------------------------------- /public/assets/images/icon_settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/icon_settings.png -------------------------------------------------------------------------------- /public/assets/images/icon_subscriptions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/icon_subscriptions.png -------------------------------------------------------------------------------- /public/assets/images/icon_type_bill.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/icon_type_bill.png -------------------------------------------------------------------------------- /public/assets/images/icon_type_feed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/icon_type_feed.png -------------------------------------------------------------------------------- /public/assets/images/icon_type_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/icon_type_search.png -------------------------------------------------------------------------------- /public/assets/images/icons_resources.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/icons_resources.png -------------------------------------------------------------------------------- /public/assets/images/icons_share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/icons_share.png -------------------------------------------------------------------------------- /public/assets/images/loader-32.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/loader-32.gif -------------------------------------------------------------------------------- /public/assets/images/logo_openstates_email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/logo_openstates_email.png -------------------------------------------------------------------------------- /public/assets/images/logo_scout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/logo_scout.png -------------------------------------------------------------------------------- /public/assets/images/logo_sunlightFoundation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/logo_sunlightFoundation.png -------------------------------------------------------------------------------- /public/assets/images/logo_sunlightFoundation_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/logo_sunlightFoundation_dark.png -------------------------------------------------------------------------------- /public/assets/images/logo_sunlightFoundation_email.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/logo_sunlightFoundation_email.png -------------------------------------------------------------------------------- /public/assets/images/more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/more.png -------------------------------------------------------------------------------- /public/assets/images/newSearch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/newSearch.png -------------------------------------------------------------------------------- /public/assets/images/productOf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/productOf.png -------------------------------------------------------------------------------- /public/assets/images/profile-placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/profile-placeholder.png -------------------------------------------------------------------------------- /public/assets/images/rss-alerts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/rss-alerts.png -------------------------------------------------------------------------------- /public/assets/images/rss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/rss.png -------------------------------------------------------------------------------- /public/assets/images/scout-collections-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/scout-collections-1.png -------------------------------------------------------------------------------- /public/assets/images/scout-collections-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/scout-collections-2.png -------------------------------------------------------------------------------- /public/assets/images/sidebar_divider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/sidebar_divider.png -------------------------------------------------------------------------------- /public/assets/images/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/splash.png -------------------------------------------------------------------------------- /public/assets/images/splash_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/splash_text.png -------------------------------------------------------------------------------- /public/assets/images/through_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/through_arrow.png -------------------------------------------------------------------------------- /public/assets/images/tinyborder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/tinyborder.png -------------------------------------------------------------------------------- /public/assets/images/tinyborder_andshadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/tinyborder_andshadow.png -------------------------------------------------------------------------------- /public/assets/images/tinyborder_andshadow_vertical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/tinyborder_andshadow_vertical.png -------------------------------------------------------------------------------- /public/assets/images/tinyborder_andshadow_vertical_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/tinyborder_andshadow_vertical_dark.png -------------------------------------------------------------------------------- /public/assets/images/tinyborder_andshadow_vertical_original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/tinyborder_andshadow_vertical_original.png -------------------------------------------------------------------------------- /public/assets/images/tinyborder_entry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/tinyborder_entry.png -------------------------------------------------------------------------------- /public/assets/images/tinyborder_footer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/tinyborder_footer.png -------------------------------------------------------------------------------- /public/assets/images/tinyshadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/tinyshadow.png -------------------------------------------------------------------------------- /public/assets/images/youtube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/public/assets/images/youtube.png -------------------------------------------------------------------------------- /public/assets/js/html5.js: -------------------------------------------------------------------------------- 1 | // html5shiv MIT @rem remysharp.com/html5-enabling-script 2 | // iepp v1.6.2 MIT @jon_neal iecss.com/print-protector 3 | /*@cc_on(function(m,c){var z="abbr|article|aside|audio|canvas|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video";function n(d){for(var a=-1;++ai";if(g.childNodes.length!==1){var i=z.split("|"),o=i.length,s=RegExp("(^|\\s)("+z+")", 4 | "gi"),t=RegExp("<(/*)("+z+")","gi"),u=RegExp("(^|[^\\n]*?\\s)("+z+")([^\\n]*)({[\\n\\w\\W]*?})","gi"),r=c.createDocumentFragment(),k=c.documentElement;g=k.firstChild;var h=c.createElement("body"),l=c.createElement("style"),f;n(c);n(r);g.insertBefore(l, 5 | g.firstChild);l.media="print";m.attachEvent("onbeforeprint",function(){var d=-1,a=p(c.styleSheets,"all"),e=[],b;for(f=f||c.body;(b=u.exec(a))!=null;)e.push((b[1]+b[2]+b[3]).replace(s,"$1.iepp_$2")+b[4]);for(l.styleSheet.cssText=e.join("\n");++d 2 | Court opinions are provided by CourtListener, which is developed by the Free Law Project. 3 |

    4 |

    5 | Scout currently limits court opinion search to federal appellate and special courts, and only as far back as 2009. We may expand this over time. 6 |

    7 |

    8 | For more comprehensive search facilities, visit CourtListener directly to search their historical archive of opinions across hundreds of jurisdictions at the state and federal level. 9 |

    -------------------------------------------------------------------------------- /subscriptions/views/court_opinions/_email.erb: -------------------------------------------------------------------------------- 1 | <% opinion = item.data %> 2 | 3 | <%= email_header opinion['case_name'], url %> 4 | 5 | <%= email_subheader_div %> 6 | 7 | <%= just_date_year item.date %>, 8 | <%= opinion['court'] %> 9 | 10 | 11 | 12 | <%= email_content_p %> 13 | <% if highlight = opinion_highlight(item, interest) %> 14 | <%= highlight %> 15 | <% else %> 16 | No excerpt available. 17 | <% end %> 18 | -------------------------------------------------------------------------------- /subscriptions/views/court_opinions/_filter.erb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/subscriptions/views/court_opinions/_filter.erb -------------------------------------------------------------------------------- /subscriptions/views/court_opinions/_result.erb: -------------------------------------------------------------------------------- 1 | <% opinion = item.data %> 2 | 3 | 4 |

    5 | <%= opinion['case_name'] %> 6 |

    7 |
    8 | 9 |
    10 | 11 | <%= opinion['court'] %> 12 | 13 |
    14 | 15 |

    16 | <% if highlight = opinion_highlight(item, interest) %> 17 | <%= highlight %> 18 | <% else %> 19 | No excerpt available. 20 | <% end %> 21 |

    -------------------------------------------------------------------------------- /subscriptions/views/court_opinions/_rss.erb: -------------------------------------------------------------------------------- 1 | <% opinion = item.data %> 2 | 3 | <%= opinion['case_name'] %> 4 | 5 | <% if highlight = opinion_highlight(item, interest) %> 6 | 8 | Court opinion from the <%= opinion['court'] %> 9 |

    10 |

    11 | <%= highlight %> 12 |

    13 | ]]>
    14 | <% else %> 15 | 16 | No excerpt available. 17 | 18 | <% end %> -------------------------------------------------------------------------------- /subscriptions/views/court_opinions/_show.erb: -------------------------------------------------------------------------------- 1 | <% opinion = item.data %> 2 | 3 |
    4 | 5 |
    6 |

    7 | <%= opinion['case_name'] %> 8 |

    9 |
    10 | Opinion from the 11 | <%= opinion['court'] %>, 12 | filed on 13 | 16 |
    17 |
    18 | 19 |
    20 | 21 |
    22 |
    Resources
    23 |
      24 | 25 | Visit our data source, 26 | 27 | CourtListener, 28 | for much more information. 29 | 30 |
    31 |
    32 | 33 |
    34 |
    35 | Full Opinion 36 | 37 | We do our best to display the opinion below. 38 | <% if opinion['download_url'] %> 39 | You can also read the 40 | 41 | original text. 42 | <% end %> 43 | 44 |
    45 | 46 |
    47 | 48 |
    49 |
    50 |         <%= opinion['text'] %>
    51 |       
    52 |
    53 |
    54 |
    -------------------------------------------------------------------------------- /subscriptions/views/documents/_data.erb: -------------------------------------------------------------------------------- 1 |

    2 | All GAO reports are sourced directly from the Government Accountability Office. 3 |

    4 |

    5 | All Inspector General reports are sourced directly from their original official website, and gathered using the unitedstates/inspectors-general project. Read more about the project. 6 |

    7 |

    8 | Scout's data covers 2009 to the present. 9 |

    -------------------------------------------------------------------------------- /subscriptions/views/documents/_email.erb: -------------------------------------------------------------------------------- 1 | <% document = item.data %> 2 | 3 | <%= email_header document['title'], url %> 4 | 5 | <%= email_subheader_div %> 6 | 7 | <%= just_date item.date %> 8 | 9 | — 10 | <%= document_subtitle document %> 11 | 12 | 13 | <%= email_content_p %> 14 | <% if highlight = document_highlight(item, interest, inline: true) %> 15 | <%= highlight %> 16 | <% elsif (description = document_description(document)).present? %> 17 | <%= truncate description, 500 %> 18 | <% else %> 19 | No excerpt or abstract available. 20 | <% end %> 21 | -------------------------------------------------------------------------------- /subscriptions/views/documents/_filter.erb: -------------------------------------------------------------------------------- 1 |
  • 2 | 3 | 8 |
  • -------------------------------------------------------------------------------- /subscriptions/views/documents/_result.erb: -------------------------------------------------------------------------------- 1 | <% document = item.data %> 2 | 3 | 4 |

    <%= document['title'] %>

    5 |
    6 | 7 |
    8 | <%= document_subtitle document %> 9 |
    10 | 11 |

    12 | <% if highlight = document_highlight(item, interest) %> 13 | <%= highlight %> 14 | <% elsif (description = document_description(document)).present? %> 15 | <%= truncate description, 500 %> 16 | <% else %> 17 | No excerpt or abstract available. 18 | <% end %> 19 |

    -------------------------------------------------------------------------------- /subscriptions/views/documents/_rss.erb: -------------------------------------------------------------------------------- 1 | <% document = item.data %> 2 | <%= document['title'] %> 3 | 5 | 6 | <%= document_subtitle document %> 7 | 8 |

    9 |

    10 | <% if (highlight = document_highlight(item, interest)) %> 11 | <%= highlight %> 12 | <% elsif (description = document_description(document)).present? %> 13 | <%= truncate description, 500 %> 14 | <% else %> 15 | No excerpt or description available. 16 | <% end %> 17 |

    18 | ]]>
    -------------------------------------------------------------------------------- /subscriptions/views/documents/_show.erb: -------------------------------------------------------------------------------- 1 | <% document = item.data %> 2 | 3 |
    4 | 5 |
    6 |

    <%= document['title'] %>

    7 |
    8 | <%= document_subtitle document %> 9 | released on 10 | 13 |
    14 |
    15 | 16 |
    17 | 18 | <% if document['document_type'] == "gao_report" %> 19 | 20 |
    21 |
    Resources
    22 | 31 |
    32 | 33 |
    34 |
    35 | Summary 36 | 37 | Below is the official summary. 38 | <% if document['source_url'] %> 39 | The 40 | full report 41 | <% if document['source_url'] =~ /\.pdf$/ %> 42 | (PDF) 43 | <% end %> 44 | is also available. 45 | <% end %> 46 | 47 |
    48 |
    49 |
    50 | <%= gao_description document %> 51 |
    52 |
    53 | <% elsif document['document_type'] == 'ig_report' %> 54 |
    55 |
    56 | Summary 57 | 58 | <% if document['description'].present? %> 59 | Below is the official summary. 60 | <% else %> 61 | No official summary available. 62 | <% end %> 63 | 64 | For more details, read the 65 | full report<% if document['ig_report']['file_type'] == 'pdf' %> 66 | (PDF)<% end %>. 67 | 68 |
    69 |
    70 |
    71 | <% if document['description'].present? %> 72 | <%= document['description'] %> 73 | <% end %> 74 |
    75 |
    76 | <% end %> 77 |
    -------------------------------------------------------------------------------- /subscriptions/views/federal_bills/_data.erb: -------------------------------------------------------------------------------- 1 |

    2 | Bill text is provided by GPO, and most other information on bills comes from THOMAS. Information from THOMAS and GPO is delayed by about one day. Scout's data covers 2009 to the present. 3 |

    4 | <% if !searching? %> 5 |

    6 | Information on floor votes and committee hearings come directly from the official House and Senate websites. This information is updated throughout the day and should appear in Scout immediately after publication. 7 |

    8 | 9 |

    10 | Notices of when a bill is scheduled for floor debate come from the House Republican Majority Leader, and Senate Democratic Caucus. This information is usually published a few days in advance and can change rapidly. 11 |

    12 | <% end %> -------------------------------------------------------------------------------- /subscriptions/views/federal_bills/_email.erb: -------------------------------------------------------------------------------- 1 | <% bill = item.data %> 2 | <% version = item.data['last_version'] %> 3 | 4 | <% if bill['short_title'].present? %> 5 | <%= email_header bill['short_title'], url %> 6 | <% else %> 7 | <%= email_header bill['official_title'], url %> 8 | <% end %> 9 | 10 | <%= email_subheader_div %> 11 | 12 | <%= just_date item.date %>, 13 | <%= bill_code bill['bill_type'], bill['number'] %> 14 | 15 | — 16 | 17 | <% if version %> 18 | <%= bill_version version['version_code'] %> 19 | <% else %> 20 | Introduced 21 | <% end %> 22 | by 23 | <%= bill_sponsor bill %> 24 | 25 | 26 | 27 | <%= email_content_p %> 28 | <% if highlight = bill_highlight(item, interest, inline: true) %> 29 | <%= highlight %> 30 | <% elsif bill['summary'] %> 31 | <%= truncate bill['summary'], 500 %> 32 | <% else %> 33 | No excerpt or summary available. 34 | <% end %> 35 | -------------------------------------------------------------------------------- /subscriptions/views/federal_bills/_filter.erb: -------------------------------------------------------------------------------- 1 |
  • 2 | 3 | 11 |
  • 12 | -------------------------------------------------------------------------------- /subscriptions/views/federal_bills/_result.erb: -------------------------------------------------------------------------------- 1 | <% bill = item.data %> 2 | <% version = item.data['last_version'] %> 3 | 4 | 5 | <% if bill['short_title'].present? %> 6 |

    <%= bill['short_title'] %>

    7 | <% else %> 8 |

    <%= bill['official_title'] %>

    9 | <% end %> 10 |
    11 | 12 |
    13 | <%= bill_code bill['bill_type'], bill['number'] %> 14 | — 15 | 16 | <% if version %> 17 | <%= bill_version version['version_code'] %> 18 | <% else %> 19 | Introduced 20 | <% end %> 21 | by 22 | <%= bill_sponsor bill %> 23 | 24 |
    25 | 26 |

    27 | <% if highlight = bill_highlight(item, interest) %> 28 | <%= highlight %> 29 | <% elsif bill['summary'] %> 30 | <%= truncate bill['summary'], 500 %> 31 | <% else %> 32 | No excerpt or summary available. 33 | <% end %> 34 |

    -------------------------------------------------------------------------------- /subscriptions/views/federal_bills/_rss.erb: -------------------------------------------------------------------------------- 1 | <% bill = item.data %> 2 | <% version = bill['version'] %> 3 | 4 | <%= bill['short_title'].present? ? bill['short_title'] : bill['official_title'] %> 5 | 6 | 8 | 9 | <%= bill_code bill['bill_type'], bill['number'] %> 10 | 11 | — 12 | <% if version %> 13 | <%= bill_version version['version_code'] %> 14 | <% else %> 15 | Introduced 16 | <% end %> 17 |

    18 |

    19 | <% if highlight = bill_highlight(item, interest) %> 20 | <%= highlight %> 21 | <% elsif bill['summary'].present? %> 22 | <%= truncate bill['summary'], 500 %> 23 | <% else %> 24 | No excerpt or summary available. 25 | <% end %> 26 |

    27 | ]]>
    -------------------------------------------------------------------------------- /subscriptions/views/federal_bills_activity/_email.erb: -------------------------------------------------------------------------------- 1 | <% bill = interest.data %> 2 | <% action = item.data %> 3 | 4 | <%= email_header just_date(item.date), url %> 5 | 6 | <%= email_content_p %> 7 | <%= action['text'] %> 8 | -------------------------------------------------------------------------------- /subscriptions/views/federal_bills_activity/_result.erb: -------------------------------------------------------------------------------- 1 | <% bill = interest.data %> 2 | <% action = item.data %> 3 | 4 | 5 |

    6 | Action on <%= bill_code bill['bill_type'], bill['number'] %> 7 |

    8 |
    9 | 10 |
    11 | Bill activity 12 |
    13 | 14 |

    15 | <%= action['text'] %> 16 |

    -------------------------------------------------------------------------------- /subscriptions/views/federal_bills_activity/_rss.erb: -------------------------------------------------------------------------------- 1 | <% action = item.data %> 2 | 3 | <%= interest_name item.interest %> 4 | 5 | New activity 7 | — 8 | <%= action['text'] %> 9 | ]]> -------------------------------------------------------------------------------- /subscriptions/views/federal_bills_hearings/_email.erb: -------------------------------------------------------------------------------- 1 | <% hearing = item.data %> 2 | <%= email_header "Hearing: #{hearing['committee']['name']}", url %> 3 | 4 | <%= email_subheader_div %> 5 | <%= just_date hearing['occurs_at'] %>, <%= just_time hearing['occurs_at'] %> 6 | 7 | 8 | <%= email_content_p %> 9 | <%= truncate hearing['description'], 500 %> 10 | -------------------------------------------------------------------------------- /subscriptions/views/federal_bills_hearings/_result.erb: -------------------------------------------------------------------------------- 1 | <% bill = interest.data %> 2 | <% hearing = item.data %> 3 | 4 | 5 |

    6 | <%= hearing['committee']['name'] %> hearing on <%= bill_code bill['bill_type'], bill['number'] %> 7 |

    8 |
    9 | 10 |
    11 | Committee hearing 12 |
    13 | 14 |

    15 | <%= truncate_more "hearing-#{hearing['occurs_at'].to_i}", hearing['description'], 200 %> 16 |

    -------------------------------------------------------------------------------- /subscriptions/views/federal_bills_hearings/_rss.erb: -------------------------------------------------------------------------------- 1 | <% hearing = item.data %> 2 | 3 | <%= interest_name item.interest %> 4 | 5 | 7 | Hearing in the <%= hearing['committee']['name'] %> 8 | 9 | — 10 | <%= hearing['description'] %> 11 | ]]> -------------------------------------------------------------------------------- /subscriptions/views/federal_bills_upcoming_floor/_email.erb: -------------------------------------------------------------------------------- 1 | <% bill = interest.data %> 2 | <% upcoming = item.data %> 3 | 4 | <%= email_header upcoming_date(upcoming), url %> 5 | 6 | <%= email_content_p %> 7 | Coming up on the <%= upcoming['chamber'].capitalize %> Floor 8 | -------------------------------------------------------------------------------- /subscriptions/views/federal_bills_upcoming_floor/_result.erb: -------------------------------------------------------------------------------- 1 | <% bill = interest.data %> 2 | <% upcoming = item.data %> 3 | 4 | 5 |

    6 | Upcoming <%= upcoming['chamber'].capitalize %> floor activity on <%= bill_code bill['bill_type'], bill['number'] %> 7 |

    8 |
    9 | 10 |
    11 | Upcoming bill debate 12 |
    13 | 14 |

    -------------------------------------------------------------------------------- /subscriptions/views/federal_bills_upcoming_floor/_rss.erb: -------------------------------------------------------------------------------- 1 | <% upcoming = item.data %> 2 | 3 | <%= interest_name item.interest %> 4 | 5 | 7 | Scheduled for the Floor 8 | 9 | — 10 | Coming up on the <%= upcoming['chamber'].capitalize %> Floor 11 | ]]> -------------------------------------------------------------------------------- /subscriptions/views/federal_bills_votes/_email.erb: -------------------------------------------------------------------------------- 1 | <% vote = item.data %> 2 | 3 | <%= email_header "#{vote['chamber'].capitalize} Vote ##{vote['number']}", url %> 4 | 5 | <%= email_subheader_div %> 6 | <%= just_date vote['voted_at'] %> 7 | 8 | 9 | <%= email_content_p %> 10 | <%= vote['roll_type'] %>: <%= vote['result'] %>, <%= vote_breakdown vote %> 11 | -------------------------------------------------------------------------------- /subscriptions/views/federal_bills_votes/_result.erb: -------------------------------------------------------------------------------- 1 | <% bill = interest.data %> 2 | <% vote = item.data %> 3 | 4 | 5 |

    6 | Vote in the <%= vote['chamber'].capitalize %> on <%= bill_code bill['bill_type'], bill['number'] %> 7 |

    8 |
    9 | 10 |
    11 | Vote 12 |
    13 | 14 |

    15 | 16 | <%= vote['roll_type'] %>, 17 | 18 | <%= vote['result'] %> 19 | 20 | <%= vote_breakdown vote %> 21 | 22 |

    -------------------------------------------------------------------------------- /subscriptions/views/federal_bills_votes/_rss.erb: -------------------------------------------------------------------------------- 1 | <% vote = item.data %> 2 | 3 | <%= interest_name item.interest %> 4 | 5 | 7 | <%= vote['chamber'].capitalize %> Vote 8 | 9 | — 10 | <%= vote['roll_type'] %>: <%= vote['result'] %>, <%= vote_breakdown vote %> 11 | ]]> -------------------------------------------------------------------------------- /subscriptions/views/feed/_email.erb: -------------------------------------------------------------------------------- 1 | <%= email_header item.data['title'], url %> 2 | 3 | <%= email_subheader_div %> 4 | <%= just_date item.date %> 5 | 6 | 7 | <%= email_content_p %> 8 | <%= truncate_feed item.data['content'], 500 %> 9 | -------------------------------------------------------------------------------- /subscriptions/views/feed/_result.erb: -------------------------------------------------------------------------------- 1 | 2 |

    <%= item.data['title'] %>

    3 |
    4 | 5 |

    6 | <%= truncate_feed item.data['content'], 500 %> 7 |

    -------------------------------------------------------------------------------- /subscriptions/views/feed/_rss.erb: -------------------------------------------------------------------------------- 1 | <%= item.data['title'] %> 2 | 4 | ]]> -------------------------------------------------------------------------------- /subscriptions/views/regulations/_data.erb: -------------------------------------------------------------------------------- 1 |

    2 | Regulatory information is provided by the Federal Register, and published throughout the day. Scout's data covers 2009 to the present. 3 |

    -------------------------------------------------------------------------------- /subscriptions/views/regulations/_email.erb: -------------------------------------------------------------------------------- 1 | <% regulation = item.data %> 2 | 3 | <%= email_header regulation_title(regulation), url %> 4 | 5 | <%= email_subheader_div %> 6 | 7 | <%= just_date item.date %> 8 | 9 | — 10 | <% if regulation['document_type'] == "public_inspection" %> 11 | 12 | Pre-release 13 | 14 | — 15 | <% end %> 16 | 17 | 18 | <%= regulation_type regulation %> 19 | 20 | 21 | — 22 | <% if regulation['agency_names'].any? %> 23 | 24 | <%= agency_names regulation %> 25 | 26 | <% end %> 27 | 28 | 29 | <%= email_content_p %> 30 | <% if highlight = regulation_highlight(item, interest, inline: true) %> 31 | <%= highlight %> 32 | <% elsif regulation['abstract'].present? %> 33 | <%= truncate regulation['abstract'], 500 %> 34 | <% else %> 35 | No excerpt or abstract available. 36 | <% end %> 37 | -------------------------------------------------------------------------------- /subscriptions/views/regulations/_filter.erb: -------------------------------------------------------------------------------- 1 | <%# yep, a database call in the view %> 2 |
  • 3 | 4 | 10 |
  • 11 | 12 |
  • 13 | 14 | 19 |
  • -------------------------------------------------------------------------------- /subscriptions/views/regulations/_result.erb: -------------------------------------------------------------------------------- 1 | <% regulation = item.data %> 2 | 3 | 4 |

    <%= regulation_title regulation %>

    5 |
    6 | 7 |
    8 | <% if regulation['document_type'] == "public_inspection" %> 9 | 10 | Pre-release 11 | 12 | — 13 | <% end %> 14 | 15 | <%= regulation_type regulation %> 16 | 17 | — 18 | <% if regulation['agency_names'].any? %> 19 | 20 | <%= agency_names regulation %> 21 | 22 | <% end %> 23 |
    24 | 25 |

    26 | <% if highlight = regulation_highlight(item, interest) %> 27 | <%= highlight %> 28 | <% elsif regulation['abstract'].present? %> 29 | <%= truncate regulation['abstract'], 500 %> 30 | <% else %> 31 | No excerpt or abstract available. 32 | <% end %> 33 |

    -------------------------------------------------------------------------------- /subscriptions/views/regulations/_rss.erb: -------------------------------------------------------------------------------- 1 | <% regulation = item.data %> 2 | <%= regulation_title regulation %> 3 | 5 | <% if regulation['document_type'] == "public_inspection" %> 6 | 7 | Pre-release 8 | 9 | — 10 | <% end %> 11 | 12 | 13 | <%= regulation_type regulation %> 14 | 15 | 16 | <% if regulation['agency_names'].select(&:present?).any? %> 17 | — 18 | <%= regulation['agency_names'].select(&:present?).join ', ' %> 19 | <% end %> 20 |

    21 | 22 |

    23 | <% if (highlight = regulation_highlight(item, interest)) %> 24 | <% highlight = highlight.gsub("\f", "") %> 25 | <%= highlight %> 26 | <% elsif regulation['abstract'].present? %> 27 | <%= regulation['abstract'] %> 28 | <% else %> 29 | No excerpt or abstract available. 30 | <% end %> 31 |

    32 | ]]>
    -------------------------------------------------------------------------------- /subscriptions/views/speeches/_data.erb: -------------------------------------------------------------------------------- 1 |

    2 | Speeches from the Congressional Record are provided by Capitol Words, a Sunlight Foundation project that analyzes word usage in Congress. 3 |

    4 |

    5 | The Congressional Record is published by GPO once each morning and covers 1996 to the present. 6 |

    -------------------------------------------------------------------------------- /subscriptions/views/speeches/_email.erb: -------------------------------------------------------------------------------- 1 | <% speech = item.data %> 2 | 3 | <%= email_header speech['title'], url %> 4 | 5 | <%= email_subheader_div %> 6 | 7 | <%= just_date item.date %> 8 | 9 | — 10 | <% if speech['bioguide_id'] %> 11 | <%= speaker_name speech %> 12 | <% else %> 13 | <%= long_date speech['date'] %> 14 | <% end %> 15 | 16 | 17 | <%= email_content_p %> 18 | "<%= speech_excerpt speech, interest, inline: true %>" 19 | -------------------------------------------------------------------------------- /subscriptions/views/speeches/_filter.erb: -------------------------------------------------------------------------------- 1 |
  • 2 | 3 | 9 |
  • 10 |
  • 11 | 12 | 18 |
  • 19 |
  • 20 | 21 | 26 |
  • 27 | 28 | <%# yep, a database call in the view %> 29 |
  • 30 | 31 | 39 |
  • -------------------------------------------------------------------------------- /subscriptions/views/speeches/_result.erb: -------------------------------------------------------------------------------- 1 | <% speech = item.data %> 2 | 3 | 4 |

    5 | <% if speech['bioguide_id'] %> 6 | <%= speaker_name speech %> 7 | <% else %> 8 | <%= long_date speech['date'] %> 9 | <% end %> 10 |

    11 |
    12 | 13 |
    14 | <%= speech['title'] %> 15 |
    16 | 17 |

    18 | "<%= speech_excerpt speech, interest %>" 19 |

    -------------------------------------------------------------------------------- /subscriptions/views/speeches/_rss.erb: -------------------------------------------------------------------------------- 1 | <% speech = item.data %> 2 | 3 | <%= speech['title'] %> 4 | 5 | 7 | Speech in Congress 8 | by 9 | <%= speaker_name speech %> 10 |

    11 |

    12 | "<%= speech_excerpt speech, interest %>" 13 |

    14 | ]]>
    -------------------------------------------------------------------------------- /subscriptions/views/speeches/_show.erb: -------------------------------------------------------------------------------- 1 | <% speech = item.data %> 2 | 3 |
    4 |
    5 |

    <%= speech['title'] %>

    6 |
    7 |

    8 | a speech in Congress by 9 | <%= speaker_name speech %>, on 10 | 13 |

    14 |
    15 |
    16 | 17 |
    18 | 19 |
    20 |
    Resources
    21 | 22 | Visit 23 | CapitolWords 24 | for more context. 25 | 26 |
    27 | 28 |
    29 |
    30 | Full Text 31 | 32 | Below, adapted from the Congressional Record. 33 | 34 |
    35 |
    36 |
    37 | <%= speech_speaking speech %> 38 |
    39 |
    40 | 41 |
    42 | 43 | -------------------------------------------------------------------------------- /subscriptions/views/state_bills/_data.erb: -------------------------------------------------------------------------------- 1 |

    2 | Information on state legislation comes from Open States, a Sunlight Foundation project that publishes data on state legislative activity for all 50 states, Puerto Rico and the District of Columbia. 3 |

    4 |

    5 | This data originates from the official websites of the 50 state legislatures, and is published at various times throughout the day. 6 |

    7 | 8 | <% if searching? %> 9 |

    10 | Note: Dates shown are the official last action date listed for the bill. This can sometimes be in the future, such as in the case of upcoming hearings, or "effective dates" for bills made law. 11 |

    12 | <% end %> -------------------------------------------------------------------------------- /subscriptions/views/state_bills/_email.erb: -------------------------------------------------------------------------------- 1 | <% 2 | # we have the user, interest, and item values available to us 3 | %> 4 | <% bill = item.data %> 5 | <%= email_header bill['bill_id'], url %> 6 | 7 | <%= email_subheader_div %> 8 | <%= state_name bill['state'] %> 9 | — 10 | <% if bill['action_dates'] and bill['action_dates']['last'] %> 11 | <% date = bill['action_dates']['last'] %> 12 | <% if date < Time.zone.now %> 13 | Last activity on 14 | <% else %> 15 | Upcoming activity on 16 | <% end %> 17 | <% else %> 18 | <% date = bill['created_at'] %> 19 | Information first discovered on 20 | <% end %> 21 | <%= just_date item.date %> 22 | 23 | 24 | <%= email_content_p %> 25 | <%= state_bill_highlight item, interest, inline: true %> 26 | -------------------------------------------------------------------------------- /subscriptions/views/state_bills/_filter.erb: -------------------------------------------------------------------------------- 1 |
  • 2 | 3 | 9 |
  • 10 | 11 |
  • 12 | 13 | 19 |
  • -------------------------------------------------------------------------------- /subscriptions/views/state_bills/_result.erb: -------------------------------------------------------------------------------- 1 | <% bill = item.data %> 2 | 3 |

    4 | <%= bill['bill_id'] %> 5 |

    6 |
    7 | 8 |
    9 | <%= state_name bill['state'] %> 10 | <% if bill['action_dates'] %> 11 | — 12 | 13 | <% if bill['action_dates']['signed'] %> 14 | Signed 15 | <% elsif bill['action_dates']['passed_lower'] and bill['action_dates']['passed_upper'] %> 16 | Passed both chambers 17 | <% elsif bill['action_dates']['passed_lower'] %> 18 | Passed lower chamber 19 | <% elsif bill['action_dates']['passed_upper'] %> 20 | Passed upper chamber 21 | <% else %> 22 | Introduced 23 | <% end %> 24 | 25 | <% end %> 26 |
    27 | 28 |

    29 | <%= state_bill_highlight item, interest %> 30 |

    -------------------------------------------------------------------------------- /subscriptions/views/state_bills/_rss.erb: -------------------------------------------------------------------------------- 1 | <% bill = item.data %> 2 | 3 | <%= bill['bill_id'] %> 4 | 5 | 7 | 8 | <%= state_name bill['state'] %> 9 | 10 |

    11 |

    12 | <%= state_bill_highlight item, interest %> 13 |

    14 | ]]>
    -------------------------------------------------------------------------------- /subscriptions/views/state_bills_activity/_email.erb: -------------------------------------------------------------------------------- 1 | <% bill = interest.data %> 2 | <% action = item.data %> 3 | 4 | <%= email_header just_date(item.date), url %> 5 | 6 | <%= email_content_p %> 7 | <%= action['action'] %> 8 | -------------------------------------------------------------------------------- /subscriptions/views/state_bills_activity/_result.erb: -------------------------------------------------------------------------------- 1 | <% bill = interest.data %> 2 | <% action = item.data %> 3 | 4 | 5 |

    6 | Action on <%= bill['bill_id'] %> 7 |

    8 |
    9 | 10 |
    11 | State bill activity 12 |
    13 | 14 |

    15 | <%= action['action'] %> 16 |

    -------------------------------------------------------------------------------- /subscriptions/views/state_bills_activity/_rss.erb: -------------------------------------------------------------------------------- 1 | <% action = item.data %> 2 | 3 | <%= interest_name item.interest %> 4 | 5 | 7 | New activity 8 | 9 | — 10 | <%= action['action'] %> 11 | ]]> -------------------------------------------------------------------------------- /subscriptions/views/state_bills_votes/_email.erb: -------------------------------------------------------------------------------- 1 | <% bill = interest.data %> 2 | <% vote = item.data %> 3 | 4 | <%= email_header vote['motion'], url %> 5 | 6 | <%= email_subheader_div %> 7 | <%= just_date(item.date) %> 8 | 9 | 10 | <%= email_content_p %> 11 | <%= state_vote_type vote %> vote, <%= state_vote_count vote %> 12 | 13 | -------------------------------------------------------------------------------- /subscriptions/views/state_bills_votes/_result.erb: -------------------------------------------------------------------------------- 1 | <% bill = interest.data %> 2 | <% vote = item.data %> 3 | 4 | 5 |

    6 | Vote on <%= bill['bill_id'] %> 7 |

    8 |
    9 | 10 |
    11 | State bill vote 12 |
    13 | 14 |

    15 | <%= vote['motion'] %>, <%= state_vote_count vote %> 16 |

    -------------------------------------------------------------------------------- /subscriptions/views/state_bills_votes/_rss.erb: -------------------------------------------------------------------------------- 1 | <% vote = item.data %> 2 | 3 | <%= interest_name item.interest %> 4 | 5 | 7 | <%= state_vote_type vote %> vote, <%= state_vote_count vote %> 8 | 9 | — 10 | <%= vote['motion'] %> 11 | ]]> -------------------------------------------------------------------------------- /subscriptions/views/state_legislators/_data.erb: -------------------------------------------------------------------------------- 1 |

    2 | Information on state legislation comes from Open States, a Sunlight Foundation project that publishes data on state legislative activity for all 50 states, Puerto Rico and the District of Columbia. 3 |

    4 |

    5 | This data originates from the official websites of the 50 state legislatures, and is published at various times throughout the day, depending on the state. 6 |

    -------------------------------------------------------------------------------- /subscriptions/views/state_legislators/_show.erb: -------------------------------------------------------------------------------- 1 | <% legislator = item.data %> 2 | 3 |
    4 |
    5 | Subscribe to bills sponsored by <%= legislator['full_name'] %>. 6 |
    7 | 8 | <%= follow_button item %> 9 | 10 |
    11 |
    12 | 13 |
    14 | 15 |
    16 |

    <%= legislator['full_name'] %>

    17 | 18 |
    19 | <%= legislator['full_name'] %> represents District 20 | <%= legislator['district'] %> in 21 | <%= state_name legislator['state'] %> 22 | <%# XXX: Add in state to this subtitle %> 23 |
    24 |
    25 | 26 |
    27 |
    28 |
    Resources
    29 | 34 |
    35 |
    36 | -------------------------------------------------------------------------------- /subscriptions/views/state_legislators_bills/_email.erb: -------------------------------------------------------------------------------- 1 | <% bill = item.data %> 2 | <%= email_header bill['bill_id'], url %> 3 | 4 | <%= email_subheader_div %> 5 | <%= state_name bill['state'] %> 6 | — 7 | <%= just_date item.date %> 8 | 9 | 10 | <%= email_content_p %> 11 | <%= state_bill_highlight item, interest, inline: true %> 12 | 13 | -------------------------------------------------------------------------------- /subscriptions/views/state_legislators_bills/_result.erb: -------------------------------------------------------------------------------- 1 | <% bill = item.data %> 2 | 3 |

    4 | <%= bill['bill_id'] %> 5 |

    6 |
    7 | 8 |
    9 | <%= state_name bill['state'] %> 10 | — 11 | <%= bill['title'] %> 12 |
    -------------------------------------------------------------------------------- /subscriptions/views/state_legislators_bills/_rss.erb: -------------------------------------------------------------------------------- 1 | <% bill = item.data %> 2 | 3 | <%= bill['bill_id'] %> 4 | 5 | 7 | 8 | <%= state_name bill['state'] %> 9 | 10 |

    11 |

    12 | <%= state_bill_title bill %> 13 |

    14 | ]]>
    -------------------------------------------------------------------------------- /tasks/assets.rake: -------------------------------------------------------------------------------- 1 | namespace :assets do 2 | 3 | desc "Synchronize assets to S3" 4 | task sync: :environment do 5 | 6 | begin 7 | # first, run through each asset and compress it using gzip 8 | Dir["public/assets/**/*.*"].each do |path| 9 | if File.extname(path) != ".gz" 10 | system "gzip -9 -c #{path} > #{path}.gz" 11 | end 12 | end 13 | 14 | # asset sync is configured to use the .gz version of a file if it exists, 15 | # and to upload it to the original non-.gz URL with the right headers 16 | AssetSync.sync 17 | rescue Exception => ex 18 | Admin.exception "assets:sync", ex 19 | puts "Error compressing and syncing assets, emailed report." 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /tasks/backups.rake: -------------------------------------------------------------------------------- 1 | require 'aws-sdk' 2 | 3 | namespace :backups do 4 | 5 | desc "Monitor S3 backup location" 6 | task check: :environment do 7 | begin 8 | 9 | if ENV['day'] 10 | day = Time.zone.parse(ENV['day']) 11 | else 12 | day = Time.zone.now 13 | end 14 | day = day.strftime("%Y%m%d") 15 | 16 | 17 | s3 = AWS::S3.new( 18 | access_key_id: Environment.config['backups']['access_key'], 19 | secret_access_key: Environment.config['backups']['secret_key'] 20 | ) 21 | bucket_name = Environment.config['backups']['bucket'] 22 | bucket = s3.buckets[bucket_name] 23 | path = Environment.config['backups']['path'] 24 | dir = bucket.objects.with_prefix(path) 25 | 26 | key = "#{path}/#{day}.tgz" 27 | 28 | puts "Hunting for s3://#{bucket_name}/#{key} ..." 29 | 30 | found = dir.find do |object| 31 | object.key == key 32 | end 33 | 34 | if found.nil? 35 | Admin.message "WARNING: No backup found for #{day}." 36 | elsif found.content_length == 0 37 | Admin.message "WARNING: 0-byte backup found for #{day}." 38 | else 39 | puts "\nNo problem, backup is fine: #{key}, #{found.content_length} bytes." 40 | end 41 | 42 | rescue Exception => ex 43 | Admin.exception "backups:check", ex 44 | puts "Error monitoring S3 backups, emailed report." 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /tasks/collection.rake: -------------------------------------------------------------------------------- 1 | namespace :collection do 2 | 3 | # Rename a collection. 4 | # 5 | # * Find the Tag, capture its interests in an array. 6 | # * Change the Tag's name field. 7 | # * Take previously captured interests, replace old name 8 | # with new one in "tags" field. 9 | task rename: :environment do 10 | 11 | unless (email = ENV['email']).present? and 12 | (collection_name = ENV['collection']).present? and 13 | (new_name = ENV['new_name']).present? and 14 | (user = User.where(email: email).first) and 15 | (collection = user.tags.where(name: collection_name).first) 16 | puts "Provide a valid 'email' and 'collection' name for that user." 17 | exit 18 | end 19 | 20 | interests = collection.interests.all.to_a 21 | collection.name = new_name.strip 22 | collection.save! 23 | interests.each do |interest| 24 | interest.tags.delete collection_name 25 | interest.tags << new_name 26 | interest.save! 27 | end 28 | 29 | puts "Renamed collection from \"#{collection_name}\" to \"#{new_name}\"." 30 | end 31 | 32 | # Copy one user's collection to another: 33 | # 34 | # * copy the Tag object itself 35 | # - keep public/private status 36 | # * copy all interests who have that tag (collection) 37 | # - *don't* copy over "notifications" or "tags" fields 38 | # (or "_id", or "created_at", or "updated_at") 39 | # - set "tags" field to [tag] 40 | # - generate subscriptions for each interest 41 | # - ensure each subscription is initialized 42 | task copy: :environment do 43 | 44 | unless (from_email = ENV['from']).present? and (to_email = ENV['to']).present? and 45 | (collection_name = ENV['collection']).present? and 46 | (from = User.where(email: from_email).first) and 47 | (to = User.where(email: to_email).first) and 48 | (collection = from.tags.where(name: collection_name).first) 49 | puts "Provide valid 'from' and 'to' user emails, and a 'collection' name." 50 | exit 51 | end 52 | 53 | # copy the collection 54 | attributes = collection.attributes.dup 55 | ["_id", "created_at", "updated_at"].each do |field| 56 | attributes.delete field 57 | end 58 | new_collection = to.tags.new attributes 59 | new_collection.save! 60 | puts "Saved collection \"#{collection_name}\"." 61 | 62 | # copy the collection's interests 63 | collection.interests.each do |interest| 64 | attributes = interest.attributes.dup 65 | ["tags", "notifications", "_id", "created_at", "updated_at"].each do |field| 66 | attributes.delete field 67 | end 68 | attributes["tags"] = [collection.name] 69 | new_interest = to.interests.new attributes 70 | new_interest.save! 71 | puts "Saved interest \"#{new_interest.in}\"." 72 | end 73 | 74 | puts "Did it work??" 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /tasks/crontab.rake: -------------------------------------------------------------------------------- 1 | namespace :crontab do 2 | desc "Set the crontab in place for this environment" 3 | task set: :environment do 4 | environment = ENV['environment'] 5 | current_path = ENV['current_path'] 6 | 7 | if environment.blank? or current_path.blank? 8 | Admin.message "No environment or current path given, emailing and exiting." 9 | next 10 | end 11 | 12 | if system("cat #{current_path}/config/cron/#{environment}/crontab | crontab") 13 | puts "Successfully overwrote crontab." 14 | else 15 | Admin.report Report.warning("Crontab", "Crontab overwriting failed on deploy.") 16 | puts "Unsuccessful in overwriting crontab, emailed report." 17 | end 18 | end 19 | 20 | desc "Disable/clear the crontab for this environment" 21 | task disable: :environment do 22 | environment = ENV['environment'] 23 | current_path = ENV['current_path'] 24 | 25 | if system("cat #{current_path}/config/cron/#{environment}/disabled | crontab") 26 | puts "Successfully switched cron to disabled mode." 27 | else 28 | Admin.report Report.warning("Crontab", "Somehow failed at disabling crontab.") 29 | puts "Unsuccessful (somehow) at disabling crontab, emailed report." 30 | end 31 | end 32 | 33 | desc "Warning to admin that the crontab is still disabled" 34 | task warn: :environment do 35 | Admin.report Report.warning("Crontab", "Just so you know: the crontab is still disabled.") 36 | puts "Warned administrator that the crontab is still disabled." 37 | end 38 | end -------------------------------------------------------------------------------- /tasks/deliver.rake: -------------------------------------------------------------------------------- 1 | namespace :deliver do 2 | 3 | desc "Custom delivery task" 4 | task :custom => :environment do 5 | interest_options = { 6 | "interest_type" => "search", 7 | "search_type" => {"$in" => ["all", "state_bills"]} 8 | } 9 | 10 | subject = "State bill alerts for 2013 so far" 11 | header = File.read("misc/header.htm") 12 | 13 | Deliveries::Manager.custom_email!( 14 | subject, header, 15 | interest_options 16 | ) 17 | end 18 | 19 | desc "Deliveries for a single daily email digest" 20 | task :email_daily => :environment do 21 | delivery_options = {"mechanism" => "email", "email_frequency" => "daily"} 22 | 23 | if ENV['email'] 24 | delivery_options["user_email"] = ENV['email'].strip 25 | end 26 | 27 | begin 28 | Deliveries::Manager.deliver! delivery_options 29 | rescue Exception => ex 30 | Admin.exception "deliver:email_daily", ex 31 | puts "Error during delivery, emailed report." 32 | end 33 | end 34 | 35 | desc "Deliveries of emails for whenever, per-interest" 36 | task :email_immediate => :environment do 37 | delivery_options = {"mechanism" => "email", "email_frequency" => "immediate"} 38 | 39 | if ENV['email'] 40 | delivery_options["user_email"] = ENV['email'].strip 41 | end 42 | 43 | begin 44 | Deliveries::Manager.deliver! delivery_options 45 | rescue Exception => ex 46 | Admin.exception "deliver:email_immediate", ex 47 | puts "Error during delivery, emailed report." 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /tasks/sitemap.rake: -------------------------------------------------------------------------------- 1 | # assumes usc already loaded, update the sitemap 2 | # saves a static file, using the production URL 3 | desc "Generate a sitemap." 4 | task :sitemap => :environment do 5 | begin 6 | require 'big_sitemap' 7 | 8 | include Helpers::Routing 9 | 10 | counts = { 11 | tags: 0, cites: 0, 12 | pages: 2 # assume / and /about work 13 | } 14 | 15 | # options: 16 | # debug: output extra info 17 | # no_ping: don't ping google or bing 18 | # only: only output certain types of info (usc, item types) 19 | 20 | debug = ENV['debug'] ? true : false 21 | ping = ENV['no_ping'] ? false : true 22 | only = ENV['only'].present? ? ENV['only'].split(',') : nil 23 | 24 | BigSitemap.generate( 25 | base_url: Environment.config['hostname'], 26 | document_root: "public/sitemap", 27 | url_path: "sitemap", 28 | ping_google: ping, 29 | ping_bing: ping) do 30 | 31 | # homepage! come back to me 32 | add "/", change_frequency: "daily" 33 | 34 | # about page, changes rarely 35 | add "/about", change_frequency: "monthly" 36 | 37 | # public tags 38 | Tag.where(public: true).each do |collection| 39 | counts[:tags] += 1 40 | path = Tag.collection_path collection.user, collection 41 | puts "[collection][#{collection.name}] Adding to sitemap..." if debug 42 | add path, change_frequency: "daily" 43 | end 44 | 45 | # map of US Code searches/landings 46 | if !only or (only and only.include?("usc")) 47 | Citation.where(citation_type: "usc").asc(:citation_id).each do |citation| 48 | counts[:cites] += 1 49 | standard = Search.cite_standard citation.attributes 50 | puts "[cite][#{standard}] Adding to sitemap..." if debug 51 | add "/search/all/#{URI.escape standard}", change_frequency: :daily 52 | end 53 | end 54 | 55 | # synced remote item landing pages 56 | frequencies = { 57 | bill: :weekly, 58 | state_bill: :weekly, 59 | speech: :monthly, 60 | regulation: :monthly, 61 | document: :monthly 62 | } 63 | 64 | item_types = frequencies.keys.sort 65 | if only #... 66 | item_types = item_types.select {|i| only.include? i.to_s} 67 | end 68 | 69 | item_types.each do |item_type| 70 | frequency = frequencies[item_type] 71 | 72 | counts[item_type] = 0 73 | Item.where(item_type: item_type.to_s).asc(:created_at).each do |item| 74 | counts[item_type] += 1 75 | url = item.path 76 | puts "[#{item_type}][#{item.item_id}] Adding to sitemap: #{url}" if debug 77 | add url, change_frequency: frequency 78 | end 79 | end 80 | 81 | end 82 | 83 | puts "Saved sitemaps." 84 | rescue Exception => ex 85 | Admin.exception 'sitemap', ex 86 | puts "Error generating sitemap, emailed report." 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /tasks/sync.rake: -------------------------------------------------------------------------------- 1 | desc "Proactively sync data from a syncable adapter" 2 | task sync: :environment do 3 | sync ENV['type'] 4 | end 5 | 6 | def sync(subscription_type) 7 | begin 8 | adapter = Subscription.adapter_for subscription_type 9 | start = Time.now 10 | 11 | # will mean something special to each adapter 12 | options = {since: ENV['since']} 13 | 14 | total = 0 15 | page = ENV['start'] ? ENV['start'].to_i : 1 16 | 17 | bad_pages = [] 18 | 19 | while true # oh boy 20 | items = Subscriptions::Manager.sync subscription_type, options.merge(page: page, start: start) 21 | 22 | unless items.is_a?(Array) 23 | bad_pages << page 24 | page += 1 25 | next 26 | end 27 | 28 | items.each {|item| Item.from_seen! item} 29 | 30 | total += items.size 31 | break if items.size < adapter::MAX_PER_PAGE 32 | 33 | # emergency brake, I hate while-true's 34 | if (Time.now - start) > 600.minutes 35 | puts "Emergency brake!" 36 | break 37 | end 38 | 39 | page += 1 40 | end 41 | 42 | if bad_pages.any? 43 | Admin.report Report.failure("sync:#{subscription_type}", "Error fetching pages", {options: options, bad_pages: bad_pages}) 44 | end 45 | 46 | # not usually needed 47 | # Admin.report Report.success("sync:#{subscription_type}", "Synced #{total} #{subscription_type}.", {duration: (Time.now - start), total: total, options: options, subscription_type: subscription_type}) 48 | rescue Exception => ex 49 | Admin.exception "sync:#{subscription_type}", ex, {died_at: page, duration: (Time.now - start), options: options, subscription_type: subscription_type} 50 | end 51 | end -------------------------------------------------------------------------------- /tasks/users.rake: -------------------------------------------------------------------------------- 1 | namespace :users do 2 | 3 | desc "Change a user's email" 4 | task update_email: :environment do 5 | email = ENV['email'] 6 | user = User.where(email: email).first if email.present? 7 | 8 | new_email = ENV['new_email'] 9 | 10 | unless user and new_email.present? 11 | puts "Specify valid 'email' and 'new_email' parameters." 12 | exit 13 | end 14 | 15 | user.email = new_email 16 | user.save! 17 | puts "Updated user." 18 | 19 | user.deliveries.update_all user_email: new_email 20 | puts "Updated #{user.deliveries.count} pending deliveries." 21 | end 22 | 23 | desc "Turn on/off all notifications for a user. Defaults to turning off" 24 | task change_notifications: :environment do 25 | if not ENV['email'].present? 26 | puts "Must provide 'email' environmental argument. Optionally include 'notifications' and 'announcements'" 27 | puts "Example: rake change_notifications email=user@example.com notifications=none announcements=false" 28 | elsif not ['none','email_daily','email_immediate'].include?(ENV['notifications']) 29 | puts "Notifications argument must be in [none, email_daily, email_immediate]" 30 | else 31 | email = ENV['email'] 32 | user = User.where(email: email).first if email.present? 33 | user.notifications = ENV['notifications'].present? ? ENV['notifications'] : 'none' 34 | if ENV['announcements'].present? 35 | bool = ENV['announcements'] == 'false' ? false : true 36 | user.announcements = bool 37 | user.sunlight_announcements = bool 38 | else 39 | user.announcements = false 40 | user.sunlight_announcements = false 41 | end 42 | 43 | user.save 44 | end 45 | end 46 | 47 | end 48 | -------------------------------------------------------------------------------- /tasks/warnings.rake: -------------------------------------------------------------------------------- 1 | # aggregated daily warnings - kept in the database through that day, 2 | # then delivered and cleared. 3 | 4 | namespace :warnings do 5 | 6 | desc "New users" 7 | task new_users: :environment do 8 | # send an email with any new users for the day. 9 | # break it up by service. 10 | 11 | body = "" 12 | 13 | day = ENV['day'] || 1.day.ago.strftime("%Y-%m-%d") 14 | ending = (Time.zone.parse(day) + 1.day).strftime "%Y-%m-%d" 15 | 16 | service = ENV['service'] || nil 17 | display_service = service || "scout" 18 | 19 | criteria = Event.where(type: "new-user", service: service).for_time(day, ending) 20 | 21 | if criteria.any? 22 | body << "[#{display_service}]\n\n" 23 | criteria.each do |event| 24 | body << "[#{event.created_at}] #{event.email}" 25 | body << " (unconfirmed)" if !event['confirmed'] 26 | body << "\n" 27 | end 28 | 29 | Admin.sensitive "[#{display_service}] New users for #{day}", body 30 | else 31 | puts "[#{display_service}] No new users for #{day} to deliver." 32 | end 33 | end 34 | 35 | desc "Backfill warnings" 36 | task backfills: :environment do 37 | # accumulate a full example of each, and a count of more 38 | 39 | header = "" 40 | 41 | backfills = [] 42 | 43 | Event.where(type: "backfills").each do |event| 44 | backfills << { 45 | example: event.backfills.first, 46 | count: event.backfills.size, 47 | subscription_type: event.subscription_type, 48 | interest_in: event.interest_in 49 | } 50 | 51 | header << "[#{event.subscription_type}][#{event.interest_in}] #{event.backfills.size}" 52 | header << "\n" 53 | end 54 | 55 | if backfills.any? 56 | Admin.report Report.warning("Check", "#{backfills.size} sets of backfills today, not delivered.", header: header, backfills: backfills) 57 | Event.where(type: "backfills").delete_all 58 | else 59 | puts "No backfill warnings to deliver today." 60 | end 61 | end 62 | 63 | task courtlistener: :environment do 64 | # accumulate a full example of each, and a count of more 65 | warnings = [] 66 | 67 | Event.where(type: "courtlistener").each do |event| 68 | warnings << { 69 | example: event.warnings.first, 70 | count: event.warnings.size, 71 | subscription_type: event.subscription_type, 72 | interest_in: event.interest_in 73 | } 74 | end 75 | 76 | if warnings.any? 77 | Admin.report Report.warning("Check", "#{warnings.size} CL warnings today, not delivered.", warnings: warnings) 78 | Event.where(type: "courtlistener").delete_all 79 | else 80 | puts "No CourtListener warnings to deliver today." 81 | end 82 | end 83 | 84 | end -------------------------------------------------------------------------------- /test/factories.rb: -------------------------------------------------------------------------------- 1 | require 'factory_girl' 2 | 3 | FactoryGirl.define do 4 | 5 | factory :user do 6 | sequence(:email) {|n| "fake#{n}@example.com"} 7 | password "test" 8 | password_confirmation "test" 9 | confirmed true # in tests, default it to true for convenience 10 | 11 | factory :service_user do 12 | service "service1" 13 | confirmed true 14 | notifications "email_daily" 15 | should_change_password false 16 | end 17 | end 18 | 19 | factory :tag do 20 | user 21 | sequence(:name) {|n| "name#{n}"} 22 | 23 | factory :public_tag do 24 | self.public true 25 | end 26 | end 27 | 28 | end -------------------------------------------------------------------------------- /test/fixtures/services.yml: -------------------------------------------------------------------------------- 1 | # services Scout will deliver alerts for 2 | 3 | service1: 4 | name: Service 1 5 | secret_key: secretkey1 6 | 7 | service2: 8 | name: Service 2 9 | secret_key: secretkey2 -------------------------------------------------------------------------------- /test/fixtures/state_bills/conscience/initialize.json: -------------------------------------------------------------------------------- 1 | [ 2 | 3 | ] -------------------------------------------------------------------------------- /test/fixtures/state_bills/environment/initialize.json: -------------------------------------------------------------------------------- 1 | [ 2 | 3 | ] -------------------------------------------------------------------------------- /test/fixtures/state_bills/science/initialize.json: -------------------------------------------------------------------------------- 1 | [ 2 | 3 | ] -------------------------------------------------------------------------------- /test/functional/feeds_test.rb: -------------------------------------------------------------------------------- 1 | require './test/test_helper' 2 | 3 | class FeedsTest < Test::Unit::TestCase 4 | include Rack::Test::Methods 5 | include TestHelper::Methods 6 | include FactoryGirl::Syntax::Methods 7 | 8 | def test_interest_feed 9 | user = create :user 10 | query = "transparency accountability" 11 | interest = search_interest! user, "all", query, "advanced" 12 | 13 | get "/interest/#{interest.id}.rss" 14 | assert_response 200 15 | end 16 | 17 | end -------------------------------------------------------------------------------- /test/helpers/routing_test.rb: -------------------------------------------------------------------------------- 1 | require './test/test_helper' 2 | 3 | class RoutingTest < Test::Unit::TestCase 4 | include Rack::Test::Methods 5 | include TestHelper::Methods 6 | include FactoryGirl::Syntax::Methods 7 | 8 | class Anonymous; extend Helpers::Routing; end 9 | def routing; Anonymous; end 10 | 11 | 12 | def test_interest_path 13 | user = create :user 14 | 15 | query = "yes and no" # has spaces 16 | 17 | all = search_interest! user, "all", query 18 | assert_equal "/search/all/#{URI.encode query}", routing.interest_path(all) 19 | 20 | single_search = search_interest! user, "federal_bills", query 21 | assert_equal "/search/federal_bills/#{URI.encode query}", routing.interest_path(single_search) 22 | 23 | search_with_data = search_interest! user, "federal_bills", query, "simple", {'stage' => "enacted"} 24 | assert_equal "/search/federal_bills/#{URI.encode query}?federal_bills[stage]=enacted", routing.interest_path(search_with_data) 25 | 26 | advanced_search = search_interest! user, "federal_bills", query, "advanced" 27 | assert_equal "/search/federal_bills/#{URI.encode query}/advanced", routing.interest_path(advanced_search) 28 | 29 | basic_item = item_interest user, "hr4192-112", "bill" 30 | assert_equal "due-process-and-military-detention-amendments-act", SeenItem.generate_slug(basic_item.item_type, basic_item.data) 31 | assert_equal "/item/bill/hr4192-112/due-process-and-military-detention-amendments-act", routing.interest_path(basic_item) 32 | end 33 | 34 | end -------------------------------------------------------------------------------- /test/helpers/subscriptions_test.rb: -------------------------------------------------------------------------------- 1 | require './test/test_helper' 2 | 3 | class SubscriptionsTest < Test::Unit::TestCase 4 | include Rack::Test::Methods 5 | include TestHelper::Methods 6 | include FactoryGirl::Syntax::Methods 7 | 8 | class Anonymous; extend Helpers::Subscriptions; end 9 | def helper; Anonymous; end 10 | 11 | 12 | def test_excerpt 13 | 14 | end 15 | 16 | end -------------------------------------------------------------------------------- /test/unit/map_test.rb: -------------------------------------------------------------------------------- 1 | require './test/test_helper' 2 | 3 | class MapTest < Test::Unit::TestCase 4 | include Rack::Test::Methods 5 | include TestHelper::Methods 6 | include FactoryGirl::Syntax::Methods 7 | 8 | # mapping from a given search adapter to the type of item it searches over 9 | def test_search_adapters_to_item_types 10 | assert search_adapters.is_a?(Hash) 11 | assert_equal search_adapters['federal_bills'], 'bill' 12 | assert_equal search_adapters['state_bills'], 'state_bill' 13 | end 14 | 15 | # mapping from a given item adapter to the type of item its data is focused on 16 | def test_item_adapters_to_item_types 17 | assert item_adapters.is_a?(Hash) 18 | assert_equal item_adapters['federal_bills_activity'], 'bill' 19 | assert_equal item_adapters['state_bills_votes'], 'state_bill' 20 | end 21 | 22 | # mapping from a given item type to the adapters that follow it 23 | def test_item_types_to_item_adapters 24 | assert item_types.is_a?(Hash) 25 | ['bill', 'state_bill'].each do |item_type| 26 | assert_equal item_types[item_type]['subscriptions'].sort, item_adapters.keys.select {|adapter| item_adapters[adapter] == item_type}.sort 27 | end 28 | end 29 | 30 | # mapping from a given item type to the adapter that searches over it 31 | def test_item_types_to_search_adapter 32 | assert item_types.is_a?(Hash) 33 | ['bill', 'state_bill', 'speech', 'regulation', 'document', 'state_legislator'].each do |item_type| 34 | assert_equal item_types[item_type]['adapter'], search_adapters.keys.find {|adapter| search_adapters[adapter] == item_type} 35 | end 36 | end 37 | 38 | end -------------------------------------------------------------------------------- /test/unit/search_test.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require './test/test_helper' 4 | 5 | # test out search helper functions (extractions) 6 | 7 | class SearchTest < Test::Unit::TestCase 8 | include Rack::Test::Methods 9 | include TestHelper::Methods 10 | include FactoryGirl::Syntax::Methods 11 | 12 | def test_extract_usc 13 | assert_equal "usc/5/552", Search.usc_check("5 U.S.C. 552") 14 | assert_equal "usc/5/552", Search.usc_check("5 USC 552") 15 | assert_equal "usc/5/552", Search.usc_check("5 U.S.C. § 552") 16 | assert_equal "usc/5/552", Search.usc_check(" 5 U.S.C. 552 ") 17 | 18 | # can't have another term next to it 19 | assert_equal nil, Search.usc_check("science 5 U.S.C. 552") 20 | assert_equal nil, Search.usc_check("5 U.S.C. 552 technology") 21 | assert_equal nil, Search.usc_check("5 U.S.C. john 552") 22 | 23 | # subsections 24 | assert_equal "usc/6/123/bb/102", Search.usc_check("6 USC 123(bb)(102)") 25 | assert_equal "usc/6/123/11111", Search.usc_check("6 USC 123(11111)") 26 | assert_equal "usc/50/404o-1/a", Search.usc_check("50 U.S.C. 404o-1(a)") 27 | 28 | 29 | # pattern 2 (section X of title Y) 30 | assert_equal "usc/5/552", Search.usc_check("section 552 of title 5") 31 | assert_equal "usc/5/552", Search.usc_check(" section 552 of title 5 ") 32 | 33 | # can't have another term next to it 34 | assert_equal nil, Search.usc_check("science section 552 of title 5") 35 | assert_equal nil, Search.usc_check("section 552 of title 5 technology") 36 | assert_equal nil, Search.usc_check("section john 552 of title 5") 37 | 38 | # subsections 39 | assert_equal "usc/6/123/bb/102", Search.usc_check("section 123(bb)(102) of title 6") 40 | assert_equal "usc/6/123/11111", Search.usc_check("section 123(11111) of title 6") 41 | assert_equal "usc/50/404o-1/a", Search.usc_check("section 404o-1(a) of title 50") 42 | end 43 | 44 | def test_state_bill_detect 45 | assert_equal "SB 112", Search.state_bill_for("SB 112") 46 | assert_equal "SB 112", Search.state_bill_for(" SB 112 ") 47 | assert_equal "SB 112", Search.state_bill_for("S.B. 112") 48 | assert_equal "SB 112", Search.state_bill_for("S.B 112") 49 | assert_equal "SB 112", Search.state_bill_for("SB. 112") 50 | assert_equal "HB 13-1043", Search.state_bill_for("HB. 13-1043") 51 | end 52 | 53 | def test_federal_bill_detect 54 | assert_equal ["hr", "3590"], Search.federal_bill_for("H.R. 3590") 55 | assert_equal ["hr", "3590"], Search.federal_bill_for("HR 3590") 56 | assert_equal ["hr", "3590"], Search.federal_bill_for("hr3590") 57 | assert_equal ["hres", "49"], Search.federal_bill_for(" H.res 49 ") 58 | assert_equal ["sconres", "1"], Search.federal_bill_for("sconres 1") 59 | assert_equal ["sconres", "1"], Search.federal_bill_for("scres1") 60 | assert_equal ["s", "74"], Search.federal_bill_for("s 74") 61 | end 62 | 63 | end -------------------------------------------------------------------------------- /test/unit/tag_test.rb: -------------------------------------------------------------------------------- 1 | require './test/test_helper' 2 | 3 | class TagTest < Test::Unit::TestCase 4 | include Rack::Test::Methods 5 | include TestHelper::Methods 6 | include FactoryGirl::Syntax::Methods 7 | 8 | def test_new_tags 9 | user = create :user 10 | interest = search_interest! user 11 | 12 | assert_equal [], interest.tags 13 | 14 | interest.new_tags = "a, b" 15 | assert_equal ["a", "b"], interest.tags 16 | 17 | interest.new_tags = " a .. , b " 18 | assert_equal ["a", "b"], interest.tags 19 | 20 | interest.new_tags = "a big one , with weird spaces and CAPITAL LETTERS ," 21 | assert_equal ["a big one", "with weird spaces and capital letters"], interest.tags 22 | 23 | interest.new_tags = "\"with quotes\", 'and single quotes'" 24 | assert_equal ["with quotes", "and single quotes"], interest.tags 25 | 26 | interest.new_tags = "now with @#%^&- special characters, and even [] brackets" 27 | assert_equal ["now with special characters", "and even brackets"], interest.tags 28 | end 29 | 30 | end -------------------------------------------------------------------------------- /test/unit/user_test.rb: -------------------------------------------------------------------------------- 1 | require './test/test_helper' 2 | 3 | class UserTest < Test::Unit::TestCase 4 | include Rack::Test::Methods 5 | include TestHelper::Methods 6 | include FactoryGirl::Syntax::Methods 7 | 8 | # A test class that intentionally should raise exceptions on save, 9 | # because it mutates an email during before_save. 10 | class TestUser < User 11 | # should raise exception 12 | before_save :mutate! 13 | def mutate!; email.upcase!; end 14 | end 15 | 16 | def test_emails_cannot_mutate_before_validation 17 | email = "Testing@example.com" 18 | user = TestUser.new email: email 19 | 20 | assert_raise_with_message(RuntimeError, "can't modify frozen String") do 21 | user.save 22 | end 23 | 24 | # but it's okay in a non-saving circumstance 25 | user = User.new email: email 26 | assert_nothing_raised do 27 | user.save 28 | user.email.upcase! 29 | end 30 | end 31 | 32 | def test_mass_assignment 33 | user = create :user 34 | 35 | not_email = "not@example.com" 36 | 37 | assert user.confirmed? 38 | assert_not_equal not_email, user.email 39 | 40 | user.attributes = { 41 | email: not_email, 42 | confirmed: false 43 | } 44 | 45 | user.save! 46 | user.reload 47 | 48 | assert_equal not_email, user.email 49 | assert user.confirmed? 50 | end 51 | 52 | end -------------------------------------------------------------------------------- /unicorn.rb.example: -------------------------------------------------------------------------------- 1 | # See http://unicorn.bogomips.org/Unicorn/Configurator.html for complete 2 | # documentation. 3 | 4 | worker_processes 2 # this should be >= nr_cpus 5 | pid "/path/to/unicorn.pid" 6 | stderr_path "/path/to/unicorn.log" 7 | stdout_path "/path/to/unicorn.log" 8 | 9 | working_directory "/path/to/current" -------------------------------------------------------------------------------- /vendor/cache/activemodel-3.2.17.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/activemodel-3.2.17.gem -------------------------------------------------------------------------------- /vendor/cache/activesupport-3.2.17.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/activesupport-3.2.17.gem -------------------------------------------------------------------------------- /vendor/cache/addressable-2.3.6.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/addressable-2.3.6.gem -------------------------------------------------------------------------------- /vendor/cache/asset_sync-1.0.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/asset_sync-1.0.0.gem -------------------------------------------------------------------------------- /vendor/cache/aws-sdk-1.38.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/aws-sdk-1.38.0.gem -------------------------------------------------------------------------------- /vendor/cache/backports-3.6.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/backports-3.6.0.gem -------------------------------------------------------------------------------- /vendor/cache/bcrypt-3.1.7.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/bcrypt-3.1.7.gem -------------------------------------------------------------------------------- /vendor/cache/big_sitemap-1.1.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/big_sitemap-1.1.0.gem -------------------------------------------------------------------------------- /vendor/cache/builder-3.0.4.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/builder-3.0.4.gem -------------------------------------------------------------------------------- /vendor/cache/climate_control-0.0.3.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/climate_control-0.0.3.gem -------------------------------------------------------------------------------- /vendor/cache/cocaine-0.5.4.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/cocaine-0.5.4.gem -------------------------------------------------------------------------------- /vendor/cache/curb-0.8.5.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/curb-0.8.5.gem -------------------------------------------------------------------------------- /vendor/cache/debugger-ruby_core_source-1.3.2.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/debugger-ruby_core_source-1.3.2.gem -------------------------------------------------------------------------------- /vendor/cache/escape_utils-1.0.1.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/escape_utils-1.0.1.gem -------------------------------------------------------------------------------- /vendor/cache/excon-0.32.1.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/excon-0.32.1.gem -------------------------------------------------------------------------------- /vendor/cache/factory_girl-4.4.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/factory_girl-4.4.0.gem -------------------------------------------------------------------------------- /vendor/cache/feedbag-0.9.2.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/feedbag-0.9.2.gem -------------------------------------------------------------------------------- /vendor/cache/feedjira-1.1.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/feedjira-1.1.0.gem -------------------------------------------------------------------------------- /vendor/cache/fog-1.21.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/fog-1.21.0.gem -------------------------------------------------------------------------------- /vendor/cache/fog-brightbox-0.0.1.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/fog-brightbox-0.0.1.gem -------------------------------------------------------------------------------- /vendor/cache/fog-core-1.21.1.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/fog-core-1.21.1.gem -------------------------------------------------------------------------------- /vendor/cache/fog-json-1.0.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/fog-json-1.0.0.gem -------------------------------------------------------------------------------- /vendor/cache/formatador-0.2.4.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/formatador-0.2.4.gem -------------------------------------------------------------------------------- /vendor/cache/gman-2.0.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/gman-2.0.0.gem -------------------------------------------------------------------------------- /vendor/cache/hpricot-0.8.6.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/hpricot-0.8.6.gem -------------------------------------------------------------------------------- /vendor/cache/http_router-0.11.1.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/http_router-0.11.1.gem -------------------------------------------------------------------------------- /vendor/cache/i18n-0.6.9.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/i18n-0.6.9.gem -------------------------------------------------------------------------------- /vendor/cache/json-1.8.1.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/json-1.8.1.gem -------------------------------------------------------------------------------- /vendor/cache/kgio-2.9.2.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/kgio-2.9.2.gem -------------------------------------------------------------------------------- /vendor/cache/loofah-1.2.1.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/loofah-1.2.1.gem -------------------------------------------------------------------------------- /vendor/cache/mail-2.2.7.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/mail-2.2.7.gem -------------------------------------------------------------------------------- /vendor/cache/mime-types-2.2.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/mime-types-2.2.gem -------------------------------------------------------------------------------- /vendor/cache/mini_portile-0.5.3.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/mini_portile-0.5.3.gem -------------------------------------------------------------------------------- /vendor/cache/mongoid-3.1.6.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/mongoid-3.1.6.gem -------------------------------------------------------------------------------- /vendor/cache/mongoid-paperclip-0.0.9.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/mongoid-paperclip-0.0.9.gem -------------------------------------------------------------------------------- /vendor/cache/moped-1.5.2.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/moped-1.5.2.gem -------------------------------------------------------------------------------- /vendor/cache/multi_json-1.9.2.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/multi_json-1.9.2.gem -------------------------------------------------------------------------------- /vendor/cache/multipart-post-2.0.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/multipart-post-2.0.0.gem -------------------------------------------------------------------------------- /vendor/cache/net-scp-1.1.2.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/net-scp-1.1.2.gem -------------------------------------------------------------------------------- /vendor/cache/net-ssh-2.8.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/net-ssh-2.8.0.gem -------------------------------------------------------------------------------- /vendor/cache/nokogiri-1.6.1.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/nokogiri-1.6.1.gem -------------------------------------------------------------------------------- /vendor/cache/oj-2.7.1.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/oj-2.7.1.gem -------------------------------------------------------------------------------- /vendor/cache/origin-1.1.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/origin-1.1.0.gem -------------------------------------------------------------------------------- /vendor/cache/padrino-core-0.12.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/padrino-core-0.12.0.gem -------------------------------------------------------------------------------- /vendor/cache/padrino-helpers-0.12.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/padrino-helpers-0.12.0.gem -------------------------------------------------------------------------------- /vendor/cache/paint-0.8.7.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/paint-0.8.7.gem -------------------------------------------------------------------------------- /vendor/cache/paperclip-4.1.1.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/paperclip-4.1.1.gem -------------------------------------------------------------------------------- /vendor/cache/polyglot-0.3.4.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/polyglot-0.3.4.gem -------------------------------------------------------------------------------- /vendor/cache/pony-1.8.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/pony-1.8.gem -------------------------------------------------------------------------------- /vendor/cache/postmark-1.1.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/postmark-1.1.0.gem -------------------------------------------------------------------------------- /vendor/cache/public_suffix-1.4.2.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/public_suffix-1.4.2.gem -------------------------------------------------------------------------------- /vendor/cache/rack-1.5.2.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/rack-1.5.2.gem -------------------------------------------------------------------------------- /vendor/cache/rack-lineprof-0.0.2.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/rack-lineprof-0.0.2.gem -------------------------------------------------------------------------------- /vendor/cache/rack-protection-1.5.2.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/rack-protection-1.5.2.gem -------------------------------------------------------------------------------- /vendor/cache/rack-ssl-1.4.1.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/rack-ssl-1.4.1.gem -------------------------------------------------------------------------------- /vendor/cache/rack-test-0.6.2.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/rack-test-0.6.2.gem -------------------------------------------------------------------------------- /vendor/cache/rainbow-2.0.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/rainbow-2.0.0.gem -------------------------------------------------------------------------------- /vendor/cache/raindrops-0.13.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/raindrops-0.13.0.gem -------------------------------------------------------------------------------- /vendor/cache/rake-10.3.1.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/rake-10.3.1.gem -------------------------------------------------------------------------------- /vendor/cache/rblineprof-0.3.6.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/rblineprof-0.3.6.gem -------------------------------------------------------------------------------- /vendor/cache/rinku-1.7.3.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/rinku-1.7.3.gem -------------------------------------------------------------------------------- /vendor/cache/rspec-mocks-2.14.6.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/rspec-mocks-2.14.6.gem -------------------------------------------------------------------------------- /vendor/cache/ruby-hmac-0.4.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/ruby-hmac-0.4.0.gem -------------------------------------------------------------------------------- /vendor/cache/safe_yaml-1.0.1.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/safe_yaml-1.0.1.gem -------------------------------------------------------------------------------- /vendor/cache/sanitize-2.1.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/sanitize-2.1.0.gem -------------------------------------------------------------------------------- /vendor/cache/sax-machine-0.2.1.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/sax-machine-0.2.1.gem -------------------------------------------------------------------------------- /vendor/cache/sinatra-1.4.4.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/sinatra-1.4.4.gem -------------------------------------------------------------------------------- /vendor/cache/sinatra-contrib-1.4.2.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/sinatra-contrib-1.4.2.gem -------------------------------------------------------------------------------- /vendor/cache/sinatra-cross_origin-0.3.2.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/sinatra-cross_origin-0.3.2.gem -------------------------------------------------------------------------------- /vendor/cache/sinatra-flash-0.3.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/sinatra-flash-0.3.0.gem -------------------------------------------------------------------------------- /vendor/cache/slack-notifier-0.4.1.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/slack-notifier-0.4.1.gem -------------------------------------------------------------------------------- /vendor/cache/swot-0.3.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/swot-0.3.0.gem -------------------------------------------------------------------------------- /vendor/cache/term-ansicolor-1.3.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/term-ansicolor-1.3.0.gem -------------------------------------------------------------------------------- /vendor/cache/thor-0.17.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/thor-0.17.0.gem -------------------------------------------------------------------------------- /vendor/cache/tilt-1.4.1.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/tilt-1.4.1.gem -------------------------------------------------------------------------------- /vendor/cache/timecop-0.7.1.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/timecop-0.7.1.gem -------------------------------------------------------------------------------- /vendor/cache/tins-1.1.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/tins-1.1.0.gem -------------------------------------------------------------------------------- /vendor/cache/treetop-1.5.3.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/treetop-1.5.3.gem -------------------------------------------------------------------------------- /vendor/cache/tzinfo-0.3.39.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/tzinfo-0.3.39.gem -------------------------------------------------------------------------------- /vendor/cache/unf-0.1.3.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/unf-0.1.3.gem -------------------------------------------------------------------------------- /vendor/cache/unf_ext-0.0.6.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/unf_ext-0.0.6.gem -------------------------------------------------------------------------------- /vendor/cache/unicorn-4.8.2.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/unicorn-4.8.2.gem -------------------------------------------------------------------------------- /vendor/cache/url_mount-0.2.1.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/url_mount-0.2.1.gem -------------------------------------------------------------------------------- /vendor/cache/uuidtools-2.1.4.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/uuidtools-2.1.4.gem -------------------------------------------------------------------------------- /vendor/cache/wirb-1.0.3.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sunlightlabs/scout/5ab417be53d26ce9d84144013cf671b63ea009bd/vendor/cache/wirb-1.0.3.gem --------------------------------------------------------------------------------