├── .dockerignore ├── .editorconfig ├── .gitignore ├── .hound.yml ├── .rubocop.yml ├── .travis.yml ├── Dockerfile ├── Gemfile ├── Gemfile.lock ├── LICENSE.md ├── Procfile ├── README.md ├── Rakefile ├── app ├── api │ └── oparl │ │ ├── api.rb │ │ ├── current.rb │ │ ├── entities │ │ ├── body.rb │ │ ├── file.rb │ │ ├── legislative_term.rb │ │ ├── organization.rb │ │ ├── paper.rb │ │ ├── person.rb │ │ ├── system.rb │ │ └── text_file.rb │ │ ├── filter.rb │ │ ├── loggers.rb │ │ ├── pagination.rb │ │ └── routes.rb ├── assets │ ├── fonts │ │ └── source-sans-pro-v12-latin │ │ │ ├── source-sans-pro-v12-latin-600.eot │ │ │ ├── source-sans-pro-v12-latin-600.svg │ │ │ ├── source-sans-pro-v12-latin-600.ttf │ │ │ ├── source-sans-pro-v12-latin-600.woff │ │ │ ├── source-sans-pro-v12-latin-600.woff2 │ │ │ ├── source-sans-pro-v12-latin-700.eot │ │ │ ├── source-sans-pro-v12-latin-700.svg │ │ │ ├── source-sans-pro-v12-latin-700.ttf │ │ │ ├── source-sans-pro-v12-latin-700.woff │ │ │ ├── source-sans-pro-v12-latin-700.woff2 │ │ │ ├── source-sans-pro-v12-latin-italic.eot │ │ │ ├── source-sans-pro-v12-latin-italic.svg │ │ │ ├── source-sans-pro-v12-latin-italic.ttf │ │ │ ├── source-sans-pro-v12-latin-italic.woff │ │ │ ├── source-sans-pro-v12-latin-italic.woff2 │ │ │ ├── source-sans-pro-v12-latin-regular.eot │ │ │ ├── source-sans-pro-v12-latin-regular.svg │ │ │ ├── source-sans-pro-v12-latin-regular.ttf │ │ │ ├── source-sans-pro-v12-latin-regular.woff │ │ │ └── source-sans-pro-v12-latin-regular.woff2 │ ├── images │ │ ├── .keep │ │ ├── LICENSE.md │ │ ├── feed-icon.svg │ │ ├── hexmap.svg │ │ ├── icon-cross.svg │ │ ├── icon-email.svg │ │ ├── icon-tick.svg │ │ ├── kleineanfragen-viewer.svg │ │ ├── kleineanfragen.svg │ │ ├── kleineanfragen@2x.png │ │ ├── okfn-logo.svg │ │ └── papers.png │ ├── javascripts │ │ ├── application.js │ │ ├── enhance.js │ │ ├── font.js │ │ ├── paper.js │ │ ├── pdfjs-header.js.erb │ │ ├── polyfills.js │ │ ├── search.js │ │ ├── site-index.js │ │ ├── tracking.js │ │ ├── turbolinks_loaded.js │ │ └── viewer.js │ └── stylesheets │ │ ├── _bootstrap.scss │ │ ├── application.css.scss │ │ ├── email.css.scss │ │ └── viewer.css.erb ├── controllers │ ├── application_controller.rb │ ├── body_controller.rb │ ├── concerns │ │ └── .keep │ ├── info_controller.rb │ ├── legislative_term_controller.rb │ ├── metrics_controller.rb │ ├── ministry_controller.rb │ ├── organization_controller.rb │ ├── paper_controller.rb │ ├── review_controller.rb │ ├── scraper_results_controller.rb │ ├── search_controller.rb │ └── site_controller.rb ├── extractors │ ├── baden_wuerttemberg_pdf_extractor.rb │ ├── bayern_pdf_extractor.rb │ ├── berlin_pdf_extractor.rb │ ├── brandenburg_pdf_extractor.rb │ ├── bremen_pdf_extractor.rb │ ├── bundestag_pdf_extractor.rb │ ├── hamburg_pdf_has_answer_extractor.rb │ ├── hessen_pdf_extractor.rb │ ├── meck_pomm_pdf_extractor.rb │ ├── mv_pdf_has_answer_extractor.rb │ ├── name_party_extractor.rb │ ├── nordrhein_westfalen_pdf_extractor.rb │ ├── pdf_extractor.rb │ ├── rheinland_pfalz_pdf_extractor.rb │ ├── saarland_pdf_extractor.rb │ ├── sachsen_pdf_extractor.rb │ ├── schleswig_holstein_pdf_extractor.rb │ └── thueringen_pdf_extractor.rb ├── helpers │ ├── application_helper.rb │ ├── body_helper.rb │ ├── legislative_term_helper.rb │ ├── ministry_helper.rb │ ├── organization_helper.rb │ ├── review_helper.rb │ ├── scraper_results_helper.rb │ ├── search_helper.rb │ └── site_helper.rb ├── jobs │ ├── application_job.rb │ ├── contains_classified_information_job.rb │ ├── contains_table_job.rb │ ├── count_page_numbers_job.rb │ ├── determine_paper_type_job.rb │ ├── extract_answerers_job.rb │ ├── extract_last_modified_from_paper_job.rb │ ├── extract_originators_job.rb │ ├── extract_related_papers_job.rb │ ├── extract_text_from_paper_job.rb │ ├── fill_paper_pdf_last_modified_job.rb │ ├── import_new_papers_job.rb │ ├── import_paper_job.rb │ ├── instant_import_new_papers_job.rb │ ├── load_paper_details_job.rb │ ├── notify_push_hub_body_feed_job.rb │ ├── paper_job.rb │ ├── reimport_hessen_papers_pdf_job.rb │ ├── reimport_missing_pdf_job.rb │ ├── reimport_papers_pdf_job.rb │ ├── store_paper_pdf_job.rb │ └── thumbnail_first_page_job.rb ├── mailers │ ├── .keep │ └── application_mailer.rb ├── models │ ├── .keep │ ├── application_record.rb │ ├── body.rb │ ├── concerns │ │ ├── .keep │ │ └── nk_syncable.rb │ ├── legislative_term.rb │ ├── ministry.rb │ ├── organization.rb │ ├── paper.rb │ ├── paper_answerer.rb │ ├── paper_originator.rb │ ├── paper_redirect.rb │ ├── paper_relation.rb │ ├── person.rb │ ├── report.rb │ ├── scraper_result.rb │ └── search_terms.rb ├── scrapers │ ├── baden_wuerttemberg_landtag_scraper.rb │ ├── bayern_landtag_scraper.rb │ ├── berlin_agh_scraper.rb │ ├── brandenburg_landtag_scraper.rb │ ├── bremen_buergerschaft_scraper.rb │ ├── bundestag_scraper.rb │ ├── detail_scraper.rb │ ├── hamburg_buergerschaft_scraper.rb │ ├── hessen_scraper.rb │ ├── meck_pomm_landtag_scraper.rb │ ├── niedersachsen_landtag_scraper.rb │ ├── nordrhein_westfalen_landtag_scraper.rb │ ├── rheinland_pfalz_landtag_scraper.rb │ ├── saarland_scraper.rb │ ├── sachsen_anhalt_landtag_scraper.rb │ ├── sachsen_export_scraper.rb │ ├── sachsen_scraper.rb │ ├── schleswig_holstein_landtag_scraper.rb │ ├── scraper.rb │ └── thueringen_landtag_scraper.rb ├── validators │ └── email_validator.rb └── views │ ├── body │ ├── feed.atom.builder │ └── show.html.erb │ ├── info │ ├── daten.html.erb │ ├── datenschutz.html.erb │ ├── index.html.erb │ ├── kontakt.html.erb │ ├── mitmachen.html.erb │ ├── spenden.html.erb │ └── stilllegung.html.erb │ ├── kaminari │ ├── _first_page.html.erb │ ├── _gap.html.erb │ ├── _last_page.html.erb │ ├── _next_page.html.erb │ ├── _page.html.erb │ ├── _paginator.html.erb │ └── _prev_page.html.erb │ ├── layouts │ ├── application.html.erb │ ├── mailer.html.erb │ └── mailer.text.erb │ ├── legislative_term │ └── show.html.erb │ ├── ministry │ ├── show.atom.builder │ └── show.html.erb │ ├── organization │ ├── show.atom.builder │ └── show.html.erb │ ├── paper │ ├── _paper.atom.builder │ ├── _paper.html.erb │ ├── recent.atom.builder │ ├── report.html.erb │ ├── report_thanks.html.erb │ ├── show.html.erb │ ├── show.json.jbuilder │ ├── viewer.html.erb │ └── viewer_notavailable.html.erb │ ├── review │ ├── index.html.erb │ ├── late.html.erb │ ├── ministries.html.erb │ ├── papers.html.erb │ ├── relations.html.erb │ └── today.html.erb │ ├── scraper_results │ └── index.html.erb │ ├── search │ ├── _searchresult.atom.builder │ ├── advanced.html.erb │ ├── opensearch.xml.builder │ ├── search.atom.builder │ └── search.html.erb │ ├── site │ ├── _tracking.html.erb │ └── index.html.erb │ └── subscription │ ├── error_blacklist.html.erb │ ├── error_invalid.html.erb │ └── error_not_found.html.erb ├── bin ├── bundle ├── rails ├── rake ├── setup ├── spring ├── update └── yarn ├── config.ru ├── config ├── application.rb ├── boot.rb ├── cable.yml ├── database.yml ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── fog.yml ├── initializers │ ├── abbyy.rb │ ├── active_job.rb │ ├── application_controller_renderer.rb │ ├── assets.rb │ ├── backtrace_silencers.rb │ ├── cookies_serializer.rb │ ├── filter_parameter_logging.rb │ ├── fog.rb │ ├── friendly_id.rb │ ├── global_id.rb │ ├── grape_route_helpers_multiple_paths_monkeypatch.rb │ ├── inflections.rb │ ├── mechanize_http_agent_monkeypatch.rb │ ├── mime_types.rb │ ├── new_framework_defaults.rb │ ├── new_framework_defaults_5_1.rb │ ├── overrides.rb │ ├── searchkick.rb │ ├── session_store.rb │ ├── typhoeus.rb │ └── wrap_parameters.rb ├── locales │ ├── de.yml │ └── en.yml ├── newrelic.yml ├── puma.rb ├── routes.rb ├── secrets.yml ├── sidekiq.yml └── spring.rb ├── contrib └── postgres-ka │ ├── Dockerfile │ └── create_db.sh ├── data └── .gitkeep ├── db ├── migrate │ ├── 20140903101921_create_bodies.rb │ ├── 20140903102005_create_people.rb │ ├── 20140903102233_create_papers.rb │ ├── 20140903144831_create_paper_originators.rb │ ├── 20140903151716_create_organizations.rb │ ├── 20140903152041_create_organizations_people.rb │ ├── 20140914200249_create_friendly_id_slugs.rb │ ├── 20140914200415_add_slug_to_body.rb │ ├── 20140914212954_add_slug_to_paper.rb │ ├── 20150110181315_create_ministries.rb │ ├── 20150110181612_create_paper_answerers.rb │ ├── 20150110211212_add_short_name_to_ministry.rb │ ├── 20150111153145_add_slug_to_ministry.rb │ ├── 20150112071510_add_contains_table_to_paper.rb │ ├── 20150204073556_add_unique_index_to_people.rb │ ├── 20150204073802_add_unique_index_to_organizations.rb │ ├── 20150209082805_update_scope_in_slug_index_of_ministry.rb │ ├── 20150209092207_update_scope_in_slug_index_of_paper.rb │ ├── 20150302150223_add_pdf_last_modified_to_paper.rb │ ├── 20150302160238_add_doc_type_to_paper.rb │ ├── 20150303140603_add_is_answer_to_paper.rb │ ├── 20150309155930_create_scraper_results.rb │ ├── 20150310121429_add_success_to_scraper_results.rb │ ├── 20150310121456_add_message_to_scraper_results.rb │ ├── 20150310121721_remove_result_from_scraper_results.rb │ ├── 20150310230538_rename_subscription_type_column.rb │ ├── 20150417142732_add_frozen_at_to_paper.rb │ ├── 20150518162148_add_paper_counts_to_scraper_results.rb │ ├── 20150818191716_add_use_mirror_for_download_to_body.rb │ ├── 20160102120618_add_slug_to_organization.rb │ ├── 20160102161745_create_paper_relations.rb │ ├── 20160224172917_add_source_url_to_paper.rb │ ├── 20160510165856_add_scraper_class_to_scraper_results.rb │ ├── 20160821113555_create_legislative_terms.rb │ ├── 20160823062847_add_slug_to_person.rb │ ├── 20160829063744_add_unique_term_to_legislative_terms.rb │ ├── 20160901101627_add_deleted_at_to_papers.rb │ ├── 20160901101643_add_deleted_at_to_people.rb │ ├── 20160901101828_add_deleted_at_to_organizations.rb │ ├── 20160901101842_add_deleted_at_to_ministries.rb │ ├── 20161208084214_add_wikidata_q_to_body.rb │ ├── 20161208084252_add_wikidata_q_to_legislative_term.rb │ ├── 20161208084334_add_wikidata_q_to_person.rb │ ├── 20170123110640_set_paper_is_answer_default_false.rb │ ├── 20170416160058_create_paper_redirects.rb │ ├── 20170923094320_add_contains_classified_information_to_paper.rb │ ├── 20190127195351_add_site_message_to_body.rb │ ├── 20201231225202_drop_table_opt_in.rb │ ├── 20201231225327_drop_table_subscription.rb │ └── 20201231234052_drop_table_email_blacklist.rb ├── schema.rb └── seeds.rb ├── docker-compose.yml ├── lib ├── assets │ └── .keep ├── classified_recognizer.rb ├── es_query_parser.rb ├── nomenklatura.rb ├── paper_importer.rb ├── table_recognizer.rb └── tasks │ ├── .keep │ └── papers.rake ├── log └── .keep ├── public ├── 404.html ├── 422.html ├── 500.html ├── apple-touch-icon.png ├── favicon.ico └── robots.txt ├── test ├── controllers │ ├── .keep │ ├── body_controller_test.rb │ ├── info_controller_test.rb │ ├── legislative_term_controller_test.rb │ ├── metrics_controller_test.rb │ ├── ministry_controller_test.rb │ ├── organization_controller_test.rb │ ├── paper_controller_test.rb │ ├── search_controller_test.rb │ └── site_controller_test.rb ├── extractors │ ├── baden_wuerttemberg_pdf_extractor_test.rb │ ├── bayern_pdf_extractor_answerers_test.rb │ ├── bayern_pdf_extractor_originators_test.rb │ ├── berlin_pdf_extractor_test.rb │ ├── brandenburg_pdf_extractor_test.rb │ ├── bremen_pdf_extractor_test.rb │ ├── bundestag_pdf_extractor_test.rb │ ├── hamburg_pdf_has_answer_test.rb │ ├── hessen_pdf_extractor_test.rb │ ├── meck_pomm_pdf_extractor_test.rb │ ├── mv_pdf_has_answer_test.rb │ ├── name_party_extractor_test.rb │ ├── nordrhein_westfalen_pdf_extractor_test.rb │ ├── pdf_extractor_test.rb │ ├── rheinland_pfalz_pdf_extractor_test.rb │ ├── saarland_pdf_extractor_doctype_test.rb │ ├── saarland_pdf_extractor_originators_test.rb │ ├── sachsen_pdf_extractor_test.rb │ ├── schleswig_holstein_pdf_extractor_test.rb │ └── thueringen_pdf_extractor_test.rb ├── fixtures │ ├── .keep │ ├── bb │ │ ├── detail.html │ │ ├── detail_2926.html │ │ ├── detail_614.html │ │ └── overview.html │ ├── be │ │ ├── detail.html │ │ ├── detail_17_13104.html │ │ ├── overview.html │ │ ├── overview_13307.html │ │ ├── overview_13566.html │ │ ├── overview_13584.html │ │ ├── overview_13768.html │ │ ├── overview_1469.html │ │ ├── overview_15027.html │ │ ├── overview_15093.html │ │ └── overview_15272.html │ ├── bodies.yml │ ├── bt │ │ ├── detail.html │ │ ├── detail_18_11403.html │ │ ├── detail_18_11403_procedure.html │ │ ├── detail_18_13660.html │ │ ├── detail_18_13660_procedure.html │ │ ├── detail_18_5644.html │ │ ├── detail_18_5714.html │ │ ├── detail_18_678.html │ │ ├── detail_18_9430.html │ │ └── detail_18_9430_procedure.html │ ├── bw │ │ ├── detail_page.html │ │ ├── detail_page_major.html │ │ ├── detail_page_unanswered.html │ │ ├── legislative_term_page.html │ │ └── overview_minor.html │ ├── by │ │ ├── detail.html │ │ ├── detail_6191.html │ │ └── overview.html │ ├── hb │ │ ├── detail_96.html │ │ └── overview.html │ ├── he │ │ ├── detail_1017.html │ │ ├── detail_1585.html │ │ ├── detail_19_1615.html │ │ ├── detail_19_1616.html │ │ ├── detail_19_18.html │ │ ├── detail_19_3272.html │ │ ├── detail_382.html │ │ ├── detail_major.html │ │ ├── detail_minor.html │ │ ├── overview.html │ │ └── search.html │ ├── hh │ │ ├── result.html │ │ ├── result_1549.html │ │ └── result_2013.html │ ├── legislative_terms.yml │ ├── ministries.yml │ ├── mv │ │ ├── detail.html │ │ ├── detail_6_1597.html │ │ ├── detail_6_2572.html │ │ ├── detail_6_3870.html │ │ ├── detail_6_4151.html │ │ ├── detail_6_4640.html │ │ └── overview.html │ ├── ni │ │ ├── detail.html │ │ ├── detail_4243.html │ │ ├── detail_4370.html │ │ ├── detail_attachment.html │ │ ├── overview.html │ │ └── overview_major.html │ ├── nw │ │ ├── detail.html │ │ ├── detail_7452.html │ │ ├── detail_7576.html │ │ ├── detail_8774.html │ │ └── overview.html │ ├── organizations.yml │ ├── paper_answerers.yml │ ├── paper_originators.yml │ ├── paper_redirects.yml │ ├── paper_relations.yml │ ├── papers.yml │ ├── people.yml │ ├── rp │ │ ├── detail_16.html │ │ ├── detail_16_1734.html │ │ ├── detail_16_3813.html │ │ ├── detail_16_4097.html │ │ ├── detail_16_4965.html │ │ ├── detail_16_863.html │ │ ├── detail_17.html │ │ ├── detail_17_439.html │ │ ├── major_detail.html │ │ ├── major_detail_16_2887.html │ │ ├── major_detail_16_4503.html │ │ ├── major_detail_16_4788.html │ │ ├── major_detail_16_579.html │ │ ├── overview.html │ │ ├── overview_17.html │ │ ├── overview_invalid.html │ │ └── overview_major.html │ ├── scraper_results.yml │ ├── sh │ │ ├── major.html │ │ ├── major_detail.html │ │ ├── minor_detail.html │ │ └── overview.html │ ├── sl │ │ ├── detail.html │ │ ├── overview.html │ │ ├── paper.txt │ │ ├── single_invalid_row.html │ │ └── single_row.html │ ├── sn │ │ ├── detail_search.html │ │ ├── detail_vorgang.html │ │ ├── detail_vorgang_vorgang.html │ │ ├── klanfrde_2016-02-29-short.xml │ │ ├── overview-unanswered.html │ │ └── overview.html │ ├── st │ │ ├── detail.html │ │ ├── detail_unanswered.html │ │ └── overview.html │ └── th │ │ ├── detail_major.html │ │ ├── detail_minor.html │ │ └── overview.html ├── helpers │ ├── .keep │ ├── body_helper_test.rb │ ├── legislative_term_helper_test.rb │ ├── paper_helper_test.rb │ └── site_helper_test.rb ├── integration │ └── .keep ├── jobs │ ├── extract_related_papers_job_test.rb │ └── extract_text_from_paper_job_test.rb ├── mailers │ └── .keep ├── models │ ├── .keep │ ├── body_test.rb │ ├── classified_recognizer_test.rb │ ├── es_query_parser_test.rb │ ├── legislative_term_test.rb │ ├── ministry_test.rb │ ├── organization_test.rb │ ├── paper_answerer_test.rb │ ├── paper_originator_test.rb │ ├── paper_redirect_test.rb │ ├── paper_relation_test.rb │ ├── paper_test.rb │ ├── person_test.rb │ ├── scraper_result_test.rb │ ├── search_terms_test.rb │ └── table_recognizer_test.rb ├── scraper │ ├── baden_wuerttemberg_landtag_scraper_test.rb │ ├── bayern_landtag_scraper_detail_test.rb │ ├── bayern_landtag_scraper_overview_test.rb │ ├── berlin_agh_scraper_detail_test.rb │ ├── berlin_agh_scraper_overview_test.rb │ ├── brandenburg_scraper_test.rb │ ├── bremen_buergerschaft_scraper_detail_test.rb │ ├── bremen_buergerschaft_scraper_overview_test.rb │ ├── bundestag_scraper_test.rb │ ├── hamburg_buergerschaft_scraper_test.rb │ ├── hessen_test.rb │ ├── meck_pomm_landtag_scraper_detail_test.rb │ ├── meck_pomm_landtag_scraper_overview_test.rb │ ├── niedersachsen_landtag_scraper_detail_test.rb │ ├── niedersachsen_landtag_scraper_overview_test.rb │ ├── nordrhein_westfalen_landtag_scraper_test.rb │ ├── rheinland_pfalz_landtag_scraper_detail_test.rb │ ├── rheinland_pfalz_landtag_scraper_overview_test.rb │ ├── saarland_scraper_test.rb │ ├── sachsen_anhalt_landtag_scraper_detail_test.rb │ ├── sachsen_anhalt_landtag_scraper_overview_test.rb │ ├── sachsen_export_scraper_test.rb │ ├── sachsen_scraper_detail_test.rb │ ├── sachsen_scraper_overview_test.rb │ ├── schleswig_holstein_landtag_scraper_overview_test.rb │ ├── thueringen_landtag_scraper_detail_test.rb │ └── thueringen_landtag_scraper_overview_test.rb └── test_helper.rb └── vendor └── assets ├── images └── pdfjs │ ├── annotation-check.svg │ ├── annotation-comment.svg │ ├── annotation-help.svg │ ├── annotation-insert.svg │ ├── annotation-key.svg │ ├── annotation-newparagraph.svg │ ├── annotation-noicon.svg │ ├── annotation-note.svg │ ├── annotation-paragraph.svg │ ├── findbarButton-next-rtl.png │ ├── findbarButton-next-rtl@2x.png │ ├── findbarButton-next.png │ ├── findbarButton-next@2x.png │ ├── findbarButton-previous-rtl.png │ ├── findbarButton-previous-rtl@2x.png │ ├── findbarButton-previous.png │ ├── findbarButton-previous@2x.png │ ├── grab.cur │ ├── grabbing.cur │ ├── loading-icon.gif │ ├── loading-small.png │ ├── loading-small@2x.png │ ├── secondaryToolbarButton-documentProperties.png │ ├── secondaryToolbarButton-documentProperties@2x.png │ ├── secondaryToolbarButton-firstPage.png │ ├── secondaryToolbarButton-firstPage@2x.png │ ├── secondaryToolbarButton-handTool.png │ ├── secondaryToolbarButton-handTool@2x.png │ ├── secondaryToolbarButton-lastPage.png │ ├── secondaryToolbarButton-lastPage@2x.png │ ├── secondaryToolbarButton-rotateCcw.png │ ├── secondaryToolbarButton-rotateCcw@2x.png │ ├── secondaryToolbarButton-rotateCw.png │ ├── secondaryToolbarButton-rotateCw@2x.png │ ├── shadow.png │ ├── texture.png │ ├── toolbarButton-bookmark.png │ ├── toolbarButton-bookmark@2x.png │ ├── toolbarButton-menuArrows.png │ ├── toolbarButton-menuArrows@2x.png │ ├── toolbarButton-pageDown-rtl.png │ ├── toolbarButton-pageDown-rtl@2x.png │ ├── toolbarButton-pageDown.png │ ├── toolbarButton-pageDown@2x.png │ ├── toolbarButton-pageUp-rtl.png │ ├── toolbarButton-pageUp-rtl@2x.png │ ├── toolbarButton-pageUp.png │ ├── toolbarButton-pageUp@2x.png │ ├── toolbarButton-presentationMode.png │ ├── toolbarButton-presentationMode@2x.png │ ├── toolbarButton-search.png │ ├── toolbarButton-search@2x.png │ ├── toolbarButton-secondaryToolbarToggle-rtl.png │ ├── toolbarButton-secondaryToolbarToggle-rtl@2x.png │ ├── toolbarButton-secondaryToolbarToggle.png │ ├── toolbarButton-secondaryToolbarToggle@2x.png │ ├── toolbarButton-zoomIn.png │ ├── toolbarButton-zoomIn@2x.png │ ├── toolbarButton-zoomOut.png │ └── toolbarButton-zoomOut@2x.png ├── javascripts ├── .keep ├── bootstrap-select.min.js ├── fontfaceobserver.standalone.js ├── handlebars-v2.0.0.js ├── pdfjs │ ├── LICENSE │ ├── cmaps │ │ ├── 78-EUC-H.bcmap │ │ ├── 78-EUC-V.bcmap │ │ ├── 78-H.bcmap │ │ ├── 78-RKSJ-H.bcmap │ │ ├── 78-RKSJ-V.bcmap │ │ ├── 78-V.bcmap │ │ ├── 78ms-RKSJ-H.bcmap │ │ ├── 78ms-RKSJ-V.bcmap │ │ ├── 83pv-RKSJ-H.bcmap │ │ ├── 90ms-RKSJ-H.bcmap │ │ ├── 90ms-RKSJ-V.bcmap │ │ ├── 90msp-RKSJ-H.bcmap │ │ ├── 90msp-RKSJ-V.bcmap │ │ ├── 90pv-RKSJ-H.bcmap │ │ ├── 90pv-RKSJ-V.bcmap │ │ ├── Add-H.bcmap │ │ ├── Add-RKSJ-H.bcmap │ │ ├── Add-RKSJ-V.bcmap │ │ ├── Add-V.bcmap │ │ ├── Adobe-CNS1-0.bcmap │ │ ├── Adobe-CNS1-1.bcmap │ │ ├── Adobe-CNS1-2.bcmap │ │ ├── Adobe-CNS1-3.bcmap │ │ ├── Adobe-CNS1-4.bcmap │ │ ├── Adobe-CNS1-5.bcmap │ │ ├── Adobe-CNS1-6.bcmap │ │ ├── Adobe-CNS1-UCS2.bcmap │ │ ├── Adobe-GB1-0.bcmap │ │ ├── Adobe-GB1-1.bcmap │ │ ├── Adobe-GB1-2.bcmap │ │ ├── Adobe-GB1-3.bcmap │ │ ├── Adobe-GB1-4.bcmap │ │ ├── Adobe-GB1-5.bcmap │ │ ├── Adobe-GB1-UCS2.bcmap │ │ ├── Adobe-Japan1-0.bcmap │ │ ├── Adobe-Japan1-1.bcmap │ │ ├── Adobe-Japan1-2.bcmap │ │ ├── Adobe-Japan1-3.bcmap │ │ ├── Adobe-Japan1-4.bcmap │ │ ├── Adobe-Japan1-5.bcmap │ │ ├── Adobe-Japan1-6.bcmap │ │ ├── Adobe-Japan1-UCS2.bcmap │ │ ├── Adobe-Korea1-0.bcmap │ │ ├── Adobe-Korea1-1.bcmap │ │ ├── Adobe-Korea1-2.bcmap │ │ ├── Adobe-Korea1-UCS2.bcmap │ │ ├── B5-H.bcmap │ │ ├── B5-V.bcmap │ │ ├── B5pc-H.bcmap │ │ ├── B5pc-V.bcmap │ │ ├── CNS-EUC-H.bcmap │ │ ├── CNS-EUC-V.bcmap │ │ ├── CNS1-H.bcmap │ │ ├── CNS1-V.bcmap │ │ ├── CNS2-H.bcmap │ │ ├── CNS2-V.bcmap │ │ ├── ETHK-B5-H.bcmap │ │ ├── ETHK-B5-V.bcmap │ │ ├── ETen-B5-H.bcmap │ │ ├── ETen-B5-V.bcmap │ │ ├── ETenms-B5-H.bcmap │ │ ├── ETenms-B5-V.bcmap │ │ ├── EUC-H.bcmap │ │ ├── EUC-V.bcmap │ │ ├── Ext-H.bcmap │ │ ├── Ext-RKSJ-H.bcmap │ │ ├── Ext-RKSJ-V.bcmap │ │ ├── Ext-V.bcmap │ │ ├── GB-EUC-H.bcmap │ │ ├── GB-EUC-V.bcmap │ │ ├── GB-H.bcmap │ │ ├── GB-V.bcmap │ │ ├── GBK-EUC-H.bcmap │ │ ├── GBK-EUC-V.bcmap │ │ ├── GBK2K-H.bcmap │ │ ├── GBK2K-V.bcmap │ │ ├── GBKp-EUC-H.bcmap │ │ ├── GBKp-EUC-V.bcmap │ │ ├── GBT-EUC-H.bcmap │ │ ├── GBT-EUC-V.bcmap │ │ ├── GBT-H.bcmap │ │ ├── GBT-V.bcmap │ │ ├── GBTpc-EUC-H.bcmap │ │ ├── GBTpc-EUC-V.bcmap │ │ ├── GBpc-EUC-H.bcmap │ │ ├── GBpc-EUC-V.bcmap │ │ ├── H.bcmap │ │ ├── HKdla-B5-H.bcmap │ │ ├── HKdla-B5-V.bcmap │ │ ├── HKdlb-B5-H.bcmap │ │ ├── HKdlb-B5-V.bcmap │ │ ├── HKgccs-B5-H.bcmap │ │ ├── HKgccs-B5-V.bcmap │ │ ├── HKm314-B5-H.bcmap │ │ ├── HKm314-B5-V.bcmap │ │ ├── HKm471-B5-H.bcmap │ │ ├── HKm471-B5-V.bcmap │ │ ├── HKscs-B5-H.bcmap │ │ ├── HKscs-B5-V.bcmap │ │ ├── Hankaku.bcmap │ │ ├── Hiragana.bcmap │ │ ├── KSC-EUC-H.bcmap │ │ ├── KSC-EUC-V.bcmap │ │ ├── KSC-H.bcmap │ │ ├── KSC-Johab-H.bcmap │ │ ├── KSC-Johab-V.bcmap │ │ ├── KSC-V.bcmap │ │ ├── KSCms-UHC-H.bcmap │ │ ├── KSCms-UHC-HW-H.bcmap │ │ ├── KSCms-UHC-HW-V.bcmap │ │ ├── KSCms-UHC-V.bcmap │ │ ├── KSCpc-EUC-H.bcmap │ │ ├── KSCpc-EUC-V.bcmap │ │ ├── Katakana.bcmap │ │ ├── LICENSE │ │ ├── NWP-H.bcmap │ │ ├── NWP-V.bcmap │ │ ├── RKSJ-H.bcmap │ │ ├── RKSJ-V.bcmap │ │ ├── Roman.bcmap │ │ ├── UniCNS-UCS2-H.bcmap │ │ ├── UniCNS-UCS2-V.bcmap │ │ ├── UniCNS-UTF16-H.bcmap │ │ ├── UniCNS-UTF16-V.bcmap │ │ ├── UniCNS-UTF32-H.bcmap │ │ ├── UniCNS-UTF32-V.bcmap │ │ ├── UniCNS-UTF8-H.bcmap │ │ ├── UniCNS-UTF8-V.bcmap │ │ ├── UniGB-UCS2-H.bcmap │ │ ├── UniGB-UCS2-V.bcmap │ │ ├── UniGB-UTF16-H.bcmap │ │ ├── UniGB-UTF16-V.bcmap │ │ ├── UniGB-UTF32-H.bcmap │ │ ├── UniGB-UTF32-V.bcmap │ │ ├── UniGB-UTF8-H.bcmap │ │ ├── UniGB-UTF8-V.bcmap │ │ ├── UniJIS-UCS2-H.bcmap │ │ ├── UniJIS-UCS2-HW-H.bcmap │ │ ├── UniJIS-UCS2-HW-V.bcmap │ │ ├── UniJIS-UCS2-V.bcmap │ │ ├── UniJIS-UTF16-H.bcmap │ │ ├── UniJIS-UTF16-V.bcmap │ │ ├── UniJIS-UTF32-H.bcmap │ │ ├── UniJIS-UTF32-V.bcmap │ │ ├── UniJIS-UTF8-H.bcmap │ │ ├── UniJIS-UTF8-V.bcmap │ │ ├── UniJIS2004-UTF16-H.bcmap │ │ ├── UniJIS2004-UTF16-V.bcmap │ │ ├── UniJIS2004-UTF32-H.bcmap │ │ ├── UniJIS2004-UTF32-V.bcmap │ │ ├── UniJIS2004-UTF8-H.bcmap │ │ ├── UniJIS2004-UTF8-V.bcmap │ │ ├── UniJISPro-UCS2-HW-V.bcmap │ │ ├── UniJISPro-UCS2-V.bcmap │ │ ├── UniJISPro-UTF8-V.bcmap │ │ ├── UniJISX0213-UTF32-H.bcmap │ │ ├── UniJISX0213-UTF32-V.bcmap │ │ ├── UniJISX02132004-UTF32-H.bcmap │ │ ├── UniJISX02132004-UTF32-V.bcmap │ │ ├── UniKS-UCS2-H.bcmap │ │ ├── UniKS-UCS2-V.bcmap │ │ ├── UniKS-UTF16-H.bcmap │ │ ├── UniKS-UTF16-V.bcmap │ │ ├── UniKS-UTF32-H.bcmap │ │ ├── UniKS-UTF32-V.bcmap │ │ ├── UniKS-UTF8-H.bcmap │ │ ├── UniKS-UTF8-V.bcmap │ │ ├── V.bcmap │ │ └── WP-Symbol.bcmap │ ├── compatibility.js │ ├── index.js │ ├── l10n.js │ ├── locale │ │ ├── de │ │ │ └── viewer.properties │ │ ├── en-GB │ │ │ └── viewer.properties │ │ └── en-US │ │ │ └── viewer.properties │ ├── pdf.js │ └── pdf.worker.js └── typeahead.bundle.min.js └── stylesheets ├── .keep ├── bootstrap-select.css └── pdfjs └── viewer.css.erb /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | 3 | # Ignore bundler config. 4 | .bundle 5 | 6 | # Ignore all logfiles and tempfiles. 7 | log/*.log 8 | tmp/* 9 | 10 | # our data dir 11 | data/* -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | insert_final_newline = false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore the default SQLite database. 11 | /db/*.sqlite3 12 | /db/*.sqlite3-journal 13 | 14 | # Ignore all logfiles and tempfiles. 15 | /log/*.log 16 | /tmp 17 | 18 | # our data dir 19 | /data/* 20 | !/data/.gitkeep 21 | 22 | # config vars 23 | .env 24 | 25 | # deployment 26 | /config/deploy.rb -------------------------------------------------------------------------------- /.hound.yml: -------------------------------------------------------------------------------- 1 | ruby: 2 | config_file: .rubocop.yml -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.5.8 4 | services: 5 | - redis-server 6 | cache: bundler 7 | notifications: 8 | slack: 9 | secure: JjGRvxBm3BQ1a3ScyIwi1cvDWFP6hKSYNqwzzxZgfhuZbEv0WftS/09T/dU+Mp5TO0ERBvgNqZCAliNfdBKB3ON8P/xYlegACdPNC+5gha3GXSyp5VN8ePaXXhju0q0MKMU1mr+6gIHgRqUEbXsfoEbP8BHos2NvFi4rZSe7ACg= -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:2.5.8 2 | 3 | RUN apt-get update -qq && apt-get install -y build-essential libpq-dev postgresql-client nodejs poppler-utils advancecomp gifsicle jhead jpegoptim libjpeg-progs optipng pngcrush pngquant 4 | 5 | # enable utf8 in irb 6 | ENV LANG C.UTF-8 7 | 8 | RUN gem install bundler --version 2.1.4 9 | 10 | RUN mkdir /app 11 | WORKDIR /app 12 | ADD Gemfile /app/Gemfile 13 | ADD Gemfile.lock /app/Gemfile.lock 14 | RUN bundle install 15 | 16 | ADD . /app 17 | 18 | EXPOSE 5000 19 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Maximilian Richt (robbi5) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: bundle exec rails server -b 0.0.0.0 -p $PORT 2 | worker: bundle exec sidekiq -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /app/api/oparl/entities/legislative_term.rb: -------------------------------------------------------------------------------- 1 | module OParl 2 | module Entities 3 | class LegislativeTerm < Grape::Entity 4 | expose(:id) { |lt| OParl::Routes.oparl_v1_body_term_url(body: lt.body.key, term: lt.term) } 5 | expose(:type) { |_| 'https://schema.oparl.org/1.0/LegislativeTerm' } 6 | expose(:body, if: { type: :lt_full }) { |lt| OParl::Routes.oparl_v1_body_url(body: lt.body.key) } 7 | expose(:name) { |lt| "#{lt.term}. Wahlperiode" } 8 | expose(:startDate, unless: lambda { |lt, _| lt.starts_at.nil? }) { |lt| lt.starts_at } 9 | expose(:endDate, unless: lambda { |lt, _| lt.ends_at.nil? }) { |lt| lt.ends_at } 10 | expose(:web) { |lt| Rails.application.routes.url_helpers.legislative_term_url(lt.body, lt.term) } 11 | 12 | expose(:'wikidata:item', unless: lambda { |obj, _| obj.wikidataq.blank? }) { |obj| obj.wikidataq } 13 | 14 | expose(:created, if: { type: :lt_full }) { |lt| lt.created_at.iso8601 } 15 | expose(:modified, if: { type: :lt_full }) { |lt| lt.updated_at.iso8601 } 16 | end 17 | end 18 | end -------------------------------------------------------------------------------- /app/api/oparl/entities/person.rb: -------------------------------------------------------------------------------- 1 | module OParl 2 | module Entities 3 | class Person < Grape::Entity 4 | expose(:id) { |person| OParl::Routes.oparl_v1_person_url(person: person.slug) } 5 | expose(:type) { |_| 'https://schema.oparl.org/1.0/Person' } 6 | 7 | expose(:body) { |person| OParl::Routes.oparl_v1_body_url(body: person.latest_body.key) } 8 | 9 | with_options(unless: lambda { |obj, _| obj.deleted? }) do 10 | expose :name 11 | end 12 | 13 | # expose(:web) { |person| Rails.application.routes.url_helpers.person_url(person) } # equivalent in html 14 | 15 | expose(:'wikidata:item', unless: lambda { |obj, _| obj.wikidataq.blank? }) { |obj| obj.wikidataq } 16 | 17 | expose(:created) { |obj| obj.created_at.iso8601 } 18 | expose(:modified) { |obj| (obj.deleted_at || obj.updated_at).iso8601 } 19 | expose(:deleted) { |obj| obj.deleted? } 20 | end 21 | end 22 | end -------------------------------------------------------------------------------- /app/api/oparl/entities/system.rb: -------------------------------------------------------------------------------- 1 | module OParl 2 | module Entities 3 | class System < Grape::Entity 4 | expose(:id) { |_| OParl::Routes.oparl_v1_system_url } 5 | expose(:type) { |_| 'https://schema.oparl.org/1.0/System' } 6 | expose(:oparlVersion) { |_| 'https://schema.oparl.org/1.0/' } 7 | expose(:license) { |_| 'http://opendatacommons.org/licenses/odbl/1.0/' } 8 | expose(:body) { |_| OParl::Routes.oparl_v1_bodies_url } 9 | expose(:name) { |_| 'kleineAnfragen' } 10 | expose(:contactName) { |sys| sys[:contact].match(/(.+)\s<(.+)>/).to_a[1] } 11 | expose(:contactEmail) { |sys| sys[:contact].match(/(.+)\s<(.+)>/).to_a[2] } 12 | expose(:website) { |_| Rails.application.routes.url_helpers.root_url } 13 | expose(:product) { |_| 'https://github.com/robbi5/kleineanfragen' } 14 | 15 | expose(:created) { |_| '2016-08-22' } 16 | expose(:modified) { |obj| '2016-08-22' } 17 | end 18 | end 19 | end -------------------------------------------------------------------------------- /app/api/oparl/loggers.rb: -------------------------------------------------------------------------------- 1 | module OParl 2 | module Loggers 3 | class RequestId < GrapeLogging::Loggers::Base 4 | def parameters(request, _) 5 | { request_id: request.env["action_dispatch.request_id"] } 6 | end 7 | end 8 | 9 | class Format < GrapeLogging::Loggers::Base 10 | def parameters(request, _) 11 | { formats: ['json'] } 12 | end 13 | end 14 | 15 | class Controller < GrapeLogging::Loggers::Base 16 | def parameters(request, _) 17 | { controller: 'OParl' } 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/assets/fonts/source-sans-pro-v12-latin/source-sans-pro-v12-latin-600.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbi5/kleineanfragen/1043e9f35620982abbfe5630e455e85e9b87741f/app/assets/fonts/source-sans-pro-v12-latin/source-sans-pro-v12-latin-600.eot -------------------------------------------------------------------------------- /app/assets/fonts/source-sans-pro-v12-latin/source-sans-pro-v12-latin-600.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbi5/kleineanfragen/1043e9f35620982abbfe5630e455e85e9b87741f/app/assets/fonts/source-sans-pro-v12-latin/source-sans-pro-v12-latin-600.ttf -------------------------------------------------------------------------------- /app/assets/fonts/source-sans-pro-v12-latin/source-sans-pro-v12-latin-600.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbi5/kleineanfragen/1043e9f35620982abbfe5630e455e85e9b87741f/app/assets/fonts/source-sans-pro-v12-latin/source-sans-pro-v12-latin-600.woff -------------------------------------------------------------------------------- /app/assets/fonts/source-sans-pro-v12-latin/source-sans-pro-v12-latin-600.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbi5/kleineanfragen/1043e9f35620982abbfe5630e455e85e9b87741f/app/assets/fonts/source-sans-pro-v12-latin/source-sans-pro-v12-latin-600.woff2 -------------------------------------------------------------------------------- /app/assets/fonts/source-sans-pro-v12-latin/source-sans-pro-v12-latin-700.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbi5/kleineanfragen/1043e9f35620982abbfe5630e455e85e9b87741f/app/assets/fonts/source-sans-pro-v12-latin/source-sans-pro-v12-latin-700.eot -------------------------------------------------------------------------------- /app/assets/fonts/source-sans-pro-v12-latin/source-sans-pro-v12-latin-700.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbi5/kleineanfragen/1043e9f35620982abbfe5630e455e85e9b87741f/app/assets/fonts/source-sans-pro-v12-latin/source-sans-pro-v12-latin-700.ttf -------------------------------------------------------------------------------- /app/assets/fonts/source-sans-pro-v12-latin/source-sans-pro-v12-latin-700.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbi5/kleineanfragen/1043e9f35620982abbfe5630e455e85e9b87741f/app/assets/fonts/source-sans-pro-v12-latin/source-sans-pro-v12-latin-700.woff -------------------------------------------------------------------------------- /app/assets/fonts/source-sans-pro-v12-latin/source-sans-pro-v12-latin-700.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbi5/kleineanfragen/1043e9f35620982abbfe5630e455e85e9b87741f/app/assets/fonts/source-sans-pro-v12-latin/source-sans-pro-v12-latin-700.woff2 -------------------------------------------------------------------------------- /app/assets/fonts/source-sans-pro-v12-latin/source-sans-pro-v12-latin-italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbi5/kleineanfragen/1043e9f35620982abbfe5630e455e85e9b87741f/app/assets/fonts/source-sans-pro-v12-latin/source-sans-pro-v12-latin-italic.eot -------------------------------------------------------------------------------- /app/assets/fonts/source-sans-pro-v12-latin/source-sans-pro-v12-latin-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbi5/kleineanfragen/1043e9f35620982abbfe5630e455e85e9b87741f/app/assets/fonts/source-sans-pro-v12-latin/source-sans-pro-v12-latin-italic.ttf -------------------------------------------------------------------------------- /app/assets/fonts/source-sans-pro-v12-latin/source-sans-pro-v12-latin-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbi5/kleineanfragen/1043e9f35620982abbfe5630e455e85e9b87741f/app/assets/fonts/source-sans-pro-v12-latin/source-sans-pro-v12-latin-italic.woff -------------------------------------------------------------------------------- /app/assets/fonts/source-sans-pro-v12-latin/source-sans-pro-v12-latin-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbi5/kleineanfragen/1043e9f35620982abbfe5630e455e85e9b87741f/app/assets/fonts/source-sans-pro-v12-latin/source-sans-pro-v12-latin-italic.woff2 -------------------------------------------------------------------------------- /app/assets/fonts/source-sans-pro-v12-latin/source-sans-pro-v12-latin-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbi5/kleineanfragen/1043e9f35620982abbfe5630e455e85e9b87741f/app/assets/fonts/source-sans-pro-v12-latin/source-sans-pro-v12-latin-regular.eot -------------------------------------------------------------------------------- /app/assets/fonts/source-sans-pro-v12-latin/source-sans-pro-v12-latin-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbi5/kleineanfragen/1043e9f35620982abbfe5630e455e85e9b87741f/app/assets/fonts/source-sans-pro-v12-latin/source-sans-pro-v12-latin-regular.ttf -------------------------------------------------------------------------------- /app/assets/fonts/source-sans-pro-v12-latin/source-sans-pro-v12-latin-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbi5/kleineanfragen/1043e9f35620982abbfe5630e455e85e9b87741f/app/assets/fonts/source-sans-pro-v12-latin/source-sans-pro-v12-latin-regular.woff -------------------------------------------------------------------------------- /app/assets/fonts/source-sans-pro-v12-latin/source-sans-pro-v12-latin-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbi5/kleineanfragen/1043e9f35620982abbfe5630e455e85e9b87741f/app/assets/fonts/source-sans-pro-v12-latin/source-sans-pro-v12-latin-regular.woff2 -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbi5/kleineanfragen/1043e9f35620982abbfe5630e455e85e9b87741f/app/assets/images/.keep -------------------------------------------------------------------------------- /app/assets/images/LICENSE.md: -------------------------------------------------------------------------------- 1 | icon-cross.svg, icon-email.svg and icon-tick.svg are from the Feather Icon Pack (http://colebemis.com/feather/): 2 | --- 3 | Feather 4 | by Cole Bemis 5 | 6 | colebemis.com 7 | @colebemis 8 | --- 9 | License: MIT -------------------------------------------------------------------------------- /app/assets/images/feed-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/assets/images/icon-cross.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/assets/images/icon-email.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/assets/images/icon-tick.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/assets/images/kleineanfragen@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbi5/kleineanfragen/1043e9f35620982abbfe5630e455e85e9b87741f/app/assets/images/kleineanfragen@2x.png -------------------------------------------------------------------------------- /app/assets/images/papers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbi5/kleineanfragen/1043e9f35620982abbfe5630e455e85e9b87741f/app/assets/images/papers.png -------------------------------------------------------------------------------- /app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | //! kleineanfragen.js 2 | //= require jquery 3 | //= require jquery_ujs 4 | //= require turbolinks 5 | //= require typeahead.bundle.min 6 | //= require handlebars-v2.0.0 7 | //= require bootstrap/dropdown 8 | //= require bootstrap-select.min 9 | //= require polyfills 10 | //= require search 11 | //= require site-index 12 | //= require paper 13 | //= require tracking 14 | //= require turbolinks_loaded -------------------------------------------------------------------------------- /app/assets/javascripts/enhance.js: -------------------------------------------------------------------------------- 1 | //= require fontfaceobserver.standalone 2 | //= require font -------------------------------------------------------------------------------- /app/assets/javascripts/font.js: -------------------------------------------------------------------------------- 1 | document.documentElement.className += ' js'; 2 | new FontFaceObserver('Source Sans Pro') 3 | .check() 4 | .then(function(){ 5 | document.documentElement.className += ' fonts-loaded'; 6 | }); -------------------------------------------------------------------------------- /app/assets/javascripts/paper.js: -------------------------------------------------------------------------------- 1 | document.addEventListener("turbolinks:load", function() { 2 | $('.shorturl-input').focus(function() { 3 | $(this).select(); 4 | }); 5 | }); -------------------------------------------------------------------------------- /app/assets/javascripts/pdfjs-header.js.erb: -------------------------------------------------------------------------------- 1 | // Initializing PDFJS global object (if still undefined) 2 | if (typeof PDFJS === 'undefined') { 3 | (typeof window !== 'undefined' ? window : this).PDFJS = {}; 4 | } 5 | 6 | // Set worker path 7 | PDFJS.workerSrc = "<%= asset_path('pdfjs/pdf.worker.js') %>"; 8 | PDFJS.imageResourcesPath = '../'; 9 | PDFJS.externalLinkTarget = 2; // == PDFJS.LinkTarget.BLANK -------------------------------------------------------------------------------- /app/assets/javascripts/site-index.js: -------------------------------------------------------------------------------- 1 | document.addEventListener("turbolinks:load", function() { 2 | $('.hexmap').addClass('is-interactive'); 3 | $('.hexmap g').hover(function() { 4 | var state = $(this).attr('id'); 5 | $('.bodies-list a.link-state-' + state).addClass('hover'); 6 | }, function() { 7 | var state = $(this).attr('id'); 8 | $('.bodies-list a.link-state-' + state).removeClass('hover'); 9 | }).click(function() { 10 | var state = $(this).attr('id') || $(this).parent().attr('id'); 11 | if (typeof state === 'undefined') return; 12 | var url = $('.bodies-list a.link-state-' + state).attr('href'); 13 | location.href = url; 14 | }); 15 | 16 | $('.bodies-list a').hover(function() { 17 | var state = $(this).data('state'); 18 | $('.hexmap g#' + state).addClass('hover'); 19 | }, function() { 20 | var state = $(this).data('state'); 21 | $('.hexmap g#' + state).removeClass('hover'); 22 | }); 23 | }); -------------------------------------------------------------------------------- /app/assets/javascripts/tracking.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('turbolinks:load', function() { 2 | if (window._paq != null) { 3 | return _paq.push(['trackPageView']); 4 | } else if (window.piwikTracker != null) { 5 | return piwikTracker.trackPageview(); 6 | } 7 | }); -------------------------------------------------------------------------------- /app/assets/javascripts/turbolinks_loaded.js: -------------------------------------------------------------------------------- 1 | // when application.js is loaded async the DOMContentLoaded event sometimes already happened 2 | // if this is the case, readyState is interactive/complete - so we can trigger the turbolinks start manually 3 | if (document.readyState === "interactive" || document.readyState === "complete") { 4 | Turbolinks.controller.pageLoaded(); 5 | } -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | # kleineAnfragen is sessionless. 3 | # So the following line is commented out: 4 | # protect_from_forgery with: :null_session 5 | 6 | # make request_id available to lograge 7 | # see other part in config/application.rb 8 | def append_info_to_payload(payload) 9 | super 10 | payload[:request_id] = request.uuid 11 | end 12 | 13 | # FIXME: correct place? 14 | def mime_extension(mime_type) 15 | case mime_type 16 | when Mime[:html] 17 | '' 18 | when Mime[:pdf] 19 | 'pdf' 20 | when Mime[:txt] 21 | 'txt' 22 | when Mime[:json] 23 | 'json' 24 | else 25 | '' 26 | end 27 | end 28 | end -------------------------------------------------------------------------------- /app/controllers/body_controller.rb: -------------------------------------------------------------------------------- 1 | class BodyController < ApplicationController 2 | before_action :find_body 3 | 4 | def show 5 | @terms = @body.legislative_terms 6 | @scraper_result = @body.scraper_results.where.not(stopped_at: nil).order(stopped_at: :desc).first 7 | @latest_paper = @body.papers.where.not(published_at: nil).order(published_at: :desc).first 8 | end 9 | 10 | def feed 11 | @papers = @body.papers 12 | .where.not(published_at: nil) 13 | .order(published_at: :desc, reference: :desc) 14 | .page params[:page] 15 | fresh_when last_modified: @papers.maximum(:updated_at), public: true 16 | respond_to :atom # the only supported format 17 | end 18 | 19 | private 20 | 21 | def find_body 22 | @body = Body.friendly.find params[:body] 23 | end 24 | end -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbi5/kleineanfragen/1043e9f35620982abbfe5630e455e85e9b87741f/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /app/controllers/info_controller.rb: -------------------------------------------------------------------------------- 1 | class InfoController < ApplicationController 2 | def daten 3 | end 4 | 5 | def mitmachen 6 | end 7 | 8 | def kontakt 9 | end 10 | 11 | def datenschutz 12 | end 13 | 14 | def spenden 15 | end 16 | 17 | def index 18 | end 19 | 20 | def stilllegung 21 | end 22 | end -------------------------------------------------------------------------------- /app/controllers/legislative_term_controller.rb: -------------------------------------------------------------------------------- 1 | class LegislativeTermController < ApplicationController 2 | before_action :find_body 3 | before_action :find_legislative_term 4 | 5 | def show 6 | redirect_to(body_feed_url(@body, format: :atom), status: :moved_permanently) if params[:format] == 'atom' 7 | 8 | @papers = @legislative_term.papers 9 | .where.not(published_at: nil) 10 | .includes(:body, :paper_originators) 11 | .order(published_at: :desc, reference: :desc) 12 | .page params[:page] 13 | fresh_when last_modified: @papers.maximum(:updated_at), public: true 14 | end 15 | 16 | private 17 | 18 | def find_body 19 | @body = Body.friendly.find params[:body] 20 | end 21 | 22 | def find_legislative_term 23 | @legislative_term = @body.legislative_terms.find_by_term!(params[:legislative_term].to_i) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /app/controllers/ministry_controller.rb: -------------------------------------------------------------------------------- 1 | class MinistryController < ApplicationController 2 | before_action :find_body 3 | before_action :find_ministry 4 | 5 | def show 6 | @papers = @ministry.papers 7 | .where.not(published_at: nil) 8 | .includes(:body, :paper_originators) 9 | .order(legislative_term: :desc, published_at: :desc, reference: :desc) 10 | .page params[:page] 11 | fresh_when last_modified: @papers.maximum(:updated_at), public: true 12 | end 13 | 14 | private 15 | 16 | def find_body 17 | @body = Body.friendly.find params[:body] 18 | end 19 | 20 | def find_ministry 21 | @ministry = Ministry.where(body: @body).friendly.find params[:ministry] 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app/controllers/organization_controller.rb: -------------------------------------------------------------------------------- 1 | class OrganizationController < ApplicationController 2 | before_action :find_body 3 | before_action :find_organization 4 | 5 | def show 6 | @papers = @organization.papers 7 | .where(body: @body) 8 | .where.not(published_at: nil) 9 | .includes(:body, :paper_originators) 10 | .order(legislative_term: :desc, published_at: :desc, reference: :desc) 11 | .page params[:page] 12 | fresh_when last_modified: @papers.maximum(:updated_at), public: true 13 | end 14 | 15 | private 16 | 17 | def find_body 18 | @body = Body.friendly.find params[:body] 19 | end 20 | 21 | def find_organization 22 | @organization = Organization.friendly.find params[:organization] 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/controllers/scraper_results_controller.rb: -------------------------------------------------------------------------------- 1 | class ScraperResultsController < ApplicationController 2 | before_action :find_scraper_result, only: [:show] 3 | 4 | def index 5 | @bodies = Body.all 6 | @results = {} 7 | @dates = ((Date.today - 10.days)..Date.today).to_a 8 | @bodies.each do |body| 9 | scraper_results = body.scraper_results.where(['created_at > ?', @dates.first]).order(created_at: :asc) 10 | @results[body.id] = @dates.map { |d| [d, scraper_results.select { |r| r.created_at.to_date == d }] }.to_h 11 | end 12 | end 13 | 14 | def show 15 | render json: @result 16 | end 17 | 18 | private 19 | 20 | def find_scraper_result 21 | @result = ScraperResult.find_by_hash(params[:scraper_result]) 22 | rescue 23 | render status: 404 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /app/controllers/site_controller.rb: -------------------------------------------------------------------------------- 1 | class SiteController < ApplicationController 2 | def index 3 | @bodies = Body.order("state != 'BT', name ASC").all 4 | @papers = Paper 5 | .where.not(published_at: nil) 6 | .limit(11) 7 | .includes(:body, :paper_originators) 8 | .order(published_at: :desc, reference: :desc) 9 | @count = Paper.count.round(-1) 10 | fresh_when last_modified: @papers.maximum(:updated_at), public: true 11 | end 12 | 13 | def status 14 | expires_now 15 | render plain: "OK - #{Time.now} - #{Socket.gethostname}" 16 | end 17 | end -------------------------------------------------------------------------------- /app/extractors/berlin_pdf_extractor.rb: -------------------------------------------------------------------------------- 1 | class BerlinPDFExtractor 2 | def initialize(paper) 3 | @contents = paper.contents 4 | end 5 | 6 | # \n.................................\nSenatsverwaltung für Stadtentwicklung und Umwelt\n 7 | # \n_____________________________\nSenatsverwaltung für Gesundheit und Soziales\n 8 | ANSWERERS = /\n[\.\_]{28,40}\s+(Senatsverwaltung\s+.+?)\s\s+\(/m 9 | 10 | def extract_answerers 11 | return nil if @contents.blank? 12 | ministries = [] 13 | 14 | m = @contents.match(ANSWERERS) 15 | return nil if m.nil? 16 | 17 | ministry = m[1] 18 | .gsub(/\p{Z}/, ' ') 19 | .gsub(/\-\n+/, '') 20 | .gsub(/\n/, ' ') 21 | .gsub(/\s+/, ' ') 22 | .gsub(/\s+,\s+/, ', ') 23 | .strip 24 | .gsub(/\p{Other}/, '') # invisible chars & private use unicode 25 | 26 | ministries << ministry unless ministry.blank? 27 | 28 | { ministries: ministries } 29 | end 30 | end -------------------------------------------------------------------------------- /app/extractors/hamburg_pdf_has_answer_extractor.rb: -------------------------------------------------------------------------------- 1 | class HamburgPDFHasAnswerExtractor 2 | def initialize(paper) 3 | @contents = paper.contents 4 | end 5 | 6 | ANSWERED_TAG = /und\s+Antwort\s+des\s+Senats\s+Betr\.?\:?/i 7 | 8 | def is_answer? 9 | return nil if @contents.nil? 10 | @contents.scan(ANSWERED_TAG).present? 11 | end 12 | end -------------------------------------------------------------------------------- /app/extractors/mv_pdf_has_answer_extractor.rb: -------------------------------------------------------------------------------- 1 | class MvPDFHasAnswerExtractor 2 | def initialize(paper) 3 | @contents = paper.contents 4 | end 5 | 6 | DUE_DATE_TAG = /\(Termin zur Beantwortung.*gemäß.*\)/m 7 | 8 | def is_answer? 9 | return nil if @contents.nil? 10 | @contents.scan(DUE_DATE_TAG).blank? 11 | end 12 | end -------------------------------------------------------------------------------- /app/extractors/nordrhein_westfalen_pdf_extractor.rb: -------------------------------------------------------------------------------- 1 | class NordrheinWestfalenPDFExtractor 2 | def initialize(paper) 3 | @contents = paper.contents 4 | end 5 | 6 | ORIGINATORS = /Antwort\s+der\s+Landesregierung\s+auf\s+die\s+[kK]leine\s+Anfrage.+?de[rs]\s+Abgeordneten\s+(.+?)\s+(\p{Lu}+?)\s+Drucksache/m 7 | 8 | def extract_originators 9 | return nil if @contents.nil? 10 | people = [] 11 | parties = [] 12 | 13 | @contents.scan(ORIGINATORS).each do |m| 14 | m[0].gsub(' und ', ',').split(',').each do |person| 15 | person = person.gsub(/\p{Z}/, ' ') 16 | .gsub("\n", ' ') 17 | .gsub(/\s+/, ' ') 18 | .strip 19 | .gsub(/\p{Other}/, '') # invisible chars & private use unicode 20 | people << person unless person.blank? 21 | end 22 | 23 | party = m[1].gsub("\n", ' ') 24 | .strip 25 | .gsub(/\p{Other}/, '') 26 | parties << party 27 | end 28 | 29 | 30 | { people: people, parties: parties } 31 | end 32 | end -------------------------------------------------------------------------------- /app/extractors/pdf_extractor.rb: -------------------------------------------------------------------------------- 1 | class PDFExtractor 2 | 3 | MULTIPLE_RELATED_MINISTRIES = /((?:Staats)?[mM]inisterium.*?)\s?(?:(?:,|,\s+sowie|sowie|und\s+mit|und|mit)\s+dem\s+((?:Staats)?[mM]inisterium.+))+$/m 4 | 5 | def self.split_ministries(text) 6 | ministries = text.match(MULTIPLE_RELATED_MINISTRIES) 7 | return [text] unless ministries 8 | results = [] 9 | x = ministries.captures 10 | results << x.shift 11 | x.each do |related_ministry| 12 | results << split_ministries(related_ministry) 13 | end 14 | results.flatten 15 | end 16 | end -------------------------------------------------------------------------------- /app/extractors/rheinland_pfalz_pdf_extractor.rb: -------------------------------------------------------------------------------- 1 | class RheinlandPfalzPDFExtractor 2 | def initialize(paper) 3 | @contents = paper.contents 4 | end 5 | 6 | ORIGINATORS = /K\s*l\s*e\s*i\s*n\s*e\s*A\s*n\s*f\s*r\s*a\s*g\s*e\n\nde[rs]\s+Abgeordneten\s+(.+?)\n\nund\s+A\s*n\s*t\s*w\s*o\s*r\s*t/m 7 | 8 | def extract_originators 9 | return nil if @contents.nil? 10 | 11 | m = @contents.match(ORIGINATORS) 12 | return nil if m.nil? 13 | 14 | names = m[1].gsub(/\p{Z}/, ' ') 15 | .gsub("\n", ' ') 16 | .gsub(/\s+/, ' ') 17 | .strip 18 | .gsub(/\p{Other}/, '') 19 | 20 | NamePartyExtractor.new(names).extract 21 | end 22 | end -------------------------------------------------------------------------------- /app/extractors/schleswig_holstein_pdf_extractor.rb: -------------------------------------------------------------------------------- 1 | class SchleswigHolsteinPDFExtractor 2 | def initialize(paper) 3 | @contents = paper.contents 4 | end 5 | 6 | ANSWERERS = /und\s+Antwort\s+der\s+Landesregierung\s+.[ ]+(.+?)\s+\n\s+/m 7 | 8 | def extract_answerers 9 | return nil if @contents.blank? 10 | ministries = [] 11 | 12 | m = @contents.match(ANSWERERS) 13 | return nil if m.nil? 14 | 15 | # clean and normalize ministry name 16 | ministry = clean_text(m[1]).gsub(/([Mm])inister(in)?($|\s)/, '\1inister/in\3') 17 | ministries << ministry unless ministry.blank? 18 | 19 | { ministries: ministries } 20 | end 21 | 22 | private 23 | 24 | def clean_text(text) 25 | text.gsub(/\p{Z}/, ' ') 26 | .gsub("\n", ' ') 27 | .gsub(/\s+/, ' ') 28 | .strip 29 | .gsub(/\p{Other}/, '') # invisible chars & private use unicode 30 | end 31 | end -------------------------------------------------------------------------------- /app/extractors/thueringen_pdf_extractor.rb: -------------------------------------------------------------------------------- 1 | class ThueringenPDFExtractor 2 | def initialize(paper) 3 | @contents = paper.contents 4 | end 5 | 6 | ORIGINATORS = /A\s+n\s+f\s+r\s+a\s+g\s+e\s+de[rs]\s+Abgeordneten\s+([^\(]+)\s+\(([^\)]+)\)/m 7 | 8 | def extract_originators 9 | return nil if @contents.nil? 10 | 11 | m = @contents.match(ORIGINATORS) 12 | return nil if m.nil? 13 | 14 | parties = [] 15 | parties << clean_text(m[2]) 16 | 17 | ## not using person, because its only the last name. 18 | # people = [] 19 | # person = clean_text(m[1]) 20 | # people << person unless person.blank? 21 | 22 | { people: [], parties: parties } 23 | end 24 | 25 | private 26 | 27 | def clean_text(text) 28 | text.gsub(/\p{Z}/, ' ') 29 | .gsub("\n", ' ') 30 | .gsub(/\s+/, ' ') 31 | .strip 32 | .gsub(/\p{Other}/, '') # invisible chars & private use unicode 33 | end 34 | end -------------------------------------------------------------------------------- /app/helpers/body_helper.rb: -------------------------------------------------------------------------------- 1 | module BodyHelper 2 | def twitter_url(handle) 3 | "https://twitter.com/#{handle}" 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/helpers/legislative_term_helper.rb: -------------------------------------------------------------------------------- 1 | module LegislativeTermHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/ministry_helper.rb: -------------------------------------------------------------------------------- 1 | module MinistryHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/organization_helper.rb: -------------------------------------------------------------------------------- 1 | module OrganizationHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/review_helper.rb: -------------------------------------------------------------------------------- 1 | module ReviewHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/scraper_results_helper.rb: -------------------------------------------------------------------------------- 1 | module ScraperResultsHelper 2 | def distance_of_time_in_min_sec(from_time, to_time = nil) 3 | to_time = Time.now if to_time.nil? 4 | from_time = from_time.to_time if from_time.respond_to?(:to_time) 5 | to_time = to_time.to_time if to_time.respond_to?(:to_time) 6 | from_time, to_time = to_time, from_time if from_time > to_time 7 | distance_in_minutes = ((to_time - from_time) / 60.0).floor 8 | distance_in_seconds = (to_time - from_time).floor - (distance_in_minutes * 60) 9 | prefix_seconds = distance_in_seconds < 10 ? '0' : '' 10 | 11 | if distance_in_minutes > 60 12 | distance_in_hours = ((to_time - from_time) / 60.0 / 60.0).floor 13 | distance_in_minutes = distance_in_minutes - (distance_in_hours * 60) 14 | prefix_minutes = distance_in_minutes < 10 ? '0' : '' 15 | "#{distance_in_hours}:#{prefix_minutes}#{distance_in_minutes}:#{prefix_seconds}#{distance_in_seconds}" 16 | else 17 | "#{distance_in_minutes}:#{prefix_seconds}#{distance_in_seconds}" 18 | end 19 | end 20 | 21 | def weekend?(date) 22 | date.saturday? || date.sunday? 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/helpers/search_helper.rb: -------------------------------------------------------------------------------- 1 | module SearchHelper 2 | def facet_count(key, term) 3 | terms = @papers.aggs.try(:[], key).try(:fetch, 'buckets', nil) 4 | terms.find { |el| el['key'] == term }.try(:fetch, 'doc_count', nil) unless terms.nil? 5 | end 6 | 7 | def write_facet_count(key, term) 8 | count = facet_count(key, term) 9 | count.present? ? "(#{number_with_delimiter(count)})" : '' 10 | end 11 | 12 | # look into actionview / atom_feed_helper 13 | def search_feed_id(query, page = nil) 14 | schema_date = "2005" # The Atom spec copyright date 15 | "tag:#{request.host},#{schema_date}:#{request.fullpath.split('.')[0]},query:#{query}" + (!page.nil? ? ",page:#{page}" : '') 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/helpers/site_helper.rb: -------------------------------------------------------------------------------- 1 | module SiteHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | end -------------------------------------------------------------------------------- /app/jobs/contains_classified_information_job.rb: -------------------------------------------------------------------------------- 1 | class ContainsClassifiedInformationJob < PaperJob 2 | queue_as :meta 3 | 4 | def perform(paper) 5 | logger.info "Looking for classified marked Answers in Paper [#{paper.body.state} #{paper.full_reference}]" 6 | 7 | fail "No Text for Paper [#{paper.body.state} #{paper.full_reference}]" if paper.contents.blank? 8 | 9 | options = {} 10 | result = ClassifiedRecognizer.new(paper.contents, options).recognize 11 | 12 | reason = result[:groups].map(&:to_s).join(',') 13 | 14 | logger.info "Probability of classified information in Paper [#{paper.body.state} #{paper.full_reference}]: #{result[:probability]} (#{reason})" 15 | 16 | paper.contains_classified_information = result[:probability] >= 1 17 | paper.save 18 | end 19 | end -------------------------------------------------------------------------------- /app/jobs/contains_table_job.rb: -------------------------------------------------------------------------------- 1 | class ContainsTableJob < PaperJob 2 | queue_as :meta 3 | 4 | def perform(paper) 5 | logger.info "Looking for Tables in Paper [#{paper.body.state} #{paper.full_reference}]" 6 | 7 | fail "No Text for Paper [#{paper.body.state} #{paper.full_reference}]" if paper.contents.blank? 8 | 9 | options = {} 10 | options[:skip] = [:looks_like_table_values] if paper.body.state == 'SN' 11 | 12 | result = TableRecognizer.new(paper.contents, options).recognize 13 | 14 | reason = result[:groups].map(&:to_s).join(',') 15 | 16 | logger.info "Probability of Table(s) in Paper [#{paper.body.state} #{paper.full_reference}]: #{result[:probability]} (#{reason})" 17 | 18 | paper.contains_table = result[:probability] >= 1 19 | paper.save 20 | end 21 | end -------------------------------------------------------------------------------- /app/jobs/count_page_numbers_job.rb: -------------------------------------------------------------------------------- 1 | class CountPageNumbersJob < PaperJob 2 | queue_as :meta 3 | 4 | def perform(paper) 5 | logger.info "Counting Pages of the Paper [#{paper.body.state} #{paper.full_reference}]" 6 | 7 | # FIXME: not multi host capable 8 | unless File.exist? paper.local_path 9 | fail "No local copy of the PDF of Paper [#{paper.body.state} #{paper.full_reference}] found" 10 | end 11 | 12 | count = Docsplit.extract_length paper.local_path 13 | paper.page_count = count 14 | paper.save 15 | end 16 | end -------------------------------------------------------------------------------- /app/jobs/determine_paper_type_job.rb: -------------------------------------------------------------------------------- 1 | class DeterminePaperTypeJob < PaperJob 2 | queue_as :meta 3 | 4 | EXTRACTORS = { 5 | 'MV' => MvPDFHasAnswerExtractor, 6 | 'HH' => HamburgPDFHasAnswerExtractor, 7 | 'HE' => HessenPDFExtractor, 8 | 'SL' => SaarlandPDFExtractor 9 | } 10 | 11 | def perform(paper) 12 | return unless EXTRACTORS.keys.include?(paper.body.state) 13 | fail "No Text for Paper [#{paper.body.state} #{paper.full_reference}]" if paper.contents.blank? && paper.body.state != 'HE' 14 | ex = EXTRACTORS[paper.body.state].new(paper) 15 | 16 | paper.is_answer = ex.is_answer? if ex.respond_to? :is_answer? 17 | 18 | if ex.respond_to? :extract_doctype 19 | type = ex.extract_doctype 20 | paper.doctype = type unless type.nil? 21 | end 22 | 23 | paper.save 24 | end 25 | end -------------------------------------------------------------------------------- /app/jobs/notify_push_hub_body_feed_job.rb: -------------------------------------------------------------------------------- 1 | class NotifyPuSHHubBodyFeedJob < ApplicationJob 2 | queue_as :subscription 3 | 4 | def perform(body) 5 | return if Rails.configuration.x.push_hubs.blank? 6 | 7 | feed_url = Rails.application.routes.url_helpers.body_feed_url(body: body, format: :atom) 8 | urls = [feed_url, "#{feed_url}?feedformat=twitter"] 9 | 10 | hubs = Rails.configuration.x.push_hubs 11 | 12 | urls.each do |url| 13 | hubs.each do |hub| 14 | success = self.class.notify(hub, url) 15 | fail "Couldn't notify push hub \"#{hub}\" for url \"#{url}\"" unless success 16 | end 17 | end 18 | end 19 | 20 | def self.notify(hub, feed_url) 21 | return false if hub.blank? 22 | 23 | response = Excon.post( 24 | hub, 25 | body: URI.encode_www_form( 26 | 'hub.mode' => 'publish', 27 | 'hub.url' => feed_url 28 | ), 29 | headers: { 'Content-Type' => 'application/x-www-form-urlencoded' } 30 | ) 31 | 32 | [200, 204].include?(response.status) 33 | end 34 | end -------------------------------------------------------------------------------- /app/jobs/paper_job.rb: -------------------------------------------------------------------------------- 1 | class PaperJob < ApplicationJob 2 | # before_perform with a symbol doesn't get job as argument, so wrap it here 3 | before_perform ->(job) { skip_if_frozen(job) } 4 | 5 | def skip_if_frozen(job) 6 | paper = job.arguments.first 7 | if paper.frozen? 8 | logger.info "Skipping because Paper [#{paper.body.state} #{paper.full_reference}] is frozen" 9 | throw :abort 10 | end 11 | end 12 | end -------------------------------------------------------------------------------- /app/jobs/reimport_hessen_papers_pdf_job.rb: -------------------------------------------------------------------------------- 1 | class ReimportHessenPapersPDFJob < ApplicationJob 2 | queue_as :meta 3 | 4 | DISTANCE = 14.days 5 | 6 | def perform 7 | body = Body.find_by_state('HE') 8 | papers = Paper.where(body: body).where(['pdf_last_modified < ?', Date.today - DISTANCE]).where(contents: nil) 9 | 10 | papers.find_each do |paper| 11 | logger.info "Adding reimport of Paper [#{paper.body.state} #{paper.full_reference}] to queue" 12 | StorePaperPDFJob.perform_later(paper, force: true) 13 | end 14 | end 15 | end -------------------------------------------------------------------------------- /app/jobs/reimport_missing_pdf_job.rb: -------------------------------------------------------------------------------- 1 | class ReimportMissingPDFJob < ApplicationJob 2 | queue_as :meta 3 | 4 | def perform 5 | Paper.where(downloaded_at: nil).where.not(url: nil).find_each do |paper| 6 | logger.info "Adding reimport of Paper [#{paper.body.state} #{paper.full_reference}] to queue" 7 | StorePaperPDFJob.perform_later(paper, force: true) 8 | end 9 | end 10 | end -------------------------------------------------------------------------------- /app/jobs/reimport_papers_pdf_job.rb: -------------------------------------------------------------------------------- 1 | class ReimportPapersPDFJob < ApplicationJob 2 | queue_as :meta 3 | 4 | DISTANCE = 5.days 5 | STATES = { 6 | 'BT' => '%Korrektur\nDrucksache%', 7 | 'HB' => '%Vorläufige, unredigierte Fassung%' 8 | } 9 | 10 | def perform 11 | STATES.each do |state, like_query| 12 | body = Body.find_by_state(state) 13 | papers = Paper.where(body: body).where(['pdf_last_modified < ?', Date.today - DISTANCE]).where('contents LIKE ?', like_query) 14 | 15 | papers.find_each do |paper| 16 | logger.info "Adding reimport of Paper [#{paper.body.state} #{paper.full_reference}] to queue" 17 | StorePaperPDFJob.perform_later(paper, force: true) 18 | end 19 | end 20 | end 21 | end -------------------------------------------------------------------------------- /app/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbi5/kleineanfragen/1043e9f35620982abbfe5630e455e85e9b87741f/app/mailers/.keep -------------------------------------------------------------------------------- /app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | # default from: Rails.configuration.x.email_from # see config/application.rb 3 | layout 'mailer' 4 | end -------------------------------------------------------------------------------- /app/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbi5/kleineanfragen/1043e9f35620982abbfe5630e455e85e9b87741f/app/models/.keep -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | self.abstract_class = true 3 | end -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbi5/kleineanfragen/1043e9f35620982abbfe5630e455e85e9b87741f/app/models/concerns/.keep -------------------------------------------------------------------------------- /app/models/legislative_term.rb: -------------------------------------------------------------------------------- 1 | class LegislativeTerm < ApplicationRecord 2 | belongs_to :body 3 | 4 | validates :term, uniqueness: { scope: [:body_id] } 5 | 6 | has_many :papers, -> (lt) { where(legislative_term: lt.term) }, through: :body 7 | 8 | def to_param 9 | term 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/models/ministry.rb: -------------------------------------------------------------------------------- 1 | class Ministry < ApplicationRecord 2 | extend FriendlyId 3 | acts_as_paranoid 4 | include NkSyncable 5 | 6 | belongs_to :body 7 | 8 | friendly_id :name, use: :scoped, scope: :body 9 | 10 | has_many :paper_answerers, as: :answerer, dependent: :destroy 11 | has_many :papers, -> { answers }, through: :paper_answerers 12 | 13 | validates :name, uniqueness: { scope: [:body_id] } 14 | end 15 | -------------------------------------------------------------------------------- /app/models/organization.rb: -------------------------------------------------------------------------------- 1 | class Organization < ApplicationRecord 2 | extend FriendlyId 3 | acts_as_paranoid 4 | include NkSyncable 5 | 6 | has_and_belongs_to_many :members, class_name: 'Person' 7 | 8 | friendly_id :name, use: :slugged 9 | 10 | has_many :paper_originators, as: :originator, dependent: :destroy 11 | has_many :papers, -> { answers }, through: :paper_originators 12 | 13 | validates :name, uniqueness: true 14 | 15 | def nomenklatura_dataset 16 | 'ka-parties' 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /app/models/paper_answerer.rb: -------------------------------------------------------------------------------- 1 | class PaperAnswerer < ApplicationRecord 2 | belongs_to :paper 3 | belongs_to :answerer, polymorphic: true 4 | end 5 | -------------------------------------------------------------------------------- /app/models/paper_originator.rb: -------------------------------------------------------------------------------- 1 | class PaperOriginator < ApplicationRecord 2 | belongs_to :paper 3 | belongs_to :originator, polymorphic: true 4 | 5 | validates :originator_id, 6 | uniqueness: { scope: [:paper_id, :originator_type] }, 7 | unless: Proc.new { |o| o.paper_id.blank? || o.originator_type.blank? } 8 | end 9 | -------------------------------------------------------------------------------- /app/models/paper_redirect.rb: -------------------------------------------------------------------------------- 1 | class PaperRedirect < ApplicationRecord 2 | belongs_to :body 3 | belongs_to :paper 4 | 5 | validates :body, presence: true 6 | validates :legislative_term, presence: true 7 | validates :reference, presence: true, uniqueness: { scope: [:body_id, :legislative_term] } 8 | end 9 | -------------------------------------------------------------------------------- /app/models/paper_relation.rb: -------------------------------------------------------------------------------- 1 | class PaperRelation < ApplicationRecord 2 | REASON_REFERENCE_IN_TITLE = 'reference_in_title' 3 | REASON_REFERENCE_IN_TEXT = 'reference_in_text' 4 | 5 | belongs_to :paper 6 | belongs_to :other_paper, class_name: 'Paper' 7 | 8 | validates :paper, :other_paper, :reason, presence: true 9 | validates :other_paper, uniqueness: { scope: [:paper, :reason] } 10 | validate :does_not_link_itself 11 | 12 | private 13 | 14 | def does_not_link_itself 15 | if !paper.nil? && !other_paper.nil? && paper.id == other_paper.id 16 | errors.add(:other_paper, 'cannot be the same paper') 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/models/person.rb: -------------------------------------------------------------------------------- 1 | class Person < ApplicationRecord 2 | extend FriendlyId 3 | acts_as_paranoid 4 | include NkSyncable 5 | 6 | friendly_id :name, use: :slugged 7 | 8 | has_and_belongs_to_many :organizations 9 | 10 | has_many :paper_originators, as: :originator, dependent: :destroy 11 | has_many :papers, -> { answers }, through: :paper_originators 12 | 13 | validates :name, uniqueness: true 14 | 15 | def bodies 16 | Body.find papers.pluck(:body_id).uniq 17 | end 18 | 19 | def latest_body 20 | Body.find papers.order(created_at: :desc).limit(1).pluck(:body_id).first 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /app/models/report.rb: -------------------------------------------------------------------------------- 1 | class Report < Hash 2 | def initialize(created_at = nil) 3 | send(:[]=, :created_at, created_at.to_s) 4 | end 5 | end -------------------------------------------------------------------------------- /app/models/scraper_result.rb: -------------------------------------------------------------------------------- 1 | class ScraperResult < ApplicationRecord 2 | belongs_to :body 3 | 4 | def status 5 | if queued? 6 | 'waiting' 7 | elsif success? 8 | 'success' 9 | elsif running? 10 | 'running' 11 | else 12 | 'failure' 13 | end 14 | end 15 | 16 | def queued? 17 | !created_at.nil? && started_at.nil? && stopped_at.nil? 18 | end 19 | 20 | def running? 21 | !started_at.nil? && stopped_at.nil? 22 | end 23 | 24 | def got_new_papers? 25 | !new_papers.nil? && new_papers > 0 26 | end 27 | 28 | def instant? 29 | !scraper_class.blank? && scraper_class.include?('Instant') 30 | end 31 | 32 | def to_param 33 | self.class.hashids.encode(id) 34 | end 35 | 36 | def self.find_by_hash(hash) 37 | id = hashids.decode(hash).first 38 | fail 'Invalid id' if id.nil? 39 | find(id) 40 | end 41 | 42 | def self.hashids 43 | Hashids.new('ScraperResult', 5) 44 | end 45 | 46 | def as_json(*options) 47 | super(*options).merge(status: status, running: running?, id: to_param, instant: instant?) 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /app/scrapers/detail_scraper.rb: -------------------------------------------------------------------------------- 1 | class DetailScraper < Scraper 2 | def initialize(legislative_term, reference) 3 | @legislative_term = legislative_term 4 | @reference = reference 5 | @sleep = 1 6 | end 7 | 8 | def full_reference 9 | @legislative_term.to_s + '/' + @reference.to_s 10 | end 11 | end -------------------------------------------------------------------------------- /app/validators/email_validator.rb: -------------------------------------------------------------------------------- 1 | class EmailValidator < ActiveModel::EachValidator 2 | def validate_each(record, attribute, value) 3 | unless value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i 4 | record.errors.add(attribute, :invalid) 5 | end 6 | end 7 | end -------------------------------------------------------------------------------- /app/views/info/mitmachen.html.erb: -------------------------------------------------------------------------------- 1 | <% title 'Quellcode' %> 2 | 3 |
5 | kleineAnfragen ist freie Software (MIT-lizenziert) und kann von allen eingesehen und angepasst werden. Der Quellcode von kleineAnfragen befindet sich auf GitHub. 6 |
7 |8 | Bitte beachte, dass ein Fork bzw. Weiterbetrieb unter anderem Namen das grundlegende Problem, dass Parlamente die Daten nicht offen und in einem weiterverwendbaren Zustand bereitstellen, auch nicht löst. Zum aktuellen Zeitpunkt sind nun wirklich die Parlamente gefordert, endlich den längst überfälligen nächsten Schritt zu gehen. Mehr dazu auch auf der <%= link_to 'Seite zu Informationen und Hintergründen zur Abschaltung', obituary_url %>. 9 |
-------------------------------------------------------------------------------- /app/views/kaminari/_first_page.html.erb: -------------------------------------------------------------------------------- 1 |<%= number_with_delimiter @papers.total_count %> <%= t :kleine_anfragen, count: @papers.total_count %>
20 | <%= paginate @papers %> -------------------------------------------------------------------------------- /app/views/ministry/show.atom.builder: -------------------------------------------------------------------------------- 1 | atom_feed( 2 | language: 'de-DE', 3 | root_url: ministry_url(body: @body, ministry: @ministry), 4 | url: feed_url_with_current_page(@papers) 5 | ) do |feed| 6 | paginated_feed(feed, @papers) 7 | feed.title "kleineAnfragen: Anfragen beantwortet von #{@ministry.name}, #{@body.name}" 8 | feed.updated @papers.maximum(:updated_at) 9 | feed.author { |author| author.name 'kleineAnfragen' } 10 | 11 | @papers.each do |paper| 12 | render(partial: 'paper/paper', locals: { feed: feed, paper: paper }) 13 | end 14 | end -------------------------------------------------------------------------------- /app/views/organization/show.atom.builder: -------------------------------------------------------------------------------- 1 | atom_feed( 2 | language: 'de-DE', 3 | root_url: organization_url(body: @body, organization: @organization), 4 | url: feed_url_with_current_page(@papers) 5 | ) do |feed| 6 | paginated_feed(feed, @papers) 7 | feed.title "kleineAnfragen: Anfragen gestellt von #{@organization.name}, #{@body.name}" 8 | feed.updated @papers.maximum(:updated_at) 9 | feed.author { |author| author.name 'kleineAnfragen' } 10 | 11 | @papers.each do |paper| 12 | render(partial: 'paper/paper', locals: { feed: feed, paper: paper }) 13 | end 14 | end -------------------------------------------------------------------------------- /app/views/paper/_paper.atom.builder: -------------------------------------------------------------------------------- 1 | url = paper_url(body: paper.body, legislative_term: paper.legislative_term, paper: paper) 2 | feed.entry paper, published: paper.published_at, updated: paper.updated_at, url: url do |entry| 3 | title = paper.title 4 | 5 | if params[:feedformat] == 'twitter' 6 | title += " (#{paper.originators.map(&:name).join(', ')})" if title.size < 120 7 | end 8 | 9 | entry.title title 10 | paper.originators.each do |originator| 11 | entry.author do |author| 12 | author.name originator.name 13 | author.uri organization_url(paper.body, originator) if originator.is_a? Organization 14 | end 15 | end 16 | entry.category(term: paper.body.state, label: paper.body.name) 17 | entry.summary paper.description 18 | entry.link rel: 'alternate', type: 'text/plain', href: paper_url(body: paper.body, legislative_term: paper.legislative_term, paper: paper, format: :txt) 19 | end -------------------------------------------------------------------------------- /app/views/paper/recent.atom.builder: -------------------------------------------------------------------------------- 1 | atom_feed(language: 'de-DE', root_url: recent_url) do |feed| 2 | down_date = Date.new(2020, 12, 31) 3 | url = Rails.application.routes.url_helpers.obituary_url 4 | 5 | feed.title "kleineAnfragen: Anfragen der letzten Tage" 6 | feed.updated down_date 7 | feed.author { |author| author.name 'kleineAnfragen' } 8 | 9 | feed.entry Paper.new, published: down_date, updated: down_date, url: url do |entry| 10 | entry.title 'kleineAnfragen wurde abgeschaltet' 11 | entry.author do |author| 12 | author.name 'kleineAnfragen' 13 | end 14 | entry.summary 'kleineAnfragen wurde nach 5 Jahren Stillstand bei den Parlamenten zum 31.12.2020 abgeschaltet' 15 | end 16 | end -------------------------------------------------------------------------------- /app/views/paper/report_thanks.html.erb: -------------------------------------------------------------------------------- 1 | <% title "Problem melden: #{@paper.title} (#{@paper.full_reference}) — #{@body.name}, #{@legislative_term}. Wahlperiode" %> 2 | <% content_for :head do noindex end %> 3 | <% content_for :main_fullwidth_prepend do %> 4 | 13 | <% end %> 14 | 15 |Um nach dem Import von Anfragen fehlende Daten zu finden, gibt es folgende Seiten:
8 |<%= paper.try(:id) %>
12 |
13 | <% },
14 | after_meta: lambda { |paper| -%>
15 |
18 | <% } } -%>
19 | <%= @ministries.size %> Ministerien mit zu kurzem/langen Namen:
7 |<%= m.id %>
12 | <%= paper.try(:id) %>
13 | <% },
14 | prepend_meta: lambda { |paper| -%>
15 |
16 | <% paper.problems.each do |p| %>
17 | •
18 | <% end %>
19 |
20 | <% },
21 | after_meta: lambda { |paper| -%>
22 |
26 | <% } } -%>
27 | 17 | Solltest du aus Versehen diese E-Mail-Adresse blockiert haben und 18 | möchtest wieder Benachrichtigungen empfangen können, <%= link_to 'kontaktiere uns', info_kontakt_path %>. 19 |
-------------------------------------------------------------------------------- /app/views/subscription/error_invalid.html.erb: -------------------------------------------------------------------------------- 1 | <%- content_for :head do %><%= noindex() %><% end -%> 2 |15 | Wenn du dir sicher bist, dass deine eingegebene E-Mail-Adresse korrekt ist, 16 | und diese Meldung trotzdem erscheint <%= link_to 'kontaktiere uns', info_kontakt_path %>. 17 |
-------------------------------------------------------------------------------- /app/views/subscription/error_not_found.html.erb: -------------------------------------------------------------------------------- 1 | <% title 'E-Mail-Adresse bestätigen' -%> 2 | <%- content_for :head do %><%= noindex() %><% end -%> 3 | 4 |Prüfe, ob der Link aus der E-Mail vollständig in der Addressleiste sichtbar ist. Manchmal brechen E-Mail-Programme lange Links um.
-------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path('../config/application', __dir__) 3 | require_relative '../config/boot' 4 | require 'rails/commands' 5 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative '../config/boot' 3 | require 'rake' 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | require 'fileutils' 4 | include FileUtils 5 | 6 | # path to your application root. 7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 8 | 9 | def system!(*args) 10 | system(*args) || abort("\n== Command #{args} failed ==") 11 | end 12 | 13 | chdir APP_ROOT do 14 | # This script is a starting point to setup your application. 15 | # Add necessary setup steps to this file. 16 | 17 | puts '== Installing dependencies ==' 18 | system! 'gem install bundler --conservative' 19 | system('bundle check') || system!('bundle install') 20 | 21 | # Install JavaScript dependencies if using Yarn 22 | # system('bin/yarn') 23 | 24 | 25 | # puts "\n== Copying sample files ==" 26 | # unless File.exist?('config/database.yml') 27 | # cp 'config/database.yml.sample', 'config/database.yml' 28 | # end 29 | 30 | puts "\n== Preparing database ==" 31 | system! 'bin/rails db:setup' 32 | 33 | puts "\n== Removing old logs and tempfiles ==" 34 | system! 'bin/rails log:clear tmp:clear' 35 | 36 | puts "\n== Restarting application server ==" 37 | system! 'bin/rails restart' 38 | end 39 | -------------------------------------------------------------------------------- /bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This file loads spring without using Bundler, in order to be fast 4 | # It gets overwritten when you run the `spring binstub` command 5 | 6 | unless defined?(Spring) 7 | require "rubygems" 8 | require "bundler" 9 | 10 | if match = Bundler.default_lockfile.read.match(/^GEM$.*?^ (?: )*spring \((.*?)\)$.*?^$/m) 11 | ENV["GEM_PATH"] = ([Bundler.bundle_path.to_s] + Gem.path).join(File::PATH_SEPARATOR) 12 | ENV["GEM_HOME"] = "" 13 | Gem.paths = ENV 14 | 15 | gem "spring", match[1] 16 | require "spring/binstub" 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /bin/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | require 'fileutils' 4 | include FileUtils 5 | 6 | # path to your application root. 7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 8 | 9 | def system!(*args) 10 | system(*args) || abort("\n== Command #{args} failed ==") 11 | end 12 | 13 | chdir APP_ROOT do 14 | # This script is a way to update your development environment automatically. 15 | # Add necessary update steps to this file. 16 | 17 | puts '== Installing dependencies ==' 18 | system! 'gem install bundler --conservative' 19 | system('bundle check') || system!('bundle install') 20 | 21 | puts "\n== Updating database ==" 22 | system! 'bin/rails db:migrate' 23 | 24 | puts "\n== Removing old logs and tempfiles ==" 25 | system! 'bin/rails log:clear tmp:clear' 26 | 27 | puts "\n== Restarting application server ==" 28 | system! 'bin/rails restart' 29 | end 30 | -------------------------------------------------------------------------------- /bin/yarn: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | VENDOR_PATH = File.expand_path('..', __dir__) 3 | Dir.chdir(VENDOR_PATH) do 4 | begin 5 | exec "yarnpkg #{ARGV.join(" ")}" 6 | rescue Errno::ENOENT 7 | $stderr.puts "Yarn executable was not detected in the system." 8 | $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" 9 | exit 1 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Rails.application 5 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 2 | 3 | require 'bundler/setup' # Set up gems listed in the Gemfile. 4 | -------------------------------------------------------------------------------- /config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: async 6 | 7 | production: 8 | adapter: redis 9 | url: redis://localhost:6379/1 10 | channel_prefix: kleineanfragen_production 11 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative 'application' 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /config/fog.yml: -------------------------------------------------------------------------------- 1 | credentials: &credentials 2 | provider: AWS 3 | region: us-east-1 4 | aws_access_key_id: <%= ENV['S3_ACCESS_KEY'] %> 5 | aws_secret_access_key: <%= ENV['S3_SECRET_KEY'] %> 6 | path_style: true 7 | aws_signature_version: 4 8 | 9 | development: 10 | <<: *credentials 11 | endpoint: <%= ENV['S3_ENDPOINT'] || 'http://s3:9000' %> 12 | bucket: ka-dev 13 | # don't panic, these are fake (see docker-compose.yml) 14 | aws_access_key_id: AKIAIOSFODNN7EXAMPLE 15 | aws_secret_access_key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY 16 | 17 | test: 18 | provider: Local 19 | local_root: <%= Rails.root.join 'tmp' %> 20 | bucket: ka-test 21 | 22 | production: 23 | <<: *credentials 24 | host: s3.kleine-anfragen.de 25 | bucket: ka-prod -------------------------------------------------------------------------------- /config/initializers/abbyy.rb: -------------------------------------------------------------------------------- 1 | Abbyy.configure do |config| 2 | config.application_id = (ENV['ABBYY_APPLICATION_ID'] || '') 3 | config.password = (ENV['ABBYY_APPLICATION_PASSWORD'] || '') 4 | end -------------------------------------------------------------------------------- /config/initializers/active_job.rb: -------------------------------------------------------------------------------- 1 | # thanks, railties/lib/rails/application/bootstrap.rb 2 | logfile = File.open 'log/active_job.log', 'a' 3 | logfile.binmode 4 | logfile.sync = true # if true make sure every write flushes 5 | 6 | ajlogger = ActiveSupport::Logger.new(logfile) 7 | ajlogger.formatter = Lograge.formatter # Logger::Formatter.new 8 | 9 | ActiveJob::Base.logger = ActiveSupport::TaggedLogging.new(ajlogger) -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # ActiveSupport::Reloader.to_prepare do 4 | # ApplicationController.renderer.defaults.merge!( 5 | # http_host: 'example.org', 6 | # https: false 7 | # ) 8 | # end 9 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = '1.0' 5 | 6 | # Add additional assets to the asset load path. 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | # Add Yarn node_modules folder to the asset load path. 9 | #Rails.application.config.assets.paths << Rails.root.join('node_modules') 10 | 11 | # Precompile additional assets. 12 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. 13 | Rails.application.config.assets.precompile += %w( enhance.js ) 14 | # viewer 15 | Rails.application.config.assets.precompile += %w( viewer.js viewer.css ) 16 | # pdfjs 17 | Rails.application.config.assets.precompile += %w( *.png *.svg *.gif pdfjs/pdf.worker.js pdfjs/locale/*.properties ) 18 | # email 19 | Rails.application.config.assets.precompile += %w( email.css ) -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Specify a serializer for the signed and encrypted cookie jars. 4 | # Valid options are :json, :marshal, and :hybrid. 5 | Rails.application.config.action_dispatch.cookies_serializer = :json 6 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /config/initializers/global_id.rb: -------------------------------------------------------------------------------- 1 | # overriding GlobalID default locator to use unscoped model 2 | GlobalID::Locator.use :kleineanfragen do |gid| 3 | Kleineanfragen.const_get(gid.model_name).unscoped.find(gid.model_id) 4 | end -------------------------------------------------------------------------------- /config/initializers/grape_route_helpers_multiple_paths_monkeypatch.rb: -------------------------------------------------------------------------------- 1 | # 2 | # Monkeypatching grape-route-helpers to support multiple routes in one resource 3 | # 4 | # Source: 5 | # https://github.com/reprah/grape-route-helpers/pull/13 6 | # 7 | module GrapeRouteHelpers 8 | module AllRoutes 9 | def all_routes 10 | routes = subclasses.flat_map { |s| s.send(:prepare_routes) } 11 | routes.uniq do |r| 12 | if r.path.nil? 13 | [r.options] 14 | else 15 | [r.options, r.path] 16 | end 17 | end 18 | end 19 | end 20 | end -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | inflect.acronym 'PuSH' 16 | inflect.acronym 'OParl' 17 | end 18 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | Mime::Type.register 'text/plain', :txt 6 | # Mime::Type.register "application/pdf", :pdf # already in action_dispatch -------------------------------------------------------------------------------- /config/initializers/new_framework_defaults.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | # 3 | # This file contains migration options to ease your Rails 5.0 upgrade. 4 | # 5 | # Once upgraded flip defaults one by one to migrate to the new default. 6 | # 7 | # Read the Rails 5.0 release notes for more info on each option. 8 | 9 | # Enable per-form CSRF tokens. Previous versions had false. 10 | Rails.application.config.action_controller.per_form_csrf_tokens = true 11 | 12 | # Enable origin-checking CSRF mitigation. Previous versions had false. 13 | Rails.application.config.action_controller.forgery_protection_origin_check = true 14 | 15 | # Make Ruby 2.4 preserve the timezone of the receiver when calling `to_time`. 16 | # Previous versions had false. 17 | ActiveSupport.to_time_preserves_timezone = true 18 | 19 | # Require `belongs_to` associations by default. Previous versions had false. 20 | Rails.application.config.active_record.belongs_to_required_by_default = true 21 | 22 | # Configure SSL options to enable HSTS with subdomains. Previous versions had false. 23 | Rails.application.config.ssl_options = { hsts: { subdomains: true } } -------------------------------------------------------------------------------- /config/initializers/new_framework_defaults_5_1.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | # 3 | # This file contains migration options to ease your Rails 5.1 upgrade. 4 | # 5 | # Once upgraded flip defaults one by one to migrate to the new default. 6 | # 7 | # Read the Guide for Upgrading Ruby on Rails for more info on each option. 8 | 9 | # Make `form_with` generate non-remote forms. 10 | Rails.application.config.action_view.form_with_generates_remote_forms = false 11 | 12 | # Unknown asset fallback will return the path passed in when the given 13 | # asset is not present in the asset pipeline. 14 | # Rails.application.config.assets.unknown_asset_fallback = false 15 | -------------------------------------------------------------------------------- /config/initializers/overrides.rb: -------------------------------------------------------------------------------- 1 | # ActiveJob-Retry is calling `ActiveJob::Base.queue_adapter.name` 2 | # https://github.com/isaacseymour/activejob-retry/blob/33b9b5dc4c04cd8127e67c8682228b710c93189e/lib/active_job/retry.rb#L105 3 | # 4 | # this doesn't work, but we're also not using one of those "PROBLEMATIC_ADAPTERS" 5 | # so simply remove it. 6 | # 7 | module ActiveJob 8 | class Retry < Module 9 | def check_adapter! 10 | # do nothing. 11 | end 12 | end 13 | end -------------------------------------------------------------------------------- /config/initializers/searchkick.rb: -------------------------------------------------------------------------------- 1 | # overriding used es client to force typhoeus. 2 | # default: if patron and typhoeus are in the same application, patron is used 3 | # see: elasticsearch-transport-1.0.15, client.rb#__auto_detect_adapter 4 | Searchkick.client = 5 | Elasticsearch::Client.new( 6 | url: ENV.fetch('ELASTICSEARCH_URL', 'localhost:9200'), 7 | adapter: :typhoeus, 8 | transport_options: { 9 | request: { 10 | timeout: Searchkick.timeout 11 | } 12 | } 13 | ) 14 | 15 | # for tracking async reindexing 16 | Searchkick.redis = Redis.new 17 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Rails.application.config.session_store :cookie_store, key: '_kleineanfragen_session' 4 | Rails.application.config.session_store :disabled 5 | -------------------------------------------------------------------------------- /config/initializers/typhoeus.rb: -------------------------------------------------------------------------------- 1 | require 'typhoeus' 2 | require 'typhoeus/adapters/faraday' 3 | # Ethon.logger = Logger.new('/dev/null') 4 | 5 | Typhoeus::Config.user_agent = Rails.configuration.x.user_agent 6 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # 'true': 'foo' 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at http://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /config/sidekiq.yml: -------------------------------------------------------------------------------- 1 | --- 2 | :concurrency: 5 3 | production: 4 | :concurrency: 10 5 | :queues: 6 | - store 7 | - meta 8 | - import 9 | - subscription 10 | - mailers -------------------------------------------------------------------------------- /config/spring.rb: -------------------------------------------------------------------------------- 1 | %w( 2 | .ruby-version 3 | .rbenv-vars 4 | tmp/restart.txt 5 | tmp/caching-dev.txt 6 | ).each { |path| Spring.watch(path) } 7 | -------------------------------------------------------------------------------- /contrib/postgres-ka/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM postgres:9.4.11 2 | 3 | RUN apt-get update && apt-get install -y --no-install-recommends bzip2 && rm -rf /var/lib/apt/lists/* 4 | 5 | RUN mkdir -p /docker-entrypoint-initdb.d 6 | ADD ./create_db.sh /docker-entrypoint-initdb.d/create_db.sh 7 | RUN chmod +x /docker-entrypoint-initdb.d/*.sh -------------------------------------------------------------------------------- /contrib/postgres-ka/create_db.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-EOSQL 5 | CREATE DATABASE nomenklatura; 6 | GRANT ALL PRIVILEGES ON DATABASE nomenklatura TO kleineanfragen; 7 | 8 | CREATE DATABASE import; 9 | GRANT ALL PRIVILEGES ON DATABASE import TO kleineanfragen; 10 | EOSQL -------------------------------------------------------------------------------- /data/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbi5/kleineanfragen/1043e9f35620982abbfe5630e455e85e9b87741f/data/.gitkeep -------------------------------------------------------------------------------- /db/migrate/20140903101921_create_bodies.rb: -------------------------------------------------------------------------------- 1 | class CreateBodies < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :bodies do |t| 4 | t.text :name 5 | t.string :state, limit: 2 6 | t.text :website 7 | 8 | t.timestamps 9 | end 10 | 11 | add_index :bodies, :name, unique: true 12 | add_index :bodies, :state, unique: true 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /db/migrate/20140903102005_create_people.rb: -------------------------------------------------------------------------------- 1 | class CreatePeople < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :people do |t| 4 | t.text :name 5 | t.text :party 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20140903102233_create_papers.rb: -------------------------------------------------------------------------------- 1 | class CreatePapers < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :papers do |t| 4 | t.references :body, index: true 5 | t.integer :legislative_term 6 | t.text :reference 7 | t.text :title 8 | t.text :contents 9 | t.integer :page_count 10 | t.text :url 11 | t.date :published_at 12 | t.datetime :downloaded_at 13 | 14 | t.timestamps 15 | end 16 | 17 | add_index :papers, [:body_id, :legislative_term, :reference], unique: true 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /db/migrate/20140903144831_create_paper_originators.rb: -------------------------------------------------------------------------------- 1 | class CreatePaperOriginators < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :paper_originators do |t| 4 | t.references :paper, index: true 5 | t.references :originator, polymorphic: true, index: true 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /db/migrate/20140903151716_create_organizations.rb: -------------------------------------------------------------------------------- 1 | class CreateOrganizations < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :organizations do |t| 4 | t.string :name 5 | 6 | t.timestamps 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20140903152041_create_organizations_people.rb: -------------------------------------------------------------------------------- 1 | class CreateOrganizationsPeople < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :organizations_people, id: false do |t| 4 | t.references :organization 5 | t.references :person 6 | end 7 | 8 | add_index :organizations_people, [:organization_id, :person_id], 9 | name: "organizations_people_index", 10 | unique: true 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20140914200249_create_friendly_id_slugs.rb: -------------------------------------------------------------------------------- 1 | class CreateFriendlyIdSlugs < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :friendly_id_slugs do |t| 4 | t.string :slug, :null => false 5 | t.integer :sluggable_id, :null => false 6 | t.string :sluggable_type, :limit => 50 7 | t.string :scope 8 | t.datetime :created_at 9 | end 10 | add_index :friendly_id_slugs, :sluggable_id 11 | add_index :friendly_id_slugs, [:slug, :sluggable_type] 12 | add_index :friendly_id_slugs, [:slug, :sluggable_type, :scope], :unique => true 13 | add_index :friendly_id_slugs, :sluggable_type 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /db/migrate/20140914200415_add_slug_to_body.rb: -------------------------------------------------------------------------------- 1 | class AddSlugToBody < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :bodies, :slug, :string 4 | add_index :bodies, :slug, unique: true 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20140914212954_add_slug_to_paper.rb: -------------------------------------------------------------------------------- 1 | class AddSlugToPaper < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :papers, :slug, :string 4 | add_index :papers, :slug, unique: true 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20150110181315_create_ministries.rb: -------------------------------------------------------------------------------- 1 | class CreateMinistries < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :ministries do |t| 4 | t.references :body, index: true 5 | t.string :name 6 | 7 | t.timestamps null: false 8 | end 9 | add_foreign_key :ministries, :bodies 10 | add_index :ministries, [:body_id, :name], unique: true 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20150110181612_create_paper_answerers.rb: -------------------------------------------------------------------------------- 1 | class CreatePaperAnswerers < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :paper_answerers do |t| 4 | t.references :paper, index: true 5 | t.references :answerer, polymorphic: true, index: true 6 | end 7 | add_foreign_key :paper_answerers, :papers 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20150110211212_add_short_name_to_ministry.rb: -------------------------------------------------------------------------------- 1 | class AddShortNameToMinistry < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :ministries, :short_name, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20150111153145_add_slug_to_ministry.rb: -------------------------------------------------------------------------------- 1 | class AddSlugToMinistry < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :ministries, :slug, :string 4 | add_index :ministries, :slug, unique: true 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20150112071510_add_contains_table_to_paper.rb: -------------------------------------------------------------------------------- 1 | class AddContainsTableToPaper < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :papers, :contains_table, :boolean 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20150204073556_add_unique_index_to_people.rb: -------------------------------------------------------------------------------- 1 | class AddUniqueIndexToPeople < ActiveRecord::Migration[4.2] 2 | def change 3 | add_index :people, :name, unique: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20150204073802_add_unique_index_to_organizations.rb: -------------------------------------------------------------------------------- 1 | class AddUniqueIndexToOrganizations < ActiveRecord::Migration[4.2] 2 | def change 3 | add_index :organizations, :name, unique: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20150209082805_update_scope_in_slug_index_of_ministry.rb: -------------------------------------------------------------------------------- 1 | class UpdateScopeInSlugIndexOfMinistry < ActiveRecord::Migration[4.2] 2 | def up 3 | remove_index :ministries, :slug 4 | add_index :ministries, [:body_id, :slug], unique: true 5 | end 6 | 7 | def down 8 | remove_index :ministries, [:body_id, :slug] 9 | add_index :ministries, :slug, unique: true 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20150209092207_update_scope_in_slug_index_of_paper.rb: -------------------------------------------------------------------------------- 1 | class UpdateScopeInSlugIndexOfPaper < ActiveRecord::Migration[4.2] 2 | def up 3 | remove_index :papers, :slug 4 | add_index :papers, [:body_id, :legislative_term, :slug], unique: true 5 | end 6 | 7 | def down 8 | remove_index :papers, [:body_id, :legislative_term, :slug] 9 | add_index :papers, :slug, unique: true 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20150302150223_add_pdf_last_modified_to_paper.rb: -------------------------------------------------------------------------------- 1 | class AddPdfLastModifiedToPaper < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :papers, :pdf_last_modified, :datetime 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20150302160238_add_doc_type_to_paper.rb: -------------------------------------------------------------------------------- 1 | class AddDocTypeToPaper < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :papers, :doctype, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20150303140603_add_is_answer_to_paper.rb: -------------------------------------------------------------------------------- 1 | class AddIsAnswerToPaper < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :papers, :is_answer, :bool 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20150309155930_create_scraper_results.rb: -------------------------------------------------------------------------------- 1 | class CreateScraperResults < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :scraper_results do |t| 4 | t.references :body 5 | t.timestamp :started_at 6 | t.timestamp :stopped_at 7 | t.string :result 8 | 9 | t.timestamps null: false 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20150310121429_add_success_to_scraper_results.rb: -------------------------------------------------------------------------------- 1 | class AddSuccessToScraperResults < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :scraper_results, :success, :boolean 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20150310121456_add_message_to_scraper_results.rb: -------------------------------------------------------------------------------- 1 | class AddMessageToScraperResults < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :scraper_results, :message, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20150310121721_remove_result_from_scraper_results.rb: -------------------------------------------------------------------------------- 1 | class RemoveResultFromScraperResults < ActiveRecord::Migration[4.2] 2 | def change 3 | remove_column :scraper_results, :result, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20150310230538_rename_subscription_type_column.rb: -------------------------------------------------------------------------------- 1 | class RenameSubscriptionTypeColumn < ActiveRecord::Migration[4.2] 2 | def change 3 | rename_column :subscriptions, :type, :subtype 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20150417142732_add_frozen_at_to_paper.rb: -------------------------------------------------------------------------------- 1 | # Sometimes the scrapers/extractors produce weird data, because 2 | # the original source had corrupt data. In those cases we can 3 | # "freeze" the paper and correct the data manually. The freezing 4 | # allows to skip the paper in the jobs (see PaperJob) for the case 5 | # the paper is accessed again. 6 | class AddFrozenAtToPaper < ActiveRecord::Migration[4.2] 7 | def change 8 | add_column :papers, :frozen_at, :datetime 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20150518162148_add_paper_counts_to_scraper_results.rb: -------------------------------------------------------------------------------- 1 | class AddPaperCountsToScraperResults < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :scraper_results, :new_papers, :int 4 | add_column :scraper_results, :old_papers, :int 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20150818191716_add_use_mirror_for_download_to_body.rb: -------------------------------------------------------------------------------- 1 | class AddUseMirrorForDownloadToBody < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :bodies, :use_mirror_for_download, :boolean, default: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20160102120618_add_slug_to_organization.rb: -------------------------------------------------------------------------------- 1 | class AddSlugToOrganization < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :organizations, :slug, :string 4 | add_index :organizations, :slug, unique: true 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20160102161745_create_paper_relations.rb: -------------------------------------------------------------------------------- 1 | class CreatePaperRelations < ActiveRecord::Migration[4.2] 2 | def change 3 | create_table :paper_relations do |t| 4 | t.references :paper, index: true, foreign_key: true, null: false 5 | t.integer :other_paper_id, index: true, null: false 6 | t.string :reason 7 | 8 | t.timestamps null: false 9 | end 10 | 11 | add_foreign_key :paper_relations, :papers, column: :other_paper_id 12 | 13 | # has a shorter name, because the autogenerated one is over 63 chars 14 | add_index :paper_relations, [:paper_id, :other_paper_id, :reason], name: 'index_paper_relations_on_p_and_other_p_and_reason', unique: true 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /db/migrate/20160224172917_add_source_url_to_paper.rb: -------------------------------------------------------------------------------- 1 | class AddSourceUrlToPaper < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :papers, :source_url, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20160510165856_add_scraper_class_to_scraper_results.rb: -------------------------------------------------------------------------------- 1 | class AddScraperClassToScraperResults < ActiveRecord::Migration[4.2] 2 | def change 3 | add_column :scraper_results, :scraper_class, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20160821113555_create_legislative_terms.rb: -------------------------------------------------------------------------------- 1 | class CreateLegislativeTerms < ActiveRecord::Migration[5.0] 2 | def change 3 | create_table :legislative_terms do |t| 4 | t.references :body, foreign_key: true 5 | t.integer :term 6 | t.date :starts_at 7 | t.date :ends_at 8 | 9 | t.timestamps 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20160823062847_add_slug_to_person.rb: -------------------------------------------------------------------------------- 1 | class AddSlugToPerson < ActiveRecord::Migration[5.0] 2 | def change 3 | add_column :people, :slug, :string 4 | add_index :people, :slug, unique: true 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20160829063744_add_unique_term_to_legislative_terms.rb: -------------------------------------------------------------------------------- 1 | class AddUniqueTermToLegislativeTerms < ActiveRecord::Migration[5.0] 2 | def change 3 | add_index :legislative_terms, [:body_id, :term], unique: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20160901101627_add_deleted_at_to_papers.rb: -------------------------------------------------------------------------------- 1 | class AddDeletedAtToPapers < ActiveRecord::Migration[5.0] 2 | def change 3 | add_column :papers, :deleted_at, :datetime 4 | add_index :papers, :deleted_at 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20160901101643_add_deleted_at_to_people.rb: -------------------------------------------------------------------------------- 1 | class AddDeletedAtToPeople < ActiveRecord::Migration[5.0] 2 | def change 3 | add_column :people, :deleted_at, :datetime 4 | add_index :people, :deleted_at 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20160901101828_add_deleted_at_to_organizations.rb: -------------------------------------------------------------------------------- 1 | class AddDeletedAtToOrganizations < ActiveRecord::Migration[5.0] 2 | def change 3 | add_column :organizations, :deleted_at, :datetime 4 | add_index :organizations, :deleted_at 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20160901101842_add_deleted_at_to_ministries.rb: -------------------------------------------------------------------------------- 1 | class AddDeletedAtToMinistries < ActiveRecord::Migration[5.0] 2 | def change 3 | add_column :ministries, :deleted_at, :datetime 4 | add_index :ministries, :deleted_at 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20161208084214_add_wikidata_q_to_body.rb: -------------------------------------------------------------------------------- 1 | class AddWikidataQToBody < ActiveRecord::Migration[5.0] 2 | def change 3 | add_column :bodies, :wikidataq, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20161208084252_add_wikidata_q_to_legislative_term.rb: -------------------------------------------------------------------------------- 1 | class AddWikidataQToLegislativeTerm < ActiveRecord::Migration[5.0] 2 | def change 3 | add_column :legislative_terms, :wikidataq, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20161208084334_add_wikidata_q_to_person.rb: -------------------------------------------------------------------------------- 1 | class AddWikidataQToPerson < ActiveRecord::Migration[5.0] 2 | def change 3 | add_column :people, :wikidataq, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20170123110640_set_paper_is_answer_default_false.rb: -------------------------------------------------------------------------------- 1 | class SetPaperIsAnswerDefaultFalse < ActiveRecord::Migration[5.0] 2 | def change 3 | change_column :papers, :is_answer, :boolean, null: false, default: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20170416160058_create_paper_redirects.rb: -------------------------------------------------------------------------------- 1 | class CreatePaperRedirects < ActiveRecord::Migration[5.0] 2 | def change 3 | create_table :paper_redirects do |t| 4 | t.references :body, foreign_key: true, null: false 5 | t.integer :legislative_term 6 | t.text :reference 7 | t.references :paper, index: true, foreign_key: true, null: false 8 | 9 | t.timestamps 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20170923094320_add_contains_classified_information_to_paper.rb: -------------------------------------------------------------------------------- 1 | class AddContainsClassifiedInformationToPaper < ActiveRecord::Migration[5.0] 2 | def change 3 | add_column :papers, :contains_classified_information, :boolean 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20190127195351_add_site_message_to_body.rb: -------------------------------------------------------------------------------- 1 | class AddSiteMessageToBody < ActiveRecord::Migration[5.0] 2 | def change 3 | add_column :bodies, :site_message, :text 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20201231225202_drop_table_opt_in.rb: -------------------------------------------------------------------------------- 1 | class DropTableOptIn < ActiveRecord::Migration[5.1] 2 | def change 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /db/migrate/20201231225327_drop_table_subscription.rb: -------------------------------------------------------------------------------- 1 | class DropTableSubscription < ActiveRecord::Migration[5.1] 2 | def change 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /db/migrate/20201231234052_drop_table_email_blacklist.rb: -------------------------------------------------------------------------------- 1 | class DropTableEmailBlacklist < ActiveRecord::Migration[5.1] 2 | def change 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbi5/kleineanfragen/1043e9f35620982abbfe5630e455e85e9b87741f/lib/assets/.keep -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbi5/kleineanfragen/1043e9f35620982abbfe5630e455e85e9b87741f/lib/tasks/.keep -------------------------------------------------------------------------------- /log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbi5/kleineanfragen/1043e9f35620982abbfe5630e455e85e9b87741f/log/.keep -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbi5/kleineanfragen/1043e9f35620982abbfe5630e455e85e9b87741f/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbi5/kleineanfragen/1043e9f35620982abbfe5630e455e85e9b87741f/public/favicon.ico -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # User-agent: * 2 | # Disallow: / -------------------------------------------------------------------------------- /test/controllers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbi5/kleineanfragen/1043e9f35620982abbfe5630e455e85e9b87741f/test/controllers/.keep -------------------------------------------------------------------------------- /test/controllers/body_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class BodyControllerTest < ActionController::TestCase 4 | test 'should get show' do 5 | get :show, params: { 'body' => 'bayern' } 6 | assert_response :success 7 | assert_not_nil assigns(:body) 8 | assert_not_nil assigns(:terms) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/controllers/info_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class InfoControllerTest < ActionController::TestCase 4 | test 'should get daten' do 5 | get :daten 6 | assert_response :success 7 | end 8 | 9 | test 'should get kontakt' do 10 | get :kontakt 11 | assert_response :success 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/controllers/legislative_term_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class LegislativeTermControllerTest < ActionController::TestCase 4 | test 'should get show' do 5 | get :show, params: { 'body' => 'bayern', 'legislative_term' => 17 } 6 | assert_response :success 7 | assert_not_nil assigns(:body) 8 | assert_not_nil assigns(:legislative_term) 9 | assert_not_nil assigns(:papers) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/controllers/metrics_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class MetricsControllerTest < ActionController::TestCase 4 | test "should get show" do 5 | get :show 6 | assert_response :success 7 | end 8 | 9 | end 10 | -------------------------------------------------------------------------------- /test/controllers/ministry_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class MinistryControllerTest < ActionController::TestCase 4 | test 'should get show' do 5 | get :show, params: { 'body' => 'bayern', 'ministry' => 'staatsministerium-fuer-umwelt-und-verbraucherschutz' } 6 | assert_response :success 7 | assert_not_nil assigns(:body) 8 | assert_not_nil assigns(:ministry) 9 | assert_not_nil assigns(:papers) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/controllers/organization_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class OrganizationControllerTest < ActionController::TestCase 4 | test 'should get show' do 5 | papers(:paper_be_17_1000).originator_organizations << organizations(:someparty) 6 | 7 | get :show, params: { 'body' => 'berlin', 'organization' => 'someparty' } 8 | assert_response :success 9 | assert_not_nil assigns(:body) 10 | assert_not_nil assigns(:organization) 11 | assert_not_nil assigns(:papers) 12 | end 13 | end -------------------------------------------------------------------------------- /test/controllers/paper_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class PaperControllerTest < ActionController::TestCase 4 | test 'should get show' do 5 | get :show, params: { 'body' => 'bayern', 'legislative_term' => 17, 'paper' => '2000-noch-nicht-erfolgter-bericht-zur-aufklaerung-ueber-das-ausmass-und-die-handhabung-des-einsatzes-derivativer' } 6 | assert_response :success 7 | assert_not_nil assigns(:body) 8 | assert_not_nil assigns(:legislative_term) 9 | assert_not_nil assigns(:paper) 10 | end 11 | 12 | test 'should redirect if slug is wrong' do 13 | get :show, params: { 'body' => 'bayern', 'legislative_term' => 17, 'paper' => '2000-something' } 14 | assert_redirected_to paper_path(assigns(:body), assigns(:legislative_term), assigns(:paper)) 15 | end 16 | 17 | test 'should redirect if paper redirect exists' do 18 | pr = paper_redirects(:one) 19 | get :show, params: { 'body' => 'hessen', 'legislative_term' => 19, 'paper' => '1874-something' } 20 | assert_redirected_to paper_path(pr.paper.body, pr.paper.legislative_term, pr.paper) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /test/controllers/search_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class SearchControllerTest < ActionController::TestCase 4 | test 'should parse query' do 5 | query = 'single body:BY "quoted string" doctype:major' 6 | 7 | search = SearchController.parse_query(query) 8 | 9 | assert_equal 'single "quoted string"', search.term 10 | assert_equal({ body: ['BY'], doctype: ['major'] }, search.conditions) 11 | end 12 | 13 | test 'params_to_nice_query should support non-array param' do 14 | params = { 15 | body: 'BE' 16 | } 17 | query = SearchController.params_to_nice_query(params) 18 | assert_equal 'body:BE', query 19 | end 20 | 21 | test 'params_to_nice_query should support array param' do 22 | params = { 23 | body: ['BE', 'BY'] 24 | } 25 | query = SearchController.params_to_nice_query(params) 26 | assert_equal 'body:BE,BY', query 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /test/controllers/site_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class SiteControllerTest < ActionController::TestCase 4 | test 'should get index' do 5 | get :index 6 | assert_response :success 7 | assert_not_nil assigns(:bodies) 8 | assert_not_nil assigns(:papers) 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/extractors/hamburg_pdf_has_answer_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class HamburgPDFHasAnswerTest < ActiveSupport::TestCase 4 | ## 5 | # (Termin zur Beantwortung 6 | # gemäß 64 Absatz 1 GO LT: 7 | # 07.01.2015) 8 | test 'answered' do 9 | paper = Struct.new(:contents).new( 10 | " und Antwort des Senats \n" + 11 | " \n" + 12 | ' Betr.:' 13 | ) 14 | assert HamburgPDFHasAnswerExtractor.new(paper).is_answer? 15 | end 16 | 17 | test 'not answered' do 18 | paper = Struct.new(:contents).new( 19 | ' Betr.:' 20 | ) 21 | assert_not HamburgPDFHasAnswerExtractor.new(paper).is_answer? 22 | end 23 | 24 | test 'answered, missing colon' do 25 | paper = Struct.new(:contents).new( 26 | " und Antwort des Senats \n" + 27 | " \n" + 28 | ' Betr.' 29 | ) 30 | assert HamburgPDFHasAnswerExtractor.new(paper).is_answer? 31 | end 32 | 33 | test 'answered, missing dot' do 34 | paper = Struct.new(:contents).new( 35 | " und Antwort des Senats \n" + 36 | " \n" + 37 | ' Betr:' 38 | ) 39 | assert HamburgPDFHasAnswerExtractor.new(paper).is_answer? 40 | end 41 | end -------------------------------------------------------------------------------- /test/extractors/mv_pdf_has_answer_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class MvPDFHasAnswerTest < ActiveSupport::TestCase 4 | ## 5 | # (Termin zur Beantwortung 6 | # gemäß 64 Absatz 1 GO LT: 7 | # 07.01.2015) 8 | test 'has a due date attatched' do 9 | paper = Struct.new(:contents).new( 10 | " (Termin zur Beantwortung \n" + 11 | " gemäß 64 Absatz 1 GO LT: \n" + 12 | " \n" + 13 | ' 07.01.2015) ' 14 | ) 15 | assert_not MvPDFHasAnswerExtractor.new(paper).is_answer? 16 | end 17 | 18 | test 'no due date attatched' do 19 | paper = Struct.new(:contents).new( 20 | "Some\n" + 21 | "other\n" + 22 | 'stuff' 23 | ) 24 | assert MvPDFHasAnswerExtractor.new(paper).is_answer? 25 | end 26 | end -------------------------------------------------------------------------------- /test/extractors/saarland_pdf_extractor_doctype_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class SaarlandPDFExtractorDoctypeTest < ActiveSupport::TestCase 4 | test 'written interpellation' do 5 | paper = Struct.new(:contents).new( 6 | "\nA N T W O R T \n \n\nzu der \n \n\nAnfrage der Abgeordneten" + 7 | " Dr. Simone Peter (B90/Grüne) \n \n \nbetr.:") 8 | 9 | doctype = SaarlandPDFExtractor.new(paper).extract_doctype 10 | assert_equal Paper::DOCTYPE_WRITTEN_INTERPELLATION, doctype 11 | end 12 | 13 | test 'major interpellation' do 14 | paper = Struct.new(:contents).new( 15 | "\nSCHRIFTLICHE ANTWORT \n \n\nder Regierung des Saarlandes \n \n\n" + 16 | "zu der \n \n\nGroßen Anfrage der B90/Grüne-Landtagsfraktion ") 17 | 18 | doctype = SaarlandPDFExtractor.new(paper).extract_doctype 19 | assert_equal Paper::DOCTYPE_MAJOR_INTERPELLATION, doctype 20 | end 21 | end -------------------------------------------------------------------------------- /test/extractors/thueringen_pdf_extractor_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ThueringenPDFExtractorTest < ActiveSupport::TestCase 4 | def paper_with_contents(contents) 5 | Struct.new(:contents).new(contents) 6 | end 7 | 8 | test 'one party' do 9 | paper = paper_with_contents("K\n l e i n e A n f r a g e\n\ndes Abgeordneten Kießling (AfD)\nund\n\nA n t w o r t") 10 | 11 | originators = ThueringenPDFExtractor.new(paper).extract_originators 12 | 13 | assert_equal 1, originators[:parties].size 14 | assert_equal 'AfD', originators[:parties].first 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /test/fixtures/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbi5/kleineanfragen/1043e9f35620982abbfe5630e455e85e9b87741f/test/fixtures/.keep -------------------------------------------------------------------------------- /test/fixtures/bb/detail.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbi5/kleineanfragen/1043e9f35620982abbfe5630e455e85e9b87741f/test/fixtures/bb/detail.html -------------------------------------------------------------------------------- /test/fixtures/bb/detail_614.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robbi5/kleineanfragen/1043e9f35620982abbfe5630e455e85e9b87741f/test/fixtures/bb/detail_614.html -------------------------------------------------------------------------------- /test/fixtures/be/overview_13307.html: -------------------------------------------------------------------------------- 1 |Berliner Grillflächen nicht weiter reduzieren | ||
Schriftliche Anfrage | ||
Schlagwort: 6 | Grünanlage, 7 | Freizeit und Erholung | ||
9 | Stephan Lenz (CDU) 10 | 11 | Drucksache 17/13307 12 | vom 28.02.2014 13 | 14 | Folge 1 15 | Antwort 16 | 17 | SenStadtUm | ||